Conversation
EnRaiha
force-pushed
the
fix/issue318-typeguard-convert
branch
from
September 17, 2026 02:16
88f2eda to
667d58f
Compare
…source key Three defects on the typeguard and CONVERT paths: - CREATE TYPEGUARD read DatabaseId::DEFAULT while collections live under the session database; thread database_id through the seven handlers and validate_typeguard - CONVERT rebuilt the strict schema from the column list and dropped the source primary key, so every later insert failed "no resolved primary key"; mark the source key column and refuse a column list that omits it - a guard DEFAULT or VALUE naming another column became a strict column DEFAULT that evaluates with no row in scope; refuse the guard at CONVERT, naming the field and clause
The explicit column list defines the schema and carries no guard onto it. A guard the list covers and that names another column has no column DEFAULT equivalent: evaluated with no row in scope it fails every insert, and dropping it loses the guard's meaning silently. Refuse it at CONVERT, naming field and clause — the shape the filed reproduction takes. Coverage: the filed reproduction as a refusal, CONVERT keeping the source primary key, a list that omits it refused, and a guard declared in a session database (declaration resolves there).
…e gate The reference check lived at two CONVERT call sites, so CREATE COLLECTION and a CONVERT column list still accepted a cross-column DEFAULT and failed at the first insert with UnevaluableDefault. The check moves into the gate every producer calls. - validate_constant_clause_expr: classify first, exactly as the default classifier does, so a generator (UUID_V7, gen_uuid_v7()) or a literal is never read as a column reference - CREATE COLLECTION, a CONVERT column list, a CONVERT typeguard carry, and the explicit-list guard path all pass through it; the two local copies are gone
ALTER TABLE ADD COLUMN accepted a DEFAULT that names another column; the first insert failed with UnevaluableDefault. The declared definition passes the same gate CREATE and CONVERT columns pass.
A guard VALUE carried onto a column DEFAULT reported DEFAULT; the clause is VALUE. The carry path passes the clause through the one gate, so the refusal names what the author wrote. Same refusal either way; only the wording changes.
The gate passed the whole definition text as type text, and the type parser finds the clause by substring — a column named is_default was read as a DEFAULT clause and refused. The gate now reads the parsed default, and the parser's clause search requires the keyword to start a token, so a name that contains the word is never the clause.
EnRaiha
force-pushed
the
fix/issue318-typeguard-convert
branch
from
September 17, 2026 04:39
667d58f to
ee544e7
Compare
…d-convert # Conflicts: # nodedb/src/control/server/shared/ddl/neutral/column_default.rs # nodedb/src/control/server/shared/ddl/neutral/convert/typeguard_columns.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three defects on the typeguard and CONVERT declaration paths, and one gate that all of them pass through:
CREATE TYPEGUARDresolved against the wrong database. The handlers readDatabaseId::DEFAULTwhile collections live under the session database, so a guard declared in another database answered42P01for a collection that exists.CONVERTdropped the source primary key. The conversion rebuilt the strict schema from the column list, and the key column lost its primary-key mark; every later insert failed with "no resolved primary key". A column list that omits the key column is now refused.VALUE LOWER(status)is evaluated per row; carried onto a strict column it becomes aDEFAULT, which is const-folded once with no row in scope — the CONVERT was accepted and the first insert failed withUnevaluableDefault. It is now refused at the declaration, naming field and clause.The same invariant now holds at one gate (
validate_constant_clause_expr) for every producer of a columnDEFAULT:CREATE COLLECTION, aCONVERTcolumn list, aCONVERTtypeguard carry, andALTER … ADD COLUMN.Scope statement (requested by the audit). The issue text names defect 3 only. This PR also carries defects 1 and 2, which the maintainer's resubmission conditions grouped under #318 — typeguard
database_idthreading, CONVERT primary-key carry, and the cross-column refusal. They ride together as in-PR prerequisites of the same declaration path; they are stated here rather than split, per the audit's remedy.Behaviour changes
CREATE TYPEGUARDin a session database42P01INSERTafterCONVERT … (id TEXT, v TEXT)CONVERTcolumn list that omits the source key42601CREATE/CONVERT/ALTER ADD COLUMN … DEFAULT LOWER(other)UnevaluableDefault42601at declarationVALUE LOWER(status)carried byCONVERT42601at theCONVERT, naming field and clauseALTER … ADD COLUMN is_default BOOLEANDEFAULT 'BOOLEAN'clauseCloses #318.
Root cause
typeguard/handlers.rsbuilt every request withDatabaseId::DEFAULT;validate.rsdid the same when resolving the target collection. The session's database never reached the handler.convert/driver.rsbuilt the target schema from the explicit column list alone. The source's key column (primary_key) was not consulted, so the converted schema had no key andassign_target_surrogatefailed on the next insert with "no resolved primary key".CompiledDefault::declareclassifies aDEFAULT; a function call likeLOWER(status)parses to anExprand was accepted. Nothing checked whether the expression names a column. The trap appeared only at the first insert (UnevaluableDefault).CREATE COLLECTION(collection/create/build.rs) and aCONVERTcolumn list (convert/column_defs.rs) still accepted a cross-columnDEFAULTand failed at the first insert. The audit foundALTER … ADD COLUMNas a fourth producer that bypassed the gate entirely.What changed
Commit
1b7dbd907— the three defects:database_idthreaded through the seven typeguard handlers andvalidate_typeguard; the router resolves the collection in the session database.CONVERTmarks the source key column in the converted schema; a column list that omits it is refused with42601.nodedb-sqlexposesdefault_expr_references_columns— the classifier's own parse, no second name list — andCONVERTrefuses a carried guard whose expression names another column.Commit
780176fac— the explicit-list path: a guard the list covers and that names another column is refused there too (the list defines the schema and carries no guard onto it).Commit
879b1329f— one gate:validate_constant_clause_expr(clause, owner, expr)is the single place that refuses an unregistered function name, an unparsable expression, or a column reference;validate_column_defaultbecomes theDEFAULT-clause form of it, and the two CONVERT-local copies of the reference check are deleted.default_expr_references_columnsnow mirrorsclassify: a generator (UUID_V7,gen_uuid_v7()) or a literal/parametric form carries no expression and is never read as a column reference. (The first cut parsed the raw text and refusedDEFAULT UUID_V7— caught by the existingsql_default_expressionsandsql_typeguard_defaultsmodules.)Commit
11a3c7b26—ALTER … ADD COLUMN: the declared definition passes the same gate.Commit
82ea86a91— wording: the carry path passes the clause through, so a guardVALUEreportsVALUE, notDEFAULT(audit advisory).Commit
b9bc243fb— the gate reads the parsedDEFAULT, never the definition text, and the type parser's clause search requires the keyword to start a token: a column namedis_defaultis no longer read as aDEFAULTclause.Merge
44b21159a— foldsorigin/main(#336 instant typing, #327 constant-row cell keys) intothe branch, resolving the DDL-gate conflict by composing both sides:
validate_column_default_clauseruns the constant/column-reference check and the literal type/range coercion at one gate.
Regression proof
On base
main, without this change: the insert afterCONVERTfails with "no resolved primary key", a column list that omits the key is accepted, the cross-column guard is accepted,CREATE TYPEGUARDin a session database answers42P01, and a cross-columnDEFAULTis accepted atCREATE/CONVERT/ALTER("got success"). With this change each is refused or resolved as listed in Behaviour changes.Tested
cargo nextest run -p nodedb --test wire -E 'test(cases::sql_convert_column_defs) | test(cases::sql_default_expressions) | test(cases::sql_typeguard_default_gate) | test(cases::sql_typeguard_defaults)'— 41 passedonly_the_expression_branch_can_reference_a_columnpins the classifier mirroring (generators and literals are not references; a column and a concatenation are)cargo fmt --all -- --check,cargo clippy -p nodedb -p nodedb-sql --lib -- -D warnings, preflight: cleanReview
A separate, read-only parity audit ran over this branch before submission. Its findings and their resolutions:
CONVERTcall sites, not the gate every producer calls879b1329f);CREATE,CONVERT, the explicit-list guard path andALTERall route through itALTER … ADD COLUMN … DEFAULTbypassed the gate11a3c7b26)ALTERgate read the whole definition, so a name containingdefaultwas read as the clauseb9bc243fb)VALUEreportedDEFAULT82ea86a91)Exclusions
Commits
1b7dbd907—fix(typeguard): resolve in the session database and keep the CONVERT source key780176fac—fix(convert): refuse a cross-column guard on the explicit-list path879b1329f—fix(ddl): refuse a column DEFAULT that names another column at the one gate11a3c7b26—fix(ddl): gate an ALTER ADD COLUMN DEFAULT like a CREATE column DEFAULT82ea86a91—fix(ddl): name the clause the author wrote on the carried guardb9bc243fb—fix(ddl): read an ALTER default from the parsed definition88f2edaa1—style: rustfmt