Skip to content

Release 2.0.0: interpolating a NULL propagates it - #10

Merged
jdatcmd merged 1 commit into
masterfrom
release/2.0.0
Aug 25, 2026
Merged

Release 2.0.0: interpolating a NULL propagates it#10
jdatcmd merged 1 commit into
masterfrom
release/2.0.0

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Third of three. Ships the behaviour change from #8 and #9.

Why major

One behaviour change, no catalog change: interpolating a NULL propagates it instead of rendering an empty string.

-- return "#{country}-#{region}-#{code}" with a NULL region
--   before: 'US--123'   a string that inserts, indexes and joins like real data
--   now:    NULL

Affects plxruby, plxphp, plxjs, plxts, plxpython3, plxgo. plxplsql and plxtsql already propagated. plxcobol is unchanged (the plx_strbuild accumulator's non-strict append is deliberate and separately tested). A RAISE message keeps its empty-string fallback so one NULL can't swallow a diagnostic.

The upgrade is not as alarming as it sounds

ALTER EXTENSION plx UPDATE TO '2.0.0' does not change any function that already exists. plx transpiles at CREATE FUNCTION time and stores the result in pg_proc.prosrc, so a catalogued function keeps the plpgsql it was created with. The new behaviour arrives when its DDL is next run.

Verified rather than assumed — built 1.3.2, created a function, installed 2.0.0:

created under 1.3.2              'user  has 3 items'
existing function after upgrade  'user  has 3 items'   <- unchanged
after recreate                   NULL                  <- new behaviour

So the risk window is a redeployment, not the upgrade. The CHANGELOG carries a query to find the affected functions ahead of that, tested with a positive and a negative control:

flagged_as_affected
-------------------
 aff_value(text)        <- value-position interpolation
                        <- aff_msgonly(text) correctly NOT flagged

The negative control matters: a function whose only COALESCE is in a RAISE message is not affected, and a naive prosrc LIKE '%COALESCE%' would wrongly flag it.

Mechanics

  • plx.control default_version and META.json to 2.0.0
  • plx--2.0.0.sql, identical to 1.3.2 since no SQL object changes
  • plx--1.3.2--2.0.0.sql, version-advancing, with a comment explaining that catalogued functions are unaffected
  • CHANGELOG [2.0.0] with the change, the reasoning, and the upgrade query

Verification on PG18.4

Clean build, 13/13 installcheck, differentialcheck 554 matching, 17 documented, 0 unexplained, 0 stale.

before          1.3.2
after_upgrade   2.0.0
ok              'user bob has 3 items'
null_propagates NULL
fresh_default   2.0.0

🤖 Generated with Claude Code

Major release for one behaviour change. Interpolating a NULL rendered it as an
empty string, because every interpolated value was wrapped in
COALESCE((x)::text, ''). A missing value therefore became a plausible-looking
string rather than a missing one, which is the wrong default for a tool whose
promise is that a body becomes the plpgsql you would have written by hand.
Nobody writes that COALESCE.

An interpolated value is now concatenated as-is, so the whole string becomes
NULL the way SQL || does, in plxruby, plxphp, plxjs, plxts, plxpython3 and
plxgo. plxplsql and plxtsql already propagated. plxcobol builds strings through
the plx_strbuild accumulator, whose append treats a NULL as nothing to append by
design, and is unchanged.

A message built for RAISE keeps each value's empty-string fallback, so one NULL
cannot swallow a diagnostic.

No catalog changes, so plx--2.0.0.sql is identical to 1.3.2 and the update
script only advances the recorded version. The major bump is for the behaviour.

Worth stating plainly in the notes, because it changes how alarming this
release is: the update does not change any function that already exists. plx
transpiles at CREATE FUNCTION time and stores the result in pg_proc.prosrc, so
a catalogued function keeps the plpgsql it was created with and takes the new
behaviour only when its DDL is next run. That was verified by building 1.3.2,
creating a function, installing 2.0.0, and confirming the existing function
still returned the old value while a recreation returned NULL. The CHANGELOG
carries a query that finds the functions a redeployment will change, verified
to flag a value-position interpolation and to exclude one whose only COALESCE
is in a RAISE message.

Verified against PG18.4: clean build, 13/13 installcheck, differentialcheck 554
matching with 0 unexplained and 0 stale, the 1.3.2 -> 2.0.0 update applies and
carries the new behaviour, and a fresh CREATE EXTENSION installs 2.0.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jdatcmd
jdatcmd merged commit cecb6d1 into master Aug 25, 2026
6 checks passed
@jdatcmd
jdatcmd deleted the release/2.0.0 branch August 25, 2026 00:46
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.

1 participant