fix(oracle): classify minus queries as set operations
This commit is contained in:
parent
0be9bcef12
commit
a5eb6fd1fa
|
|
@ -112,7 +112,7 @@ export function analyzeEditableQueryEditability(sql: string): QueryEditability {
|
|||
if (!normalized) return { editable: false, reason: "not-select" };
|
||||
if (/^\s*WITH\b/i.test(normalized)) return { editable: false, reason: "cte" };
|
||||
if (!/^SELECT\b/i.test(normalized)) return { editable: false, reason: "not-select" };
|
||||
if (hasTopLevelKeyword(normalized, ["UNION", "INTERSECT", "EXCEPT"])) {
|
||||
if (hasTopLevelKeyword(normalized, ["UNION", "INTERSECT", "EXCEPT", "MINUS"])) {
|
||||
return { editable: false, reason: "set-operation" };
|
||||
}
|
||||
if (normalized.includes(";")) return { editable: false, reason: "complex-source" };
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ pub fn analyze_editable_query_editability(sql: &str) -> QueryEditability {
|
|||
if !starts_with_keyword(&normalized, "SELECT") {
|
||||
return not_editable(QueryEditabilityReason::NotSelect);
|
||||
}
|
||||
if has_top_level_keyword(&normalized, &["UNION", "INTERSECT", "EXCEPT"]) {
|
||||
if has_top_level_keyword(&normalized, &["UNION", "INTERSECT", "EXCEPT", "MINUS"]) {
|
||||
return not_editable(QueryEditabilityReason::SetOperation);
|
||||
}
|
||||
if normalized.contains(';') {
|
||||
|
|
@ -917,6 +917,32 @@ mod tests {
|
|||
assert!(analysis.columns.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recognizes_top_level_sql_set_operations_as_read_only() {
|
||||
for operator in ["UNION", "INTERSECT", "EXCEPT", "MINUS"] {
|
||||
let sql = format!("SELECT id FROM users {operator} SELECT id FROM archived_users");
|
||||
let result = analyze_editable_query_editability(&sql);
|
||||
|
||||
assert!(!result.editable, "{operator}");
|
||||
assert_eq!(result.reason, Some(QueryEditabilityReason::SetOperation), "{operator}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_minus_in_strings_comments_and_nested_queries() {
|
||||
for sql in [
|
||||
"SELECT id, 'MINUS' AS operation FROM users",
|
||||
"SELECT id FROM users -- MINUS\nWHERE active = 1",
|
||||
"SELECT id FROM users /* MINUS */ WHERE active = 1",
|
||||
"SELECT * FROM users WHERE id IN (SELECT id FROM archived_users MINUS SELECT id FROM blocked_users)",
|
||||
] {
|
||||
let result = analyze_editable_query_editability(sql);
|
||||
|
||||
assert!(result.editable, "{sql}: {:?}", result.reason);
|
||||
assert_eq!(result.analysis.unwrap().table_name, "users", "{sql}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recognizes_oracle_for_update_clauses_without_treating_for_as_an_alias() {
|
||||
for sql in [
|
||||
|
|
|
|||
|
|
@ -36,6 +36,23 @@ test("keeps the legacy analyzer API for editable SELECT queries", () => {
|
|||
assert.deepEqual(analysis.columns, []);
|
||||
});
|
||||
|
||||
test("recognizes top-level SQL set operations as read-only", () => {
|
||||
for (const operator of ["UNION", "INTERSECT", "EXCEPT", "MINUS"]) {
|
||||
const sql = `SELECT id FROM users ${operator} SELECT id FROM archived_users`;
|
||||
|
||||
assert.deepEqual(analyzeEditableQueryEditability(sql), { editable: false, reason: "set-operation" }, operator);
|
||||
}
|
||||
});
|
||||
|
||||
test("ignores MINUS in strings, comments, and nested queries", () => {
|
||||
for (const sql of ["SELECT id, 'MINUS' AS operation FROM users", "SELECT id FROM users -- MINUS\nWHERE active = 1", "SELECT id FROM users /* MINUS */ WHERE active = 1", "SELECT * FROM users WHERE id IN (SELECT id FROM archived_users MINUS SELECT id FROM blocked_users)"]) {
|
||||
const result = analyzeEditableQueryEditability(sql);
|
||||
|
||||
assert.equal(result.editable, true, sql);
|
||||
assert.equal(result.analysis.tableName, "users", sql);
|
||||
}
|
||||
});
|
||||
|
||||
test("recognizes Oracle FOR UPDATE variants without treating FOR as an alias", () => {
|
||||
for (const sql of [
|
||||
"SELECT * FROM employees FOR UPDATE",
|
||||
|
|
|
|||
Loading…
Reference in New Issue