1#[cfg(test)]
16use std::collections::{HashMap, HashSet};
17use std::sync::Arc;
18
19use anyhow::Context;
20use base64::Engine as _;
21use base64::engine::general_purpose;
22use chrono::{DateTime, Datelike, Timelike};
23use chrono_tz::Tz;
24use indexmap::IndexMap;
25use itertools::Itertools;
26use risingwave_common::array::{ArrayError, ArrayResult};
27use risingwave_common::catalog::{Field, Schema};
28use risingwave_common::row::Row;
29use risingwave_common::types::{DataType, DatumRef, JsonbVal, ScalarRefImpl, ToText};
30use risingwave_common::util::iter_util::ZipEqDebug;
31use serde_json::{Map, Value, json};
32use thiserror_ext::AsReport;
33
34use super::{
35 CustomJsonType, DateHandlingMode, DorisJsonConfig, JsonbHandlingMode, KafkaConnectParams,
36 KafkaConnectParamsRef, Result, RowEncoder, SerTo, TimeHandlingMode, TimestampHandlingMode,
37 TimestamptzHandlingMode,
38};
39use crate::sink::SinkError;
40
41pub struct JsonEncoderConfig {
42 time_handling_mode: TimeHandlingMode,
43 date_handling_mode: DateHandlingMode,
44 timestamp_handling_mode: TimestampHandlingMode,
45 timestamptz_handling_mode: TimestamptzHandlingMode,
46 custom_json_type: CustomJsonType,
47 jsonb_handling_mode: JsonbHandlingMode,
48}
49
50pub struct JsonEncoder {
51 schema: Schema,
52 col_indices: Option<Vec<usize>>,
53 kafka_connect: Option<KafkaConnectParamsRef>,
54 config: JsonEncoderConfig,
55}
56
57impl JsonEncoder {
58 pub fn new(
59 schema: Schema,
60 col_indices: Option<Vec<usize>>,
61 date_handling_mode: DateHandlingMode,
62 timestamp_handling_mode: TimestampHandlingMode,
63 timestamptz_handling_mode: TimestamptzHandlingMode,
64 time_handling_mode: TimeHandlingMode,
65 jsonb_handling_mode: JsonbHandlingMode,
66 ) -> Self {
67 let config = JsonEncoderConfig {
68 time_handling_mode,
69 date_handling_mode,
70 timestamp_handling_mode,
71 timestamptz_handling_mode,
72 custom_json_type: CustomJsonType::None,
73 jsonb_handling_mode,
74 };
75 Self {
76 schema,
77 col_indices,
78 kafka_connect: None,
79 config,
80 }
81 }
82
83 pub fn new_with_es(schema: Schema, col_indices: Option<Vec<usize>>) -> Self {
84 let config = JsonEncoderConfig {
85 time_handling_mode: TimeHandlingMode::String,
86 date_handling_mode: DateHandlingMode::String,
87 timestamp_handling_mode: TimestampHandlingMode::String,
88 timestamptz_handling_mode: TimestamptzHandlingMode::UtcWithoutSuffix,
89 custom_json_type: CustomJsonType::Es,
90 jsonb_handling_mode: JsonbHandlingMode::Dynamic,
91 };
92 Self {
93 schema,
94 col_indices,
95 kafka_connect: None,
96 config,
97 }
98 }
99
100 pub fn new_with_doris(
101 schema: Schema,
102 col_indices: Option<Vec<usize>>,
103 doris_config: DorisJsonConfig,
104 ) -> Self {
105 let config = JsonEncoderConfig {
106 time_handling_mode: TimeHandlingMode::Milli,
107 date_handling_mode: DateHandlingMode::String,
108 timestamp_handling_mode: TimestampHandlingMode::String,
109 timestamptz_handling_mode: TimestamptzHandlingMode::UtcWithoutSuffix,
110 custom_json_type: CustomJsonType::Doris(doris_config),
111 jsonb_handling_mode: JsonbHandlingMode::String,
112 };
113 Self {
114 schema,
115 col_indices,
116 kafka_connect: None,
117 config,
118 }
119 }
120
121 pub fn new_with_starrocks(
122 schema: Schema,
123 col_indices: Option<Vec<usize>>,
124 time_zone: Tz,
125 ) -> Self {
126 let config = JsonEncoderConfig {
127 time_handling_mode: TimeHandlingMode::Milli,
128 date_handling_mode: DateHandlingMode::String,
129 timestamp_handling_mode: TimestampHandlingMode::String,
130 timestamptz_handling_mode: TimestamptzHandlingMode::SpecifiedTimezoneWithoutSuffix(
131 time_zone,
132 ),
133 custom_json_type: CustomJsonType::StarRocks,
134 jsonb_handling_mode: JsonbHandlingMode::Dynamic,
135 };
136 Self {
137 schema,
138 col_indices,
139 kafka_connect: None,
140 config,
141 }
142 }
143
144 pub fn new_with_turbopuffer(schema: Schema, col_indices: Option<Vec<usize>>) -> Self {
145 let config = JsonEncoderConfig {
146 time_handling_mode: TimeHandlingMode::String,
147 date_handling_mode: DateHandlingMode::String,
148 timestamp_handling_mode: TimestampHandlingMode::Iso8601String,
149 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
150 custom_json_type: CustomJsonType::Turbopuffer,
151 jsonb_handling_mode: JsonbHandlingMode::Dynamic,
152 };
153 Self {
154 schema,
155 col_indices,
156 kafka_connect: None,
157 config,
158 }
159 }
160
161 pub fn with_kafka_connect(self, kafka_connect: KafkaConnectParams) -> Self {
162 Self {
163 kafka_connect: Some(Arc::new(kafka_connect)),
164 ..self
165 }
166 }
167}
168
169impl JsonEncoderConfig {
170 fn jsonb_handling_mode_for_field(&self, field_name: &str) -> JsonbHandlingMode {
171 match &self.custom_json_type {
172 CustomJsonType::Doris(config) if config.variant_columns.contains(field_name) => {
173 JsonbHandlingMode::Dynamic
174 }
175 _ => self.jsonb_handling_mode,
176 }
177 }
178}
179
180impl RowEncoder for JsonEncoder {
181 type Output = Map<String, Value>;
182
183 fn schema(&self) -> &Schema {
184 &self.schema
185 }
186
187 fn col_indices(&self) -> Option<&[usize]> {
188 self.col_indices.as_ref().map(Vec::as_ref)
189 }
190
191 fn encode_cols(
192 &self,
193 row: impl Row,
194 col_indices: impl Iterator<Item = usize>,
195 ) -> Result<Self::Output> {
196 let mut mappings = Map::with_capacity(self.schema.len());
197 let col_indices = col_indices.collect_vec();
198 for idx in &col_indices {
199 let field = &self.schema[*idx];
200 let key = field.name.clone();
201 let value = datum_to_json_object(field, row.datum_at(*idx), &self.config)
202 .map_err(|e| SinkError::Encode(e.to_report_string()))?;
203 mappings.insert(key, value);
204 }
205
206 Ok(if let Some(param) = &self.kafka_connect {
207 json_converter_with_schema(
208 Value::Object(mappings),
209 param.schema_name.clone(),
210 col_indices.into_iter().map(|i| &self.schema[i]),
211 &self.config,
212 )
213 } else {
214 mappings
215 })
216 }
217}
218
219impl SerTo<String> for Map<String, Value> {
220 fn ser_to(self) -> Result<String> {
221 Value::Object(self).ser_to()
222 }
223}
224
225impl SerTo<String> for Value {
226 fn ser_to(self) -> Result<String> {
227 Ok(self.to_string())
228 }
229}
230
231fn datum_to_json_object(
232 field: &Field,
233 datum: DatumRef<'_>,
234 config: &JsonEncoderConfig,
235) -> ArrayResult<Value> {
236 let scalar_ref = match datum {
237 None => {
238 return Ok(Value::Null);
239 }
240 Some(datum) => datum,
241 };
242
243 let data_type = field.data_type();
244
245 tracing::trace!("datum_to_json_object: {:?}, {:?}", data_type, scalar_ref);
246
247 let value = match (data_type, scalar_ref) {
248 (DataType::Boolean, ScalarRefImpl::Bool(v)) => {
249 json!(v)
250 }
251 (DataType::Int16, ScalarRefImpl::Int16(v)) => {
252 json!(v)
253 }
254 (DataType::Int32, ScalarRefImpl::Int32(v)) => {
255 json!(v)
256 }
257 (DataType::Int64, ScalarRefImpl::Int64(v)) => {
258 json!(v)
259 }
260 (DataType::Serial, ScalarRefImpl::Serial(v)) => {
261 if matches!(&config.custom_json_type, CustomJsonType::Turbopuffer) {
262 json!(v.into_inner())
263 } else {
264 json!(format!("{:#018x}", v.into_inner()))
266 }
267 }
268 (DataType::Float32, ScalarRefImpl::Float32(v)) => {
269 json!(f32::from(v))
270 }
271 (DataType::Float64, ScalarRefImpl::Float64(v)) => {
272 json!(f64::from(v))
273 }
274 (DataType::Varchar, ScalarRefImpl::Utf8(v)) => {
275 json!(v)
276 }
277 (DataType::Decimal, ScalarRefImpl::Decimal(mut v)) => match &config.custom_json_type {
279 CustomJsonType::Doris(config) => {
280 let s = config.decimal_scale.get(&field.name).unwrap();
281 v.rescale(*s as u32);
282 json!(v.to_text())
283 }
284 CustomJsonType::Turbopuffer => {
285 let value = f64::try_from(v).map_err(|err| {
286 ArrayError::internal(format!(
287 "failed to convert decimal to f64: {}",
288 err.as_report()
289 ))
290 })?;
291 serde_json::Number::from_f64(value)
292 .map(Value::Number)
293 .ok_or_else(|| {
294 ArrayError::internal(
295 "failed to encode non-finite decimal as JSON number".to_owned(),
296 )
297 })?
298 }
299 CustomJsonType::Es | CustomJsonType::None | CustomJsonType::StarRocks => {
300 json!(v.to_text())
301 }
302 },
303 (DataType::Timestamptz, ScalarRefImpl::Timestamptz(v)) => {
304 match config.timestamptz_handling_mode {
305 TimestamptzHandlingMode::UtcString => {
306 let parsed = v.to_datetime_utc();
307 let v = parsed.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
308 json!(v)
309 }
310 TimestamptzHandlingMode::UtcWithoutSuffix => {
311 let parsed = v.to_datetime_utc().naive_utc();
312 let v = parsed.format("%Y-%m-%d %H:%M:%S%.6f").to_string();
313 json!(v)
314 }
315 TimestamptzHandlingMode::SpecifiedTimezoneWithoutSuffix(time_zone) => {
316 let parsed = v.to_datetime_in_zone(time_zone).naive_local();
317 let v = parsed.format("%Y-%m-%d %H:%M:%S%.6f").to_string();
318 json!(v)
319 }
320 TimestamptzHandlingMode::Micro => json!(v.timestamp_micros()),
321 TimestamptzHandlingMode::Milli => json!(v.timestamp_millis()),
322 }
323 }
324 (DataType::Time, ScalarRefImpl::Time(v)) => match config.time_handling_mode {
325 TimeHandlingMode::Milli => {
326 json!(v.0.num_seconds_from_midnight() as i64 * 1000)
328 }
329 TimeHandlingMode::String => {
330 let a = v.0.format("%H:%M:%S%.6f").to_string();
331 json!(a)
332 }
333 },
334 (DataType::Date, ScalarRefImpl::Date(v)) => match config.date_handling_mode {
335 DateHandlingMode::FromCe => json!(v.0.num_days_from_ce()),
336 DateHandlingMode::FromEpoch => {
337 let duration = v.0 - DateTime::UNIX_EPOCH.date_naive();
338 json!(duration.num_days())
339 }
340 DateHandlingMode::String => {
341 let a = v.0.format("%Y-%m-%d").to_string();
342 json!(a)
343 }
344 },
345 (DataType::Timestamp, ScalarRefImpl::Timestamp(v)) => {
346 match config.timestamp_handling_mode {
347 TimestampHandlingMode::Milli => json!(v.0.and_utc().timestamp_millis()),
348 TimestampHandlingMode::String => {
349 json!(v.0.format("%Y-%m-%d %H:%M:%S%.6f").to_string())
350 }
351 TimestampHandlingMode::Iso8601String => {
352 json!(v.0.format("%Y-%m-%dT%H:%M:%S%.6f").to_string())
353 }
354 }
355 }
356 (DataType::Bytea, ScalarRefImpl::Bytea(v)) => {
357 json!(general_purpose::STANDARD.encode(v))
358 }
359 (DataType::Interval, ScalarRefImpl::Interval(v)) => {
361 json!(v.as_iso_8601())
362 }
363
364 (DataType::Jsonb, ScalarRefImpl::Jsonb(jsonb_ref)) => {
365 match config.jsonb_handling_mode_for_field(&field.name) {
366 JsonbHandlingMode::String => {
367 json!(jsonb_ref.to_string())
368 }
369 JsonbHandlingMode::Dynamic => JsonbVal::from(jsonb_ref).take(),
370 }
371 }
372 (DataType::List(lt), ScalarRefImpl::List(list_ref)) => {
373 let elems = list_ref.iter();
374 let mut vec = Vec::with_capacity(elems.len());
375 let inner_field = Field::unnamed(lt.into_elem());
376 for sub_datum_ref in elems {
377 let value = datum_to_json_object(&inner_field, sub_datum_ref, config)?;
378 vec.push(value);
379 }
380 json!(vec)
381 }
382 (DataType::Vector(_), ScalarRefImpl::Vector(vector)) => {
383 let elems = vector.as_raw_slice();
384 let mut vec = Vec::with_capacity(elems.len());
385 for v in elems {
386 let value = serde_json::Number::from_f64(*v as _)
387 .map(Value::Number)
388 .unwrap_or(Value::Null);
389 vec.push(value);
390 }
391 json!(vec)
392 }
393 (DataType::Struct(st), ScalarRefImpl::Struct(struct_ref)) => {
394 match config.custom_json_type {
395 CustomJsonType::Doris(_) => {
396 let mut map = IndexMap::with_capacity(st.len());
398 for (sub_datum_ref, sub_field) in struct_ref.iter_fields_ref().zip_eq_debug(
399 st.iter()
400 .map(|(name, dt)| Field::with_name(dt.clone(), name)),
401 ) {
402 let value = datum_to_json_object(&sub_field, sub_datum_ref, config)?;
403 map.insert(sub_field.name.clone(), value);
404 }
405 Value::String(
406 serde_json::to_string(&map).context("failed to serialize into JSON")?,
407 )
408 }
409 CustomJsonType::StarRocks => {
410 return Err(ArrayError::internal(
411 "starrocks can't support struct".to_owned(),
412 ));
413 }
414 CustomJsonType::Es | CustomJsonType::None | CustomJsonType::Turbopuffer => {
415 let mut map = Map::with_capacity(st.len());
416 for (sub_datum_ref, sub_field) in struct_ref.iter_fields_ref().zip_eq_debug(
417 st.iter()
418 .map(|(name, dt)| Field::with_name(dt.clone(), name)),
419 ) {
420 let value = datum_to_json_object(&sub_field, sub_datum_ref, config)?;
421 map.insert(sub_field.name.clone(), value);
422 }
423 json!(map)
424 }
425 }
426 }
427 (data_type, scalar_ref) => {
429 return Err(ArrayError::internal(format!(
430 "datum_to_json_object: unsupported data type: field name: {:?}, logical type: {:?}, physical type: {:?}",
431 field.name, data_type, scalar_ref
432 )));
433 }
434 };
435
436 Ok(value)
437}
438
439fn json_converter_with_schema<'a>(
440 object: Value,
441 name: String,
442 fields: impl Iterator<Item = &'a Field>,
443 config: &JsonEncoderConfig,
444) -> Map<String, Value> {
445 let mut mapping = Map::with_capacity(2);
446 mapping.insert(
447 "schema".to_owned(),
448 json!({
449 "type": "struct",
450 "fields": fields.map(|field| {
451 let mut mapping = type_as_json_schema(&field.data_type, config);
452 mapping.insert("field".to_owned(), json!(field.name));
453 mapping
454 }).collect_vec(),
455 "optional": false,
456 "name": name,
457 }),
458 );
459 mapping.insert("payload".to_owned(), object);
460 mapping
461}
462
463pub(crate) fn schema_type_mapping(rw_type: &DataType, config: &JsonEncoderConfig) -> &'static str {
465 match rw_type {
466 DataType::Boolean => "boolean",
467 DataType::Int16 => "int16",
468 DataType::Int32 => "int32",
469 DataType::Int64 => "int64",
470 DataType::Float32 => "float",
471 DataType::Float64 => "double",
472 DataType::Decimal => "string",
473 DataType::Date => match config.date_handling_mode {
474 DateHandlingMode::FromCe | DateHandlingMode::FromEpoch => "int32",
475 DateHandlingMode::String => "string",
476 },
477 DataType::Varchar => "string",
478 DataType::Time => "int64",
479 DataType::Timestamp => "int64",
480 DataType::Timestamptz => match config.timestamptz_handling_mode {
481 TimestamptzHandlingMode::Micro | TimestamptzHandlingMode::Milli => "int64",
482 TimestamptzHandlingMode::UtcString
483 | TimestamptzHandlingMode::UtcWithoutSuffix
484 | TimestamptzHandlingMode::SpecifiedTimezoneWithoutSuffix(_) => "string",
485 },
486 DataType::Interval => "string",
487 DataType::Struct(_) => "struct",
488 DataType::List(_) => "array",
489 DataType::Vector(_) => "array",
490 DataType::Bytea => "bytes",
491 DataType::Jsonb => "string",
492 DataType::Variant => "string",
493 DataType::Serial => "string",
494 DataType::Int256 => "string",
495 DataType::Map(_) => "map",
496 }
497}
498
499fn type_as_json_schema(rw_type: &DataType, config: &JsonEncoderConfig) -> Map<String, Value> {
500 let mut mapping = Map::with_capacity(4); mapping.insert(
502 "type".to_owned(),
503 json!(schema_type_mapping(rw_type, config)),
504 );
505 mapping.insert("optional".to_owned(), json!(true));
506 match rw_type {
507 DataType::Struct(struct_type) => {
508 let sub_fields = struct_type
509 .iter()
510 .map(|(sub_name, sub_type)| {
511 let mut sub_mapping = type_as_json_schema(sub_type, config);
512 sub_mapping.insert("field".to_owned(), json!(sub_name));
513 sub_mapping
514 })
515 .collect_vec();
516 mapping.insert("fields".to_owned(), json!(sub_fields));
517 }
518 DataType::List(list_type) => {
519 mapping.insert(
520 "items".to_owned(),
521 json!(type_as_json_schema(list_type.elem(), config)),
522 );
523 }
524 _ => {}
525 }
526
527 mapping
528}
529
530#[cfg(test)]
531mod tests {
532 use risingwave_common::row::OwnedRow;
533 use risingwave_common::types::{
534 Date, Decimal, Interval, ListValue, Scalar, ScalarImpl, StructRef, StructType, StructValue,
535 Time, Timestamp,
536 };
537
538 use super::*;
539
540 fn assert_temporal_schema_and_payload(
541 data_type: DataType,
542 value: ScalarImpl,
543 date_mode: DateHandlingMode,
544 timestamptz_mode: TimestamptzHandlingMode,
545 expected_type: &str,
546 expected_value: Value,
547 ) {
548 let schema = Schema::new(vec![
549 Field::with_name(data_type.clone(), "value"),
550 Field::with_name(DataType::list(data_type.clone()), "items"),
551 Field::with_name(
552 StructType::new(vec![
553 ("value", data_type.clone()),
554 ("items", DataType::list(data_type.clone())),
555 ])
556 .into(),
557 "nested",
558 ),
559 ]);
560 let items = ScalarImpl::List(ListValue::from_datum_iter(
561 &data_type,
562 [Some(value.clone()), None],
563 ));
564 let row = OwnedRow::new(vec![
565 Some(value.clone()),
566 Some(items.clone()),
567 Some(ScalarImpl::Struct(StructValue::new(vec![
568 Some(value),
569 Some(items),
570 ]))),
571 ]);
572 let value_schema = json!({
573 "field": "value", "type": expected_type, "optional": true,
574 });
575 let items_schema = json!({
576 "field": "items", "type": "array", "optional": true,
577 "items": {"type": expected_type, "optional": true},
578 });
579 let nested_schema = json!({
580 "field": "nested", "type": "struct", "optional": true,
581 "fields": [value_schema, items_schema],
582 });
583
584 for col_indices in [None, Some(vec![0])] {
585 let is_key = col_indices.is_some();
586 let encoder = JsonEncoder::new(
587 schema.clone(),
588 col_indices,
589 date_mode,
590 TimestampHandlingMode::Milli,
591 timestamptz_mode,
592 TimeHandlingMode::Milli,
593 JsonbHandlingMode::String,
594 )
595 .with_kafka_connect(KafkaConnectParams {
596 schema_name: "test.temporal".to_owned(),
597 });
598 let fields = if is_key {
599 json!([value_schema])
600 } else {
601 json!([value_schema, items_schema, nested_schema])
602 };
603 let payload = if is_key {
604 json!({"value": expected_value})
605 } else {
606 json!({
607 "value": expected_value,
608 "items": [expected_value, null],
609 "nested": {
610 "value": expected_value,
611 "items": [expected_value, null],
612 },
613 })
614 };
615 assert_eq!(
616 Value::Object(encoder.encode(&row).unwrap()),
617 json!({
618 "schema": {
619 "type": "struct", "name": "test.temporal", "optional": false,
620 "fields": fields,
621 },
622 "payload": payload,
623 })
624 );
625 }
626 }
627
628 #[test]
629 fn test_json_schema_date_modes() {
630 for (mode, expected_type, expected_value) in [
631 (DateHandlingMode::FromCe, "int32", json!(719163)),
632 (DateHandlingMode::FromEpoch, "int32", json!(0)),
633 (DateHandlingMode::String, "string", json!("1970-01-01")),
634 ] {
635 assert_temporal_schema_and_payload(
636 DataType::Date,
637 ScalarImpl::Date(Date::from_ymd_uncheck(1970, 1, 1)),
638 mode,
639 TimestamptzHandlingMode::UtcString,
640 expected_type,
641 expected_value,
642 );
643 }
644 }
645
646 #[test]
647 fn test_json_schema_timestamptz_modes() {
648 for (mode, expected_type, expected_value) in [
649 (
650 TimestamptzHandlingMode::UtcString,
651 "string",
652 json!("1970-01-01T00:00:01.123456Z"),
653 ),
654 (
655 TimestamptzHandlingMode::UtcWithoutSuffix,
656 "string",
657 json!("1970-01-01 00:00:01.123456"),
658 ),
659 (
660 TimestamptzHandlingMode::SpecifiedTimezoneWithoutSuffix(chrono_tz::Asia::Shanghai),
661 "string",
662 json!("1970-01-01 08:00:01.123456"),
663 ),
664 (TimestamptzHandlingMode::Micro, "int64", json!(1123456)),
665 (TimestamptzHandlingMode::Milli, "int64", json!(1123)),
666 ] {
667 assert_temporal_schema_and_payload(
668 DataType::Timestamptz,
669 ScalarImpl::Timestamptz("1970-01-01T00:00:01.123456Z".parse().unwrap()),
670 DateHandlingMode::FromCe,
671 mode,
672 expected_type,
673 expected_value,
674 );
675 }
676 }
677
678 #[test]
679 fn test_starrocks_timestamptz_encoding() {
680 let schema = Schema::new(vec![Field::with_name(DataType::Timestamptz, "ts")]);
681 let encoder = JsonEncoder::new_with_starrocks(schema, None, chrono_tz::Asia::Shanghai);
682 let tstz = "2018-01-26T18:30:09.453Z".parse().unwrap();
683 let row = OwnedRow::new(vec![Some(ScalarImpl::Timestamptz(tstz))]);
684
685 let encoded = encoder.encode(row).unwrap();
686
687 assert_eq!(
688 encoded.get("ts"),
689 Some(&json!("2018-01-27 02:30:09.453000"))
690 );
691 }
692
693 #[test]
694 fn test_to_json_basic_type() {
695 let mock_field = Field {
696 data_type: DataType::Boolean,
697 name: Default::default(),
698 };
699
700 let config = JsonEncoderConfig {
701 time_handling_mode: TimeHandlingMode::Milli,
702 date_handling_mode: DateHandlingMode::FromCe,
703 timestamp_handling_mode: TimestampHandlingMode::String,
704 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
705 custom_json_type: CustomJsonType::None,
706 jsonb_handling_mode: JsonbHandlingMode::String,
707 };
708
709 let boolean_value = datum_to_json_object(
710 &Field {
711 data_type: DataType::Boolean,
712 ..mock_field.clone()
713 },
714 Some(ScalarImpl::Bool(false).as_scalar_ref_impl()),
715 &config,
716 )
717 .unwrap();
718 assert_eq!(boolean_value, json!(false));
719
720 let int16_value = datum_to_json_object(
721 &Field {
722 data_type: DataType::Int16,
723 ..mock_field.clone()
724 },
725 Some(ScalarImpl::Int16(16).as_scalar_ref_impl()),
726 &config,
727 )
728 .unwrap();
729 assert_eq!(int16_value, json!(16));
730
731 let int64_value = datum_to_json_object(
732 &Field {
733 data_type: DataType::Int64,
734 ..mock_field.clone()
735 },
736 Some(ScalarImpl::Int64(i64::MAX).as_scalar_ref_impl()),
737 &config,
738 )
739 .unwrap();
740 assert_eq!(
741 serde_json::to_string(&int64_value).unwrap(),
742 i64::MAX.to_string()
743 );
744
745 let serial_value = datum_to_json_object(
746 &Field {
747 data_type: DataType::Serial,
748 ..mock_field.clone()
749 },
750 Some(ScalarImpl::Serial(i64::MAX.into()).as_scalar_ref_impl()),
751 &config,
752 )
753 .unwrap();
754 assert_eq!(
755 serde_json::to_string(&serial_value).unwrap(),
756 format!("\"{:#018x}\"", i64::MAX)
757 );
758
759 let turbopuffer_config = JsonEncoderConfig {
760 time_handling_mode: TimeHandlingMode::String,
761 date_handling_mode: DateHandlingMode::String,
762 timestamp_handling_mode: TimestampHandlingMode::String,
763 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
764 custom_json_type: CustomJsonType::Turbopuffer,
765 jsonb_handling_mode: JsonbHandlingMode::Dynamic,
766 };
767 let turbopuffer_serial_value = datum_to_json_object(
768 &Field {
769 data_type: DataType::Serial,
770 ..mock_field.clone()
771 },
772 Some(ScalarImpl::Serial(i64::MAX.into()).as_scalar_ref_impl()),
773 &turbopuffer_config,
774 )
775 .unwrap();
776 assert_eq!(turbopuffer_serial_value, json!(i64::MAX));
777 let turbopuffer_decimal_value = datum_to_json_object(
778 &Field {
779 data_type: DataType::Decimal,
780 ..mock_field.clone()
781 },
782 Some(ScalarImpl::Decimal(Decimal::try_from(1.25).unwrap()).as_scalar_ref_impl()),
783 &turbopuffer_config,
784 )
785 .unwrap();
786 assert_eq!(turbopuffer_decimal_value, json!(1.25));
787 assert!(
788 datum_to_json_object(
789 &Field {
790 data_type: DataType::Decimal,
791 ..mock_field.clone()
792 },
793 Some(ScalarImpl::Decimal(Decimal::NaN).as_scalar_ref_impl()),
794 &turbopuffer_config,
795 )
796 .unwrap_err()
797 .to_string()
798 .contains("non-finite decimal")
799 );
800
801 let tstz_inner = "2018-01-26T18:30:09.453Z".parse().unwrap();
803 let tstz_value = datum_to_json_object(
804 &Field {
805 data_type: DataType::Timestamptz,
806 ..mock_field.clone()
807 },
808 Some(ScalarImpl::Timestamptz(tstz_inner).as_scalar_ref_impl()),
809 &config,
810 )
811 .unwrap();
812 assert_eq!(tstz_value, "2018-01-26T18:30:09.453000Z");
813
814 let unix_wo_suffix_config = JsonEncoderConfig {
815 time_handling_mode: TimeHandlingMode::Milli,
816 date_handling_mode: DateHandlingMode::FromCe,
817 timestamp_handling_mode: TimestampHandlingMode::String,
818 timestamptz_handling_mode: TimestamptzHandlingMode::UtcWithoutSuffix,
819 custom_json_type: CustomJsonType::None,
820 jsonb_handling_mode: JsonbHandlingMode::String,
821 };
822
823 let tstz_inner = "2018-01-26T18:30:09.453Z".parse().unwrap();
824 let tstz_value = datum_to_json_object(
825 &Field {
826 data_type: DataType::Timestamptz,
827 ..mock_field.clone()
828 },
829 Some(ScalarImpl::Timestamptz(tstz_inner).as_scalar_ref_impl()),
830 &unix_wo_suffix_config,
831 )
832 .unwrap();
833 assert_eq!(tstz_value, "2018-01-26 18:30:09.453000");
834
835 let timestamp_milli_config = JsonEncoderConfig {
836 time_handling_mode: TimeHandlingMode::String,
837 date_handling_mode: DateHandlingMode::FromCe,
838 timestamp_handling_mode: TimestampHandlingMode::Milli,
839 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
840 custom_json_type: CustomJsonType::None,
841 jsonb_handling_mode: JsonbHandlingMode::String,
842 };
843 let ts_value = datum_to_json_object(
844 &Field {
845 data_type: DataType::Timestamp,
846 ..mock_field.clone()
847 },
848 Some(
849 ScalarImpl::Timestamp(Timestamp::from_timestamp_uncheck(1000, 0))
850 .as_scalar_ref_impl(),
851 ),
852 ×tamp_milli_config,
853 )
854 .unwrap();
855 assert_eq!(ts_value, json!(1000 * 1000));
856
857 let ts_value = datum_to_json_object(
858 &Field {
859 data_type: DataType::Timestamp,
860 ..mock_field.clone()
861 },
862 Some(
863 ScalarImpl::Timestamp(Timestamp::from_timestamp_uncheck(1000, 0))
864 .as_scalar_ref_impl(),
865 ),
866 &config,
867 )
868 .unwrap();
869 assert_eq!(ts_value, json!("1970-01-01 00:16:40.000000".to_owned()));
870
871 let time_value = datum_to_json_object(
873 &Field {
874 data_type: DataType::Time,
875 ..mock_field.clone()
876 },
877 Some(
878 ScalarImpl::Time(Time::from_num_seconds_from_midnight_uncheck(1000, 0))
879 .as_scalar_ref_impl(),
880 ),
881 &config,
882 )
883 .unwrap();
884 assert_eq!(time_value, json!(1000 * 1000));
885
886 let interval_value = datum_to_json_object(
887 &Field {
888 data_type: DataType::Interval,
889 ..mock_field.clone()
890 },
891 Some(
892 ScalarImpl::Interval(Interval::from_month_day_usec(13, 2, 1000000))
893 .as_scalar_ref_impl(),
894 ),
895 &config,
896 )
897 .unwrap();
898 assert_eq!(interval_value, json!("P1Y1M2DT0H0M1S"));
899
900 let mut decimal_scale = HashMap::default();
901 decimal_scale.insert("aaa".to_owned(), 5_u8);
902 let doris_config = JsonEncoderConfig {
903 time_handling_mode: TimeHandlingMode::String,
904 date_handling_mode: DateHandlingMode::String,
905 timestamp_handling_mode: TimestampHandlingMode::String,
906 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
907 custom_json_type: CustomJsonType::Doris(DorisJsonConfig {
908 decimal_scale,
909 variant_columns: HashSet::default(),
910 }),
911 jsonb_handling_mode: JsonbHandlingMode::String,
912 };
913 let decimal = datum_to_json_object(
914 &Field {
915 data_type: DataType::Decimal,
916 name: "aaa".to_owned(),
917 },
918 Some(ScalarImpl::Decimal(Decimal::try_from(1.1111111).unwrap()).as_scalar_ref_impl()),
919 &doris_config,
920 )
921 .unwrap();
922 assert_eq!(decimal, json!("1.11111"));
923
924 let date_value = datum_to_json_object(
925 &Field {
926 data_type: DataType::Date,
927 ..mock_field.clone()
928 },
929 Some(ScalarImpl::Date(Date::from_ymd_uncheck(1970, 1, 1)).as_scalar_ref_impl()),
930 &config,
931 )
932 .unwrap();
933 assert_eq!(date_value, json!(719163));
934
935 let from_epoch_config = JsonEncoderConfig {
936 time_handling_mode: TimeHandlingMode::String,
937 date_handling_mode: DateHandlingMode::FromEpoch,
938 timestamp_handling_mode: TimestampHandlingMode::String,
939 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
940 custom_json_type: CustomJsonType::None,
941 jsonb_handling_mode: JsonbHandlingMode::String,
942 };
943 let date_value = datum_to_json_object(
944 &Field {
945 data_type: DataType::Date,
946 ..mock_field.clone()
947 },
948 Some(ScalarImpl::Date(Date::from_ymd_uncheck(1970, 1, 1)).as_scalar_ref_impl()),
949 &from_epoch_config,
950 )
951 .unwrap();
952 assert_eq!(date_value, json!(0));
953
954 let doris_config = JsonEncoderConfig {
955 time_handling_mode: TimeHandlingMode::String,
956 date_handling_mode: DateHandlingMode::String,
957 timestamp_handling_mode: TimestampHandlingMode::String,
958 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
959 custom_json_type: CustomJsonType::Doris(DorisJsonConfig {
960 decimal_scale: HashMap::default(),
961 variant_columns: HashSet::default(),
962 }),
963 jsonb_handling_mode: JsonbHandlingMode::String,
964 };
965 let date_value = datum_to_json_object(
966 &Field {
967 data_type: DataType::Date,
968 ..mock_field.clone()
969 },
970 Some(ScalarImpl::Date(Date::from_ymd_uncheck(2010, 10, 10)).as_scalar_ref_impl()),
971 &doris_config,
972 )
973 .unwrap();
974 assert_eq!(date_value, json!("2010-10-10"));
975
976 let value = StructValue::new(vec![
977 Some(3_i32.to_scalar_value()),
978 Some(2_i32.to_scalar_value()),
979 Some(1_i32.to_scalar_value()),
980 ]);
981
982 let interval_value = datum_to_json_object(
983 &Field {
984 data_type: DataType::Struct(StructType::new(vec![
985 ("v3", DataType::Int32),
986 ("v2", DataType::Int32),
987 ("v1", DataType::Int32),
988 ])),
989 ..mock_field.clone()
990 },
991 Some(ScalarRefImpl::Struct(StructRef::ValueRef { val: &value })),
992 &doris_config,
993 )
994 .unwrap();
995 assert_eq!(interval_value, json!("{\"v3\":3,\"v2\":2,\"v1\":1}"));
996
997 let encode_jsonb_obj_config = JsonEncoderConfig {
998 time_handling_mode: TimeHandlingMode::String,
999 date_handling_mode: DateHandlingMode::String,
1000 timestamp_handling_mode: TimestampHandlingMode::String,
1001 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
1002 custom_json_type: CustomJsonType::None,
1003 jsonb_handling_mode: JsonbHandlingMode::Dynamic,
1004 };
1005 let json_value = datum_to_json_object(
1006 &Field {
1007 data_type: DataType::Jsonb,
1008 ..mock_field
1009 },
1010 Some(ScalarImpl::Jsonb(JsonbVal::from(json!([1, 2, 3]))).as_scalar_ref_impl()),
1011 &encode_jsonb_obj_config,
1012 )
1013 .unwrap();
1014 assert_eq!(json_value, json!([1, 2, 3]));
1015
1016 let variant_json_value = datum_to_json_object(
1017 &Field {
1018 data_type: DataType::Jsonb,
1019 name: "variant_col".into(),
1020 },
1021 Some(
1022 ScalarImpl::Jsonb(JsonbVal::from(json!({"nested": [1, 2, 3]})))
1023 .as_scalar_ref_impl(),
1024 ),
1025 &JsonEncoderConfig {
1026 time_handling_mode: TimeHandlingMode::String,
1027 date_handling_mode: DateHandlingMode::String,
1028 timestamp_handling_mode: TimestampHandlingMode::String,
1029 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
1030 custom_json_type: CustomJsonType::Doris(DorisJsonConfig {
1031 decimal_scale: HashMap::default(),
1032 variant_columns: HashSet::from(["variant_col".to_owned()]),
1033 }),
1034 jsonb_handling_mode: JsonbHandlingMode::String,
1035 },
1036 )
1037 .unwrap();
1038 assert_eq!(variant_json_value, json!({"nested": [1, 2, 3]}));
1039 }
1040
1041 #[test]
1042 fn test_generate_json_converter_schema() {
1043 let fields = vec![
1044 Field {
1045 data_type: DataType::Boolean,
1046 name: "v1".into(),
1047 },
1048 Field {
1049 data_type: DataType::Int16,
1050 name: "v2".into(),
1051 },
1052 Field {
1053 data_type: DataType::Int32,
1054 name: "v3".into(),
1055 },
1056 Field {
1057 data_type: DataType::Float32,
1058 name: "v4".into(),
1059 },
1060 Field {
1061 data_type: DataType::Decimal,
1062 name: "v5".into(),
1063 },
1064 Field {
1065 data_type: DataType::Date,
1066 name: "v6".into(),
1067 },
1068 Field {
1069 data_type: DataType::Varchar,
1070 name: "v7".into(),
1071 },
1072 Field {
1073 data_type: DataType::Time,
1074 name: "v8".into(),
1075 },
1076 Field {
1077 data_type: DataType::Interval,
1078 name: "v9".into(),
1079 },
1080 Field {
1081 data_type: DataType::Struct(StructType::new(vec![
1082 ("a", DataType::Timestamp),
1083 ("b", DataType::Timestamptz),
1084 (
1085 "c",
1086 DataType::Struct(StructType::new(vec![
1087 ("aa", DataType::Int64),
1088 ("bb", DataType::Float64),
1089 ])),
1090 ),
1091 ])),
1092 name: "v10".into(),
1093 },
1094 Field {
1095 data_type: DataType::list(DataType::list(DataType::Struct(StructType::new(vec![
1096 ("aa", DataType::Int64),
1097 ("bb", DataType::Float64),
1098 ])))),
1099 name: "v11".into(),
1100 },
1101 Field {
1102 data_type: DataType::Jsonb,
1103 name: "12".into(),
1104 },
1105 Field {
1106 data_type: DataType::Serial,
1107 name: "13".into(),
1108 },
1109 Field {
1110 data_type: DataType::Int256,
1111 name: "14".into(),
1112 },
1113 ];
1114 let config = JsonEncoderConfig {
1115 time_handling_mode: TimeHandlingMode::Milli,
1116 date_handling_mode: DateHandlingMode::FromCe,
1117 timestamp_handling_mode: TimestampHandlingMode::Milli,
1118 timestamptz_handling_mode: TimestamptzHandlingMode::UtcString,
1119 custom_json_type: CustomJsonType::None,
1120 jsonb_handling_mode: JsonbHandlingMode::String,
1121 };
1122 let schema = json_converter_with_schema(
1123 json!({}),
1124 "test".to_owned(),
1125 fields.iter(),
1126 &config,
1127 )["schema"]
1128 .to_string();
1129 let ans = r#"{"fields":[{"field":"v1","optional":true,"type":"boolean"},{"field":"v2","optional":true,"type":"int16"},{"field":"v3","optional":true,"type":"int32"},{"field":"v4","optional":true,"type":"float"},{"field":"v5","optional":true,"type":"string"},{"field":"v6","optional":true,"type":"int32"},{"field":"v7","optional":true,"type":"string"},{"field":"v8","optional":true,"type":"int64"},{"field":"v9","optional":true,"type":"string"},{"field":"v10","fields":[{"field":"a","optional":true,"type":"int64"},{"field":"b","optional":true,"type":"string"},{"field":"c","fields":[{"field":"aa","optional":true,"type":"int64"},{"field":"bb","optional":true,"type":"double"}],"optional":true,"type":"struct"}],"optional":true,"type":"struct"},{"field":"v11","items":{"items":{"fields":[{"field":"aa","optional":true,"type":"int64"},{"field":"bb","optional":true,"type":"double"}],"optional":true,"type":"struct"},"optional":true,"type":"array"},"optional":true,"type":"array"},{"field":"12","optional":true,"type":"string"},{"field":"13","optional":true,"type":"string"},{"field":"14","optional":true,"type":"string"}],"name":"test","optional":false,"type":"struct"}"#;
1130 assert_eq!(schema, ans);
1131 }
1132}