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