Jsonpath and jsonb
Skill matejformanek/postgres-claude/.claude/skills/jsonpath-and-jsonb
PostgreSQL's SQL/JSON support — jsonpath (SQL:2016 path language) + jsonb (binary-encoded JSON) + the SQL/JSON operators like `->`, `->>`, `@?`, `@@`, `jsonb_path_query`. Covers `src/backend/utils/adt/jsonb*.c` (jsonb storage + operators + GIN opclass + subscripting) and `jsonpath*.c` (path language parser + executor). Loads when the user asks about jsonpath semantics, path variables ($, @, current), lax vs strict mode, predicate expressions, `jsonb_path_query` / `jsonb_path_exists`, JSON_TABLE (SQL:2023 / PG 17+), jsonb_gin operator class, memory management in jsonpath_exec (Tom Lane's 5a2043bf713 rewrite), or "why is my JSON search slow" (usually GIN or path complexity). Skip when the ask is about the older `json` type (`json.c` is separate — text representation) or about JSON output formats (that's `format_type` territory).From its SKILL.md
npx -y skills add matejformanek/postgres-claude --skill jsonpath-and-jsonbAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.3 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it
jsonpath-and-jsonb — SQL/JSON path language and binary JSON
PG has TWO JSON types:
json— text storage. Preserves formatting/whitespace/key-order. Slow to query (parsed every access).jsonb— binary storage. Deduped/sorted keys, faster to query, indexable via GIN. Almost always what you want.
Plus jsonpath — a query language type (SQL:2016). Compiled path expressions used by jsonb_path_query, jsonb_path_exists, @@ operator, JSON_TABLE.
The file map
| File | KB | Role |
|---|---|---|
utils/adt/jsonb.c | 47 | Public jsonb operators + I/O — jsonb_in, jsonb_out, jsonb_object, ->, ->>, key existence ?, containment @>. |
utils/adt/jsonb_util.c | 58 | Internal serialization + traversal. JsonbIterator, findJsonbValueFromContainer, packing helpers. Contains ordering/deduplication rules. |
utils/adt/jsonb_gin.c | 35 | GIN opclass for jsonb — extract-value / extract-query / consistent functions. Multiple index shapes: default, path_ops. |
utils/adt/jsonb_op.c | 7 | Small operator wrappers. |
utils/adt/jsonbsubs.c | 12 | Array/object subscripting — jsonb['key'] (PG 14+). |
utils/adt/jsonpath.c | 38 | jsonpath type support — parser (via jsonpath_scan.l + jsonpath_gram.y), I/O, tree walks. |
utils/adt/jsonpath_exec.c | 127 | jsonpath EXECUTOR. executeItem, executePredicate, executeBinaryArithmExpr — traverses the jsonpath tree against a jsonb value. This is where the leak fix landed — see planning/jsonpath_leak/. |
include/utils/jsonb.h, jsonapi.h, jsonpath.h | — | Public APIs. |
jsonb storage layout
A jsonb is a varlena with:
- Header — 4 bytes VARSIZE_ANY + 4 bytes JEntry-array-count for the root container.
- JEntry array — one per top-level element, encoding type + length/offset.
- Serialized values — the raw bytes for scalars, or nested containers with their own JEntry arrays.
- Recursive — a jsonb OBJECT's value is a nested container repeating the pattern.
Keys are sorted lexically (bytewise UTF-8) and deduped (last-wins semantics). This is why:
'{"a": 1, "a": 2}'::jsonbreturns{"a": 2}.->on a large object is O(log n) via binary search on sorted keys.
jsonpath language basics
Path expressions like $.employees[*] ? (@.name == "Alice") mean:
$— the root (the jsonb value passed in)..name— field access.[*]— all array elements.? (predicate)— filter — keep only elements where predicate is true.@— the current element in a filter context.
Two modes:
- lax (default) — missing paths return "no result" instead of erroring. Auto-unwraps arrays. Auto-wraps scalars. Forgiving.
- strict — errors on missing paths / type mismatches. Strict RFC compliance.
Set at query time: SELECT jsonb_path_query(v, 'strict $.a').
The executor's key structures
From jsonpath_exec.c:
JsonPathExecContext— walks state: current position, mode, variables map.JsonValueList— accumulates result values. This is the struct Tom Lane's5a2043bf713redesigned inline to eliminate the executePredicate memory leak. Seeplanning/jsonpath_leak/comparison.mdfor the pattern comparison.JsonPathItem— one node in the compiled path tree.JsonbValue— the value being examined.
The executor does DFS over the jsonpath tree, executing predicates, applying arithmetic, accumulating results into a JsonValueList.
GIN index on jsonb
Two operator classes:
jsonb_ops(default) — index each unique key/value combo. Supports@>,?,?&,?|. Bigger index; more query flexibility.jsonb_path_ops— hash each path-to-value. Only supports@>. Smaller; faster; strict subset.
Choose path_ops for pure containment queries (JSON-Blob search). Choose default for key-existence + containment.
Common patch shapes
Add a new jsonpath function
- Extend
jsonpath_gram.yfor the syntax. - Add case in
executeItem(jsonpath_exec.c) — implement the semantics. - If the function returns multiple values, decide lax vs strict behavior.
- Regress in
src/test/regress/sql/jsonpath.sql.
Add a new jsonb operator
- Add C function in
jsonb.corjsonb_op.c. - pg_operator.dat entry (with commutator / negator / restrict / join fns).
- Consider GIN opclass support — a new operator without index support is a footgun.
Optimize a jsonb query
- EXPLAIN — is the GIN index being used?
- If not:
path_opsmay not support your operator; switch to default. Or your predicate has too much post-index work. - If yes but slow: consider a partial index on the specific path shape.
- Container-emit vs path-emit — the executor may be materializing large intermediate results. Consider strict mode + explicit path.
Fix a jsonpath memory leak
Been done. See planning/jsonpath_leak/:
- The blind-trilogy calibration reproduced Tom Lane's leak fix (5a2043bf713) with a different mechanism but identical RSS envelope (32 MB vs 32 MB).
- The bug pattern:
JsonValueListaccumulated results without per-call context cleanup. Fixes: struct redesign (Tom's) OR per-call AllocSetContext wrap (calibration's approach). Both work. - Lesson: for iterator-style accumulator structs, ownership of the elements matters as much as ownership of the containers.
Pitfalls
jsonvsjsonbtype — pick jsonb — unless you have a specific reason (preserving formatting, key order), always jsonb. json is a "text with syntax check", not a queryable structure.- Key deduplication in jsonb —
'{"a":1,"a":2}'::jsonbsilently keeps the last. Applications that generate jsonb should not rely on duplicate keys. - jsonpath variables need explicit passing —
jsonb_path_query(v, '$var', '{"var": 5}'::jsonb). Beginners often try to embed variables inline. ->returns jsonb;->>returns text — using->when you want a text scalar leaves you with a jsonb-encoded scalar (e.g."hello"with quotes).@?vs@@—@?returns bool (path exists);@@returns bool (path returns truthy). Common mixup.jsonb_path_opsis more restrictive than you think — no?operator, no->(that's the wrong tool anyway).- Deep jsonpath is quadratic — a path like
$.a.b.c...zon a deeply nested jsonb is O(depth × object-size). Consider caching sub-values. - jsonpath_exec.c is a memory hotspot — see
planning/jsonpath_leak/for a real bug fixed at scale (5.7 GB → 32 MB RSS on the reproducer). New iterator-style code paths should apply the per-call context pattern. - JSON_TABLE (PG 17+) is jsonpath at heart — its columns are jsonpath expressions. Same performance considerations.
- JIT doesn't help jsonb operations — jsonpath executor is interpreter-only. JIT compiles ExprState machinery, not the jsonpath tree.
Related corpus
- Idioms:
error-handling(jsonpath errors in strict mode),heap-tuple-decompression-pattern(jsonb is toasted → detoast + traverse). - Subsystems:
utils-mmgr(jsonpath_exec is a memory hotspot — hence the leak),parser-and-rewrite(jsonpath grammar is separate but sits in the same layer). - Scenarios:
add-new-builtin-function(for adding jsonb / jsonpath fns),fix-memory-leak(jsonpath_leak is the canonical calibration). - Planning:
planning/jsonpath_leak/— 3-phase blind calibration reproducing Tom Lane's fix. Readcomparison.mdfor the mechanism comparison (inline storage vs per-call context).
Corpus-chain shortcut
python3 scripts/corpus-chain.py --file src/backend/utils/adt/jsonpath_exec.c
python3 scripts/corpus-chain.py --file src/backend/utils/adt/jsonb.c
python3 scripts/corpus-chain.py --idiom heap-tuple-decompression-pattern
Boundary
Use this skill for jsonb + jsonpath + related operators + JSON_TABLE.
Don't use for:
jsontype (text-based) — separatejson.c; use only for compatibility.hstore— contrib module, different data model.hstore_plpython/hstore_plperl— different transformers.- XML type —
xml.cetc., separate subsystem. - JSON parsing at the SQL boundary — that's
jsonapi.c's pull-based parser, used by many callers; often you don't need to touch it.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.