Files
core/test/rust/compiler/database/schema.rs
T
2026-11-03 03:32:07 +00:00

123 lines
4.5 KiB
Rust

use super::packages;
#[test]
fn storage_declarations_do_not_reserve_existing_function_names() {
for name in ["seed", "index", "unique", "migrate"] {
let source = format!("public {name}() (answer: Int) {{ answer = 1 }}");
packages(&[("main", &source)]).unwrap();
let source = format!("public {name}(value: Int) (answer: Int) {{ answer = value }}");
packages(&[("main", &source)]).unwrap();
}
}
fn model(extra: &str) -> String {
format!(
"type Account {{ email: Text(64) unique\nname: Text(16)\namount: Decimal(9, 2) default 0\nstatus: Int default 1 }}\n{extra}"
)
}
#[test]
fn schema_checks_seed_unique_keys_and_composite_indexes() {
let source = model(
"index(status, created_at)\nunique(email, name)\nseed { { email = \"admin@sample.test\", name = \"Admin\" } }",
);
let program = packages(&[("demo.account.model", &source)]).unwrap();
assert_eq!(program.models[0].indexes.len(), 2);
assert_eq!(program.models[0].seeds.len(), 1);
assert_eq!(program.models[0].seeds[0].key, [2]);
for extra in [
"index(missing)",
"index(name, name)",
"index(name)\nindex(name)",
"seed { { name = \"Admin\" } }",
"seed { { email = \"a\" } }",
"seed { { email = \"a\", name = \"a\", amount = 0.001 } }",
"seed { { email = \"a\", name = \"a\", id = 1 } }",
"seed {}\nseed {}",
"migrate same { drop legacy }\nmigrate same { drop old }",
"migrate bad { drop name }",
"migrate bad { drop created_at }",
"migrate empty {}",
] {
let source = model(extra);
let error = packages(&[("demo.account.model", &source)]).unwrap_err();
assert_eq!(error.code, "C014", "{extra}: {error:?}");
}
for source in [
"type Account { first: Text from old\nsecond: Text from old }",
"type Account { first: Text from second\nsecond: Text }",
"type Account { first: Text from created_at }",
] {
assert_eq!(
packages(&[("demo.account.model", source)])
.unwrap_err()
.code,
"C014"
);
}
}
#[test]
fn migration_sql_tracks_boundaries_and_rejects_unsafe_or_lossy_inputs() {
let statement = |sqlite: &str, postgres: &str, values: &str| {
let extra = format!(
"migrate normalize {{ before {{ sqlite = {}\npostgres = {}\nparameters = {values} }} }}",
serde_json::to_string(sqlite).unwrap(),
serde_json::to_string(postgres).unwrap()
);
packages(&[("demo.account.model", &model(&extra))])
};
statement("/* prefix */ UPDATE demo_account_model SET name = ?1 WHERE name = ?2 AND ';?90' = ';?90'; -- end", "/* nested /* ok */ */ UPDATE demo_account_model SET name = $1 WHERE name = $2 AND $text$;$90$text$ = $text$;$90$text$; -- end", "[\"new\",\"old\"]").unwrap();
for sql in [
"COMMIT",
"SELECT ?1, ?2",
"DROP TABLE account",
"UPDATE account SET name = ?1; DELETE FROM account WHERE name = ?2",
"UPDATE account SET name = ?3 WHERE name = ?2",
"UPDATE account SET name = $1 WHERE name = $2",
"UPDATE _dever_history SET name = ?1 WHERE name = ?2",
"UPDATE sqlite_master SET name = ?1 WHERE name = ?2",
"DELETE FROM main.'_dever_history' WHERE name IN (?1,?2)",
"UPDATE account SET name = '未闭合",
"UPDATE [未闭合 SET name = ?1 WHERE name = ?2",
] {
let error = statement(
sql,
"UPDATE account SET name = $1 WHERE name = $2",
"[\"new\",\"old\"]",
)
.unwrap_err();
assert_eq!(error.code, "C014", "{sql}: {error:?}");
}
for sql in [
"UPDATE account SET name = $tag$未闭合",
"UPDATE account SET name = $$未闭合",
"UPDATE pg_catalog.pg_class SET name = $1",
] {
assert_eq!(
statement("UPDATE account SET name = ?1", sql, "[\"new\"]")
.unwrap_err()
.code,
"C014"
);
}
for value in ["1.25", "1e999", "9223372036854775808", "name", "1 + 2"] {
assert_eq!(
statement(
"UPDATE account SET name = ?1",
"UPDATE account SET name = $1",
&format!("[{value}]")
)
.unwrap_err()
.code,
"C014"
);
}
statement(
"UPDATE account SET name = ?1 WHERE status = ?2",
"UPDATE account SET name = $1 WHERE status = $2",
"[\"new\",1e0]",
)
.unwrap();
}