Skip to content

Releases: commandprompt/plx

plx 2.0.1

Choose a tag to compare

@jdatcmd jdatcmd released this 25 Aug 14:25
184fd03

Packaging only. The extension is unchanged at 2.0.0, so there is no ALTER EXTENSION plx UPDATE for this release and nothing to install if you are already running 2.0.0. Only the distribution metadata changed, which is why the distribution version moved and the extension version did not.

Fixed

  • META.json listed the tag pl/sql, which PGXN rejects, since a Tag may not contain a slash:

    Field /tags[11]: "pl/sql" is not a valid tag [Spec v1.0.0]
    

    It is now plsql. No other tag was at fault.

Added

  • make metacheck (test/check_meta.py) validates META.json against the PGXN Meta Specification v1, and runs in CI. Previously the only thing validating the metadata was PGXN itself, at upload time, which is after a release has been tagged and published.

    Worth recording, because the specification's prose and its schema disagree and the prose is the trap. The text says a Tag may contain no "slash, backslash, control, or space" characters, but that sentence describes a Term. The Tag schema is ^[^/\\\p{Cntrl}]{2,}$, which permits spaces. So sql server and string builder are valid tags that would be invalid terms, and the slash was the only real fault. The validator implements the schema rather than the prose.

Verification

CI green on PostgreSQL 13 through 18, with the new metadata check running ahead of the regression suite. On 18.4: 13/13 installcheck.

plx 2.0.0

Choose a tag to compare

@jdatcmd jdatcmd released this 25 Aug 00:46
cecb6d1

Major release for one behaviour change. Upgrade with ALTER EXTENSION plx UPDATE TO '2.0.0' after installing the new module.

Interpolating a NULL now propagates it

An interpolated value is concatenated as-is, so the whole string becomes NULL the way SQL || does. Previously every interpolated value was wrapped in COALESCE((x)::text, ''), so a missing value silently became a plausible-looking one:

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

This 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. The damage case is not a log line but a value that gets stored, where there is no error at any stage and every test that does not specifically exercise NULL passes.

Affects 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 is the exception and keeps each interpolated value's empty-string fallback, so one NULL cannot swallow a diagnostic and the literal text of the message survives.

plxgo's fmt.Sprintf is now lowered to a SQL concatenation rather than format(), which is what allows the propagation, since format() renders a NULL operand as empty and cannot be made to do otherwise. Observable output is otherwise unchanged: verbs render their operand as text, a - flag and a width still pad, other flags and the precision field are still dropped, %% is still a literal percent.

Upgrading

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. The new behaviour arrives the next time that function's DDL is run, which for most installations means the next deployment.

To find the functions a redeployment will change:

SELECT p.oid::regprocedure
  FROM pg_proc p JOIN pg_language l ON l.oid = p.prolang
 WHERE l.lanname LIKE 'plx%'
   AND EXISTS (SELECT 1
                 FROM regexp_split_to_table(p.prosrc, E'\n') AS ln
                WHERE ln LIKE '%COALESCE((%)::text, ''''%'
                  AND ln NOT LIKE '%RAISE %')
 ORDER BY 1;

Each result interpolates a value that will become NULL when the value is NULL. Where the old rendering was wanted, make it explicit with coalesce(x, '') in the body.

Verification

CI green on PostgreSQL 13 through 18. On 18.4: clean build with no warnings, 13/13 installcheck, and the differential check at 554 matching, 17 documented, 0 unexplained. The upgrade was exercised from a real 1.3.2 install, and the claim that catalogued functions are unaffected 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.

plx 1.3.2

Choose a tag to compare

@jdatcmd jdatcmd released this 24 Aug 23:50
3f74de2

Code-only patch release (no catalog changes). Upgrade with ALTER EXTENSION plx UPDATE TO '1.3.2' after installing the new module.

Fixed

  • plxgo fmt.Sprintf. In expression position fmt.Sprintf passed its Go format string straight into SQL format(), which understands only %s, %I, %L and %%. Every other verb raised unrecognized format() type specifier when the function was called. The transpile succeeded and the failure only appeared at run time, so fmt.Sprintf("%d", n), the ordinary way to format an integer in Go, produced a function that could not run. Go's verbs are now rewritten to the %s that format() understands, keeping a - flag and a width. A % that starts no directive is escaped rather than passed through, since a lone % is itself an error to format().

    The verbs that change an operand's representation rather than its padding render what %s renders, so fmt.Sprintf("%x", 255) yields 255 and not ff. Convert explicitly where the representation matters. Recorded in the Go chapter and in the limitations page.

Added

  • make differentialcheck. Each case is one small program written once as a plpgsql reference and once per dialect, then called with the same arguments. The reference is the plpgsql a PostgreSQL developer would have written for the same logic, so a disagreement is a defect rather than a difference of opinion. Values are compared by text form with NULL distinguished from the empty string, and a case that raises must raise the same SQLSTATE. Intended divergences are recorded per case with a reason, and the check fails if a recorded divergence stops happening, so a documented limitation cannot outlive its documentation. This is what found the Sprintf bug above.
  • A migration page, comparing plx against rewriting by hand, an embedded PL, ora2pg, and leaving the logic in the application, including how to leave plx while keeping the generated plpgsql: https://commandprompt.github.io/plx/MIGRATION/

Changed

  • The internal transpiler refactor behind a PlxSurface.parse_body vtable, and its follow-up cleanups. No functional change; generated plpgsql is byte-identical.

Verification

CI green on PostgreSQL 13 through 18. On 18.4: clean build with no warnings, 13/13 installcheck, differentialcheck at 374 matching and 14 documented. ALTER EXTENSION plx UPDATE TO '1.3.2' from 1.3.1 applies and carries the fix, and a fresh CREATE EXTENSION plx installs 1.3.2.

plx 1.3.1

Choose a tag to compare

@jdatcmd jdatcmd released this 16 Jul 23:08

Code-only patch release (no catalog changes) carrying the memory-safety and robustness fixes from the full-repo transpiler audit (#1). Upgrade with ALTER EXTENSION plx UPDATE TO '1.3.1' after installing the new module.

Fixed

  • Missing capacity guard on the trailing T_EOF token write in lex() (heap overflow / SIGSEGV when a source lexed to exactly cap-1 tokens).
  • plx_strbuild sb_ensure() int32 doubling could wrap negative near ~1GB and spin while passing a bogus size to repalloc; growth is now 64-bit and clamped to MaxAllocSize.
  • The recursion-guarded transpiler entries now call check_stack_depth(), so deeply nested input raises a clean error (honoring max_stack_depth) instead of crashing the backend.
  • The Python lexer raises a clean "indentation nested too deeply" error at the indent-stack limit instead of emitting an unbalanced T_INDENT.
  • Raw single-quoted strings in Ruby and PHP keep backslashes literal (only \\ and \' are special).
  • PHP ${name} curly interpolation in double-quoted strings is now recognized.
  • Non-decimal integer literals (0x/0o/0b, with _ separators) are lexed as one token and rewritten to decimal (portable to PG13-15); a >64-bit literal raises a clean "integer literal out of range" error.

Other

  • Declare the built-in dialect descriptors in plx.h, clearing -Wmissing-variable-declarations.

Verified against PostgreSQL 18.4: clean build, all 13 installcheck tests pass, and the 1.3.0 → 1.3.1 upgrade applies cleanly.

plx 1.3.0

Choose a tag to compare

@jdatcmd jdatcmd released this 15 Jul 23:40

plx 1.3.0 makes trigger row mutation available across the dialects and ships a verified cookbook for every dialect.

Added

  • plxtsql trigger row mutation. A Transact-SQL trigger can now assign to NEW fields with SET NEW.col = e, which lowers to NEW.col := e, so a trigger can rewrite the row and not only validate it. A SET with a qualified target and a top-level = is an assignment; SET NOCOUNT ON and other session options are still ignored. With the plxphp arrow assignment added in 1.2.2, assigning to a trigger's NEW fields is now supported across the dialects, each in its own idiom.
  • Cookbooks and limitations docs. A verified, runnable cookbook for each of the nine dialects (scalar functions, loops, string building, query loops, set-returning functions, error handling, triggers, dynamic SQL, and dialect idioms), and a consolidated gaps-and-limitations page. Both are on the documentation site.

No catalog changes. All 13 regression suites pass on PostgreSQL 13 through 18, plus 19beta and 20devel built from source.

Upgrade

Install the new module (make && make install), then:

ALTER EXTENSION plx UPDATE TO '1.3.0';

A fresh CREATE EXTENSION plx lands on 1.3.0 directly. The PGXN distribution zip (plx-1.3.0.zip) is attached.

plx 1.2.2

Choose a tag to compare

@jdatcmd jdatcmd released this 15 Jul 23:02

A code-only patch release.

Fixed

  • plxphp: assigning to a record field with the arrow form ($NEW->col = e), the idiomatic PHP spelling, previously raised "unsupported operator in statement". It now lowers to NEW.col := e, so a trigger function can stamp NEW fields with $NEW->col = e. The array-element form ($NEW['col'] = e) continues to work.

No catalog changes. The full 13-suite regression passes on PostgreSQL 13 through 18, plus 19beta2 and 20devel built from source.

Upgrade

Install the new module (make && make install), then:

ALTER EXTENSION plx UPDATE TO '1.2.2';

A fresh CREATE EXTENSION plx lands on 1.2.2 directly. The PGXN distribution zip (plx-1.2.2.zip) is attached.

See CHANGELOG.md.

plx 1.2.1

Choose a tag to compare

@jdatcmd jdatcmd released this 15 Jul 21:19

A code-only patch release: plx now builds on PostgreSQL 19 and 20.

Fixed

  • Build on PostgreSQL 19 and 20 with a C23 toolchain (for example gcc 15). There, PostgreSQL defines pg_noreturn as the standard [[noreturn]] attribute, whose placement is strict. plx wrote it after the storage class (static pg_noreturn void ...), which C23 rejects, so plx did not compile on 19/20. It is now the first token of each declaration (pg_noreturn static void ...), matching PostgreSQL's own convention, and still compiles on 13 through 18.

The full 13-suite regression passes on PostgreSQL 13 through 18, plus 19beta2 and 20devel built from source. No catalog changes: the only change is in the loadable module.

Upgrade

Install the new module (make && make install), then:

ALTER EXTENSION plx UPDATE TO '1.2.1';

A fresh CREATE EXTENSION plx lands on 1.2.1 directly. The PGXN distribution zip (plx-1.2.1.zip) is attached.

See CHANGELOG.md.

plx 1.2

Choose a tag to compare

@jdatcmd jdatcmd released this 15 Jul 18:27

plx 1.2 makes it nine dialects. Write a PostgreSQL function body in the language you already know; plx transpiles it to plpgsql at CREATE FUNCTION time and the standard plpgsql interpreter runs it. No language runtime is loaded into the backend, and the generated plpgsql is visible in the catalog.

Dialects (9)

plxruby · plxphp · plxjs · plxts (TypeScript) · plxpython3 · plxgo (Go) · plxcobol · plxplsql (Oracle PL/SQL) · plxtsql (Transact-SQL / SQL Server)

New since 1.1

  • Four new dialects: Oracle PL/SQL, Transact-SQL (SQL Server), Go, and TypeScript. The SQL-family and Go dialects are restructuring front ends (own tokenizers/parsers); PL/SQL and TypeScript build on the near-plpgsql and JavaScript paths. See each chapter under doc/.
  • COBOL tables: OCCURS n maps a WORKING-STORAGE item to a PostgreSQL array, with WS-ARR(i) subscripts and PERFORM v OVER ARRAY iteration.
  • plpgsql completeness audit across dialects (OUT/INOUT/RETURNS TABLE/STRICT), a worked examples/ directory, and doc/DEBUGGING.md + a plx_source() helper.
  • Whole-project hardening: a fresh-eyes review of every front end plus mutation fuzzing of all nine dialects fixed real backend crashes, hangs, and wrong/invalid-output cases. The fuzzer (test/fuzz.py) and corpus now cover all nine dialects and are crash- and hang-clean.

Compatibility

All 13 regression suites pass on PostgreSQL 13 through 18 (CI + local matrix). plx_strbuild's in-place fast path (amortized-O(1) string append) is accelerated on PostgreSQL 18; correct but unaccelerated on 13–17.

Install

Build from source (make && make install), then CREATE EXTENSION plx;. Upgrade from an earlier install with ALTER EXTENSION plx UPDATE TO '1.2';. A PGXN distribution zip (plx-1.2.0.zip) is attached.

See CHANGELOG.md for the full list.

plx 1.1.1

Choose a tag to compare

@jdatcmd jdatcmd released this 15 Jul 13:05

plx 1.1.1 is a code-only patch release (no catalog changes). It restores
compilation on PostgreSQL 13-15 and fixes several plxcobol bugs found in review.

Upgrade an existing install with:

ALTER EXTENSION plx UPDATE TO '1.1.1';

Fixed

  • Compilation on PostgreSQL 13, 14, and 15. plx_strbuild.c included
    varatt.h unconditionally, but that header was only split out of postgres.h
    in PostgreSQL 16, so 1.1 did not build on 13-15. The full 9-suite regression
    now passes on PostgreSQL 13 through 18 (verified in CI).
  • plxcobol crash (out-of-bounds read) on a body truncated at
    PERFORM VARYING ... UNTIL; it now errors cleanly.
  • plxcobol ADD a b GIVING c and other multi-addend ADD/SUBTRACT forms
    were rejected; they are parsed as operand lists now.
  • plxcobol multi-argument function calls (mod(a, b)) were broken because
    the tokenizer stripped commas everywhere; commas inside parentheses are kept.
  • plxcobol GREATER/LESS ... OR EQUAL at the end of a condition, a PICTURE
    repeat-count overflow, and unterminated string handling.

Added

  • Continuous integration (GitHub Actions) running the full suite on PostgreSQL
    13 through 18 (green).
  • plxcobol coverage in the fuzzer and corpus runner.
  • A PGXN META.json, and the plx-1.1.1.zip distribution attached to this
    release.

Compatibility

Builds and passes the full regression suite on PostgreSQL 13, 14, 15, 16, 17,
and 18.

plx 1.1

Choose a tag to compare

@jdatcmd jdatcmd released this 15 Jul 02:04

plx 1.1 adds plxcobol, a COBOL dialect (ISO/IEC 1989:2023, COBOL 2023, free
format), at full plpgsql construct parity. As with the other dialects, a COBOL
function body is transpiled to plpgsql at CREATE FUNCTION time and runs on the
standard plpgsql interpreter, with no separate language runtime in the backend.

plxcobol

COBOL is verb-driven and free-format, so it has its own front end (tokenizer and
recursive-descent parser). Data names map to plpgsql identifiers (lower-cased,
hyphens to underscores). Coverage:

  • WORKING-STORAGE declarations, with PICTURE mapped to SQL types, plus TYPE
    (for %ROWTYPE, RECORD, refcursor) and CONSTANT AS.
  • MOVE / COMPUTE and the ADD / SUBTRACT / MULTIPLY / DIVIDE verbs;
    ** is exponent and % is modulo.
  • IF / ELSE / END-IF; EVALUATE (simple and searched EVALUATE TRUE).
  • PERFORM in the UNTIL, VARYING, TIMES, inline, query (OVER "sql") and
    array (OVER ARRAY) forms; EXIT PERFORM [CYCLE].
  • GOBACK RETURNING, RETURN-NEXT, RETURN-QUERY; EXECUTE with USING/INTO;
    cursors (OPEN-CURSOR/FETCH-CURSOR/MOVE-CURSOR/CLOSE-CURSOR).
  • Exception handling (BEGIN-TRY/WHEN/END-TRY) with stacked diagnostics via
    GET; GET ROW-COUNT; FOUND.
  • STRING-APPEND <expr> TO <var>, which lowers to the plx_strbuild string
    builder (amortized-O(1) in-loop string building).
  • RAISE, DISPLAY, ASSERT, CALL, COMMIT/ROLLBACK.

See doc/plxcobol.md
and the parity matrix in
doc/PARITY.md.

Performance

plxcobol transpiles to plpgsql like the other dialects, so it matches plpgsql on
the arithmetic, iteration, string-building, and call workloads; see
bench/BENCHMARKS.md.

Upgrading

Existing 1.0 installations upgrade in place:

ALTER EXTENSION plx UPDATE TO '1.1';

A fresh CREATE EXTENSION plx installs 1.1 directly.

Compatibility

Tested against PostgreSQL 13 through 18; the full regression suite (nine suites,
including plxcobol) passes on each.