Contents
Changelog
All notable changes to PL/Ruby are documented in this file.
The format is based on Keep a Changelog, and the project aims to follow Semantic Versioning.
[2.5.0] - 2026-07-06
PL/Python parity: per-function $_SD storage, a raw-binary bytea mapping, SPI
result column metadata, and an ltree transform. With these, PL/Ruby is at or
ahead of PL/Python across the board. The core changes are in the shared library;
ALTER EXTENSION plruby UPDATE completes the upgrade after installing the new
binary. The ltree transform ships as the separate ltree_plruby extension.
Added
ltree_plruby: a companion extension (inltree_plruby/) providingTRANSFORM FOR TYPE ltree: opted-in functions receive ltree arguments as a RubyArrayof label Strings and may return anArrayof labels into an ltree result (elements stringified and joined with.; the empty ltree is the emptyArray). Mirrors the in-coreltree_plpythontransform. Implemented against ltree’s publicltree2text/text2ltreefunctions, so it needs no ltree headers and works with the packaged ltree on PostgreSQL 11-18.$_SD: per-function static storage. AHashprivate to each function that persists across calls to that function within a session, the counterpart of PL/Python’sSD(where$_SHAREDisGD). Each function’s$_SDis independent and resets when the function is recompiled; an anonymousDOblock gets a fresh, empty$_SDeach run.- SPI result column metadata.
spi_colnames(result),spi_coltypes(result), andspi_coltypmods(result)return parallelArrays of a result’s column names, type OIDs, and type modifiers, the counterparts of PL/Python’scolnames/coltypes/coltypmods. A non-SELECTresult returns emptyArrays.
Changed
byteamaps to a binaryString, not hex text (breaking). Abyteavalue now reaches Ruby as a binary (ASCII-8BIT)Stringof its raw bytes, NUL-safe, instead of its\x...hex text; returning aStringinto abyteatakes the string’s raw bytes verbatim, with no hex/escape parsing. This matches PL/Python’sbytea<->bytesmapping and applies wherever abyteacrosses between SQL and Ruby (arguments, returns,bytea[]elements, composite fields, and SPI result rows). Function bodies that previously produced or consumed the hex text form must be updated (e.g. build bytes with[..].pack('C*')rather than assembling a\xstring).
[2.4.0] - 2026-07-06
Feature parity with the sibling PL/php 2.4: an on_init hook, the
anycompatible polymorphics, and error CONTEXT lines. All plruby changes are
in the shared library; ALTER EXTENSION plruby UPDATE completes the upgrade
after installing the new binary.
Added
plruby.on_init: a snippet of Ruby source evaluated at the top level when the interpreter is first initialized in a session, beforeplruby_modulesandplruby.start_proc. The counterpart ofplperl.on_init, and the natural place forrequires and helper definitions.anycompatibleandanycompatiblearrayare accepted as argument and return types on PostgreSQL 13 and newer.- Error
CONTEXTlines. An error raised while PL/Ruby code runs now carries aCONTEXT: PL/Ruby function "name"line (orPL/Ruby anonymous code blockfor aDOblock), like every other procedural language.
Changed
- Polymorphic array arguments arrive as Ruby
Arrays. Ananyarray(oranycompatiblearray) argument is now resolved to its concrete type at call time and converts to a RubyArray, instead of arriving as its text form.anyelementarguments likewise arrive as their native Ruby type.
[2.3.0] - 2026-07-05
Inline class definitions, the hstore transform, and transform coverage in
every conversion context, including triggers. All plruby changes are in the
shared library; ALTER EXTENSION plruby UPDATE completes the upgrade after
installing the new binary.
Added
- Inline
class/moduledefinitions in function bodies. Bodies now compile to a top-level lambda (with a delegator method), whereclassis legal; it was a SyntaxError anywhere inside the previousdef-based compilation. Definitions register globally for the session, exactly likeplruby_modulescode, and work in functions, triggers, andDOblocks. - Transforms reach nested contexts. A
TRANSFORM FOR TYPEdeclaration now also applies to fields of composite arguments and results, OUT parameters, array elements (e.g.jsonb[]), andreturn_nextrows, includingRETURNS SETOF jsonb/hstore. SPI results are deliberately unaffected. (The SRF row builder is now datum-based, which also letsreturn_nextrows carry nested composite values.) - Transforms in triggers. A trigger function that declares
TRANSFORM FOR TYPEreceives transformed columns in$_TD['new']/$_TD['old']and may put native Ruby data back via'MODIFY', going beyond PL/Perl, whose transforms skip trigger data. Without the clause, triggers behave exactly as before. hstore_plruby: a companion extension (inhstore_plruby/) providingTRANSFORM FOR TYPE hstore: opted-in functions receive hstore arguments as a RubyHashof String keys to String-or-nilvalues and may return aHashinto an hstore result (keys/values stringified,nilmaps toNULLin both directions). Implemented against hstore’s public SQL functions, so it needs no hstore headers and works with the packaged hstore on PostgreSQL 11-18.
[2.2.0] - 2026-07-05
Native jsonb, Ruby 3.3/3.4 support, and CI. The jsonb_plruby companion extension
exchanges jsonb with native Ruby data; RubyGems is enabled in the embedded
interpreter (restoring csv/bigdecimal/base64 on Ruby 3.4 and making
installed gems requirable); every push is now verified by GitHub Actions on
PostgreSQL 12-18 and Ruby 3.2/3.3/3.4 (PG 11 verified out-of-band). All
plruby changes are in the shared library; ALTER EXTENSION plruby UPDATE
completes the upgrade after installing the new binary.
Added
jsonb_plruby: a companion extension (injsonb_plruby/) providingTRANSFORM FOR TYPE jsonb: opted-in functions receive jsonb arguments as native Ruby data (Hash/Array/String/Integer/Float/booleans/nil) and return Ruby data into jsonb directly. Integers beyondFloatprecision andBigDecimalvalues serialize exactly.- Structured
pg_raise:pg_raise(level, message, detail:, hint:, sqlstate:)maps the keywords onto the correspondingereportfields (like PL/pgSQL’sRAISE ... USING); a PL/Ruby caller that rescues the resulting error reads them back viaPLRuby::Error#detail/#hint/#sqlstate. PLRuby::Error#detail/#hint: a caught database error now carries itsDETAILandHINTalongside the SQLSTATE.- Streaming
spi_query_prepared: executes a prepared plan through a cursor (block form andPLRuby::Cursorhandle form) instead of materializing; the plan stays reusable after the cursor closes. It was previously an alias ofspi_exec_prepared. - Regression suite 35 to 37 tests (
replace: mid-sessionCREATE OR REPLACErecompilation; extendedmisc/sqlstate/prepare), plus thejsonb_plrubysuite. - CI (GitHub Actions): every push and pull request builds both extensions and runs both suites on PostgreSQL 12-18 (system Ruby 3.2) and on Ruby 3.3/3.4 (PostgreSQL 18).
- Ruby 3.3 and 3.4 support: alternate expected files cover their
changed
NoMethodError/Hash#inspectoutput; validation errors are reported as a single line (Ruby 3.4’s parser quotes the generated wrapper source over many lines); the cookbook’s token recipe encodes withArray#pack('m0')instead of thebase64library.
Changed
- Domain arguments arrive as the base type’s Ruby value (a domain over
intis anInteger, overint[]anArray, …); previously they arrived as the text-form String. - RubyGems is enabled in the embedded interpreter (was
--disable-gems): Ruby 3.4 shipscsv,bigdecimal, andbase64as bundled gems, which plainrequirecannot see without it, and installed gems become requirable as a side benefit.did_you_mean/error_highlightare disabled so error messages stay deterministic. Verified on Ruby 3.2, 3.3, and 3.4 (CI covers all three).
[2.1.0] - 2026-07-05
Feature and hardening release: broader trigger and argument-mode coverage,
error objects carrying SQLSTATE, streaming SPI, a tested cookbook, and a regression
suite grown to 35 tests verified on PostgreSQL 11 through 18. All changes
are in the shared library; ALTER EXTENSION plruby UPDATE completes the
upgrade after installing the new binary.
Added
Expanded regression suite: 20 to 28 tests, verified on PostgreSQL 11, 12, 13, 14, 16, and 18 with Ruby 3.2. New coverage:
byteaconversion, special numeric/float values (NaN,±Infinity, integer overflow), nested and array-bearing composites, SPI DML status codes and NULL columns, DELETE-trigger$_TD['old']handling, the error/ensuremodel, prepared-plan reuse, standard-libraryrequire,$stdout/$stderrredirection, quoting helpers, andplruby.start_proc.Cursor streaming:
spi_query(sql)opens a portal and reads rows a batch at a time, so large results stream without materializing. Block form (spi_query(sql) { |row| ... }), handle form (spi_fetchrow/spi_cursor_close), andPLRuby::Cursor#each(Enumerable).- Broader string encoding: text is tagged with the Ruby encoding matching the database encoding (LATIN, WIN, EUC*, SJIS, KOI8, Big5, GBK, …), not just UTF-8; unmapped encodings fall back to ASCII-8BIT.
PLRuby::Error#sqlstate: a caught PostgreSQL error now carries its five-characterSQLSTATE(e.g.42P01,22012) on the Ruby exception; it isnilon a PL/Ruby error not backed by a database error.VARIADICarguments: the variadic tail arrives as a single RubyArrayargument (VARIADIC "any"remains unsupported).INSTEAD OFtriggers on views:$_TD['when']isINSTEAD OF, with the samenil/'SKIP'/'MODIFY'return handling as BEFORE row triggers.- Cookbook:
doc/cookbook.md, recipes built on Ruby’s stdlib (JSON reshaping, HMAC/PBKDF2, audit trigger, CSV, Zlib-to-bytea, BigDecimal, slugify, ERB, streaming scans, batch commits); every recipe in the “tested” section runs verbatim in the regression suite (cookbooktest). - Regression suite 28 to 35 tests, adding
datetime(date/time/timestamp/ interval),jsonb,misc(uuid/inet/enum/domain),variadic,trigger2(INSTEAD OF,WHENclauses, deferred constraint triggers, composite-column'MODIFY'),hostile(mid-SRF errors, cursor misuse, ~1MB TOAST values, prepared-plan edge cases), andcookbook.
Fixed
- Nested composites are now built and read recursively: a composite field
whose value is a
Hashbecomes a sub-record (to any depth), and a function may return an array of composite. A nested composite argument arrives as a nestedHash. (The tuple builder inplruby_io.cis now datum-based.) TRUNCATEtriggers are now dispatched:$_TD['event']isTRUNCATE, statement-level, return value ignored.spi_freeplannow invalidates the plan handle; reusing a freed plan raises a clear PL/Ruby error instead of silently failing.- Trigger
'MODIFY'with composite columns: the modified-tuple path is now datum-based, so assigning aHashto a composite-typed field of$_TD['new']works (it previously failed with “malformed record literal”). spi_fetchrowafterspi_cursor_closenow returnsnil; previously it could keep returning rows left in the cursor’s prefetch buffer.
Known limitations
- A function body compiles to a method, so a
class/moduledefinition cannot appear inline in the body (use aplruby_modulesmodule instead).
[2.0.0] - 2026-07-01
The initial release of the modernized PL/Ruby: an MRI Ruby interpreter embedded
in PostgreSQL, packaged as a CREATE EXTENSION and offering the same feature
set as PL/php. Tested on PostgreSQL 11 through 18 with Ruby 3.2 (shared
library, non-thread-safe embedding).
Added
- Functions with native Ruby argument and return types:
Integer,Float,true/false,String,nil, nestedArray(multidimensional PostgreSQL arrays), and composite/record types asHash. - Set-returning functions:
RETURNS SETOFandRETURNS TABLE(...)withreturn_next(both explicit-value and no-argument forms, the latter reading the TABLE/OUT column locals from the running body’s binding). - Argument modes:
IN,OUT,INOUT,TABLE, and named parameters aliased as local variables. - Trigger functions:
RETURNS triggerwith the$_TDhash andnil/'SKIP'/'MODIFY'return semantics. - Event trigger functions:
RETURNS event_trigger, with$_TD['event']and$_TD['tag']. - Anonymous
DOblocks:DO $$ ... $$ LANGUAGE plruby. - Database access (SPI):
spi_exec,spi_fetch_row,spi_processed,spi_status,spi_rewind. - Prepared statements:
spi_prepare,spi_exec_prepared,spi_query_prepared, andspi_freeplan. - Transaction control in procedures:
spi_commitandspi_rollback. - Explicit subtransactions:
subtransaction { ... }andsubtransaction(callable, ...), rolling back and re-raising on any exception. - Quoting helpers:
quote_literal,quote_nullable,quote_ident. elog(level, message)supportingDEBUG/LOG/INFO/NOTICE/WARNING/ERROR, and the narrowerpg_raise(level, message).$_SHARED: a hash persisting across calls within a session; standard output (puts,print) is forwarded to the server log.- Session initialization: module autoloading from a
plruby_modulestable and aplruby.start_procconfiguration setting. - UTF-8 string handling: text from the database is tagged with the
database encoding (UTF-8 when the database is UTF-8), so multibyte Ruby String
operations (
length,reverse, regexp, …) behave correctly. - Packaging as a first-class extension (
CREATE EXTENSION plruby) and a regression suite of 20 tests covering every feature, including dedicated type-mapping (types) and error-handling (errors) unit tests. - Documentation: a language reference (
doc/plruby.md), a combined PL/Ruby vs PL/php vs PL/Perl vs PL/Tcl feature matrix (doc/comparison.md), and the individual PL/Perl and PL/Tcl comparisons.
License
- Released under the MIT License (see
LICENSE).
Security
- Ruby’s
$SAFElevels and object tainting were removed in Ruby 3.0, so PL/Ruby cannot be sandboxed. It is an untrusted, superuser-only language, created without theTRUSTEDattribute; only superusers may install the extension or create PL/Ruby functions.