diff --git a/core/error.rs b/core/error.rs index 2e688867f..3ab2a7f54 100644 --- a/core/error.rs +++ b/core/error.rs @@ -54,5 +54,12 @@ macro_rules! bail_corrupt_error { }; } +#[macro_export] +macro_rules! bail_constraint_error { + ($($arg:tt)*) => { + return Err($crate::error::LimboError::Constraint(format!($($arg)*))) + }; +} + pub const SQLITE_CONSTRAINT: usize = 19; pub const SQLITE_CONSTRAINT_PRIMARYKEY: usize = SQLITE_CONSTRAINT | (6 << 8); diff --git a/core/json/mod.rs b/core/json/mod.rs index f0bffa8f1..b1394b2bd 100644 --- a/core/json/mod.rs +++ b/core/json/mod.rs @@ -50,13 +50,13 @@ pub fn get_json(json_value: &OwnedValue) -> crate::Result { } } -pub fn json_array(values: Vec) -> crate::Result { +pub fn json_array(values: Vec<&OwnedValue>) -> crate::Result { let mut s = String::new(); s.push('['); for (idx, value) in values.iter().enumerate() { match value { - OwnedValue::Blob(_) => crate::bail_parse_error!("JSON cannot hold BLOB values"), + OwnedValue::Blob(_) => crate::bail_constraint_error!("JSON cannot hold BLOB values"), OwnedValue::Text(t) => { if t.subtype == TextSubtype::Json { s.push_str(&t.value); @@ -67,8 +67,15 @@ pub fn json_array(values: Vec) -> crate::Result { } } } - OwnedValue::Integer(i) => s.push_str(&i.to_string()), - OwnedValue::Float(f) => s.push_str(&f.to_string()), + OwnedValue::Integer(i) => match crate::json::to_string(&i) { + Ok(json) => s.push_str(&json), + Err(_) => crate::bail_parse_error!("malformed JSON"), + }, + OwnedValue::Float(f) => match crate::json::to_string(&f) { + Ok(json) => s.push_str(&json), + Err(_) => crate::bail_parse_error!("malformed JSON"), + }, + OwnedValue::Null => s.push_str("null"), _ => unreachable!(), } @@ -78,7 +85,7 @@ pub fn json_array(values: Vec) -> crate::Result { } s.push(']'); - Ok(OwnedValue::build_text(Rc::new(s))) + Ok(OwnedValue::Text(LimboText::json(Rc::new(s)))) } #[cfg(test)] @@ -92,6 +99,7 @@ mod tests { let result = get_json(&input).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.value.contains("\"key\":\"value\"")); + assert_eq!(result_str.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -103,6 +111,7 @@ mod tests { let result = get_json(&input).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.value.contains("\"key\":\"value\"")); + assert_eq!(result_str.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -114,6 +123,7 @@ mod tests { let result = get_json(&input).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.value.contains("{\"key\":9e999}")); + assert_eq!(result_str.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -125,6 +135,7 @@ mod tests { let result = get_json(&input).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.value.contains("{\"key\":-9e999}")); + assert_eq!(result_str.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -136,6 +147,7 @@ mod tests { let result = get_json(&input).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.value.contains("{\"key\":null}")); + assert_eq!(result_str.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -157,6 +169,7 @@ mod tests { let result = get_json(&input).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.value.contains("\"key\":\"value\"")); + assert_eq!(result_str.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -179,6 +192,7 @@ mod tests { let result = get_json(&input).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.value.contains("\"asd\":\"adf\"")); + assert_eq!(result_str.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -210,11 +224,17 @@ mod tests { fn test_json_array_simple() { let text = OwnedValue::build_text(Rc::new("value1".to_string())); let json = OwnedValue::Text(LimboText::json(Rc::new("\"value2\"".to_string()))); - let input = vec![text, json, OwnedValue::Integer(1), OwnedValue::Float(1.1)]; + let input = vec![ + &text, + &json, + &OwnedValue::Integer(1), + &OwnedValue::Float(1.1), + ]; let result = json_array(input).unwrap(); if let OwnedValue::Text(res) = result { assert_eq!(res.value.as_str(), "[\"value1\",\"value2\",1,1.1]"); + assert_eq!(res.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -227,6 +247,7 @@ mod tests { let result = json_array(input).unwrap(); if let OwnedValue::Text(res) = result { assert_eq!(res.value.as_str(), "[]"); + assert_eq!(res.subtype, TextSubtype::Json); } else { panic!("Expected OwnedValue::Text"); } @@ -236,7 +257,7 @@ mod tests { fn test_json_array_blob_invalid() { let blob = OwnedValue::Blob(Rc::new("1".as_bytes().to_vec())); - let input = vec![blob]; + let input = vec![&blob]; let result = json_array(input); diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 679e86819..363d96a99 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -899,7 +899,7 @@ pub fn translate_expr( Ok(target_register) } JsonFunc::JsonArray => { - allocate_registers( + let start_reg = translate_variable_sized_function_parameter_list( program, args, referenced_tables, @@ -908,7 +908,7 @@ pub fn translate_expr( program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -921,7 +921,7 @@ pub fn translate_expr( unreachable!("this is always ast::Expr::Cast") } ScalarFunc::Char => { - allocate_registers( + let start_reg = translate_variable_sized_function_parameter_list( program, args, referenced_tables, @@ -930,7 +930,7 @@ pub fn translate_expr( program.emit_insn(Insn::Function { constant_mask: 0, - start_reg: target_register + 1, + start_reg, dest: target_register, func: func_ctx, }); @@ -1952,26 +1952,32 @@ pub fn translate_expr( } } -fn allocate_registers( +// Returns the starting register for the function. +// TODO: Use this function for all functions with variable number of parameters in `translate_expr` +fn translate_variable_sized_function_parameter_list( program: &mut ProgramBuilder, args: &Option>, referenced_tables: Option<&[BTreeTableReference]>, precomputed_exprs_to_registers: Option<&Vec<(&ast::Expr, usize)>>, -) -> Result<()> { - let args = args.clone().unwrap_or_else(Vec::new); +) -> Result { + let args = args.as_deref().unwrap_or_default(); + + let reg = program.alloc_registers(args.len()); + let mut current_reg = reg; for arg in args.iter() { - let reg = program.alloc_register(); translate_expr( program, referenced_tables, arg, - reg, + current_reg, precomputed_exprs_to_registers, )?; + + current_reg += 1; } - Ok(()) + Ok(reg) } fn wrap_eval_jump_expr( diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 9d07d94b0..84ef0e778 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2267,8 +2267,9 @@ impl Program { } #[cfg(feature = "json")] crate::function::Func::Json(JsonFunc::JsonArray) => { - let reg_values = - state.registers[*start_reg..*start_reg + arg_count].to_vec(); + let reg_values = state.registers[*start_reg..*start_reg + arg_count] + .iter() + .collect(); let json_array = json_array(reg_values); diff --git a/testing/json.test b/testing/json.test index 3b839f3c9..a62040555 100755 --- a/testing/json.test +++ b/testing/json.test @@ -60,10 +60,26 @@ do_execsql_test json_array_str { SELECT json_array('a') } {{["a"]}} +do_execsql_test json_array_numbers { + SELECT json_array(1, 1.5) +} {{[1,1.5]}} + +do_execsql_test json_array_numbers_2 { + SELECT json_array(1., +2., -2.) +} {{[1.0,2.0,-2.0]}} + +do_execsql_test json_array_null { + SELECT json_array(null) +} {{[null]}} + do_execsql_test json_array_not_json { - SELECT json_array('{"a":1}'); + SELECT json_array('{"a":1}') } {{["{\"a\":1}"]}} do_execsql_test json_array_json { - SELECT json_array(json('{"a":1}')); + SELECT json_array(json('{"a":1}')) } {{[{"a":1}]}} + +do_execsql_test json_array_nested { + SELECT json_array(json_array(1,2,3), json('[1,2,3]'), '[1,2,3]') +} {{[[1,2,3],[1,2,3],"[1,2,3]"]}} diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index 5b7151c58..e7f1c1b10 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -39,6 +39,10 @@ do_execsql_test char { select char(108, 105) } {li} +do_execsql_test char-nested { + select char(106 + 2, 105) +} {li} + do_execsql_test char-empty { select char() } {}