From f06bd7e6faecf2df8a71eada5364c669c04e3e8f Mon Sep 17 00:00:00 2001 From: elianddb Date: Mon, 10 Nov 2025 09:53:08 -0500 Subject: [PATCH 01/11] add unwrap on json convert and test --- enginetest/queries/json_scripts.go | 22 ++++++++++++++++++++++ sql/types/json.go | 15 +++++++++++++++ sql/types/jsontests/json_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/enginetest/queries/json_scripts.go b/enginetest/queries/json_scripts.go index cb981cd1c8..9b79d697df 100644 --- a/enginetest/queries/json_scripts.go +++ b/enginetest/queries/json_scripts.go @@ -15,6 +15,7 @@ package queries import ( + "github.com/dolthub/go-mysql-server/sql/plan" "github.com/dolthub/vitess/go/vt/sqlparser" "github.com/dolthub/go-mysql-server/sql" @@ -22,6 +23,27 @@ import ( ) var JsonScripts = []ScriptTest{ + { + Name: "TextStorage converts to JSON when using dolt wrapper", + SetUpScript: []string{ + "CREATE TABLE pages (id INT PRIMARY KEY, text_col TEXT, text_json JSON)", + "INSERT INTO pages VALUES (1, '{\"message\":\"hello\"}', NULL)", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "UPDATE pages SET text_json = text_col", + Expected: []sql.Row{ + {types.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}}, + }, + }, + { + Query: "SELECT text_json FROM pages", + Expected: []sql.Row{ + {types.MustJSON("{\"message\":\"hello\"}")}, + }, + }, + }, + }, { Name: "json_type scripts", Assertions: []ScriptTestAssertion{ diff --git a/sql/types/json.go b/sql/types/json.go index 44cb3434f9..5db9c81e00 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -68,6 +68,21 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in if err != nil { return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) } + // Text values may be stored in wrappers (e.g. Dolt's TextStorage), so unwrap to the raw string before decoding. + case sql.StringWrapper: + str, err := v.Unwrap(c) + if err != nil { + return nil, sql.OutOfRange, err + } + charsetMaxLength := sql.Collation_Default.CharacterSet().MaxLength() + length := int64(len(str)) * charsetMaxLength + if length > MaxJsonFieldByteLength { + return nil, sql.InRange, ErrLengthTooLarge.New(length, MaxJsonFieldByteLength) + } + err = json.Unmarshal([]byte(str), &doc) + if err != nil { + return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) + } case int8: return JSONDocument{Val: int64(v)}, sql.InRange, nil case int16: diff --git a/sql/types/jsontests/json_test.go b/sql/types/jsontests/json_test.go index e83aa4005d..1540106e0e 100644 --- a/sql/types/jsontests/json_test.go +++ b/sql/types/jsontests/json_test.go @@ -28,6 +28,34 @@ import ( "github.com/dolthub/go-mysql-server/sql/types" ) +type mockStringWrapper struct { + val string +} + +func (m mockStringWrapper) Unwrap(ctx context.Context) (string, error) { + return m.val, nil +} + +func (m mockStringWrapper) UnwrapAny(ctx context.Context) (interface{}, error) { + return m.val, nil +} + +func (m mockStringWrapper) IsExactLength() bool { + return false +} + +func (m mockStringWrapper) MaxByteLength() int64 { + return int64(len(m.val)) +} + +func (m mockStringWrapper) Compare(ctx context.Context, other interface{}) (int, bool, error) { + return 0, false, nil +} + +func (m mockStringWrapper) Hash() interface{} { + return m.val +} + func TestJsonCompare(t *testing.T) { RunJsonCompareTests(t, JsonCompareTests, func(t *testing.T, left, right interface{}) (interface{}, interface{}) { return ConvertToJson(t, left), ConvertToJson(t, right) @@ -58,6 +86,7 @@ func TestJsonConvert(t *testing.T) { {types.MustJSON(`{"field":"test"}`), types.MustJSON(`{"field":"test"}`), false}, {[]string{}, types.MustJSON(`[]`), false}, {[]string{`555-555-5555`}, types.MustJSON(`["555-555-5555"]`), false}, + {mockStringWrapper{val: `{"c": 1}`}, types.MustJSON(`{"c":1}`), false}, } for _, test := range tests { From 269719f591b6f3bb2352f3df3acb3257795e67ca Mon Sep 17 00:00:00 2001 From: elianddb Date: Mon, 10 Nov 2025 09:59:07 -0500 Subject: [PATCH 02/11] add issue link --- enginetest/queries/json_scripts.go | 1 + 1 file changed, 1 insertion(+) diff --git a/enginetest/queries/json_scripts.go b/enginetest/queries/json_scripts.go index 9b79d697df..24d00a0bd3 100644 --- a/enginetest/queries/json_scripts.go +++ b/enginetest/queries/json_scripts.go @@ -24,6 +24,7 @@ import ( var JsonScripts = []ScriptTest{ { + // https://github.com/dolthub/dolt/issues/10050 Name: "TextStorage converts to JSON when using dolt wrapper", SetUpScript: []string{ "CREATE TABLE pages (id INT PRIMARY KEY, text_col TEXT, text_json JSON)", From 1c26f64d7dbacbcfabde4111f05d8ebc4ac78982 Mon Sep 17 00:00:00 2001 From: elianddb Date: Mon, 10 Nov 2025 14:09:24 -0500 Subject: [PATCH 03/11] add conv val func --- sql/types/json.go | 61 ++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/sql/types/json.go b/sql/types/json.go index 5db9c81e00..e4ac1619b8 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -45,28 +45,49 @@ func (t JsonType) Compare(ctx context.Context, a interface{}, b interface{}) (in return CompareJSON(ctx, a, b) } +func convertJSONValue(v interface{}) (interface{}, sql.ConvertInRange, error) { + var data []byte + + switch x := v.(type) { + case []byte: + data = x + case string: + charsetMaxLength := sql.Collation_Default.CharacterSet().MaxLength() + length := int64(len(x)) * charsetMaxLength + if length > MaxJsonFieldByteLength { + return nil, sql.InRange, ErrLengthTooLarge.New(length, MaxJsonFieldByteLength) + } + data = []byte(x) + default: + return nil, sql.OutOfRange, sql.ErrInvalidJson.New("unsupported JSON input type") + } + + if int64(len(data)) > MaxJsonFieldByteLength { + return nil, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength) + } + + var doc interface{} + if err := json.Unmarshal(data, &doc); err != nil { + return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) + } + + return doc, sql.InRange, nil +} + // Convert implements Type interface. func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, inRange sql.ConvertInRange, err error) { switch v := v.(type) { case sql.JSONWrapper: return v, sql.InRange, nil case []byte: - if int64(len(v)) > MaxJsonFieldByteLength { - return nil, sql.InRange, ErrLengthTooLarge.New(len(v), MaxJsonFieldByteLength) - } - err = json.Unmarshal(v, &doc) + doc, inRange, err = convertJSONValue(v) if err != nil { - return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) + return nil, inRange, err } case string: - charsetMaxLength := sql.Collation_Default.CharacterSet().MaxLength() - length := int64(len(v)) * charsetMaxLength - if length > MaxJsonFieldByteLength { - return nil, sql.InRange, ErrLengthTooLarge.New(length, MaxJsonFieldByteLength) - } - err = json.Unmarshal([]byte(v), &doc) + doc, inRange, err = convertJSONValue(v) if err != nil { - return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) + return nil, inRange, err } // Text values may be stored in wrappers (e.g. Dolt's TextStorage), so unwrap to the raw string before decoding. case sql.StringWrapper: @@ -74,14 +95,9 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in if err != nil { return nil, sql.OutOfRange, err } - charsetMaxLength := sql.Collation_Default.CharacterSet().MaxLength() - length := int64(len(str)) * charsetMaxLength - if length > MaxJsonFieldByteLength { - return nil, sql.InRange, ErrLengthTooLarge.New(length, MaxJsonFieldByteLength) - } - err = json.Unmarshal([]byte(str), &doc) + doc, inRange, err = convertJSONValue(str) if err != nil { - return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) + return nil, inRange, err } case int8: return JSONDocument{Val: int64(v)}, sql.InRange, nil @@ -109,12 +125,9 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in // if |v| can be marshalled, it contains // a valid JSON document representation if b, berr := json.Marshal(v); berr == nil { - if int64(len(b)) > MaxJsonFieldByteLength { - return nil, sql.InRange, ErrLengthTooLarge.New(len(b), MaxJsonFieldByteLength) - } - err = json.Unmarshal(b, &doc) + doc, inRange, err = convertJSONValue(b) if err != nil { - return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) + return nil, inRange, err } } } From bb624e08e35d177ea9fdb04ef3cc46d5ebdd8652 Mon Sep 17 00:00:00 2001 From: elianddb Date: Mon, 10 Nov 2025 15:37:48 -0500 Subject: [PATCH 04/11] rm extra comp --- sql/types/json.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sql/types/json.go b/sql/types/json.go index e4ac1619b8..89f95839e8 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -47,22 +47,18 @@ func (t JsonType) Compare(ctx context.Context, a interface{}, b interface{}) (in func convertJSONValue(v interface{}) (interface{}, sql.ConvertInRange, error) { var data []byte - + var charsetMaxLength int64 = 1 switch x := v.(type) { case []byte: data = x case string: - charsetMaxLength := sql.Collation_Default.CharacterSet().MaxLength() - length := int64(len(x)) * charsetMaxLength - if length > MaxJsonFieldByteLength { - return nil, sql.InRange, ErrLengthTooLarge.New(length, MaxJsonFieldByteLength) - } data = []byte(x) + charsetMaxLength = sql.Collation_Default.CharacterSet().MaxLength() default: return nil, sql.OutOfRange, sql.ErrInvalidJson.New("unsupported JSON input type") } - if int64(len(data)) > MaxJsonFieldByteLength { + if int64(len(data))*charsetMaxLength > MaxJsonFieldByteLength { return nil, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength) } From c9d4544ec1e08cf85701cb39dd7605c17a5d74c5 Mon Sep 17 00:00:00 2001 From: elianddb Date: Mon, 10 Nov 2025 17:01:52 -0500 Subject: [PATCH 05/11] add func doc and amend default case --- sql/types/json.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sql/types/json.go b/sql/types/json.go index 89f95839e8..13a6e69cb1 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -45,7 +45,10 @@ func (t JsonType) Compare(ctx context.Context, a interface{}, b interface{}) (in return CompareJSON(ctx, a, b) } -func convertJSONValue(v interface{}) (interface{}, sql.ConvertInRange, error) { +// convertJSONValue parses JSON-encoded data if the input is a string or []byte, returning the resulting Go value. For +// other types, the value is returned as-is. The returned value is the raw, unwrapped JSON representation and is later +// wrapped in a JSONDocument by JsonType.Convert. +func convertJSONValue(v interface{}) (val interface{}, inRange sql.ConvertInRange, err error) { var data []byte var charsetMaxLength int64 = 1 switch x := v.(type) { @@ -55,19 +58,18 @@ func convertJSONValue(v interface{}) (interface{}, sql.ConvertInRange, error) { data = []byte(x) charsetMaxLength = sql.Collation_Default.CharacterSet().MaxLength() default: - return nil, sql.OutOfRange, sql.ErrInvalidJson.New("unsupported JSON input type") + return v, sql.InRange, nil } if int64(len(data))*charsetMaxLength > MaxJsonFieldByteLength { return nil, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength) } - var doc interface{} - if err := json.Unmarshal(data, &doc); err != nil { + if err := json.Unmarshal(data, &val); err != nil { return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) } - return doc, sql.InRange, nil + return val, sql.InRange, nil } // Convert implements Type interface. From 408fbe3a134ad7a7ebb95abb6a96e948273820e2 Mon Sep 17 00:00:00 2001 From: elianddb Date: Mon, 10 Nov 2025 17:08:43 -0500 Subject: [PATCH 06/11] add doc val --- sql/types/json.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sql/types/json.go b/sql/types/json.go index 13a6e69cb1..85d2d30b4e 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -74,16 +74,17 @@ func convertJSONValue(v interface{}) (val interface{}, inRange sql.ConvertInRang // Convert implements Type interface. func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, inRange sql.ConvertInRange, err error) { + docVal := v switch v := v.(type) { case sql.JSONWrapper: return v, sql.InRange, nil case []byte: - doc, inRange, err = convertJSONValue(v) + docVal, inRange, err = convertJSONValue(v) if err != nil { return nil, inRange, err } case string: - doc, inRange, err = convertJSONValue(v) + docVal, inRange, err = convertJSONValue(v) if err != nil { return nil, inRange, err } @@ -93,7 +94,7 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in if err != nil { return nil, sql.OutOfRange, err } - doc, inRange, err = convertJSONValue(str) + docVal, inRange, err = convertJSONValue(str) if err != nil { return nil, inRange, err } @@ -123,7 +124,7 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in // if |v| can be marshalled, it contains // a valid JSON document representation if b, berr := json.Marshal(v); berr == nil { - doc, inRange, err = convertJSONValue(b) + docVal, inRange, err = convertJSONValue(b) if err != nil { return nil, inRange, err } @@ -132,7 +133,7 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in if err != nil { return nil, sql.OutOfRange, err } - return JSONDocument{Val: doc}, sql.InRange, nil + return JSONDocument{Val: docVal}, sql.InRange, nil } // Equals implements the Type interface. From d814eb479b165583776aaa270e431b48c40d2464 Mon Sep 17 00:00:00 2001 From: elianddb Date: Mon, 10 Nov 2025 17:51:47 -0500 Subject: [PATCH 07/11] mv default to helper --- sql/types/json.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sql/types/json.go b/sql/types/json.go index 85d2d30b4e..dffa09a202 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -58,7 +58,11 @@ func convertJSONValue(v interface{}) (val interface{}, inRange sql.ConvertInRang data = []byte(x) charsetMaxLength = sql.Collation_Default.CharacterSet().MaxLength() default: - return v, sql.InRange, nil + // if |v| can be marshalled, it contains + // a valid JSON document representation + if b, berr := json.Marshal(v); berr == nil { + data = b + } } if int64(len(data))*charsetMaxLength > MaxJsonFieldByteLength { @@ -121,14 +125,7 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in case decimal.Decimal: return JSONDocument{Val: v}, sql.InRange, nil default: - // if |v| can be marshalled, it contains - // a valid JSON document representation - if b, berr := json.Marshal(v); berr == nil { - docVal, inRange, err = convertJSONValue(b) - if err != nil { - return nil, inRange, err - } - } + docVal, inRange, err = convertJSONValue(v) } if err != nil { return nil, sql.OutOfRange, err From 682309a3b0649c39b884d586e9e1d8f068c06a85 Mon Sep 17 00:00:00 2001 From: elianddb Date: Mon, 10 Nov 2025 22:53:08 +0000 Subject: [PATCH 08/11] [ga-format-pr] Run ./format_repo.sh to fix formatting --- enginetest/queries/json_scripts.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enginetest/queries/json_scripts.go b/enginetest/queries/json_scripts.go index 24d00a0bd3..3c0b09744e 100644 --- a/enginetest/queries/json_scripts.go +++ b/enginetest/queries/json_scripts.go @@ -15,9 +15,10 @@ package queries import ( - "github.com/dolthub/go-mysql-server/sql/plan" "github.com/dolthub/vitess/go/vt/sqlparser" + "github.com/dolthub/go-mysql-server/sql/plan" + "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" ) From a0204522d5e7262f0cbba1a4b7c78b0115fd2780 Mon Sep 17 00:00:00 2001 From: Elian Date: Mon, 10 Nov 2025 16:18:10 -0800 Subject: [PATCH 09/11] refactor to ret conversion instead --- sql/types/json.go | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/sql/types/json.go b/sql/types/json.go index dffa09a202..ecf4a44489 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -45,10 +45,9 @@ func (t JsonType) Compare(ctx context.Context, a interface{}, b interface{}) (in return CompareJSON(ctx, a, b) } -// convertJSONValue parses JSON-encoded data if the input is a string or []byte, returning the resulting Go value. For -// other types, the value is returned as-is. The returned value is the raw, unwrapped JSON representation and is later -// wrapped in a JSONDocument by JsonType.Convert. -func convertJSONValue(v interface{}) (val interface{}, inRange sql.ConvertInRange, err error) { +// convertJSONValue parses JSON-encoded data if the input is a string or []byte, returning the resulting JSONDocument. For +// other types, the value is returned if it can be marshalled. +func convertJSONValue(v interface{}) (interface{}, sql.ConvertInRange, error) { var data []byte var charsetMaxLength int64 = 1 switch x := v.(type) { @@ -62,46 +61,39 @@ func convertJSONValue(v interface{}) (val interface{}, inRange sql.ConvertInRang // a valid JSON document representation if b, berr := json.Marshal(v); berr == nil { data = b - } + } else { + return JSONDocument{Val: nil}, sql.InRange, nil + } } if int64(len(data))*charsetMaxLength > MaxJsonFieldByteLength { - return nil, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength) + return JSONDocument{Val: nil}, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength) } + var val interface{} if err := json.Unmarshal(data, &val); err != nil { - return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) + return JSONDocument{Val: nil}, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) } - return val, sql.InRange, nil + return JSONDocument{Val: val}, sql.InRange, nil } // Convert implements Type interface. -func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, inRange sql.ConvertInRange, err error) { - docVal := v +func (t JsonType) Convert(c context.Context, v interface{}) (interface{}, sql.ConvertInRange, error) { switch v := v.(type) { case sql.JSONWrapper: return v, sql.InRange, nil case []byte: - docVal, inRange, err = convertJSONValue(v) - if err != nil { - return nil, inRange, err - } + return convertJSONValue(v) case string: - docVal, inRange, err = convertJSONValue(v) - if err != nil { - return nil, inRange, err - } + return convertJSONValue(v) // Text values may be stored in wrappers (e.g. Dolt's TextStorage), so unwrap to the raw string before decoding. case sql.StringWrapper: str, err := v.Unwrap(c) if err != nil { return nil, sql.OutOfRange, err } - docVal, inRange, err = convertJSONValue(str) - if err != nil { - return nil, inRange, err - } + return convertJSONValue(str) case int8: return JSONDocument{Val: int64(v)}, sql.InRange, nil case int16: @@ -125,12 +117,8 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in case decimal.Decimal: return JSONDocument{Val: v}, sql.InRange, nil default: - docVal, inRange, err = convertJSONValue(v) - } - if err != nil { - return nil, sql.OutOfRange, err + return convertJSONValue(v) } - return JSONDocument{Val: docVal}, sql.InRange, nil } // Equals implements the Type interface. From ae13cf667a0773a308d486bba575b3726721fea6 Mon Sep 17 00:00:00 2001 From: elianddb Date: Tue, 11 Nov 2025 00:19:26 +0000 Subject: [PATCH 10/11] [ga-format-pr] Run ./format_repo.sh to fix formatting --- sql/types/json.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/types/json.go b/sql/types/json.go index ecf4a44489..d853b86f16 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -62,15 +62,15 @@ func convertJSONValue(v interface{}) (interface{}, sql.ConvertInRange, error) { if b, berr := json.Marshal(v); berr == nil { data = b } else { - return JSONDocument{Val: nil}, sql.InRange, nil - } + return JSONDocument{Val: nil}, sql.InRange, nil + } } if int64(len(data))*charsetMaxLength > MaxJsonFieldByteLength { return JSONDocument{Val: nil}, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength) } - var val interface{} + var val interface{} if err := json.Unmarshal(data, &val); err != nil { return JSONDocument{Val: nil}, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) } From fea27dfd503d8f142330a32062fb517cd8c71bb3 Mon Sep 17 00:00:00 2001 From: Elian Date: Mon, 10 Nov 2025 20:00:00 -0500 Subject: [PATCH 11/11] amend ret nil on err --- sql/types/json.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/types/json.go b/sql/types/json.go index d853b86f16..64ba15842c 100644 --- a/sql/types/json.go +++ b/sql/types/json.go @@ -62,17 +62,17 @@ func convertJSONValue(v interface{}) (interface{}, sql.ConvertInRange, error) { if b, berr := json.Marshal(v); berr == nil { data = b } else { - return JSONDocument{Val: nil}, sql.InRange, nil + return nil, sql.InRange, nil } } if int64(len(data))*charsetMaxLength > MaxJsonFieldByteLength { - return JSONDocument{Val: nil}, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength) + return nil, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength) } var val interface{} if err := json.Unmarshal(data, &val); err != nil { - return JSONDocument{Val: nil}, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) + return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error()) } return JSONDocument{Val: val}, sql.InRange, nil