Skip to main content

reifydb_routine/function/json/
object.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns};
5use reifydb_type::{
6	util::bitvec::BitVec,
7	value::{Value, r#type::Type},
8};
9
10use crate::routine::{Function, FunctionKind, Routine, RoutineInfo, context::FunctionContext, error::RoutineError};
11
12pub struct JsonObject {
13	info: RoutineInfo,
14}
15
16impl Default for JsonObject {
17	fn default() -> Self {
18		Self::new()
19	}
20}
21
22impl JsonObject {
23	pub fn new() -> Self {
24		Self {
25			info: RoutineInfo::new("json::object"),
26		}
27	}
28}
29
30impl<'a> Routine<FunctionContext<'a>> for JsonObject {
31	fn info(&self) -> &RoutineInfo {
32		&self.info
33	}
34
35	fn return_type(&self, _input_types: &[Type]) -> Type {
36		Type::Any
37	}
38
39	fn execute(&self, ctx: &mut FunctionContext<'a>, args: &Columns) -> Result<Columns, RoutineError> {
40		// Check for any option columns and unwrap them
41		let mut unwrapped: Vec<_> = Vec::with_capacity(args.len());
42		let mut combined_bv: Option<BitVec> = None;
43
44		for col in args.iter() {
45			let (data, bitvec) = col.data().unwrap_option();
46			if let Some(bv) = bitvec {
47				combined_bv = Some(match combined_bv {
48					Some(existing) => existing.and(bv),
49					None => bv.clone(),
50				});
51			}
52			unwrapped.push(data);
53		}
54
55		if !unwrapped.len().is_multiple_of(2) {
56			return Err(RoutineError::FunctionExecutionFailed {
57				function: ctx.fragment.clone(),
58				reason: "json::object requires an even number of arguments (key-value pairs)"
59					.to_string(),
60			});
61		}
62
63		// Validate that key columns (even indices) are Utf8
64		for i in (0..unwrapped.len()).step_by(2) {
65			let col_data = unwrapped[i];
66			match col_data {
67				ColumnBuffer::Utf8 {
68					..
69				} => {}
70				other => {
71					return Err(RoutineError::FunctionInvalidArgumentType {
72						function: ctx.fragment.clone(),
73						argument_index: i,
74						expected: vec![Type::Utf8],
75						actual: other.get_type(),
76					});
77				}
78			}
79		}
80
81		let row_count = if unwrapped.is_empty() {
82			1
83		} else {
84			unwrapped[0].len()
85		};
86		let num_pairs = unwrapped.len() / 2;
87		let mut results: Vec<Box<Value>> = Vec::with_capacity(row_count);
88
89		for row in 0..row_count {
90			let mut fields = Vec::with_capacity(num_pairs);
91			for pair in 0..num_pairs {
92				let key_data = unwrapped[pair * 2];
93				let val_data = unwrapped[pair * 2 + 1];
94
95				let key: String = key_data.get_as::<String>(row).unwrap_or_default();
96				let value = val_data.get_value(row);
97
98				fields.push((key, value));
99			}
100			results.push(Box::new(Value::Record(fields)));
101		}
102
103		let result_data = ColumnBuffer::any(results);
104		let final_data = match combined_bv {
105			Some(bv) => ColumnBuffer::Option {
106				inner: Box::new(result_data),
107				bitvec: bv,
108			},
109			None => result_data,
110		};
111
112		Ok(Columns::new(vec![ColumnWithName::new(ctx.fragment.clone(), final_data)]))
113	}
114}
115
116impl Function for JsonObject {
117	fn kinds(&self) -> &[FunctionKind] {
118		&[FunctionKind::Scalar]
119	}
120}