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(); }