Skip to main content

risingwave_connector/parser/unified/
json.rs

1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::str::FromStr;
16use std::sync::LazyLock;
17
18use base64::Engine;
19use itertools::Itertools;
20use num_bigint::BigInt;
21use risingwave_common::array::{Finite32, ListValue, StructValue};
22use risingwave_common::cast::{i64_to_timestamp, i64_to_timestamptz, str_to_bytea};
23use risingwave_common::log::LogSuppressor;
24use risingwave_common::types::{
25    DEBEZIUM_UNAVAILABLE_VALUE, DataType, Date, Decimal, Int256, Interval, JsonbVal, ScalarImpl,
26    Time, Timestamp, Timestamptz, ToOwnedDatum, VectorVal, debezium_unavailable_vector,
27};
28use risingwave_connector_codec::decoder::utils::scaled_bigint_to_rust_decimal;
29use simd_json::base::ValueAsObject;
30use simd_json::prelude::{
31    TypedValue, ValueAsArray, ValueAsScalar, ValueObjectAccess, ValueTryAsScalar,
32};
33use simd_json::{BorrowedValue, ValueType};
34use thiserror_ext::AsReport;
35
36use super::{Access, AccessError, AccessResult};
37use crate::parser::DatumCow;
38use crate::schema::{InvalidOptionError, bail_invalid_option_error};
39
40/// Try to parse Debezium `PostGIS` `geometry` object.
41///
42/// Debezium represents `PostGIS` `geometry` as an object: `{"srid": <int>, "wkb": <base64_string>}`.
43/// For our current Postgres CDC ingestion, the `wkb` field is expected to be EWKB bytes (base64-encoded),
44/// and `srid` is redundant. We decode `wkb` into raw bytes and store it as `bytea`.
45///
46/// Note: Debezium provides an SMT to convert between `WKB` and `EWKB`, which may be useful for future
47/// unification across connectors (e.g., MySQL): see `GeometryFormatTransformer`.
48///
49/// Return semantics:
50/// - `Ok(Some(bytes))`: The input matches the Debezium geometry shape (`srid` is numeric AND `wkb` is string),
51///   and we successfully decoded `wkb` into bytes.
52/// - `Ok(None)`: The input does NOT look like a Debezium geometry object. This allows the caller to keep the
53///   match arm focused on dispatching, and avoids misclassifying other JSON objects that might map to `bytea`
54///   in the future.
55/// - `Err(...)`: The input looks like a Debezium geometry object, but decoding/parsing failed (e.g. invalid
56///   base64). This indicates a real data/format error and should not be silently ignored.
57fn try_parse_debezium_geometry_as_bytea(
58    value: &BorrowedValue<'_>,
59    create_error: impl Fn() -> AccessError,
60) -> AccessResult<Option<Box<[u8]>>> {
61    let obj = match value.as_object() {
62        Some(obj) => obj,
63        None => return Ok(None),
64    };
65
66    // Strictly identify the geometry object by checking both fields and their types.
67    // There may be other objects that map to bytea in the future.
68    let srid = obj.get("srid").and_then(|v| v.as_i64());
69    let wkb = obj.get("wkb").and_then(|v| v.as_str());
70
71    let (Some(_srid), Some(wkb)) = (srid, wkb) else {
72        return Ok(None);
73    };
74
75    let bytes = base64::engine::general_purpose::STANDARD
76        .decode(wkb)
77        .map_err(|_| create_error())?
78        .into_boxed_slice();
79
80    Ok(Some(bytes))
81}
82
83#[derive(Clone, Debug)]
84pub enum ByteaHandling {
85    Standard,
86    // debezium converts postgres bytea to base64 format
87    Base64,
88}
89#[derive(Clone, Debug)]
90pub enum TimeHandling {
91    Milli,
92    Micro,
93}
94
95#[derive(Clone, Copy, Debug)]
96pub enum BigintUnsignedHandlingMode {
97    /// Convert unsigned bigint to signed bigint (default)
98    Long,
99    /// Use base64-encoded decimal for unsigned bigint (Debezium precise mode)
100    Precise,
101}
102
103#[derive(Clone, Debug)]
104pub enum TimestamptzHandling {
105    /// `"2024-04-11T02:00:00.123456Z"`
106    UtcString,
107    /// `"2024-04-11 02:00:00.123456"`
108    UtcWithoutSuffix,
109    /// `1712800800123`
110    Milli,
111    /// `1712800800123456`
112    Micro,
113    /// Both `1712800800123` (ms) and `1712800800123456` (us) maps to `2024-04-11`.
114    ///
115    /// Only works for `[1973-03-03 09:46:40, 5138-11-16 09:46:40)`.
116    ///
117    /// This option is backward compatible.
118    GuessNumberUnit,
119}
120
121impl TimestamptzHandling {
122    pub const OPTION_KEY: &'static str = "timestamptz.handling.mode";
123
124    pub fn from_options(value: &str) -> Result<Self, InvalidOptionError> {
125        let mode = match value {
126            "utc_string" => Self::UtcString,
127            "utc_without_suffix" => Self::UtcWithoutSuffix,
128            "micro" => Self::Micro,
129            "milli" => Self::Milli,
130            "guess_number_unit" => Self::GuessNumberUnit,
131            v => bail_invalid_option_error!("unrecognized {} value {}", Self::OPTION_KEY, v),
132        };
133        Ok(mode)
134    }
135}
136
137#[derive(Clone, Debug)]
138pub enum TimestampHandling {
139    Milli,
140    Micro,
141    GuessNumberUnit,
142}
143
144#[derive(Clone, Debug)]
145pub enum JsonValueHandling {
146    AsValue,
147    AsString,
148}
149#[derive(Clone, Debug)]
150pub enum NumericHandling {
151    Strict,
152    // should integer be parsed to float
153    Relax {
154        // should "3.14" be parsed to 3.14 in float
155        string_parsing: bool,
156    },
157}
158#[derive(Clone, Debug)]
159pub enum BooleanHandling {
160    Strict,
161    // should integer 1,0 be parsed to boolean (debezium)
162    Relax {
163        // should "True" "False" be parsed to true or false in boolean
164        string_parsing: bool,
165        // should string "1" "0" be paesed to boolean (cannal + mysql)
166        string_integer_parsing: bool,
167    },
168}
169
170#[derive(Clone, Debug)]
171pub enum VarcharHandling {
172    // do not allow other types cast to varchar
173    Strict,
174    // allow Json Value (Null, Bool, I64, I128, U64, U128, F64) cast to varchar
175    OnlyPrimaryTypes,
176    // allow all type cast to varchar (inc. Array, Object)
177    AllTypes,
178}
179
180#[derive(Clone, Debug)]
181pub enum StructHandling {
182    // only allow object parsed to struct
183    Strict,
184    // allow string containing a serialized json object (like "{\"a\": 1, \"b\": 2}") parsed to
185    // struct
186    AllowJsonString,
187}
188
189#[derive(Clone, Debug)]
190pub struct JsonParseOptions {
191    pub bytea_handling: ByteaHandling,
192    pub time_handling: TimeHandling,
193    pub timestamp_handling: TimestampHandling,
194    pub timestamptz_handling: TimestamptzHandling,
195    pub json_value_handling: JsonValueHandling,
196    pub numeric_handling: NumericHandling,
197    pub boolean_handling: BooleanHandling,
198    pub varchar_handling: VarcharHandling,
199    pub struct_handling: StructHandling,
200    pub bigint_unsigned_handling: BigintUnsignedHandlingMode,
201    pub ignoring_keycase: bool,
202    pub handle_toast_columns: bool,
203}
204
205impl Default for JsonParseOptions {
206    fn default() -> Self {
207        Self::DEFAULT.clone()
208    }
209}
210
211impl JsonParseOptions {
212    pub const CANAL: JsonParseOptions = JsonParseOptions {
213        bytea_handling: ByteaHandling::Standard,
214        time_handling: TimeHandling::Micro,
215        timestamp_handling: TimestampHandling::GuessNumberUnit, // backward-compatible
216        timestamptz_handling: TimestamptzHandling::GuessNumberUnit, // backward-compatible
217        json_value_handling: JsonValueHandling::AsValue,
218        numeric_handling: NumericHandling::Relax {
219            string_parsing: true,
220        },
221        boolean_handling: BooleanHandling::Relax {
222            string_parsing: true,
223            string_integer_parsing: true,
224        },
225        varchar_handling: VarcharHandling::Strict,
226        struct_handling: StructHandling::Strict,
227        bigint_unsigned_handling: BigintUnsignedHandlingMode::Long, // default to long mode
228        ignoring_keycase: true,
229        handle_toast_columns: false,
230    };
231    pub const DEFAULT: JsonParseOptions = JsonParseOptions {
232        bytea_handling: ByteaHandling::Standard,
233        time_handling: TimeHandling::Micro,
234        timestamp_handling: TimestampHandling::GuessNumberUnit, // backward-compatible
235        timestamptz_handling: TimestamptzHandling::GuessNumberUnit, // backward-compatible
236        json_value_handling: JsonValueHandling::AsValue,
237        numeric_handling: NumericHandling::Relax {
238            string_parsing: true,
239        },
240        boolean_handling: BooleanHandling::Strict,
241        varchar_handling: VarcharHandling::OnlyPrimaryTypes,
242        struct_handling: StructHandling::AllowJsonString,
243        bigint_unsigned_handling: BigintUnsignedHandlingMode::Long, // default to long mode
244        ignoring_keycase: true,
245        handle_toast_columns: false,
246    };
247
248    pub fn new_for_debezium(
249        timestamptz_handling: TimestamptzHandling,
250        timestamp_handling: TimestampHandling,
251        time_handling: TimeHandling,
252        bigint_unsigned_handling: BigintUnsignedHandlingMode,
253        handle_toast_columns: bool,
254    ) -> Self {
255        Self {
256            bytea_handling: ByteaHandling::Base64,
257            time_handling,
258            timestamp_handling,
259            timestamptz_handling,
260            json_value_handling: JsonValueHandling::AsString,
261            numeric_handling: NumericHandling::Relax {
262                string_parsing: false,
263            },
264            boolean_handling: BooleanHandling::Relax {
265                string_parsing: false,
266                string_integer_parsing: false,
267            },
268            varchar_handling: VarcharHandling::Strict,
269            struct_handling: StructHandling::Strict,
270            bigint_unsigned_handling,
271            ignoring_keycase: true,
272            handle_toast_columns,
273        }
274    }
275
276    pub fn parse<'a>(
277        &self,
278        value: &'a BorrowedValue<'a>,
279        type_expected: &DataType,
280    ) -> AccessResult<DatumCow<'a>> {
281        let create_error = || AccessError::TypeError {
282            expected: format!("{:?}", type_expected),
283            got: value.value_type().to_string(),
284            value: value.to_string(),
285        };
286        let v: ScalarImpl = match (type_expected, value.value_type()) {
287            (_, ValueType::Null) => return Ok(DatumCow::NULL),
288            // ---- Boolean -----
289            (DataType::Boolean, ValueType::Bool) => value.as_bool().unwrap().into(),
290
291            (
292                DataType::Boolean,
293                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
294            ) if matches!(self.boolean_handling, BooleanHandling::Relax { .. })
295                && matches!(value.as_i64(), Some(0i64) | Some(1i64)) =>
296            {
297                (value.as_i64() == Some(1i64)).into()
298            }
299
300            (DataType::Boolean, ValueType::String)
301                if matches!(
302                    self.boolean_handling,
303                    BooleanHandling::Relax {
304                        string_parsing: true,
305                        ..
306                    }
307                ) =>
308            {
309                match value.as_str().unwrap().to_lowercase().as_str() {
310                    "true" => true.into(),
311                    "false" => false.into(),
312                    c @ ("1" | "0")
313                        if matches!(
314                            self.boolean_handling,
315                            BooleanHandling::Relax {
316                                string_parsing: true,
317                                string_integer_parsing: true
318                            }
319                        ) =>
320                    {
321                        if c == "1" {
322                            true.into()
323                        } else {
324                            false.into()
325                        }
326                    }
327                    _ => Err(create_error())?,
328                }
329            }
330            // ---- Int16 -----
331            (
332                DataType::Int16,
333                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
334            ) => value.try_as_i16().map_err(|_| create_error())?.into(),
335
336            (DataType::Int16, ValueType::String)
337                if matches!(
338                    self.numeric_handling,
339                    NumericHandling::Relax {
340                        string_parsing: true
341                    }
342                ) =>
343            {
344                value
345                    .as_str()
346                    .unwrap()
347                    .parse::<i16>()
348                    .map_err(|_| create_error())?
349                    .into()
350            }
351            // ---- Int32 -----
352            (
353                DataType::Int32,
354                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
355            ) => value.try_as_i32().map_err(|_| create_error())?.into(),
356
357            (DataType::Int32, ValueType::String)
358                if matches!(
359                    self.numeric_handling,
360                    NumericHandling::Relax {
361                        string_parsing: true
362                    }
363                ) =>
364            {
365                value
366                    .as_str()
367                    .unwrap()
368                    .parse::<i32>()
369                    .map_err(|_| create_error())?
370                    .into()
371            }
372            // ---- Int64 -----
373            (
374                DataType::Int64,
375                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
376            ) => value.try_as_i64().map_err(|_| create_error())?.into(),
377
378            (DataType::Int64, ValueType::String)
379                if matches!(
380                    self.numeric_handling,
381                    NumericHandling::Relax {
382                        string_parsing: true
383                    }
384                ) =>
385            {
386                value
387                    .as_str()
388                    .unwrap()
389                    .parse::<i64>()
390                    .map_err(|_| create_error())?
391                    .into()
392            }
393            // ---- Float32 -----
394            (
395                DataType::Float32,
396                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
397            ) if matches!(self.numeric_handling, NumericHandling::Relax { .. }) => {
398                (value.try_as_i64().map_err(|_| create_error())? as f32).into()
399            }
400            (DataType::Float32, ValueType::String)
401                if matches!(
402                    self.numeric_handling,
403                    NumericHandling::Relax {
404                        string_parsing: true
405                    }
406                ) =>
407            {
408                value
409                    .as_str()
410                    .unwrap()
411                    .parse::<f32>()
412                    .map_err(|_| create_error())?
413                    .into()
414            }
415            (DataType::Float32, ValueType::F64) => {
416                value.try_as_f32().map_err(|_| create_error())?.into()
417            }
418            // ---- Float64 -----
419            (
420                DataType::Float64,
421                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
422            ) if matches!(self.numeric_handling, NumericHandling::Relax { .. }) => {
423                (value.try_as_i64().map_err(|_| create_error())? as f64).into()
424            }
425            (DataType::Float64, ValueType::String)
426                if matches!(
427                    self.numeric_handling,
428                    NumericHandling::Relax {
429                        string_parsing: true
430                    }
431                ) =>
432            {
433                value
434                    .as_str()
435                    .unwrap()
436                    .parse::<f64>()
437                    .map_err(|_| create_error())?
438                    .into()
439            }
440            (DataType::Float64, ValueType::F64) => {
441                value.try_as_f64().map_err(|_| create_error())?.into()
442            }
443            // ---- Decimal -----
444            (DataType::Decimal, ValueType::I128 | ValueType::U128) => {
445                Decimal::from_str(&value.try_as_i128().map_err(|_| create_error())?.to_string())
446                    .map_err(|_| create_error())?
447                    .into()
448            }
449            (DataType::Decimal, ValueType::I64 | ValueType::U64) => {
450                let i64_val = value.try_as_i64().map_err(|_| create_error())?;
451                Decimal::from(i64_val).into()
452            }
453            (DataType::Decimal, ValueType::String) => {
454                let str_val = value.as_str().unwrap();
455                // the following values are special string generated by Debezium and should be handled separately
456                match str_val {
457                    "NAN" => return Ok(DatumCow::Owned(Some(ScalarImpl::Decimal(Decimal::NaN)))),
458                    "POSITIVE_INFINITY" => {
459                        return Ok(DatumCow::Owned(Some(ScalarImpl::Decimal(
460                            Decimal::PositiveInf,
461                        ))));
462                    }
463                    "NEGATIVE_INFINITY" => {
464                        return Ok(DatumCow::Owned(Some(ScalarImpl::Decimal(
465                            Decimal::NegativeInf,
466                        ))));
467                    }
468                    _ => {}
469                }
470
471                Decimal::from_str(str_val)
472                    .or_else(|_err| {
473                        try_base64_decode_decimal(
474                            str_val,
475                            self.bigint_unsigned_handling,
476                            create_error,
477                        )
478                    })?
479                    .into()
480            }
481
482            (DataType::Decimal, ValueType::F64) => {
483                Decimal::try_from(value.try_as_f64().map_err(|_| create_error())?)
484                    .map_err(|_| create_error())?
485                    .into()
486            }
487            (DataType::Decimal, ValueType::Object) => {
488                // ref https://github.com/risingwavelabs/risingwave/issues/10628
489                // handle debezium json (variable scale): {"scale": int, "value": bytes}
490                let scale = value
491                    .get("scale")
492                    .ok_or_else(create_error)?
493                    .as_i32()
494                    .unwrap();
495                let value = value
496                    .get("value")
497                    .ok_or_else(create_error)?
498                    .as_str()
499                    .unwrap()
500                    .as_bytes();
501                let unscaled = BigInt::from_signed_bytes_be(value);
502                let decimal = scaled_bigint_to_rust_decimal(unscaled, scale as _)?;
503                ScalarImpl::Decimal(Decimal::Normalized(decimal))
504            }
505            // ---- Date -----
506            (
507                DataType::Date,
508                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
509            ) => Date::with_days_since_unix_epoch(value.try_as_i32().map_err(|_| create_error())?)
510                .map_err(|_| create_error())?
511                .into(),
512            (DataType::Date, ValueType::String) => value
513                .as_str()
514                .unwrap()
515                .parse::<Date>()
516                .map_err(|_| create_error())?
517                .into(),
518            // ---- Varchar -----
519            (DataType::Varchar, ValueType::String) => {
520                return Ok(DatumCow::Borrowed(Some(value.as_str().unwrap().into())));
521            }
522            (
523                DataType::Varchar,
524                ValueType::Bool
525                | ValueType::I64
526                | ValueType::I128
527                | ValueType::U64
528                | ValueType::U128
529                | ValueType::F64,
530            ) if matches!(self.varchar_handling, VarcharHandling::OnlyPrimaryTypes) => {
531                value.to_string().into()
532            }
533            (
534                DataType::Varchar,
535                ValueType::Bool
536                | ValueType::I64
537                | ValueType::I128
538                | ValueType::U64
539                | ValueType::U128
540                | ValueType::F64
541                | ValueType::Array
542                | ValueType::Object,
543            ) if matches!(self.varchar_handling, VarcharHandling::AllTypes) => {
544                value.to_string().into()
545            }
546            // ---- Time -----
547            (DataType::Time, ValueType::String) => value
548                .as_str()
549                .unwrap()
550                .parse::<Time>()
551                .map_err(|_| create_error())?
552                .into(),
553            (
554                DataType::Time,
555                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
556            ) => value
557                .as_i64()
558                .map(|i| match self.time_handling {
559                    TimeHandling::Milli => Time::with_milli(i as u32),
560                    TimeHandling::Micro => Time::with_micro(i as u64),
561                })
562                .unwrap()
563                .map_err(|_| create_error())?
564                .into(),
565            // ---- Timestamp -----
566            (DataType::Timestamp, ValueType::String) => value
567                .as_str()
568                .unwrap()
569                .parse::<Timestamp>()
570                .map_err(|_| create_error())?
571                .into(),
572            (
573                DataType::Timestamp,
574                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
575            ) => value
576                .as_i64()
577                .map(|num| match self.timestamp_handling {
578                    // Only when user configures debezium.time.precision.mode = 'connect',
579                    // the Milli branch will be executed
580                    TimestampHandling::Milli => {
581                        Timestamp::with_millis(num).map_err(|_| create_error())
582                    }
583                    TimestampHandling::Micro => {
584                        Timestamp::with_micros(num).map_err(|_| create_error())
585                    }
586                    TimestampHandling::GuessNumberUnit => {
587                        i64_to_timestamp(num).map_err(|_| create_error())
588                    }
589                })
590                .ok_or_else(create_error)??
591                .into(),
592            // ---- Timestamptz -----
593            (DataType::Timestamptz, ValueType::String) => match self.timestamptz_handling {
594                TimestamptzHandling::UtcWithoutSuffix => value
595                    .as_str()
596                    .unwrap()
597                    .parse::<Timestamp>()
598                    .map(|naive_utc| {
599                        Timestamptz::from_micros(naive_utc.0.and_utc().timestamp_micros())
600                    })
601                    .map_err(|_| create_error())?
602                    .into(),
603                // Unless explicitly requested `utc_without_utc`, we parse string with `YYYY-MM-DDTHH:MM:SSZ`.
604                _ => value
605                    .as_str()
606                    .unwrap()
607                    .parse::<Timestamptz>()
608                    .map_err(|_| create_error())?
609                    .into(),
610            },
611            (
612                DataType::Timestamptz,
613                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
614            ) => value
615                .as_i64()
616                .and_then(|num| match self.timestamptz_handling {
617                    TimestamptzHandling::GuessNumberUnit => i64_to_timestamptz(num).ok(),
618                    TimestamptzHandling::Micro => Some(Timestamptz::from_micros(num)),
619                    TimestamptzHandling::Milli => Timestamptz::from_millis(num),
620                    // When explicitly requested string format, number without units are rejected.
621                    TimestamptzHandling::UtcString | TimestamptzHandling::UtcWithoutSuffix => None,
622                })
623                .ok_or_else(create_error)?
624                .into(),
625            // ---- Interval -----
626            (DataType::Interval, ValueType::String) => value
627                .as_str()
628                .unwrap()
629                .parse::<Interval>()
630                .map_err(|_| create_error())?
631                .into(),
632            // ---- Struct -----
633            (DataType::Struct(struct_type_info), ValueType::Object) => {
634                // Collecting into a Result<Vec<_>> doesn't reserve the capacity in advance, so we `Vec::with_capacity` instead.
635                // https://github.com/rust-lang/rust/issues/48994
636                let mut fields = Vec::with_capacity(struct_type_info.len());
637                for (field_name, field_type) in struct_type_info.iter() {
638                    let field_value = json_object_get_case_insensitive(value, field_name)
639                            .unwrap_or_else(|| {
640                                let error = AccessError::Undefined {
641                                    name: field_name.to_owned(),
642                                    path: struct_type_info.to_string(), // TODO: this is not good, we should maintain a path stack
643                                };
644                                // TODO: is it possible to unify the logging with the one in `do_action`?
645                                static LOG_SUPPRESSOR: LazyLock<LogSuppressor> =  LazyLock::new(LogSuppressor::default);
646                                if let Ok(suppressed_count) = LOG_SUPPRESSOR.check() {
647                                    tracing::warn!(error = %error.as_report(), suppressed_count, "undefined nested field, padding with `NULL`");
648                                }
649                                &BorrowedValue::Static(simd_json::StaticNode::Null)
650                            });
651                    fields.push(
652                        self.parse(field_value, field_type)
653                            .map(|d| d.to_owned_datum())?,
654                    );
655                }
656                StructValue::new(fields).into()
657            }
658
659            // String containing json object, e.g. "{\"a\": 1, \"b\": 2}"
660            // Try to parse it as json object.
661            (DataType::Struct(_), ValueType::String)
662                if matches!(self.struct_handling, StructHandling::AllowJsonString) =>
663            {
664                // TODO: avoid copy by accepting `&mut BorrowedValue` in `parse` method.
665                let mut value = value.as_str().unwrap().as_bytes().to_vec();
666                let value =
667                    simd_json::to_borrowed_value(&mut value[..]).map_err(|_| create_error())?;
668                return self
669                    .parse(&value, type_expected)
670                    .map(|d| d.to_owned_datum().into());
671            }
672
673            // ---- List -----
674            (DataType::List(list_type), ValueType::Array) => ListValue::new({
675                let item_type = list_type.elem();
676                let array = value.as_array().unwrap();
677                let mut builder = item_type.create_array_builder(array.len());
678                for v in array {
679                    let value = self.parse(v, item_type)?;
680                    builder.append(value);
681                }
682                builder.finish()
683            })
684            .into(),
685            // ---- Vector -----
686            (DataType::Vector(size), ValueType::Array) => {
687                let array = value.as_array().unwrap();
688                if array.len() != *size {
689                    Err(create_error())?
690                }
691                let mut elems = Vec::with_capacity(array.len());
692                for v in array {
693                    let value = match v.value_type() {
694                        ValueType::I64 | ValueType::I128 => {
695                            let i128_value = v.try_as_i128().map_err(|_| create_error())?;
696                            i128_value
697                                .to_string()
698                                .parse::<f32>()
699                                .map_err(|_| create_error())?
700                        }
701                        ValueType::U64 | ValueType::U128 => {
702                            let u128_value = v.try_as_u128().map_err(|_| create_error())?;
703                            u128_value
704                                .to_string()
705                                .parse::<f32>()
706                                .map_err(|_| create_error())?
707                        }
708                        ValueType::F64 => {
709                            let f64_value = v.try_as_f64().map_err(|_| create_error())?;
710                            if !f64_value.is_finite() {
711                                Err(create_error())?
712                            }
713                            f64_value
714                                .to_string()
715                                .parse::<f32>()
716                                .map_err(|_| create_error())?
717                        }
718                        _ => Err(create_error())?,
719                    };
720                    let finite = Finite32::try_from(value).map_err(|_| create_error())?;
721                    elems.push(finite);
722                }
723                VectorVal::from(elems).into()
724            }
725            // Vector emitted as a string. Reached via the Java `PgVectorToStringConverter`,
726            // which downgrades the pgvector column from `DoubleVector` (ARRAY) to STRING so
727            // that the Debezium unchanged-TOAST placeholder can flow through without tripping
728            // Connect's schema validation. The value is either the pgvector text form
729            // `"[a,b,...]"` or the placeholder; the latter is mapped to a sentinel `VectorVal`
730            // that the materialize executor recognises and replaces with the old row value.
731            (DataType::Vector(size), ValueType::String) => {
732                let s = value.as_str().unwrap();
733                if self.handle_toast_columns
734                    && s.len() == DEBEZIUM_UNAVAILABLE_VALUE.len()
735                    && s == DEBEZIUM_UNAVAILABLE_VALUE
736                {
737                    // Build the sentinel at the declared dimension so it passes
738                    // `check_datum_type` in the chunk builder; the materialize
739                    // executor recognises it by checking that every element
740                    // equals `DEBEZIUM_UNAVAILABLE_VECTOR_ELEM`.
741                    debezium_unavailable_vector(*size).into()
742                } else {
743                    VectorVal::from_text(s, *size)
744                        .map_err(|_| create_error())?
745                        .into()
746                }
747            }
748
749            // ---- Bytea -----
750            (DataType::Bytea, ValueType::String) => {
751                let value_str = value.as_str().unwrap();
752
753                match self.bytea_handling {
754                    ByteaHandling::Standard => {
755                        let mut buf = Vec::new();
756                        str_to_bytea(value_str, &mut buf).map_err(|_| create_error())?;
757                        buf.into()
758                    }
759                    ByteaHandling::Base64 => base64::engine::general_purpose::STANDARD
760                        .decode(value_str)
761                        .map_err(|_| create_error())?
762                        .into_boxed_slice()
763                        .into(),
764                }
765            }
766            // Handle Debezium PostGIS geometry type: {"srid": <int>, "wkb": <base64_string>}
767            // We extract the wkb field and decode it as EWKB bytes
768            (DataType::Bytea, ValueType::Object) => {
769                match try_parse_debezium_geometry_as_bytea(value, create_error)? {
770                    Some(bytes) => bytes.into(),
771                    None => Err(create_error())?,
772                }
773            }
774            // ---- Jsonb -----
775            (DataType::Jsonb, ValueType::String)
776                if matches!(self.json_value_handling, JsonValueHandling::AsString) =>
777            {
778                // Check if this value is the Debezium unavailable value (TOAST handling for postgres-cdc).
779                // Debezium will base64 encode the bytea type placeholder.
780                // When a placeholder is encountered, it is converted into a jsonb format placeholder to match the original type.
781                match self.handle_toast_columns {
782                    true => JsonbVal::from_debezium_unavailable_value(value.as_str().unwrap())
783                        .map_err(|_| create_error())?
784                        .into(),
785                    false => JsonbVal::from_str(value.as_str().unwrap())
786                        .map_err(|_| create_error())?
787                        .into(),
788                }
789            }
790            (DataType::Jsonb, _)
791                if matches!(self.json_value_handling, JsonValueHandling::AsValue) =>
792            {
793                let value: serde_json::Value =
794                    value.clone().try_into().map_err(|_| create_error())?;
795                JsonbVal::from(value).into()
796            }
797            // ---- Int256 -----
798            (
799                DataType::Int256,
800                ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128,
801            ) => Int256::from(value.try_as_i64().map_err(|_| create_error())?).into(),
802
803            (DataType::Int256, ValueType::String) => Int256::from_str(value.as_str().unwrap())
804                .map_err(|_| create_error())?
805                .into(),
806
807            (_expected, _got) => Err(create_error())?,
808        };
809        Ok(DatumCow::Owned(Some(v)))
810    }
811}
812
813/// Try to decode a base64-encoded decimal string for unsigned bigint handling in Precise mode.
814///
815/// This is used when processing CDC data from upstream systems with unsigned bigint (e.g., MySQL CDC).
816/// When users configure `debezium.bigint.unsigned.handling.mode='precise'`, Debezium converts
817/// unsigned bigint to base64-encoded decimal.
818///
819/// Reference: <https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-property-bigint-unsigned-handling-mode>.
820fn try_base64_decode_decimal(
821    str_val: &str,
822    bigint_unsigned_handling: BigintUnsignedHandlingMode,
823    create_error: impl Fn() -> AccessError,
824) -> Result<Decimal, AccessError> {
825    match bigint_unsigned_handling {
826        BigintUnsignedHandlingMode::Precise => {
827            // A better approach would be to get bytes + org.apache.kafka.connect.data.Decimal from schema
828            // instead of string, as described in <https://github.com/risingwavelabs/risingwave/issues/16852>.
829            // However, Rust doesn't have a library to parse Kafka Connect metadata, so we'll refactor this
830            // after implementing that functionality.
831            let value = base64::engine::general_purpose::STANDARD
832                .decode(str_val)
833                .map_err(|_| create_error())?;
834            let unscaled = num_bigint::BigInt::from_signed_bytes_be(&value);
835            Decimal::from_str(&unscaled.to_string()).map_err(|_| create_error())
836        }
837        BigintUnsignedHandlingMode::Long => {
838            // In Long mode, don't attempt base64 decoding
839            Err(create_error())
840        }
841    }
842}
843
844pub struct JsonAccess<'a> {
845    value: BorrowedValue<'a>,
846    options: &'a JsonParseOptions,
847}
848
849impl<'a> JsonAccess<'a> {
850    pub fn new_with_options(value: BorrowedValue<'a>, options: &'a JsonParseOptions) -> Self {
851        Self { value, options }
852    }
853
854    pub fn new(value: BorrowedValue<'a>) -> Self {
855        Self::new_with_options(value, &JsonParseOptions::DEFAULT)
856    }
857}
858
859impl Access for JsonAccess<'_> {
860    fn access<'a>(&'a self, path: &[&str], type_expected: &DataType) -> AccessResult<DatumCow<'a>> {
861        let mut value = &self.value;
862
863        for (idx, &key) in path.iter().enumerate() {
864            if let Some(sub_value) = if self.options.ignoring_keycase {
865                json_object_get_case_insensitive(value, key)
866            } else {
867                value.get(key)
868            } {
869                value = sub_value;
870            } else {
871                Err(AccessError::Undefined {
872                    name: key.to_owned(),
873                    path: path.iter().take(idx).join("."),
874                })?;
875            }
876        }
877
878        self.options.parse(value, type_expected)
879    }
880}
881
882/// Get a value from a json object by key, case insensitive.
883///
884/// Returns `None` if the given json value is not an object, or the key is not found.
885fn json_object_get_case_insensitive<'b>(
886    v: &'b simd_json::BorrowedValue<'b>,
887    key: &str,
888) -> Option<&'b simd_json::BorrowedValue<'b>> {
889    let obj = v.as_object()?;
890    let value = obj.get(key);
891    if value.is_some() {
892        return value; // fast path
893    }
894    for (k, v) in obj {
895        if k.eq_ignore_ascii_case(key) {
896            return Some(v);
897        }
898    }
899    None
900}