fixed merge lookup issue
This commit is contained in:
@ -972,7 +972,119 @@
|
||||
"LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id",
|
||||
"LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id",
|
||||
"LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id",
|
||||
"WHERE \"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '1990-01-01T00:00:00Z' AND \"pronouns\" = 'they/them'"
|
||||
"WHERE (",
|
||||
" \"first_name\" = 'LookupFirst'",
|
||||
" AND \"last_name\" = 'LookupLast'",
|
||||
" AND \"date_of_birth\" = '1990-01-01T00:00:00Z'",
|
||||
" AND \"pronouns\" = 'they/them'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"UPDATE agreego.\"person\"",
|
||||
"SET",
|
||||
" \"contact_id\" = 'abc-contact'",
|
||||
"WHERE",
|
||||
" id = '22222222-2222-2222-2222-222222222222'"
|
||||
],
|
||||
[
|
||||
"UPDATE agreego.\"entity\"",
|
||||
"SET",
|
||||
" \"modified_at\" = '2026-03-10T00:00:00Z',",
|
||||
" \"modified_by\" = '00000000-0000-0000-0000-000000000000'",
|
||||
"WHERE",
|
||||
" id = '22222222-2222-2222-2222-222222222222'"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.change (",
|
||||
" \"old\",",
|
||||
" \"new\",",
|
||||
" entity_id,",
|
||||
" id,",
|
||||
" kind,",
|
||||
" modified_at,",
|
||||
" modified_by",
|
||||
")",
|
||||
"VALUES (",
|
||||
" '{",
|
||||
" \"contact_id\":\"old-contact\"",
|
||||
" }',",
|
||||
" '{",
|
||||
" \"contact_id\":\"abc-contact\",",
|
||||
" \"type\":\"person\"",
|
||||
" }',",
|
||||
" '22222222-2222-2222-2222-222222222222',",
|
||||
" '{{uuid}}',",
|
||||
" 'update',",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"SELECT pg_notify('entity', '{",
|
||||
" \"complete\":{",
|
||||
" \"contact_id\":\"abc-contact\",",
|
||||
" \"date_of_birth\":\"1990-01-01T00:00:00Z\",",
|
||||
" \"first_name\":\"LookupFirst\",",
|
||||
" \"id\":\"22222222-2222-2222-2222-222222222222\",",
|
||||
" \"last_name\":\"LookupLast\",",
|
||||
" \"modified_at\":\"2026-03-10T00:00:00Z\",",
|
||||
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||
" \"pronouns\":\"they/them\",",
|
||||
" \"type\":\"person\"",
|
||||
" },",
|
||||
" \"new\":{",
|
||||
" \"contact_id\":\"abc-contact\",",
|
||||
" \"type\":\"person\"",
|
||||
" },",
|
||||
" \"old\":{",
|
||||
" \"contact_id\":\"old-contact\"",
|
||||
" }",
|
||||
" }')"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Update existing person with id (lookup)",
|
||||
"action": "merge",
|
||||
"data": {
|
||||
"id": "33333333-3333-3333-3333-333333333333",
|
||||
"type": "person",
|
||||
"first_name": "LookupFirst",
|
||||
"last_name": "LookupLast",
|
||||
"date_of_birth": "1990-01-01T00:00:00Z",
|
||||
"pronouns": "they/them",
|
||||
"contact_id": "abc-contact"
|
||||
},
|
||||
"mocks": [
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
"type": "person",
|
||||
"first_name": "LookupFirst",
|
||||
"last_name": "LookupLast",
|
||||
"date_of_birth": "1990-01-01T00:00:00Z",
|
||||
"pronouns": "they/them",
|
||||
"contact_id": "old-contact"
|
||||
}
|
||||
],
|
||||
"schema_id": "person",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"sql": [
|
||||
[
|
||||
"SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
|
||||
"FROM agreego.\"person\" t1",
|
||||
"LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id",
|
||||
"LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id",
|
||||
"LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id",
|
||||
"WHERE",
|
||||
" t1.id = '33333333-3333-3333-3333-333333333333'",
|
||||
" OR (",
|
||||
" \"first_name\" = 'LookupFirst'",
|
||||
" AND \"last_name\" = 'LookupLast'",
|
||||
" AND \"date_of_birth\" = '1990-01-01T00:00:00Z'",
|
||||
" AND \"pronouns\" = 'they/them'",
|
||||
" )"
|
||||
],
|
||||
[
|
||||
"UPDATE agreego.\"person\"",
|
||||
@ -1484,7 +1596,7 @@
|
||||
"SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
|
||||
"FROM agreego.\"order\" t1",
|
||||
"LEFT JOIN agreego.\"entity\" t2 ON t2.id = t1.id",
|
||||
"WHERE t1.id = 'abc'"
|
||||
"WHERE t1.id = 'abc' OR (\"id\" = 'abc')"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.\"entity\" (",
|
||||
|
||||
@ -124,42 +124,23 @@ fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
|
||||
return None;
|
||||
};
|
||||
|
||||
// 2. Extract WHERE conditions
|
||||
let mut conditions = Vec::new();
|
||||
// 2. Extract WHERE conditions string
|
||||
let mut where_clause = String::new();
|
||||
if let Some(where_idx) = sql_upper.find(" WHERE ") {
|
||||
let mut where_end = sql_upper.find(" ORDER BY ").unwrap_or(sql.len());
|
||||
let mut where_end = sql_upper.find(" ORDER BY ").unwrap_or(sql_upper.len());
|
||||
if let Some(limit_idx) = sql_upper.find(" LIMIT ") {
|
||||
if limit_idx < where_end {
|
||||
where_end = limit_idx;
|
||||
}
|
||||
}
|
||||
let where_clause = &sql[where_idx + 7..where_end];
|
||||
let and_regex = Regex::new(r"(?i)\s+AND\s+").ok()?;
|
||||
let parts = and_regex.split(where_clause);
|
||||
for part in parts {
|
||||
if let Some(eq_idx) = part.find('=') {
|
||||
let left = part[..eq_idx]
|
||||
.trim()
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap_or("")
|
||||
.trim_matches('"');
|
||||
let right = part[eq_idx + 1..].trim().trim_matches('\'');
|
||||
conditions.push((left.to_string(), right.to_string()));
|
||||
} else if part.to_uppercase().contains(" IS NULL") {
|
||||
let left = part[..part.to_uppercase().find(" IS NULL").unwrap()]
|
||||
.trim()
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap_or("")
|
||||
.replace('"', ""); // Remove quotes explicitly
|
||||
conditions.push((left, "null".to_string()));
|
||||
}
|
||||
}
|
||||
where_clause = sql[where_idx + 7..where_end].to_string();
|
||||
}
|
||||
|
||||
// 3. Find matching mocks
|
||||
let mut matches = Vec::new();
|
||||
let or_regex = Regex::new(r"(?i)\s+OR\s+").ok()?;
|
||||
let and_regex = Regex::new(r"(?i)\s+AND\s+").ok()?;
|
||||
|
||||
for mock in mocks {
|
||||
if let Some(mock_obj) = mock.as_object() {
|
||||
if let Some(t) = mock_obj.get("type") {
|
||||
@ -168,25 +149,66 @@ fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut matches_all = true;
|
||||
for (k, v) in &conditions {
|
||||
let mock_val_str = match mock_obj.get(k) {
|
||||
Some(Value::String(s)) => s.clone(),
|
||||
Some(Value::Number(n)) => n.to_string(),
|
||||
Some(Value::Bool(b)) => b.to_string(),
|
||||
Some(Value::Null) => "null".to_string(),
|
||||
_ => {
|
||||
matches_all = false;
|
||||
break;
|
||||
if where_clause.is_empty() {
|
||||
matches.push(mock.clone());
|
||||
continue;
|
||||
}
|
||||
|
||||
let or_parts = or_regex.split(&where_clause);
|
||||
let mut any_branch_matched = false;
|
||||
|
||||
for or_part in or_parts {
|
||||
let branch_str = or_part.replace('(', "").replace(')', "");
|
||||
let mut branch_matches = true;
|
||||
|
||||
for part in and_regex.split(&branch_str) {
|
||||
if let Some(eq_idx) = part.find('=') {
|
||||
let left = part[..eq_idx]
|
||||
.trim()
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap_or("")
|
||||
.trim_matches('"');
|
||||
let right = part[eq_idx + 1..].trim().trim_matches('\'');
|
||||
|
||||
let mock_val_str = match mock_obj.get(left) {
|
||||
Some(Value::String(s)) => s.clone(),
|
||||
Some(Value::Number(n)) => n.to_string(),
|
||||
Some(Value::Bool(b)) => b.to_string(),
|
||||
Some(Value::Null) => "null".to_string(),
|
||||
_ => "".to_string(),
|
||||
};
|
||||
if mock_val_str != right {
|
||||
branch_matches = false;
|
||||
break;
|
||||
}
|
||||
} else if part.to_uppercase().contains(" IS NULL") {
|
||||
let left = part[..part.to_uppercase().find(" IS NULL").unwrap()]
|
||||
.trim()
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap_or("")
|
||||
.trim_matches('"');
|
||||
|
||||
let mock_val_str = match mock_obj.get(left) {
|
||||
Some(Value::Null) => "null".to_string(),
|
||||
_ => "".to_string(),
|
||||
};
|
||||
|
||||
if mock_val_str != "null" {
|
||||
branch_matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
if mock_val_str != *v {
|
||||
matches_all = false;
|
||||
}
|
||||
|
||||
if branch_matches {
|
||||
any_branch_matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if matches_all {
|
||||
if any_branch_matched {
|
||||
matches.push(mock.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@ -585,11 +585,14 @@ impl Merger {
|
||||
template
|
||||
};
|
||||
|
||||
let where_clause = if let Some(id) = id_val {
|
||||
format!("WHERE t1.id = {}", Self::quote_literal(id))
|
||||
} else if lookup_complete {
|
||||
let mut lookup_predicates = Vec::new();
|
||||
let mut where_parts = Vec::new();
|
||||
|
||||
if let Some(id) = id_val {
|
||||
where_parts.push(format!("t1.id = {}", Self::quote_literal(id)));
|
||||
}
|
||||
|
||||
if lookup_complete {
|
||||
let mut lookup_predicates = Vec::new();
|
||||
for column in &entity_type.lookup_fields {
|
||||
let val = entity_fields.get(column).unwrap_or(&Value::Null);
|
||||
if column == "type" {
|
||||
@ -598,10 +601,14 @@ impl Merger {
|
||||
lookup_predicates.push(format!("\"{}\" = {}", column, Self::quote_literal(val)));
|
||||
}
|
||||
}
|
||||
format!("WHERE {}", lookup_predicates.join(" AND "))
|
||||
} else {
|
||||
where_parts.push(format!("({})", lookup_predicates.join(" AND ")));
|
||||
}
|
||||
|
||||
if where_parts.is_empty() {
|
||||
return Ok(None);
|
||||
};
|
||||
}
|
||||
|
||||
let where_clause = format!("WHERE {}", where_parts.join(" OR "));
|
||||
|
||||
let final_sql = format!("{} {}", fetch_sql_template, where_clause);
|
||||
|
||||
|
||||
@ -8596,3 +8596,9 @@ fn test_merger_0_10() {
|
||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 10).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merger_0_11() {
|
||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 11).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user