Skip to content

Commit 4325f84

Browse files
jdatcmdclaude
andcommitted
Release 2.3.0; documentation style pass
Bump the extension to 2.3 (all plruby changes are in the shared library, so plruby--2.2--2.3.sql is a no-op version move) and date the changelog. Documentation cleanup: em and en dashes replaced with standard punctuation, arrows spelled out, decorative adjectives removed, and version-specific wording where "modern Ruby" was vague. Verified: all three suites pass with zero failures on PostgreSQL 11 through 18, and CREATE EXTENSION VERSION '2.0' followed by ALTER EXTENSION UPDATE chains to 2.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 98b9f76 commit 4325f84

12 files changed

Lines changed: 165 additions & 118 deletions

CHANGELOG.md

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ All notable changes to PL/Ruby are documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and the project aims to follow [Semantic Versioning](https://semver.org/).
77

8-
## [Unreleased]
8+
## [2.3.0] - 2026-07-05
9+
10+
Inline class definitions, the hstore transform, and transform coverage in
11+
every conversion context, including triggers. All plruby changes are in the
12+
shared library; `ALTER EXTENSION plruby UPDATE` completes the upgrade after
13+
installing the new binary.
914

1015
### Added
1116

1217
- **Inline `class`/`module` definitions in function bodies.** Bodies now
1318
compile to a top-level lambda (with a delegator method), where `class` is
14-
legal it was a SyntaxError anywhere inside the previous `def`-based
19+
legal; it was a SyntaxError anywhere inside the previous `def`-based
1520
compilation. Definitions register globally for the session, exactly like
1621
`plruby_modules` code, and work in functions, triggers, and `DO` blocks.
1722
- **Transforms reach nested contexts.** A `TRANSFORM FOR TYPE` declaration
@@ -22,50 +27,50 @@ and the project aims to follow [Semantic Versioning](https://semver.org/).
2227
`return_next` rows carry nested composite values.)
2328
- **Transforms in triggers.** A trigger function that declares
2429
`TRANSFORM FOR TYPE` receives transformed columns in `$_TD['new']`/
25-
`$_TD['old']` and may put native Ruby data back via `'MODIFY'` going
30+
`$_TD['old']` and may put native Ruby data back via `'MODIFY'`, going
2631
beyond PL/Perl, whose transforms skip trigger data. Without the clause,
2732
triggers behave exactly as before.
28-
- **`hstore_plruby`** a companion extension (in `hstore_plruby/`) providing
33+
- **`hstore_plruby`**: a companion extension (in `hstore_plruby/`) providing
2934
`TRANSFORM FOR TYPE hstore`: opted-in functions receive hstore arguments as
3035
a Ruby `Hash` of String keys to String-or-`nil` values and may return a
31-
`Hash` into an hstore result (keys/values stringified, `nil` `NULL`).
36+
`Hash` into an hstore result (keys/values stringified, `nil` maps to `NULL` in both directions).
3237
Implemented against hstore's public SQL functions, so it needs no hstore
33-
headers and works with the packaged hstore on PostgreSQL 1118.
38+
headers and works with the packaged hstore on PostgreSQL 11-18.
3439

35-
## [2.2.0] 2026-07-05
40+
## [2.2.0] - 2026-07-05
3641

37-
Native jsonb, modern Ruby, and CI. The `jsonb_plruby` companion extension
42+
Native jsonb, Ruby 3.3/3.4 support, and CI. The `jsonb_plruby` companion extension
3843
exchanges jsonb with native Ruby data; RubyGems is enabled in the embedded
3944
interpreter (restoring csv/bigdecimal/base64 on Ruby 3.4 and making
4045
installed gems requirable); every push is now verified by GitHub Actions on
41-
PostgreSQL 1218 and Ruby 3.2/3.3/3.4 (PG 11 verified out-of-band). All
46+
PostgreSQL 12-18 and Ruby 3.2/3.3/3.4 (PG 11 verified out-of-band). All
4247
plruby changes are in the shared library; `ALTER EXTENSION plruby UPDATE`
4348
completes the upgrade after installing the new binary.
4449

4550
### Added
4651

47-
- **`jsonb_plruby`** a companion extension (in `jsonb_plruby/`) providing
52+
- **`jsonb_plruby`**: a companion extension (in `jsonb_plruby/`) providing
4853
`TRANSFORM FOR TYPE jsonb`: opted-in functions receive jsonb arguments as
4954
native Ruby data (`Hash`/`Array`/`String`/`Integer`/`Float`/booleans/`nil`)
5055
and return Ruby data into jsonb directly. Integers beyond `Float` precision
5156
and `BigDecimal` values serialize exactly.
52-
- **Structured `pg_raise`** `pg_raise(level, message, detail:, hint:,
57+
- **Structured `pg_raise`**: `pg_raise(level, message, detail:, hint:,
5358
sqlstate:)` maps the keywords onto the corresponding `ereport` fields (like
5459
PL/pgSQL's `RAISE ... USING`); a PL/Ruby caller that rescues the resulting
5560
error reads them back via `PLRuby::Error#detail` / `#hint` / `#sqlstate`.
56-
- **`PLRuby::Error#detail` / `#hint`** a caught database error now carries
61+
- **`PLRuby::Error#detail` / `#hint`**: a caught database error now carries
5762
its `DETAIL` and `HINT` alongside the SQLSTATE.
58-
- **Streaming `spi_query_prepared`** executes a prepared plan through a
63+
- **Streaming `spi_query_prepared`**: executes a prepared plan through a
5964
cursor (block form and `PLRuby::Cursor` handle form) instead of
6065
materializing; the plan stays reusable after the cursor closes. It was
6166
previously an alias of `spi_exec_prepared`.
62-
- **Regression suite 35 37 tests** (`replace`: mid-session
67+
- **Regression suite 35 to 37 tests** (`replace`: mid-session
6368
`CREATE OR REPLACE` recompilation; extended `misc`/`sqlstate`/`prepare`),
6469
plus the `jsonb_plruby` suite.
65-
- **CI (GitHub Actions)** every push and pull request builds both
66-
extensions and runs both suites on PostgreSQL 1218 (system Ruby 3.2) and
70+
- **CI (GitHub Actions)**: every push and pull request builds both
71+
extensions and runs both suites on PostgreSQL 12-18 (system Ruby 3.2) and
6772
on Ruby 3.3/3.4 (PostgreSQL 18).
68-
- **Ruby 3.3 and 3.4 support** alternate expected files cover their
73+
- **Ruby 3.3 and 3.4 support**: alternate expected files cover their
6974
changed `NoMethodError`/`Hash#inspect` output; validation errors are
7075
reported as a single line (Ruby 3.4's parser quotes the generated wrapper
7176
source over many lines); the cookbook's token recipe encodes with
@@ -78,48 +83,48 @@ completes the upgrade after installing the new binary.
7883
arrived as the text-form String.
7984
- **RubyGems is enabled in the embedded interpreter** (was `--disable-gems`):
8085
Ruby 3.4 ships `csv`, `bigdecimal`, and `base64` as bundled gems, which
81-
plain `require` cannot see without it and installed gems become
86+
plain `require` cannot see without it, and installed gems become
8287
requirable as a side benefit. `did_you_mean`/`error_highlight` are disabled
8388
so error messages stay deterministic. Verified on Ruby 3.2, 3.3, and 3.4
8489
(CI covers all three).
8590

86-
## [2.1.0] 2026-07-05
91+
## [2.1.0] - 2026-07-05
8792

8893
Feature and hardening release: broader trigger and argument-mode coverage,
89-
richer error objects, streaming SPI, a tested cookbook, and a regression
94+
error objects carrying SQLSTATE, streaming SPI, a tested cookbook, and a regression
9095
suite grown to 35 tests verified on PostgreSQL 11 through 18. All changes
9196
are in the shared library; `ALTER EXTENSION plruby UPDATE` completes the
9297
upgrade after installing the new binary.
9398

9499
### Added
95100

96-
- **Expanded regression suite**20 28 tests, verified on **PostgreSQL 11,
101+
- **Expanded regression suite**: 20 to 28 tests, verified on **PostgreSQL 11,
97102
12, 13, 14, 16, and 18** with Ruby 3.2. New coverage: `bytea` conversion,
98103
special numeric/float values (`NaN`, `±Infinity`, integer overflow), nested
99104
and array-bearing composites, SPI DML status codes and NULL columns,
100105
DELETE-trigger `$_TD['old']` handling, the error/`ensure` model, prepared-plan
101106
reuse, standard-library `require`, `$stdout`/`$stderr` redirection, quoting
102107
helpers, and `plruby.start_proc`.
103108

104-
- **Cursor streaming** `spi_query(sql)` opens a portal and reads rows a batch
109+
- **Cursor streaming**: `spi_query(sql)` opens a portal and reads rows a batch
105110
at a time, so large results stream without materializing. Block form
106-
(`spi_query(sql) { |row| }`), handle form (`spi_fetchrow` /
111+
(`spi_query(sql) { |row| ... }`), handle form (`spi_fetchrow` /
107112
`spi_cursor_close`), and `PLRuby::Cursor#each` (Enumerable).
108-
- **Broader string encoding** text is tagged with the Ruby encoding matching
109-
the database encoding (LATIN*, WIN*, EUC*, SJIS, KOI8, Big5, GBK, ), not just
113+
- **Broader string encoding**: text is tagged with the Ruby encoding matching
114+
the database encoding (LATIN*, WIN*, EUC*, SJIS, KOI8, Big5, GBK, ...), not just
110115
UTF-8; unmapped encodings fall back to ASCII-8BIT.
111-
- **`PLRuby::Error#sqlstate`** a caught PostgreSQL error now carries its
116+
- **`PLRuby::Error#sqlstate`**: a caught PostgreSQL error now carries its
112117
five-character `SQLSTATE` (e.g. `42P01`, `22012`) on the Ruby exception; it
113118
is `nil` on a PL/Ruby error not backed by a database error.
114-
- **`VARIADIC` arguments** the variadic tail arrives as a single Ruby
119+
- **`VARIADIC` arguments**: the variadic tail arrives as a single Ruby
115120
`Array` argument (`VARIADIC "any"` remains unsupported).
116-
- **`INSTEAD OF` triggers** on views `$_TD['when']` is `INSTEAD OF`, with
121+
- **`INSTEAD OF` triggers** on views: `$_TD['when']` is `INSTEAD OF`, with
117122
the same `nil`/`'SKIP'`/`'MODIFY'` return handling as BEFORE row triggers.
118-
- **Cookbook**`doc/cookbook.md`, practical recipes built on Ruby's stdlib
123+
- **Cookbook**: `doc/cookbook.md`, recipes built on Ruby's stdlib
119124
(JSON reshaping, HMAC/PBKDF2, audit trigger, CSV, Zlib-to-bytea, BigDecimal,
120125
slugify, ERB, streaming scans, batch commits); every recipe in the "tested"
121126
section runs verbatim in the regression suite (`cookbook` test).
122-
- **Regression suite 28 35 tests**, adding `datetime` (date/time/timestamp/
127+
- **Regression suite 28 to 35 tests**, adding `datetime` (date/time/timestamp/
123128
interval), `jsonb`, `misc` (uuid/inet/enum/domain), `variadic`, `trigger2`
124129
(INSTEAD OF, `WHEN` clauses, deferred constraint triggers, composite-column
125130
`'MODIFY'`), `hostile` (mid-SRF errors, cursor misuse, ~1MB TOAST values,
@@ -135,7 +140,7 @@ upgrade after installing the new binary.
135140
statement-level, return value ignored.
136141
- **`spi_freeplan`** now invalidates the plan handle; reusing a freed plan
137142
raises a clear PL/Ruby error instead of silently failing.
138-
- **Trigger `'MODIFY'` with composite columns** the modified-tuple path is
143+
- **Trigger `'MODIFY'` with composite columns**: the modified-tuple path is
139144
now datum-based, so assigning a `Hash` to a composite-typed field of
140145
`$_TD['new']` works (it previously failed with "malformed record literal").
141146
- **`spi_fetchrow` after `spi_cursor_close`** now returns `nil`; previously it
@@ -146,7 +151,7 @@ upgrade after installing the new binary.
146151
- A function body compiles to a method, so a `class`/`module` definition cannot
147152
appear inline in the body (use a `plruby_modules` module instead).
148153

149-
## [2.0.0] 2026-07-01
154+
## [2.0.0] - 2026-07-01
150155

151156
The initial release of the modernized PL/Ruby: an MRI Ruby interpreter embedded
152157
in PostgreSQL, packaged as a `CREATE EXTENSION` and offering the same feature
@@ -155,34 +160,34 @@ library, non-thread-safe embedding).
155160

156161
### Added
157162

158-
- **Functions** with native Ruby argument and return types `Integer`,
163+
- **Functions** with native Ruby argument and return types: `Integer`,
159164
`Float`, `true`/`false`, `String`, `nil`, nested `Array` (multidimensional
160165
PostgreSQL arrays), and composite/record types as `Hash`.
161-
- **Set-returning functions** `RETURNS SETOF` and `RETURNS TABLE(...)` with
166+
- **Set-returning functions**: `RETURNS SETOF` and `RETURNS TABLE(...)` with
162167
`return_next` (both explicit-value and no-argument forms, the latter reading
163168
the TABLE/OUT column locals from the running body's binding).
164-
- **Argument modes** `IN`, `OUT`, `INOUT`, `TABLE`, and named parameters
169+
- **Argument modes**: `IN`, `OUT`, `INOUT`, `TABLE`, and named parameters
165170
aliased as local variables.
166-
- **Trigger functions** `RETURNS trigger` with the `$_TD` hash and
171+
- **Trigger functions**: `RETURNS trigger` with the `$_TD` hash and
167172
`nil`/`'SKIP'`/`'MODIFY'` return semantics.
168-
- **Event trigger functions** `RETURNS event_trigger`, with `$_TD['event']`
173+
- **Event trigger functions**: `RETURNS event_trigger`, with `$_TD['event']`
169174
and `$_TD['tag']`.
170-
- **Anonymous `DO` blocks** `DO $$ ... $$ LANGUAGE plruby`.
171-
- **Database access (SPI)** `spi_exec`, `spi_fetch_row`, `spi_processed`,
175+
- **Anonymous `DO` blocks**: `DO $$ ... $$ LANGUAGE plruby`.
176+
- **Database access (SPI)**: `spi_exec`, `spi_fetch_row`, `spi_processed`,
172177
`spi_status`, `spi_rewind`.
173-
- **Prepared statements** `spi_prepare`, `spi_exec_prepared`,
178+
- **Prepared statements**: `spi_prepare`, `spi_exec_prepared`,
174179
`spi_query_prepared`, and `spi_freeplan`.
175-
- **Transaction control** in procedures `spi_commit` and `spi_rollback`.
176-
- **Explicit subtransactions** `subtransaction { ... }` and
180+
- **Transaction control** in procedures: `spi_commit` and `spi_rollback`.
181+
- **Explicit subtransactions**: `subtransaction { ... }` and
177182
`subtransaction(callable, ...)`, rolling back and re-raising on any exception.
178-
- **Quoting helpers** `quote_literal`, `quote_nullable`, `quote_ident`.
183+
- **Quoting helpers**: `quote_literal`, `quote_nullable`, `quote_ident`.
179184
- **`elog(level, message)`** supporting `DEBUG`/`LOG`/`INFO`/`NOTICE`/`WARNING`/`ERROR`,
180185
and the narrower `pg_raise(level, message)`.
181-
- **`$_SHARED`** a hash persisting across calls within a session; standard
186+
- **`$_SHARED`**: a hash persisting across calls within a session; standard
182187
output (`puts`, `print`) is forwarded to the server log.
183-
- **Session initialization** module autoloading from a `plruby_modules` table
188+
- **Session initialization**: module autoloading from a `plruby_modules` table
184189
and a `plruby.start_proc` configuration setting.
185-
- **UTF-8 string handling** text from the database is tagged with the
190+
- **UTF-8 string handling**: text from the database is tagged with the
186191
database encoding (UTF-8 when the database is UTF-8), so multibyte Ruby String
187192
operations (`length`, `reverse`, regexp, ...) behave correctly.
188193
- Packaging as a first-class extension (`CREATE EXTENSION plruby`) and a

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ MODULE_big = plruby
1717
OBJS = plruby.o plruby_io.o plruby_spi.o
1818

1919
EXTENSION = plruby
20-
DATA = plruby--2.0.sql plruby--2.1.sql plruby--2.2.sql plruby--2.0--2.1.sql plruby--2.1--2.2.sql
20+
DATA = plruby--2.0.sql plruby--2.1.sql plruby--2.2.sql plruby--2.3.sql plruby--2.0--2.1.sql plruby--2.1--2.2.sql plruby--2.2--2.3.sql
2121

2222
# Ruby compile/link flags, discovered via RbConfig.
2323
RUBY ?= ruby

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
PL/Ruby is a procedural-language handler that lets you write database functions
1616
in **Ruby**, stored and executed inside PostgreSQL. You get the expressiveness
1717
of Ruby and its standard library with the full power of a native PostgreSQL
18-
function plain functions, set-returning functions, triggers, event triggers,
18+
function: plain functions, set-returning functions, triggers, event triggers,
1919
and procedures with transaction control.
2020

2121
```sql
@@ -30,7 +30,7 @@ SELECT hello('world'); -- Hello, world!
3030

3131
> [!NOTE]
3232
> PL/Ruby embeds an MRI Ruby interpreter in the backend. It targets **PostgreSQL
33-
> 1118** and **Ruby 3.x**, installs as a first-class `CREATE EXTENSION`, and
33+
> 11-18** and **Ruby 3.x**, installs as a first-class `CREATE EXTENSION`, and
3434
> mirrors the feature set of PL/php with a large set of PL/Perl- and
3535
> PL/Tcl-inspired capabilities.
3636
@@ -40,20 +40,20 @@ SELECT hello('world'); -- Hello, world!
4040

4141
| | |
4242
|---|---|
43-
| 🧩 **Scalars, arrays, composites** | Arguments arrive as native Ruby values `Integer`, `Float`, `true`/`false`, `String`, nested `Array`, and composite/record types as `Hash`. |
43+
| 🧩 **Scalars, arrays, composites** | Arguments arrive as native Ruby values: `Integer`, `Float`, `true`/`false`, `String`, nested `Array`, and composite/record types as `Hash`. |
4444
| 🔁 **Set-returning functions** | `RETURNS SETOF` / `RETURNS TABLE` with `return_next`. |
4545
|**Triggers** | Row & statement triggers via `$_TD` (with `'SKIP'` / `'MODIFY'`). |
4646
| 📣 **Event triggers** | Back `CREATE EVENT TRIGGER` with `RETURNS event_trigger`. |
4747
| 🗄️ **Database access (SPI)** | `spi_exec`, `spi_fetch_row`, `spi_processed`, `spi_status`, `spi_rewind`. |
48-
| 🌊 **Cursor streaming** | `spi_query` (block or handle), `spi_fetchrow`, `spi_cursor_close`, `Cursor#each` — consume huge results without materializing them. |
48+
| 🌊 **Cursor streaming** | `spi_query` (block or handle), `spi_fetchrow`, `spi_cursor_close`, `Cursor#each`. Consume large results without materializing them. |
4949
| 📝 **Prepared statements** | `spi_prepare` / `spi_exec_prepared` / `spi_query_prepared` / `spi_freeplan`. |
5050
| 🔐 **Transaction control** | `spi_commit` / `spi_rollback` in procedures, plus `subtransaction` blocks. |
5151
| 🧰 **Utilities** | `quote_literal` / `quote_nullable` / `quote_ident`, `elog`, `$_SHARED`. |
5252
| 📦 **Session setup** | Anonymous `DO` blocks, `plruby_modules` autoloading, and a `plruby.start_proc` hook. |
5353
| 🔄 **Transforms** | `jsonb_plruby` and `hstore_plruby`: functions declared `TRANSFORM FOR TYPE` exchange native Ruby Hashes/Arrays with `jsonb` and `hstore`. |
5454

5555
See the [**language reference**](doc/plruby.md) for the full API, the
56-
[**cookbook**](doc/cookbook.md) for practical tested recipes, and the
56+
[**cookbook**](doc/cookbook.md) for tested recipes, and the
5757
[PL/Perl](doc/plperl-comparison.md) and [PL/Tcl](doc/pltcl-comparison.md)
5858
comparisons for feature-by-feature detail.
5959

@@ -96,7 +96,7 @@ $$;
9696

9797
## Requirements
9898

99-
- **PostgreSQL 11 or newer** (tested on 1118; 18 recommended), with the server
99+
- **PostgreSQL 11 or newer** (tested on 11-18; 18 recommended), with the server
100100
development files that provide `pg_config`.
101101
- **Ruby 3.x** built as a shared library (`ENABLE_SHARED=yes`) with development
102102
headers. On Debian/Ubuntu, install `ruby-dev`.
@@ -120,9 +120,9 @@ See [**INSTALL**](INSTALL) for details, and run the regression suite with
120120
## Security
121121

122122
> [!WARNING]
123-
> **PL/Ruby is an untrusted language.** On modern Ruby there is no sandbox
123+
> **PL/Ruby is an untrusted language.** Ruby 3.0 and later have no sandbox
124124
> (`$SAFE` and object tainting were removed in Ruby 3.0), so a PL/Ruby function
125-
> can do anything the PostgreSQL server's operating-system user can read and
125+
> can do anything the PostgreSQL server's operating-system user can: read and
126126
> write files, open network connections, run shell commands, and so on.
127127
128128
The language is created **without** the `TRUSTED` attribute, so only superusers
@@ -132,7 +132,7 @@ to roles you would trust with the server's OS account.
132132
## Documentation
133133

134134
- [Language reference](doc/plruby.md)
135-
- [Cookbook: practical tested recipes](doc/cookbook.md)
135+
- [Cookbook: tested recipes](doc/cookbook.md)
136136
- [Installation](INSTALL)
137137
- [Changelog](CHANGELOG.md)
138138
- [Feature comparison: PL/Ruby vs PL/php vs PL/Perl vs PL/Tcl](doc/comparison.md)
@@ -141,4 +141,4 @@ to roles you would trust with the server's OS account.
141141

142142
## License
143143

144-
PL/Ruby is licensed under the **MIT License** see [LICENSE](LICENSE).
144+
PL/Ruby is licensed under the **MIT License**; see [LICENSE](LICENSE).

0 commit comments

Comments
 (0)