diff --git a/dozer-sql/expression/src/error.rs b/dozer-sql/expression/src/error.rs index 7e4ee3572e..1e01f16df2 100644 --- a/dozer-sql/expression/src/error.rs +++ b/dozer-sql/expression/src/error.rs @@ -75,6 +75,10 @@ pub enum Error { FailedToCalculateVincentyDistance( #[from] dozer_types::geo::vincenty_distance::FailedToConvergeError, ), + #[error("Fail to deserialize: {0}")] + FailedToDeserialize( + #[from] dozer_types::errors::types::DeserializationError, + ), #[error("Invalid like escape: {0}")] InvalidLikeEscape(#[from] like::InvalidEscapeError), diff --git a/dozer-sql/expression/src/json_functions.rs b/dozer-sql/expression/src/json_functions.rs index d7528bbfa1..b6fed7adf7 100644 --- a/dozer-sql/expression/src/json_functions.rs +++ b/dozer-sql/expression/src/json_functions.rs @@ -2,17 +2,20 @@ use crate::arg_utils::validate_num_arguments; use crate::error::Error; use crate::execution::Expression; -use dozer_types::json_types::JsonValue; -use dozer_types::types::Record; +use dozer_types::json_types::{field_to_json_value, json_value_to_serde_json, JsonValue, serde_json_to_json_value}; +use dozer_types::types::{FieldType, Record}; use dozer_types::types::{Field, Schema}; use jsonpath::{JsonPathFinder, JsonPathInst}; use std::fmt::{Display, Formatter}; use std::str::FromStr; +use dozer_types::serde_json; +use dozer_types::serde_json::Value; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] pub enum JsonFunctionType { JsonValue, JsonQuery, + JsonObject, } impl Display for JsonFunctionType { @@ -20,6 +23,7 @@ impl Display for JsonFunctionType { match self { JsonFunctionType::JsonValue => f.write_str("JSON_VALUE".to_string().as_str()), JsonFunctionType::JsonQuery => f.write_str("JSON_QUERY".to_string().as_str()), + JsonFunctionType::JsonObject => f.write_str("JSON_OBJECT".to_string().as_str()), } } } @@ -29,6 +33,7 @@ impl JsonFunctionType { match name { "json_value" => Some(JsonFunctionType::JsonValue), "json_query" => Some(JsonFunctionType::JsonQuery), + "json_object" => Some(JsonFunctionType::JsonObject), _ => None, } } @@ -42,9 +47,43 @@ impl JsonFunctionType { match self { JsonFunctionType::JsonValue => self.evaluate_json_value(schema, args, record), JsonFunctionType::JsonQuery => self.evaluate_json_query(schema, args, record), + JsonFunctionType::JsonObject => self.evaluate_json_object(schema, args, record), } } + pub(crate) fn evaluate_json_object( + &self, + schema: &Schema, + args: &mut [Expression], + record: &Record, + ) -> Result { + let mut json_output = serde_json::Map::new(); + for arg in args { + if arg.get_type(schema)?.return_type == FieldType::String { + let arg_string = arg.to_string(schema); + let object_name: &str = arg_string.split(":").collect::>()[0].trim(); + let mut object_value: &str = arg_string.split(":").collect::>()[1].trim(); + if object_value.contains(".") { + object_value = object_value.split(".").collect::>()[1]; + } + let column_num = schema.fields.iter().position(|x| x.name == object_value); + if let Some(idx) = column_num { + let val = Expression::Column {index: idx}.evaluate(record, schema)?; + json_output.insert( + object_name.to_string(), + json_value_to_serde_json(&field_to_json_value(val)) + ); + } else { + json_output.insert( + object_name.to_string(), + json_value_to_serde_json(&field_to_json_value(Field::String(object_value.to_string()))) + ); + } + } + } + Ok(Field::Json(serde_json_to_json_value(Value::Object(json_output))?)) + } + pub(crate) fn evaluate_json_value( &self, schema: &Schema, diff --git a/dozer-sql/src/expression/tests/json_functions.rs b/dozer-sql/src/expression/tests/json_functions.rs index 619b85aa00..95c20b0ccf 100644 --- a/dozer-sql/src/expression/tests/json_functions.rs +++ b/dozer-sql/src/expression/tests/json_functions.rs @@ -38,6 +38,34 @@ fn test_json_value() { ); assert_eq!(f, Field::Json(String::from("Bristol").into())); + + let town = "Bristol"; + let f = run_fct( + "SELECT JSON_OBJECT('id: user.id', 'town: user.town', 'town_2: Bristol') as info FROM users", + Schema::default() + .field( + FieldDefinition::new( + String::from("id"), + FieldType::Int, + false, + SourceDefinition::Dynamic, + ), + false, + ) + .field( + FieldDefinition::new( + String::from("town"), + FieldType::String, + false, + SourceDefinition::Dynamic, + ), + false, + ) + .clone(), + vec![Field::Int(1), Field::String(town.to_string())], + ); + + assert_eq!(f, Field::Json(json!({"id": 1, "town": "Bristol", "town_2": "Bristol"}))); } #[test] diff --git a/dozer-types/src/json_types.rs b/dozer-types/src/json_types.rs index 8a196a9b77..3bc07a245c 100644 --- a/dozer-types/src/json_types.rs +++ b/dozer-types/src/json_types.rs @@ -124,7 +124,7 @@ pub fn field_to_json_value(field: Field) -> JsonValue { } } -fn json_value_to_serde_json(value: &JsonValue) -> Value { +pub fn json_value_to_serde_json(value: &JsonValue) -> Value { // Note that while this cannot fail, the other way might, as our internal JSON // representation does not support `inf`, `-inf` and NaN ijson::from_value(value).expect("Json to Json conversion should never fail") diff --git a/dozer-types/src/types/field.rs b/dozer-types/src/types/field.rs index 45d8a3de00..4c25c722da 100644 --- a/dozer-types/src/types/field.rs +++ b/dozer-types/src/types/field.rs @@ -809,7 +809,7 @@ impl Display for Field { Field::Boolean(true) => { write!(f, "TRUE") } - Field::String(s) => f.write_str(s), + Field::String(s) => write!(f, "{s}"), Field::Text(t) => write!(f, "{t}"), Field::Date(d) => write!(f, "{}", d.format(DATE_FORMAT)), Field::Timestamp(t) => write!(f, "{}", t.to_rfc3339()),