fix(oracle): classify minus queries as set operations
This commit is contained in:
3 files changed
+45
-2
No files matched your search
@@ -112,7 +112,7 @@ export function analyzeEditableQueryEditability(sql: string): QueryEditability {
|
|||||||
if (!normalized) return { editable: false, reason: "not-select" };
|
if (!normalized) return { editable: false, reason: "not-select" };
|
||||||
if (/^\s*WITH\b/i.test(normalized)) return { editable: false, reason: "cte" };
|
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 (!/^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" };
|
return { editable: false, reason: "set-operation" };
|
||||||
}
|
}
|
||||||
if (normalized.includes(";")) return { editable: false, reason: "complex-source" };
|
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") {
|
if !starts_with_keyword(&normalized, "SELECT") {
|
||||||
return not_editable(QueryEditabilityReason::NotSelect);
|
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);
|
return not_editable(QueryEditabilityReason::SetOperation);
|
||||||
}
|
}
|
||||||
if normalized.contains(';') {
|
if normalized.contains(';') {
|
||||||
@@ -917,6 +917,32 @@ mod tests {
|
|||||||
assert!(analysis.columns.is_empty());
|
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]
|
#[test]
|
||||||
fn recognizes_oracle_for_update_clauses_without_treating_for_as_an_alias() {
|
fn recognizes_oracle_for_update_clauses_without_treating_for_as_an_alias() {
|
||||||
for sql in [
|
for sql in [
|
||||||
|
|||||||
+17
@@ -36,6 +36,23 @@ test("keeps the legacy analyzer API for editable SELECT queries", () => {
|
|||||||
assert.deepEqual(analysis.columns, []);
|
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", () => {
|
test("recognizes Oracle FOR UPDATE variants without treating FOR as an alias", () => {
|
||||||
for (const sql of [
|
for (const sql of [
|
||||||
"SELECT * FROM employees FOR UPDATE",
|
"SELECT * FROM employees FOR UPDATE",
|
||||||
|
|||||||
Reference in new issue
Block a user