Repository navigation
feat(engine): narrow try()'s error to what a statement writes through - #96
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Important
Three ways a statement that runs user code still comes out closed, so its narrowed QueryError doesn't hold at run time. Each one is reproduced below: the two Postgres cases on a Postgres 16 cluster using this PR's ERRORS_QUERY, and the SQLite case with node:sqlite. On Postgres the narrowed type makes code the strict PostgresCode list, so a user's U0001 is a value the type says can't happen.
Reviewed changes
I reviewed the whole PR: the engine's error narrowing on all three dialects, the run time's narrowed error types, the cache fingerprint, the oracle's new check, and the docs.
- Core —
Errorsand the word scanner inengine/core/src/errors.rs.with_factsreadsD::errorsin the same round as the freshness facts, and only when the analysis has no diagnostics. - Postgres — one catalog query reports what opens a statement (triggers, rules, foreign tables,
pg_dependon user procs and operators, coded types, user functions, operators and casts) and the names each constraint error can carry.analyze.rsnow collects operators, type names andchanges_keys. - MySQL — an
information_schemaquery covers triggers, stored functions in any schema and views that call routines. Multi-table UPDATE and DELETE now list every table they may write. - SQLite — a pragma-based query covers triggers, unique, primary-key, rowid, NOT NULL and STRICT names. These are copied to the primary
SQLITE_CONSTRAINTcode, exceptconstraint. - Run time —
PostgresQueryError<N>,MysqlQueryError<N>andSqliteQueryError<N>, plusqueryErrorType. Modules now declareQuery<…, QueryError>. The fingerprint also hashes what the narrowing reads. - Oracle — every raised error is checked against its statement's narrowing. Three new cases have an
errors.txt.
claude-opus-5-5 | 𝕏
There was a problem hiding this comment.
ℹ️ All three earlier findings are fixed, and I've resolved their threads. One related gap is left, with a suggestion inline.
Reviewed changes
This run covers the two commits since the last Pullfrog review: 91f7d00 and 0340c01.
- Postgres domain defaults and operator classes:
depends onnow covers a domain's own dependencies (classid = 'pg_type'overdom) and dependencies onpg_opclassandpg_opfamily. Two new oracle statements,insert_defaultedandinsert_ordered, cover them. - Postgres built-ins that run SQL text: a call to anything in
RUNS_SQL,ts_statorts_rewritenow opens the statement before the catalog query runs. The newquery_to_xmlstatement covers this. - SQLite name case: the written-table lookup, the foreign key lookup and the trigger lookup now compare with
collate nocase. The newinsert_memberanddelete_teamstatements cover this. - Fingerprint: the
trow now hashestypdefault, and a newqrow hashes user operator classes. - Docs:
postgres.mdxnow lists operator classes and SQL-running functions as things that keep the error type unnarrowed.
claude-opus-5-5 | 𝕏
0340c01 to
72c3d84
Compare
There was a problem hiding this comment.
ℹ️ The fix covers a direct read through a user operator class, and I've resolved that thread. One related gap remains, for inherited and partitioned children, and it has a suggestion inline.
Reviewed changes
This run covers the one commit since the last Pullfrog review, 822d6f1.
- Operator classes on reads: a new
ERRORS_QUERYclause opens a statement when any relation it reads, or any index on one, depends on a user'spg_opclassorpg_opfamily. The same clause covers partition keys. The doc comment now says so, and the new oracle statementselect_ordered.sqlblesses asopen.
claude-opus-5-5 | 𝕏
d22a65d to
897b1eb
Compare
There was a problem hiding this comment.
ℹ️ No new correctness issues. There's one misplaced doc comment, noted inline. The thread on reads through inherited and partitioned children is still open, since the code it points at hasn't changed.
Reviewed changes
This run covers what changed since the last Pullfrog review (822d6f1). The branch was rebased onto main at ec01766. The one new commit with code in it is ed7752b; the rest are engine and capture rebuilds.
- Same request every round (Postgres):
requestnow sorts and dedupes each ofERRORS_QUERY's list parameters, so the need is identical in every round.prefetchasks for it right after the rewrite, in the statement's first round. The new testthe_query_is_asked_alike_every_roundcovers this. - Same request every round (MySQL):
requestbuilds the query text from the statement's kind andmysql_params::tables.prefetchasks for it next to the parameter prefetch. The inlinekindmatch is now a function. - Rebuilds: all three
engine.wasmfiles were rebuilt on the new main. The capture from main's newmy_null_subqueries.sqlnow declaresQueryError.
claude-opus-5-5 | 𝕏
There was a problem hiding this comment.
✅ No new issues found. Both open threads are fixed, and I've resolved them.
Reviewed changes
This run covers the one commit since the last Pullfrog review, 6b9b13c.
- Reads reach a parent's children (Postgres):
edgenow has areadflag. It is set on inheritance and partition edges and on view edges, andrfollows only those, so foreign-key action edges stay write-only. A read through a parent now sees its children's user operator class indexes, sub-partition keys and foreign partitions. This is more conservative forONLYreads, but still sound. - Oracle: the new
select_logs.sql(an inheritance child with athirteen_opsindex) andselect_parts.sql(a partition with one) both bless asopen. - MySQL doc placement:
fn kindnow sits aboveparse's doc comment, so the doc attaches toparseagain. - Rebuilds: the Postgres and MySQL
engine.wasmfiles were rebuilt.
claude-opus-5-5 | 𝕏
6b9b13c to
38093b8
Compare
A user function opens a statement whatever it takes, and a user operator or cast where the statement can reach its types. MySQL lists every table a multi-table write may reach, reads quoted and commented calls, and looks a stored function up in any schema. SQLite's primary code no longer narrows the constraint, which may be a CHECK's.
On Postgres, a domain's default and an index's operator class that call a user's code open the statement, as does a server function that runs SQL it is given, such as query_to_xml(). On SQLite, a trigger or a foreign key that spells its table in another case is found.
|
No configured credential can serve this run. Your Claude Pro/Max subscription has hit its usage limit, which resets at 2026-10-26 08:00 UTC. Re-trigger after it resets, or add an API key for this provider, which Pullfrog falls back to automatically. The providers' responses
|
The parse lists a statement's tables in no fixed order, so the query's parameters changed between rounds and it was asked again each one, past the replay's limit for a large statement. Each list is sorted now, and the query is asked beside the statement's first needs, so it takes no round of its own.
38093b8 to
722b11a
Compare
|
No configured credential can serve this run. Your Claude Pro/Max subscription has hit its usage limit, which resets at 2026-10-26 08:00 UTC. Re-trigger after it resets, or add an API key for this provider, which Pullfrog falls back to automatically. The providers' responses
|

Builds on #88, now merged. This replaces #89, which closed when #88's branch was deleted; its review is there. When a query runs none of the user's own code, its
QueryErroris narrowed to the names each error can carry. The engine works this out from the catalog for each statement.A statement is closed when it runs only the database's own code, in the statement itself or in anything it sets off. Each closed statement's narrowing goes into the analysis as
errors, beside the freshness facts and in the same round. A statement that isn't closed keeps the whole type, since a trigger can raise any code with any name.Postgres
One catalog query per statement. It reports anything that opens the statement:
pg_depend);Otherwise
codeis the strict list, and each error names only what the statement writes through:MySQL
information_schemareports:Otherwise each error names only what the statement writes through: duplicate keys as
table.index, foreign keys on either side, checks, NOT NULL columns, and the columns of value errors.SQLite
Pragmas report a trigger on a table a write reaches. Otherwise each error names only what the statement writes through: unique keys' columns as SQLite's message lists them, expression indexes, primary-key, rowid, NOT NULL and STRICT columns. The tables and columns are listed under the primary
SQLITE_CONSTRAINTtoo, for drivers that give only that; a constraint isn't, since under that code it may be a CHECK's. SQLite's catalog doesn't name a CHECK, so CHECK errors aren't narrowed.Types and checks
PostgresQueryError<N>,MysqlQueryError<N>andSqliteQueryError<N>each take the narrowing. A module declaresQuery<…, QueryError>.errors,mysql_errors,sqlite_errors) carry anerrors.txt. All 120 cases pass with--strict-coverage.engine.wasmare rebuilt.Fixes from #89's review
select :m ~~~ 1, where Postgres resolves the parameter to a user type.SQLITE_CONSTRAINTcode no longer narrowsconstraint, which may be a CHECK's.