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}'::jsonb returns {"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's 5a2043bf713 redesigned inline to eliminate the executePredicate memory leak. See planning/jsonpath_leak/comparison.md for 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.y for 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.c or jsonb_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_ops may 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:
JsonValueList accumulated 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
json vs jsonb type — 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}'::jsonb silently 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_ops is more restrictive than you think — no ? operator, no -> (that's the wrong tool anyway).
- Deep jsonpath is quadratic — a path like
$.a.b.c...z on 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. Read comparison.md for 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:
json type (text-based) — separate json.c; use only for compatibility.
hstore — contrib module, different data model.
hstore_plpython / hstore_plperl — different transformers.
- XML type —
xml.c etc., 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.
1---2name: jsonpath-and-jsonb3description: 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).4---56# jsonpath-and-jsonb — SQL/JSON path language and binary JSON78PG has TWO JSON types:910- **`json`** — text storage. Preserves formatting/whitespace/key-order. Slow to query (parsed every access).11- **`jsonb`** — binary storage. Deduped/sorted keys, faster to query, indexable via GIN. Almost always what you want.1213Plus **`jsonpath`** — a query language type (SQL:2016). Compiled path expressions used by `jsonb_path_query`, `jsonb_path_exists`, `@@` operator, `JSON_TABLE`.1415## The file map1617| File | KB | Role |18|---|---:|---|19| `utils/adt/jsonb.c` | 47 | Public `jsonb` operators + I/O — `jsonb_in`, `jsonb_out`, `jsonb_object`, `->`, `->>`, key existence `?`, containment `@>`. |20| `utils/adt/jsonb_util.c` | 58 | Internal serialization + traversal. `JsonbIterator`, `findJsonbValueFromContainer`, packing helpers. Contains ordering/deduplication rules. |21| `utils/adt/jsonb_gin.c` | 35 | GIN opclass for jsonb — extract-value / extract-query / consistent functions. Multiple index shapes: default, path_ops. |22| `utils/adt/jsonb_op.c` | 7 | Small operator wrappers. |23| `utils/adt/jsonbsubs.c` | 12 | Array/object subscripting — `jsonb['key']` (PG 14+). |24| `utils/adt/jsonpath.c` | 38 | jsonpath type support — parser (via `jsonpath_scan.l` + `jsonpath_gram.y`), I/O, tree walks. |25| `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/`. |26| `include/utils/jsonb.h`, `jsonapi.h`, `jsonpath.h` | — | Public APIs. |2728## jsonb storage layout2930A jsonb is a varlena with:3132- **Header** — 4 bytes VARSIZE_ANY + 4 bytes JEntry-array-count for the root container.33- **JEntry array** — one per top-level element, encoding type + length/offset.34- **Serialized values** — the raw bytes for scalars, or nested containers with their own JEntry arrays.35- **Recursive** — a jsonb OBJECT's value is a nested container repeating the pattern.3637Keys are sorted lexically (bytewise UTF-8) and deduped (last-wins semantics). This is why:38- `'{"a": 1, "a": 2}'::jsonb` returns `{"a": 2}`.39- `->` on a large object is O(log n) via binary search on sorted keys.4041## jsonpath language basics4243Path expressions like `$.employees[*] ? (@.name == "Alice")` mean:4445- `$` — the root (the jsonb value passed in).46- `.name` — field access.47- `[*]` — all array elements.48- `? (predicate)` — filter — keep only elements where predicate is true.49- `@` — the current element in a filter context.5051Two modes:5253- **lax** (default) — missing paths return "no result" instead of erroring. Auto-unwraps arrays. Auto-wraps scalars. Forgiving.54- **strict** — errors on missing paths / type mismatches. Strict RFC compliance.5556Set at query time: `SELECT jsonb_path_query(v, 'strict $.a')`.5758## The executor's key structures5960From `jsonpath_exec.c`:6162- **`JsonPathExecContext`** — walks state: current position, mode, variables map.63- **`JsonValueList`** — accumulates result values. **This is the struct Tom Lane's `5a2043bf713` redesigned inline** to eliminate the executePredicate memory leak. See `planning/jsonpath_leak/comparison.md` for the pattern comparison.64- **`JsonPathItem`** — one node in the compiled path tree.65- **`JsonbValue`** — the value being examined.6667The executor does DFS over the jsonpath tree, executing predicates, applying arithmetic, accumulating results into a `JsonValueList`.6869## GIN index on jsonb7071Two operator classes:7273- **`jsonb_ops`** (default) — index each unique key/value combo. Supports `@>`, `?`, `?&`, `?|`. Bigger index; more query flexibility.74- **`jsonb_path_ops`** — hash each path-to-value. Only supports `@>`. Smaller; faster; strict subset.7576Choose `path_ops` for pure containment queries (JSON-Blob search). Choose default for key-existence + containment.7778## Common patch shapes7980### Add a new jsonpath function8182- Extend `jsonpath_gram.y` for the syntax.83- Add case in `executeItem` (jsonpath_exec.c) — implement the semantics.84- If the function returns multiple values, decide lax vs strict behavior.85- Regress in `src/test/regress/sql/jsonpath.sql`.8687### Add a new jsonb operator8889- Add C function in `jsonb.c` or `jsonb_op.c`.90- pg_operator.dat entry (with commutator / negator / restrict / join fns).91- Consider GIN opclass support — a new operator without index support is a footgun.9293### Optimize a jsonb query9495- EXPLAIN — is the GIN index being used?96- If not: `path_ops` may not support your operator; switch to default. Or your predicate has too much post-index work.97- If yes but slow: consider a **partial index** on the specific path shape.98- Container-emit vs path-emit — the executor may be materializing large intermediate results. Consider strict mode + explicit path.99100### Fix a jsonpath memory leak101102Been done. See `planning/jsonpath_leak/`:103- The blind-trilogy calibration reproduced Tom Lane's leak fix (5a2043bf713) with a different mechanism but identical RSS envelope (32 MB vs 32 MB).104- The bug pattern: `JsonValueList` accumulated results without per-call context cleanup. Fixes: struct redesign (Tom's) OR per-call AllocSetContext wrap (calibration's approach). Both work.105- Lesson: for iterator-style accumulator structs, ownership of the elements matters as much as ownership of the containers.106107## Pitfalls108109- **`json` vs `jsonb` type — 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.110- **Key deduplication in jsonb** — `'{"a":1,"a":2}'::jsonb` silently keeps the last. Applications that generate jsonb should not rely on duplicate keys.111- **jsonpath variables need explicit passing** — `jsonb_path_query(v, '$var', '{"var": 5}'::jsonb)`. Beginners often try to embed variables inline.112- **`->` returns jsonb; `->>` returns text** — using `->` when you want a text scalar leaves you with a jsonb-encoded scalar (e.g. `"hello"` with quotes).113- **`@?` vs `@@`** — `@?` returns bool (path exists); `@@` returns bool (path returns truthy). Common mixup.114- **`jsonb_path_ops` is more restrictive than you think** — no `?` operator, no `->` (that's the wrong tool anyway).115- **Deep jsonpath is quadratic** — a path like `$.a.b.c...z` on a deeply nested jsonb is O(depth × object-size). Consider caching sub-values.116- **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.117- **JSON_TABLE (PG 17+) is jsonpath at heart** — its columns are jsonpath expressions. Same performance considerations.118- **JIT doesn't help jsonb operations** — jsonpath executor is interpreter-only. JIT compiles ExprState machinery, not the jsonpath tree.119120## Related corpus121122- **Idioms**: `error-handling` (jsonpath errors in strict mode), `heap-tuple-decompression-pattern` (jsonb is toasted → detoast + traverse).123- **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).124- **Scenarios**: `add-new-builtin-function` (for adding jsonb / jsonpath fns), `fix-memory-leak` (jsonpath_leak is the canonical calibration).125- **Planning**: `planning/jsonpath_leak/` — 3-phase blind calibration reproducing Tom Lane's fix. Read `comparison.md` for the mechanism comparison (inline storage vs per-call context).126127## Corpus-chain shortcut128129```130python3 scripts/corpus-chain.py --file src/backend/utils/adt/jsonpath_exec.c131python3 scripts/corpus-chain.py --file src/backend/utils/adt/jsonb.c132python3 scripts/corpus-chain.py --idiom heap-tuple-decompression-pattern133```134135## Boundary136137**Use this skill** for jsonb + jsonpath + related operators + JSON_TABLE.138139**Don't use** for:140- **`json` type (text-based)** — separate `json.c`; use only for compatibility.141- **`hstore`** — contrib module, different data model.142- **`hstore_plpython` / `hstore_plperl`** — different transformers.143- **XML type** — `xml.c` etc., separate subsystem.144- **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.