Skip to content

Complete Database persistence lifecycles and current Laravel parity#452

Merged
binaryfire merged 27 commits into
0.4from
audit/database-persistence-lifecycles
Jul 24, 2026
Merged

Complete Database persistence lifecycles and current Laravel parity#452
binaryfire merged 27 commits into
0.4from
audit/database-persistence-lifecycles

Conversation

@binaryfire

Copy link
Copy Markdown
Collaborator

Summary

This PR completes the Database persistence lifecycle audit and brings the supported Database surface up to date with current Laravel.

The main goal is simple: a pooled connection must always have one clear owner, and every transaction state change must reflect what actually happened on the underlying connection. The same rules now hold across coroutine requests, non-coroutine Swoole tasks, process forks, test workers, SQLite memory databases, and Redis operations that pin a connection.

For the full design and decision record, see the implementation plan.

Lifecycle ownership

  • Add a terminal task event after task action and result handling.
  • Add a final pre-fork event immediately before the native Swoole server starts.
  • Retain the exact Database wrappers borrowed by non-coroutine task workers and release them at task completion.
  • Discard already-created Database and Redis resources before process forks and when child workers start.
  • Replace reflective Database context cleanup with resolver-owned cleanup.
  • Keep cleanup failure-safe: independent resources are still closed when another cleanup step fails, while the earliest failure remains visible.

These events are cold lifecycle boundaries. They do not add work to ordinary HTTP requests or Database queries.

Redis connection state

  • Keep WATCH, MULTI, PIPELINE, EXEC, UNWATCH, RESET, and DISCARD on the correct native connection.
  • Prevent abandoned queueing or watched clients from returning to the pool.
  • Route the Redis DISCARD command around the existing pool lifecycle method with the same name.
  • Register at most one terminal release callback per Redis pool and coroutine.
  • Preserve immediate release for closure-based transactions and pipelines, including long-running coroutines.
  • Use the owning coroutine ID so copied context cannot inherit cleanup ownership that belongs to its parent.
  • Complete command event handling without allowing a throwing listener to leak the borrowed connection.
  • Make manager purge and lifecycle cleanup use the proxy's actual pool identity.

The release check reads local phpredis state only. It does not send another Redis command.

SQLite behavior

  • Add one SQLite database-name classifier for literal memory databases, URI memory databases, and file URIs.
  • Use it across connection creation, pooling, schema loading, RefreshDatabase, parallel testing, Testbench, and integration fixtures.
  • Resolve the canonical attached SQLite path before refreshing a file-backed database.
  • Reject URI names at APIs that require a plain filesystem path instead of treating the URI as a literal filename.
  • Limit a shared in-memory SQLite PDO to one pool owner.
  • Make base, read, and write aliases share that owner when they point at the same physical PDO.
  • Handle mixed file and memory connections correctly during test database refresh.

The existing pool channel remains the only synchronization primitive. No second lock or SQLite connection coordinator is introduced.

Transaction correctness

  • Publish logical transaction state only after the corresponding physical operation succeeds.
  • Separate callback, event, physical commit, manager callback, rollback, and disconnect phases.
  • Roll back before retrying a concurrency failure.
  • Do not retry when rollback cleanup failed.
  • Preserve the original operation failure when cleanup also fails.
  • Detach transaction records before invoking rollback callbacks.
  • Run rollback cleanup callbacks exhaustively and in deepest-first order.
  • Preserve Laravel's existing after-commit callback ordering.
  • Clear lost physical sessions and PDO references without publishing false reusable state.
  • Restore query logging state through finally.
  • Preserve rollback callbacks registered inside committed savepoints when an outer transaction later rolls back.

This fixes both Hypervel-specific pooled reuse failures and transaction defects shared with upstream behavior.

Eloquent and query updates

  • Serialize only an incomplete first model boot, then keep normal construction lock-free.
  • Allow post-publication model construction from boot callbacks while rejecting pre-publication recursion.
  • Let a failed pre-publication boot retry cleanly.
  • Move Scout and NestedSet model-dependent setup out of the incomplete publication window.
  • Resolve class attributes carried by traits.
  • Add #[RouteKey].
  • Add model incrementEach, decrementEach, quiet variants, and saveOrIgnore.
  • Preserve false when model events veto increment or decrement operations.
  • Add closure-valued defaults across the current create-or-first and relation APIs.
  • Correct relationship touching, morph matching, nested removed scopes, timestamp columns, soft-delete restore events, enum model IDs, and nested resource discovery.

The query builder now includes the supported current Laravel additions:

  • MySQL and MariaDB straight joins.
  • Null-safe equality.
  • Explicit value ordering with inOrderOf.
  • MySQL and MariaDB query timeouts.
  • Conflict-safe returning inserts for PostgreSQL and SQLite.
  • DatePeriod bounds.
  • PostgreSQL date, time, full-text, and update-subquery corrections.

Raw SQL placeholder substitution now uses an index rather than repeatedly shifting the bindings array.

Schema, migrations, and exceptions

  • Add foreignUuidFor.
  • Add hasForeignKey and matching facade metadata.
  • Add PostgreSQL tsvector columns.
  • Add native MariaDB vector indexes.
  • Correct PostgreSQL collation inspection and sequence initialization.
  • Add migration filenames to migration events.
  • Add schema dumps that omit migration rows while retaining the migration table.
  • Correct missing-database handling in migrate:fresh.
  • Update lost-connection detection and MariaDB client-version handling.
  • Expose unique constraint names and columns when the native driver provides them.
  • Use the active PostgreSQL read or write PDO options when handling emulated prepares.

Unsupported drivers and dynamic connection APIs remain intentionally unsupported.

Performance

This work does not add a lock, yield, retry, container lookup, network request, or registry to the normal Database query path.

The recurring correctness costs are small local checks:

  • one static owner lookup during normal model construction;
  • one local phpredis mode read when returning a Redis connection;
  • direct local branches around Redis ownership cleanup.

The work also removes hot-path work:

  • ordinary Eloquent attribute reads merge only the requested cached cast;
  • raw SQL binding substitution is linear rather than repeatedly reindexing the binding list;
  • repeated closure-based Redis operations no longer retain one deferred callback per call.

Package and documentation updates

  • Complete Database and Redis split-package dependencies.
  • Add manifest regressions so monorepo transitive dependencies cannot hide missing declarations.
  • Record Database package provenance and deliberate deprecated omissions.
  • Document the new Query, Eloquent, Routing, Migration, Schema, and testing behavior.
  • Update the audit ledger and package routing records.

No existing supported Laravel API is removed or renamed.

Validation

  • Ran the formatter and both static-analysis configurations.
  • Ran the complete parallel component suite.
  • Ran the Testbench package contract suite and dogfood project.
  • Ran focused Database, Redis, Core, Server, Foundation testing, Testbench, Scout, NestedSet, and Telescope suites.
  • Exercised configured SQLite and Redis integration coverage, including real pooled transaction and connection-state behavior.
  • Reviewed the complete diff for lifecycle ownership, failure precedence, API parity, documentation accuracy, hot-path costs, retained worker state, dead code, and unnecessary abstractions.

Record the complete, reviewed implementation plan for the Database audit and its required Redis, Core, Server, Pool, Support, and documentation boundaries.\n\nCapture exact task and fork ownership, pooled Redis transaction state, SQLite classification and single-owner pooling, truthful transaction cleanup, coroutine-safe Eloquent boot publication, current Laravel parity, performance corrections, metadata, documentation, regression coverage, and full validation criteria.\n\nDocument the evidence behind each mechanism, every owner-approved hot-path cost, and the rejected alternatives so implementation can remain complete without accumulating speculative abstractions or compatibility debris.
Add a TaskTerminated event that carries the exact native server and task after task action and result handling completes.

Dispatch terminal cleanup through a listener-aware boundary on every success and failure path while preserving the earliest action, finish, or cleanup throwable. Cover both native task signatures, dispatch ordering, no-listener behavior, and combined failure precedence.
Add BeforeServerFork as the final parent-process boundary immediately before the native Swoole server starts and forks its child processes.

Allow lifecycle consumers to discard parent-owned sockets, pools, timers, and context without relying on per-port events. Preserve native start failure behavior and cover exact event payload, ordering, listener failure, and false start results.
Retain exact non-coroutine pooled wrappers in ConnectionResolver and detach their context before terminal release or process-safe discard. Replace reflective configured-name cleanup with a concrete lifecycle listener that never resolves unused owners and exhausts independent cleanup phases.

Register task, pre-fork, and worker-start boundaries through DatabaseServiceProvider, bind the queue entity resolver at its canonical contract, and cover aliases, read and write roles, publication failures, reentrant cleanup, custom resolvers, unresolved services, and failure precedence.
Register at most one terminal defer per pool and owning coroutine while keeping callback-form transactions and pipelines on their immediate-release path. Use coroutine owner IDs so copied context cannot inherit a defer that the child does not own.

Keep WATCH, MULTI, PIPELINE, EXEC, UNWATCH, RESET, and native DISCARD state on the exact wrapper generation. Complete command event handling before ownership handoff, discard abandoned native queue or watch state, and preserve established failure precedence without network checks or a parallel transaction model.

Add unit and live Redis coverage for copied contexts, bounded defer retention, callback release, optimistic conflicts, listener failures, selected databases, native mode recovery, and pool generation replacement.
Aggregate already-created proxies through RedisManager so task completion releases their exact pinned wrappers and process boundaries discard them before flushing resolved pools.

Make purge use the proxy real pool identity, detach context before pool destruction, continue independent manager and factory cleanup after failures, and avoid resolving unused services. Register the lifecycle only for the applicable task mode and at both pre-fork and worker-start boundaries.

Cover non-coroutine MULTI, PIPELINE, SELECT, and WATCH ownership, abandoned transactions, selected-database restoration, divergent manager and pool names, unresolved services, and cleanup failure precedence.
Add a native-faithful SQLiteDatabase classifier for literal memory databases, URI memory forms, and file URIs, then route connection creation, schema loading, integration fixtures, and filesystem management through that single owner.

Resolve the canonical attached file before refreshing a live database, preserve its inode, reject URI names at plain-path management boundaries, and surface failed truncation instead of reporting false success.

Cover the complete URI matrix, standalone connector behavior, CLI routing, canonical path refresh, invalid filesystem operations, and WAL fixture handling without introducing URI rewriting or another database-name abstraction.
Normalize valid in-memory SQLite pools to one managed wrapper because every wrapper shares the same physical PDO. Reuse the existing pool channel as the only synchronization primitive and preserve PoolOption as the validator for invalid configurations.

Canonicalize unsplit base, read, and write aliases around the shared owner in both runtime and testing resolvers. Update concurrency, session-configuration, persistence, query-log, alias-flush, and pool-capacity tests to prove deterministic transfer without adding another lock or coordinator.
Use SQLiteDatabase across RefreshDatabase, application parallel testing, package testing, and Testbench so every supported memory and URI form receives the same lifecycle decision.

Cache only physical memory PDOs in mixed transaction sets, keep the live refresh connection override, avoid suffixing memory databases, and fail fast when automatic file management receives a non-memory URI. Preserve assigned ParaTest identity and the existing without-databases escape hatch.

Add focused coverage for mixed connections, both parallel systems, Testbench setup and cleanup, plain-path suffixing, URI rejection, and documented test configuration behavior.
Separate callback, committing event, physical commit, logical publication, manager callback, rollback, and disconnect phases so each state changes only after its owning operation succeeds.

Rollback before concurrency retries, suppress retries after failed cleanup, preserve the earliest operation failure, detach lost sessions terminally, clear PDO references exhaustively, and invalidate session configuration at every physical rollback boundary. Restore query logging in finally and publish unique-violation metadata from the connection boundary.

Detach transaction records before rollback callbacks, exhaust cleanup deepest first, retain commit callback ordering, and cover begin, commit, rollback, savepoint, retry, listener, manager, disconnect, pooled reuse, and failure-precedence edges with deterministic regressions.
Port the supported current Laravel query surface: MySQL straight joins and timeouts, null-safe equality, explicit value ordering, conflict-safe returning inserts, DatePeriod bounds, escaped column operators, PostgreSQL date and full-text behavior, delimited aggregates, and builder-backed update subqueries.

Keep unsupported grammars explicit, preserve binding order and modified-record state, and use indexed placeholder substitution so raw SQL rendering is linear. Render live and closed resources through their stable identity string without consuming stream contents.

Merge current builder, grammar, relationship, and Telescope coverage for call shapes, SQL dialects, bindings, validation, expressions, unions, conflicts, and resource handling.
Synchronize only the requested class or Attribute cast during ordinary attribute reads instead of walking and serializing every cached cast on the model.

Retain full cached-cast merges around legacy and Attribute get and set mutators because those callbacks may inspect sibling state. Preserve mutable and encrypted cast persistence, add ARRAY_AS_PROPS behavior for encrypted array objects, and cover unrelated-cast avoidance alongside sibling-mutator visibility.
Serialize only incomplete first model boot publication with an exact coroutine owner and the existing model-class mutex. Permit post-publication recursion, retry failed pre-publication boot, keep successful publication after later hook failure, and leave the stable construction path at one static owner lookup.

Move Scout model-dependent setup after publication and remove NestedSet model construction from boot. Resolve class attributes carried by traits and add RouteKey support without changing explicit overrides.

Port model increment and decrement collections, quiet variants, truthful event-veto returns, and saveOrIgnore with conflict-safe state publication. Cover recursive, concurrent, non-coroutine, failed, and post-publication boot together with trait attributes, route keys, model events, casts, IDs, timestamps, and conflict behavior.
Port closure-valued create and update defaults across builders and relations without evaluating them on already-found paths. Propagate removed nested scopes, reject private attributed scopes, touch exact related keys and multiple timestamp columns, and normalize MorphTo dictionary keys.

Publish restored events only after a successful save, normalize enum model IDs at the exception string boundary, preserve class-string and callback inference, and guess resources from the complete nested model namespace.

Merge the current unit and integration matrix for create-or-first families, relation updates, scope visibility, eager morph matching, soft-delete events, enum IDs, timestamps, and nested resource discovery.
Add model-aware foreign UUIDs, foreign-key inspection, PostgreSQL tsvector columns, and native MariaDB vector indexes while keeping driver support explicit.

Correct PostgreSQL pre-9.1 collation inspection and sequence initialization for wrapped custom schemas, expose facade metadata, and retain concise markers for deliberately omitted deprecated forwarding.

Port the supported blueprint, builder, MariaDB, PostgreSQL, and SQLite grammar regressions for inferred keys, names and column lists, vector SQL, server versions, and exact sequence expressions.
Attach the offending constraint name and available columns to UniqueConstraintViolationException while preserving the original query, bindings, driver exception, connection details, and read or write classification.

Parse native MySQL, MariaDB, PostgreSQL, and SQLite messages at each driver boundary and degrade to null or an empty list when the server omits usable metadata. Also honor the active PostgreSQL read or write PDO configuration when deciding emulated-prepare boolean handling.

Cover parsed and unparseable unit messages, live supported-driver violations, multiple columns, named constraints, and read versus write prepare options.
Expose migration filenames on start and end events and allow schema dumps to retain table definitions while omitting migration rows. Keep nullable migration-table state explicit through every schema-state implementation.

Let migrate fresh recover through the existing migration path when repository inspection cannot reach a missing database, report the correct prune option, recognize current lost-connection messages, and detect MariaDB client versions with the documented minimum fallback.

Refresh callback generic metadata and cover migration names, dump modes, missing databases, command validation, client detection, and schema-state output on supported environments.
Declare every direct Database runtime dependency required by the split package and remove the stale concrete Config dependency after deleting reflective task cleanup.

Record Laravel provenance and the deliberate omission of directly deprecated compatibility forwarding. Add source markers at natural upstream sync points and a manifest regression that verifies the complete direct dependency set without relying on the monorepo graph.
Declare direct Swoole, Core lifecycle, and PSR logging dependencies used by Redis source while retaining phpredis as an optional connector capability.

Add a split-manifest regression that checks the complete direct runtime dependency set and prevents monorepo-only transitive availability from hiding missing package requirements.
Document MySQL and MariaDB straight joins, null-safe equality, explicit value ordering, per-query timeouts, and conflict-safe returning inserts at their existing task-oriented sections.

Use the final public signatures, show realistic application examples, and state driver boundaries without exposing grammar or pooled-connection internals.
Document saveOrIgnore and model-instance incrementEach and decrementEach operations beside the existing persistence and atomic update guidance.

Describe event behavior, quiet variants, conflict return values, and supported PostgreSQL and SQLite boundaries without duplicating query-builder material or internal publication mechanics.
Add the RouteKey class attribute beside the existing getRouteKeyName customization and show how it configures implicit model binding with a slug.

Keep the explicit method override available and describe the public application-facing behavior without exposing class-attribute caching internals.
Document model-aware foreign UUIDs, foreign-key inspection, PostgreSQL tsvector columns, MariaDB vector indexes, schema dumps without migration rows, and migration event filenames.

Place each addition beside the existing schema or event task, preserve distinct PostgreSQL vector guidance, and state supported-driver behavior using the final source signatures.
Finalize the signed implementation plan with every discovered ownership, transaction, Redis, SQLite, Eloquent, parity, metadata, documentation, performance, and anti-overengineering decision reflected in the delivered code.

Record database-05 through database-13 and redis-03 through redis-08 in the audit ledger, update cross-package routing and revalidation status, capture the complete validation and review result, and mark the Database package checklist complete.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 161 files, which is 61 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dc08f037-498a-4390-a15c-2cd64fc88a2d

📥 Commits

Reviewing files that changed from the base of the PR and between ab47053 and baca71e.

📒 Files selected for processing (163)
  • docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
  • docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
  • docs/plans/2026-07-24-1013-database-persistence-lifecycles-and-current-laravel-parity.md
  • src/boost/docs/eloquent.md
  • src/boost/docs/migrations.md
  • src/boost/docs/queries.md
  • src/boost/docs/routing.md
  • src/boost/docs/testing.md
  • src/core/src/Bootstrap/TaskCallback.php
  • src/core/src/Events/BeforeServerFork.php
  • src/core/src/Events/TaskTerminated.php
  • src/database/README.md
  • src/database/composer.json
  • src/database/src/Concerns/ManagesTransactions.php
  • src/database/src/Connection.php
  • src/database/src/ConnectionInterface.php
  • src/database/src/ConnectionResolver.php
  • src/database/src/Connectors/SQLiteConnector.php
  • src/database/src/Console/DumpCommand.php
  • src/database/src/Console/Migrations/FreshCommand.php
  • src/database/src/Console/PruneCommand.php
  • src/database/src/DatabaseManager.php
  • src/database/src/DatabaseServiceProvider.php
  • src/database/src/DatabaseTransactionRecord.php
  • src/database/src/DatabaseTransactionsManager.php
  • src/database/src/Eloquent/Attributes/RouteKey.php
  • src/database/src/Eloquent/Builder.php
  • src/database/src/Eloquent/Casts/AsEncryptedArrayObject.php
  • src/database/src/Eloquent/Concerns/HasAttributes.php
  • src/database/src/Eloquent/Concerns/HasEvents.php
  • src/database/src/Eloquent/Concerns/HasRelationships.php
  • src/database/src/Eloquent/Concerns/HasTimestamps.php
  • src/database/src/Eloquent/Concerns/QueriesRelationships.php
  • src/database/src/Eloquent/Concerns/TransformsToResource.php
  • src/database/src/Eloquent/Factories/Factory.php
  • src/database/src/Eloquent/Model.php
  • src/database/src/Eloquent/ModelNotFoundException.php
  • src/database/src/Eloquent/Relations/BelongsToMany.php
  • src/database/src/Eloquent/Relations/HasOneOrMany.php
  • src/database/src/Eloquent/Relations/HasOneOrManyThrough.php
  • src/database/src/Eloquent/Relations/MorphTo.php
  • src/database/src/Eloquent/SoftDeletes.php
  • src/database/src/Events/MigrationEvent.php
  • src/database/src/Grammar.php
  • src/database/src/Listeners/DatabaseConnectionLifecycleListener.php
  • src/database/src/Listeners/UnsetContextInTaskWorkerListener.php
  • src/database/src/LostConnectionDetector.php
  • src/database/src/Migrations/Migrator.php
  • src/database/src/MySqlConnection.php
  • src/database/src/Pool/DbPool.php
  • src/database/src/PostgresConnection.php
  • src/database/src/Query/Builder.php
  • src/database/src/Query/Grammars/Grammar.php
  • src/database/src/Query/Grammars/MySqlGrammar.php
  • src/database/src/Query/Grammars/PostgresGrammar.php
  • src/database/src/Query/Grammars/SQLiteGrammar.php
  • src/database/src/Query/Processors/MySqlProcessor.php
  • src/database/src/SQLiteConnection.php
  • src/database/src/SQLiteDatabase.php
  • src/database/src/Schema/Blueprint.php
  • src/database/src/Schema/Builder.php
  • src/database/src/Schema/Grammars/Grammar.php
  • src/database/src/Schema/Grammars/MariaDbGrammar.php
  • src/database/src/Schema/Grammars/PostgresGrammar.php
  • src/database/src/Schema/MariaDbSchemaState.php
  • src/database/src/Schema/SQLiteBuilder.php
  • src/database/src/Schema/SchemaState.php
  • src/database/src/Schema/SqliteSchemaState.php
  • src/database/src/UniqueConstraintViolationException.php
  • src/foundation/src/Testing/Concerns/InteractsWithParallelDatabase.php
  • src/foundation/src/Testing/DatabaseConnectionResolver.php
  • src/foundation/src/Testing/RefreshDatabase.php
  • src/nested-set/src/HasNode.php
  • src/redis/composer.json
  • src/redis/src/Listeners/RedisConnectionLifecycleListener.php
  • src/redis/src/RedisConnection.php
  • src/redis/src/RedisManager.php
  • src/redis/src/RedisProxy.php
  • src/redis/src/RedisServiceProvider.php
  • src/redis/src/Traits/MultiExec.php
  • src/scout/src/Searchable.php
  • src/server/src/Server.php
  • src/support/src/Facades/Schema.php
  • src/testbench/src/Concerns/Database/InteractsWithSqliteDatabaseFile.php
  • src/testbench/src/Concerns/HandlesDatabases.php
  • src/testing/src/Concerns/TestDatabases.php
  • tests/Core/Bootstrap/TaskCallbackTest.php
  • tests/Database/ConnectionResolverSetDefaultConnectionTest.php
  • tests/Database/ConnectionResolverTest.php
  • tests/Database/DatabaseConnectionLifecycleListenerTest.php
  • tests/Database/DatabaseConnectionLostTest.php
  • tests/Database/DatabaseConnectionTest.php
  • tests/Database/DatabaseConnectorTest.php
  • tests/Database/DatabaseEloquentBelongsToManyCreateOrFirstTest.php
  • tests/Database/DatabaseEloquentBuilderCreateOrFirstTest.php
  • tests/Database/DatabaseEloquentBuilderTest.php
  • tests/Database/DatabaseEloquentGlobalScopesTest.php
  • tests/Database/DatabaseEloquentHasManyCreateOrFirstTest.php
  • tests/Database/DatabaseEloquentHasManyThroughCreateOrFirstTest.php
  • tests/Database/DatabaseEloquentModelAttributesTest.php
  • tests/Database/DatabaseEloquentModelTest.php
  • tests/Database/DatabaseEloquentMorphToTest.php
  • tests/Database/DatabaseEloquentRelationTest.php
  • tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php
  • tests/Database/DatabaseMariaDbSchemaGrammarTest.php
  • tests/Database/DatabaseMariaDbSchemaStateTest.php
  • tests/Database/DatabaseMigrationFreshCommandTest.php
  • tests/Database/DatabaseMySqlQueryGrammarTest.php
  • tests/Database/DatabasePostgresConnectionTest.php
  • tests/Database/DatabasePostgresSchemaGrammarTest.php
  • tests/Database/DatabaseQueryBuilderTest.php
  • tests/Database/DatabaseQueryGrammarTest.php
  • tests/Database/DatabaseSQLiteBuilderTest.php
  • tests/Database/DatabaseSchemaBlueprintTest.php
  • tests/Database/DatabaseSchemaBuilderIntegrationTest.php
  • tests/Database/DatabaseServiceProviderTest.php
  • tests/Database/DatabaseSessionConfiguratorTest.php
  • tests/Database/DatabaseSqliteSchemaStateTest.php
  • tests/Database/DatabaseTransactionsManagerTest.php
  • tests/Database/DatabaseUniqueConstraintViolationTest.php
  • tests/Database/Eloquent/Concerns/TransformsToResourceTest.php
  • tests/Database/Eloquent/ModelBootNonCoroutineTest.php
  • tests/Database/Eloquent/ModelBootTest.php
  • tests/Database/Fixtures/Models/Administration/Billing/TransformsToResourceTestNestedModel.php
  • tests/Database/PackageMetadataTest.php
  • tests/Database/PruneCommandTest.php
  • tests/Database/SQLiteDatabaseTest.php
  • tests/Foundation/Testing/Concerns/InteractsWithParallelDatabaseTest.php
  • tests/Foundation/Testing/DatabaseConnectionResolverTest.php
  • tests/Foundation/Testing/RefreshDatabaseTest.php
  • tests/Integration/Database/ConnectionCoroutineSafetyTest.php
  • tests/Integration/Database/DatabaseConnectionsTest.php
  • tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php
  • tests/Integration/Database/EloquentBelongsToManyTest.php
  • tests/Integration/Database/EloquentModelEncryptedCastingTest.php
  • tests/Integration/Database/EloquentModelScopeTest.php
  • tests/Integration/Database/EloquentMorphToEagerLoadTest.php
  • tests/Integration/Database/MigratorEventsTest.php
  • tests/Integration/Database/PooledConnectionTest.php
  • tests/Integration/Database/Sqlite/Console/MigrateFreshCommandWithJournalModeWalTest.php
  • tests/Integration/Database/Sqlite/DatabaseSchemaBlueprintTest.php
  • tests/Integration/Database/Sqlite/DatabaseSqliteSchemaBuilderTest.php
  • tests/Integration/Database/Sqlite/InMemorySqliteSharedPdoTest.php
  • tests/Integration/Database/Sqlite/SchemaStateTest.php
  • tests/Integration/Database/Sqlite/SqliteTestCase.php
  • tests/Integration/Database/UniqueConstraintViolationTest.php
  • tests/Integration/Horizon/Feature/SupervisorTest.php
  • tests/Integration/Redis/RedisProxyIntegrationTest.php
  • tests/Integration/Redis/RedisProxyNonCoroutineIntegrationTest.php
  • tests/NestedSet/NestedSetTest.php
  • tests/Redis/MultiExecTest.php
  • tests/Redis/PackageMetadataTest.php
  • tests/Redis/RedisConnectionLifecycleListenerTest.php
  • tests/Redis/RedisConnectionTest.php
  • tests/Redis/RedisManagerTest.php
  • tests/Redis/RedisProxyNonCoroutineTest.php
  • tests/Redis/RedisProxyTest.php
  • tests/Redis/RedisServiceProviderTest.php
  • tests/Scout/Feature/SearchableModelTest.php
  • tests/Server/ServerTest.php
  • tests/Telescope/Watchers/QueryWatcherTest.php
  • tests/Testbench/DefaultConfigurationTest.php
  • tests/Testing/Concerns/TestDatabasesTest.php

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/database-persistence-lifecycles

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR completes a large-scope database and Redis persistence lifecycle audit for Hypervel, bringing connection ownership, transaction correctness, SQLite memory database handling, Eloquent model boot serialization, and query/schema APIs up to date with current Laravel.

  • Lifecycle ownership: adds TaskTerminated and BeforeServerFork events, retains and releases non-coroutine task connections, and discards DB/Redis resources before process forks and at worker start via new DatabaseConnectionLifecycleListener and RedisConnectionLifecycleListener.
  • Transaction correctness: separates physical commit from logical state publication, introduces failure-safe rollback callbacks, defers committing event to just before commit, adds terminateTransactionState for lost-connection cleanup, and fixes callback ordering/deepest-first rollback execution.
  • Redis state: tracks WATCH state on RedisConnection, prevents watched/queueing connections from returning to the pool, routes discard around the pool lifecycle method via discardTransaction, and registers at most one deferred release callback per pool/coroutine.
  • Eloquent and query: adds coroutine-safe model boot serialization via Mutex, new incrementEach/decrementEach/quiet variants, saveOrIgnore, RouteKey attribute, and per-key mergeAttributeFromCachedCasts for hot-path optimization.

Confidence Score: 5/5

This PR is safe to merge. The changes are well-scoped to cold lifecycle boundaries and correctness fixes — they do not add work to the normal HTTP request or database query path.

Every new mechanism has a clear owner, a verified failure path, and a corresponding test. Transaction state changes are now published only after their physical operations succeed. Redis pool connections left in WATCH or MULTI/PIPELINE mode are discarded rather than returned. Model boot serialization via Mutex prevents the coroutine race that existed before. The lifecycle cleanup is failure-safe and idempotent. No existing public API is removed or renamed.

No files require special attention.

Important Files Changed

Filename Overview
src/database/src/Concerns/ManagesTransactions.php Transaction lifecycle hardened: committing event fires before physical commit, rollback callbacks run exhaustively in deepest-first order, concurrency retry blocked if rollback fails, lost-connection cleanup calls terminateTransactionState, beginTransaction rolls back on publication failure.
src/database/src/DatabaseTransactionsManager.php rollback/removeAllTransactions refactored to collect+detach records before running callbacks, executeRollbackCallbacks runs all callbacks exhaustively preserving the first exception, callback ordering deepest-first, partial recursion bug in removeCommittedTransactionsThatAreChildrenOf fixed to return descendants.
src/database/src/Eloquent/Model.php bootIfNotBooted serialized with Mutex for coroutine safety; adds $booting static tracker for recursion detection; new incrementEach/decrementEach/quiet variants; saveOrIgnore with performInsertOrIgnore; RouteKey attribute resolution; trait class attribute resolution; isScopeMethodWithAttribute excludes private methods.
src/redis/src/RedisProxy.php watch added to shouldUseSameConnection; discard routed to discardTransaction(); deferred release registered at most once per coroutine via DEFERRED_RELEASE_OWNER_CONTEXT_KEY_PREFIX; releaseContextConnection/discardContextConnection made public for lifecycle use.
src/redis/src/RedisConnection.php $watching state tracked on connection; release() discards rather than returns connection left in WATCH or MULTI/PIPELINE mode; discardTransaction/clearWatchState added as internal methods; markReconnected clears watching state.
src/database/src/ConnectionResolver.php Non-coroutine task connections retained in $nonCoroutineConnections and released/discarded via new public releaseConnections/discardConnections; shared in-memory SQLite PDO role aliases share single pool wrapper owner.
src/database/src/Pool/DbPool.php In-memory SQLite pool limited to max_connections=1 at construction; isInMemorySqlite and ensureNotDerivedInMemorySqlitePool now delegate to SQLiteDatabase::isInMemory classifier.
src/database/src/SQLiteDatabase.php New utility class for SQLite URI/in-memory classification: isUri detects file: prefix, isInMemory handles :memory: literal, URI :memory: path, and mode=memory query parameter.
src/database/src/DatabaseServiceProvider.php Wires DatabaseConnectionLifecycleListener to TaskTerminated (when task coroutines disabled), BeforeServerFork, and BeforeWorkerStart; replaces UnsetContextInTaskWorkerListener; registers QueueEntityResolver; converts array-access to make().
src/redis/src/RedisServiceProvider.php Adds boot() to wire RedisConnectionLifecycleListener to TaskTerminated/BeforeServerFork/BeforeWorkerStart with same task-coroutine guard as database provider.

Reviews (2): Last reviewed commit: "test(horizon): bound supervisor process ..." | Re-trigger Greptile

Comment thread src/redis/src/RedisProxy.php
Comment thread src/database/src/Eloquent/Model.php
Comment thread src/redis/src/RedisProxy.php
PostgreSQL may report an active transaction after the backend has already disappeared. During pooled refresh, rolling back that dead generation could throw after replacement PDOs were created, aborting reconnection and discarding the healthy replacements.

Treat a lost-classified rollback failure during disconnect as proof that the old physical session is already terminal. Continue transaction-manager cleanup and PDO detachment, surface any manager failure, and retain the existing precedence for non-lost physical failures.

Add deterministic coverage for lost rollback cleanup and failure precedence, and align the Database implementation plan and audit ledger with the corrected database-09 invariant.
Supervisor integration cleanup sent SIGTERM, immediately requested a hard stop, and then waited without a timeout. When the child exited from the earlier SIGTERM after Symfony recorded SIGKILL, wait() raised ProcessSignaledException; the unbounded wait could also strand suite shutdown.

Use WorkerProcess::kill() as the single synchronous hard-stop and reap boundary. Exhaust cleanup across every active and terminating process, preserve the earliest genuine cleanup failure, and remove the redundant competing signal and wait phases.
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@greptile-apps I traced all three inline findings against the pre-change Hypervel implementation, current Laravel, and the relevant tests and documentation. Two comments report the same Redis precedence concern.

  • Redis exception precedence is intentional. A throwing CommandFailed listener replaced the command exception before this refactor, and current Laravel behaves the same way. The new code preserves that contract while guaranteeing connection cleanup. Reordering it would be a behavior regression.
  • saveOrIgnore does not fail silently on MySQL or MariaDB. The base grammar throws a descriptive unsupported-operation exception before query execution, dedicated coverage verifies that boundary, and both user-facing guides explicitly scope the API to PostgreSQL and SQLite. A second model-level guard would duplicate the grammar check.

No source or documentation changes are needed from this review round.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Both points are well-justified — withdrawing the remaining open threads.

Redis exception precedence (both threads): Confirmed. The pre-change dispatch-inside-catch ordering means a listener failure could already replace the command exception before this refactor, current Laravel has the same contract, and testThrowingFailureListenerStillReleasesAndReplacesCommandFailure names the exact invariant. The refactor correctly reproduces established behavior while adding cleanup guarantees. No change warranted.

saveOrIgnore on MySQL/MariaDB: Confirmed. compileInsertOrIgnoreReturning on the base grammar throws a RuntimeException before any query reaches the engine, which is the opposite of silent failure. Duplicating that check at the model layer would diverge from upstream API structure without adding any safety. The documentation scope is also explicit. No change warranted.

No action items remain from this review round.

@binaryfire
binaryfire merged commit e2a74ca into 0.4 Jul 24, 2026
38 checks passed
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