diff --git a/cli/tests/00-base.result b/cli/tests/00-base.result index 0709bb3a..6c2f6605 100644 --- a/cli/tests/00-base.result +++ b/cli/tests/00-base.result @@ -32,9 +32,9 @@ Asia/Shanghai 1 1.00 2 2.00 2 -[1,2,3] NULL (1,'ab') -NULL {'k1':'v1','k2':'v2'} (2,NULL) +[1,2,3] NULL "(1,""ab"")" +NULL "{""k1"":""v1"",""k2"":""v2""}" (2,NULL) 1 NULL 1 ab NULL v1 2 NULL -{'k1':'v1','k2':'v2'} [6162,78797A] ('[1,2]','2024-04-10') +"{""k1"":""v1"",""k2"":""v2""}" [6162,78797A] "([1,2],""2024-04-10"")" bye diff --git a/sql/Cargo.toml b/sql/Cargo.toml index ee9ff1b2..f9a27a54 100644 --- a/sql/Cargo.toml +++ b/sql/Cargo.toml @@ -35,5 +35,5 @@ memchr = "2.7" roaring = { version = "0.10.12", features = ["serde"] } jiff = { workspace = true } serde = { version = "1.0", default-features = false, features = ["derive"] } -serde_json = { version = "1.0", default-features = false, features = ["std"] } +serde_json = { version = "1.0", default-features = false, features = ["std", "raw_value"] } url = { version = "2.5", default-features = false } diff --git a/sql/src/value/format/display.rs b/sql/src/value/format/display.rs index ead355af..fb1da48f 100644 --- a/sql/src/value/format/display.rs +++ b/sql/src/value/format/display.rs @@ -64,16 +64,21 @@ impl Value { } Value::Number(n) => write!(f, "{n}"), Value::Binary(s) => write!(f, "{}", hex::encode_upper(s)), - Value::String(s) - | Value::Bitmap(s) - | Value::Variant(s) - | Value::Interval(s) - | Value::Geometry(s) - | Value::Geography(s) => { + Value::String(s) | Value::Bitmap(s) | Value::Interval(s) => { if raw { write!(f, "{s}") } else { - write!(f, "'{s}'") + write!(f, "\"{s}\"") + } + } + Value::Variant(s) => { + write!(f, "{s}") + } + Value::Geometry(s) | Value::Geography(s) => { + if raw || s.starts_with('{') { + write!(f, "{s}") + } else { + write!(f, "\"{s}\"") } } Value::Timestamp(dt) => { @@ -81,7 +86,7 @@ impl Value { if raw { write!(f, "{formatted}") } else { - write!(f, "'{formatted}'") + write!(f, "\"{formatted}\"") } } Value::TimestampTz(dt) => { @@ -89,7 +94,7 @@ impl Value { if raw { write!(f, "{formatted}") } else { - write!(f, "'{formatted}'") + write!(f, "\"{formatted}\"") } } Value::Date(i) => { @@ -98,7 +103,7 @@ impl Value { if raw { write!(f, "{d}") } else { - write!(f, "'{d}'") + write!(f, "\"{d}\"") } } Value::Array(vals) => { diff --git a/sql/src/value/format/result_encode.rs b/sql/src/value/format/result_encode.rs index 0590bc48..d030d539 100644 --- a/sql/src/value/format/result_encode.rs +++ b/sql/src/value/format/result_encode.rs @@ -81,14 +81,19 @@ impl Value { } }, Value::Binary(s) => bytes.extend_from_slice(hex::encode_upper(s).as_bytes()), - Value::String(s) - | Value::Bitmap(s) - | Value::Variant(s) - | Value::Interval(s) - | Value::Geometry(s) - | Value::Geography(s) => { + Value::String(s) | Value::Bitmap(s) | Value::Interval(s) => { Self::write_string(bytes, s, raw); } + Value::Variant(s) => { + bytes.extend_from_slice(s.as_bytes()); + } + Value::Geometry(s) | Value::Geography(s) => { + if s.starts_with('{') { + bytes.extend_from_slice(s.as_bytes()); + } else { + Self::write_string(bytes, s, raw); + } + } Value::Timestamp(dt) => { let s = dt.strftime(TIMESTAMP_FORMAT).to_string(); Self::write_string(bytes, &s, raw); @@ -150,9 +155,9 @@ impl Value { fn write_string(bytes: &mut Vec, string: &String, raw: bool) { if !raw { - bytes.push(b'\''); - write_quoted_string_min_escape(string.as_bytes(), bytes, b'\''); - bytes.push(b'\''); + bytes.push(b'"'); + write_quoted_string_min_escape(string.as_bytes(), bytes, b'"'); + bytes.push(b'"'); } else { bytes.extend_from_slice(string.as_bytes()); } @@ -202,7 +207,7 @@ fn write_quoted_string_min_escape(bytes: &[u8], buf: &mut Vec, quote: u8) { if start < i { buf.extend_from_slice(&bytes[start..i]); } - buf.push(quote); + buf.push(b'\\'); buf.push(quote); start = i + 1; } diff --git a/sql/src/value/string_decoder.rs b/sql/src/value/string_decoder.rs index 5058d5de..9d3c0151 100644 --- a/sql/src/value/string_decoder.rs +++ b/sql/src/value/string_decoder.rs @@ -24,6 +24,8 @@ use databend_client::schema::{DataType, DecimalDataType, DecimalSize, NumberData use ethnum::i256; use hex; use jiff::{civil::DateTime as JiffDateTime, tz::TimeZone, Zoned}; +use serde::Deserialize; +use serde_json::{value::RawValue, Deserializer}; use std::io::{BufRead, Cursor}; use std::str::FromStr; @@ -281,7 +283,9 @@ impl ValueDecoder { fn read_string>(&self, reader: &mut Cursor) -> Result { let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; + if reader.read_quoted_text(&mut buf, b'"').is_err() { + reader.read_quoted_text(&mut buf, b'\'')?; + } Ok(Value::String(unsafe { String::from_utf8_unchecked(buf) })) } @@ -295,7 +299,9 @@ impl ValueDecoder { fn read_date>(&self, reader: &mut Cursor) -> Result { let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; + if reader.read_quoted_text(&mut buf, b'"').is_err() { + reader.read_quoted_text(&mut buf, b'\'')?; + } let v = unsafe { std::str::from_utf8_unchecked(&buf) }; let days = NaiveDate::parse_from_str(v, "%Y-%m-%d")?.num_days_from_ce() - DAYS_FROM_CE; Ok(Value::Date(days)) @@ -303,14 +309,18 @@ impl ValueDecoder { fn read_timestamp>(&self, reader: &mut Cursor) -> Result { let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; + if reader.read_quoted_text(&mut buf, b'"').is_err() { + reader.read_quoted_text(&mut buf, b'\'')?; + } let v = unsafe { std::str::from_utf8_unchecked(&buf) }; parse_timestamp(v, &self.timezone) } fn read_timestamp_tz>(&self, reader: &mut Cursor) -> Result { let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; + if reader.read_quoted_text(&mut buf, b'"').is_err() { + reader.read_quoted_text(&mut buf, b'\'')?; + } let v = unsafe { std::str::from_utf8_unchecked(&buf) }; let t = Zoned::strptime(TIMESTAMP_TIMEZONE_FORMAT, v)?; Ok(Value::TimestampTz(t)) @@ -318,31 +328,49 @@ impl ValueDecoder { fn read_interval>(&self, reader: &mut Cursor) -> Result { let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; + if reader.read_quoted_text(&mut buf, b'"').is_err() { + reader.read_quoted_text(&mut buf, b'\'')?; + } Ok(Value::Interval(unsafe { String::from_utf8_unchecked(buf) })) } fn read_bitmap>(&self, reader: &mut Cursor) -> Result { let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; + if reader.read_quoted_text(&mut buf, b'"').is_err() { + reader.read_quoted_text(&mut buf, b'\'')?; + } Ok(Value::Bitmap(unsafe { String::from_utf8_unchecked(buf) })) } fn read_variant>(&self, reader: &mut Cursor) -> Result { - let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; - Ok(Value::Variant(unsafe { String::from_utf8_unchecked(buf) })) + if let Ok(val) = self.read_json(reader) { + Ok(Value::Variant(val)) + } else { + let mut buf = Vec::new(); + reader.read_quoted_text(&mut buf, b'\'')?; + Ok(Value::Variant(unsafe { String::from_utf8_unchecked(buf) })) + } } fn read_geometry>(&self, reader: &mut Cursor) -> Result { let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; + if reader.read_quoted_text(&mut buf, b'"').is_err() { + if let Ok(val) = self.read_json(reader) { + return Ok(Value::Variant(val)); + } + reader.read_quoted_text(&mut buf, b'\'')?; + } Ok(Value::Geometry(unsafe { String::from_utf8_unchecked(buf) })) } fn read_geography>(&self, reader: &mut Cursor) -> Result { let mut buf = Vec::new(); - reader.read_quoted_text(&mut buf, b'\'')?; + if reader.read_quoted_text(&mut buf, b'"').is_err() { + if let Ok(val) = self.read_json(reader) { + return Ok(Value::Variant(val)); + } + reader.read_quoted_text(&mut buf, b'\'')?; + } Ok(Value::Geography(unsafe { String::from_utf8_unchecked(buf) })) @@ -457,6 +485,15 @@ impl ValueDecoder { reader.must_ignore_byte(b')')?; Ok(Value::Tuple(vals)) } + + fn read_json>(&self, reader: &mut Cursor) -> Result { + let start = reader.position() as usize; + let data = reader.get_ref().as_ref(); + let mut deserializer = Deserializer::from_slice(&data[start..]); + let raw: Box = Box::::deserialize(&mut deserializer)?; + reader.set_position((start + raw.get().len()) as u64); + Ok(raw.to_string()) + } } fn parse_timestamp(ts_string: &str, tz: &TimeZone) -> Result {