Skip to content

Apply jsonb/hstore transforms in nested contexts (triggers, composites, SETOF)#30

Merged
jdatcmd merged 1 commit into
masterfrom
plphp-nested-transforms
Jul 6, 2026
Merged

Apply jsonb/hstore transforms in nested contexts (triggers, composites, SETOF)#30
jdatcmd merged 1 commit into
masterfrom
plphp-nested-transforms

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #29. Closes the one item deferred there: a TRANSFORM FOR TYPE (jsonb_plphp / hstore_plphp) previously applied only to a function's top-level scalar arguments and result. Now it also converts values of that type nested inside rows, in both directions — matching PL/Ruby.

Built and tested on PostgreSQL 18 / PHP 8.3; all suites green (25 plphp, jsonb_plphp, hstore_plphp).

What now transforms

FROM SQL (value → PHP):

  • a trigger's $_TD['new'] / $_TD['old'] columns
  • composite argument fields

TO SQL (PHP → value):

  • composite / record result fields
  • RETURNS SETOF / return_next rows (including RETURNS SETOF hstore)
  • RETURNS TABLE(...) columns
  • the row returned by a trigger's 'MODIFY'

Unchanged: rows read back through SPI (spi_exec/spi_fetch_row/cursors) stay in text form, exactly as PL/Ruby leaves them.

How

  • The function's declared transform types (pg_proc.protrftypes) are now resolved for every function (triggers included) and stored on the compiled descriptor (lang_oid + trftypes).
  • A small plphp_trans_ctx (language + declared types) is threaded explicitly into the tuple builders — not via a global — so the SPI path can opt out by passing no context. The SRF return_next path uses a current_trans_ctx set alongside the existing current_* SRF globals.
  • FROM-side columns call the FromSQL transform directly (Datum → zval).
  • TO-side columns run the ToSQL transform to a Datum and render it to text, so the existing BuildTupleFromCStrings path re-parses it — no rewrite of the tuple-building mechanism, keeping the change low-risk. Non-transform columns are byte-for-byte unchanged.

Testing

New nested-context cases added to both hstore_plphp and jsonb_plphp regression suites (trigger MODIFY, composite arg/result, SETOF, TABLE, plus an assertion that SPI-read rows are not transformed). Expected files generated from real runs. No existing expected output changed.

🤖 Generated with Claude Code

…s, SETOF)

Previously a declared TRANSFORM FOR TYPE applied only to a function's top-level
scalar arguments and result.  Now it also converts values of that type nested
inside rows, in both directions:

- FROM SQL: a trigger's $_TD['new']/['old'] columns and composite argument
  fields arrive as native PHP values (e.g. jsonb/hstore as PHP arrays).
- TO SQL: composite/record result fields, RETURNS SETOF / return_next rows,
  RETURNS TABLE columns, and the 'MODIFY' trigger return are built from native
  PHP values.

Mechanism: the function's declared transform types (protrftypes) are stored on
the compiled descriptor for every function (triggers included) and threaded
into the tuple builders as an explicit plphp_trans_ctx.  FROM-side columns are
converted by calling the FromSQL transform directly; TO-side columns run the
ToSQL transform to a Datum and render it to text for the existing
BuildTupleFromCStrings path (no tuple-mechanism rewrite).  Rows read back
through SPI are passed a NULL context and stay in text form, matching PL/Ruby.

Verified on PostgreSQL 18: new nested-context cases in both hstore_plphp and
jsonb_plphp, plus the full plphp suite -- all green (25 / 1 / 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jdatcmd jdatcmd merged commit bc9dd4d into master Jul 6, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants