diff --git a/Cargo.toml b/Cargo.toml index 6c8a588..15843bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,9 +35,9 @@ nom = "8.0.0" num-traits = "0.2.19" ordered-float = { version = "5.1.0", default-features = false } rand = { version = "0.9.2", features = ["small_rng"] } -ryu = "1.0" serde = "1.0" serde_json = { version = "1.0", default-features = false, features = ["std"] } +zmij = "1.0" [dev-dependencies] goldenfile = "1.8" diff --git a/src/core/databend/util.rs b/src/core/databend/util.rs index 1a0ca65..b24336a 100644 --- a/src/core/databend/util.rs +++ b/src/core/databend/util.rs @@ -604,7 +604,7 @@ impl ExtensionValue<'_> { writer.write_all(&[EXTENSION_TIMESTAMP_TZ])?; writer.write_all(&v.value.to_be_bytes())?; writer.write_all(&v.offset.to_be_bytes())?; - Ok(10) + Ok(13) } ExtensionValue::Interval(v) => { writer.write_all(&[EXTENSION_INTERVAL])?; @@ -645,11 +645,16 @@ impl ExtensionValue<'_> { ExtensionValue::Timestamp(Timestamp { value }) } EXTENSION_TIMESTAMP_TZ => { - if len != 9 { + if len != 9 && len != 12 { return Err(Error::InvalidJsonbNumber); } let value = i64::from_be_bytes(bytes[1..9].try_into().unwrap()); - let offset = i8::from_be_bytes(bytes[9..10].try_into().unwrap()); + let offset = if len == 9 { + let hours = i8::from_be_bytes(bytes[9..10].try_into().unwrap()); + (hours as i32) * 3_600 + } else { + i32::from_be_bytes(bytes[9..13].try_into().unwrap()) + }; ExtensionValue::TimestampTz(TimestampTz { offset, value }) } diff --git a/src/extension.rs b/src/extension.rs index f1af94f..196d62b 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -28,6 +28,7 @@ const MICROS_PER_HOUR: i64 = 60 * MICROS_PER_MINUTE; const MONTHS_PER_YEAR: i32 = 12; const TIMESTAMP_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.6f"; +const TIMESTAMP_TIMEZONE_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.6f %z"; /// Represents extended JSON value types that are not supported in standard JSON. /// @@ -80,8 +81,8 @@ pub struct Timestamp { /// Standard JSON has no native timezone-aware timestamp type. #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)] pub struct TimestampTz { - /// Timezone offset in hours from UTC - pub offset: i8, + /// Timezone offset in seconds from UTC + pub offset: i32, /// Microseconds since Unix epoch (January 1, 1970 00:00:00 UTC) pub value: i64, } @@ -149,58 +150,49 @@ impl Display for TimestampTz { nanos = 0; } let ts = jiff::Timestamp::new(secs, nanos as i32).unwrap(); - let tz = Offset::constant(self.offset).to_time_zone(); + let tz_offset = Offset::from_seconds(self.offset).expect("invalid timezone offset seconds"); + let tz = tz_offset.to_time_zone(); let zoned = ts.to_zoned(tz); - write!(f, "{}", strtime::format(TIMESTAMP_FORMAT, &zoned).unwrap()) + write!( + f, + "{}", + strtime::format(TIMESTAMP_TIMEZONE_FORMAT, &zoned).unwrap() + ) } } impl Display for Interval { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - let mut date_parts = vec![]; + let mut wrote_date_part = false; let years = self.months / MONTHS_PER_YEAR; let months = self.months % MONTHS_PER_YEAR; - match years.cmp(&1) { - Ordering::Equal => { - date_parts.push((years, "year")); - } - Ordering::Greater => { - date_parts.push((years, "years")); - } - _ => {} - } - match months.cmp(&1) { - Ordering::Equal => { - date_parts.push((months, "month")); - } - Ordering::Greater => { - date_parts.push((months, "months")); - } - _ => {} - } - match self.days.cmp(&1) { - Ordering::Equal => { - date_parts.push((self.days, "day")); - } - Ordering::Greater => { - date_parts.push((self.days, "days")); - } - _ => {} - } - if !date_parts.is_empty() { - for (i, (val, name)) in date_parts.into_iter().enumerate() { - if i > 0 { + + let mut write_component = |value: i32, singular: &str, plural: &str| -> std::fmt::Result { + if value != 0 { + if wrote_date_part { write!(f, " ")?; } - write!(f, "{} {}", val, name)?; - } - if self.micros != 0 { - write!(f, " ")?; + let abs_val = value.abs(); + let unit = if abs_val == 1 { singular } else { plural }; + if value < 0 { + write!(f, "-{} {}", abs_val, unit)?; + } else { + write!(f, "{} {}", abs_val, unit)?; + } + wrote_date_part = true; } - } + Ok(()) + }; + + write_component(years, "year", "years")?; + write_component(months, "month", "months")?; + write_component(self.days, "day", "days")?; if self.micros != 0 { + if wrote_date_part { + write!(f, " ")?; + } let mut micros = self.micros; if micros < 0 { write!(f, "-")?; @@ -221,7 +213,7 @@ impl Display for Interval { if micros != 0 { write!(f, ".{:06}", micros)?; } - } else if self.months == 0 && self.days == 0 { + } else if !wrote_date_part { write!(f, "00:00:00")?; } Ok(()) diff --git a/src/functions/scalar.rs b/src/functions/scalar.rs index a79e079..b5c19a3 100644 --- a/src/functions/scalar.rs +++ b/src/functions/scalar.rs @@ -1783,7 +1783,7 @@ impl RawJsonb<'_> { /// use jsonb::Value; /// /// // TimestampTz value - /// let timestamp_tz_value = Value::TimestampTz(TimestampTz { offset: 8, value: 1760140800000000 }); + /// let timestamp_tz_value = Value::TimestampTz(TimestampTz { offset: 8 * 3600, value: 1760140800000000 }); /// let buf = timestamp_tz_value.to_vec(); /// let raw_jsonb = RawJsonb::new(&buf); /// assert!(raw_jsonb.is_timestamp_tz().unwrap()); @@ -1830,10 +1830,10 @@ impl RawJsonb<'_> { /// use jsonb::Value; /// /// // TimestampTz value - /// let timestamp_tz_value = Value::TimestampTz(TimestampTz { offset: 8, value: 1760140800000000 }); + /// let timestamp_tz_value = Value::TimestampTz(TimestampTz { offset: 8 * 3600, value: 1760140800000000 }); /// let buf = timestamp_tz_value.to_vec(); /// let raw_jsonb = RawJsonb::new(&buf); - /// assert_eq!(raw_jsonb.as_timestamp_tz().unwrap(), Some(TimestampTz { offset: 8, value: 1760140800000000 })); + /// assert_eq!(raw_jsonb.as_timestamp_tz().unwrap(), Some(TimestampTz { offset: 8 * 3600, value: 1760140800000000 })); /// ``` pub fn as_timestamp_tz(&self) -> Result> { let jsonb_item = JsonbItem::from_raw_jsonb(*self)?; diff --git a/src/number.rs b/src/number.rs index 93b11e3..8058b68 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1174,7 +1174,7 @@ impl Display for Number { write!(f, "{}", s) } Number::Float64(v) => { - let mut buffer = ryu::Buffer::new(); + let mut buffer = zmij::Buffer::new(); let s = buffer.format(*v); write!(f, "{}", s) } diff --git a/tests/it/decode.rs b/tests/it/decode.rs index ce6b5cb..b54abd2 100644 --- a/tests/it/decode.rs +++ b/tests/it/decode.rs @@ -245,10 +245,18 @@ fn test_decode_extension() { value: 1760140800000000, }), ), + // Backward-compatible implementation with offset as int8 hours ( b"\x20\0\0\0\x60\0\0\x0a\x30\0\x06\x40\xd6\xb7\x23\x80\0\x08".to_vec(), Value::TimestampTz(TimestampTz { - offset: 8, + offset: 8 * 3600, + value: 1760140800000000, + }), + ), + ( + b"\x20\0\0\0\x60\0\0\x0d\x30\0\x06\x40\xd6\xb7\x23\x80\0\0\0\x70\x80".to_vec(), + Value::TimestampTz(TimestampTz { + offset: 8 * 3600, value: 1760140800000000, }), ), diff --git a/tests/it/encode.rs b/tests/it/encode.rs index aa16386..6e61d90 100644 --- a/tests/it/encode.rs +++ b/tests/it/encode.rs @@ -177,11 +177,11 @@ fn test_encode_array() { Value::Binary(&[100, 101, 102, 103]), Value::Date(Date {value: 20381 }), Value::Timestamp(Timestamp { value: 1540230120000000 }), - Value::TimestampTz(TimestampTz { offset: 8, value: 1670389100000000 }), + Value::TimestampTz(TimestampTz { offset: 8 * 3600, value: 1670389100000000 }), Value::Interval(Interval { months: 2, days: 10, micros: 500000000 }), Value::Number(Number::Decimal256(Decimal256 { scale: 2, value: I256::new(1234) })), ]).to_vec(), - b"\x80\0\0\x07\x30\0\0\0\x60\0\0\x05\x60\0\0\x05\x60\0\0\x09\x60\0\0\x0A\x60\0\0\x11\x20\0\0\x22\0\x64\x65\x66\x67\x10\0\0\x4F\x9D\x20\0\x05\x78\xD4\xC5\x2C\xCA\0\x30\0\x05\xEF\x35\xC4\xF1\x33\0\x08\x40\0\0\0\x02\0\0\0\x0A\0\0\0\0\x1D\xCD\x65\0\x70\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\xD2\x02", + b"\x80\0\0\x07\x30\0\0\0\x60\0\0\x05\x60\0\0\x05\x60\0\0\x09\x60\0\0\x0D\x60\0\0\x11\x20\0\0\x22\0\x64\x65\x66\x67\x10\0\0\x4F\x9D\x20\0\x05\x78\xD4\xC5\x2C\xCA\0\x30\0\x05\xEF\x35\xC4\xF1\x33\0\0\0\x70\x80\x40\0\0\0\x02\0\0\0\x0A\0\0\0\0\x1D\xCD\x65\0\x70\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\xD2\x02", ); } @@ -207,7 +207,7 @@ fn test_encode_object() { obj2.insert( "k5".to_string(), Value::TimestampTz(TimestampTz { - offset: 8, + offset: 8 * 3600, value: 1670389100000000, }), ); @@ -229,7 +229,7 @@ fn test_encode_object() { assert_eq!( &Value::Object(obj2).to_vec(), - b"\x40\0\0\x07\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x60\0\0\x05\x60\0\0\x05\x60\0\0\x09\x60\0\0\x0A\x60\0\0\x11\x20\0\0\x22\x6B\x31\x6B\x32\x6B\x33\x6B\x34\x6B\x35\x6B\x36\x6B\x37\x76\x31\0\xC8\xC9\xCA\xCB\x10\0\0\x4F\x9D\x20\0\x05\x78\xD4\xC5\x2C\xCA\0\x30\0\x05\xEF\x35\xC4\xF1\x33\0\x08\x40\0\0\0\x02\0\0\0\x0A\0\0\0\0\x1D\xCD\x65\0\x70\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\xD2\x02" + b"\x40\0\0\x07\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x10\0\0\x02\x60\0\0\x05\x60\0\0\x05\x60\0\0\x09\x60\0\0\x0D\x60\0\0\x11\x20\0\0\x22\x6B\x31\x6B\x32\x6B\x33\x6B\x34\x6B\x35\x6B\x36\x6B\x37\x76\x31\0\xC8\xC9\xCA\xCB\x10\0\0\x4F\x9D\x20\0\x05\x78\xD4\xC5\x2C\xCA\0\x30\0\x05\xEF\x35\xC4\xF1\x33\0\0\0\x70\x80\x40\0\0\0\x02\0\0\0\x0A\0\0\0\0\x1D\xCD\x65\0\x70\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\xD2\x02" ); } @@ -252,11 +252,11 @@ fn test_encode_extension() { ); assert_eq!( Value::TimestampTz(TimestampTz { - offset: 8, + offset: 8 * 3600, value: 1760140800000000 }) .to_vec(), - b"\x20\0\0\0\x60\0\0\x0a\x30\0\x06\x40\xd6\xb7\x23\x80\0\x08" + b"\x20\0\0\0\x60\0\0\x0d\x30\0\x06\x40\xd6\xb7\x23\x80\0\0\0\x70\x80" ); assert_eq!( Value::Interval(Interval { diff --git a/tests/it/functions.rs b/tests/it/functions.rs index d3117f9..48c61ce 100644 --- a/tests/it/functions.rs +++ b/tests/it/functions.rs @@ -826,19 +826,32 @@ fn test_to_string() { let extension_sources = vec![ (Value::Binary(&[97, 98, 99]), r#""616263""#), + (Value::Binary(&[]), r#""""#), (Value::Date(Date { value: 90570 }), r#""2217-12-22""#), + (Value::Date(Date { value: -1 }), r#""1969-12-31""#), ( Value::Timestamp(Timestamp { value: 190390000000, }), r#""1970-01-03 04:53:10.000000""#, ), + ( + Value::Timestamp(Timestamp { value: 0 }), + r#""1970-01-01 00:00:00.000000""#, + ), ( Value::TimestampTz(TimestampTz { - offset: 8, + offset: 8 * 3600, value: 190390000000, }), - r#""1970-01-03 12:53:10.000000""#, + r#""1970-01-03 12:53:10.000000 +0800""#, + ), + ( + Value::TimestampTz(TimestampTz { + offset: 90 * 60, + value: 0, + }), + r#""1970-01-01 01:30:00.000000 +0130""#, ), ( Value::Interval(Interval { @@ -848,6 +861,38 @@ fn test_to_string() { }), r#""10 months 20 days 00:05:00""#, ), + ( + Value::Interval(Interval { + months: -14, + days: -3, + micros: -90000000, + }), + r#""-1 year -2 months -3 days -00:01:30""#, + ), + ( + Value::Interval(Interval { + months: -25, + days: -1, + micros: 0, + }), + r#""-2 years -1 month -1 day""#, + ), + ( + Value::Interval(Interval { + months: 0, + days: -2, + micros: 5_000_000, + }), + r#""-2 days 00:00:05""#, + ), + ( + Value::Interval(Interval { + months: 0, + days: 0, + micros: 0, + }), + r#""00:00:00""#, + ), ( Value::Number(Number::Decimal128(Decimal128 { scale: 2, @@ -855,6 +900,13 @@ fn test_to_string() { })), r#"12.34"#, ), + ( + Value::Number(Number::Decimal64(Decimal64 { + scale: 2, + value: -765, + })), + r#"-7.65"#, + ), ( Value::Number(Number::Decimal256(Decimal256 { scale: 2, @@ -870,7 +922,7 @@ fn test_to_string() { value: 190390000000, }), Value::TimestampTz(TimestampTz { - offset: 8, + offset: 8 * 3600, value: 190390000000, }), Value::Interval(Interval { @@ -887,7 +939,7 @@ fn test_to_string() { value: I256::new(981724), })), ]), - r#"["616263","2217-12-22","1970-01-03 04:53:10.000000","1970-01-03 12:53:10.000000","10 months 20 days 00:05:00",12.34,9817.24]"#, + r#"["616263","2217-12-22","1970-01-03 04:53:10.000000","1970-01-03 12:53:10.000000 +0800","10 months 20 days 00:05:00",12.34,9817.24]"#, ), ( Value::Object(BTreeMap::from([ @@ -902,7 +954,7 @@ fn test_to_string() { ( "k4".to_string(), Value::TimestampTz(TimestampTz { - offset: 8, + offset: 8 * 3600, value: 190390000000, }), ), @@ -929,7 +981,7 @@ fn test_to_string() { })), ), ])), - r#"{"k1":"616263","k2":"2217-12-22","k3":"1970-01-03 04:53:10.000000","k4":"1970-01-03 12:53:10.000000","k5":"10 months 20 days 00:05:00","k6":12.34,"k7":9817.24}"#, + r#"{"k1":"616263","k2":"2217-12-22","k3":"1970-01-03 04:53:10.000000","k4":"1970-01-03 12:53:10.000000 +0800","k5":"10 months 20 days 00:05:00","k6":12.34,"k7":9817.24}"#, ), ]; @@ -1117,7 +1169,7 @@ fn test_type_of() { ), ( Value::TimestampTz(TimestampTz { - offset: 8, + offset: 8 * 3600, value: 190390000000, }), "TIMESTAMP_TZ", diff --git a/tests/it/number.rs b/tests/it/number.rs index d93c0ff..744ccb5 100644 --- a/tests/it/number.rs +++ b/tests/it/number.rs @@ -2265,8 +2265,8 @@ fn test_parse_decimal() { #[test] fn test_parse_float() { let tests = [ - ("-1e77", "-99999999999999999999999999999999999999999999999999999999999999999999999999991"), - ("1e79", "9999999999999999999999999999999999999999999999999999999999999999999999999999123"), + ("-1e+77", "-99999999999999999999999999999999999999999999999999999999999999999999999999991"), + ("1e+79", "9999999999999999999999999999999999999999999999999999999999999999999999999999123"), ("2.350988981904268e-38", "2.35098898190426788090088725919040801362055736959656341832065776397049129686767088287524529732763767242431640625E-38"), ("2.350987440475957e-38", "2.350987440475957123602109243087866394712812961308427354153308831195379018097479928428583662025630474090576171875E-38"), ("3.1700000098946436e-38", "3.1700000098946435501119816090716154772221806896649747100732700841687651538425285480116144753992557525634765625E-38"), @@ -2274,11 +2274,11 @@ fn test_parse_float() { ("1.0005271035279194e-42", "1.0005271035279193886395429224690001177341070264998322610345467546973108330377044694614596664905548095703125e-42"), ("3.919999330594565e-39", "3.91999933059456489828739575494312783522406115751507460249208160269472102366083987590172910131514072418212890625E-39"), ("8.544283616667655e-306", "8.5442836166676545758745469881475846986178991076220674838778719735182619591847930738097459423424470941335996703553180065389909675214026779902482660710563190540056652827644969523715287333767167538014707594736533997824798692690142890189753467148541192574394234161821394612038920127719106177776787375705338074667624093006332620080979623387970617655687653904110103913103933178304212511707769987213793880764157458662751217010283883439888757033430556011326632895537144105152597427684695380215955244686097497705226475608085097617996058799189036784865947060736971859470127760066696392182317083388979882704968230500619384728741377732016919538675848783600526390429792978252568964346334556191024880163233082812954995600973750951114861484914086986464099027216434478759765625e-306"), - ("3e300", "3000000000000000157514280765613260746113405743324477464747562346535407373966724587359114125241343592131113331498651634530827569706081291726934376554360120948545161602779727411213490701384364270178106859704912399835243357116902922640223958228340427483737776366460170528514347008416589160596378201620480"), - ("3.105036184601418e231", "3105036184601417870297958976925005110513772034233393222278104076052101905372753772661756817657292955900975461394262146412343160088229628782888574550082362278408909952041699811100530571263196889650525998387432937501785693707632115712"), + ("3e+300", "3000000000000000157514280765613260746113405743324477464747562346535407373966724587359114125241343592131113331498651634530827569706081291726934376554360120948545161602779727411213490701384364270178106859704912399835243357116902922640223958228340427483737776366460170528514347008416589160596378201620480"), + ("3.105036184601418e+231", "3105036184601417870297958976925005110513772034233393222278104076052101905372753772661756817657292955900975461394262146412343160088229628782888574550082362278408909952041699811100530571263196889650525998387432937501785693707632115712"), ("2.81341650018752e-308", "2.8134165001875198278759275525943498067505063001967969175506480744152639496835355462897889950138699429916690515722729976876607247658891051736045520063301219592298855232146428654590713004216312194773871772185068366206180596731958890086634117134422695105490626598276746331472433159429067991016548063113298957324839879447939977012897422163463450947345510093578791948321798481101505330952230105511530048812659083481787407026258844307461890753626327683153826358878159001221539330872743255707112001100520519610144879206546597846231715071742093092641158571855689231930930474890818690333095288369471228217443460522531282790309374378111440076317827545086535792316428407651758951233693496387904508572484340169054222573303301594335791590596740352481219815672375261783599853515625E-308"), - ("-1.7976931348623157e308", "-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368"), - ("1.7976931348623157e308", "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368"), + ("-1.7976931348623157e+308", "-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368"), + ("1.7976931348623157e+308", "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368"), ]; for (expected, test) in tests {