lolor Release Notes
lolor 1.3.0
- Add bidirectional large object migration between native PostgreSQL and lolor storage, implemented as a direct relation-to-relation copy in C (
lolor.migrate_storage()) rather than a loop through the large object API: lolor.migrate_from_native(peer_oids oid[] DEFAULT NULL)moves native large objects into lolor storage. It is a manual step afterCREATE EXTENSION lolor, requires superuser, and returns the number of objects moved or -1 if the migration was refused.lolor.migrate_to_native()moves them back, whether or not lolor is enabled. It is a manual step before dropping the extension; the drop is refused while any object remains in lolor storage and no longer migrates anything itself.- Both directions preserve OIDs, owners, ACLs, comments and the exact page layout, so sparse objects stay sparse. Ownership and ACLs are recorded in
pg_shdependon the way back, soDROP ROLE,REASSIGN OWNEDandDROP OWNEDsee the objects again. Comments are parked inlolor.pg_largeobject_descriptionwhile an object is in lolor storage. Native objects are removed with the same bookkeeping asDROP(dependencies, comments and labels), but without the per-object locksDROPtakes, so a database with more large objects than the lock table can hold is migrated without raisingmax_locks_per_transaction. - Storage layouts are verified against the running server at migration time. Security labels, which lolor storage cannot represent, cause
migrate_from_native()to refuse. OID conflicts are reported with the conflicting OIDs. - Migration holds
ShareRowExclusiveLockon both stores until commit. Ordinary large object access takesRowExclusiveLock, which does not conflict with itself, so a concurrentlo_write()could otherwise be lost silently. Opposing migrations take their locks in a fixed order and cannot deadlock. - Both migration functions refuse while any other session is connected to the database, and refuse to run from anything but a client session.
- With spock installed, both functions run under
spock.repair_mode(). If a logical slot spock cannot suppress exists, a non-spock slot or any slot when spock is absent, they refuse rather than let subscribers decode the migration as row changes. - Migration is node-local: native large objects are never replicated, so run the migration on each node by connecting to it directly. Do not send it through DDL replication, which would run it inside every subscriber's apply worker. Native OIDs are not node-encoded, so pass the other nodes' OIDs (from
lolor.native_lo_oids()) aspeer_oidsto turn a cross-node collision into a refusal, and comparelolor.digest()across nodes afterwards. migrate_to_native()refuses with a clear error while any object refers to a dropped role, instead of failing part-way with "role N was concurrently dropped"; remove those objects withlo_unlink()and retry.- New helpers
lolor.digest()andlolor.native_lo_oids()for cross-node checks after a node-local migration. - New helpers
lolor.check_orphans()andlolor.fix_orphans(new_owner)for objects whose owner, grantee or grantor has been dropped. Objects in lolor storage cannot participate inpg_shdepend, soDROP ROLEdoes not notice them;check_orphans()lists them and which reference is dangling, andfix_orphans()reassigns dead owners, replacing them in the ACL asREASSIGN OWNEDwould so that grants they made survive, and drops entries that still name a missing role. - Security fix:
lo_import()andlo_export()were executable by any database user. These read and write files on the server as the account PostgreSQL runs under, so core revokesEXECUTEon them fromPUBLIC. lolor replaces them by renaming the originals to*_orig; an ACL belongs to a function rather than a name, so the restriction stayed on the parked original while each replacement got the defaultEXECUTE TO PUBLIC. Any user could read an arbitrary server file withlo_import()or overwrite one withlo_export(). The replacements are now locked down at install time, and upgrading revokes the privilege on existing installations in either state. Versions 1.0 through 1.2.2 are affected. - The extension is no longer marked
trusted. Installing lolor renames functions inpg_catalogfor the whole database, which a non-superuser should not be able to do. - Fixed
DROP ROLEfailing with "unrecognized object class". Creating a large object recorded apg_shdependrow whoseclassIdwas the OID oflolor.pg_largeobject, an ordinary table rather than a catalog. Any role that had created a large object became undroppable, and the rows were never cleaned up becauseinv_drop()deleted withPERFORM_DELETION_SKIP_ORIGINAL. lolor no longer records these rows, and the upgrade removes the ones already present. - The cleanup is per database, so
ALTER EXTENSION lolor UPDATEmust be run in every database that has lolor. Until it is,DROP ROLEanywhere in the cluster keeps reporting "owner of objects in database X". - A database that ran
DROP EXTENSION loloron an earlier version still has the rows, now pointing at a table that no longer exists, and no upgrade script will run there. Clean it by hand as superuser in that database: find the stale class OIDs withSELECT DISTINCT classid FROM pg_shdepend WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database()) AND classid NOT IN (SELECT oid FROM pg_class), thenDELETE FROM pg_shdepend WHERE dbid = <that dbid> AND classid IN (<those OIDs>). - lolor must now be listed in
shared_preload_libraries. Loading it on demand is refused:CREATE EXTENSION lolor,LOAD 'lolor'and any call that reaches one of lolor's functions fail withlolor must be loaded via "shared_preload_libraries"; the nativepg_catalog.lo_*functions are not affected while lolor is not installed or is disabled. The drop guard below is an object access hook, which is only present in every backend and worker when the library is preloaded, and a missing or broken library now fails at server start rather than at the firstlo_open(). Upgrade in this order: install the new library files, addlolortoshared_preload_libraries, restart, then runALTER EXTENSION lolor UPDATEin each database. Between the file install and the restart, any session that loads the new library on demand gets that error, so restart promptly. - Removing the extension is guarded by an object access hook.
DROP EXTENSION,DROP SCHEMA lolor CASCADE,DROP OWNED BYand any other path that reaches the extension are refused while lolor is enabled or while any object remains in lolor storage, checked in the catalog at the moment of deletion from whichever backend performs it. The drop no longer migrates the objects out itself: runlolor.migrate_to_native()first. The event trigger only puts the nativepg_catalog.lo_*names back. The new superuser settinglolor.allow_unsafe_dropskips both checks and discards the contents of lolor storage. Because the drop renames the native functions back, it has the same requirements aslolor.disable(): no other session connected to the database, and a client session. lolor.enable()andlolor.disable()refuse to run from anything but a client session, so replicated DDL cannot execute them inside an apply worker, where a refusal would leave that node's replication retrying forever.lolor.enable(),lolor.disable()andlolor.is_enabled()now probe exact function signatures inpg_catalog. They previously matched onpronameacross every schema, so any user withCREATEon any schema could define a function namedlolor_lo_openand wedge lolor into a permanent "inconsistent state" that also blockedDROP EXTENSION. Both now emit a notice that client sessions must reconnect, since libpq caches the large object function OIDs per connection, and refuse while any other session is connected to the database, since a renamed function keeps its OID and other sessions cannot be made to see the switch.- Fixed the
lolor.nodeupper bound. The GUC accepted 0..16 while a generated OID reserves four bits for the node id, so node 16 did not fit: the node field of every OID it generated read back as 0. The bound is now derived from the encoding (LOLOR_MAX_NODE_ID), giving 0..15. If you havelolor.node = 16configured, the server still starts but logs16 is outside the valid range for parameter "lolor.node" (0 .. 15)and falls back to 0, which is the node id it was effectively using already. Set it to a value in 0..15, and check for OID collisions against whichever node is genuinely 0. - Expanded test coverage: TAP tests for dump/restore, streaming and logical replication, standby promotion, the drop paths, migration fidelity and locking, and cross-node OID collisions; regression tests for
lo_lseek,lo_tell,lo_truncate, the 64-bit interface, sparse objects, comment round trips,pg_shdependrestoration, permission enforcement, the orphan helpers and name squatting. The regression suite runs on its own preloaded instance. - Security hardening: addressed Codacy/Flawfinder warnings.
lolor 1.2.3
- Add support for PostgreSQL 19
- Add in-repo release workflow and RPM/DEB packaging
- Improve CI: build the test cluster from source, add PostgreSQL 19 to the test matrix, and bound test run time
lolor 1.2.2
- Fix lolor upgrades
- Fix issues with pg_upgrade. Note that this fixes upgrades with pg_upgrade going forward.
ALTER EXTENSION UPDATEfor upgrading the extension itself works, so if wanting to run pg_upgrade, first update your extension to 1.2.2, then run pg_upgrade - Address CVEs CVE-2022-26520, GHSA-673j-qm5f-xpv8
- PATH updated to match native Postgres packaging layout
- Make table OID caching safer