Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: CI

on:
push:
branches: [master]
pull_request:

jobs:
test:
name: PG ${{ matrix.pg }} / PHP ${{ matrix.php }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
# Every supported PostgreSQL against the distro PHP...
pg: [11, 12, 13, 14, 15, 16, 17, 18]
php: ["8.3"]
# ...plus the other supported PHP versions against the newest PG.
include:
- { pg: 18, php: "8.1" }
- { pg: 18, php: "8.2" }
- { pg: 18, php: "8.4" }

steps:
- uses: actions/checkout@v4

- name: Add the PGDG apt repository
run: |
sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
-o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
| sudo tee /etc/apt/sources.list.d/pgdg.list

- name: Add the ondrej/php PPA (non-distro PHP versions)
if: matrix.php != '8.3'
run: sudo add-apt-repository -y ppa:ondrej/php

- name: Install PostgreSQL ${{ matrix.pg }} and PHP ${{ matrix.php }}
run: |
sudo apt-get update
sudo apt-get install -y \
postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }} \
php${{ matrix.php }}-dev libphp${{ matrix.php }}-embed php${{ matrix.php }}-xml
php-config${{ matrix.php }} --php-sapis | grep -q embed

- name: Build and install PL/php
run: |
make PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config \
PHP_CONFIG=php-config${{ matrix.php }}
sudo make PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config \
PHP_CONFIG=php-config${{ matrix.php }} install

- name: Build and install jsonb_plphp
run: |
make -C jsonb_plphp \
PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config \
PHP_CONFIG=php-config${{ matrix.php }}
sudo make -C jsonb_plphp \
PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config \
PHP_CONFIG=php-config${{ matrix.php }} install

- name: Start the cluster
run: |
# the runner image disables automatic cluster creation
sudo pg_createcluster ${{ matrix.pg }} main 2>/dev/null || true
sudo pg_ctlcluster ${{ matrix.pg }} main start
pg_lsclusters

- name: Run the regression tests
run: |
port=$(pg_lsclusters | awk '$1 == "${{ matrix.pg }}" && $2 == "main" { print $3 }')
# let the postgres user reach and write the workspace
sudo chmod a+x /home/runner /home/runner/work /home/runner/work/PL-php
chmod -R a+rwX .
sudo -u postgres env PGPORT=$port \
make PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config installcheck

- name: Run the jsonb_plphp regression tests
run: |
port=$(pg_lsclusters | awk '$1 == "${{ matrix.pg }}" && $2 == "main" { print $3 }')
sudo -u postgres env PGPORT=$port \
make -C jsonb_plphp \
PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config installcheck

- name: Show regression diffs on failure
if: failure()
run: |
cat regression.diffs 2>/dev/null || true
cat jsonb_plphp/regression.diffs 2>/dev/null || true
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ and the project aims to follow [Semantic Versioning](https://semver.org/).

### Added

- **Continuous integration** — a GitHub Actions matrix building and running
the full regression suite (core + `jsonb_plphp`) on PostgreSQL 11-18 and
PHP 8.1/8.2/8.3/8.4 for every pull request.
- **VARIADIC parameters.** A variadic function's collected arguments arrive
as a single PHP array, matching every other PL (`VARIADIC "any"` remains
unsupported and is rejected with a clear error). Previously any VARIADIC
declaration failed.

### Fixed

- **PHP 8.1, 8.2, and 8.4 compatibility.** PL/php now builds and passes the
full suite on PHP 8.1 through 8.4: a version guard for the
`php_module_startup` signature change (8.2), explicit `fgetcsv` arguments
in the cookbook (8.4 deprecation), and a clear compile error below 8.1.
The Makefiles now prefer the `libphp` matching `PHP_CONFIG`'s version when
several are installed (see INSTALL for a packaging caveat about shared
SONAMEs).

## [2.2.0] — 2026-07-05

### Added
Expand Down
6 changes: 6 additions & 0 deletions INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ from PATH. Override them if needed:
make PG_CONFIG=/usr/lib/postgresql/18/bin/pg_config PHP_CONFIG=/usr/bin/php-config
sudo make PG_CONFIG=/usr/lib/postgresql/18/bin/pg_config install

Note: if several libphpX.Y-embed versions are installed side by side, some
packagings (e.g. deb.sury.org) give them all the same SONAME (libphp.so), so
the backend loads whatever /usr/lib/libphp.so points at -- regardless of the
version linked at build time. Keep a single embed package installed, or
point that symlink at the intended version.

This installs the shared library plphp.so plus the extension control and SQL
files into the directories reported by pg_config.

Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ DATA = plphp--2.2.sql plphp--2.0--2.1.sql plphp--2.1--2.2.sql
PHP_CONFIG ?= php-config
PHP_INCLUDES := $(shell $(PHP_CONFIG) --includes)
PHP_LIBDIR := $(shell $(PHP_CONFIG) --prefix)/lib
PHP_LIBNAME := $(patsubst lib%.so,%,$(notdir $(firstword $(wildcard $(PHP_LIBDIR)/libphp*.so /usr/lib/libphp*.so /usr/lib/*/libphp*.so))))
# Prefer the library matching PHP_CONFIG's version (e.g. libphp8.3.so); with
# several PHP versions installed, a bare "libphp*.so" glob would pick the
# unversioned symlink of whichever version owns it.
PHP_VERSION := $(shell $(PHP_CONFIG) --version | cut -d. -f1-2)
PHP_LIBNAME := $(patsubst lib%.so,%,$(notdir $(firstword $(wildcard \
$(PHP_LIBDIR)/libphp$(PHP_VERSION).so /usr/lib/libphp$(PHP_VERSION).so /usr/lib/*/libphp$(PHP_VERSION).so \
$(PHP_LIBDIR)/libphp*.so /usr/lib/libphp*.so /usr/lib/*/libphp*.so))))

PG_CPPFLAGS = $(PHP_INCLUDES)
# Link against the PHP embed library only; its own transitive dependencies
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
**Write PostgreSQL functions, triggers, and procedures in PHP.**

[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-11_to_18-336791?logo=postgresql&logoColor=white)](https://www.postgresql.org/)
[![PHP](https://img.shields.io/badge/PHP-8.x-777BB4?logo=php&logoColor=white)](https://www.php.net/)
[![PHP](https://img.shields.io/badge/PHP-8.1--8.4-777BB4?logo=php&logoColor=white)](https://www.php.net/)
[![Tests](https://img.shields.io/badge/tests-17_passing-brightgreen)](sql/)
[![License](https://img.shields.io/badge/license-permissive-blue)](#license)

Expand Down Expand Up @@ -99,7 +99,7 @@ $$;

- **PostgreSQL 11 or newer** (tested on 11–18; 18 recommended), with the server
development files that provide `pg_config`.
- **PHP 8.x** built with the **embed SAPI** and **without** ZTS (thread safety).
- **PHP 8.1 through 8.4** built with the **embed SAPI** and **without** ZTS (thread safety).
On Debian/Ubuntu, install `php8.x-dev` and `libphp8.x-embed`.

## Installation
Expand Down
2 changes: 1 addition & 1 deletion doc/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ LANGUAGE plphp STRICT AS $$
$fh = fopen($args[0], "r");
if ($fh === false)
pg_raise('error', "cannot open {$args[0]}");
while (($rec = fgetcsv($fh)) !== false)
while (($rec = fgetcsv($fh, null, ",", "\"", "\\")) !== false)
return_next($rec);
fclose($fh);
return;
Expand Down
4 changes: 2 additions & 2 deletions doc/plphp.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ PL/php lets you write PostgreSQL functions and triggers in PHP. This document
describes the programming interface. For build and install instructions see
[`INSTALL`](../INSTALL); for a feature summary see [`README`](../README.md).

Tested on **PostgreSQL 11 through 18** with **PHP 8.3** (embed SAPI,
non-thread-safe).
Tested on **PostgreSQL 11 through 18** with **PHP 8.1 through 8.4** (embed
SAPI, non-thread-safe).

- [Enabling the language](#enabling-the-language)
- [Writing functions](#writing-functions)
Expand Down
2 changes: 1 addition & 1 deletion expected/cookbook.out
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ LANGUAGE plphp STRICT AS $$
$fh = fopen($args[0], "r");
if ($fh === false)
pg_raise('error', "cannot open {$args[0]}");
while (($rec = fgetcsv($fh)) !== false)
while (($rec = fgetcsv($fh, null, ",", "\"", "\\")) !== false)
return_next($rec);
fclose($fh);
return;
Expand Down
8 changes: 7 additions & 1 deletion jsonb_plphp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ DATA = jsonb_plphp--1.0.sql
PHP_CONFIG ?= php-config
PHP_INCLUDES := $(shell $(PHP_CONFIG) --includes)
PHP_LIBDIR := $(shell $(PHP_CONFIG) --prefix)/lib
PHP_LIBNAME := $(patsubst lib%.so,%,$(notdir $(firstword $(wildcard $(PHP_LIBDIR)/libphp*.so /usr/lib/libphp*.so /usr/lib/*/libphp*.so))))
# Prefer the library matching PHP_CONFIG's version (e.g. libphp8.3.so); with
# several PHP versions installed, a bare "libphp*.so" glob would pick the
# unversioned symlink of whichever version owns it.
PHP_VERSION := $(shell $(PHP_CONFIG) --version | cut -d. -f1-2)
PHP_LIBNAME := $(patsubst lib%.so,%,$(notdir $(firstword $(wildcard \
$(PHP_LIBDIR)/libphp$(PHP_VERSION).so /usr/lib/libphp$(PHP_VERSION).so /usr/lib/*/libphp$(PHP_VERSION).so \
$(PHP_LIBDIR)/libphp*.so /usr/lib/libphp*.so /usr/lib/*/libphp*.so))))

PG_CPPFLAGS = $(PHP_INCLUDES)
SHLIB_LINK = -L$(PHP_LIBDIR) -l$(PHP_LIBNAME) $(shell $(PHP_CONFIG) --ldflags)
Expand Down
39 changes: 37 additions & 2 deletions plphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
/* PHP stuff */
#include "php.h"

#if PHP_VERSION_ID < 80100
#error "PL/php requires PHP 8.1 or newer (zend_error_cb signature)"
#endif

#include "php_variables.h"
#include "php_globals.h"
#include "zend_hash.h"
Expand Down Expand Up @@ -413,9 +417,11 @@ plphp_init_all(void)
plphp_init();

/*
* Any other initialization that must be done each time a new
* backend starts -- currently none.
* Loaded PHP extensions can swap zend_error_cb behind our back at any
* point; put ours back before running anything. It is a plain pointer
* assignment, so doing it on every call is free.
*/
zend_error_cb = plphp_error_cb;
}

/*
Expand Down Expand Up @@ -450,8 +456,14 @@ plphp_init(void)
plphp_sapi_module.phpinfo_as_text = 1;
sapi_startup(&plphp_sapi_module);

#if PHP_VERSION_ID >= 80200
if (php_module_startup(&plphp_sapi_module, NULL) == FAILURE)
elog(ERROR, "php_module_startup call failed");
#else
/* PHP 8.1 still took an additional-modules count */
if (php_module_startup(&plphp_sapi_module, NULL, 0) == FAILURE)
elog(ERROR, "php_module_startup call failed");
#endif

/* php_module_startup changed it, so put it back */
zend_error_cb = plphp_error_cb;
Expand All @@ -476,6 +488,22 @@ plphp_init(void)
INI_HARDCODED("max_execution_time", "0");
INI_HARDCODED("max_input_time", "-1");

/*
* The host's php.ini must not decide which errors reach our
* zend_error_cb, or how they are routed: force full reporting
* and disable opcache/JIT, which add nothing for our one-shot
* eval'd functions and can interfere with error delivery.
* (GitHub's runner images, for example, ship a PHP configured
* with opcache+JIT enabled for CLI, which broke both notices
* and error messages.)
*/
INI_HARDCODED("error_reporting", "32767"); /* E_ALL */
INI_HARDCODED("display_errors", "0");
INI_HARDCODED("log_errors", "0");
INI_HARDCODED("opcache.enable", "0");
INI_HARDCODED("opcache.enable_cli", "0");
INI_HARDCODED("xdebug.mode", "off");

/*
* Set memory limit to ridiculously high value. This helps the
* server not to crash, because the PHP allocator has the really
Expand Down Expand Up @@ -504,6 +532,13 @@ plphp_init(void)

PG(during_request_startup) = true;

/*
* Extensions loaded from the host's php.ini (xdebug, for one)
* may have hooked zend_error_cb during module or request
* startup; ours must be the one in place.
*/
zend_error_cb = plphp_error_cb;

/*
* Register our SPI functions and the procedure cache now that the
* request (and its interned-string tables) is fully initialized.
Expand Down
2 changes: 1 addition & 1 deletion sql/cookbook.sql
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ LANGUAGE plphp STRICT AS $$
$fh = fopen($args[0], "r");
if ($fh === false)
pg_raise('error', "cannot open {$args[0]}");
while (($rec = fgetcsv($fh)) !== false)
while (($rec = fgetcsv($fh, null, ",", "\"", "\\")) !== false)
return_next($rec);
fclose($fh);
return;
Expand Down
Loading