Skip to content

v1.76.0 — Algol

Latest

Choose a tag to compare

@MichaelSowah MichaelSowah released this 08 Aug 08:02
c4acdc1

The complete database-layer roadmap in one release — the native alternative to Doctrine DBAL. Database failures are now typed, retries run on one honest
configurable budget, SQLite alterations are fail-closed, and connections recover from loss — with commit-ambiguous losses never replayed.

Highlights

Typed database exceptions

Glueful\Database\Exceptions: UniqueConstraintViolationException, ForeignKeyConstraintViolationException, NotNullConstraintViolationException (under a
ConstraintViolationException parent), DeadlockException, SerializationFailureException, LockContentionException, and ConnectionLostException — classified
from SQLSTATE plus vendor codes (vendor-first, because MySQL reports deadlocks under SQLSTATE 40001), rooted at DatabaseException extends \PDOException so every
existing catch keeps working, with full state preservation and sqlState()/driverCode()/driver() accessors. TransactionManager retries by marker interface —
PostgreSQL 40P01 deadlocks finally retry — and unique violations render HTTP 409 with a fixed message that never leaks column or value detail.

SQLite alterations are fail-closed

Modify column, drop column, add/drop foreign key, rename column, and inline-unique drops previously generated comment SQL that executed as successful no-ops. They
now run through an audited, atomic create-copy-swap rebuild: a preservation audit fails closed before any DDL (generated columns, COLLATE, composite FKs,
indexes/triggers/views referencing changed columns, journal_mode=OFF), a global foreign_key_check runs before mutation and before commit, rowids and the
sqlite_sequence high-water mark survive, and the rebuilt table is re-introspected and canonically verified against the planned target. Anything unsupported throws
UnsupportedSchemaOperationException before mutation — a SQLite migration can never again succeed by doing nothing. The driver-neutral compile fix also delivers
complete change-sets to MySQL and PostgreSQL, whose rename and FK-drop changes were silently discarded before.

Reconnect resilience

One shared retry budget (DB_RETRY_MAX_ATTEMPTS / DB_RETRY_BACKOFF_MS, default 3 total attempts with 500 ms linear backoff — distinct from the pool's acquisition
retries) spans deadlock retries and connection-loss replays. Outermost Connection::transaction() calls reconnect and replay work the framework can prove
uncommitted; Connection::idempotentRead() re-runs caller-declared idempotent reads; Connection::reconnect() re-establishes outside transactions. A loss while
COMMIT is in flight is never replayed — the server may have committed before the acknowledgement was lost — and surfaces as the non-retryable
CommitOutcomeUnknownException with the connection invalidated for lazy reconnection. Failed savepoint rollbacks no longer leak the outer transaction, and
AlterTableBuilder::comment() — the last silent-no-op alteration path anywhere — now fails closed.

Upgrade Notes

  • Applications: composer update glueful/framework; optionally tune DB_RETRY_MAX_ATTEMPTS/DB_RETRY_BACKOFF_MS.
  • Audit SQLite-targeting migrations that modify/drop columns or foreign keys: their intent now actually applies (or fails loudly) instead of silently passing.
  • External implementors of framework interfaces: TransactionManagerInterface::transaction() gained an optional ?RetryBudget param; SchemaBuilderInterface
    gained executeSqliteRebuild() and executeSqliteNativeAlteration(); TableBuilderInterface gained rename().
  • Connection::transaction() retry tuning moved to configsetMaxRetries() still governs direct TransactionManager use only.
  • Replay callbacks must build query chains inside the callback from the supplied connection; prebuilt builders retain the stale PDO.
  • Code that retried on commit-phase ConnectionLostException was risking duplicate commits — it now sees CommitOutcomeUnknownException (still a PDOException
    subclass); audit any such handler.

Full changelog: v1.75.0...v1.76.0