// Copyright 2026 Noverberrain.
// SPDX-License-Identifier: Apache-2.0
///|
/// Complete (not partial-update) MySQL binary JSON, with exact integer values.
/// Object members retain binary order. Floating bits preserve negative zero.
pub(all) enum BinaryJsonValue {
Null
Boolean(Bool)
Integer(IntegerValue)
DoubleNumber(value~ : Double, bits~ : UInt64)
Text(String)
Array(ReadOnlyArray[BinaryJsonValue])
Object(ReadOnlyArray[(String, BinaryJsonValue)])
} derive(Debug, Eq)
///|
pub extend BinaryJsonValue with Eq::{equal, not_equal}
///|
pub extend BinaryJsonValue with @debug.Debug::{to_repr}
///|
pub struct DecodedJson {
value : BinaryJsonValue
bytes_read : Int32
} derive(Debug, Eq)
///|
pub extend DecodedJson with Eq::{equal, not_equal}
///|
pub extend DecodedJson with @debug.Debug::{to_repr}
///|
/// Decode MYSQL_TYPE_JSON (245) with its 1..4 byte length prefix. The byte
/// budget excludes the prefix; depth counts the root as one (hard cap 128),
/// nodes count values including containers. Opaque types and empty documents
/// are rejected. String bytes must be UTF-8; no replacement or BOM stripping.
pub fn TableColumn::decode_json(
self : TableColumn,
data : BytesView,
file_offset~ : UInt64,
column_index~ : Int32,
max_value_bytes~ : Int32 = 16_777_216,
max_depth~ : Int32 = 64,
max_nodes~ : Int32 = 65536,
) -> DecodedJson raise DecodeError {
guard max_value_bytes >= 0 &&
max_value_bytes <= 2_147_483_643 &&
max_depth > 0 &&
max_depth <= 128 &&
max_nodes > 0 else {
raise InvalidJsonLimits(max_value_bytes~, max_depth~, max_nodes~)
}
guard self.wire_type == 245 else {
raise UnsupportedJsonColumn(
file_offset~,
column_index~,
wire_type=self.wire_type,
)
}
guard self.metadata.length() == 1 &&
self.metadata[0] >= 1 &&
self.metadata[0] <= 4 else {
raise InvalidJsonMetadata(file_offset~, column_index~)
}
let width = self.metadata[0].to_int()
guard data.length() >= width else {
raise TruncatedColumnValue(
file_offset~,
column_index~,
expected=width,
actual=data.length(),
)
}
let mut length = 0UL
for i in 0..= bytes_read else {
raise TruncatedColumnValue(
file_offset~,
column_index~,
expected=bytes_read,
actual=data.length(),
)
}
guard file_offset <=
0xffffffffffffffffUL - bytes_read.reinterpret_as_uint().to_uint64() else {
raise InvalidJsonValue(
file_offset~,
column_index~,
value_offset=0,
reason="physical offset range overflows",
)
}
let reader = JsonBinaryReader::{
data: data[width:bytes_read],
file_offset,
column_index,
max_depth,
max_nodes,
nodes: 0,
}
guard length > 0 else {
raise reader.invalid(0, "empty binary JSON document")
}
let size = reader.extent(reader.data[0], 1, reader.data.length())
guard size == reader.data.length() - 1 else {
raise reader.invalid(1 + size, "trailing JSON document bytes")
}
let value = reader.value(reader.data[0], 1, reader.data.length(), 1)
{ value, bytes_read, }
}