247 lines
8.2 KiB
Rust
247 lines
8.2 KiB
Rust
use super::*;
|
|
|
|
#[test]
|
|
fn conditional_writes_compare_and_modify_in_one_statement() {
|
|
let directory = Directory::new();
|
|
let project = Project::load(&fixture().with_file_name("database")).unwrap();
|
|
let program = project.check().unwrap();
|
|
let binary = build::build(
|
|
&program,
|
|
&project.sources,
|
|
"main",
|
|
&directory.0.join("conditional"),
|
|
)
|
|
.unwrap();
|
|
std::fs::create_dir(directory.0.join("config")).unwrap();
|
|
std::fs::write(
|
|
directory.0.join("config/setting.json"),
|
|
r#"{"database":{"default":{"type":"sqlite","path":"conditional.db"}}}"#,
|
|
)
|
|
.unwrap();
|
|
let run = |name: &str, input: serde_json::Value| {
|
|
let output = execute(&binary, &format!("demo.account.{name}"), &input.to_string());
|
|
assert!(
|
|
output.status.success(),
|
|
"{name}: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
serde_json::from_slice::<serde_json::Value>(&output.stdout).unwrap()["data"].clone()
|
|
};
|
|
let first = run("create", serde_json::json!({"name":"original"}));
|
|
run("create", serde_json::json!({"name":"other"}));
|
|
let input =
|
|
serde_json::json!({"id":first["id"],"expected":"original","note":null,"next":"updated"});
|
|
assert_eq!(run("compare_name", input.clone()), 1);
|
|
assert_eq!(run("compare_name", input), 0);
|
|
let updated = run("get", serde_json::json!({"id":first["id"]}));
|
|
assert_eq!(updated["name"], "updated");
|
|
assert_eq!(updated["note"], "changed");
|
|
let invalid = serde_json::json!({"id":first["id"],"expected":"updated","note":"changed","next":"x".repeat(65)});
|
|
assert!(
|
|
!execute(&binary, "demo.account.compare_name", &invalid.to_string())
|
|
.status
|
|
.success()
|
|
);
|
|
assert_eq!(run("get", serde_json::json!({"id":first["id"]})), updated);
|
|
assert_eq!(
|
|
run("remove_matching", serde_json::json!({"rank":7,"note":null})),
|
|
1
|
|
);
|
|
assert_eq!(
|
|
run("remove_matching", serde_json::json!({"rank":7,"note":null})),
|
|
0
|
|
);
|
|
assert_eq!(run("get", serde_json::json!({"id":first["id"]})), updated);
|
|
assert_eq!(
|
|
run(
|
|
"remove_matching",
|
|
serde_json::json!({"rank":7,"note":"changed"})
|
|
),
|
|
1
|
|
);
|
|
assert_eq!(run("count", serde_json::json!({})), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn static_query_predicates_match_literal_text_and_inclusive_ranges() {
|
|
let directory = Directory::new();
|
|
let project = Project::load(&fixture().with_file_name("database")).unwrap();
|
|
let program = project.check().unwrap();
|
|
let binary = build::build(
|
|
&program,
|
|
&project.sources,
|
|
"main",
|
|
&directory.0.join("queries"),
|
|
)
|
|
.unwrap();
|
|
std::fs::create_dir(directory.0.join("config")).unwrap();
|
|
std::fs::write(
|
|
directory.0.join("config/setting.json"),
|
|
r#"{"database":{"default":{"type":"sqlite","path":"query.db"}}}"#,
|
|
)
|
|
.unwrap();
|
|
let run = |name: &str, input: serde_json::Value| {
|
|
let output = execute(&binary, &format!("demo.account.{name}"), &input.to_string());
|
|
assert!(
|
|
output.status.success(),
|
|
"{name}: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
serde_json::from_slice::<serde_json::Value>(&output.stdout).unwrap()["data"].clone()
|
|
};
|
|
let names = [
|
|
"ab",
|
|
"Ab",
|
|
"a\0b",
|
|
"ab\0",
|
|
"前雪尾",
|
|
"雪_%' OR 1=1 --",
|
|
"excluded",
|
|
];
|
|
let created: Vec<_> = names
|
|
.into_iter()
|
|
.map(|name| run("create", serde_json::json!({"name":name})))
|
|
.collect();
|
|
let recent = run("recent", serde_json::json!({}));
|
|
assert_eq!(
|
|
recent["items"],
|
|
serde_json::json!(created.iter().rev().collect::<Vec<_>>())
|
|
);
|
|
assert_eq!(recent["has_more"], false);
|
|
assert!(recent["next"].is_null());
|
|
let mut ordered: Vec<_> = created
|
|
.iter()
|
|
.filter(|row| row["name"] != "excluded")
|
|
.cloned()
|
|
.collect();
|
|
ordered.sort_by(|left, right| {
|
|
left["created_at"]
|
|
.as_str()
|
|
.cmp(&right["created_at"].as_str())
|
|
.then_with(|| right["name"].as_str().cmp(&left["name"].as_str()))
|
|
.then_with(|| right["id"].as_i64().cmp(&left["id"].as_i64()))
|
|
});
|
|
for size in [1, 2, 4, 20] {
|
|
let mut after = serde_json::Value::Null;
|
|
let mut seen = Vec::new();
|
|
loop {
|
|
let page = run("scan", serde_json::json!({"after":after,"size":size}));
|
|
let items = page["items"].as_array().unwrap();
|
|
assert!(items.len() <= size);
|
|
seen.extend(items.iter().cloned());
|
|
assert!(seen.len() <= ordered.len(), "cursor must advance");
|
|
if page["has_more"] == false {
|
|
assert!(page["next"].is_null());
|
|
break;
|
|
}
|
|
assert_eq!(page["next"], *items.last().unwrap());
|
|
after = page["next"].clone();
|
|
}
|
|
assert_eq!(seen, ordered);
|
|
}
|
|
for size in [0, -1, 101] {
|
|
assert!(
|
|
!execute(
|
|
&binary,
|
|
"demo.account.scan",
|
|
&serde_json::json!({"after":null,"size":size}).to_string()
|
|
)
|
|
.status
|
|
.success()
|
|
);
|
|
}
|
|
for needle in [
|
|
"",
|
|
"a",
|
|
"A",
|
|
"ab",
|
|
"\0",
|
|
"a\0",
|
|
"b\0",
|
|
"雪",
|
|
"%",
|
|
"_",
|
|
"' OR 1=1 --",
|
|
"missing",
|
|
] {
|
|
let actual = run("probe", serde_json::json!({"needle":needle}));
|
|
assert_eq!(
|
|
actual,
|
|
serde_json::json!({
|
|
"contained":names.iter().filter(|name|name.contains(needle)).count(),
|
|
"prefixed":names.iter().filter(|name|name.starts_with(needle)).count(),
|
|
"suffixed":names.iter().filter(|name|name.ends_with(needle)).count(),
|
|
"notes":0
|
|
}),
|
|
"needle {needle:?}"
|
|
);
|
|
}
|
|
assert_eq!(
|
|
run("select", serde_json::json!({"first":"ab","second":"Ab"})),
|
|
2
|
|
);
|
|
assert_eq!(
|
|
run("select", serde_json::json!({"first":"ab","second":"ab"})),
|
|
1
|
|
);
|
|
for (lower, upper) in [("Ab", "ab"), ("ab", "ab"), ("ab", "Ab"), ("", "雪")] {
|
|
let expected = names
|
|
.iter()
|
|
.filter(|name| **name >= lower && **name <= upper && **name != "excluded")
|
|
.count();
|
|
assert_eq!(
|
|
run("range", serde_json::json!({"lower":lower,"upper":upper})),
|
|
expected
|
|
);
|
|
}
|
|
assert_eq!(
|
|
run(
|
|
"bulk",
|
|
serde_json::json!({"first":"bulk one","second":"bulk two"})
|
|
),
|
|
2
|
|
);
|
|
let bulk = run("recent", serde_json::json!({}));
|
|
assert_eq!(bulk["items"][0]["rank"], 9);
|
|
assert_eq!(bulk["items"][1]["rank"], 7);
|
|
let mut unique = std::collections::HashSet::new();
|
|
for row in bulk["items"].as_array().unwrap() {
|
|
let uid = row["uid"].as_str().unwrap();
|
|
assert_eq!(&uid[14..15], "7");
|
|
assert!(unique.insert(uid));
|
|
}
|
|
let before = run("count", serde_json::json!({}));
|
|
for (command, input) in [
|
|
(
|
|
"bulk",
|
|
serde_json::json!({"first":"must rollback","second":"ab"}),
|
|
),
|
|
(
|
|
"failed_bulk",
|
|
serde_json::json!({"first":"rollback one","second":"rollback two","value":i64::MAX}),
|
|
),
|
|
] {
|
|
let output = execute(
|
|
&binary,
|
|
&format!("demo.account.{command}"),
|
|
&input.to_string(),
|
|
);
|
|
assert!(!output.status.success(), "{command}");
|
|
assert_eq!(run("count", serde_json::json!({})), before);
|
|
}
|
|
let created = run(
|
|
"save",
|
|
serde_json::json!({"name":"upsert","initial":"first","replacement":"unused"}),
|
|
);
|
|
assert_eq!(created["note"], "first");
|
|
let updated = run(
|
|
"save",
|
|
serde_json::json!({"name":"upsert","initial":"ignored","replacement":"changed"}),
|
|
);
|
|
assert_eq!(updated["id"], created["id"]);
|
|
assert_eq!(updated["created_at"], created["created_at"]);
|
|
assert_eq!(updated["uid"], created["uid"]);
|
|
assert_eq!(updated["note"], "changed");
|
|
assert_eq!(run("retain", serde_json::json!({"name":"upsert"})), updated);
|
|
}
|