From 53507eb098d80d54129fae20cd149b7cef34f32c Mon Sep 17 00:00:00 2001 From: fys Date: Thu, 23 Jul 2026 16:34:26 +0800 Subject: [PATCH] add more sqlness Signed-off-by: fys --- src/datatypes/src/vectors/json/array.rs | 27 +++++++- .../standalone/common/types/json/json2.result | 65 +++++++++++++++++-- .../standalone/common/types/json/json2.sql | 27 ++++++++ 3 files changed, 112 insertions(+), 7 deletions(-) diff --git a/src/datatypes/src/vectors/json/array.rs b/src/datatypes/src/vectors/json/array.rs index 424ff21a2de..8cfb8ef78b2 100644 --- a/src/datatypes/src/vectors/json/array.rs +++ b/src/datatypes/src/vectors/json/array.rs @@ -205,8 +205,8 @@ impl JsonArray<'_> { if self.inner.is_null(i) { return Ok(None); } - let value = self.try_get_value(i)?; - let value = match value { + let value = match self.try_get_value(i)? { + Value::Null => return Ok(None), Value::String(value) => value, value => value.to_string(), }; @@ -407,6 +407,29 @@ mod test { Ok(()) } + #[test] + fn test_cast_variant_to_utf8_view_preserves_json_null() -> Result<()> { + let encode = |json: &[u8]| jsonb::parse_value(json).unwrap().to_vec(); + let json_null = encode(b"null"); + let object = encode(br#"{"value":1}"#); + let string = encode(br#""text""#); + let variants: ArrayRef = Arc::new(BinaryArray::from(vec![ + Some(json_null.as_slice()), + Some(object.as_slice()), + Some(string.as_slice()), + None, + ])); + + let casted = JsonArray::from(&variants).try_cast(&DataType::Utf8View)?; + let casted = casted.as_string_view(); + assert!(casted.is_null(0)); + assert_eq!(casted.value(1), r#"{"value":1}"#); + assert_eq!(casted.value(2), "text"); + assert!(casted.is_null(3)); + + Ok(()) + } + #[test] fn test_align_json_array() -> Result<()> { struct TestCase { diff --git a/tests/cases/standalone/common/types/json/json2.result b/tests/cases/standalone/common/types/json/json2.result index 2f7cad05458..56e2f16af1e 100644 --- a/tests/cases/standalone/common/types/json/json2.result +++ b/tests/cases/standalone/common/types/json/json2.result @@ -218,15 +218,15 @@ select j.d from json2_table order by ts; +-----------------------------------+ | json_get(json2_table.j,Utf8("d")) | +-----------------------------------+ -| [{e: {f: 0.1, g: }}] | -| [{e: {f: 0.2, g: }}] | +| [{"e":{"f":0.1,"g":null}}] | +| [{"e":{"f":0.2,"g":null}}] | | | -| [{e: {f: , g: -0.4}}] | +| [{"e":{"f":null,"g":-0.4}}] | | | | | -| [{e: {g: -0.7}}] | +| [{"e":{"g":-0.7}}] | | | -| [{e: {g: -0.9}}] | +| [{"e":{"g":-0.9}}] | | | +-----------------------------------+ @@ -234,6 +234,61 @@ drop table json2_table; Affected Rows: 0 +-- A JSON null in a dynamically typed field must remain SQL NULL when the +-- field is projected to the default string type, rather than becoming "null". +create table json2_variant_null ( + ts timestamp time index, + j json2 +) with ( + 'append_mode' = 'true', + 'sst_format' = 'flat' +); + +Affected Rows: 0 + +insert into json2_variant_null values + (1, '{"payload":{"value":1}}'), + (2, '{"payload":"text"}'), + (3, '{"payload":null}'); + +Affected Rows: 3 + +select ts, j.payload, j.payload is null as payload_is_null +from json2_variant_null +order by ts; + ++-------------------------+------------------------------------------------+-----------------+ +| ts | json_get(json2_variant_null.j,Utf8("payload")) | payload_is_null | ++-------------------------+------------------------------------------------+-----------------+ +| 1970-01-01T00:00:00.001 | {"value":1} | false | +| 1970-01-01T00:00:00.002 | text | false | +| 1970-01-01T00:00:00.003 | | true | ++-------------------------+------------------------------------------------+-----------------+ + +admin flush_table('json2_variant_null'); + ++-----------------------------------------+ +| ADMIN flush_table('json2_variant_null') | ++-----------------------------------------+ +| 0 | ++-----------------------------------------+ + +select ts, j.payload, j.payload is null as payload_is_null +from json2_variant_null +order by ts; + ++-------------------------+------------------------------------------------+-----------------+ +| ts | json_get(json2_variant_null.j,Utf8("payload")) | payload_is_null | ++-------------------------+------------------------------------------------+-----------------+ +| 1970-01-01T00:00:00.001 | {"value":1} | false | +| 1970-01-01T00:00:00.002 | text | false | +| 1970-01-01T00:00:00.003 | | true | ++-------------------------+------------------------------------------------+-----------------+ + +drop table json2_variant_null; + +Affected Rows: 0 + create table json2_without_append_mode ( ts timestamp time index, j json2 diff --git a/tests/cases/standalone/common/types/json/json2.sql b/tests/cases/standalone/common/types/json/json2.sql index 0f1c484cddf..39390bf592a 100644 --- a/tests/cases/standalone/common/types/json/json2.sql +++ b/tests/cases/standalone/common/types/json/json2.sql @@ -73,6 +73,33 @@ select j.d from json2_table order by ts; drop table json2_table; +-- A JSON null in a dynamically typed field must remain SQL NULL when the +-- field is projected to the default string type, rather than becoming "null". +create table json2_variant_null ( + ts timestamp time index, + j json2 +) with ( + 'append_mode' = 'true', + 'sst_format' = 'flat' +); + +insert into json2_variant_null values + (1, '{"payload":{"value":1}}'), + (2, '{"payload":"text"}'), + (3, '{"payload":null}'); + +select ts, j.payload, j.payload is null as payload_is_null +from json2_variant_null +order by ts; + +admin flush_table('json2_variant_null'); + +select ts, j.payload, j.payload is null as payload_is_null +from json2_variant_null +order by ts; + +drop table json2_variant_null; + create table json2_without_append_mode ( ts timestamp time index, j json2