Repository navigation
fix: mark record/aggregate encoders STABLE, not IMMUTABLE - #8
Merged
Merged
Conversation
to_toon, row_to_toon, and the toon_agg support functions are built on
to_json/row_to_json, which PostgreSQL itself marks STABLE because their
output depends on session GUCs (TimeZone, DateStyle, extra_float_digits).
The same timestamptz encodes differently across sessions:
SET TIME ZONE 'UTC';
SELECT row_to_toon(q) FROM (SELECT '2024-01-01 00:00+00'::timestamptz AS t) q;
-- t: "2024-01-01T00:00:00+00:00"
SET TIME ZONE 'America/New_York'; -- same input:
-- t: "2023-12-31T19:00:00-05:00"
Declaring them IMMUTABLE lets the planner constant-fold across GUC
changes and, worse, permits expression indexes that silently return
wrong results when read under a different TimeZone/DateStyle.
The pure string helpers (toon_escape, toon_quote_key, toon_quote_value,
toon_encode_field) are genuinely immutable and keep IMMUTABLE. A
regression test now pins the volatility of every function.
Fixes #2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEYjgkWDYiRMYwwRS985g5
# Conflicts: # AGENTS.md
xzilla
pushed a commit
that referenced
this pull request
Oct 1, 2026
Resolve conflicts with #8 (volatility), #9 (delimiter validation) and #10 (NaN docs), which landed on master first: - toon_agg_sfunc: keep this branch's text[] state, mark it STABLE per #8, and run #9's delimiter guard before the NULL-record skip so an invalid delimiter raises even when the row is NULL. - Volatility test: replace toon_agg_sfunc_default with the functions this branch adds (rows_to_toon, toon_agg_rows_ffunc, toon_delim_ok, toon_rows_ok). - AGENTS.md: list the new aggregate helpers in place of the removed toon_agg_state / sfunc_default entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014JAaWhjV4FjNThbcAAWksS
xzilla
added a commit
that referenced
this pull request
Oct 1, 2026
toon_agg was O(n²) for two reasons: PostgreSQL flattens and re-expands SQL/plpgsql transition state on every call, and the finalizer built its output with a per-row concat loop. The default toon_agg(anycompatible) now collects rows with the C transition pg_catalog.array_append and encodes once at finalization via a new public rows_to_toon(anyarray, delim), a single set-based pass over json_array_elements(array_to_json(rows)). The 2-arg toon_agg(q, delim) keeps a plpgsql transition (text[] state) and stays quadratic; rows_to_toon(array_agg(q), delim) is the documented linear alternative. The toon_agg_state type is removed. NULL records are now skipped rather than NULL-poisoning the result, and [N] counts only encoded rows. Integrates the delimiter guard from #9 and STABLE volatility from #8 into the new transition function. Fixes #4.
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.
Fixes #2.
to_toon,row_to_toon, and thetoon_aggsupport functions wrapto_json/row_to_json, which PostgreSQL marks STABLE because their output depends onTimeZone,DateStyle, andextra_float_digits. Same input, different output across sessions — verified on PG 16.13. Declaring them IMMUTABLE permits expression indexes that silently return wrong results when read under different GUCs.The pure string helpers (
toon_escape,toon_quote_key,toon_quote_value,toon_encode_field) stay IMMUTABLE. A catalog-based regression test pins the volatility of every function (by explicit name list, so unrelated schema objects can't break it). AGENTS.md volatility guidance updated.Release note for users:
CREATE INDEX ... (to_toon(col))and similar expression indexes / generated columns will now fail with functions in index expression must be marked IMMUTABLE. That's intentional — such indexes were wrong-results hazards.Merge order: if #11 (perf) merges first, the volatility test's function list needs its new function names added — happy to rebase either way. Suite: 68/68.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEYjgkWDYiRMYwwRS985g5