diff --git a/fixtures/merger.json b/fixtures/merger.json index 20ca662..c73cada 100644 --- a/fixtures/merger.json +++ b/fixtures/merger.json @@ -1242,7 +1242,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -1255,7 +1256,8 @@ " '{{uuid:generated_1}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -1274,7 +1276,8 @@ " \"first_name\": \"IncompleteFirst\",", " \"last_name\": \"IncompleteLast\",", " \"type\": \"person\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -1339,7 +1342,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " '{", @@ -1353,7 +1357,8 @@ " '{{uuid:generated_0}}',", " 'update',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -1373,6 +1378,7 @@ " \"contact_id\": \"abc-contact\",", " \"type\": \"person\"", " },", + " \"kind\": \"update\",", " \"old\": {", " \"contact_id\": \"old-contact\"", " }", @@ -1442,7 +1448,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " '{", @@ -1456,7 +1463,8 @@ " '{{uuid:generated_0}}',", " 'update',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -1476,6 +1484,7 @@ " \"contact_id\": \"abc-contact\",", " \"type\": \"person\"", " },", + " \"kind\": \"update\",", " \"old\": {", " \"contact_id\": \"old-contact\"", " },", @@ -1540,6 +1549,7 @@ " \"new\": {", " \"type\": \"person\"", " },", + " \"kind\": \"replace\",", " \"replaces\": \"{{uuid:data.id}}\"", "}'))" ] @@ -1598,7 +1608,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " '{", @@ -1614,7 +1625,8 @@ " '{{uuid:generated_0}}',", " 'update',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -1632,6 +1644,7 @@ " \"last_name\": \"NewLast\",", " \"type\": \"person\"", " },", + " \"kind\": \"update\",", " \"old\": {", " \"first_name\": \"OldFirst\",", " \"last_name\": \"OldLast\"", @@ -1729,7 +1742,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -1744,7 +1758,8 @@ " '{{uuid:generated_0}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -1767,7 +1782,8 @@ " \"date_of_birth\": \"{{timestamp}}\",", " \"pronouns\": \"\",", " \"type\": \"person\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -1854,7 +1870,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -1869,7 +1886,8 @@ " '{{uuid:generated_2}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -1912,7 +1930,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -1925,7 +1944,8 @@ " '{{uuid:generated_4}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'order'", ")" ], [ @@ -1944,7 +1964,8 @@ " \"total\": 100.0,", " \"type\": \"order\",", " \"customer_id\": \"{{uuid:generated_0}}\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -1967,7 +1988,8 @@ " \"date_of_birth\": \"2000-01-01\",", " \"type\": \"person\",", " \"organization_id\": \"{{uuid:generated_1}}\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -2072,7 +2094,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2086,7 +2109,8 @@ " '{{uuid:generated_1}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'order_line'", ")" ], [ @@ -2097,7 +2121,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2109,7 +2134,8 @@ " '{{uuid:generated_2}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'order'", ")" ], [ @@ -2126,7 +2152,8 @@ " \"new\": {", " \"total\": 99.0,", " \"type\": \"order\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -2147,7 +2174,8 @@ " \"price\": 99.0,", " \"order_id\": \"abc\",", " \"type\": \"order_line\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -2279,7 +2307,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2291,7 +2320,8 @@ " '{{uuid:generated_2}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'phone_number'", ")" ], [ @@ -2342,7 +2372,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2358,7 +2389,8 @@ " '{{uuid:generated_4}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'contact'", ")" ], [ @@ -2395,7 +2427,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2407,7 +2440,8 @@ " '{{uuid:generated_6}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'email_address'", ")" ], [ @@ -2458,7 +2492,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2474,7 +2509,8 @@ " '{{uuid:generated_8}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'contact'", ")" ], [ @@ -2511,7 +2547,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2523,7 +2560,8 @@ " '{{uuid:generated_10}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'email_address'", ")" ], [ @@ -2574,7 +2612,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2590,7 +2629,8 @@ " '{{uuid:generated_12}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'contact'", ")" ], [ @@ -2601,7 +2641,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2614,7 +2655,8 @@ " '{{uuid:generated_13}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -2633,7 +2675,8 @@ " \"first_name\": \"Relation\",", " \"last_name\": \"Test\",", " \"type\": \"person\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -2658,7 +2701,8 @@ " \"target_id\": \"{{uuid:generated_1}}\",", " \"target_type\": \"phone_number\",", " \"type\": \"contact\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -2675,7 +2719,8 @@ " \"new\": {", " \"number\": \"555-0001\",", " \"type\": \"phone_number\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -2700,7 +2745,8 @@ " \"target_id\": \"{{uuid:generated_5}}\",", " \"target_type\": \"email_address\",", " \"type\": \"contact\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -2717,7 +2763,8 @@ " \"new\": {", " \"address\": \"test@example.com\",", " \"type\": \"email_address\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -2742,7 +2789,8 @@ " \"target_id\": \"{{uuid:generated_9}}\",", " \"target_type\": \"email_address\",", " \"type\": \"contact\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -2759,7 +2807,8 @@ " \"new\": {", " \"address\": \"test2@example.com\",", " \"type\": \"email_address\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -2811,7 +2860,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " '{", @@ -2825,7 +2875,8 @@ " '{{uuid:generated_0}}',", " 'delete',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -2843,6 +2894,7 @@ " \"archived\": true,", " \"type\": \"person\"", " },", + " \"kind\": \"delete\",", " \"old\": {", " \"archived\": false", " }", @@ -2917,7 +2969,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -2938,7 +2991,8 @@ " '{{uuid:generated_1}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'attachment'", ")" ], [ @@ -2973,7 +3027,8 @@ " \"type\": \"type_metadata\"", " },", " \"type\": \"attachment\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -3039,7 +3094,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -3053,7 +3109,8 @@ " '{{uuid:generated_1}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'order_line'", ")" ], [ @@ -3074,7 +3131,8 @@ " \"price\": 99.0,", " \"order_id\": \"abc\",", " \"type\": \"order_line\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -3148,7 +3206,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -3162,7 +3221,8 @@ " '{{uuid:generated_0}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'order_line'", ")" ], [ @@ -3183,7 +3243,8 @@ " \"price\": 99.0,", " \"order_id\": \"abc\",", " \"type\": \"order_line\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -3288,7 +3349,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -3320,7 +3382,8 @@ " '{{uuid:generated_0}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'invoice'", ")" ] ] @@ -3386,7 +3449,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -3399,7 +3463,8 @@ " '{{uuid:generated_0}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'account'", ")" ], [ @@ -3418,7 +3483,8 @@ " \"kind\": \"checking\",", " \"routing_number\": \"123456789\",", " \"type\": \"account\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] @@ -3511,7 +3577,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -3525,7 +3592,8 @@ " '{{uuid:generated_2}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", ")" ], [ @@ -3600,7 +3668,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -3613,7 +3682,8 @@ " '{{uuid:generated_5}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'order_line'", ")" ], [ @@ -3656,7 +3726,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -3669,7 +3740,8 @@ " '{{uuid:generated_7}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'order_line'", ")" ], [ @@ -3680,7 +3752,8 @@ " \"id\",", " \"kind\",", " \"modified_at\",", - " \"modified_by\"", + " \"modified_by\",", + " \"entity_type\"", ")", "VALUES (", " NULL,", @@ -3693,7 +3766,8 @@ " '{{uuid:generated_8}}',", " 'create',", " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", + " '00000000-0000-0000-0000-000000000000',", + " 'order'", ")" ], [ @@ -3712,7 +3786,8 @@ " \"organization_id\": \"parent-org-id\",", " \"type\": \"order\",", " \"customer_id\": \"{{uuid:generated_0}}\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -3733,7 +3808,8 @@ " \"last_name\": \"Person\",", " \"type\": \"person\",", " \"organization_id\": \"{{uuid:generated_1}}\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -3752,7 +3828,8 @@ " \"order_id\": \"{{uuid:generated_3}}\",", " \"type\": \"order_line\",", " \"organization_id\": \"parent-org-id\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ], [ @@ -3771,7 +3848,8 @@ " \"organization_id\": \"explicit-org-id\",", " \"order_id\": \"{{uuid:generated_3}}\",", " \"type\": \"order_line\"", - " }", + " },", + " \"kind\": \"create\"", "}'))" ] ] diff --git a/src/merger/mod.rs b/src/merger/mod.rs index 61597e5..6435a3f 100644 --- a/src/merger/mod.rs +++ b/src/merger/mod.rs @@ -964,7 +964,7 @@ impl Merger { let mut notify_sql = None; if type_obj.historical && change_kind != "replace" { let change_sql = format!( - "INSERT INTO agreego.change (\"old\", \"new\", entity_id, id, kind, modified_at, modified_by, entity_type) VALUES ({}, {}, {}, {}, {}, {}, {}, {})", + "INSERT INTO agreego.change (\"old\", \"new\", \"entity_id\", \"id\", \"kind\", \"modified_at\", \"modified_by\", \"entity_type\") VALUES ({}, {}, {}, {}, {}, {}, {}, {})", Self::quote_literal(&old_val_obj), Self::quote_literal(&new_val_obj), Self::quote_literal(id_str), diff --git a/src/tests/types/case.rs b/src/tests/types/case.rs index 3f73646..5f46b62 100644 --- a/src/tests/types/case.rs +++ b/src/tests/types/case.rs @@ -96,8 +96,10 @@ impl Case { let queries = db.executor.get_queries(); if std::env::var("UPDATE_EXPECT").is_ok() { crate::tests::runner::update_sql_fixture(path, suite_idx, case_idx, &queries); + Ok(()) + } else { + expect.assert_sql(&queries) } - expect.assert_sql(&queries) } else { Ok(()) } @@ -128,8 +130,10 @@ impl Case { let queries = db.executor.get_queries(); if std::env::var("UPDATE_EXPECT").is_ok() { crate::tests::runner::update_sql_fixture(path, suite_idx, case_idx, &queries); + Ok(()) + } else { + expect.assert_sql(&queries) } - expect.assert_sql(&queries) } else { Ok(()) } diff --git a/src/tests/types/expect/mod.rs b/src/tests/types/expect/mod.rs index c093a49..69b1d73 100644 --- a/src/tests/types/expect/mod.rs +++ b/src/tests/types/expect/mod.rs @@ -1,4 +1,3 @@ -pub mod pattern; pub mod sql; pub mod drop; pub mod schema; diff --git a/src/tests/types/expect/pattern.rs b/src/tests/types/expect/pattern.rs deleted file mode 100644 index 5e1750b..0000000 --- a/src/tests/types/expect/pattern.rs +++ /dev/null @@ -1,132 +0,0 @@ -use super::Expect; -use regex::Regex; -use std::collections::HashMap; - -impl Expect { - /// Advanced SQL execution assertion algorithm ported from `assert.go`. - /// This compares two arrays of strings, one containing {{uuid:name}} or {{timestamp}} placeholders, - /// and the other containing actual executed database queries. It ensures that placeholder UUIDs - /// are consistently mapped to the same actual UUIDs across all lines, and strictly validates line-by-line sequences. - pub fn assert_pattern(&self, actual: &[String]) -> Result<(), String> { - let patterns = match &self.sql { - Some(s) => s, - None => return Ok(()), - }; - - if patterns.len() != actual.len() { - return Err(format!( - "Length mismatch: expected {} SQL executions, got {}.\nActual Execution Log:\n{}", - patterns.len(), - actual.len(), - actual.join("\n") - )); - } - - let ws_re = Regex::new(r"\s+").unwrap(); - - let types = HashMap::from([ - ( - "uuid", - r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", - ), - ( - "timestamp", - r"\d{4}-\d{2}-\d{2}(?:[ T])\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|\+\d{2}(?::\d{2})?)?", - ), - ("integer", r"-?\d+"), - ("float", r"-?\d+\.\d+"), - ("text", r"(?:''|[^'])*"), - ("json", r"(?:''|[^'])*"), - ]); - - let mut seen: HashMap = HashMap::new(); - let system_uuid = "00000000-0000-0000-0000-000000000000"; - - // Placeholder regex: {{type:name}} or {{type}} - let ph_rx = Regex::new(r"\{\{([a-z]+)(?:[:]([^}]+))?\}\}").unwrap(); - - let clean_str = |s: &str| -> String { - let mut s = ws_re.replace_all(s, " ").into_owned(); - for token in ["(", ")", ",", "{", "}", "\"", "=", "'"] { - s = s.replace(&format!(" {}", token), token); - s = s.replace(&format!("{} ", token), token); - } - s.trim().to_string() - }; - - for (i, pattern_expect) in patterns.iter().enumerate() { - let aline_raw = &actual[i]; - let aline = clean_str(aline_raw); - - let pattern_str_raw = match pattern_expect { - super::SqlExpectation::Single(s) => s.clone(), - super::SqlExpectation::Multi(m) => m.join(" "), - }; - - let pattern_str = clean_str(&pattern_str_raw); - - let mut pp = regex::escape(&pattern_str); - pp = pp.replace(r"\{\{", "{{").replace(r"\}\}", "}}"); - - let mut cap_names = HashMap::new(); // cg_X -> var_name - let mut group_idx = 0; - - let mut final_rx_str = String::new(); - let mut last_match = 0; - - let pp_clone = pp.clone(); - for caps in ph_rx.captures_iter(&pp_clone) { - let full_match = caps.get(0).unwrap(); - final_rx_str.push_str(&pp[last_match..full_match.start()]); - - let type_name = caps.get(1).unwrap().as_str(); - let var_name = caps.get(2).map(|m| m.as_str()); - - if let Some(name) = var_name { - if let Some(val) = seen.get(name) { - final_rx_str.push_str(®ex::escape(val)); - } else { - let type_pattern = types.get(type_name).unwrap_or(&".*?"); - let cg_name = format!("cg_{}", group_idx); - final_rx_str.push_str(&format!("(?P<{}>{})", cg_name, type_pattern)); - cap_names.insert(cg_name, name.to_string()); - group_idx += 1; - } - } else { - let type_pattern = types.get(type_name).unwrap_or(&".*?"); - final_rx_str.push_str(&format!("(?:{})", type_pattern)); - } - - last_match = full_match.end(); - } - final_rx_str.push_str(&pp[last_match..]); - - let final_rx = match Regex::new(&format!("^{}$", final_rx_str)) { - Ok(r) => r, - Err(e) => return Err(format!("Bad constructed regex: {} -> {}", final_rx_str, e)), - }; - - if let Some(captures) = final_rx.captures(&aline) { - for (cg_name, var_name) in cap_names { - if let Some(m) = captures.name(&cg_name) { - let matched_str = m.as_str(); - if matched_str != system_uuid { - seen.insert(var_name, matched_str.to_string()); - } - } - } - } else { - return Err(format!( - "Line mismatched at execution sequence {}.\nExpected Pattern: {}\nActual SQL: {}\nRegex used: {}\nVariables Mapped: {:?}", - i + 1, - pattern_str, - aline, - final_rx_str, - seen - )); - } - } - - Ok(()) - } -} diff --git a/src/tests/types/expect/sql.rs b/src/tests/types/expect/sql.rs index f21baff..102d54e 100644 --- a/src/tests/types/expect/sql.rs +++ b/src/tests/types/expect/sql.rs @@ -1,8 +1,9 @@ use super::Expect; +use regex::Regex; use sqlparser::ast::{Expr, Query, SelectItem, Statement, TableFactor}; use sqlparser::dialect::PostgreSqlDialect; use sqlparser::parser::Parser; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; impl Expect { pub fn assert_sql(&self, actual: &[String]) -> Result<(), String> { @@ -204,4 +205,132 @@ impl Expect { } Ok(()) } + + /// Advanced SQL execution assertion algorithm ported from `assert.go`. + /// This compares two arrays of strings, one containing {{uuid:name}} or {{timestamp}} placeholders, + /// and the other containing actual executed database queries. It ensures that placeholder UUIDs + /// are consistently mapped to the same actual UUIDs across all lines, and strictly validates line-by-line sequences. + pub fn assert_pattern(&self, actual: &[String]) -> Result<(), String> { + let patterns = match &self.sql { + Some(s) => s, + None => return Ok(()), + }; + + if patterns.len() != actual.len() { + return Err(format!( + "Length mismatch: expected {} SQL executions, got {}.\nActual Execution Log:\n{}", + patterns.len(), + actual.len(), + actual.join("\n") + )); + } + + let ws_re = Regex::new(r"\s+").unwrap(); + + let types = HashMap::from([ + ( + "uuid", + r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", + ), + ( + "timestamp", + r"\d{4}-\d{2}-\d{2}(?:[ T])\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|\+\d{2}(?::\d{2})?)?", + ), + ("integer", r"-?\d+"), + ("float", r"-?\d+\.\d+"), + ("text", r"(?:''|[^'])*"), + ("json", r"(?:''|[^'])*"), + ]); + + let mut seen: HashMap = HashMap::new(); + let system_uuid = "00000000-0000-0000-0000-000000000000"; + + // Placeholder regex: {{type:name}} or {{type}} + let ph_rx = Regex::new(r"\{\{([a-z]+)(?:[:]([^}]+))?\}\}").unwrap(); + + let clean_str = |s: &str| -> String { + let mut s = ws_re.replace_all(s, " ").into_owned(); + for token in ["(", ")", ",", "{", "}", "\"", "=", "'"] { + s = s.replace(&format!(" {}", token), token); + s = s.replace(&format!("{} ", token), token); + } + s.trim().to_string() + }; + + for (i, pattern_expect) in patterns.iter().enumerate() { + let aline_raw = &actual[i]; + let formatted_actual = crate::tests::formatter::SqlFormatter::format(aline_raw).join(" "); + let aline = clean_str(&formatted_actual); + + let pattern_str_raw = match pattern_expect { + super::SqlExpectation::Single(s) => s.clone(), + super::SqlExpectation::Multi(m) => m.join(" "), + }; + + let pattern_str = clean_str(&pattern_str_raw); + + let mut pp = regex::escape(&pattern_str); + pp = pp.replace(r"\{\{", "{{").replace(r"\}\}", "}}"); + + let mut cap_names = HashMap::new(); // cg_X -> var_name + let mut group_idx = 0; + + let mut final_rx_str = String::new(); + let mut last_match = 0; + + let pp_clone = pp.clone(); + for caps in ph_rx.captures_iter(&pp_clone) { + let full_match = caps.get(0).unwrap(); + final_rx_str.push_str(&pp[last_match..full_match.start()]); + + let type_name = caps.get(1).unwrap().as_str(); + let var_name = caps.get(2).map(|m| m.as_str()); + + if let Some(name) = var_name { + if let Some(val) = seen.get(name) { + final_rx_str.push_str(®ex::escape(val)); + } else { + let type_pattern = types.get(type_name).unwrap_or(&".*?"); + let cg_name = format!("cg_{}", group_idx); + final_rx_str.push_str(&format!("(?P<{}>{})", cg_name, type_pattern)); + cap_names.insert(cg_name, name.to_string()); + group_idx += 1; + } + } else { + let type_pattern = types.get(type_name).unwrap_or(&".*?"); + final_rx_str.push_str(&format!("(?:{})", type_pattern)); + } + + last_match = full_match.end(); + } + final_rx_str.push_str(&pp[last_match..]); + + let final_rx = match Regex::new(&format!("^{}$", final_rx_str)) { + Ok(r) => r, + Err(e) => return Err(format!("Bad constructed regex: {} -> {}", final_rx_str, e)), + }; + + if let Some(captures) = final_rx.captures(&aline) { + for (cg_name, var_name) in cap_names { + if let Some(m) = captures.name(&cg_name) { + let matched_str = m.as_str(); + if matched_str != system_uuid { + seen.insert(var_name, matched_str.to_string()); + } + } + } + } else { + return Err(format!( + "Line mismatched at execution sequence {}.\nExpected Pattern: {}\nActual SQL: {}\nRegex used: {}\nVariables Mapped: {:?}", + i + 1, + pattern_str, + aline, + final_rx_str, + seen + )); + } + } + + Ok(()) + } }