Skip to content

Catalog query classification is not string-literal aware #106

Description

@erans

Problem

The catalog interceptor decides whether a query is a catalog query by substring-matching the whole query text, including inside SQL string literals (src/catalog/query_interceptor.rs:29 and the lower_query.contains(...) chain below it).

So a query that merely mentions a catalog relation in a literal is misclassified:

CREATE TABLE notes (id INT, msg TEXT);
INSERT INTO notes VALUES (1, 'see pg_catalog.pg_class for details');

SELECT count(*) FROM notes WHERE msg = 'see pg_catalog.pg_class for details';

The row matches and the count is correct, but the result column is typed text instead of int8, so a binary-protocol client doing try_get::<i64> fails.

Any table storing SQL text triggers this — audit logs of executed statements, migration-history tables, documentation or notes tables.

Related: a third blind-replace site

src/session/db_handler.rs:2743-2751 still does blind String::replace of schema qualifiers over the whole query text, the same defect #103 fixed in SchemaPrefixTranslator. It was left alone there as out of scope.

#103 added replace_outside_literals (case-insensitive, skips '...' literals with the doubled-quote escape and "..." quoted identifiers, single left-to-right scan, verified linear and UTF-8 safe). That helper is directly reusable here.

Suggested fix

Make the classification gate literal-aware, and switch the db_handler.rs site to replace_outside_literals. A cheap version of the former: strip string literals into a scratch buffer once, match the gate against that, and execute the original text.

Note the two are the same root cause — "we pattern-match SQL as an opaque string" — and are worth fixing together.

Also worth folding in

count(*) over pg_class is still typed text rather than int8 (src/query/extended.rs:2422). #103 added an Expr::Function arm that types it correctly, but scoped it with query.contains("information_schema"). Review confirmed empirically that the value encoder follows the RowDescription OID, so dropping that scope would fix pg_class too rather than break it — it was left narrow only to limit that change's blast radius.

Severity

Not urgent — this produces a wrong type OID, not a wrong value. Before #103 the same query class produced a genuinely wrong answer (the blind replace mangled the stored literal so the WHERE matched nothing); that part is already fixed.

Split out of #88 / #103.

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