Skip to content

DELETE FROM ONLY and UPDATE ONLY don't handle PostgreSQL ONLY keyword correctly #74

@devmaha

Description

@devmaha

PostgreSQL supports the ONLY keyword in DELETE and UPDATE to prevent scanning child tables in an inheritance hierarchy:

DELETE FROM ONLY archived_employees WHERE archived_at < NOW() - INTERVAL '1 year';
UPDATE ONLY parent_table SET status = 'inactive' WHERE id = 1;

Current behavior:
DELETE FROM ONLY archived_employees parses successfully but treats ONLY as the table name:

Delete.table.name = "ONLY"
Delete.table.only = false

UPDATE ONLY parent_table SET status = 'inactive' fails with:

Parse error: Expected identifier, got "Only"
Expected behavior:

Both should parse with table.name = "archived_employees" / "parent_table" and table.only = true, consistent with how parse_table_expression() handles ONLY in SELECT FROM clauses.

Root cause:

parse_table_expression() (line ~3086 in parser.rs) correctly handles ONLY:

let has_only = self.match_token(TokenType::Only);
// ... parse table ...
if has_only {
if let Expression::Table(ref mut table) = expr {
table.only = true;
}
}
But parse_delete() (line ~10082) calls parse_table_ref() directly after consuming FROM, and parse_update() (line ~9737) calls expect_identifier_with_quoted() directly. Neither consumes the ONLY token first.

Suggested fix:
In parse_delete, after consuming FROM, check for and consume ONLY before calling parse_table_ref(), then set table.only = true on the resulting TableRef.
In parse_update, after consuming UPDATE, check for and consume ONLY before parsing the table identifier, then set table.only = true.

Affected dialects: PostgreSQL (and any dialect using inheritance with ONLY).

Reproduction:
use polyglot_sql::{parse_one, DialectType};
use polyglot_sql::expressions::Expression;

let expr = parse_one(
"DELETE FROM ONLY archived_employees WHERE archived_at < NOW() - INTERVAL '1 year'",
DialectType::PostgreSQL,
).unwrap();

if let Expression::Delete(d) = &expr {
assert_eq!(d.table.name.name, "archived_employees"); // FAILS: actual = "ONLY"
assert!(d.table.only); // FAILS: actual = false
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions