Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,200 @@
|
||||
use super::{ERROR, packages};
|
||||
|
||||
fn checked(
|
||||
sqlite: &str,
|
||||
postgres: &str,
|
||||
entry: &str,
|
||||
) -> Result<dever::compiler::model::Program, dever::syntax::diagnostic::Diagnostic> {
|
||||
let model = format!(
|
||||
"type Account {{ name: Text }}\nsql lookup(name: Text) (row: app.Summary?) {{ sqlite = {sqlite:?}\npostgres = {postgres:?} }}"
|
||||
);
|
||||
packages(&[
|
||||
("dever.database", ERROR),
|
||||
("demo.account.model", &model),
|
||||
(
|
||||
"demo.account.app",
|
||||
"type Summary { name: Text }\nread(name: Text) (value: Text) { row = model.lookup(name)\nvalue = text(row) }\ntext(row: Summary?) (value: Text) { value = \"present\" }",
|
||||
),
|
||||
("demo.account.api", entry),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_sql_proves_only_owned_static_projections_readonly() {
|
||||
let sqlite = "SELECT name FROM demo_account_model WHERE name = ?1 ORDER BY id DESC";
|
||||
let postgres = sqlite.replace('?', "$");
|
||||
let program = checked(sqlite, &postgres, "public get read = app.read").unwrap();
|
||||
assert!(program.models[0].sql[0].readonly);
|
||||
let complex = "SELECT max(name) AS name FROM demo_account_model WHERE name = ?1";
|
||||
assert!(checked(complex, &complex.replace('?', "$"), "cmd read = app.read").is_ok());
|
||||
assert!(
|
||||
checked(
|
||||
complex,
|
||||
&complex.replace('?', "$"),
|
||||
"public get read = app.read"
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
checked(
|
||||
sqlite,
|
||||
"SELECT name FROM other_table WHERE name = $1",
|
||||
"public get read = app.read"
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_sql_accepts_readonly_alias_count_and_parameterized_paging() {
|
||||
for sql in [
|
||||
"SELECT name AS label FROM demo_account_model WHERE name = ?1",
|
||||
"SELECT COUNT(*) AS total FROM demo_account_model WHERE name = ?1",
|
||||
"SELECT name AS label FROM demo_account_model ORDER BY id DESC LIMIT ?1 OFFSET ?1",
|
||||
] {
|
||||
assert!(
|
||||
checked(sql, &sql.replace('?', "$"), "public get read = app.read")
|
||||
.unwrap()
|
||||
.models[0]
|
||||
.sql[0]
|
||||
.readonly
|
||||
);
|
||||
}
|
||||
for sql in [
|
||||
"SELECT name AS label, unknown() FROM demo_account_model WHERE name = ?1",
|
||||
"SELECT count(unknown()) FROM demo_account_model WHERE name = ?1",
|
||||
"SELECT name FROM demo_account_model LIMIT ?1 OFFSET unknown()",
|
||||
] {
|
||||
assert!(
|
||||
checked(sql, &sql.replace('?', "$"), "public get read = app.read").is_err(),
|
||||
"{sql}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_sql_rejects_binding_and_statement_escape() {
|
||||
for sql in [
|
||||
"SELECT name FROM demo_account_model WHERE name = ?2",
|
||||
"SELECT name FROM demo_account_model WHERE name = :name",
|
||||
"SELECT name FROM demo_account_model WHERE name = ?1; DELETE FROM demo_account_model",
|
||||
"SELECT name FROM _dever_role WHERE name = ?1",
|
||||
"SELECT name FROM \"_dever_role\" WHERE name = ?1",
|
||||
"SELECT name FROM demo_account_model WHERE name = '?1'",
|
||||
"SELECT name FROM demo_account_model WHERE name = ?1 /*",
|
||||
"SELECT name FROM demo_account_model, other_table WHERE name = ?1",
|
||||
"SELECT name FROM demo_account_model.other_table WHERE name = ?1",
|
||||
"SELECT name FROM demo_account_model JOIN other_table ON name = ?1",
|
||||
] {
|
||||
assert!(
|
||||
checked(sql, &sql.replace('?', "$"), "cmd read = app.read").is_err(),
|
||||
"{sql}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_sql_accepts_same_domain_app_view_and_rejects_constrained_results() {
|
||||
let model = "type Account { name: Text }\nsql lookup(name: Text) (row: app.Summary?) { sqlite = \"SELECT name FROM demo_account_model WHERE name = ?1\"\npostgres = \"SELECT name FROM demo_account_model WHERE name = $1\" }";
|
||||
let app = "type Summary { name: Text }\nread(name: Text) (row: Summary?) { row = model.lookup(name) }";
|
||||
assert!(
|
||||
packages(&[
|
||||
("dever.database", ERROR),
|
||||
("demo.account.model", model),
|
||||
("demo.account.app", app),
|
||||
("demo.account.api", "public get read = app.read")
|
||||
])
|
||||
.is_ok()
|
||||
);
|
||||
for record in [
|
||||
"type Extra { value: Int }",
|
||||
"type Extra { value: Secret }",
|
||||
"type Extra { value: List<Int> }",
|
||||
] {
|
||||
let model = format!("type Account {{ name: Text }}\n{record}");
|
||||
assert!(packages(&[("demo.account.model", &model)]).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_sql_records_stay_private_and_all_joined_models_are_dependencies() {
|
||||
let model = "type Account { name: Text }\ntype Summary { name: Text }\nsql joined() (row: Summary) { sqlite = \"SELECT demo_account_model.name FROM demo_account_model, demo_profile_model\"\npostgres = \"SELECT demo_account_model.name FROM demo_account_model JOIN demo_profile_model ON true\" }";
|
||||
let app = "read() (name: Text) { row = model.joined()\nname = row.name }";
|
||||
let mut program = packages(&[
|
||||
("dever.database", ERROR),
|
||||
("demo.account.model", model),
|
||||
("demo.profile.model", "type Profile { name: Text }"),
|
||||
("demo.account.app", app),
|
||||
("demo.account.api", "cmd read = app.read"),
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(program.models[0].sql[0].dependencies, [0, 1]);
|
||||
program.commands.clear();
|
||||
assert!(
|
||||
program
|
||||
.functions
|
||||
.iter()
|
||||
.all(|function| !function.signature.transaction)
|
||||
);
|
||||
let generated = dever::compiler::emit::generate(&program, &[], &Default::default());
|
||||
assert!(generated.contains("registry.check_transactions(&[&[0,1]])"));
|
||||
let leaked = "read() (row: model.Summary) { row = model.joined() }";
|
||||
assert!(
|
||||
packages(&[
|
||||
("dever.database", ERROR),
|
||||
("demo.account.model", model),
|
||||
("demo.profile.model", "type Profile { name: Text }"),
|
||||
("demo.account.app", leaked),
|
||||
("demo.account.api", "cmd read = app.read")
|
||||
])
|
||||
.unwrap_err()
|
||||
.message
|
||||
.contains("private type")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_sql_reuses_model_columns_but_rejects_unproved_projection_bounds() {
|
||||
let model = "type Account { name: Text(8) }\nsql snapshot() (row: Account) { sqlite = \"SELECT id, created_at, name FROM demo_account_model\"\npostgres = \"SELECT id, created_at, name FROM demo_account_model\" }";
|
||||
assert!(
|
||||
packages(&[
|
||||
("dever.database", ERROR),
|
||||
("demo.account.model", model),
|
||||
(
|
||||
"demo.account.app",
|
||||
"read() (name: Text) { row = model.snapshot()\nname = row.name }"
|
||||
),
|
||||
("demo.account.api", "cmd read = app.read")
|
||||
])
|
||||
.is_ok()
|
||||
);
|
||||
for field in ["name: Text(8)", "name: Text unique", "name: Text index"] {
|
||||
let model = format!(
|
||||
"type Account {{ name: Text }}\ntype Summary {{ {field} }}\nsql summary() (row: Summary) {{ sqlite = \"SELECT name FROM demo_account_model\"\npostgres = \"SELECT name FROM demo_account_model\" }}"
|
||||
);
|
||||
assert!(
|
||||
packages(&[("demo.account.model", &model)]).is_err(),
|
||||
"{field}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quoted_sql_fields_are_identifiers_and_do_not_create_fake_table_dependencies() {
|
||||
let sql = "SELECT \"from\" FROM \"demo_account_model\" WHERE \"from\" = ?1";
|
||||
let model = format!(
|
||||
"type Account {{ from: Text }}\nsql lookup(value: Text) (row: app.View) {{ sqlite = {sql:?}\npostgres = {:?} }}",
|
||||
sql.replace('?', "$")
|
||||
);
|
||||
let program = packages(&[
|
||||
("dever.database", ERROR),
|
||||
("demo.account.model", &model),
|
||||
(
|
||||
"demo.account.app",
|
||||
"type View { from: Text }\nread(value: Text) (row: View) { row = model.lookup(value) }",
|
||||
),
|
||||
("demo.account.api", "public get read = app.read"),
|
||||
])
|
||||
.unwrap();
|
||||
assert!(program.models[0].sql[0].readonly);
|
||||
}
|
||||
Reference in new issue
Block a user