Skip to content

5.0.0 release prep - #32

Merged
taplin merged 13 commits into
mainfrom
release-prep
Oct 13, 2026
Merged

taplin merged 13 commits into
mainfrom
release-prep

Conversation

@taplin

@taplin taplin commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Release prep for Perfect-PostgreSQL 5.0.0, the first release since 4.0.0 (2020). It requires Perfect-CRUD from: "3.0.1".

Fixes:

  • Connection parameters are quoted. PostgresDatabaseConfiguration(database:host:port:username:password:) pasted values into the conninfo string unquoted. A password containing a space failed, and a value like x sslmode=disable injected its own options. Values are now single-quoted per libpq's rules.
  • PostgresDatabaseConfiguration is Sendable. Before, CRUD's DatabaseConnectionPool and async methods didn't compile with this connector, though the README advertised them.
  • returning(insert: []) returns [] instead of trapping.
  • Whole-row returning(insert:) with every key ignored uses DEFAULT VALUES. It used to be a syntax error.

Other changes:

  • CI:
    • Linux Swift 6.2/6.4 × debug/release, run live against a postgres:17 service.
    • macOS Xcode 26.0.1/26.6, using Homebrew libpq.
  • Tests: live suites skip visibly without PG_TESTS=1; they used to silently count as passes.
  • README:
    • Requirements, libpq setup, usage, and "Migrating from 4.x".
    • Dynamic statements throw only after they've run.
    • Database.transaction isn't covered by PGConnection's lock, so use the pool rather than sharing one Database across tasks.
    • Isolation levels are ignored.
    • Known limitations.

Testing

64/64 live against PostgreSQL 17, with warnings as errors:

  • Xcode 27 and Xcode 26.3, debug and release;
  • Linux 6.2 and 6.4.

Re-run live on Perfect-CRUD 3.0.1 after the pin change.

Three adversarial reviews. The final one checked:

  • the pool's determinism (40/40);
  • DEFAULT VALUES/RETURNING edge cases;
  • every touched release-note claim against the live server.

🤖 Generated with Claude Code

taplin and others added 13 commits October 6, 2026 07:27
A package that depends on another by branch can't be resolved by a
version-pinned consumer, so 5.0.0 needs CRUD's release. Resolves to
CRUD 3.0.0 (swift-log 1.15.1). No Package.resolved is committed
(*.resolved is ignored).

53/53 tests pass against postgres:17 with -warnings-as-errors in debug
and release on Xcode 27 (Swift 6.4), Xcode 26.3 (Swift 6.2.4), and
Linux swift:6.2-noble / swift:6.4-noble.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Modeled on Perfect-CRUD's workflow. Linux runs Swift 6.2 and 6.4 in the
swift:X-noble container with libpq-dev and a postgres:17 service (health
checked), and runs the live tests with PG_TESTS=1 PGUSER=postgres. The
tests connect with host=localhost, which PGHOST can't override, so the
job sets libpq's PGHOSTADDR to the service's address; that mechanism
was checked locally (Linux containers reaching a separate postgres:17
container through PGHOSTADDR), not on GitHub.

macOS runs Xcode 26.0.1 and 26.6 on macos-26 with Homebrew libpq and
PKG_CONFIG_PATH (Swift 6.2's SwiftPM doesn't find the keg-only libpq
without it); there's no server there, so the live tests skip. No iOS
job: libpq is a host system library and the package declares only macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…...)

The values were pasted into the libpq conninfo string unquoted, so a
password (or database, host or user name) containing a space made the
string unparseable, and a value such as "x sslmode=disable" added a
connection option of its own. Each value is now single-quoted with \
and ' backslash-escaped, as libpq's conninfo syntax requires. The
behaviour is unchanged since 4.0.0 for values without spaces, quotes or
backslashes.

ConnectionInfoTests parses the result with libpq's PQconninfoParse (no
server needed) and, with PG_TESTS=1, connects with a password containing
a space. Without the fix 4 of the 5 tests fail; plainValues passes on
both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Install snippet uses from: "5.0.0" (was branch: "main" with a
  "no tagged releases" note).
- Requirements: Linux is supported and CI-tested; the old text called it
  implicit and not guaranteed. Badges list macOS 12+ and Linux.
- libpq setup: Homebrew's libpq is keg-only; Swift 6.2's SwiftPM
  doesn't find it without PKG_CONFIG_PATH (checked with Xcode 26.3:
  'libpq-fe.h' file not found), Swift 6.4's does.
- A usage example, and what the CRUD backend doesn't support: Dynamic
  select (throws once a row comes back), affectedRows (always 0),
  insertedID (nil) and transaction isolation levels (ignored). The
  example and each of these were checked against postgres:17 from a
  scratch consumer package.
- Testing: PG_TESTS, the databases the tests drop, and PGPORT /
  PGHOSTADDR for other servers.
- A "Migrating from 4.x" section and the legacy branch pointer
  (legacy is the 4.0.0 commit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Most of them return early and count as passed rather than skipping; only
SubTableCreateOrderLiveTests uses .enabled(if:).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checks for the crash patterns found in the Perfect-SQLite review. None
of them applies here, so there's no code change:

- The connector doesn't implement nextDynamicRow(), so Dynamic select
  (and execute(DynamicSQL) returning rows) throws CRUD's
  CRUDSQLExeError once a row comes back; no DynamicRow is built, so
  columns differing only by case (SELECT 1 AS a, 2 AS "A") can't hit
  DynamicRow.init's duplicate-key trap. An empty result returns empty.
- DynamicSQL with allowsMultipleStatements: true throws: PQexecParams
  takes one statement ("cannot insert multiple commands into a prepared
  statement") and neither statement runs. Without the flag, CRUD's
  guard throws CRUDSQLGenError.
- Unsigned values are bound as decimal text, with no Int conversion:
  .uint(UInt64.max) stores exactly in a numeric column and gets a
  server error (not a trap) for a bigint one; .uinteger/.uinteger64/
  32/16/8 at their maximums round-trip.
- affectedRows is 0 and insertedID nil for mutations.

62/62 pass against postgres:17 with -warnings-as-errors in debug and
release on Xcode 27, Xcode 26.3, and Linux swift:6.2-noble and
swift:6.4-noble.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Perfect-CRUD 3.0.0's DatabaseConnectionPool, transactionAsync,
sqlAsync, insertAsync, fetchAll and the other async entry points require
a Sendable configuration, so none of them compiled with this connector.
Its only stored property is a PGConnection, which is Sendable.

PoolAndAsyncTests (live, skipped without PG_TESTS=1) runs a pool with a
transactionAsync round trip, a rolled-back transactionAsync, insertAsync,
fetchAll, firstAsync, and 20 concurrent pooled inserts. Without the
conformance the test target doesn't compile ("type
'PostgresDatabaseConfiguration' does not conform to the 'Sendable'
protocol").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both _insert variants read instances[0] before checking the count, so
returning(insert: []) and returning(_:insert: []) trapped with "Index
out of range". They now return [] without touching the database.

returningInsertEmptyArray (live) covers the whole-row and key-path
forms, with and without setKeys/ignoreKeys; it crashes the test
process without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With every key ignored, the whole-row form generated
INSERT INTO "t" () VALUES () RETURNING *, a syntax error in
PostgreSQL; the key-path form already used DEFAULT VALUES. Both now do.

emptyInsert (live) now checks the key-path form's returned default and
the whole-row form for one row and for several; without the fix it fails
with 'syntax error at or near ")"'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46 live tests began with 'guard pgEnabled else { return }', so without
PG_TESTS=1 they reported as passed. PerfectPostgreSQLTests (and its
QuoteIdentifierTests extension), PGConnectionParameterTests,
PGConnectionConcurrencyTests and DynamicAPITests now carry a suite-level
.enabled(if: pgTestsEnabled) trait and the guards are gone, so without a
server they show as skipped. The two server-free tests that lived in
those suites moved to their own suites so they still run: dateFormat
(DateFormatTests) and quoteIdentifierDoublesEmbeddedQuotes
(QuoteIdentifierUnitTests).

Without a server: 64 tests, 9 run and pass, the rest skipped. With
postgres:17: 64/64 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dynamic select/execute throw only after the statement has run (and
  committed under autocommit), so a retried INSERT ... RETURNING through
  execute inserts twice.
- PGConnection's lock doesn't cover CRUD's Database.transaction /
  transactionAsync, which send BEGIN and COMMIT/ROLLBACK as separate
  calls; a Database used for transactions mustn't be shared between
  tasks. Points at the pool, which now works (the configuration is
  Sendable). Drops "there's no async/await API".
- Known limitations: NUL in a String, bytea_output = escape, NULL into a
  non-optional Int/String, millisecond Dates, and CRUD 3.0.0's trap for
  a Codable UInt/UInt64 above Int.max.
- Reconcile fails with "must be owner" only when it needs to alter a
  table the role doesn't own.
- Migration: the quoted connection parameters, including options that
  were appended inside a value; swift-log covers only the connector's
  own diagnostic, CRUD still logs through CRUDLogging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.0.1 fixes four crashes in 3.0.0 reachable from public API, including
a Codable UInt/UInt64 above Int.max trapping in CRUD's encoder (now
bound as unsigned). Tests pass against 3.0.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@taplin
taplin merged commit 60fdd89 into main Oct 13, 2026
16 checks passed
@taplin
taplin deleted the release-prep branch October 13, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant