Skip to content

[BUG] TablesNamesFinder misses tables or throws for piped queries, DML statements and analytic clauses #2478

Description

@fudianchn

Failing SQL Feature:

TablesNamesFinder misses tables silently or throws for six statement / expression families. Since it is the documented entry point for SQL auditing / firewall use cases, a silently incomplete table list is a security relevant defect.

# SQL family Result on master
1 Piped query [], every table missing
2 DELETE with a WITH list CTE tables missing, the CTE alias leaks as a phantom table
3 MERGE tables inside the ON condition and the WHEN branches missing
4 INSERT with SET list, ON DUPLICATE KEY UPDATE or ON CONFLICT ... DO UPDATE; OUTPUT / RETURNING clauses of INSERT / UPDATE / DELETE clause subqueries missing
5 Data modifying CTE (WITH del AS (DELETE FROM ...)) ClassCastException
6 Analytic function with function level ORDER BY, window ORDER BY or FILTER clause NullPointerException / silently skipped

Root causes in TablesNamesFinder.java @ f41c0b8:

  • visit(FromQuery) returns null without traversing anything: piped queries were never wired up
  • visit(Delete) does not traverse withItemsList (Update and Insert do)
  • visit(Merge) does not traverse onCondition and operations
  • visit(Insert) does not traverse setUpdateSets, duplicateAction, conflictAction, outputClause, returningClause; visit(Update) / visit(Delete) skip outputClause / returningClause as well
  • visit(WithItem) dispatches the payload through getSelect(), which assumes a Select, while data modifying CTE payloads (ParenthesedDelete / ParenthesedUpdate / ParenthesedInsert) are legal
  • visit(AnalyticExpression) guards on getFuncOrderBy() but iterates getOrderByElements(), and skips filterExpression; [BUG] NPE in TableNamesFinder in AnalyticExpression visitor #2443 fixed a sibling NPE (window element range / offset) in the same method, this guard went unnoticed

SQL Example:

// 1. piped query: returns []
TablesNamesFinder.findTables("FROM MY_TABLE1 "
        + "|> WHERE id IN (SELECT id FROM MY_TABLE2) "
        + "|> LEFT JOIN (SELECT item FROM MY_TABLE3) AS t3 ON t3.item = item "
        + "|> UNION ALL (SELECT * FROM MY_TABLE4) "
        + "|> SELECT item");

// 2. DELETE with WITH: returns [cte, MY_TABLE1]
TablesNamesFinder.findTables(
        "WITH cte AS (SELECT * FROM MY_TABLE2) DELETE FROM MY_TABLE1 WHERE id IN (SELECT id FROM cte)");

// 3. MERGE: returns [src, MY_TABLE1]
TablesNamesFinder.findTables("MERGE INTO MY_TABLE1 USING src "
        + "ON id IN (SELECT id FROM MY_TABLE3) "
        + "WHEN MATCHED THEN UPDATE SET v = (SELECT MAX(v) FROM MY_TABLE4)");

// 4. INSERT actions / clauses: returns [MY_TABLE1]
TablesNamesFinder.findTables(
        "INSERT INTO MY_TABLE1 (a) VALUES (1) ON DUPLICATE KEY UPDATE b = (SELECT x FROM MY_TABLE2)");
TablesNamesFinder.findTables("UPDATE MY_TABLE1 SET a = 1 RETURNING (SELECT x FROM MY_TABLE2)");

// 5. data modifying CTE: ClassCastException (ParenthesedDelete cannot be cast to ParenthesedSelect)
TablesNamesFinder.findTables(
        "WITH del AS (DELETE FROM MY_TABLE2) INSERT INTO MY_TABLE1 SELECT * FROM del");

// 6. analytic function with function level ORDER BY: NullPointerException
//    ("...getOrderByElements()" is null because the return value of ...")
TablesNamesFinder.findTables(
        "SELECT string_agg(name, ',' ORDER BY id) OVER (PARTITION BY grp) FROM MY_TABLE1");

Software Information:

  • JSqlParser version: 5.4-SNAPSHOT (master f41c0b8)
  • Database: vendor neutral (piped queries: BigQuery / ZetaSQL; data modifying CTEs and FILTER: PostgreSQL; ON DUPLICATE KEY UPDATE: MySQL; OUTPUT: SQL Server)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions