Repository navigation
feat(postgres): type a json key read by the column's json schema - #95
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
ℹ️ One narrow soundness gap inline. Everything else I traced holds up.
Reviewed changes
I reviewed the whole PR at 56457ca: the core schema walk, the Postgres reader and probe changes, the carry-through for derived tables and with queries, the new oracle case, and the docs and skill.
schema_type::at_path: walks a canonical schema one key at a time. It returns the schema found at the path and whether the value is there for certain. It givesNonefornot, and also for an integer key under#>that might land on an array.Expr::JsonPathin the reader: the built-in->,->>,#>and#>>with literal keys over a column (bare or narrowed) now build a path, and chained reads extend it. Non-literal keys, shadowed operators and non-column bases stayExpr::Unknown, which matches the old behaviour, since->was never inPOSTGRES_OPERATORS.json_readand the probe: a top-level read sets the column'sdocument(for a JSON read) or itsvalues(for a text read whose schema allows only strings). Thechecks.valuesloop no longer overwrites those values withNone.ItemColumninitem_columns: a derived table orwithquery now passes its column'sdocumentandvaluesto an outer column that reads it as it is.- Tests and docs: a new
oracle/cases/json_schema_keyscase with 14 statements, unit tests for the walk and the reader, a captured docs card, and the skill line.
Points I checked and found sound:
- Canonical schemas fold
patternPropertiesintoadditionalProperties. anyOfandenumignore sibling keywords, which can only loosen the type.- A text key read with
->on an array returns SQL NULL. #> '{}'returns the whole document.
claude-opus-5-5 | 𝕏
56457ca to
6053c4b
Compare
There was a problem hiding this comment.
✅ No new issues found. The new commit fixes the
#>index gap from the prior review.
Reviewed changes
I reviewed the one commit pushed since the prior review, 6053c4b.
- Index keys follow
strtoint: under#>and#>>,at_keynow counts a key as a possible array index ifstrtointwould read the whole key: optional leading C whitespace, one optional+or-, then digits. So-1,+0and1now stop the walk where an array may be. A key with trailing whitespace, whichstrtointrejects, is still read as an object key. A digit key too large to fit is also counted as an index, which is the safe side. - Coverage: the new oracle statement
signed_index.sqlreads a value that is either an array or an object, using signed keys, a space-led key and a plain key. Unit tests inschema_type.rscover the same keys. The doc comments inschema_type.rsandinfer.rsnow describe the rule. - Artifacts: all three
engine.wasmfiles were rebuilt for the core change.
claude-opus-5-5 | 𝕏
6053c4b to
0a8a346
Compare

On Postgres, a
jsonorjsonbcolumn a JSON SchemaCHECKholds was typed by that schema only when read whole. A key read from it was typed by the operator's result alone:->asjsonb, which isunknown, and->>astext, both nullable, so a value the schema already describes needed a cast, and never came back non-null.Now a read at literal keys, with the built-in
->,->>,#>or#>>, takes what the schema says is there. Each step reads the schema's object at its key, as the server reads the value's:->and#>are typed by the property's schema;->>and#>>aretext, a union of strings where the schema keeps the value to a list of them.#>(digits, after a sign or leading whitespace, a negative one counting from the end) may index an array instead, which is not followed: the read staysunknownwhere a step may land on one.->(hstore defines one), and any other read stay as they were.withquery or a derived table keeps its type when the statement selects its column as it is, as built JSON does.With
docchecked against{ name: string, kind: "a" | "b", size: integer | null, owner: { id: integer, email?: string }, meta?: { note: string } | null }, requiredname,kind,sizeandowner:doc -> 'name'unknown | nullstringdoc ->> 'kind'string | null"a" | "b"doc -> 'size'unknown | nullnumber | null(a JSON null, in a non-nulljsonb)doc ->> 'size'string | nullstring | nulldoc -> 'owner' -> 'id'/doc #> '{owner,id}'unknown | nullnumberdoc -> 'meta' ->> 'note'string | nullstring | null(doc -> 'owner' ->> 'id')::intnumber | nullnumberjsonb_build_object('id', doc #> '{owner,id}'){ id: unknown | null }{ id: number }doc #> '{tags,0}',doc -> :keyunknown | nullunknown | nullThe walk over a schema is core's (
schema_type::at_path), over the canonical schema the engine already reads aCHECKinto, so the MySQL and SQLite readers can use it too; this changes all three artifacts' fingerprints. A key read from a typed JSON parameter (:p::jsonb('…') -> 'x') staysunknown: its schema is applied after the analysis that reads the operator.Tests:
oracle/cases/json_schema_keys: 15 statements over rows with a key left out, a JSON null, a nested object that may be null, a key no property names, an array, a value that is an array or an object read at signed and space-led keys, a tagged union, ajsoncolumn and a nullable column, sound over the seeded edge values.json_schema_checks/expression.sqlnow typesdoc -> 'name'asstring.engine/core/src/schema_type.rs(the walk) andengine/postgres/src/infer.rs(which reads are claimed, the literal keys, and the read's nullability).--strict-coverage, the engine's tests, the workspace build with no warning, the three rebuilt artifacts' fingerprints, the loader's typecheck and tests, the docs site.