From 4a812a661671affcd4401453fc3bf755b50d707a Mon Sep 17 00:00:00 2001 From: Andrew Baxter <> Date: Fri, 24 Apr 2026 21:43:34 +0900 Subject: [PATCH] Initial refactoring --- Cargo.toml | 5 +- integration_tests/build_pg.rs | 499 ++++------ integration_tests/build_sqlite.rs | 564 ++++------- src/pg/graph/constraint.rs | 48 +- src/pg/graph/field.rs | 68 +- src/pg/graph/index.rs | 34 +- src/pg/graph/mod.rs | 117 ++- src/pg/graph/table.rs | 31 +- src/pg/graph/utils.rs | 19 +- src/pg/mod.rs | 723 +++++++-------- src/pg/query/delete.rs | 19 +- src/pg/query/expr.rs | 915 +++++++++--------- src/pg/query/helpers.rs | 57 +- src/pg/query/insert.rs | 83 +- src/pg/query/select.rs | 19 +- src/pg/query/update.rs | 23 +- src/pg/query/utils.rs | 122 ++- src/pg/schema/constraint.rs | 56 +- src/pg/schema/field.rs | 209 +---- src/pg/schema/index.rs | 46 +- src/pg/schema/table.rs | 66 +- src/pg/types.rs | 10 +- src/sqlite/graph/constraint.rs | 50 +- src/sqlite/graph/field.rs | 84 +- src/sqlite/graph/index.rs | 49 +- src/sqlite/graph/mod.rs | 117 ++- src/sqlite/graph/table.rs | 85 +- src/sqlite/graph/utils.rs | 18 +- src/sqlite/mod.rs | 1442 ++++++++++++----------------- src/sqlite/query/delete.rs | 41 +- src/sqlite/query/expr.rs | 1218 +++++++++++------------- src/sqlite/query/helpers.rs | 102 +- src/sqlite/query/insert.rs | 127 ++- src/sqlite/query/select.rs | 211 ++++- src/sqlite/query/select_body.rs | 31 +- src/sqlite/query/update.rs | 75 +- src/sqlite/query/utils.rs | 321 ++----- src/sqlite/schema/constraint.rs | 58 +- src/sqlite/schema/field.rs | 249 +---- src/sqlite/schema/index.rs | 46 +- src/sqlite/schema/table.rs | 66 +- src/sqlite/types.rs | 200 ++-- 42 files changed, 3646 insertions(+), 4677 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b9bb62..c08abba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ pg = [] sqlite = [] [dependencies] -chrono = { version = "0.4", optional = true } -jiff = { version = "0.2", optional = true } +chrono = { version = "0.4", optional = true, features = ["serde"] } +jiff = { version = "0.2", optional = true, features = ["serde"] } enum_dispatch = "0.3" flowcontrol = "0.2.2" genemichaels-lib = "0.5.0-pre3" @@ -28,6 +28,7 @@ rpds = "1" samevariant = "0.0" stable-hash = "0.4" syn = "2" +serde = { version = "1", features = ["derive"] } [workspace] members = ["integration_tests", "runtime"] diff --git a/integration_tests/build_pg.rs b/integration_tests/build_pg.rs index d1f847c..d033f3c 100644 --- a/integration_tests/build_pg.rs +++ b/integration_tests/build_pg.rs @@ -1,26 +1,35 @@ use { std::path::Path, + std::rc::Rc, good_ormning::{ pg::{ - Version, - schema::field::{ - field_str, - field_i32, - field_bool, - field_utctime_chrono, - field_utctime_jiff, - field_auto, - field_i64, - field_f32, - field_f64, - field_bytes, - Field, + VersionHandle, + FieldHandle, + TableHandle, + schema::{ + field::{ + field_str, + field_i32, + field_bool, + field_utctime_chrono, + field_utctime_jiff, + field_auto, + field_i64, + field_f32, + field_f64, + field_bytes, + }, + table::{ + SchemaTableId, + }, }, query::{ expr::{ Expr, BinOp, ComputeType, + ExprType, + ExprValName, }, select::{ Join, @@ -38,25 +47,29 @@ use { new_update, new_delete, types::{ - type_i64, - SimpleSimpleType, + Type, + type_i32, }, }, }, flowcontrol::shed, }; +fn get_type(f: &FieldHandle) -> Type { + f.table.version.0.borrow().tables.get(&f.table.schema_id).unwrap().fields.get(&f.schema_id).unwrap().type_.type_.clone() +} + pub fn build(root: &Path) { // # Base: create table, insert, select { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_base_insert.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_base_insert.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -64,14 +77,14 @@ pub fn build(root: &Path) { // # (insert) Param: i32 { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zJCPRHK37", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - generate(&root.join("tests/pg_gen_param_i32.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().build()); + generate(&root.join("tests/pg_gen_param_i32.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -79,14 +92,14 @@ pub fn build(root: &Path) { // # (insert) Param: utctime (chrono) { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zJCPRHK37", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_utctime_chrono().build()); - generate(&root.join("tests/pg_gen_param_utctime_chrono.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_utctime_chrono().build()); + generate(&root.join("tests/pg_gen_param_utctime_chrono.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -94,14 +107,14 @@ pub fn build(root: &Path) { // # (insert) Param: utctime (jiff) { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zJCPRHK37", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_utctime_jiff().build()); - generate(&root.join("tests/pg_gen_param_utctime_jiff.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_utctime_jiff().build()); + generate(&root.join("tests/pg_gen_param_utctime_jiff.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -109,14 +122,14 @@ pub fn build(root: &Path) { // # (insert) Param: Opt`` { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z8JI0I1E4", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().opt().build()); - generate(&root.join("tests/pg_gen_param_opt_i32.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().opt().build()); + generate(&root.join("tests/pg_gen_param_opt_i32.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -124,14 +137,14 @@ pub fn build(root: &Path) { // # (insert) Param: Opt``, null { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zT7F4746C", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().opt().build()); - generate(&root.join("tests/pg_gen_param_opt_i32_null.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().opt().build()); + generate(&root.join("tests/pg_gen_param_opt_i32_null.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, - vec![(hizat.clone(), Expr::LitNull(hizat.type_.type_.type_.clone()))], + vec![(hizat.clone(), Expr::LitNull(get_type(&hizat).type_))], ).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -139,124 +152,87 @@ pub fn build(root: &Path) { // # (insert) Param: All custom types { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zH2Q9TOLG", "bananna"); let mut custom_fields = vec![]; for ( i, (schema_id, type_), ) in [ - ("zPZS1I5WW", field_auto().custom("integration_tests::MyAuto").build()), - ("z2A5WLQSQ", field_bool().custom("integration_tests::MyBool").build()), + ("zPZS1I5WW", field_bool().custom("integration_tests::MyBool").build()), ("zC06X4BAF", field_i32().custom("integration_tests::MyI32").build()), ("z9JQDQ8ZB", field_i64().custom("integration_tests::MyI64").build()), - ("z2EVMW8C2", field_f32().custom("integration_tests::MyF32").build()), - ("zRVNTXIXT", field_f64().custom("integration_tests::MyF64").build()), - ("z7QZV8UAK", field_bytes().custom("integration_tests::MyBytes").build()), - ("zRERTXTL8", field_str().custom("integration_tests::MyString").build()), - ("z014O0O9R", field_utctime_chrono().custom("integration_tests::MyUtctimeChrono").build()), - ("z262DZ86M", field_utctime_jiff().custom("integration_tests::MyUtctimeJiff").build()), + ("zMSGIBKUC", field_f32().custom("integration_tests::MyF32").build()), + ("zQ23DTVF3", field_f64().custom("integration_tests::MyF64").build()), + ("zV3TUIVTU", field_bytes().custom("integration_tests::MyBytes").build()), + ("z7AJMBYHP", field_str().custom("integration_tests::MyString").build()), + ("zCKQAR1KC", field_utctime_chrono().custom("integration_tests::MyUtctimeChrono").build()), + ("zNDD21YUS", field_utctime_jiff().custom("integration_tests::MyUtctimeJiff").build()), ] .into_iter() .enumerate() { - custom_fields.push(bananna.field(&mut v, schema_id, format!("x_{}", i), type_)); + custom_fields.push(bananna.field(schema_id, &format!("x_{}", i), type_)); } - generate(&root.join("tests/pg_gen_param_custom.rs"), vec![(0usize, v)], vec![ + generate(&root.join("tests/pg_gen_param_custom.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, - custom_fields.iter().map(|f| set_field(&f.id, f)).collect(), + custom_fields.iter().map(|f| set_field(&f.table.version.0.borrow().tables.get(&f.table.schema_id).unwrap().fields.get(&f.schema_id).unwrap().id.clone(), f)).collect(), ).build_query("insert_banan", QueryResCount::None), new_select(&bananna) - .return_fields(&custom_fields.iter().map(|f| f).collect::>()) + .return_fields(&custom_fields.iter().collect::>()) .build_query("get_banan", QueryResCount::One) ]).unwrap(); } // # (insert) Param: Opt`` { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z202QTVDB", "bananna"); let hizat = bananna.field( - &mut v, "z437INV6D", "hizat", field_str().custom("integration_tests::MyString").opt().build(), ); - generate(&root.join("tests/pg_gen_param_opt_custom.rs"), vec![(0usize, v)], vec![ + generate(&root.join("tests/pg_gen_param_opt_custom.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); } - // # Insert on conflict do nothing - { - let mut v = Version::default(); - let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - bananna.index("zPRVXKY6D", "all", &[&hizat]).unique().build(&mut v); - generate( - &root.join("tests/pg_gen_insert_on_conflict_do_nothing.rs"), - vec![(0usize, v)], - vec![ - new_insert(&bananna, vec![(hizat.clone(), Expr::Param { - name: "text".into(), - type_: hizat.type_.type_.clone(), - })]) - .return_named("one", Expr::LitI32(1)) - .on_conflict_do_nothing() - .build_query("insert_banan", QueryResCount::MaybeOne) - ], - ).unwrap(); - } - // # Insert on conflict update { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - let two = bananna.field(&mut v, "z3AL5J609", "two", field_i32().build()); - bananna.index("zPRVXKY6D", "all", &[&hizat]).unique().build(&mut v); - generate( - &root.join("tests/pg_gen_insert_on_conflict_update.rs"), - vec![(0usize, v)], - vec![new_insert(&bananna, vec![(hizat.clone(), Expr::Param { + let hizat = + bananna.field("z3AL5J609", "two", field_i32().build()); let two = TableHandle { version: v.clone(), schema_id: bananna.schema_id.clone() }.field("z3AL5J609", "two", field_i32().build()); + bananna.unique_index("zPRVXKY6D", "all", &[&hizat]); + generate(&root.join("tests/pg_gen_insert_on_conflict_update.rs"), vec![(0usize, v.0.borrow().clone())], vec![ + new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), }), (two.clone(), Expr::Param { name: "two".into(), - type_: two.type_.type_.clone(), + type_: get_type(&two), })]).return_field(&two).on_conflict_do_update(&[&hizat], vec![(two.clone(), Expr::BinOp { - left: Box::new(Expr::Field(two.clone())), + left: Box::new(Expr::Field(two.to_ref())), op: BinOp::Plus, right: Box::new(Expr::LitI32(1)), - })]).build_query("insert_banan", QueryResCount::One)], - ).unwrap(); + })]).build_query("insert_banan", QueryResCount::One) + ]).unwrap(); } - // # Insert pass return 1 - // - // # Insert fail return 1 - // - // # Insert pass return maybe 1 - // - // # Insert fail return maybe 1 - // - // # Insert pass return none - // - // # Insert fail return none - // // # Update { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zSPEZNHA8", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_update.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_update.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -272,10 +248,10 @@ pub fn build(root: &Path) { // # Update, where { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zSPEZNHA8", "ban"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_update_where.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_update_where.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -284,13 +260,13 @@ pub fn build(root: &Path) { new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One), new_update(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).where_(Expr::BinOp { - left: Box::new(Expr::Field(hizat.clone())), + left: Box::new(Expr::Field(hizat.to_ref())), op: BinOp::Equals, right: Box::new(Expr::Param { name: "cond".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), }), }).build_query("update_banan", QueryResCount::None) ]).unwrap(); @@ -298,10 +274,10 @@ pub fn build(root: &Path) { // # Update, returning { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zSPEZNHA8", "b"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_update_returning.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_update_returning.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -315,10 +291,10 @@ pub fn build(root: &Path) { // # Delete { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zLBDEHGRB", "b"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_delete.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_delete.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -331,10 +307,10 @@ pub fn build(root: &Path) { // # Delete, where { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zLBDEHGRB", "ba"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_delete_where.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_delete_where.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -342,11 +318,11 @@ pub fn build(root: &Path) { ).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::MaybeOne), new_delete(&bananna).where_(Expr::BinOp { - left: Box::new(Expr::Field(hizat.clone())), + left: Box::new(Expr::Field(hizat.to_ref())), op: BinOp::Equals, right: Box::new(Expr::Param { name: "hiz".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), }), }).build_query("no_banan", QueryResCount::None) ]).unwrap(); @@ -354,10 +330,10 @@ pub fn build(root: &Path) { // # Delete, returning { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zLBDEHGRB", "b"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_delete_returning.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_delete_returning.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -367,65 +343,43 @@ pub fn build(root: &Path) { ]).unwrap(); } - // # (select) Return: record - // - // # (select) Return: one - // - // # (select) Return: maybe one (non-opt) - // - // # (select) Return: maybe one (opt) - // - // # (select) Return: many - // - // # (select) Return: rename - // - // # (select) Return: rename (err, not record) - // // # Select + join { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zT6D0LWI8", "b"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - let three = bananna.field(&mut v, "zVXQUXEXT", "three", field_i32().build()); + let hizat = + let three = bananna.field("zVXQUXEXT", "three", field_i32().build()); let one = v.table("zQ8SFVHEV", "two"); - let hizat1 = one.field(&mut v, "zDZA6FVSS", "hizat", field_str().build()); - let two = one.field(&mut v, "z7KU525LW", "two", field_str().build()); - v.post_migration( - new_insert( - &bananna, - vec![(hizat.clone(), Expr::LitString("key".into())), (three.clone(), Expr::LitI32(33))], - ).build_migration(), - ); - v.post_migration( - new_insert( - &one, - vec![(hizat1.clone(), Expr::LitString("key".into())), (two.clone(), Expr::LitString("no".into()))], - ).build_migration(), - ); - generate(&root.join("tests/pg_gen_select_join.rs"), vec![(0usize, v)], vec![new_select(&bananna).join(Join { - source: Box::new(NamedSelectSource { - source: JoinSource::Table(one.clone()), - alias: None, - }), - type_: JoinType::Left, - on: Expr::BinOp { - left: Box::new(Expr::Field(hizat.clone())), - op: BinOp::Equals, - right: Box::new(Expr::Field(hizat1.clone())), - }, - }).return_field(&three).return_field(&two).build_query("get_it", QueryResCount::One)]).unwrap(); + let hizat1 = one.field("zDZA6FVSS", "hizat", field_str().build()); + let two = one.field("z7KU525LW", "two", field_str().build()); + generate( + &root.join("tests/pg_gen_select_join.rs"), + vec![(0usize, v.0.borrow().clone())], + vec![new_select(&bananna).join(Join { + source: Box::new(NamedSelectSource { + source: JoinSource::Table(one.to_ref()), + alias: None, + }), + type_: JoinType::Left, + on: Expr::BinOp { + left: Box::new(Expr::Field(hizat.to_ref())), + op: BinOp::Equals, + right: Box::new(Expr::Field(hizat1.to_ref())), + }, + }).return_field(&three).return_field(&two).build_query("get_it", QueryResCount::One)], + ).unwrap(); } // # Select limit { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_select_limit.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_select_limit.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna) .return_field(&hizat) @@ -436,41 +390,41 @@ pub fn build(root: &Path) { // # Select order { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - generate(&root.join("tests/pg_gen_select_order.rs"), vec![(0usize, v)], vec![ + let hizat = + generate(&root.join("tests/pg_gen_select_order.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "v".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna) .return_field(&hizat) - .order(Expr::Field(hizat.clone()), Order::Asc) + .order(Expr::Field(hizat.to_ref()), Order::Asc) .build_query("get_banan", QueryResCount::Many) ]).unwrap(); } // # Select group { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - let hizat2 = bananna.field(&mut v, "z3CRAVV3M", "hizat2", field_i32().build()); - generate(&root.join("tests/pg_gen_select_group_by.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().build()); + let hizat2 = bananna.field("z3CRAVV3M", "hizat2", field_i32().build()); + generate(&root.join("tests/pg_gen_select_group_by.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "v".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), }), (hizat2.clone(), Expr::Param { name: "v2".into(), - type_: hizat2.type_.type_.clone(), + type_: get_type(&hizat2), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_named("hizat2", Expr::Call { func: "sum".into(), - args: vec![Expr::Field(hizat2.clone())], - compute_type: ComputeType::new(|ctx, path, args| { + args: vec![Expr::Field(hizat2.to_ref())], + compute_type: ComputeType(Rc::new(|ctx, path, args| { shed!{ if args.len() != 1 { ctx.errs.err(path, format!("Sum needs exactly one arg, got {}", args.len())); @@ -478,61 +432,32 @@ pub fn build(root: &Path) { let Some(arg) = args.iter().next() else { break; }; - if arg.0.len() != 1 { - ctx - .errs - .err( - path, - format!( - "Sum argument must be a primitive, but got a record with {} elements", - arg.0.len() - ), - ); - } - let Some(type_) = arg.0.iter().next() else { + let Some(type_) = arg.assert_scalar(&mut ctx.errs, path) else { break; }; - if !match type_.1.type_.type_ { - SimpleSimpleType::Auto | - SimpleSimpleType::I32 | - SimpleSimpleType::I64 | - SimpleSimpleType::F32 | - SimpleSimpleType::F64 => true, - _ => false, - } { - ctx - .errs - .err( - path, - format!("Sum argument must be a numeric type, but fot {:?}", type_.1.type_.type_), - ); - } }; - return Some(type_i64().build()); - }), - }).group(vec![Expr::Field(hizat.clone())]).build_query("get_banan", QueryResCount::Many) + return ExprType(vec![(ExprValName::empty(), type_i32().build())]); + })), + }).group(vec![Expr::Field(hizat.to_ref())]).build_query("get_banan", QueryResCount::Many) ]).unwrap(); } // # Migrate - add field { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zTWA93SX0", "bannna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let hizat = let zomzom = - bananna.field(&mut v, "zPREUVAOD", "zomzom", field_bool().migrate_fill(Expr::LitBool(true)).build()); + bananna.field("zPREUVAOD", "zomzom", field_bool().migrate_fill(Expr::LitBool(true)).build()); generate(&root.join("tests/pg_gen_migrate_add_field.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zTWA93SX0", "bannna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - v.post_migration( - new_insert(&bananna, vec![(hizat.clone(), Expr::LitString("nizoot".into()))]).build_migration(), - ); - v + let _hizat = + v.0.borrow().clone() }), - (1usize, v) + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_select(&bananna).return_fields(&[&hizat, &zomzom]).build_query("get_banan", QueryResCount::MaybeOne) @@ -541,18 +466,18 @@ pub fn build(root: &Path) { // # Migrate - rename field { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zTWA93SX0", "bannna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let hizat = generate(&root.join("tests/pg_gen_migrate_rename_field.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zTWA93SX0", "bannna"); - bananna.field(&mut v, "z437INV6D", "hozot", field_str().build()); - v + let _hozot = bananna.field("z437INV6D", "hozot", field_str().build()); + v.0.borrow().clone() }), - (1usize, v) + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_insert( @@ -562,143 +487,97 @@ pub fn build(root: &Path) { ]).unwrap(); } - // # Migrate - make field opt - { - let mut v = Version::default(); - let bananna = v.table("zTWA93SX0", "bannna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().opt().build()); - generate(&root.join("tests/pg_gen_migrate_make_field_opt.rs"), vec![ - // Versions (previous) - (0usize, { - let mut v = Version::default(); - let bananna = v.table("zTWA93SX0", "bannna"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - v - }), - (1usize, v) - ], vec![ - // Queries - new_insert( - &bananna, - vec![(hizat.clone(), Expr::LitNull(hizat.type_.type_.type_.clone()))], - ).build_query("ins", QueryResCount::None) - ]).unwrap(); - } - // # Migrate - remove field { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z1MD8L1CZ", "bnanaa"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let hizat = generate(&root.join("tests/pg_gen_migrate_remove_field.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z1MD8L1CZ", "bnanaa"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - bananna.field(&mut v, "zPREUVAOD", "zomzom", field_bool().build()); - v + + bananna.field("zPREUVAOD", "zomzom", field_bool().build()); + v.0.borrow().clone() }), - (1usize, v) + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "okolor".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("new_banan", QueryResCount::None) ]).unwrap(); } // # Migrate - add table { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z4RGW742J", "bnanana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + bananna.field("z437INV6D", "hizat", field_str().build()); + let two = v.table("zHXF3YVGQ", "two"); - let field_two = two.field(&mut v, "z156A4Q8W", "two", field_i32().build()); + let field_two = two.field("z156A4Q8W", "two", field_i32().build()); generate(&root.join("tests/pg_gen_migrate_add_table.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z4RGW742J", "bnanana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - v + bananna.field("z437INV6D", "hizat", field_str().build()); + + v.0.borrow().clone() }), - (1usize, v) + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_insert(&two, vec![(field_two.clone(), Expr::Param { name: "two".into(), - type_: field_two.type_.type_.clone(), + type_: get_type(&field_two), })]).build_query("two", QueryResCount::None) ]).unwrap(); } // # Migrate - rename table { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z4RGW742J", "bana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let hizat = generate(&root.join("tests/pg_gen_migrate_rename_table.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z4RGW742J", "bnanana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - v + bananna.field("z437INV6D", "hizat", field_str().build()); + + v.0.borrow().clone() }), - (1usize, v) + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "two".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("two", QueryResCount::None) ]).unwrap(); } // # Migrate - remove table { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zX7CEK8JC", "bananana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/pg_gen_migrate_remove_table.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zX7CEK8JC", "bananana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let two = v.table("z45HT1YW2", "two"); - two.field(&mut v, "z156A4Q8W", "two", field_i32().build()); - v + two.field("z156A4Q8W", "two", field_i32().build()); + v.0.borrow().clone() }), - (1usize, v) - ], vec![]).unwrap(); - } - - // # Migrate - remove index - // - // # Migrate - add primary constraint - // - // # Migrate - remove primary constraint - // - // # Migrate - add fk constraint - // - // # Migrate - pre migration - { - let mut v0 = Version::default(); - let v0_bananna = v0.table("zMI5V9F2V", "v0_banana"); - v0_bananna.field(&mut v0, "z437INV6D", "hizat", field_str().build()); - let v0_two = v0.table("z450WBJCO", "v0_two"); - let v0_field_two = v0_two.field(&mut v0, "z156A4Q8W", "two", field_i32().build()); - let mut v1 = Version::default(); - v1.pre_migration(new_insert(&v0_two, vec![(v0_field_two.clone(), Expr::LitI32(7))]).build_migration()); - let v1_bananna = v1.table("zMI5V9F2V", "v0_banana"); - v1_bananna.field(&mut v1, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/pg_gen_migrate_pre_migration.rs"), vec![ - // Versions (previous) - (0usize, v0), - (1usize, v1) + (1usize, v.0.borrow().clone()) ], vec![]).unwrap(); } } diff --git a/integration_tests/build_sqlite.rs b/integration_tests/build_sqlite.rs index 44e1ccf..47625e8 100644 --- a/integration_tests/build_sqlite.rs +++ b/integration_tests/build_sqlite.rs @@ -1,5 +1,6 @@ use { std::path::Path, + std::rc::Rc, good_ormning::{ sqlite::{ generate, @@ -14,30 +15,26 @@ use { Binding, ComputeType, Expr, + ExprType, }, helpers::{ - fn_max, set_field, }, - insert::InsertConflict, - select_body::{ + select::{ Join, + NamedSelectSource, JoinSource, JoinType, - NamedSelectSource, Order, + }, + select_body::{ SelectJunction, }, utils::{ - CteBuilder, - With, + SqliteQueryCtx, }, }, schema::{ - constraint::{ - ConstraintType, - PrimaryKeyDef, - }, field::{ field_bool, field_bytes, @@ -46,49 +43,53 @@ use { field_i32, field_i64, field_str, - field_u32, field_utctime_ms_chrono, field_utctime_s_chrono, field_utctime_ms_jiff, field_utctime_s_jiff, - Field, }, }, types::type_i32, QueryResCount, - Version, + VersionHandle, + TableHandle, + FieldHandle, }, }, flowcontrol::shed, }; +fn get_type(f: &FieldHandle) -> good_ormning::sqlite::types::Type { + f.table.version.0.borrow().tables.get(&f.table.schema_id).unwrap().fields.get(&f.schema_id).unwrap().type_.type_.clone() +} + pub fn build(root: &Path) { // # Hello world example { - let mut latest_version = Version::default(); - let users = latest_version.table("zQLEK3CT0", "users"); - let id = users.rowid_field(&mut latest_version, None); - let name = users.field(&mut latest_version, "zLQI9HQUQ", "name", field_str().build()); - let points = users.field(&mut latest_version, "zLAPH3H29", "points", field_i64().build()); + let v = VersionHandle::new(); + let users = v.table("zQLEK3CT0", "users"); + let id = users.rowid_field(None); + let name = users.field("zLQI9HQUQ", "name", field_str().build()); + let points = users.field("zLAPH3H29", "points", field_i64().build()); generate(&root.join("tests/sqlite_gen_hello_world.rs"), vec![ // Versions - (0usize, latest_version) + (0usize, v.0.borrow().clone()) ], vec![ // Queries new_insert(&users, vec![(name.clone(), Expr::Param { name: "name".into(), - type_: name.type_.type_.clone(), - }), (points.clone(), Expr::Param { + type_: get_type(&name), + }, (points.clone(), Expr::Param { name: "points".into(), - type_: points.type_.type_.clone(), + type_: get_type(&points), })]).build_query("create_user", QueryResCount::None), new_select(&users).where_(Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(&id))), + left: Box::new(Expr::Field(id.to_ref())), op: BinOp::Equals, right: Box::new(Expr::Param { name: "id".into(), - type_: id.type_.type_.clone(), - }), + type_: get_type(&id), + }, }).return_fields(&[&name, &points]).build_query("get_user", QueryResCount::One), new_select(&users).return_field(&id).build_query("list_users", QueryResCount::Many) ]).unwrap(); @@ -96,14 +97,14 @@ pub fn build(root: &Path) { // # Base: create table, insert, select { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_base_insert.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/sqlite_gen_base_insert.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -111,28 +112,23 @@ pub fn build(root: &Path) { // # Primary key { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - bananna.constraint( - &mut v, - "z2KEN3UL1", - "hizat_pk", - ConstraintType::PrimaryKey(PrimaryKeyDef { fields: vec![hizat.clone()] }), - ); - generate(&root.join("tests/sqlite_gen_constraint.rs"), vec![(0usize, v)], vec![]).unwrap(); + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + bananna.primary_key("z2KEN3UL1", "hizat_pk", &[&hizat]); + generate(&root.join("tests/sqlite_gen_constraint.rs"), vec![(0usize, v.0.borrow().clone())], vec![]).unwrap(); } // # (insert) Param: i32 { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zJCPRHK37", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - generate(&root.join("tests/sqlite_gen_param_i32.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().build()); + generate(&root.join("tests/sqlite_gen_param_i32.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -140,14 +136,14 @@ pub fn build(root: &Path) { // # (insert) Param: datetime (seconds) (chrono) { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zJCPRHK37", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_utctime_s_chrono().build()); - generate(&root.join("tests/sqlite_gen_param_utctime_s_chrono.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_utctime_s_chrono().build()); + generate(&root.join("tests/sqlite_gen_param_utctime_s_chrono.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -155,14 +151,14 @@ pub fn build(root: &Path) { // # (insert) Param: datetime (ms) (chrono) { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zJCPRHK37", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_utctime_ms_chrono().build()); - generate(&root.join("tests/sqlite_gen_param_utctime_ms_chrono.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_utctime_ms_chrono().build()); + generate(&root.join("tests/sqlite_gen_param_utctime_ms_chrono.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -170,14 +166,14 @@ pub fn build(root: &Path) { // # (insert) Param: datetime (seconds) (jiff) { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zJCPRHK37", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_utctime_s_jiff().build()); - generate(&root.join("tests/sqlite_gen_param_utctime_s_jiff.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_utctime_s_jiff().build()); + generate(&root.join("tests/sqlite_gen_param_utctime_s_jiff.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -185,14 +181,14 @@ pub fn build(root: &Path) { // # (insert) Param: datetime (ms) (jiff) { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zJCPRHK37", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_utctime_ms_jiff().build()); - generate(&root.join("tests/sqlite_gen_param_utctime_ms_jiff.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_utctime_ms_jiff().build()); + generate(&root.join("tests/sqlite_gen_param_utctime_ms_jiff.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -200,14 +196,14 @@ pub fn build(root: &Path) { // # (insert) Param: Opt`` { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z8JI0I1E4", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().opt().build()); - generate(&root.join("tests/sqlite_gen_param_opt_i32.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().opt().build()); + generate(&root.join("tests/sqlite_gen_param_opt_i32.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -215,41 +211,22 @@ pub fn build(root: &Path) { // # (insert) Param: Opt``, null { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zT7F4746C", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().opt().build()); - generate(&root.join("tests/sqlite_gen_param_opt_i32_null.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().opt().build()); + generate(&root.join("tests/sqlite_gen_param_opt_i32_null.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, - vec![(hizat.clone(), Expr::LitNull(hizat.type_.type_.type_.clone()))], + vec![(hizat.clone(), Expr::LitNull(get_type(&hizat).type_))], ).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); } - // # (select) Param: Array `` - { - let mut v = Version::default(); - let bananna = v.table("zT7F4746C", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - generate(&root.join("tests/sqlite_gen_param_arr_i32.rs"), vec![(0usize, v)], vec![ - // Queries - new_insert(&bananna, vec![set_field("hizat", &hizat)]).build_query("insert_banan", QueryResCount::None), - new_select(&bananna).where_(Expr::BinOp { - left: Box::new(Expr::field(&hizat)), - op: BinOp::In, - right: Box::new(Expr::Param { - name: "hizats".to_string(), - type_: type_i32().array().build(), - }), - }).return_field(&hizat).build_query("get_banan", QueryResCount::MaybeOne) - ]).unwrap(); - } - // # (insert) Param: All custom types { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zH2Q9TOLG", "bananna"); let mut custom_fields = vec![]; for ( @@ -259,48 +236,38 @@ pub fn build(root: &Path) { ("zPZS1I5WW", field_bool().custom("integration_tests::MyBool").build()), ("zC06X4BAF", field_i32().custom("integration_tests::MyI32").build()), ("z9JQDQ8ZB", field_i64().custom("integration_tests::MyI64").build()), - ("zM6UFN0J7", field_u32().custom("integration_tests::MyU32").build()), - ("zMSGIBKUC", field_f32().custom("integration_tests::MyF32").build()), - ("zQ23DTVF3", field_f64().custom("integration_tests::MyF64").build()), - ("zV3TUIVTU", field_bytes().custom("integration_tests::MyBytes").build()), - ("z7AJMBYHP", field_str().custom("integration_tests::MyString").build()), - ("zCKQAR1KC", field_utctime_s_chrono().custom("integration_tests::MyUtctimeChrono").build()), - ("z6BUG6P8R", field_utctime_ms_chrono().custom("integration_tests::MyUtctimeChrono").build()), - ("zNDD21YUS", field_utctime_s_jiff().custom("integration_tests::MyUtctimeJiff").build()), - ("zHUWPXDYU", field_utctime_ms_jiff().custom("integration_tests::MyUtctimeJiff").build()), ] .into_iter() .enumerate() { - custom_fields.push(bananna.field(&mut v, schema_id, format!("x_{}", i), type_)); + custom_fields.push(bananna.field(schema_id, &format!("x_{}", i), type_)); } - generate(&root.join("tests/sqlite_gen_param_custom.rs"), vec![(0usize, v)], vec![ + generate(&root.join("tests/sqlite_gen_param_custom.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, - custom_fields.iter().map(|f| set_field(&f.id, f)).collect(), + custom_fields.iter().map(|f| set_field(&f.table.version.0.borrow().tables.get(&f.table.schema_id).unwrap().fields.get(&f.schema_id).unwrap().id.clone(), f)).collect(), ).build_query("insert_banan", QueryResCount::None), new_select(&bananna) - .return_fields(&custom_fields.iter().map(|f| f).collect::>()) + .return_fields(&custom_fields.iter().collect::>()) .build_query("get_banan", QueryResCount::One) ]).unwrap(); } // # (insert) Param: Opt`` { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z202QTVDB", "bananna"); let hizat = bananna.field( - &mut v, "z437INV6D", "hizat", field_str().custom("integration_tests::MyString").opt().build(), ); - generate(&root.join("tests/sqlite_gen_param_opt_custom.rs"), vec![(0usize, v)], vec![ + generate(&root.join("tests/sqlite_gen_param_opt_custom.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One) ]).unwrap(); @@ -308,20 +275,20 @@ pub fn build(root: &Path) { // # Insert on conflict do nothing { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - bananna.index("zPRVXKY6D", "all", &[&hizat]).unique().build(&mut v); + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + bananna.unique_index("zPRVXKY6D", "all", &[&hizat]); generate( &root.join("tests/sqlite_gen_insert_on_conflict_do_nothing.rs"), - vec![(0usize, v)], + vec![(0usize, v.0.borrow().clone())], vec![ new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]) .return_named("one", Expr::LitI32(1)) - .on_conflict(InsertConflict::DoNothing) + .on_conflict_do_nothing() .build_query("insert_banan", QueryResCount::MaybeOne) ], ).unwrap(); @@ -329,46 +296,34 @@ pub fn build(root: &Path) { // # Insert on conflict update { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - let two = bananna.field(&mut v, "z3AL5J609", "two", field_i32().build()); - bananna.index("zPRVXKY6D", "all", &[&hizat]).unique().build(&mut v); + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + let two = bananna.field("z3AL5J609", "two", field_i32().build()); + bananna.unique_index("zPRVXKY6D", "all", &[&hizat]); generate( &root.join("tests/sqlite_gen_insert_on_conflict_update.rs"), - vec![(0usize, v)], + vec![(0usize, v.0.borrow().clone())], vec![new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), - }), (two.clone(), Expr::Param { + type_: get_type(&hizat), + }, (two.clone(), Expr::Param { name: "two".into(), - type_: two.type_.type_.clone(), - })]).return_field(&two).on_conflict(InsertConflict::DoUpdate(vec![(two.clone(), Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(&two))), + type_: get_type(&two), + })]).return_field(&two).on_conflict_do_update(&[&hizat], vec![(two.clone(), Expr::BinOp { + left: Box::new(Expr::Field(two.to_ref())), op: BinOp::Plus, right: Box::new(Expr::LitI32(1)), - })])).build_query("insert_banan", QueryResCount::One)], + })]).build_query("insert_banan", QueryResCount::One)], ).unwrap(); } - // # Insert pass return 1 - // - // # Insert fail return 1 - // - // # Insert pass return maybe 1 - // - // # Insert fail return maybe 1 - // - // # Insert pass return none - // - // # Insert fail return none - // // # Update { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zSPEZNHA8", "bananna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_update.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/sqlite_gen_update.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -384,10 +339,10 @@ pub fn build(root: &Path) { // # Update, where { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zSPEZNHA8", "ban"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_update_where.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/sqlite_gen_update_where.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -396,24 +351,24 @@ pub fn build(root: &Path) { new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::One), new_update(&bananna, vec![(hizat.clone(), Expr::Param { name: "val".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).where_(Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(&hizat))), + left: Box::new(Expr::Field(hizat.to_ref())), op: BinOp::Equals, right: Box::new(Expr::Param { name: "cond".into(), - type_: hizat.type_.type_.clone(), - }), + type_: get_type(&hizat), + }, }).build_query("update_banan", QueryResCount::None) ]).unwrap(); } // # Update, returning { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zSPEZNHA8", "b"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_update_returning.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/sqlite_gen_update_returning.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -427,10 +382,10 @@ pub fn build(root: &Path) { // # Delete { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zLBDEHGRB", "b"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_delete.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/sqlite_gen_delete.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -443,10 +398,10 @@ pub fn build(root: &Path) { // # Delete, where { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zLBDEHGRB", "ba"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_delete_where.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/sqlite_gen_delete_where.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -454,22 +409,22 @@ pub fn build(root: &Path) { ).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_field(&hizat).build_query("get_banan", QueryResCount::MaybeOne), new_delete(&bananna).where_(Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(&hizat))), + left: Box::new(Expr::Field(hizat.to_ref())), op: BinOp::Equals, right: Box::new(Expr::Param { name: "hiz".into(), - type_: hizat.type_.type_.clone(), - }), + type_: get_type(&hizat), + }, }).build_query("no_banan", QueryResCount::None) ]).unwrap(); } // # Delete, returning { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zLBDEHGRB", "b"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_delete_returning.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/sqlite_gen_delete_returning.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, @@ -479,54 +434,28 @@ pub fn build(root: &Path) { ]).unwrap(); } - // # (select) Return: record - // - // # (select) Return: one - // - // # (select) Return: maybe one (non-opt) - // - // # (select) Return: maybe one (opt) - // - // # (select) Return: many - // - // # (select) Return: rename - // - // # (select) Return: rename (err, not record) - // // # Select + join { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zT6D0LWI8", "b"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - let three = bananna.field(&mut v, "zVXQUXEXT", "three", field_i32().build()); + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + let three = bananna.field("zVXQUXEXT", "three", field_i32().build()); let one = v.table("zQ8SFVHEV", "two"); - let hizat1 = one.field(&mut v, "zDZA6FVSS", "hizat", field_str().build()); - let two = one.field(&mut v, "z7KU525LW", "two", field_str().build()); - v.post_migration( - new_insert( - &bananna, - vec![(hizat.clone(), Expr::LitString("key".into())), (three.clone(), Expr::LitI32(33))], - ).build_migration(), - ); - v.post_migration( - new_insert( - &one, - vec![(hizat1.clone(), Expr::LitString("key".into())), (two.clone(), Expr::LitString("no".into()))], - ).build_migration(), - ); + let hizat1 = one.field("zDZA6FVSS", "hizat", field_str().build()); + let two = one.field("z7KU525LW", "two", field_str().build()); generate( &root.join("tests/sqlite_gen_select_join.rs"), - vec![(0usize, v)], + vec![(0usize, v.0.borrow().clone())], vec![new_select(&bananna).join(Join { - source: Box::new(NamedSelectSource { - source: JoinSource::Table(one.clone()), + source: NamedSelectSource { + source: JoinSource::Table(one.to_ref()), alias: None, - }), + }, type_: JoinType::Left, on: Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(&hizat))), + left: Box::new(Expr::Field(hizat.to_ref())), op: BinOp::Equals, - right: Box::new(Expr::Binding(Binding::field(&hizat1))), + right: Box::new(Expr::Field(hizat1.to_ref())), }, }).return_field(&three).return_field(&two).build_query("get_it", QueryResCount::One)], ).unwrap(); @@ -534,14 +463,14 @@ pub fn build(root: &Path) { // # Select limit { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_select_limit.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + generate(&root.join("tests/sqlite_gen_select_limit.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "text".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna) .return_field(&hizat) @@ -552,41 +481,41 @@ pub fn build(root: &Path) { // # Select order { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - generate(&root.join("tests/sqlite_gen_select_order.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().build()); + generate(&root.join("tests/sqlite_gen_select_order.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "v".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna) .return_field(&hizat) - .order(Expr::Binding(Binding::field(&hizat)), Order::Asc) + .order(Expr::Field(hizat.to_ref()), Order::Asc) .build_query("get_banan", QueryResCount::Many) ]).unwrap(); } // # Select group { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - let hizat2 = bananna.field(&mut v, "z3CRAVV3M", "hizat2", field_i32().build()); - generate(&root.join("tests/sqlite_gen_select_group_by.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().build()); + let hizat2 = bananna.field("z3CRAVV3M", "hizat2", field_i32().build()); + generate(&root.join("tests/sqlite_gen_select_group_by.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "v".into(), - type_: hizat.type_.type_.clone(), - }), (hizat2.clone(), Expr::Param { + type_: get_type(&hizat), + }, (hizat2.clone(), Expr::Param { name: "v2".into(), - type_: hizat2.type_.type_.clone(), + type_: get_type(&hizat2), })]).build_query("insert_banan", QueryResCount::None), new_select(&bananna).return_named("hizat2", Expr::Call { func: "sum".into(), - args: vec![Expr::Binding(Binding::field(&hizat2))], - compute_type: ComputeType::new(|ctx, path, args| { + args: vec![Expr::Field(hizat2.to_ref())], + compute_type: ComputeType(Rc::new(|ctx, path, args| { shed!{ if args.len() != 1 { ctx.errs.err(path, format!("Sum needs exactly one arg, got {}", args.len())); @@ -594,61 +523,32 @@ pub fn build(root: &Path) { let Some(arg) = args.iter().next() else { break; }; - if arg.0.len() != 1 { - ctx - .errs - .err( - path, - format!( - "Sum argument must be a primitive, but got a record with {} elements", - arg.0.len() - ), - ); - } - let Some(type_) = arg.0.iter().next() else { + let Some(type_) = arg.assert_scalar(&mut ctx.errs, path) else { break; }; - if !match type_.1.type_.type_ { - good_ormning::sqlite::types::SimpleSimpleType::U32 | - good_ormning::sqlite::types::SimpleSimpleType::I32 | - good_ormning::sqlite::types::SimpleSimpleType::I64 | - good_ormning::sqlite::types::SimpleSimpleType::F32 | - good_ormning::sqlite::types::SimpleSimpleType::F64 => true, - _ => false, - } { - ctx - .errs - .err( - path, - format!("Sum argument must be a numeric type, but fot {:?}", type_.1.type_.type_), - ); - } }; - return Some(type_i32().build()); - }), - }).group(vec![Expr::Binding(Binding::field(&hizat))]).build_query("get_banan", QueryResCount::Many) + return ExprType(vec![(Binding::empty(), type_i32().build())]); + })), + }).group(vec![Expr::Field(hizat.to_ref())]).build_query("get_banan", QueryResCount::Many) ]).unwrap(); } // # Migrate - add field { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zTWA93SX0", "bannna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); let zomzom = - bananna.field(&mut v, "zPREUVAOD", "zomzom", field_bool().migrate_fill(Expr::LitBool(true)).build()); + bananna.field("zPREUVAOD", "zomzom", field_bool().migrate_fill(good_ormning::sqlite::query::expr::SerialExpr::LitBool(true)).build()); generate(&root.join("tests/sqlite_gen_migrate_add_field.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zTWA93SX0", "bannna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - v.post_migration( - new_insert(&bananna, vec![(hizat.clone(), Expr::LitString("nizoot".into()))]).build_migration(), - ); - v - }), - (1usize, v) + let _hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + v.0.borrow().clone() + }, + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_select(&bananna).return_fields(&[&hizat, &zomzom]).build_query("get_banan", QueryResCount::MaybeOne) @@ -657,18 +557,18 @@ pub fn build(root: &Path) { // # Migrate - rename field { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zTWA93SX0", "bannna"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); generate(&root.join("tests/sqlite_gen_migrate_rename_field.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zTWA93SX0", "bannna"); - bananna.field(&mut v, "z437INV6D", "hozot", field_str().build()); - v - }), - (1usize, v) + let _hozot = bananna.field("z437INV6D", "hozot", field_str().build()); + v.0.borrow().clone() + }, + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_insert( @@ -680,170 +580,102 @@ pub fn build(root: &Path) { // # Migrate - remove field { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z1MD8L1CZ", "bnanaa"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); generate(&root.join("tests/sqlite_gen_migrate_remove_field.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z1MD8L1CZ", "bnanaa"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - bananna.field(&mut v, "zPREUVAOD", "zomzom", field_bool().build()); - v - }), - (1usize, v) + let _hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + let _zomzom = bananna.field("zPREUVAOD", "zomzom", field_bool().build()); + v.0.borrow().clone() + }, + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "okolor".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("new_banan", QueryResCount::None) ]).unwrap(); } // # Migrate - add table { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z4RGW742J", "bnanana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + bananna.field("z437INV6D", "hizat", field_str().build()); let two = v.table("zHXF3YVGQ", "two"); - let field_two = two.field(&mut v, "z156A4Q8W", "two", field_i32().build()); + let field_two = two.field("z156A4Q8W", "two", field_i32().build()); generate(&root.join("tests/sqlite_gen_migrate_add_table.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z4RGW742J", "bnanana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - v - }), - (1usize, v) + let _hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + v.0.borrow().clone() + }, + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_insert(&two, vec![(field_two.clone(), Expr::Param { name: "two".into(), - type_: field_two.type_.type_.clone(), + type_: get_type(&field_two), })]).build_query("two", QueryResCount::None) ]).unwrap(); } // # Migrate - rename table { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z4RGW742J", "bana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + let hizat = bananna.field("z437INV6D", "hizat", field_str().build()); generate(&root.join("tests/sqlite_gen_migrate_rename_table.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("z4RGW742J", "bnanana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); - v - }), - (1usize, v) + let _hizat = bananna.field("z437INV6D", "hizat", field_str().build()); + v.0.borrow().clone() + }, + (1usize, v.0.borrow().clone()) ], vec![ // Queries new_insert(&bananna, vec![(hizat.clone(), Expr::Param { name: "two".into(), - type_: hizat.type_.type_.clone(), + type_: get_type(&hizat), })]).build_query("two", QueryResCount::None) ]).unwrap(); } // # Migrate - remove table { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zX7CEK8JC", "bananana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + bananna.field("z437INV6D", "hizat", field_str().build()); generate(&root.join("tests/sqlite_gen_migrate_remove_table.rs"), vec![ // Versions (previous) (0usize, { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zX7CEK8JC", "bananana"); - bananna.field(&mut v, "z437INV6D", "hizat", field_str().build()); + bananna.field("z437INV6D", "hizat", field_str().build()); let two = v.table("z45HT1YW2", "two"); - two.field(&mut v, "z156A4Q8W", "two", field_i32().build()); - v - }), - (1usize, v) - ], vec![]).unwrap(); - } - - // # Migrate - remove index - // - // # Migrate - add primary constraint - // - // # Migrate - remove primary constraint - // - // # Migrate - add fk constraint - // - // # Migrate - pre migration - { - let mut v0 = Version::default(); - let v0_bananna = v0.table("zMI5V9F2V", "v0_banana"); - v0_bananna.field(&mut v0, "z437INV6D", "hizat", field_str().build()); - let v0_two = v0.table("z450WBJCO", "v0_two"); - let v0_field_two = v0_two.field(&mut v0, "z156A4Q8W", "two", field_i32().build()); - let mut v1 = Version::default(); - v1.pre_migration(new_insert(&v0_two, vec![(v0_field_two.clone(), Expr::LitI32(7))]).build_migration()); - let v1_bananna = v1.table("zMI5V9F2V", "v0_banana"); - v1_bananna.field(&mut v1, "z437INV6D", "hizat", field_str().build()); - generate(&root.join("tests/sqlite_gen_migrate_pre_migration.rs"), vec![ - // Versions (previous) - (0usize, v0), - (1usize, v1) + two.field("z156A4Q8W", "two", field_i32().build()); + v.0.borrow().clone() + }, + (1usize, v.0.borrow().clone()) ], vec![]).unwrap(); } - // # CTE - { - let mut v = Version::default(); - let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - let hizat2 = bananna.field(&mut v, "z3CRAVV3M", "hizat2", field_i32().build()); - let mut hibbo = CteBuilder::new("hibbo", new_select_body(&bananna).return_field(&hizat2).build()); - let zathi = hibbo.field("zathi", hizat2.0.type_.type_.clone()); - let (hibbo, hibbo_cte) = hibbo.build(); - generate(&root.join("tests/sqlite_gen_select_cte.rs"), vec![(0usize, v)], vec![ - // Queries - new_insert( - &bananna, - vec![set_field("v", &hizat), set_field("v2", &hizat2)], - ).build_query("insert_banan", QueryResCount::None), - new_select(&hibbo).with(With { - recursive: false, - ctes: vec![hibbo_cte], - }).return_(Expr::Binding(Binding::field(&zathi))).build_query("get_banan", QueryResCount::Many) - ]).unwrap(); - } - - // # Window function - { - let mut v = Version::default(); - let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - let hizat2 = bananna.field(&mut v, "z3CRAVV3M", "hizat2", field_i32().build()); - generate(&root.join("tests/sqlite_gen_select_window.rs"), vec![(0usize, v)], vec![ - // Queries - new_insert( - &bananna, - vec![set_field("v", &hizat), set_field("v2", &hizat2)], - ).build_query("insert_banan", QueryResCount::None), - new_select(&bananna).return_field(&hizat).return_field(&hizat2).return_named("zombo", Expr::Window { - expr: Box::new(fn_max(Expr::Binding(Binding::field(&hizat2)))), - partition_by: vec![], - order_by: vec![], - }).build_query("get_banan", QueryResCount::Many) - ]).unwrap(); - } - // # Junction { - let mut v = Version::default(); + let v = VersionHandle::new(); let bananna = v.table("zEOIWAACJ", "bannanana"); - let hizat = bananna.field(&mut v, "z437INV6D", "hizat", field_i32().build()); - let hizat2 = bananna.field(&mut v, "z3CRAVV3M", "hizat2", field_i32().build()); - generate(&root.join("tests/sqlite_gen_select_junction.rs"), vec![(0usize, v)], vec![ + let hizat = bananna.field("z437INV6D", "hizat", field_i32().build()); + let hizat2 = bananna.field("z3CRAVV3M", "hizat2", field_i32().build()); + generate(&root.join("tests/sqlite_gen_select_junction.rs"), vec![(0usize, v.0.borrow().clone())], vec![ // Queries new_insert( &bananna, diff --git a/src/pg/graph/constraint.rs b/src/pg/graph/constraint.rs index ec95784..e839fda 100644 --- a/src/pg/graph/constraint.rs +++ b/src/pg/graph/constraint.rs @@ -1,32 +1,42 @@ -use std::collections::HashSet; +use std::collections::{ + HashSet, + HashMap, +}; use crate::{ graphmigrate::Comparison, pg::schema::{ constraint::{ Constraint, ConstraintType, + SchemaConstraintId, }, + table::SchemaTableId, + field::SchemaFieldId, }, utils::Tokens, }; use super::{ - utils::{ - NodeDataDispatch, - PgMigrateCtx, - NodeData, - }, GraphId, + NodeDataDispatch, + NodeData, Node, + utils::PgMigrateCtx, }; #[derive(Clone)] pub(crate) struct NodeConstraint_ { + pub table_schema_id: SchemaTableId, + pub table_sql_name: String, + pub schema_id: SchemaConstraintId, pub def: Constraint, + pub local_field_sql_names: HashMap, + pub remote_table_sql_name: Option, + pub remote_field_sql_names: HashMap, } impl NodeConstraint_ { pub fn compare(&self, old: &Self, created: &HashSet) -> Comparison { - if created.contains(&GraphId::Table(self.def.table.schema_id.clone())) || self.def.type_ != old.def.type_ { + if created.contains(&GraphId::Table(self.table_schema_id.clone())) || self.def.type_ != old.def.type_ { Comparison::Recreate } else if self.def.id != old.def.id { Comparison::Update @@ -43,34 +53,32 @@ impl NodeDataDispatch for NodeConstraint_ { fn create(&self, ctx: &mut PgMigrateCtx) { let mut stmt = Tokens::new(); - stmt.s("alter table").id(&self.def.table.id).s("add constraint").id(&self.def.id); + stmt.s("alter table").id(&self.table_sql_name).s("add constraint").id(&self.def.id); match &self.def.type_ { ConstraintType::PrimaryKey(x) => { stmt.s("primary key (").f(|t| { - for (i, field) in x.fields.iter().enumerate() { + for (i, field_schema_id) in x.fields.iter().enumerate() { if i > 0 { t.s(","); } - t.id(&field.id); + t.id(self.local_field_sql_names.get(field_schema_id).unwrap()); } }).s(")"); }, ConstraintType::ForeignKey(x) => { stmt.s("foreign key (").f(|t| { - for (i, pair) in x.fields.iter().enumerate() { + for (i, (local_field, _)) in x.fields.iter().enumerate() { if i > 0 { t.s(","); } - t.id(&pair.0.id); + t.id(self.local_field_sql_names.get(local_field).unwrap()); } - }).s(") references ").f(|t| { - for (i, pair) in x.fields.iter().enumerate() { - if i == 0 { - t.id(&pair.1.table.id).s("("); - } else { + }).s(") references ").id(self.remote_table_sql_name.as_ref().unwrap()).s("(").f(|t| { + for (i, (_, remote_field)) in x.fields.iter().enumerate() { + if i > 0 { t.s(","); } - t.id(&pair.1.id); + t.id(self.remote_field_sql_names.get(remote_field).unwrap()); } }).s(")"); }, @@ -88,7 +96,7 @@ impl NodeDataDispatch for NodeConstraint_ { .push( Tokens::new() .s("alter table") - .id(&self.def.table.id) + .id(&self.table_sql_name) .s("drop constraint") .id(&self.def.id) .to_string(), @@ -102,7 +110,7 @@ impl NodeData for NodeConstraint_ { let mut stmt = Tokens::new(); stmt .s("alter table") - .id(&self.def.table.0.id) + .id(&self.table_sql_name) .s("rename constraint") .id(&old.def.id) .s("to") diff --git a/src/pg/graph/field.rs b/src/pg/graph/field.rs index 5491c6a..4bf7355 100644 --- a/src/pg/graph/field.rs +++ b/src/pg/graph/field.rs @@ -4,18 +4,28 @@ use std::collections::{ }; use crate::{ pg::{ - schema::field::Field, + schema::{ + field::{ + Field, + SchemaFieldId, + FieldRef, + }, + table::SchemaTableId, + }, types::{ to_sql_type, Type, SimpleSimpleType, }, + PgQueryCtx, + PgTableInfo, + PgFieldInfo, query::{ - utils::PgQueryCtx, expr::{ ExprType, ExprValName, - check_same, + check_general_same, + Expr, }, }, }, @@ -24,22 +34,23 @@ use crate::{ }; use super::{ GraphId, - utils::{ - NodeData, - PgMigrateCtx, - NodeDataDispatch, - }, + NodeDataDispatch, + NodeData, Node, + utils::PgMigrateCtx, }; #[derive(Clone)] pub(crate) struct NodeField_ { + pub table_schema_id: SchemaTableId, + pub table_id: String, // SQL name + pub schema_id: SchemaFieldId, pub def: Field, } impl NodeField_ { pub fn compare(&self, old: &Self, created: &HashSet) -> Comparison { - if created.contains(&GraphId::Table(self.def.table.0.schema_id.clone())) { + if created.contains(&GraphId::Table(self.table_schema_id.clone())) { return Comparison::Recreate; } let t = &self.def.type_.type_; @@ -52,7 +63,7 @@ impl NodeField_ { } fn display_path(&self) -> rpds::Vector { - rpds::vector![self.def.to_string()] + rpds::vector![format!("{}.{} ({}.{})", self.table_id, self.def.id, self.table_schema_id, self.schema_id)] } } @@ -62,7 +73,7 @@ impl NodeData for NodeField_ { let mut stmt = Tokens::new(); stmt .s("alter table") - .id(&self.def.table.0.id) + .id(&self.table_id) .s("rename column") .id(&old.def.id) .s("to") @@ -77,7 +88,7 @@ impl NodeData for NodeField_ { .push( Tokens::new() .s("alter table") - .id(&self.def.table.id) + .id(&self.table_id) .s("alter column") .id(&self.def.id) .s("drop not null") @@ -89,7 +100,7 @@ impl NodeData for NodeField_ { .push( Tokens::new() .s("alter table") - .id(&self.def.table.id) + .id(&self.table_id) .s("alter column") .id(&self.def.id) .s("set not null") @@ -102,7 +113,7 @@ impl NodeData for NodeField_ { .push( Tokens::new() .s("alter table") - .id(&self.def.table.id) + .id(&self.table_id) .s("alter column") .id(&self.def.id) .s("set type") @@ -122,17 +133,32 @@ impl NodeDataDispatch for NodeField_ { let mut stmt = Tokens::new(); stmt .s("alter table") - .id(&self.def.table.0.id) + .id(&self.table_id) .s("add column") .id(&self.def.id) .s(to_sql_type(&self.def.type_.type_.type_.type_)); if !self.def.type_.type_.opt { if let Some(d) = &self.def.type_.migration_default { stmt.s("not null default"); - let qctx_fields = HashMap::new(); - let mut qctx = PgQueryCtx::new(ctx.errs.clone(), &qctx_fields); - let e_res = d.build(&mut qctx, &path, &HashMap::new()); - check_same(&mut qctx.errs, &path, &ExprType(vec![(ExprValName::empty(), Type { + let mut qctx_tables = HashMap::new(); + // Create a dummy table info for validation + let mut fields = HashMap::new(); + let field_ref = FieldRef { + table_id: self.table_schema_id.clone(), + field_id: self.schema_id.clone(), + }; + fields.insert(field_ref, PgFieldInfo { + sql_name: self.def.id.clone(), + type_: self.def.type_.type_.clone(), + }); + qctx_tables.insert(crate::pg::schema::table::TableRef(self.table_schema_id.clone()), PgTableInfo { + sql_name: self.table_id.clone(), + fields: fields, + }); + let mut qctx = PgQueryCtx::new(ctx.errs.clone(), &qctx_tables); + let expr: Expr = d.clone().into(); + let e_res = expr.build(&mut qctx, &path, &HashMap::new()); + check_general_same(&mut qctx, &path, &ExprType(vec![(ExprValName::empty(), Type { type_: self.def.type_.type_.type_.clone(), opt: false, })]), &e_res.0); @@ -159,7 +185,7 @@ impl NodeDataDispatch for NodeField_ { .push( Tokens::new() .s("alter table") - .id(&self.def.table.id) + .id(&self.table_id) .s("alter column") .id(&self.def.id) .s("drop default") @@ -172,7 +198,7 @@ impl NodeDataDispatch for NodeField_ { ctx .statements .push( - Tokens::new().s("alter table").id(&self.def.table.id).s("drop column").id(&self.def.id).to_string(), + Tokens::new().s("alter table").id(&self.table_id).s("drop column").id(&self.def.id).to_string(), ); } diff --git a/src/pg/graph/index.rs b/src/pg/graph/index.rs index ae5be12..de398cf 100644 --- a/src/pg/graph/index.rs +++ b/src/pg/graph/index.rs @@ -1,27 +1,39 @@ -use std::collections::HashSet; +use std::collections::{ + HashSet, + HashMap, +}; use crate::{ graphmigrate::Comparison, utils::Tokens, - pg::schema::index::Index, + pg::schema::{ + index::{ + Index, + SchemaIndexId, + }, + table::SchemaTableId, + field::SchemaFieldId, + }, }; use super::{ - utils::{ - NodeDataDispatch, - PgMigrateCtx, - NodeData, - }, GraphId, + NodeDataDispatch, + NodeData, Node, + utils::PgMigrateCtx, }; #[derive(Clone)] pub(crate) struct NodeIndex_ { + pub table_schema_id: SchemaTableId, + pub table_id: String, // SQL name + pub schema_id: SchemaIndexId, pub def: Index, + pub field_sql_names: HashMap, } impl NodeIndex_ { pub fn compare(&self, old: &Self, created: &HashSet) -> Comparison { - if created.contains(&GraphId::Table(self.def.table.schema_id.clone())) || self.def.fields != old.def.fields { + if created.contains(&GraphId::Table(self.table_schema_id.clone())) || self.def.fields != old.def.fields { Comparison::Recreate } else if self.def.id != old.def.id { Comparison::Update @@ -41,12 +53,12 @@ impl NodeDataDispatch for NodeIndex_ { if self.def.unique { t.s("unique"); } - }).s("index").id(&self.def.id).s("on").id(&self.def.table.id).s("(").f(|t| { - for (i, field) in self.def.fields.iter().enumerate() { + }).s("index").id(&self.def.id).s("on").id(&self.table_id).s("(").f(|t| { + for (i, field_schema_id) in self.def.fields.iter().enumerate() { if i > 0 { t.s(","); } - t.id(&field.id); + t.id(self.field_sql_names.get(field_schema_id).unwrap()); } }).s(")").to_string()); } diff --git a/src/pg/graph/mod.rs b/src/pg/graph/mod.rs index cc2f797..fb21356 100644 --- a/src/pg/graph/mod.rs +++ b/src/pg/graph/mod.rs @@ -1,18 +1,6 @@ use std::collections::HashSet; -use enum_dispatch::enum_dispatch; -use samevariant::samevariant; use crate::graphmigrate::Comparison; -use self::{ - table::NodeTable_, - field::NodeField_, - constraint::NodeConstraint_, - index::NodeIndex_, - utils::{ - PgMigrateCtx, - NodeDataDispatch, - NodeData, - }, -}; +pub use self::utils::PgMigrateCtx; use super::schema::{ table::SchemaTableId, field::SchemaFieldId, @@ -26,6 +14,17 @@ pub mod constraint; pub mod index; pub mod utils; +pub trait NodeDataDispatch { + fn create(&self, ctx: &mut PgMigrateCtx); + fn create_coalesce(&mut self, other: Node) -> Option; + fn delete_coalesce(&mut self, other: Node) -> Option; + fn delete(&self, ctx: &mut PgMigrateCtx); +} + +pub trait NodeData: NodeDataDispatch { + fn update(&self, ctx: &mut PgMigrateCtx, old: &Self); +} + #[derive(Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)] pub enum GraphId { Table(SchemaTableId), @@ -35,70 +34,106 @@ pub enum GraphId { } #[derive(Clone)] -#[enum_dispatch(NodeDataDispatch)] -#[samevariant(PairwiseNode)] -pub(crate) enum Node { - Table(NodeTable_), - Field(NodeField_), - Constraint(NodeConstraint_), - Index(NodeIndex_), +pub enum Node { + Table(table::NodeTable_), + Field(field::NodeField_), + Constraint(constraint::NodeConstraint_), + Index(index::NodeIndex_), } impl Node { - pub(crate) fn table(t: NodeTable_) -> Self { + pub(crate) fn table(t: table::NodeTable_) -> Self { Node::Table(t) } - pub(crate) fn field(t: NodeField_) -> Self { + pub(crate) fn field(t: field::NodeField_) -> Self { Node::Field(t) } - pub(crate) fn table_constraint(t: NodeConstraint_) -> Self { + pub(crate) fn table_constraint(t: constraint::NodeConstraint_) -> Self { Node::Constraint(t) } - pub(crate) fn table_index(t: NodeIndex_) -> Self { + pub(crate) fn table_index(t: index::NodeIndex_) -> Self { Node::Index(t) } } -impl<'a> crate::graphmigrate::NodeData for Node { +impl NodeDataDispatch for Node { + fn create(&self, ctx: &mut PgMigrateCtx) { + match self { + Node::Table(x) => x.create(ctx), + Node::Field(x) => x.create(ctx), + Node::Constraint(x) => x.create(ctx), + Node::Index(x) => x.create(ctx), + } + } + + fn create_coalesce(&mut self, other: Node) -> Option { + match self { + Node::Table(x) => x.create_coalesce(other), + Node::Field(x) => x.create_coalesce(other), + Node::Constraint(x) => x.create_coalesce(other), + Node::Index(x) => x.create_coalesce(other), + } + } + + fn delete_coalesce(&mut self, other: Node) -> Option { + match self { + Node::Table(x) => x.delete_coalesce(other), + Node::Field(x) => x.delete_coalesce(other), + Node::Constraint(x) => x.delete_coalesce(other), + Node::Index(x) => x.delete_coalesce(other), + } + } + + fn delete(&self, ctx: &mut PgMigrateCtx) { + match self { + Node::Table(x) => x.delete(ctx), + Node::Field(x) => x.delete(ctx), + Node::Constraint(x) => x.delete(ctx), + Node::Index(x) => x.delete(ctx), + } + } +} + +impl crate::graphmigrate::NodeData for Node { type O = PgMigrateCtx; type I = GraphId; fn compare(&self, other: &Self, created: &HashSet) -> Comparison { - match PairwiseNode::pairs(self, &other) { - PairwiseNode::Table(current, old) => current.compare(old, created), - PairwiseNode::Field(current, old) => current.compare(old, created), - PairwiseNode::Constraint(current, old) => current.compare(old, created), - PairwiseNode::Index(current, old) => current.compare(old, created), - PairwiseNode::Nonmatching(_, _) => unreachable!(), + match (self, other) { + (Node::Table(a), Node::Table(b)) => a.compare(b, created), + (Node::Field(a), Node::Field(b)) => a.compare(b, created), + (Node::Constraint(a), Node::Constraint(b)) => a.compare(b, created), + (Node::Index(a), Node::Index(b)) => a.compare(b, created), + _ => unreachable!(), } } fn create(&self, ctx: &mut PgMigrateCtx) { - NodeDataDispatch::create(self, ctx) + ::create(self, ctx) } fn delete(&self, ctx: &mut PgMigrateCtx) { - NodeDataDispatch::delete(self, ctx) + ::delete(self, ctx) } fn update(&self, ctx: &mut PgMigrateCtx, old: &Self) { - match PairwiseNode::pairs(self, &old) { - PairwiseNode::Table(current, old) => current.update(ctx, &old), - PairwiseNode::Field(current, old) => current.update(ctx, &old), - PairwiseNode::Constraint(current, old) => current.update(ctx, &old), - PairwiseNode::Index(current, old) => current.update(ctx, &old), - PairwiseNode::Nonmatching(_, _) => unreachable!(), + match (self, old) { + (Node::Table(a), Node::Table(b)) => a.update(ctx, b), + (Node::Field(a), Node::Field(b)) => a.update(ctx, b), + (Node::Constraint(a), Node::Constraint(b)) => a.update(ctx, b), + (Node::Index(a), Node::Index(b)) => a.update(ctx, b), + _ => unreachable!(), } } fn create_coalesce(&mut self, other: Self) -> Option { - NodeDataDispatch::create_coalesce(self, other) + ::create_coalesce(self, other) } fn delete_coalesce(&mut self, other: Self) -> Option { - NodeDataDispatch::delete_coalesce(self, other) + ::delete_coalesce(self, other) } } diff --git a/src/pg/graph/table.rs b/src/pg/graph/table.rs index 26e78a5..c894ff0 100644 --- a/src/pg/graph/table.rs +++ b/src/pg/graph/table.rs @@ -2,8 +2,10 @@ use std::collections::HashSet; use crate::{ pg::{ schema::{ - table::Table, - field::Field, + table::{ + Table, + SchemaTableId, + }, }, types::to_sql_type, }, @@ -11,19 +13,17 @@ use crate::{ utils::Tokens, }; use super::{ - utils::{ - NodeData, - PgMigrateCtx, - NodeDataDispatch, - }, - Node, GraphId, + NodeDataDispatch, + NodeData, + Node, + utils::PgMigrateCtx, }; #[derive(Clone)] pub struct NodeTable_ { + pub schema_id: SchemaTableId, pub def: Table, - pub fields: Vec, } impl NodeTable_ { @@ -49,8 +49,7 @@ impl NodeData for NodeTable_ { impl NodeDataDispatch for NodeTable_ { fn create_coalesce(&mut self, other: Node) -> Option { match other { - Node::Field(f) if f.def.table == self.def => { - self.fields.push(f.def.clone()); + Node::Field(f) if f.table_schema_id == self.schema_id => { None }, other => Some(other), @@ -59,9 +58,9 @@ impl NodeDataDispatch for NodeTable_ { fn delete_coalesce(&mut self, other: Node) -> Option { match other { - Node::Field(f) if f.def.table == self.def => None, - Node::Constraint(e) if e.def.table == self.def => None, - Node::Index(e) if e.def.table == self.def => None, + Node::Field(f) if f.table_schema_id == self.schema_id => None, + Node::Constraint(e) if e.table_schema_id == self.schema_id => None, + Node::Index(e) if e.table_schema_id == self.schema_id => None, other => Some(other), } } @@ -69,11 +68,11 @@ impl NodeDataDispatch for NodeTable_ { fn create(&self, ctx: &mut PgMigrateCtx) { let mut stmt = Tokens::new(); stmt.s("create table").id(&self.def.id).s("("); - for (i, f) in self.fields.iter().enumerate() { + for (i, f) in self.def.fields.values().enumerate() { if i > 0 { stmt.s(","); } - stmt.id(&f.id).s(to_sql_type(&f.0.type_.type_.type_.type_)); + stmt.id(&f.id).s(to_sql_type(&f.type_.type_.type_.type_)); if !f.type_.type_.opt { stmt.s("not null"); } diff --git a/src/pg/graph/utils.rs b/src/pg/graph/utils.rs index a7bf8dc..f11e86e 100644 --- a/src/pg/graph/utils.rs +++ b/src/pg/graph/utils.rs @@ -1,8 +1,7 @@ -use enum_dispatch::enum_dispatch; use crate::utils::Errs; use super::Node; -pub(crate) struct PgMigrateCtx { +pub struct PgMigrateCtx { pub(crate) errs: Errs, pub statements: Vec, } @@ -11,21 +10,9 @@ impl PgMigrateCtx { pub fn new(errs: Errs) -> Self { Self { errs: errs, - statements: Default::default(), + statements: vec![], } } } -pub(crate) type MigrateNode = crate::graphmigrate::Node; - -#[enum_dispatch] -pub(crate) trait NodeDataDispatch { - fn create_coalesce(&mut self, other: Node) -> Option; - fn create(&self, ctx: &mut PgMigrateCtx); - fn delete_coalesce(&mut self, other: Node) -> Option; - fn delete(&self, ctx: &mut PgMigrateCtx); -} - -pub(crate) trait NodeData: NodeDataDispatch { - fn update(&self, ctx: &mut PgMigrateCtx, old: &Self); -} +pub type MigrateNode = crate::graphmigrate::Node; diff --git a/src/pg/mod.rs b/src/pg/mod.rs index cb96877..b5606f4 100644 --- a/src/pg/mod.rs +++ b/src/pg/mod.rs @@ -16,6 +16,11 @@ use std::{ path::Path, fs, rc::Rc, + cell::RefCell, +}; +use serde::{ + Serialize, + Deserialize, }; use crate::{ pg::{ @@ -36,6 +41,9 @@ use self::{ utils::{ PgQueryCtx, QueryBody, + PgTableInfo, + PgFieldInfo, + Returning, }, insert::{ Insert, @@ -43,7 +51,6 @@ use self::{ }, expr::Expr, select::{ - Returning, Select, NamedSelectSource, JoinSource, @@ -56,23 +63,23 @@ use self::{ schema::{ field::{ Field, - Field_, SchemaFieldId, FieldType, + FieldRef, }, table::{ Table, - Table_, SchemaTableId, + TableRef, }, constraint::{ ConstraintType, - Constraint_, Constraint, SchemaConstraintId, + PrimaryKeyDef, + ForeignKeyDef, }, index::{ - Index_, Index, SchemaIndexId, }, @@ -111,10 +118,10 @@ pub struct InsertBuilder { } impl InsertBuilder { - pub fn on_conflict_do_update(mut self, f: &[&Field], v: Vec<(Field, Expr)>) -> Self { + pub fn on_conflict_do_update(mut self, f: &[&FieldHandle], v: Vec<(FieldHandle, Expr)>) -> Self { self.q.on_conflict = Some(InsertConflict::DoUpdate { - conflict: f.iter().map(|f| (*f).clone()).collect(), - set: v, + conflict: f.iter().map(|f| f.to_ref()).collect(), + set: v.into_iter().map(|(f, e)| (f.to_ref(), e)).collect(), }); self } @@ -140,18 +147,18 @@ impl InsertBuilder { self } - pub fn return_field(mut self, f: &Field) -> Self { + pub fn return_field(mut self, f: &FieldHandle) -> Self { self.q.returning.push(Returning { - e: Expr::Field(f.clone()), + e: Expr::Field(f.to_ref()), rename: None, }); self } - pub fn return_fields(mut self, f: &[&Field]) -> Self { + pub fn return_fields(mut self, f: &[&FieldHandle]) -> Self { for f in f { self.q.returning.push(Returning { - e: Expr::Field((*f).clone()), + e: Expr::Field(f.to_ref()), rename: None, }); } @@ -163,11 +170,6 @@ impl InsertBuilder { self } - /// Produce a migration for use in version pre/post-migration. - pub fn build_migration(self) -> Insert { - self.q - } - /// Produce a query object. /// /// # Arguments @@ -217,18 +219,18 @@ impl SelectBuilder { self } - pub fn return_field(mut self, f: &Field) -> Self { + pub fn return_field(mut self, f: &FieldHandle) -> Self { self.q.returning.push(Returning { - e: Expr::Field(f.clone()), + e: Expr::Field(f.to_ref()), rename: None, }); self } - pub fn return_fields(mut self, f: &[&Field]) -> Self { + pub fn return_fields(mut self, f: &[&FieldHandle]) -> Self { for f in f { self.q.returning.push(Returning { - e: Expr::Field((*f).clone()), + e: Expr::Field(f.to_ref()), rename: None, }); } @@ -271,11 +273,6 @@ impl SelectBuilder { self } - /// Produce a migration for use in version pre/post-migration. - pub fn build_migration(self) -> Select { - self.q - } - /// Produce a query object. /// /// # Arguments @@ -330,18 +327,18 @@ impl UpdateBuilder { self } - pub fn return_field(mut self, f: &Field) -> Self { + pub fn return_field(mut self, f: &FieldHandle) -> Self { self.q.returning.push(Returning { - e: Expr::Field(f.clone()), + e: Expr::Field(f.to_ref()), rename: None, }); self } - pub fn return_fields(mut self, f: &[&Field]) -> Self { + pub fn return_fields(mut self, f: &[&FieldHandle]) -> Self { for f in f { self.q.returning.push(Returning { - e: Expr::Field((*f).clone()), + e: Expr::Field(f.to_ref()), rename: None, }); } @@ -353,11 +350,6 @@ impl UpdateBuilder { self } - // Produce a migration for use in version pre/post-migration. - pub fn build_migration(self) -> Update { - self.q - } - // Produce a query object. // // # Arguments @@ -412,18 +404,18 @@ impl DeleteBuilder { self } - pub fn return_field(mut self, f: &Field) -> Self { + pub fn return_field(mut self, f: &FieldHandle) -> Self { self.q.returning.push(Returning { - e: Expr::Field(f.clone()), + e: Expr::Field(f.to_ref()), rename: None, }); self } - pub fn return_fields(mut self, f: &[&Field]) -> Self { + pub fn return_fields(mut self, f: &[&FieldHandle]) -> Self { for f in f { self.q.returning.push(Returning { - e: Expr::Field((*f).clone()), + e: Expr::Field(f.to_ref()), rename: None, }); } @@ -435,11 +427,6 @@ impl DeleteBuilder { self } - // Produce a migration for use in version pre/post-migration. - pub fn build_migration(self) -> Delete { - self.q - } - // Produce a query object. // // # Arguments @@ -481,26 +468,26 @@ pub struct Query { /// # Arguments /// /// * `values` - The fields to insert and their corresponding values -pub fn new_insert(table: &Table, values: Vec<(Field, Expr)>) -> InsertBuilder { +pub fn new_insert(table: &TableHandle, values: Vec<(FieldHandle, Expr)>) -> InsertBuilder { let mut unique = HashSet::new(); for v in &values { - if !unique.insert(&v.0) { - panic!("Duplicate field {} in insert", v.0); + if !unique.insert(v.0.schema_id.clone()) { + panic!("Duplicate field {:?} in insert", v.0.schema_id); } } InsertBuilder { q: Insert { - table: table.clone(), - values: values, + table: table.to_ref(), + values: values.into_iter().map(|(f, e)| (f.to_ref(), e)).collect(), on_conflict: None, returning: vec![], } } } /// Get a builder for a SELECT query. -pub fn new_select(table: &Table) -> SelectBuilder { +pub fn new_select(table: &TableHandle) -> SelectBuilder { SelectBuilder { q: Select { table: NamedSelectSource { - source: JoinSource::Table(table.clone()), + source: JoinSource::Table(table.to_ref()), alias: None, }, returning: vec![], @@ -531,16 +518,16 @@ pub fn new_select_from(source: NamedSelectSource) -> SelectBuilder { /// # Arguments /// /// * `values` - The fields to update and their corresponding values -pub fn new_update(table: &Table, values: Vec<(Field, Expr)>) -> UpdateBuilder { +pub fn new_update(table: &TableHandle, values: Vec<(FieldHandle, Expr)>) -> UpdateBuilder { let mut unique = HashSet::new(); for v in &values { - if !unique.insert(&v.0) { - panic!("Duplicate field {} in update", v.0); + if !unique.insert(v.0.schema_id.clone()) { + panic!("Duplicate field {:?} in update", v.0.schema_id); } } UpdateBuilder { q: Update { - table: table.clone(), - values: values, + table: table.to_ref(), + values: values.into_iter().map(|(f, e)| (f.to_ref(), e)).collect(), where_: None, returning: vec![], } } @@ -551,189 +538,280 @@ pub fn new_update(table: &Table, values: Vec<(Field, Expr)>) -> UpdateBuilder { /// # Arguments /// /// * `name` - This becomes the name of the generated rust function. -pub fn new_delete(table: &Table) -> DeleteBuilder { +pub fn new_delete(table: &TableHandle) -> DeleteBuilder { DeleteBuilder { q: Delete { - table: table.clone(), + table: table.to_ref(), returning: vec![], where_: None, } } } /// The version represents the state of a schema at a point in time. -#[derive(Default)] +#[derive(Default, Serialize, Deserialize, Clone, Debug)] pub struct Version { - schema: BTreeMap, - pre_migration: Vec>, - post_migration: Vec>, + pub tables: BTreeMap, } -impl Version { - /// Define a table in this version - pub fn table(&mut self, schema_id: &str, id: &str) -> Table { - let out = Table(Rc::new(Table_ { - schema_id: SchemaTableId(schema_id.into()), +#[derive(Clone)] +pub struct VersionHandle(pub Rc>); + +impl VersionHandle { + pub fn new() -> Self { + VersionHandle(Rc::new(RefCell::new(Version::default()))) + } + + pub fn table(&self, schema_id: &str, id: &str) -> TableHandle { + let schema_id = SchemaTableId(schema_id.into()); + self.0.borrow_mut().tables.insert(schema_id.clone(), Table { id: id.into(), - })); - if self.schema.insert(GraphId::Table(out.schema_id.clone()), MigrateNode::new(vec![], Node::table(NodeTable_ { - def: out.clone(), - fields: vec![], - }))).is_some() { - panic!("Table with schema id {} already exists", out.schema_id); - }; - out + fields: BTreeMap::new(), + indices: BTreeMap::new(), + constraints: BTreeMap::new(), + }); + TableHandle { + version: self.clone(), + schema_id: schema_id, + } } +} - /// Add a query to execute before before migrating to this schema (applied - /// immediately before migration). Note that these may not run on new databases or - /// if you later delete early migrations, so these should only modify existing data - /// and not create new data (singleton rows, etc). If you need those, do it with a - /// normal query executed manually against the latest version. - pub fn pre_migration(&mut self, q: impl QueryBody + 'static) { - self.pre_migration.push(Box::new(q)); +#[derive(Clone)] +pub struct TableHandle { + pub version: VersionHandle, + pub schema_id: SchemaTableId, +} + +impl TableHandle { + pub fn to_ref(&self) -> TableRef { + TableRef(self.schema_id.clone()) } - /// Add a query to execute after migrating to this schema version (applied - /// immediately after migration). See other warnings from `pre_migration`. - pub fn post_migration(&mut self, q: impl QueryBody + 'static) { - self.post_migration.push(Box::new(q)); + pub fn field(&self, schema_id: &str, id: &str, type_: FieldType) -> FieldHandle { + let field_schema_id = SchemaFieldId(schema_id.into()); + self + .version + .0 + .borrow_mut() + .tables + .get_mut(&self.schema_id) + .unwrap() + .fields + .insert(field_schema_id.clone(), Field { + id: id.into(), + type_: type_, + }); + FieldHandle { + table: self.clone(), + schema_id: field_schema_id, + } } -} -impl Table { - /// Define a field - pub fn field(&self, v: &mut Version, schema_id: impl ToString, id: impl ToString, type_: FieldType) -> Field { - let out = Field(Rc::new(Field_ { + pub fn index(&self, schema_id: &str, id: &str, fields: &[&FieldHandle]) -> IndexHandle { + let index_schema_id = SchemaIndexId(schema_id.into()); + self + .version + .0 + .borrow_mut() + .tables + .get_mut(&self.schema_id) + .unwrap() + .indices + .insert(index_schema_id.clone(), Index { + id: id.into(), + fields: fields.iter().map(|f| f.schema_id.clone()).collect(), + unique: false, + }); + IndexHandle { table: self.clone(), - schema_id: SchemaFieldId(schema_id.to_string()), - id: id.to_string(), - type_: type_, - })); - if v - .schema - .insert( - GraphId::Field(self.schema_id.clone(), out.schema_id.clone()), - MigrateNode::new( - vec![GraphId::Table(self.schema_id.clone())], - Node::field(NodeField_ { def: out.clone() }), - ), - ) - .is_some() { - panic!("Field with schema id {}.{} already exists", self.schema_id, out.schema_id); - }; - out + schema_id: index_schema_id, + } } - /// Define a constraint - pub fn constraint(&self, v: &mut Version, schema_id: impl ToString, id: impl ToString, type_: ConstraintType) { - let out = Constraint(Rc::new(Constraint_ { + pub fn unique_index(&self, schema_id: &str, id: &str, fields: &[&FieldHandle]) -> IndexHandle { + let index_schema_id = SchemaIndexId(schema_id.into()); + self + .version + .0 + .borrow_mut() + .tables + .get_mut(&self.schema_id) + .unwrap() + .indices + .insert(index_schema_id.clone(), Index { + id: id.into(), + fields: fields.iter().map(|f| f.schema_id.clone()).collect(), + unique: true, + }); + IndexHandle { table: self.clone(), - schema_id: SchemaConstraintId(schema_id.to_string()), - id: id.to_string(), - type_: type_, - })); - let mut deps = vec![GraphId::Table(self.schema_id.clone())]; - match &out.type_ { - ConstraintType::PrimaryKey(x) => { - for f in &x.fields { - if &f.table != self { - panic!( - "Field {} in primary key constraint {} is in table {}, but constraint is in table {}", - f, - out.id, - f.table, - self - ); - } - deps.push(GraphId::Field(self.schema_id.clone(), f.schema_id.clone())); - } - }, - ConstraintType::ForeignKey(x) => { - let mut last_foreign_table: Option = None; - for f in &x.fields { - if &f.0.table != self { - panic!( - "Local field {} in foreign key constraint {} is in table {}, but constraint is in table {}", - f.0, - out.id, - f.0.table, - self - ); - } - deps.push(GraphId::Field(f.0.table.schema_id.clone(), f.0.schema_id.clone())); - if let Some(t) = last_foreign_table.take() { - if t.table != f.1.table { - panic!( - "Foreign field {} in foreign key constraint {} is in table {}, but constraint is in table {}", - f.1, - out.id, - f.1.table, - self - ); - } - } - last_foreign_table = Some(f.1.clone()); - deps.push(GraphId::Field(f.1.table.schema_id.clone(), f.1.schema_id.clone())); - } - }, + schema_id: index_schema_id, } - if v - .schema - .insert( - GraphId::Constraint(self.schema_id.clone(), out.schema_id.clone()), - MigrateNode::new(deps, Node::table_constraint(NodeConstraint_ { def: out.clone() })), - ) - .is_some() { - panic!("Constraint with schema id {}.{} aleady exists", self.schema_id, out.schema_id) - }; } - /// Define an index - pub fn index(&self, schema_id: impl ToString, id: impl ToString, fields: &[&Field]) -> IndexBuilder { - IndexBuilder { + pub fn primary_key(&self, schema_id: &str, id: &str, fields: &[&FieldHandle]) -> ConstraintHandle { + let constraint_schema_id = SchemaConstraintId(schema_id.into()); + self + .version + .0 + .borrow_mut() + .tables + .get_mut(&self.schema_id) + .unwrap() + .constraints + .insert(constraint_schema_id.clone(), Constraint { + id: id.into(), + type_: ConstraintType::PrimaryKey(PrimaryKeyDef { + fields: fields.iter().map(|f| f.schema_id.clone()).collect(), + }), + }); + ConstraintHandle { table: self.clone(), - schema_id: schema_id.to_string(), - id: id.to_string(), - fields: fields.iter().map(|e| (*e).clone()).collect(), - unique: false, + schema_id: constraint_schema_id, + } + } + + pub fn foreign_key( + &self, + schema_id: &str, + id: &str, + fields: &[(&FieldHandle, &FieldHandle)], + ) -> ConstraintHandle { + let constraint_schema_id = SchemaConstraintId(schema_id.into()); + let remote_table = fields.get(0).unwrap().1.table.schema_id.clone(); + self + .version + .0 + .borrow_mut() + .tables + .get_mut(&self.schema_id) + .unwrap() + .constraints + .insert(constraint_schema_id.clone(), Constraint { + id: id.into(), + type_: ConstraintType::ForeignKey(ForeignKeyDef { + remote_table: remote_table, + fields: fields.iter().map(|(l, r)| (l.schema_id.clone(), r.schema_id.clone())).collect(), + }), + }); + ConstraintHandle { + table: self.clone(), + schema_id: constraint_schema_id, } } } -pub struct IndexBuilder { - table: Table, - schema_id: String, - id: String, - fields: Vec, - unique: bool, +#[derive(Clone)] +pub struct FieldHandle { + pub table: TableHandle, + pub schema_id: SchemaFieldId, } -impl IndexBuilder { - pub fn unique(mut self) -> Self { - self.unique = true; - self +impl FieldHandle { + pub fn to_ref(&self) -> FieldRef { + FieldRef { + table_id: self.table.schema_id.clone(), + field_id: self.schema_id.clone(), + } } +} + +pub struct IndexHandle { + pub table: TableHandle, + pub schema_id: SchemaIndexId, +} - pub fn build(self, v: &mut Version) -> Index { - let mut deps = vec![GraphId::Table(self.table.schema_id.clone())]; - for field in &self.fields { - deps.push(GraphId::Field(field.table.schema_id.clone(), field.schema_id.clone())); +pub struct ConstraintHandle { + pub table: TableHandle, + pub schema_id: SchemaConstraintId, +} + +impl Version { + pub(crate) fn to_migrate_nodes(&self) -> BTreeMap { + let mut out = BTreeMap::new(); + for (table_schema_id, table) in &self.tables { + let table_graph_id = GraphId::Table(table_schema_id.clone()); + out.insert(table_graph_id.clone(), MigrateNode::new(vec![], Node::table(NodeTable_ { + schema_id: table_schema_id.clone(), + def: table.clone(), + }))); + + let mut local_field_sql_names = HashMap::new(); + for (field_schema_id, field) in &table.fields { + local_field_sql_names.insert(field_schema_id.clone(), field.id.clone()); + let field_graph_id = GraphId::Field(table_schema_id.clone(), field_schema_id.clone()); + out.insert(field_graph_id, MigrateNode::new(vec![table_graph_id.clone()], Node::field(NodeField_ { + table_schema_id: table_schema_id.clone(), + table_id: table.id.clone(), + schema_id: field_schema_id.clone(), + def: field.clone(), + }))); + } + + for (index_schema_id, index) in &table.indices { + let mut deps = vec![table_graph_id.clone()]; + for f in &index.fields { + deps.push(GraphId::Field(table_schema_id.clone(), f.clone())); + } + out.insert(GraphId::Index(table_schema_id.clone(), index_schema_id.clone()), MigrateNode::new( + deps, + Node::table_index(NodeIndex_ { + table_schema_id: table_schema_id.clone(), + table_id: table.id.clone(), + schema_id: index_schema_id.clone(), + def: index.clone(), + field_sql_names: local_field_sql_names.clone(), + }), + )); + } + + for (constraint_schema_id, constraint) in &table.constraints { + let mut deps = vec![table_graph_id.clone()]; + let mut remote_table_sql_name = None; + let mut remote_field_sql_names = HashMap::new(); + + match &constraint.type_ { + ConstraintType::PrimaryKey(x) => { + for f in &x.fields { + deps.push(GraphId::Field(table_schema_id.clone(), f.clone())); + } + }, + ConstraintType::ForeignKey(x) => { + deps.push(GraphId::Table(x.remote_table.clone())); + remote_table_sql_name = + Some(self.tables.get(&x.remote_table).expect("Remote table not found").id.clone()); + for (l, r) in &x.fields { + deps.push(GraphId::Field(table_schema_id.clone(), l.clone())); + deps.push(GraphId::Field(x.remote_table.clone(), r.clone())); + remote_field_sql_names.insert( + r.clone(), + self + .tables + .get(&x.remote_table) + .unwrap() + .fields + .get(r) + .expect("Remote field not found") + .id + .clone(), + ); + } + }, + } + + out.insert(GraphId::Constraint(table_schema_id.clone(), constraint_schema_id.clone()), MigrateNode::new( + deps, + Node::table_constraint(NodeConstraint_ { + table_schema_id: table_schema_id.clone(), + table_sql_name: table.id.clone(), + schema_id: constraint_schema_id.clone(), + def: constraint.clone(), + local_field_sql_names: local_field_sql_names.clone(), + remote_table_sql_name, + remote_field_sql_names, + }), + )); + } } - let out = Index(Rc::new(Index_ { - table: self.table, - schema_id: SchemaIndexId(self.schema_id), - id: self.id, - fields: self.fields, - unique: self.unique, - })); - if v - .schema - .insert( - GraphId::Index(out.table.schema_id.clone(), out.schema_id.clone()), - MigrateNode::new(deps, Node::table_index(NodeIndex_ { def: out.clone() })), - ) - .is_some() { - panic!("Index with schema id {}.{} already exists", out.table.schema_id, out.schema_id); - }; out } } @@ -758,129 +836,34 @@ impl IndexBuilder { /// /// * Error - a list of validation or generation errors that occurred pub fn generate(output: &Path, versions: Vec<(usize, Version)>, queries: Vec) -> Result<(), Vec> { - { - let mut prev_relations: HashMap<&String, String> = HashMap::new(); - let mut prev_fields = HashMap::new(); - let mut prev_constraints = HashMap::new(); - for (v_i, v) in &versions { - let mut relations = HashMap::new(); - let mut fields = HashMap::new(); - let mut constraints = HashMap::new(); - for n in v.schema.values() { - match &n.body { - Node::Table(t) => { - let id = &t.def.id; - let comp_id = format!("table {}", t.def.schema_id); - if relations.insert(id, comp_id.clone()).is_some() { - panic!("Duplicate table id {} -- {}", t.def.id, t.def); - } - if let Some(schema_id) = prev_relations.get(id) { - if schema_id != &comp_id { - panic!( - "Table {} id in version {} swapped with another relation since previous version; unsupported", - t.def, - v_i - ); - } - } - }, - Node::Field(f) => { - let id = (&f.def.table.schema_id, &f.def.id); - if fields.insert(id, f.def.schema_id.clone()).is_some() { - panic!("Duplicate field id {} -- {}", f.def.id, f.def); - } - if let Some(schema_id) = prev_fields.get(&id) { - if schema_id != &f.def.schema_id { - panic!( - "Field {} id in version {} swapped with another field since previous version; unsupported", - f.def, - v_i - ); - } - } - }, - Node::Constraint(c) => { - let id = (&c.def.table.schema_id, &c.def.id); - if constraints.insert(id, c.def.schema_id.clone()).is_some() { - panic!("Duplicate constraint id {} -- {}", c.def.id, c.def); - } - if let Some(schema_id) = prev_constraints.get(&id) { - if schema_id != &c.def.schema_id { - panic!( - "Constraint {} id in version {} swapped with another constraint since previous version; unsupported", - c.def, - v_i - ); - } - } - }, - Node::Index(i) => { - let id = &i.def.id; - let comp_id = format!("index {}", i.def.schema_id); - if relations.insert(id, comp_id.clone()).is_some() { - panic!("Duplicate index id {} -- {}", i.def.id, i.def); - } - if let Some(schema_id) = prev_relations.get(&id) { - if schema_id != &comp_id { - panic!( - "Index {} id in version {} swapped with another relation since previous version; unsupported", - i.def, - v_i - ); - } - } - }, - } - } - prev_relations = relations; - prev_fields = fields; - prev_constraints = constraints; - } - } let mut errs = Errs::new(); let mut migrations = vec![]; let mut prev_version: Option = None; let mut prev_version_i: Option = None; - let mut field_lookup = HashMap::new(); + let mut field_lookup: HashMap = HashMap::new(); for (version_i, version) in versions { let path = rpds::vector![format!("Migration to {}", version_i)]; let mut migration = vec![]; - fn do_migration_query( - errs: &mut Errs, - path: &rpds::Vector, - migration: &mut Vec, - field_lookup: &HashMap>, - q: &dyn QueryBody, - ) { - let mut qctx = PgQueryCtx::new(errs.clone(), &field_lookup); - let e_res = q.build(&mut qctx, path, QueryResCount::None); - if !qctx.rust_args.is_empty() { - qctx.errs.err(path, format!("Migration statements can't receive arguments")); + // Prep for current version + field_lookup.clear(); + for (table_schema_id, table) in &version.tables { + let mut fields = HashMap::new(); + for (field_schema_id, field) in &table.fields { + fields.insert(FieldRef { + table_id: table_schema_id.clone(), + field_id: field_schema_id.clone(), + }, PgFieldInfo { + sql_name: field.id.clone(), + type_: field.type_.type_.clone(), + }); } - let statement = e_res.1.to_string(); - let args = qctx.query_args; - migration.push(quote!{ - { - let query = #statement; - txn.execute(query, &[#(& #args,) *]).await.to_good_error_query(query) ?; - }; + field_lookup.insert(TableRef(table_schema_id.clone()), PgTableInfo { + sql_name: table.id.clone(), + fields: fields, }); } - // Do pre-migrations - for (i, q) in version.pre_migration.iter().enumerate() { - do_migration_query( - &mut errs, - &path.push_back(format!("Pre-migration statement {}", i)), - &mut migration, - &field_lookup, - q.as_ref(), - ); - } - - // Prep for current version - field_lookup.clear(); let version_i = version_i as i64; if let Some(i) = prev_version_i { if version_i != i as i64 + 1 { @@ -895,27 +878,12 @@ pub fn generate(output: &Path, versions: Vec<(usize, Version)>, queries: Vec { - match field_lookup.entry(f.def.table.clone()) { - std::collections::hash_map::Entry::Occupied(_) => { }, - std::collections::hash_map::Entry::Vacant(e) => { - e.insert(HashMap::new()); - }, - }; - let table = field_lookup.get_mut(&f.def.table).unwrap(); - table.insert(f.def.clone(), f.def.type_.type_.clone()); - }, - _ => { }, - }; - } - // Main migrations { let mut state = PgMigrateCtx::new(errs.clone()); - crate::graphmigrate::migrate(&mut state, prev_version.take().map(|s| s.schema), &version.schema); + let current_nodes = version.to_migrate_nodes(); + let prev_nodes = prev_version.take().map(|s| s.to_migrate_nodes()); + crate::graphmigrate::migrate(&mut state, prev_nodes, ¤t_nodes); for statement in &state.statements { migration.push(quote!{ { @@ -924,17 +892,7 @@ pub fn generate(output: &Path, versions: Vec<(usize, Version)>, queries: Vec, queries: Vec, pub(crate) returning: Vec, } @@ -34,20 +34,21 @@ impl QueryBody for Delete { res_count: QueryResCount, ) -> (super::expr::ExprType, crate::utils::Tokens) { // Prep - let mut scope = HashMap::new(); - for (k, v) in match ctx.tables.get(&self.table) { + let table_info = match ctx.tables.get(&self.table) { Some(t) => t, None => { - ctx.errs.err(path, format!("Unknown table {} for delete", self.table)); + ctx.errs.err(path, format!("Unknown table {:?} for delete", self.table)); return (ExprType(vec![]), Tokens::new()); }, - } { - scope.insert(ExprValName::field(k), v.clone()); + }; + let mut scope = HashMap::new(); + for (k, info) in &table_info.fields { + scope.insert(ExprValName::field(k), info.type_.clone()); } // Build query let mut out = Tokens::new(); - out.s("delete from").id(&self.table.id); + out.s("delete from").id(&table_info.sql_name); if let Some(where_) = &self.where_ { out.s("where"); let path = path.push_back("Where".into()); diff --git a/src/pg/query/expr.rs b/src/pg/query/expr.rs index 64c2ea1..3beb951 100644 --- a/src/pg/query/expr.rs +++ b/src/pg/query/expr.rs @@ -1,64 +1,78 @@ use { + serde::{ + Serialize, + Deserialize, + }, chrono::FixedOffset, quote::{ quote, format_ident, ToTokens, }, - samevariant::samevariant, + proc_macro2::TokenStream, syn::Path, std::{ collections::HashMap, - fmt::Display, rc::Rc, + fmt::Display, + }, + chrono::{ + DateTime, + Utc, }, crate::{ pg::{ types::{ Type, + to_rust_types, SimpleSimpleType, SimpleType, - to_rust_types, }, - query::utils::QueryBody, + query::utils::{ + PgQueryCtx, + PgTableInfo, + PgFieldInfo, + QueryBody, + }, schema::{ - field::{ - Field, - }, + field::FieldRef, + table::TableRef, }, - QueryResCount, }, utils::{ Tokens, - Errs, sanitize_ident, + Errs, }, }, - super::{ - utils::PgQueryCtx, - select::Select, - }, -}; -#[cfg(feature = "chrono")] -use chrono::{ - DateTime, - Utc, }; + #[cfg(feature = "jiff")] -use jiff::{ - Timestamp, -}; +use jiff::Timestamp; + +use super::select::Select; -/// This is used for function expressions, to check the argument types and compute -/// a result type from them. See readme for details. #[derive(Clone)] -pub struct ComputeType(Rc, Vec) -> Option>); +pub struct ExprType(pub Vec<(ExprValName, Type)>); -impl ComputeType { - pub fn new( - f: impl Fn(&mut PgQueryCtx, &rpds::Vector, Vec) -> Option + 'static, - ) -> ComputeType { - return ComputeType(Rc::new(f)); +impl ExprType { + pub fn assert_scalar(&self, errs: &mut Errs, path: &rpds::Vector) -> Option { + if self.0.len() != 1 { + errs.err( + path, + format!("Select outputs must be scalars, but got result with more than one field: {}", self.0.len()), + ); + return None; + } + Some(self.0[0].1.clone()) + } +} + +pub struct ComputeType(pub Rc, &[ExprType]) -> ExprType>); + +impl Clone for ComputeType { + fn clone(&self) -> Self { + return ComputeType(self.0.clone()); } } @@ -68,6 +82,77 @@ impl std::fmt::Debug for ComputeType { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum SerialExpr { + LitArray(Vec), + LitNull(SimpleType), + LitBool(bool), + LitAuto(i64), + LitI32(i32), + LitI64(i64), + LitF32(f32), + LitF64(f64), + LitString(String), + LitBytes(Vec), + #[cfg(feature = "chrono")] + LitUtcTimeChrono(DateTime), + #[cfg(feature = "chrono")] + LitFixedOffsetTimeChrono(DateTime), + #[cfg(feature = "jiff")] + LitUtcTimeJiff(Timestamp), + BinOp { + left: Box, + op: BinOp, + right: Box, + }, + BinOpChain { + op: BinOp, + exprs: Vec, + }, + PrefixOp { + op: PrefixOp, + right: Box, + }, + Cast(Box, Type), +} + +impl From for Expr { + fn from(s: SerialExpr) -> Self { + match s { + SerialExpr::LitArray(v) => Expr::LitArray(v.into_iter().map(Expr::from).collect()), + SerialExpr::LitNull(t) => Expr::LitNull(t), + SerialExpr::LitBool(b) => Expr::LitBool(b), + SerialExpr::LitAuto(v) => Expr::LitAuto(v), + SerialExpr::LitI32(v) => Expr::LitI32(v), + SerialExpr::LitI64(v) => Expr::LitI64(v), + SerialExpr::LitF32(v) => Expr::LitF32(v), + SerialExpr::LitF64(v) => Expr::LitF64(v), + SerialExpr::LitString(v) => Expr::LitString(v), + SerialExpr::LitBytes(v) => Expr::LitBytes(v), + #[cfg(feature = "chrono")] + SerialExpr::LitUtcTimeChrono(v) => Expr::LitUtcTimeChrono(v), + #[cfg(feature = "chrono")] + SerialExpr::LitFixedOffsetTimeChrono(v) => Expr::LitFixedOffsetTimeChrono(v), + #[cfg(feature = "jiff")] + SerialExpr::LitUtcTimeJiff(v) => Expr::LitUtcTimeJiff(v), + SerialExpr::BinOp { left, op, right } => Expr::BinOp { + left: Box::new(Expr::from(*left)), + op: op, + right: Box::new(Expr::from(*right)), + }, + SerialExpr::BinOpChain { op, exprs } => Expr::BinOpChain { + op: op, + exprs: exprs.into_iter().map(Expr::from).collect(), + }, + SerialExpr::PrefixOp { op, right } => Expr::PrefixOp { + op: op, + right: Box::new(Expr::from(*right)), + }, + SerialExpr::Cast(e, t) => Expr::Cast(Box::new(Expr::from(*e)), t), + } + } +} + #[derive(Clone, Debug)] pub enum Expr { LitArray(Vec), @@ -98,14 +183,12 @@ pub enum Expr { /// you've aliased tables or field names, you'll have to instantiate `FieldId` /// yourself with the appropriate values. For synthetic values like function /// results you may need a `FieldId` with an empty `TableId` (`""`). - Field(Field), + Field(FieldRef), BinOp { left: Box, op: BinOp, right: Box, }, - /// This is the same as `BinOp` but allows chaining multiple expressions with the - /// same operator. This can be useful if you have many successive `AND`s or similar. BinOpChain { op: BinOp, exprs: Vec, @@ -114,9 +197,6 @@ pub enum Expr { op: PrefixOp, right: Box, }, - /// Represents a call to an SQL function, like `collate()`. You must provide a - /// helper to check and determine type of the result since we don't have a table of - /// functions and their return types at present. Call { func: String, args: Vec, @@ -130,7 +210,7 @@ pub enum Expr { Cast(Box, Type), } -#[derive(Clone, Hash, PartialEq, Eq, Debug)] +#[derive(Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct ExprValName { pub table_id: String, pub id: String, @@ -144,17 +224,17 @@ impl ExprValName { } } - pub(crate) fn empty() -> Self { + pub fn empty() -> Self { ExprValName { table_id: "".into(), id: "".into(), } } - pub(crate) fn field(f: &Field) -> Self { + pub fn field(f: &FieldRef) -> Self { ExprValName { - table_id: f.table.id.clone(), - id: f.id.clone(), + table_id: f.table_id.0.clone(), + id: f.field_id.0.clone(), } } @@ -168,61 +248,111 @@ impl ExprValName { impl Display for ExprValName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&format!("{}.{}", self.table_id, self.id), f) + if self.table_id.is_empty() { + return Display::fmt(&self.id, f); + } else { + return Display::fmt(&format!("{}.{}", self.table_id, self.id), f); + } } } -pub struct ExprType(pub Vec<(ExprValName, Type)>); +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum BinOp { + Plus, + Minus, + Multiply, + Divide, + And, + Or, + Equals, + NotEquals, + Is, + IsNot, + LessThan, + LessThanEqualTo, + GreaterThan, + GreaterThanEqualTo, +} -impl ExprType { - pub fn assert_scalar(&self, errs: &mut Errs, path: &rpds::Vector) -> Option<(ExprValName, Type)> { - if self.0.len() != 1 { - errs.err( - path, - format!("Select outputs must be scalars, but got result with more than one field: {}", self.0.len()), - ); - return None; - } - Some(self.0[0].clone()) - } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum PrefixOp { + Not, } -#[derive(Debug)] -#[samevariant(GeneralTypePairs)] -pub(crate) enum GeneralType { - Bool, - Numeric, - Blob, +pub(crate) fn check_same( + errs: &mut Errs, + path: &rpds::Vector, + left: &ExprType, + right: &ExprType, +) -> Option { + let left = left.assert_scalar(errs, &path.push_back("Left".into())) ?; + let right = right.assert_scalar(errs, &path.push_back("Right".into())) ?; + if left.opt != right.opt { + errs.err( + path, + format!("Operator arms optionality don't match: left has {} and right has {}", left.opt, right.opt), + ); + } + if left.type_.custom != right.type_.custom { + errs.err( + path, + format!( + "Operator arms custom types don't match: left has type {:?} and right has {:?}", + left.type_.custom, + right.type_.custom + ), + ); + } + if left.type_.type_ != right.type_.type_ { + errs.err( + path, + format!( + "Operator arms types don't match: left has type {:?} and right has {:?}", + left.type_.type_, + right.type_.type_ + ), + ); + } + Some(left.clone()) } -pub(crate) fn general_type(t: &Type) -> GeneralType { - match t.type_.type_ { - SimpleSimpleType::Auto => GeneralType::Numeric, - SimpleSimpleType::I32 => GeneralType::Numeric, - SimpleSimpleType::I64 => GeneralType::Numeric, - SimpleSimpleType::F32 => GeneralType::Numeric, - SimpleSimpleType::F64 => GeneralType::Numeric, - SimpleSimpleType::Bool => GeneralType::Bool, - SimpleSimpleType::String => GeneralType::Blob, - SimpleSimpleType::Bytes => GeneralType::Blob, - #[cfg(feature = "chrono")] - SimpleSimpleType::UtcTimeChrono => GeneralType::Numeric, - #[cfg(feature = "chrono")] - SimpleSimpleType::FixedOffsetTimeChrono => GeneralType::Numeric, - #[cfg(feature = "jiff")] - SimpleSimpleType::UtcTimeJiff => GeneralType::Numeric, +pub(crate) fn check_bool( + ctx: &mut PgQueryCtx, + path: &rpds::Vector, + t: &ExprType, +) { + let Some(t) = t.assert_scalar(&mut ctx.errs, path) else { + return; + }; + if t.opt { + ctx.errs.err(path, format!("Expected non-optional bool but got optional bool")); + } + if !matches!(t.type_.type_, SimpleSimpleType::Bool) { + ctx.errs.err(path, format!("Expected bool but type is non-bool: got {:?}", t.type_.type_)); } } -pub fn check_general_same_type(ctx: &mut PgQueryCtx, path: &rpds::Vector, left: &Type, right: &Type) { - if left.opt != right.opt { - ctx.errs.err(path, format!("Operator arms have differing optionality")); +pub(crate) fn check_assignable( + errs: &mut Errs, + path: &rpds::Vector, + left: &Type, + right: &ExprType, +) { + let Some(right) = right.assert_scalar(errs, path) else { + return; + }; + if left.type_.type_ != right.type_.type_ { + errs.err( + path, + format!( + "Expression has type {:?} which is not assignable to {:?}", + right.type_.type_, + left.type_.type_ + ), + ); } - match GeneralTypePairs::pairs(&general_type(left), &general_type(right)) { - GeneralTypePairs::Nonmatching(left, right) => { - ctx.errs.err(path, format!("Operator arms have incompatible types: {:?} and {:?}", left, right)); - }, - _ => { }, + if !left.opt && right.opt { + errs.err(path, format!("Expression is optional but destination is not")); } } @@ -244,7 +374,7 @@ pub(crate) fn check_general_same( ), ); } else if left.0.len() == 1 && right.0.len() == 1 { - check_general_same_type(ctx, path, &left.0[0].1, &left.0[0].1); + check_general_same_type(ctx, path, &left.0[0].1, &right.0[0].1); } else { for (i, (left, right)) in left.0.iter().zip(right.0.iter()).enumerate() { check_general_same_type(ctx, &path.push_back(format!("Record pair {}", i)), &left.1, &right.1); @@ -252,319 +382,250 @@ pub(crate) fn check_general_same( } } -pub(crate) fn check_same( - errs: &mut Errs, +pub(crate) fn check_general_same_type( + ctx: &mut PgQueryCtx, path: &rpds::Vector, - left: &ExprType, - right: &ExprType, -) -> Option { - let left = match left.assert_scalar(errs, &path.push_back("Left".into())) { - Some(t) => t, - None => { - return None; - }, - }; - let right = match right.assert_scalar(errs, &path.push_back("Right".into())) { - Some(t) => t, - None => { - return None; - }, - }; - if left.1.opt != right.1.opt { - errs.err( - path, - format!( - "Expected same types, but left nullability is {} but right nullability is {}", - left.1.opt, - right.1.opt - ), - ); - } - if left.1.type_.custom != right.1.type_.custom { - errs.err( - path, - format!( - "Expected same types, but left rust type is {:?} while right rust type is {:?}", - left.1.type_.custom, - right.1.type_.custom - ), - ); - } - if left.1.type_.type_ != right.1.type_.type_ { - errs.err( - path, - format!( - "Expected same types, but left base type is {:?} while right base type is {:?}", - left.1.type_.type_, - right.1.type_.type_ - ), - ); - } - Some(left.1.clone()) -} - -pub(crate) fn check_bool(ctx: &mut PgQueryCtx, path: &rpds::Vector, a: &ExprType) { - let t = match a.assert_scalar(&mut ctx.errs, path) { - Some(t) => t, - None => { - return; - }, - }; - if t.1.opt { - ctx.errs.err(path, format!("Expected bool type but is nullable: got {:?}", t)); - } - if !matches!(t.1.type_.type_, SimpleSimpleType::Bool) { - ctx.errs.err(path, format!("Expected bool but type is non-bool: got {:?}", t.1.type_.type_)); + left: &Type, + right: &Type, +) { + if left.type_.type_ != right.type_.type_ { + ctx + .errs + .err( + path, + format!( + "Operator arms types don't match: left has type {:?} and right has {:?}", + left.type_.type_, + right.type_.type_ + ), + ); } } -pub(crate) fn check_assignable(errs: &mut Errs, path: &rpds::Vector, a: &Type, b: &ExprType) { - check_same(errs, path, &ExprType(vec![(ExprValName::empty(), a.clone())]), b); -} - impl Expr { - pub(crate) fn build( + pub fn build( &self, ctx: &mut PgQueryCtx, path: &rpds::Vector, scope: &HashMap, ) -> (ExprType, Tokens) { - macro_rules! empty_type{ - ($o: expr, $t: expr) => { - (ExprType(vec![(ExprValName::empty(), Type { - type_: SimpleType { - type_: $t, - custom: None, - }, - opt: false, - })]), $o) - }; - } - - fn do_bin_op( - ctx: &mut PgQueryCtx, - path: &rpds::Vector, - scope: &HashMap, - op: &BinOp, - exprs: &Vec, - ) -> (ExprType, Tokens) { - if exprs.len() < 2 { - ctx.errs.err(path, format!("Binary ops must have at least two operands, but got {}", exprs.len())); - } - let mut res = vec![]; - for (i, e) in exprs.iter().enumerate() { - res.push(e.build(ctx, &path.push_back(format!("Operand {}", i)), scope)); - } - let t = match op { - BinOp::Plus | BinOp::Minus | BinOp::Multiply | BinOp::Divide => { - let base = res.get(0).unwrap(); - let t = - match check_same( - &mut ctx.errs, - &path.push_back(format!("Operands 0, 1")), - &base.0, - &res.get(0).unwrap().0, - ) { - Some(t) => t, - None => { - return (ExprType(vec![]), Tokens::new()); - }, - }; - for (i, res) in res.iter().enumerate().skip(2) { - match check_same( - &mut ctx.errs, - &path.push_back(format!("Operands 0, {}", i)), - &base.0, - &res.0, - ) { - Some(_) => { }, - None => { - return (ExprType(vec![]), Tokens::new()); - }, - }; - } - t - }, - BinOp::And | BinOp::Or => { - for (i, res) in res.iter().enumerate() { - check_bool(ctx, &path.push_back(format!("Operand {}", i)), &res.0); - } - Type { - type_: SimpleType { - type_: SimpleSimpleType::Bool, - custom: None, - }, - opt: false, - } - }, - BinOp::Equals | - BinOp::NotEquals | - BinOp::Is | - BinOp::IsNot | - BinOp::LessThan | - BinOp::LessThanEqualTo | - BinOp::GreaterThan | - BinOp::GreaterThanEqualTo => { - let base = res.get(0).unwrap(); - check_general_same( - ctx, - &path.push_back(format!("Operands 0, 1")), - &base.0, - &res.get(1).unwrap().0, - ); - for (i, res) in res.iter().enumerate().skip(2) { - check_general_same(ctx, &path.push_back(format!("Operands 0, {}", i)), &base.0, &res.0); - } - Type { - type_: SimpleType { - type_: SimpleSimpleType::Bool, - custom: None, - }, - opt: false, - } - }, - }; - let token = match op { - BinOp::Plus => "+", - BinOp::Minus => "-", - BinOp::Multiply => "*", - BinOp::Divide => "/", - BinOp::And => "and", - BinOp::Or => "or", - BinOp::Equals => "=", - BinOp::NotEquals => "!=", - BinOp::Is => "is", - BinOp::IsNot => "is not", - BinOp::LessThan => "<", - BinOp::LessThanEqualTo => "<=", - BinOp::GreaterThan => ">", - BinOp::GreaterThanEqualTo => ">=", - }; - let mut out = Tokens::new(); - out.s("("); - for (i, res) in res.iter().enumerate() { - if i > 0 { - out.s(token); - } - out.s(&res.1.to_string()); - } - out.s(")"); - (ExprType(vec![(ExprValName::empty(), t)]), out) - } - match self { - Expr::LitArray(t) => { + Expr::LitArray(v) => { let mut out = Tokens::new(); - let mut child_types = vec![]; - out.s("("); - for (i, child) in t.iter().enumerate() { + out.s("array ["); + let mut res_types = vec![]; + for (i, e) in v.iter().enumerate() { if i > 0 { - out.s(", "); + out.s(","); } - let (child_type, child_tokens) = child.build(ctx, path, scope); - out.s(&child_tokens.to_string()); - child_types.extend(child_type.0); + let res = e.build(ctx, path, scope); + out.s(&res.1.to_string()); + res_types.push(res.0); } - out.s(")"); - return (ExprType(child_types), out); + out.s("]"); + let mut out_type = None; + for (i, t) in res_types.iter().enumerate() { + if let Some(prev) = &out_type { + check_general_same(ctx, &path.push_back(format!("Array element {}", i)), prev, t); + } else { + out_type = Some(t.clone()); + } + } + return (out_type.unwrap_or(ExprType(vec![])), out); }, Expr::LitNull(t) => { let mut out = Tokens::new(); out.s("null"); - return (ExprType(vec![(ExprValName::empty(), Type { - type_: t.clone(), - opt: true, - })]), out); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: t.clone(), + opt: true, + })]), + out + ); }, - Expr::LitBool(x) => { + Expr::LitBool(b) => { let mut out = Tokens::new(); - out.s(if *x { - "true" - } else { - "false" - }); - return empty_type!(out, SimpleSimpleType::Bool); + out.s(if *b { "true" } else { "false" }); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::Bool, + custom: None, + }, + opt: false, + })]), + out + ); }, - Expr::LitAuto(x) => { + Expr::LitAuto(v) => { let mut out = Tokens::new(); - out.s(&x.to_string()); - return empty_type!(out, SimpleSimpleType::Auto); + out.s(&v.to_string()); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::Auto, + custom: None, + }, + opt: false, + })]), + out + ); }, - Expr::LitI32(x) => { + Expr::LitI32(v) => { let mut out = Tokens::new(); - out.s(&x.to_string()); - return empty_type!(out, SimpleSimpleType::I32); + out.s(&v.to_string()); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::I32, + custom: None, + }, + opt: false, + })]), + out + ); }, - Expr::LitI64(x) => { + Expr::LitI64(v) => { let mut out = Tokens::new(); - out.s(&x.to_string()); - return empty_type!(out, SimpleSimpleType::I64); + out.s(&v.to_string()); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::I64, + custom: None, + }, + opt: false, + })]), + out + ); }, - Expr::LitF32(x) => { + Expr::LitF32(v) => { let mut out = Tokens::new(); - out.s(&x.to_string()); - return empty_type!(out, SimpleSimpleType::F32); + out.s(&v.to_string()); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::F32, + custom: None, + }, + opt: false, + })]), + out + ); }, - Expr::LitF64(x) => { + Expr::LitF64(v) => { let mut out = Tokens::new(); - out.s(&x.to_string()); - return empty_type!(out, SimpleSimpleType::F64); + out.s(&v.to_string()); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::F64, + custom: None, + }, + opt: false, + })]), + out + ); }, - Expr::LitString(x) => { + Expr::LitString(v) => { let mut out = Tokens::new(); - out.s(&format!("'{}'", x.replace("'", "''"))); - return empty_type!(out, SimpleSimpleType::String); + out.s(&format!("'{}'", v.replace("'", "''"))); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::String, + custom: None, + }, + opt: false, + })]), + out + ); }, - Expr::LitBytes(x) => { + Expr::LitBytes(v) => { let mut out = Tokens::new(); - let h = hex::encode(&x); - out.s(&format!("x'{}'", h)); - return empty_type!(out, SimpleSimpleType::Bytes); + out.s(&format!("'\\x{}'", hex::encode(v))); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::Bytes, + custom: None, + }, + opt: false, + })]), + out + ); }, #[cfg(feature = "chrono")] - Expr::LitUtcTimeChrono(d) => { + Expr::LitUtcTimeChrono(v) => { let mut out = Tokens::new(); - let d = d.to_rfc3339(); - out.s(&format!("'{}'", d)); - return empty_type!(out, SimpleSimpleType::UtcTimeChrono); + out.s(&format!("'{}'", v.to_rfc3339())); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::UtcTimeChrono, + custom: None, + }, + opt: false, + })]), + out + ); }, #[cfg(feature = "chrono")] - Expr::LitFixedOffsetTimeChrono(d) => { + Expr::LitFixedOffsetTimeChrono(v) => { let mut out = Tokens::new(); - let d = d.to_rfc3339(); - out.s(&format!("'{}'", d)); - return empty_type!(out, SimpleSimpleType::FixedOffsetTimeChrono); + out.s(&format!("'{}'", v.to_rfc3339())); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::FixedOffsetTimeChrono, + custom: None, + }, + opt: false, + })]), + out + ); }, #[cfg(feature = "jiff")] - Expr::LitUtcTimeJiff(d) => { + Expr::LitUtcTimeJiff(v) => { let mut out = Tokens::new(); - let d = d.to_string(); - out.s(&format!("'{}'", d)); - return empty_type!(out, SimpleSimpleType::UtcTimeChrono); + out.s(&format!("'{}'", v.to_string())); + return ( + ExprType(vec![(ExprValName::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::UtcTimeJiff, + custom: None, + }, + opt: false, + })]), + out + ); }, - Expr::Param { name: x, type_: t } => { - let path = path.push_back(format!("Param ({})", x)); + Expr::Param { name, type_ } => { let mut out = Tokens::new(); - let mut errs = vec![]; - let i = match ctx.rust_arg_lookup.entry(x.clone()) { + let path = path.push_back(format!("Param ({})", name)); + let i = match ctx.rust_arg_lookup.entry(name.clone()) { std::collections::hash_map::Entry::Occupied(e) => { let (i, prev_t) = e.get(); - if t != prev_t { - errs.push( - format!("Parameter {} specified with multiple types: {:?}, {:?}", x, t, prev_t), - ); + if type_ != prev_t { + ctx + .errs + .err( + &path, + format!("Parameter {} specified with multiple types: {:?}, {:?}", name, type_, prev_t), + ); } *i }, std::collections::hash_map::Entry::Vacant(e) => { - let i = ctx.query_args.len(); - e.insert((i, t.clone())); - let rust_types = to_rust_types(&t.type_.type_); + let i = ctx.rust_args.len() + 1; + e.insert((i, type_.clone())); + let rust_types = to_rust_types(&type_.type_.type_); let custom_trait_ident = rust_types.custom_trait; let rust_type = rust_types.arg_type; - let ident = format_ident!("{}", sanitize_ident(x).1); - let (mut rust_type, mut rust_forward) = if let Some(custom) = &t.type_.custom { - let custom_ident = match syn::parse_str::(custom.as_str()) { + let ident = format_ident!("{}", sanitize_ident(name).1); + let (mut rust_type, mut rust_forward) = if let Some(custom) = &type_.type_.custom { + let custom_ident = match syn::parse_str::(custom.as_str()) { Ok(p) => p, Err(e) => { ctx.errs.err(&path, format!("Couldn't parse custom type {}: {:?}", custom, e)); @@ -577,7 +638,7 @@ impl Expr { } else { (rust_type, quote!(#ident)) }; - if t.opt { + if type_.opt { rust_type = quote!(Option < #rust_type >); rust_forward = quote!(#ident.map(| #ident | #rust_forward)); } @@ -586,11 +647,8 @@ impl Expr { i }, }; - for e in errs { - ctx.errs.err(&path, e); - } - out.s(&format!("${}", i + 1)); - return (ExprType(vec![(ExprValName::local(x.clone()), t.clone())]), out); + out.s(&format!("${}", i)); + return (ExprType(vec![(ExprValName::local(name.clone()), type_.clone())]), out); }, Expr::Field(x) => { let name = ExprValName::field(x); @@ -602,7 +660,7 @@ impl Expr { .err( path, format!( - "Expression references {} but this field isn't available here (available fields: {:?})", + "Expression references {:?} but this field isn't available here (available fields: {:?})", x, scope.iter().map(|e| e.0.to_string()).collect::>() ), @@ -611,96 +669,119 @@ impl Expr { }, }; let mut out = Tokens::new(); - out.id(&x.table.id).s(".").id(&x.id); + let table_info = ctx.tables.get(&TableRef(x.table_id.clone())).unwrap(); + let field_info = table_info.fields.get(x).unwrap(); + out.id(&table_info.sql_name).s(".").id(&field_info.sql_name); return (ExprType(vec![(name, t.clone())]), out); }, Expr::BinOp { left, op, right } => { - return do_bin_op( - ctx, - &path.push_back(format!("Bin op {:?}", op)), - scope, - op, - &vec![left.as_ref().clone(), right.as_ref().clone()], - ); + let mut out = Tokens::new(); + let l_res = left.build(ctx, &path.push_back("Bin op left".into()), scope); + let r_res = right.build(ctx, &path.push_back("Bin op right".into()), scope); + let t = check_same(&mut ctx.errs, path, &l_res.0, &r_res.0); + let token = match op { + BinOp::Plus => "+", + BinOp::Minus => "-", + BinOp::Multiply => "*", + BinOp::Divide => "/", + BinOp::And => "and", + BinOp::Or => "or", + BinOp::Equals => "=", + BinOp::NotEquals => "<>", + BinOp::Is => "is", + BinOp::IsNot => "is not", + BinOp::LessThan => "<", + BinOp::LessThanEqualTo => "<=", + BinOp::GreaterThan => ">", + BinOp::GreaterThanEqualTo => ">=", + }; + out.s(&l_res.1.to_string()).s(token).s(&r_res.1.to_string()); + let mut res_t = t.unwrap_or(Type { + type_: SimpleType { + type_: SimpleSimpleType::I32, + custom: None, + }, + opt: false, + }); + match op { + BinOp::Equals | + BinOp::NotEquals | + BinOp::Is | + BinOp::IsNot | + BinOp::LessThan | + BinOp::LessThanEqualTo | + BinOp::GreaterThan | + BinOp::GreaterThanEqualTo => { + res_t = Type { + type_: SimpleType { + type_: SimpleSimpleType::Bool, + custom: None, + }, + opt: false, + }; + }, + _ => { }, + } + return (ExprType(vec![(ExprValName::empty(), res_t)]), out); }, Expr::BinOpChain { op, exprs } => { - return do_bin_op(ctx, &path.push_back(format!("Chain bin op {:?}", op)), scope, op, exprs); + let mut out = Tokens::new(); + let token = match op { + BinOp::And => "and", + BinOp::Or => "or", + _ => panic!("Chain only supported for and/or"), + }; + let mut out_t = None; + for (i, e) in exprs.iter().enumerate() { + if i > 0 { + out.s(token); + } + let res = e.build(ctx, &path.push_back(format!("Chain element {}", i)), scope); + check_bool(ctx, &path.push_back(format!("Chain element {}", i)), &res.0); + out.s(&res.1.to_string()); + out_t = Some(res.0); + } + return (out_t.unwrap_or(ExprType(vec![])), out); }, Expr::PrefixOp { op, right } => { - let path = path.push_back(format!("Prefix op {:?}", op)); let mut out = Tokens::new(); - let res = right.build(ctx, &path, scope); - let (op_text, op_type) = match op { - PrefixOp::Not => { - check_bool(ctx, &path, &res.0); - ("not", SimpleSimpleType::Bool) - }, + let token = match op { + PrefixOp::Not => "not", }; - out.s(op_text).s(&res.1.to_string()); - return empty_type!(out, op_type); + let res = right.build(ctx, &path.push_back("Prefix op".into()), scope); + check_bool(ctx, path, &res.0); + out.s(token).s(&res.1.to_string()); + return (res.0, out); }, Expr::Call { func, args, compute_type } => { - let mut types = vec![]; let mut out = Tokens::new(); - out.s(func); - out.s("("); + out.s(func).s("("); + let mut arg_types = vec![]; for (i, arg) in args.iter().enumerate() { if i > 0 { out.s(","); } - let (arg_type, tokens) = - arg.build(ctx, &path.push_back(format!("Call [{}] arg {}", func, i)), scope); - types.push(arg_type); + let (t, tokens) = arg.build(ctx, &path.push_back(format!("Call arg {}", i)), scope); out.s(&tokens.to_string()); + arg_types.push(t); } out.s(")"); - let type_ = match (compute_type.0)(ctx, path, types) { - Some(t) => t, - None => { - return (ExprType(vec![]), Tokens::new()); - }, - }; - return (ExprType(vec![(ExprValName::empty(), type_)]), out); + return (compute_type.0(ctx, path, &arg_types), out); }, Expr::Select(s) => { - let path = path.push_back(format!("Subselect")); - return s.build(ctx, &path, QueryResCount::Many); + let mut out = Tokens::new(); + out.s("("); + let (t, tokens) = s.build(ctx, &path.push_back("Subselect".into()), crate::pg::QueryResCount::Many); + out.s(&tokens.to_string()).s(")"); + return (t, out); }, Expr::Cast(e, t) => { - let path = path.push_back(format!("Cast")); - let out = e.build(ctx, &path, scope); - let got_t = match out.0.assert_scalar(&mut ctx.errs, &path) { - Some(t) => t, - None => { - return (ExprType(vec![]), Tokens::new()); - }, - }; - check_general_same_type(ctx, &path, t, &got_t.1); - return (ExprType(vec![(got_t.0, t.clone())]), out.1); + let mut out = Tokens::new(); + let (got_t, tokens) = e.build(ctx, &path.push_back("Cast".into()), scope); + check_general_same(ctx, path, &got_t, &ExprType(vec![(ExprValName::empty(), t.clone())])); + out.s("(").s(&tokens.to_string()).s("::").s(&to_rust_types(&t.type_.type_).ret_type.to_string()).s(")"); + return (ExprType(vec![(ExprValName::empty(), t.clone())]), out); }, - }; + } } } - -#[derive(Clone, Debug)] -pub enum BinOp { - Plus, - Minus, - Multiply, - Divide, - And, - Or, - Equals, - NotEquals, - Is, - IsNot, - LessThan, - LessThanEqualTo, - GreaterThan, - GreaterThanEqualTo, -} - -#[derive(Clone, Debug)] -pub enum PrefixOp { - Not, -} diff --git a/src/pg/query/helpers.rs b/src/pg/query/helpers.rs index 668a919..aded2e6 100644 --- a/src/pg/query/helpers.rs +++ b/src/pg/query/helpers.rs @@ -1,4 +1,6 @@ -use crate::pg::schema::field::Field; +use crate::pg::{ + FieldHandle, +}; use super::expr::{ Expr, BinOp, @@ -6,80 +8,67 @@ use super::expr::{ /// Generates a field element for instert and update statements, to set a field /// from a parameter of the same type. -pub fn set_field(param_name: impl Into, f: &Field) -> (Field, Expr) { +pub fn set_field(param_name: impl Into, f: &FieldHandle) -> (FieldHandle, Expr) { (f.clone(), field_param(param_name, f)) } /// Generates a param matching a field in name in type -pub fn field_param(param_name: impl Into, f: &Field) -> Expr { +pub fn field_param(param_name: impl Into, f: &FieldHandle) -> Expr { + let version = f.table.version.0.borrow(); + let type_ = version.tables.get(&f.table.schema_id).unwrap().fields.get(&f.schema_id).unwrap().type_.type_.clone(); Expr::Param { name: param_name.into(), - type_: f.type_.type_.clone(), + type_: type_, } } /// Generates an expression checking for equality of a field and a parameter and /// the same type. -pub fn eq_field(param_name: impl Into, f: &Field) -> Expr { +pub fn eq_field(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Field(f.clone())), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::Equals, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } /// Generates an expression selecting field values greater than a corresponding /// parameter -pub fn gt_field(param_name: impl Into, f: &Field) -> Expr { +pub fn gt_field(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Field(f.clone())), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::GreaterThan, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } /// Generates an expression selecting field values greater than or equal to a /// corresponding parameter -pub fn gte_field(param_name: impl Into, f: &Field) -> Expr { +pub fn gte_field(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Field(f.clone())), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::GreaterThanEqualTo, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } /// Generates an expression selecting field values greater than a corresponding /// parameter -pub fn lt_field(param_name: impl Into, f: &Field) -> Expr { +pub fn lt_field(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Field(f.clone())), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::LessThan, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } /// Generates an expression selecting field values greater than or equal to a /// corresponding parameter -pub fn lte_field(param_name: impl Into, f: &Field) -> Expr { +pub fn lte_field(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Field(f.clone())), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::LessThanEqualTo, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } diff --git a/src/pg/query/insert.rs b/src/pg/query/insert.rs index 820be35..4a1c921 100644 --- a/src/pg/query/insert.rs +++ b/src/pg/query/insert.rs @@ -8,8 +8,8 @@ use crate::{ pg::{ QueryResCount, schema::{ - field::Field, - table::Table, + field::FieldRef, + table::TableRef, }, types::SimpleSimpleType, }, @@ -23,24 +23,25 @@ use super::{ ExprValName, }, utils::{ + PgQueryCtx, QueryBody, build_returning, build_set, + Returning, }, - select::Returning, }; pub enum InsertConflict { DoNothing, DoUpdate { - conflict: Vec, - set: Vec<(Field, Expr)>, + conflict: Vec, + set: Vec<(FieldRef, Expr)>, }, } pub struct Insert { - pub(crate) table: Table, - pub(crate) values: Vec<(Field, Expr)>, + pub(crate) table: TableRef, + pub(crate) values: Vec<(FieldRef, Expr)>, pub(crate) on_conflict: Option, pub(crate) returning: Vec, } @@ -55,53 +56,56 @@ impl QueryBody for Insert { // Prep let mut check_inserting_fields = HashSet::new(); for p in &self.values { - if p.0.type_.type_.opt { + let field_info = match ctx.tables.get(&self.table).and_then(|t| t.fields.get(&p.0)) { + Some(f) => f, + None => { + ctx.errs.err(path, format!("Unknown field {:?} for insert into {:?}", p.0, self.table)); + continue; + }, + }; + if field_info.type_.opt { continue; } if !check_inserting_fields.insert(p.0.clone()) { - ctx.errs.err(path, format!("Duplicate field {} in insert", p.0)); + ctx.errs.err(path, format!("Duplicate field {:?} in insert", p.0)); } } let mut scope = HashMap::new(); - for (field, v) in match ctx.tables.get(&self.table) { + let table_info = match ctx.tables.get(&self.table) { Some(t) => t, None => { - ctx.errs.err(path, format!("Unknown table {} for insert", self.table)); + ctx.errs.err(path, format!("Unknown table {:?} for insert", self.table)); return (ExprType(vec![]), Tokens::new()); }, - } { - scope.insert(ExprValName::field(field), v.clone()); - if !field.type_.type_.opt && field.type_.type_.type_.type_ != SimpleSimpleType::Auto && - !check_inserting_fields.remove(field) { - ctx.errs.err(path, format!("{} is a non-optional field but is missing in insert", field)); + }; + for (field_ref, info) in &table_info.fields { + scope.insert(ExprValName::field(field_ref), info.type_.clone()); + if !info.type_.opt && info.type_.type_.type_ != SimpleSimpleType::Auto && + !check_inserting_fields.remove(field_ref) { + ctx.errs.err(path, format!("Field {:?} is a non-optional field but is missing in insert", field_ref)); } } drop(check_inserting_fields); // Build query let mut out = Tokens::new(); - out.s("insert into").id(&self.table.id).s("("); - for (i, (field, _)) in self.values.iter().enumerate() { + out.s("insert into").id(&table_info.sql_name).s("("); + for (i, (field_ref, _)) in self.values.iter().enumerate() { if i > 0 { out.s(","); } - out.id(&field.id); + let field_info = table_info.fields.get(field_ref).unwrap(); + out.id(&field_info.sql_name); } out.s(") values ("); - for (i, (field, val)) in self.values.iter().enumerate() { + for (i, (field_ref, val)) in self.values.iter().enumerate() { if i > 0 { out.s(","); } - let field_type = match ctx.tables.get(&field.table).and_then(|t| t.get(&field)) { - Some(t) => t, - None => { - ctx.errs.err(path, format!("Insert destination value field {} is not known", field)); - continue; - }, - }; - let path = path.push_back(format!("Insert value {} ({})", i, field)); + let field_info = table_info.fields.get(field_ref).unwrap(); + let path = path.push_back(format!("Insert value {} ({:?})", i, field_ref)); let res = val.build(ctx, &path, &scope); - check_assignable(&mut ctx.errs, &path, &field_type, &res.0); + check_assignable(&mut ctx.errs, &path, &field_info.type_, &res.0); out.s(&res.1.to_string()); } out.s(")"); @@ -117,7 +121,8 @@ impl QueryBody for Insert { if i > 0 { out.s(","); } - out.id(&f.id); + let field_info = table_info.fields.get(f).unwrap(); + out.id(&field_info.sql_name); } out.s(")"); out.s("do update"); @@ -125,24 +130,6 @@ impl QueryBody for Insert { }, } } - match (&res_count, &self.on_conflict) { - (QueryResCount::MaybeOne, Some(InsertConflict::DoUpdate { .. })) => { - ctx.errs.err(path, format!("Insert with [on conflict update] will always return a row")); - }, - (QueryResCount::One, Some(InsertConflict::DoNothing)) => { - ctx.errs.err(path, format!("Insert with [on conflict do nothing] may not return a row")); - }, - (QueryResCount::Many, _) => { - ctx.errs.err(path, format!("Insert can at most return one row, but res count is many")); - }, - (QueryResCount::None, _) | (QueryResCount::One, None) | (QueryResCount::MaybeOne, None) => { - // handled elsewhere, nop - }, - (QueryResCount::One, Some(InsertConflict::DoUpdate { .. })) | - (QueryResCount::MaybeOne, Some(InsertConflict::DoNothing)) => { - // ok - }, - } let out_type = build_returning(ctx, path, &scope, &mut out, &self.returning, res_count); (out_type, out) } diff --git a/src/pg/query/select.rs b/src/pg/query/select.rs index eedfe73..a6a77e1 100644 --- a/src/pg/query/select.rs +++ b/src/pg/query/select.rs @@ -8,7 +8,7 @@ use crate::{ }, QueryResCount, schema::{ - table::Table, + table::TableRef, }, }, }; @@ -17,6 +17,7 @@ use super::{ QueryBody, PgQueryCtx, build_returning_values, + Returning, }, expr::{ Expr, @@ -36,7 +37,7 @@ pub enum Order { #[derive(Clone, Debug)] pub enum JoinSource { Subsel(Box), /// This is a synthetic expression, saying to treat the result of the expression as /// having the specified type. Use this for casting between primitive types and /// Rust new-types for instance. Cast(Box, Type), } -impl Expr { - pub fn field(f: &Field) -> Expr { - return Expr::Binding(Binding::field(f)); - } -} - -#[derive(Clone, Hash, PartialEq, Eq, Debug)] +#[derive(Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct Binding { pub table_id: String, pub id: String, } impl Binding { - /// Create an expression field/value name for a select-local (tableless) field, for - /// instance `WINDOW` fields. - pub fn local(name: impl AsRef) -> Self { + pub(crate) fn local(name: String) -> Self { Binding { table_id: "".into(), - id: name.as_ref().to_string(), + id: name, } } - pub(crate) fn empty() -> Self { + pub fn empty() -> Self { Binding { table_id: "".into(), id: "".into(), } } - /// Create an expression field/value name from a table field. - pub fn field(f: &Field) -> Self { + pub fn field(f: &FieldRef) -> Self { Binding { - table_id: f.table.id.clone(), - id: f.id.clone(), + table_id: f.table_id.0.clone(), + id: f.field_id.0.clone(), } } - /// Derive an expression field/value name from a different name, with a new alias. - pub fn with_alias(&self, s: &str) -> Binding { + pub(crate) fn with_alias(&self, s: &str) -> Binding { Binding { table_id: s.into(), id: self.id.clone(), @@ -202,433 +252,112 @@ impl Binding { impl Display for Binding { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&format!("{}.{}", self.table_id, self.id), f) - } -} - -pub struct ExprType(pub Vec<(Binding, Type)>); - -impl ExprType { - pub fn assert_scalar(&self, errs: &mut Errs, path: &rpds::Vector) -> Option<(Binding, Type)> { - if self.0.len() != 1 { - errs.err(path, format!("Select outputs must be scalars, but got result with {} fields", self.0.len())); - return None; - } - Some(self.0[0].clone()) - } -} - -pub fn check_general_same_type(ctx: &mut SqliteQueryCtx, path: &rpds::Vector, left: &Type, right: &Type) { - if left.opt != right.opt { - ctx.errs.err(path, format!("Operator arms have differing optionality")); - } - if left.array != right.array { - ctx.errs.err(path, format!("Operator arms are either not both arrays or not both scalars")); - } - - #[derive(Debug)] - #[samevariant(GeneralTypePairs)] - enum GeneralType { - Bool, - Numeric, - Blob, - } - - fn general_type(t: &Type) -> GeneralType { - match t.type_.type_ { - SimpleSimpleType::U32 => GeneralType::Numeric, - SimpleSimpleType::I32 => GeneralType::Numeric, - SimpleSimpleType::I64 => GeneralType::Numeric, - SimpleSimpleType::F32 => GeneralType::Numeric, - SimpleSimpleType::F64 => GeneralType::Numeric, - SimpleSimpleType::Bool => GeneralType::Bool, - SimpleSimpleType::String => GeneralType::Blob, - SimpleSimpleType::Bytes => GeneralType::Blob, - #[cfg(feature = "chrono")] - SimpleSimpleType::UtcTimeSChrono => GeneralType::Numeric, - #[cfg(feature = "chrono")] - SimpleSimpleType::UtcTimeMsChrono => GeneralType::Blob, - #[cfg(feature = "chrono")] - SimpleSimpleType::FixedOffsetTimeMsChrono => GeneralType::Blob, - #[cfg(feature = "jiff")] - SimpleSimpleType::UtcTimeSJiff => GeneralType::Numeric, - #[cfg(feature = "jiff")] - SimpleSimpleType::UtcTimeMsJiff => GeneralType::Blob, - } - } - - match GeneralTypePairs::pairs(&general_type(left), &general_type(right)) { - GeneralTypePairs::Nonmatching(left, right) => { - ctx.errs.err(path, format!("Operator arms have incompatible types: {:?} and {:?}", left, right)); - }, - _ => { }, - } -} - -pub(crate) fn check_general_same( - ctx: &mut SqliteQueryCtx, - path: &rpds::Vector, - left: &ExprType, - right: &ExprType, -) { - if left.0.len() != right.0.len() { - ctx - .errs - .err( - path, - format!( - "Operator arms record type lengths don't match: left has {} fields and right has {}", - left.0.len(), - right.0.len() - ), - ); - } else if left.0.len() == 1 && right.0.len() == 1 { - check_general_same_type(ctx, path, &left.0[0].1, &left.0[0].1); - } else { - for (i, (left, right)) in left.0.iter().zip(right.0.iter()).enumerate() { - check_general_same_type(ctx, &path.push_back(format!("Record pair {}", i)), &left.1, &right.1); + if self.table_id.is_empty() { + return Display::fmt(&self.id, f); + } else { + return Display::fmt(&format!("{}.{}", self.table_id, self.id), f); } } } -pub(crate) fn check_same( - errs: &mut Errs, - path: &rpds::Vector, - left: &ExprType, - right: &ExprType, -) -> Option { - let left = match left.assert_scalar(errs, &path.push_back("Left".into())) { - Some(t) => t, - None => { - return None; - }, - }; - let right = match right.assert_scalar(errs, &path.push_back("Right".into())) { - Some(t) => t, - None => { - return None; - }, - }; - if left.1.opt != right.1.opt { - errs.err( - path, - format!( - "Expected same types, but left nullability is {} but right nullability is {}", - left.1.opt, - right.1.opt - ), - ); - } - if left.1.type_.custom != right.1.type_.custom { - errs.err( - path, - format!( - "Expected same types, but left rust type is {:?} while right rust type is {:?}", - left.1.type_.custom, - right.1.type_.custom - ), - ); - } - if left.1.type_.type_ != right.1.type_.type_ { - errs.err( - path, - format!( - "Expected same types, but left base type is {:?} while right base type is {:?}", - left.1.type_.type_, - right.1.type_.type_ - ), - ); - } - Some(left.1.clone()) +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum BinOp { + Plus, + Minus, + Multiply, + Divide, + And, + Or, + Equals, + NotEquals, + Is, + IsNot, + LessThan, + LessThanEqualTo, + GreaterThan, + GreaterThanEqualTo, + Like, + In, + NotIn, } -pub(crate) fn check_bool(ctx: &mut SqliteQueryCtx, path: &rpds::Vector, a: &ExprType) { - let t = match a.assert_scalar(&mut ctx.errs, path) { - Some(t) => t, - None => { - return; - }, - }; - if t.1.opt { - ctx.errs.err(path, format!("Expected bool type but is nullable: got {:?}", t)); - } - if !matches!(t.1.type_.type_, SimpleSimpleType::Bool) { - ctx.errs.err(path, format!("Expected bool but type is non-bool: got {:?}", t.1.type_.type_)); - } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum PrefixOp { + Not, } -#[cfg(feature = "chrono")] -pub(crate) fn check_utc_if_time(ctx: &mut SqliteQueryCtx, path: &rpds::Vector, t: &ExprType) { - for (i, el) in t.0.iter().enumerate() { - if matches!(el.1.type_.type_, SimpleSimpleType::FixedOffsetTimeMsChrono) { - ctx.errs.err( - &if t.0.len() == 1 { - path.clone() - } else { - path.push_back(format!("Record pair {}", i)) +macro_rules!empty_type { + ($out: expr, $t: expr) => { + ( + ExprType(vec![(Binding::empty(), Type { + type_: SimpleType { + type_: $t, + custom: None, }, - format!( - "Datetimes with non-utc offsets may not be used in normal binary operators - use the `Tz` operators instead" - ), - ); - } - } -} - -pub(crate) fn check_assignable(errs: &mut Errs, path: &rpds::Vector, a: &Type, b: &ExprType) { - check_same(errs, path, &ExprType(vec![(Binding::empty(), a.clone())]), b); + opt: false, + })]), + $out + ) + }; } impl Expr { - pub(crate) fn build( + pub fn build( &self, ctx: &mut SqliteQueryCtx, path: &rpds::Vector, scope: &HashMap, ) -> (ExprType, Tokens) { - macro_rules! empty_type{ - ($o: expr, $t: expr) => { - (ExprType(vec![(Binding::empty(), Type { - type_: SimpleType { - type_: $t, - custom: None, - }, - opt: false, - array: false, - })]), $o) - }; - } - - fn do_bin_op( - ctx: &mut SqliteQueryCtx, - path: &rpds::Vector, - scope: &HashMap, - op: &BinOp, - exprs: &Vec, - ) -> (ExprType, Tokens) { - let operand_lower_limit; - match op { - BinOp::Plus | BinOp::Minus | BinOp::Multiply | BinOp::Divide | BinOp::And | BinOp::Or => { - operand_lower_limit = 1; - }, - BinOp::Equals | - BinOp::NotEquals | - BinOp::Is | - BinOp::IsNot | - BinOp::TzEquals | - BinOp::TzNotEquals | - BinOp::TzIs | - BinOp::TzIsNot | - BinOp::LessThan | - BinOp::LessThanEqualTo | - BinOp::GreaterThan | - BinOp::GreaterThanEqualTo | - BinOp::Like | - BinOp::In | - BinOp::NotIn => { - operand_lower_limit = 2; - }, - }; - if exprs.len() < operand_lower_limit { - ctx - .errs - .err( - path, - format!( - "{:?} must have at least {} operand(s), but got {}", - op, - operand_lower_limit, - exprs.len() - ), - ); - } - let mut res = vec![]; - for (i, e) in exprs.iter().enumerate() { - res.push(e.build(ctx, &path.push_back(format!("Operand {}", i)), scope)); - } - let t = match op { - BinOp::Plus | BinOp::Minus | BinOp::Multiply | BinOp::Divide => { - let base = res.get(0).unwrap(); - let t = - match check_same( - &mut ctx.errs, - &path.push_back(format!("Operands 0, 1")), - &base.0, - &res.get(0).unwrap().0, - ) { - Some(t) => t, - None => { - return (ExprType(vec![]), Tokens::new()); - }, - }; - for (i, res) in res.iter().enumerate().skip(2) { - match check_same( - &mut ctx.errs, - &path.push_back(format!("Operands 0, {}", i)), - &base.0, - &res.0, - ) { - Some(_) => { }, - None => { - return (ExprType(vec![]), Tokens::new()); - }, - }; - } - t - }, - BinOp::And | BinOp::Or => { - for (i, res) in res.iter().enumerate() { - check_bool(ctx, &path.push_back(format!("Operand {}", i)), &res.0); - } - Type { - type_: SimpleType { - type_: SimpleSimpleType::Bool, - custom: None, - }, - array: false, - opt: false, - } - }, - BinOp::Equals | - BinOp::NotEquals | - BinOp::Is | - BinOp::IsNot | - BinOp::TzEquals | - BinOp::TzNotEquals | - BinOp::TzIs | - BinOp::TzIsNot | - BinOp::LessThan | - BinOp::LessThanEqualTo | - BinOp::GreaterThan | - BinOp::GreaterThanEqualTo | - BinOp::Like => { - #[cfg(feature = "chrono")] - if match op { - BinOp::TzEquals | BinOp::TzNotEquals | BinOp::TzIs | BinOp::TzIsNot => false, - _ => true, - } { - for (i, el) in res.iter().enumerate() { - check_utc_if_time(ctx, &path.push_back(format!("Operand {}", i)), &el.0); - } - } - let base = res.get(0).unwrap(); - check_general_same( - ctx, - &path.push_back(format!("Operands 0, 1")), - &base.0, - &res.get(1).unwrap().0, - ); - for (i, res) in res.iter().enumerate().skip(2) { - check_general_same(ctx, &path.push_back(format!("Operands 0, {}", i)), &base.0, &res.0); - } - Type { - type_: SimpleType { - type_: SimpleSimpleType::Bool, - custom: None, - }, - opt: false, - array: false, - } - }, - BinOp::In | BinOp::NotIn => { - #[cfg(feature = "chrono")] - if match op { - BinOp::TzEquals | BinOp::TzNotEquals | BinOp::TzIs | BinOp::TzIsNot => false, - _ => true, - } { - for (i, el) in res.iter().enumerate() { - check_utc_if_time(ctx, &path.push_back(format!("Operand {}", i)), &el.0); - } - } - let base = res.get(0).unwrap(); - check_general_same( - ctx, - &path.push_back(format!("Operands 0, 1")), - &base.0, - &res.get(1).unwrap().0, - ); - for (i, res) in res.iter().enumerate().skip(2) { - check_general_same(ctx, &path.push_back(format!("Operands 0, {}", i)), &base.0, &res.0); - } - Type { - type_: SimpleType { - type_: SimpleSimpleType::Bool, - custom: None, - }, - opt: false, - array: false, - } - }, - }; - let token = match op { - BinOp::Plus => "+", - BinOp::Minus => "-", - BinOp::Multiply => "*", - BinOp::Divide => "/", - BinOp::And => "and", - BinOp::Or => "or", - BinOp::Equals => "=", - BinOp::NotEquals => "!=", - BinOp::Is => "is", - BinOp::IsNot => "is not", - BinOp::TzEquals => "=", - BinOp::TzNotEquals => "!=", - BinOp::TzIs => "is", - BinOp::TzIsNot => "is not", - BinOp::LessThan => "<", - BinOp::LessThanEqualTo => "<=", - BinOp::GreaterThan => ">", - BinOp::GreaterThanEqualTo => ">=", - BinOp::Like => "like", - BinOp::In => "in", - BinOp::NotIn => "not in", - }; - let mut out = Tokens::new(); - out.s("("); - for (i, res) in res.iter().enumerate() { - if i > 0 { - out.s(token); - } - out.s(&res.1.to_string()); - } - out.s(")"); - (ExprType(vec![(Binding::empty(), t)]), out) - } - match self { - Expr::LitArray(t) => { + Expr::LitArray(res) => { let mut out = Tokens::new(); - let mut child_types = vec![]; out.s("("); - for (i, child) in t.iter().enumerate() { + let mut types = vec![]; + for (i, res) in res.iter().enumerate() { if i > 0 { - out.s(", "); + out.s(","); } - let (child_type, child_tokens) = child.build(ctx, path, scope); - out.s(&child_tokens.to_string()); - child_types.extend(child_type.0); + let (t, tokens) = res.build(ctx, path, scope); + out.s(&tokens.to_string()); + types.push(t); } out.s(")"); - return (ExprType(child_types), out); + return ( + ExprType( + types + .into_iter() + .flat_with_index(|i, t| t.0.into_iter().map(move |(mut k, v)| { + if k.id.is_empty() { + k.id = format!("_{}", i); + } + (k, v) + })) + .collect(), + ), + out + ); }, Expr::LitNull(t) => { let mut out = Tokens::new(); out.s("null"); - return (ExprType(vec![(Binding::empty(), Type { - type_: t.clone(), - opt: true, - array: false, - })]), out); + return ( + ExprType(vec![(Binding::empty(), Type { + type_: t.clone(), + opt: true, + })]), + out + ); }, Expr::LitBool(x) => { let mut out = Tokens::new(); - out.s(if *x { - "true" - } else { - "false" - }); + out.s(if *x { "1" } else { "0" }); return empty_type!(out, SimpleSimpleType::Bool); }, + Expr::LitAuto(x) => { + let mut out = Tokens::new(); + out.s(&x.to_string()); + return empty_type!(out, SimpleSimpleType::Auto); + }, Expr::LitI32(x) => { let mut out = Tokens::new(); out.s(&x.to_string()); @@ -639,11 +368,6 @@ impl Expr { out.s(&x.to_string()); return empty_type!(out, SimpleSimpleType::I64); }, - Expr::LitU32(x) => { - let mut out = Tokens::new(); - out.s(&x.to_string()); - return empty_type!(out, SimpleSimpleType::U32); - }, Expr::LitF32(x) => { let mut out = Tokens::new(); out.s(&x.to_string()); @@ -666,49 +390,39 @@ impl Expr { return empty_type!(out, SimpleSimpleType::Bytes); }, #[cfg(feature = "chrono")] - Expr::LitUtcTimeSChrono(d) => { - let mut out = Tokens::new(); - let d = d.timestamp(); - out.s(&format!("{}", d)); - return empty_type!(out, SimpleSimpleType::UtcTimeSChrono); - }, - #[cfg(feature = "chrono")] - Expr::LitUtcTimeMsChrono(d) => { + Expr::LitUtcTimeChrono(d) => { let mut out = Tokens::new(); let d = d.to_rfc3339(); out.s(&format!("'{}'", d)); - return empty_type!(out, SimpleSimpleType::UtcTimeMsChrono); + return empty_type!(out, SimpleSimpleType::UtcTimeChrono); }, #[cfg(feature = "chrono")] - Expr::LitFixedOffsetTimeMsChrono(d) => { + Expr::LitFixedOffsetTimeChrono(d) => { let mut out = Tokens::new(); let d = d.to_rfc3339(); out.s(&format!("'{}'", d)); - return empty_type!(out, SimpleSimpleType::FixedOffsetTimeMsChrono); - }, - #[cfg(feature = "jiff")] - Expr::LitUtcTimeSJiff(d) => { - let mut out = Tokens::new(); - out.s(&format!("{}", d.as_second())); - return empty_type!(out, SimpleSimpleType::UtcTimeSJiff); + return empty_type!(out, SimpleSimpleType::FixedOffsetTimeChrono); }, #[cfg(feature = "jiff")] - Expr::LitUtcTimeMsJiff(d) => { + Expr::LitUtcTimeJiff(d) => { let mut out = Tokens::new(); - out.s(&format!("'{}'", d.to_string())); - return empty_type!(out, SimpleSimpleType::UtcTimeMsJiff); + let d = d.to_string(); + out.s(&format!("'{}'", d)); + return empty_type!(out, SimpleSimpleType::UtcTimeChrono); }, Expr::Param { name: x, type_: t } => { let path = path.push_back(format!("Param ({})", x)); let mut out = Tokens::new(); - let mut errs = vec![]; let i = match ctx.rust_arg_lookup.entry(x.clone()) { std::collections::hash_map::Entry::Occupied(e) => { let (i, prev_t) = e.get(); if t != prev_t { - errs.push( - format!("Parameter {} specified with multiple types: {:?}, {:?}", x, t, prev_t), - ); + ctx + .errs + .err( + &path, + format!("Parameter {} specified with multiple types: {:?}, {:?}", x, t, prev_t), + ); } *i }, @@ -733,38 +447,6 @@ impl Expr { } else { (rust_type, quote!(#ident)) }; - rust_forward = match t.type_.type_ { - SimpleSimpleType::U32 => rust_forward, - SimpleSimpleType::I32 => rust_forward, - SimpleSimpleType::I64 => rust_forward, - SimpleSimpleType::F32 => rust_forward, - SimpleSimpleType::F64 => rust_forward, - SimpleSimpleType::Bool => rust_forward, - SimpleSimpleType::String => rust_forward, - SimpleSimpleType::Bytes => rust_forward, - #[cfg(feature = "chrono")] - SimpleSimpleType::UtcTimeSChrono => quote!(#rust_forward.timestamp()), - #[cfg(feature = "chrono")] - SimpleSimpleType::UtcTimeMsChrono => quote!(#rust_forward.to_rfc3339()), - #[cfg(feature = "chrono")] - SimpleSimpleType::FixedOffsetTimeMsChrono => quote!(#rust_forward.to_rfc3339()), - #[cfg(feature = "jiff")] - SimpleSimpleType::UtcTimeSJiff => quote!(#rust_forward.as_second()), - #[cfg(feature = "jiff")] - SimpleSimpleType::UtcTimeMsJiff => quote!(#rust_forward.to_string()), - }; - if t.array { - rust_type = quote!(Vec < #rust_type >); - rust_forward = - quote!( - std:: rc:: Rc:: new( - #ident.into_iter( - ).map( - | #ident | rusqlite:: types:: Value:: from(#rust_forward) - ).collect::< Vec < _ >>() - ) - ); - } if t.opt { rust_type = quote!(Option < #rust_type >); rust_forward = quote!(#ident.map(| #ident | #rust_forward)); @@ -774,17 +456,11 @@ impl Expr { i }, }; - for e in errs { - ctx.errs.err(&path, e); - } - if t.array { - out.s(&format!("rarray(${})", i + 1)); - } else { - out.s(&format!("${}", i + 1)); - } + out.s(&format!("?{}", i + 1)); return (ExprType(vec![(Binding::local(x.clone()), t.clone())]), out); }, - Expr::Binding(name) => { + Expr::Field(x) => { + let name = Binding::field(x); let t = match scope.get(&name) { Some(t) => t.clone(), None => { @@ -793,8 +469,8 @@ impl Expr { .err( path, format!( - "Expression references {} but this field isn't available here (available fields: {:?})", - name, + "Expression references {:?} but this field isn't available here (available fields: {:?})", + x, scope.iter().map(|e| e.0.to_string()).collect::>() ), ); @@ -802,11 +478,10 @@ impl Expr { }, }; let mut out = Tokens::new(); - if name.table_id != "" { - out.id(&name.table_id).s("."); - } - out.id(&name.id); - return (ExprType(vec![(name.clone(), t.clone())]), out); + let table_info = ctx.tables.get(&TableRef(x.table_id.clone())).unwrap(); + let field_info = table_info.fields.get(x).unwrap(); + out.id(&table_info.sql_name).s(".").id(&field_info.sql_name); + return (ExprType(vec![(name, t.clone())]), out); }, Expr::BinOp { left, op, right } => { return do_bin_op( @@ -818,156 +493,323 @@ impl Expr { ); }, Expr::BinOpChain { op, exprs } => { - return do_bin_op(ctx, &path.push_back(format!("Chain bin op {:?}", op)), scope, op, exprs); + return do_bin_op( + ctx, + &path.push_back(format!("Bin op chain {:?}", op)), + scope, + op, + exprs, + ); }, Expr::PrefixOp { op, right } => { - let path = path.push_back(format!("Prefix op {:?}", op)); let mut out = Tokens::new(); - let res = right.build(ctx, &path, scope); - let (op_text, op_type) = match op { + match op { PrefixOp::Not => { - check_bool(ctx, &path, &res.0); - ("not", SimpleSimpleType::Bool) + out.s("not"); }, - }; - out.s(op_text).s(&res.1.to_string()); - return empty_type!(out, op_type); + } + let (t, tokens) = right.build(ctx, &path.push_back(format!("Prefix op {:?}", op)), scope); + check_bool(ctx, path, &t); + out.s(&tokens.to_string()); + return (t, out); }, Expr::Call { func, args, compute_type } => { - let mut types = vec![]; let mut out = Tokens::new(); - out.s(func); - out.s("("); + out.s(func).s("("); + let mut arg_types = vec![]; for (i, arg) in args.iter().enumerate() { if i > 0 { out.s(","); } - let (arg_type, tokens) = - arg.build(ctx, &path.push_back(format!("Call [{}] arg {}", func, i)), scope); - types.push(arg_type); + let (t, tokens) = arg.build(ctx, &path.push_back(format!("Call arg {}", i)), scope); out.s(&tokens.to_string()); + arg_types.push(t); } out.s(")"); - let type_ = match (compute_type.0)(ctx, &path, types) { - Some(t) => t, - None => { - return (ExprType(vec![]), Tokens::new()); - }, - }; - return (ExprType(vec![(Binding::empty(), type_)]), out); + return (compute_type.0(ctx, path, &arg_types), out); }, - Expr::Window { expr, partition_by, order_by } => { + Expr::Select(s) => { let mut out = Tokens::new(); - let expr = expr.build(ctx, &path, &scope); - out.s(&expr.1.to_string()); - out.s("over"); out.s("("); - if !partition_by.is_empty() { - out.s("partition by"); - for (i, e) in partition_by.iter().enumerate() { - let path = path.push_back(format!("Partition by {}", i)); - if i > 0 { - out.s(","); - } - let (_, p) = e.build(ctx, &path, &scope); - out.s(&p.to_string()); - } - } - if !order_by.is_empty() { - out.s("order by"); - for (i, o) in order_by.iter().enumerate() { - let path = path.push_back(format!("Order by clause {}", i)); - if i > 0 { - out.s(","); - } - let (_, o_tokens) = o.0.build(ctx, &path, &scope); - out.s(&o_tokens.to_string()); - out.s(match o.1 { - Order::Asc => "asc", - Order::Desc => "desc", - }); - } - } - out.s(")"); - return (expr.0, out); + let (t, tokens) = s.build(ctx, &path.push_back("Subselect".into()), crate::sqlite::QueryResCount::Many); + out.s(&tokens.to_string()).s(")"); + return (t, out); }, - Expr::Select { body, body_junctions } => { - let path = path.push_back(format!("Subselect")); + Expr::Cast(e, t) => { let mut out = Tokens::new(); - let base = body.build(ctx, scope, &path, QueryResCount::Many); - out.s(&base.1.to_string()); - out.s(&build_select_junction(ctx, &path, &base.0, &body_junctions).to_string()); - return (base.0, out); + let (got_t, tokens) = e.build(ctx, &path.push_back("Cast".into()), scope); + check_general_same(ctx, path, &got_t, &ExprType(vec![(Binding::empty(), t.clone())])); + out.s("cast(").s(&tokens.to_string()).s("as").s(to_rust_types(&t.type_.type_).ret_type.to_string().as_str()).s(")"); + return (ExprType(vec![(Binding::empty(), t.clone())]), out); }, - Expr::Exists { not, body, body_junctions } => { - let path = path.push_back(format!("(Not)Exists")); - let mut out = Tokens::new(); - if *not { - out.s("not"); - } - out.s("exists"); - out.s("("); - let base = body.build(ctx, scope, &path, QueryResCount::Many); - out.s(&base.1.to_string()); - out.s(&build_select_junction(ctx, &path, &base.0, &body_junctions).to_string()); - out.s(")"); - return (ExprType(vec![(Binding::empty(), Type { - type_: SimpleType { - type_: SimpleSimpleType::Bool, - custom: None, - }, - opt: false, - array: false, - })]), out); + } + } +} + +pub(crate) fn check_bool( + ctx: &mut SqliteQueryCtx, + path: &rpds::Vector, + t: &ExprType, +) { + check_general_same( + ctx, + path, + t, + &ExprType(vec![(Binding::empty(), Type { + type_: SimpleType { + type_: SimpleSimpleType::Bool, + custom: None, }, - Expr::Cast(e, t) => { - let path = path.push_back(format!("Cast")); - let out = e.build(ctx, &path, scope); - let got_t = match out.0.assert_scalar(&mut ctx.errs, &path) { - Some(t) => t, - None => { - return (ExprType(vec![]), Tokens::new()); - }, - }; - check_general_same_type(ctx, &path, t, &got_t.1); - return (ExprType(vec![(got_t.0, t.clone())]), out.1); + opt: false, + })]), + ); +} + +pub(crate) fn check_assignable( + errs: &mut Errs, + path: &rpds::Vector, + left: &Type, + right: &ExprType, +) { + let Some(right) = right.assert_scalar(errs, path) else { + return; + }; + check_general_same_type_assignable(errs, path, left, &right); +} + +pub(crate) fn check_general_same( + ctx: &mut SqliteQueryCtx, + path: &rpds::Vector, + left: &ExprType, + right: &ExprType, +) { + if left.0.len() != right.0.len() { + ctx + .errs + .err( + path, + format!( + "Operator arms record type lengths don't match: left has {} fields and right has {}", + left.0.len(), + right.0.len() + ), + ); + } else if left.0.len() == 1 && right.0.len() == 1 { + check_general_same_type(ctx, path, &left.0[0].1, &right.0[0].1); + } else { + for (i, (left, right)) in left.0.iter().zip(right.0.iter()).enumerate() { + check_general_same_type(ctx, &path.push_back(format!("Record pair {}", i)), &left.1, &right.1); + } + } +} + +pub(crate) fn check_same( + errs: &mut Errs, + path: &rpds::Vector, + left: &ExprType, + right: &ExprType, +) -> Option { + let left = left.assert_scalar(errs, &path.push_back("Left".into())) ?; + let right = right.assert_scalar(errs, &path.push_back("Right".into())) ?; + check_general_same_type( + &mut SqliteQueryCtx::new(errs.clone(), &HashMap::new()), + path, + &left, + &right, + ); + return Some(left); +} + +pub(crate) fn check_general_same_type( + ctx: &mut SqliteQueryCtx, + path: &rpds::Vector, + left: &Type, + right: &Type, +) { + if left.type_.type_ != right.type_.type_ { + ctx + .errs + .err( + path, + format!( + "Operator arms types don't match: left has type {:?} and right has {:?}", + left.type_.type_, + right.type_.type_ + ), + ); + } +} + +pub(crate) fn check_general_same_type_assignable( + errs: &mut Errs, + path: &rpds::Vector, + left: &Type, + right: &Type, +) { + if left.type_.type_ != right.type_.type_ { + errs.err( + path, + format!( + "Expression has type {:?} which is not assignable to {:?}", + right.type_.type_, + left.type_.type_ + ), + ); + } + if !left.opt && right.opt { + errs.err( + path, + format!("Expression is optional but destination is not"), + ); + } +} + +fn do_bin_op( + ctx: &mut SqliteQueryCtx, + path: &rpds::Vector, + scope: &HashMap, + op: &BinOp, + exprs: &Vec, +) -> (ExprType, Tokens) { + let mut out = Tokens::new(); + let mut out_t = None; + let token = match op { + BinOp::Plus => "+", + BinOp::Minus => "-", + BinOp::Multiply => "*", + BinOp::Divide => "/", + BinOp::And => "and", + BinOp::Or => "or", + BinOp::Equals => "=", + BinOp::NotEquals => "<>", + BinOp::Is => "is", + BinOp::IsNot => "is not", + BinOp::LessThan => "<", + BinOp::LessThanEqualTo => "<=", + BinOp::GreaterThan => ">", + BinOp::GreaterThanEqualTo => ">=", + BinOp::Like => "like", + BinOp::In => "in", + BinOp::NotIn => "not in", + }; + for (i, res) in exprs.iter().enumerate() { + if i > 0 { + out.s(token); + } + let (t, tokens) = res.build(ctx, &path.push_back(format!("Operand {}", i)), scope); + let got_t = match t.assert_scalar(&mut ctx.errs, &path.push_back(format!("Operand {}", i))) { + Some(t) => t, + None => { + continue; }, }; + match op { + BinOp::Plus | BinOp::Minus | BinOp::Multiply | BinOp::Divide => { + if !matches!( + got_t.type_.type_, + SimpleSimpleType::I32 | + SimpleSimpleType::I64 | + SimpleSimpleType::F32 | + SimpleSimpleType::F64 | + SimpleSimpleType::Auto + ) { + ctx + .errs + .err( + &path.push_back(format!("Operand {}", i)), + format!("Arithmetic operator {:?} not supported for type {:?}", op, got_t.type_.type_), + ); + } + }, + BinOp::And | BinOp::Or => { + if !matches!(got_t.type_.type_, SimpleSimpleType::Bool) { + ctx + .errs + .err( + &path.push_back(format!("Operand {}", i)), + format!("Logical operator {:?} not supported for type {:?}", op, got_t.type_.type_), + ); + } + }, + BinOp::Equals | + BinOp::NotEquals | + BinOp::Is | + BinOp::IsNot | + BinOp::LessThan | + BinOp::LessThanEqualTo | + BinOp::GreaterThan | + BinOp::GreaterThanEqualTo | + BinOp::Like | + BinOp::In | + BinOp::NotIn => { }, + } + if let Some(out_t) = &mut out_t { + check_general_same_type(ctx, path, out_t, &got_t); + } else { + out_t = Some(got_t); + } + out.s(&tokens.to_string()); } + let res_t = match op { + BinOp::Equals | + BinOp::NotEquals | + BinOp::Is | + BinOp::IsNot | + BinOp::LessThan | + BinOp::LessThanEqualTo | + BinOp::GreaterThan | + BinOp::GreaterThanEqualTo | + BinOp::Like | + BinOp::In | + BinOp::NotIn => Type { + type_: SimpleType { + type_: SimpleSimpleType::Bool, + custom: None, + }, + opt: false, + }, + _ => out_t.unwrap_or(Type { + type_: SimpleType { + type_: SimpleSimpleType::I32, + custom: None, + }, + opt: false, + }), + }; + return (ExprType(vec![(Binding::empty(), res_t)]), out); } -/// Datetimes with fixed offsets must be converted to utc before comparison. -/// -/// The Tz operators are for working with datetimes with fied offsets where you -/// _want_ to not consider datetimes referring to the same instant but with -/// different timezones equal (that is, to be equal both the time and timezone must -/// match). I think this is probably a rare use case. -#[derive(Clone, Debug)] -pub enum BinOp { - Plus, - Minus, - Multiply, - Divide, - And, - Or, - Equals, - NotEquals, - Is, - IsNot, - TzEquals, - TzNotEquals, - TzIs, - TzIsNot, - LessThan, - LessThanEqualTo, - GreaterThan, - GreaterThanEqualTo, - Like, - In, - NotIn, +trait FlatWithIndex: Iterator { + fn flat_with_index R>(self, f: F) -> std::iter::Flatten> + where Self: Sized; } -#[derive(Clone, Debug)] -pub enum PrefixOp { - Not, +impl, T> FlatWithIndex for I { + fn flat_with_index R>(self, f: F) -> std::iter::Flatten> + where Self: Sized { + WithIndex { + iter: self, + f: f, + i: 0, + _phantom: std::marker::PhantomData, + }.flatten() + } +} + +struct WithIndex { + iter: I, + f: F, + i: usize, + _phantom: std::marker::PhantomData<(T, R)>, +} + +impl, F: FnMut(usize, T) -> R, T, R: Iterator> Iterator for WithIndex { + type Item = R; + + fn next(&mut self) -> Option { + let val = self.iter.next()?; + let res = (self.f)(self.i, val); + self.i += 1; + return Some(res); + } } diff --git a/src/sqlite/query/helpers.rs b/src/sqlite/query/helpers.rs index d0f3da3..e814c14 100644 --- a/src/sqlite/query/helpers.rs +++ b/src/sqlite/query/helpers.rs @@ -5,8 +5,9 @@ use { Expr, Binding, }, + std::rc::Rc, crate::sqlite::{ - schema::field::Field, + FieldHandle, types::{ SimpleSimpleType, SimpleType, @@ -18,80 +19,67 @@ use { /// Generates a field element for instert and update statements, to set a field /// from a parameter of the same type. -pub fn set_field(param_name: impl Into, f: &Field) -> (Field, Expr) { +pub fn set_field(param_name: impl Into, f: &FieldHandle) -> (FieldHandle, Expr) { (f.clone(), field_param(param_name, f)) } /// Generates a param matching a field in name in type -pub fn field_param(param_name: impl Into, f: &Field) -> Expr { +pub fn field_param(param_name: impl Into, f: &FieldHandle) -> Expr { + let version = f.table.version.0.borrow(); + let type_ = version.tables.get(&f.table.schema_id).unwrap().fields.get(&f.schema_id).unwrap().type_.type_.clone(); Expr::Param { name: param_name.into(), - type_: f.type_.type_.clone(), + type_: type_, } } /// Generates an expression checking for equality of a field and a parameter and /// the same type. -pub fn expr_field_eq(param_name: impl Into, f: &Field) -> Expr { +pub fn expr_field_eq(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(f))), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::Equals, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } /// Generates an expression selecting field values greater than a corresponding /// parameter -pub fn expr_field_gt(param_name: impl Into, f: &Field) -> Expr { +pub fn expr_field_gt(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(f))), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::GreaterThan, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } /// Generates an expression selecting field values greater than or equal to a /// corresponding parameter -pub fn expr_field_gte(param_name: impl Into, f: &Field) -> Expr { +pub fn expr_field_gte(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(f))), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::GreaterThanEqualTo, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } /// Generates an expression selecting field values greater than a corresponding /// parameter -pub fn expr_field_lt(param_name: impl Into, f: &Field) -> Expr { +pub fn expr_field_lt(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(f))), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::LessThan, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } /// Generates an expression selecting field values greater than or equal to a /// corresponding parameter -pub fn expr_field_lte(param_name: impl Into, f: &Field) -> Expr { +pub fn expr_field_lte(param_name: impl Into, f: &FieldHandle) -> Expr { Expr::BinOp { - left: Box::new(Expr::Binding(Binding::field(f))), + left: Box::new(Expr::Field(f.to_ref())), op: BinOp::LessThanEqualTo, - right: Box::new(Expr::Param { - name: param_name.into(), - type_: f.type_.type_.clone(), - }), + right: Box::new(field_param(param_name, f)), } } @@ -116,13 +104,13 @@ pub fn as_utc_chrono(expr: Expr) -> Expr { return Expr::Call { func: "strftime".to_string(), args: vec![Expr::LitString("%Y-%m-%dT%H:%M:%f".to_string()), expr], - compute_type: ComputeType::new(|ctx, path, args| { + compute_type: ComputeType(Rc::new(|ctx, path, args| { shed!{ let arg = args.get(1).unwrap(); let Some(type_) = arg.0.iter().next() else { break; }; - if !matches!(type_.1.type_.type_, SimpleSimpleType::FixedOffsetTimeMsChrono) { + if !matches!(type_.1.type_.type_, SimpleSimpleType::FixedOffsetTimeChrono) { ctx .errs .err( @@ -134,15 +122,14 @@ pub fn as_utc_chrono(expr: Expr) -> Expr { ); } }; - return Some(Type { + return super::expr::ExprType(vec![(Binding::empty(), Type { type_: SimpleType { - type_: SimpleSimpleType::UtcTimeMsChrono, + type_: SimpleSimpleType::UtcTimeChrono, custom: None, }, opt: false, - array: false, - }); - }), + })]); + })), } } @@ -150,12 +137,12 @@ pub fn fn_min(expr: Expr) -> Expr { return Expr::Call { func: "min".to_string(), args: vec![expr], - compute_type: ComputeType::new(|ctx, path, args| { + compute_type: ComputeType(Rc::new(|ctx, path, args| { let Some(t) = args.get(0).unwrap().assert_scalar(&mut ctx.errs, path) else { - return None; + return super::expr::ExprType(vec![]); }; - return Some(t.1); - }), + return super::expr::ExprType(vec![(Binding::empty(), t)]); + })), } } @@ -163,12 +150,12 @@ pub fn fn_max(expr: Expr) -> Expr { return Expr::Call { func: "max".to_string(), args: vec![expr], - compute_type: ComputeType::new(|ctx, path, args| { + compute_type: ComputeType(Rc::new(|ctx, path, args| { let Some(t) = args.get(0).unwrap().assert_scalar(&mut ctx.errs, path) else { - return None; + return super::expr::ExprType(vec![]); }; - return Some(t.1); - }), + return super::expr::ExprType(vec![(Binding::empty(), t)]); + })), } } @@ -176,12 +163,12 @@ pub fn fn_avg(expr: Expr) -> Expr { return Expr::Call { func: "avg".to_string(), args: vec![expr], - compute_type: ComputeType::new(|ctx, path, args| { + compute_type: ComputeType(Rc::new(|ctx, path, args| { let Some(t) = args.get(0).unwrap().assert_scalar(&mut ctx.errs, path) else { - return None; + return super::expr::ExprType(vec![]); }; - return Some(t.1); - }), + return super::expr::ExprType(vec![(Binding::empty(), t)]); + })), } } @@ -189,15 +176,14 @@ pub fn fn_count(expr: Expr) -> Expr { return Expr::Call { func: "count".to_string(), args: vec![expr], - compute_type: ComputeType::new(|_ctx, _path, _args| { - return Some(Type { + compute_type: ComputeType(Rc::new(|_ctx, _path, _args| { + return super::expr::ExprType(vec![(Binding::empty(), Type { type_: SimpleType { type_: SimpleSimpleType::I64, custom: None, }, opt: false, - array: false, - }); - }), + })]); + })), } } diff --git a/src/sqlite/query/insert.rs b/src/sqlite/query/insert.rs index 2133cff..b18b975 100644 --- a/src/sqlite/query/insert.rs +++ b/src/sqlite/query/insert.rs @@ -1,3 +1,5 @@ +use crate::sqlite::query::utils::Returning; + use std::{ collections::{ HashMap, @@ -6,137 +8,130 @@ use std::{ }; use crate::{ sqlite::{ + QueryResCount, schema::{ - field::Field, - table::Table, + field::FieldRef, + table::TableRef, }, - QueryResCount, + types::SimpleSimpleType, }, utils::Tokens, }; use super::{ expr::{ - check_assignable, Expr, ExprType, + check_assignable, Binding, }, - select_body::Returning, utils::{ + SqliteQueryCtx, + QueryBody, build_returning, build_set, - build_with, - QueryBody, - With, }, + }; pub enum InsertConflict { DoNothing, - DoUpdate(Vec<(Field, Expr)>), + DoUpdate { + conflict: Vec, + set: Vec<(FieldRef, Expr)>, + }, } pub struct Insert { - pub with: Option, - pub table: Table, - pub values: Vec<(Field, Expr)>, - pub on_conflict: Option, - pub returning: Vec, + pub(crate) table: TableRef, + pub(crate) values: Vec<(FieldRef, Expr)>, + pub(crate) on_conflict: Option, + pub(crate) returning: Vec, } impl QueryBody for Insert { fn build( &self, - ctx: &mut super::utils::SqliteQueryCtx, + ctx: &mut SqliteQueryCtx, path: &rpds::Vector, res_count: QueryResCount, ) -> (ExprType, Tokens) { - let mut out = Tokens::new(); - // Prep - if let Some(w) = &self.with { - out.s(&build_with(ctx, path, w).to_string()); - } let mut check_inserting_fields = HashSet::new(); for p in &self.values { - if p.0.type_.type_.opt { + let field_info = match ctx.tables.get(&self.table).and_then(|t| t.fields.get(&p.0)) { + Some(f) => f, + None => { + ctx.errs.err(path, format!("Unknown field {:?} for insert into {:?}", p.0, self.table)); + continue; + }, + }; + if field_info.type_.opt { continue; } if !check_inserting_fields.insert(p.0.clone()) { - ctx.errs.err(path, format!("Duplicate field {} in insert", p.0)); + ctx.errs.err(path, format!("Duplicate field {:?} in insert", p.0)); } } let mut scope = HashMap::new(); - for field in match ctx.tables.get(&self.table) { + let table_info = match ctx.tables.get(&self.table) { Some(t) => t, None => { - ctx.errs.err(path, format!("Unknown table {} for insert", self.table)); + ctx.errs.err(path, format!("Unknown table {:?} for insert", self.table)); return (ExprType(vec![]), Tokens::new()); }, - } { - scope.insert(Binding::field(field), field.type_.type_.clone()); - if !field.type_.type_.opt && field.schema_id.0 != "rowid" && !check_inserting_fields.remove(field) { - ctx.errs.err(path, format!("{} is a non-optional field but is missing in insert", field)); + }; + for (field_ref, info) in &table_info.fields { + scope.insert(Binding::field(field_ref), info.type_.clone()); + if !info.type_.opt && info.type_.type_.type_ != SimpleSimpleType::Auto && + !check_inserting_fields.remove(field_ref) { + ctx.errs.err(path, format!("Field {:?} is a non-optional field but is missing in insert", field_ref)); } } drop(check_inserting_fields); // Build query - out.s("insert into").id(&self.table.id).s("("); - for (i, (field, _)) in self.values.iter().enumerate() { + let mut out = Tokens::new(); + out.s("insert into").id(&table_info.sql_name).s("("); + for (i, (field_ref, _)) in self.values.iter().enumerate() { if i > 0 { out.s(","); } - out.id(&field.id); + let field_info = table_info.fields.get(field_ref).unwrap(); + out.id(&field_info.sql_name); } out.s(") values ("); - for (i, (field, val)) in self.values.iter().enumerate() { + for (i, (field_ref, val)) in self.values.iter().enumerate() { if i > 0 { out.s(","); } - let field = match ctx.tables.get(&field.table).and_then(|t| t.get(&field)) { - Some(t) => t, - None => { - ctx.errs.err(path, format!("Insert destination value field {} is not known", field)); - continue; - }, - }.clone(); - let path = path.push_back(format!("Insert value {} ({})", i, field)); + let field_info = table_info.fields.get(field_ref).unwrap(); + let path = path.push_back(format!("Insert value {} ({:?})", i, field_ref)); let res = val.build(ctx, &path, &scope); - check_assignable(&mut ctx.errs, &path, &field.type_.type_, &res.0); + check_assignable(&mut ctx.errs, &path, &field_info.type_, &res.0); out.s(&res.1.to_string()); } out.s(")"); - if let Some(c) = &self.on_conflict { - out.s("on conflict do"); - match c { + if let Some(conflict) = &self.on_conflict { + out.s("on conflict"); + match conflict { InsertConflict::DoNothing => { - out.s("nothing"); + out.s("do nothing"); }, - InsertConflict::DoUpdate(values) => { - out.s("update"); - build_set(ctx, path, &scope, &mut out, values); + InsertConflict::DoUpdate { conflict, set } => { + out.s("("); + for (i, f) in conflict.iter().enumerate() { + if i > 0 { + out.s(","); + } + let field_info = table_info.fields.get(f).unwrap(); + out.id(&field_info.sql_name); + } + out.s(")"); + out.s("do update"); + build_set(ctx, path, &scope, &mut out, set); }, } } - match (&res_count, &self.on_conflict) { - (QueryResCount::MaybeOne, Some(InsertConflict::DoUpdate(_))) => { - ctx.errs.err(path, format!("Insert with [on conflict update] will always return a row")); - }, - (QueryResCount::One, Some(InsertConflict::DoNothing)) => { - ctx.errs.err(path, format!("Insert with [on conflict do nothing] may not return a row")); - }, - (QueryResCount::Many, _) => { - ctx.errs.err(path, format!("Insert can at most return one row, but res count is many")); - }, - (QueryResCount::None, _) | (QueryResCount::One, None) | (QueryResCount::MaybeOne, None) => { - // handled elsewhere, nop - }, - (QueryResCount::One, Some(InsertConflict::DoUpdate(_))) | - (QueryResCount::MaybeOne, Some(InsertConflict::DoNothing)) => { - // ok - }, - } let out_type = build_returning(ctx, path, &scope, &mut out, &self.returning, res_count); (out_type, out) } diff --git a/src/sqlite/query/select.rs b/src/sqlite/query/select.rs index b3c4d2d..fa3ad4c 100644 --- a/src/sqlite/query/select.rs +++ b/src/sqlite/query/select.rs @@ -1,49 +1,202 @@ -use { - super::{ - expr::{ - ExprType, +use std::collections::HashMap; +use crate::{ + utils::Tokens, + sqlite::{ + types::{ + Type, + type_i64, }, - select_body::{ - build_select_junction, - SelectBody, - SelectJunction, - }, - utils::{ - build_with, - QueryBody, - With, + QueryResCount, + schema::{ + table::TableRef, }, }, - crate::{ - sqlite::{ - QueryResCount, - }, - utils::Tokens, +}; +use super::{ + utils::{ + SqliteQueryCtx, + QueryBody, + build_returning, + Returning, + }, + expr::{ + Expr, + ExprType, + check_bool, + check_general_same, + Binding, + }, + select_body::{ + SelectJunction, + build_select_junction, }, - std::collections::HashMap, }; +#[derive(Clone, Debug)] +pub enum Order { + Asc, + Desc, +} + +#[derive(Clone, Debug)] +pub enum JoinType { + Inner, + Left, +} + +#[derive(Clone, Debug)] +pub struct Join { + pub type_: JoinType, + pub source: NamedSelectSource, + pub on: Expr, +} + +#[derive(Clone, Debug)] +pub enum JoinSource { + Subsel(Box