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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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