#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
fn val(c: &Connection, sql: &str) -> Value {
c.query(sql).unwrap().rows.remove(0).remove(0)
}
fn text(c: &Connection, sql: &str) -> String {
match val(c, sql) {
Value::Text(s) => s,
other => panic!("expected text from {sql:?}, got {other:?}"),
}
}
fn int(c: &Connection, sql: &str) -> i64 {
match val(c, sql) {
Value::Integer(i) => i,
other => panic!("expected integer from {sql:?}, got {other:?}"),
}
}
fn err_contains(c: &Connection, sql: &str, needle: &str) {
match c.query(sql) {
Ok(r) => panic!("expected error containing {needle:?} from {sql:?}, got {r:?}"),
Err(e) => {
let msg = alloc_to_string(&e);
assert!(
msg.contains(needle),
"error for {sql:?} was {msg:?}, expected to contain {needle:?}"
);
}
}
}
fn alloc_to_string(e: &graphitesql::Error) -> String {
format!("{e}")
}
#[test]
fn extract_end_relative_index() {
let c = Connection::open_memory().unwrap();
assert_eq!(int(&c, "SELECT json_extract('[1,2,3]','$[#-1]')"), 3);
assert_eq!(int(&c, "SELECT json_extract('[1,2,3]','$[#-2]')"), 2);
assert_eq!(
int(&c, "SELECT json_extract('{\"a\":[1,2,3]}','$.a[#-1]')"),
3
);
assert_eq!(
val(&c, "SELECT json_extract('[1,2,3]','$[#]')"),
Value::Null
);
assert_eq!(
val(&c, "SELECT json_extract('[1,2,3]','$[#-9]')"),
Value::Null
);
}
#[test]
fn type_and_length_end_relative() {
let c = Connection::open_memory().unwrap();
assert_eq!(text(&c, "SELECT json_type('[1,2,3]','$[#-1]')"), "integer");
assert_eq!(
int(&c, "SELECT json_array_length('[[1,2],[3,4,5]]','$[#-1]')"),
3
);
}
#[test]
fn insert_set_append_with_hash() {
let c = Connection::open_memory().unwrap();
assert_eq!(text(&c, "SELECT json_insert('[1,2]','$[#]',3)"), "[1,2,3]");
assert_eq!(text(&c, "SELECT json_set('[1,2]','$[#]',3)"), "[1,2,3]");
assert_eq!(text(&c, "SELECT json_replace('[1,2]','$[#]',3)"), "[1,2]");
assert_eq!(
text(&c, "SELECT json_insert('[1,2]','$[#]', json('[3]'))"),
"[1,2,[3]]"
);
assert_eq!(
text(&c, "SELECT json_set('{\"a\":[1,2]}','$.a[#]',3)"),
"{\"a\":[1,2,3]}"
);
assert_eq!(
text(
&c,
"SELECT json_insert('{\"a\":[1]}','$.a[#]',2,'$.a[#]',3)"
),
"{\"a\":[1,2,3]}"
);
}
#[test]
fn set_remove_end_relative() {
let c = Connection::open_memory().unwrap();
assert_eq!(
text(&c, "SELECT json_set('[1,2,3]','$[#-1]',99)"),
"[1,2,99]"
);
assert_eq!(text(&c, "SELECT json_remove('[1,2,3]','$[#-1]')"), "[1,2]");
assert_eq!(text(&c, "SELECT json_set('[1,2]','$[#-5]',9)"), "[1,2]");
}
#[test]
fn set_literal_out_of_range_is_noop() {
let c = Connection::open_memory().unwrap();
assert_eq!(text(&c, "SELECT json_set('[1,2]','$[5]',9)"), "[1,2]");
assert_eq!(text(&c, "SELECT json_insert('[1,2]','$[5]',9)"), "[1,2]");
}
#[test]
fn bad_json_path_errors() {
let c = Connection::open_memory().unwrap();
err_contains(
&c,
"SELECT json_extract('{\"a\":1}','a')",
"bad JSON path: 'a'",
);
err_contains(
&c,
"SELECT json_set('{\"a\":1}','bad',2)",
"bad JSON path: 'bad'",
);
err_contains(&c, "SELECT json_type('{}','foo')", "bad JSON path: 'foo'");
err_contains(
&c,
"SELECT json_array_length('[]','foo')",
"bad JSON path: 'foo'",
);
err_contains(&c, "SELECT json_remove('{}','foo')", "bad JSON path: 'foo'");
err_contains(
&c,
"SELECT json_extract('[1,2,3]','$[-1]')",
"bad JSON path: '$[-1]'",
);
err_contains(
&c,
"SELECT json_extract('[1,2,3]','$[#+1]')",
"bad JSON path",
);
err_contains(&c, "SELECT json_extract('{}','$.')", "bad JSON path");
}
#[test]
fn missing_path_is_null_not_error() {
let c = Connection::open_memory().unwrap();
assert_eq!(
val(&c, "SELECT json_extract('{\"a\":1}','$.b')"),
Value::Null
);
assert_eq!(
val(&c, "SELECT json_extract('[1,2,3]','$[5]')"),
Value::Null
);
assert_eq!(val(&c, "SELECT json_type('{\"a\":1}','$.b')"), Value::Null);
assert_eq!(val(&c, "SELECT json_type(null,'foo')"), Value::Null);
assert_eq!(val(&c, "SELECT json_extract(null,'foo')"), Value::Null);
}
#[test]
fn blob_values_rejected() {
let c = Connection::open_memory().unwrap();
err_contains(
&c,
"SELECT json_quote(x'4142')",
"JSON cannot hold BLOB values",
);
err_contains(
&c,
"SELECT json_array(x'4142')",
"JSON cannot hold BLOB values",
);
err_contains(
&c,
"SELECT json_object('a',x'4142')",
"JSON cannot hold BLOB values",
);
err_contains(
&c,
"SELECT json_insert('{}','$.a',x'4142')",
"JSON cannot hold BLOB values",
);
err_contains(
&c,
"SELECT json_set('{}','$.a',x'4142')",
"JSON cannot hold BLOB values",
);
}
#[test]
fn json_object_labels_must_be_text() {
let c = Connection::open_memory().unwrap();
err_contains(
&c,
"SELECT json_object(1,2)",
"json_object() labels must be TEXT",
);
err_contains(
&c,
"SELECT json_object(null,2)",
"json_object() labels must be TEXT",
);
err_contains(
&c,
"SELECT json_object(1.5,2)",
"json_object() labels must be TEXT",
);
err_contains(
&c,
"SELECT json_object(x'4142',1)",
"json_object() labels must be TEXT",
);
}
#[test]
fn json_quote_scalars_and_infinity() {
let c = Connection::open_memory().unwrap();
assert_eq!(text(&c, "SELECT json_quote('abc')"), "\"abc\"");
assert_eq!(text(&c, "SELECT json_quote(1)"), "1");
assert_eq!(text(&c, "SELECT json_quote(1.0)"), "1.0");
assert_eq!(text(&c, "SELECT json_quote(null)"), "null");
assert_eq!(text(&c, "SELECT json_quote(9e999)"), "9.0e+999");
assert_eq!(text(&c, "SELECT json_quote(-9e999)"), "-9.0e+999");
assert_eq!(text(&c, "SELECT json('9e999')"), "9e999");
}
#[test]
fn json_extract_multipath_and_whole_doc() {
let c = Connection::open_memory().unwrap();
assert_eq!(
text(&c, "SELECT json_extract('{\"a\":1,\"b\":2}','$.a','$.b')"),
"[1,2]"
);
assert_eq!(
text(&c, "SELECT json_extract('[1,2,3]','$[0]','$[2]')"),
"[1,3]"
);
assert_eq!(text(&c, "SELECT json_extract('[1,2,3]','$')"), "[1,2,3]");
assert_eq!(
text(&c, "SELECT json_extract('{\"a\":1}','$')"),
"{\"a\":1}"
);
assert_eq!(int(&c, "SELECT json_extract('5','$')"), 5);
assert_eq!(text(&c, "SELECT json_extract('\"hi\"','$')"), "hi");
}
#[test]
fn set_insert_replace_semantics() {
let c = Connection::open_memory().unwrap();
assert_eq!(
text(&c, "SELECT json_insert('{\"a\":1}','$.a',2,'$.b',3)"),
"{\"a\":1,\"b\":3}"
);
assert_eq!(
text(&c, "SELECT json_replace('{\"a\":1}','$.a',2,'$.b',3)"),
"{\"a\":2}"
);
assert_eq!(
text(&c, "SELECT json_set('{\"a\":1}','$.a',2,'$.b',3)"),
"{\"a\":2,\"b\":3}"
);
}
#[test]
fn remove_multiple_paths_and_whole_doc() {
let c = Connection::open_memory().unwrap();
assert_eq!(
text(&c, "SELECT json_remove('[1,2,3,4]','$[0]','$[1]')"),
"[2,4]"
);
assert_eq!(val(&c, "SELECT json_remove('{\"a\":1}','$')"), Value::Null);
assert_eq!(
val(&c, "SELECT json_remove('{\"a\":1,\"b\":2}','$.a','$')"),
Value::Null
);
}
#[test]
fn constructors_embed_json_vs_string() {
let c = Connection::open_memory().unwrap();
assert_eq!(text(&c, "SELECT json_array('[1,2]')"), "[\"[1,2]\"]");
assert_eq!(text(&c, "SELECT json_array(json('[1,2]'))"), "[[1,2]]");
assert_eq!(
text(&c, "SELECT json_object('a',json('[1,2]'))"),
"{\"a\":[1,2]}"
);
assert_eq!(
text(&c, "SELECT json_object('a','[1,2]')"),
"{\"a\":\"[1,2]\"}"
);
assert_eq!(
text(&c, "SELECT json_array(json_array(1,2),json_array(3))"),
"[[1,2],[3]]"
);
assert_eq!(
text(&c, "SELECT json_object('a',1,'a',2)"),
"{\"a\":1,\"a\":2}"
);
}
#[test]
fn json_patch_merge_semantics() {
let c = Connection::open_memory().unwrap();
assert_eq!(
text(
&c,
"SELECT json_patch('{\"a\":1,\"b\":2}','{\"b\":null,\"c\":3}')"
),
"{\"a\":1,\"c\":3}"
);
assert_eq!(
text(
&c,
"SELECT json_patch('{\"a\":{\"x\":1}}','{\"a\":{\"y\":2}}')"
),
"{\"a\":{\"x\":1,\"y\":2}}"
);
assert_eq!(
text(&c, "SELECT json_patch('{\"a\":1}','{\"a\":{\"b\":2}}')"),
"{\"a\":{\"b\":2}}"
);
assert_eq!(
text(&c, "SELECT json_patch('[1,2]','{\"a\":1}')"),
"{\"a\":1}"
);
}
#[test]
fn arrow_operators_negative_index() {
let c = Connection::open_memory().unwrap();
assert_eq!(text(&c, "SELECT '[1,2,3]' -> -1"), "3");
assert_eq!(int(&c, "SELECT '[1,2,3]' ->> -1"), 3);
assert_eq!(int(&c, "SELECT '[1,2,3,4,5]' ->> -2"), 4);
assert_eq!(int(&c, "SELECT '[1,2,3]' ->> 0"), 1);
assert_eq!(int(&c, "SELECT '{\"a\":5}' ->> 'a'"), 5);
}