Skip to content

2.x dev#7

Open
yorchperaza wants to merge 11 commits intomainfrom
2.x-dev
Open

2.x dev#7
yorchperaza wants to merge 11 commits intomainfrom
2.x-dev

Conversation

@yorchperaza
Copy link
Copy Markdown
Member

No description provided.

BREAKING CHANGES:
- Removed 4-class connection hierarchy (AbstractConnection + MySQL/PG/SQLite)
  → Replaced with unified Connection class using match() dispatch
- Removed 5-class DSN builder hierarchy (Abstract + 3 builders + Factory)
  → Replaced with DsnConfig immutable value object
- Removed ConnectionFactory
  → Replaced with ConnectionManager (read/write splitting, lazy connections)
- Removed DatabaseType enum → replaced with DatabaseDriver enum
- Removed Helpers.php and ConnectionHelper.php

NEW FEATURES:
- ConnectionManager: read/write splitting, sticky-after-write, replica strategies
- LazyConnection: proxy that defers PDO construction until first use
- ConnectionPool: in-memory pool with health monitoring, idle eviction
- 18 structured exception hierarchy with ErrorClassifier
- SchemaIntrospector: centralized table/column/FK discovery with static caching
- PHP 8.4: property hooks, asymmetric visibility, readonly classes

TESTS:
- 163 tests, 361 assertions — all green
- Covers all 6 components: Types, Config, Connection, Contracts, Exceptions, Support
- Full documentation of ConnectionManager, read/write splitting, lazy connections
- Connection pooling configuration and stats
- Typed exception hierarchy with usage examples
- Schema introspection API
- DI container setup guide
- v1 → v2 migration table
- basic_usage.php: ConnectionManager, CRUD, transactions, typed exceptions,
  lazy connections, property hooks, pool stats
- advanced_usage.php: config objects, multi-connection, schema introspection,
  health checks, full exception hierarchy, lazy lifecycle, read/write splitting
- Both examples are runnable and tested against SQLite :memory:
- Added queryCount/uptimeSeconds proxy hooks to LazyConnection
PHP 8.4 interface property hooks make these observability properties
part of the contract, resolving IDE lint warnings on ConnectionInterface
references.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR appears to be a v2 (“2.x dev”) refactor of the database package, replacing the previous per-driver connection + DSN-builder + factory approach with a unified configuration model, a single Connection implementation, and a ConnectionManager that supports lazy initialization, read/write splitting, and pooling. It also reorganizes and expands the unit test suite and updates package metadata to match the new architecture.

Changes:

  • Introduces new typed config/value objects (DSN/pool/read replicas) and new enums for driver/isolation/replica strategy.
  • Adds new connection primitives: unified Connection, LazyConnection, ConnectionManager, and an in-memory ConnectionPool.
  • Adds new support utilities (error classification, health checking, schema introspection) and rewrites tests into tests/Unit while removing legacy tests.

Reviewed changes

Copilot reviewed 75 out of 76 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/Unit/Types/ReadReplicaStrategyTest.php Adds unit tests for read-replica strategy enum values/labels.
tests/Unit/Types/IsolationLevelTest.php Adds unit tests for isolation level enum values/labels and enum construction.
tests/Unit/Types/DatabaseDriverTest.php Adds unit tests for new DatabaseDriver enum helpers and alias parsing.
tests/Unit/Support/ErrorClassifierTest.php Adds unit tests for PDOException → typed exception classification.
tests/Unit/Support/ConnectionPoolStatsTest.php Adds unit tests for pool stats snapshot helpers.
tests/Unit/Exceptions/ExceptionHierarchyTest.php Adds comprehensive tests for new exception hierarchy and helper constructors.
tests/Unit/Connection/LazyConnectionTest.php Adds tests for lazy-connection proxy behavior and lifecycle resets.
tests/Unit/Connection/ConnectionTest.php Adds tests for unified connection lifecycle, execution, transactions, and SQLite defaults.
tests/Unit/Connection/ConnectionPoolTest.php Adds tests for acquire/release, exhaustion, rollback-on-release, drain, and stats.
tests/Unit/Connection/ConnectionManagerTest.php Adds tests for manager defaults, named connections, disconnect/purge, stats, and sticky flags.
tests/Unit/Config/PoolConfigTest.php Adds tests for pool config defaults and array parsing.
tests/Unit/Config/DsnConfigTest.php Adds tests for DSN building across MySQL/PostgreSQL/SQLite and array parsing.
tests/Unit/Config/DatabaseConfigTest.php Adds tests for config parsing, PDO option merging, pool parsing, timezone defaults.
tests/SQLiteConnectionTest.php Removes legacy SQLite connection tests tied to old class structure.
tests/PostgreSQLConnectionTest.php Removes legacy PostgreSQL connection tests tied to old class structure.
tests/MySQLConnectionTest.php Removes legacy MySQL connection tests tied to old class structure.
tests/DSN/SQLiteDsnBuilderTest.php Removes legacy DSN builder tests (replaced by DsnConfig).
tests/DSN/MySQLDsnBuilderTest.php Removes legacy DSN builder tests (replaced by DsnConfig).
tests/ConnectionFactoryTest.php Removes legacy ConnectionFactory tests (replaced by ConnectionManager).
tests/Cache/CacheManagerBridgeTest.php Removes cache bridge tests (cache is now optional/suggested and excluded from PHPUnit source).
src/Types/ReadReplicaStrategy.php Adds backed enum for replica selection strategies with labels.
src/Types/IsolationLevel.php Adds backed enum for transaction isolation levels with labels.
src/Types/DatabaseType.php Removes legacy DatabaseType enum (superseded by DatabaseDriver + configs).
src/Types/DatabaseDriver.php Adds new DatabaseDriver enum with aliases, defaults, detection helpers.
src/Support/SchemaIntrospector.php Adds schema introspection utilities with static caching for tables/columns/FKs/indexes.
src/Support/Helpers.php Removes legacy global helper file previously autoloaded.
src/Support/HealthCheckResult.php Adds immutable health check result value object.
src/Support/HealthChecker.php Adds reusable health check logic for connections.
src/Support/ErrorClassifier.php Adds centralized PDOException classification into typed exceptions.
src/Support/ConnectionPoolStats.php Adds immutable pool stats snapshot with helper methods.
src/Support/ConnectionHelper.php Removes legacy PDO creation helper with host fallback.
src/SQLite/Connection.php Removes legacy SQLite-specific connection class (replaced by unified Connection).
src/PostgreSQL/Connection.php Removes legacy PostgreSQL-specific connection class (replaced by unified Connection).
src/MySQL/Connection.php Removes legacy MySQL-specific connection class (replaced by unified Connection).
src/Factory/ConnectionFactory.php Removes legacy factory entry point (replaced by ConnectionManager).
src/Exceptions/TransactionException.php Adds typed transaction exception with operation/nesting context.
src/Exceptions/TableNotFoundException.php Adds typed schema exception for missing tables.
src/Exceptions/SyntaxException.php Adds typed query exception for syntax errors with non-retryable semantics.
src/Exceptions/SchemaException.php Adds base schema exception type.
src/Exceptions/QueryException.php Adds base query exception type including SQL/params and debug SQL helper.
src/Exceptions/PoolException.php Adds pool exception type with helper constructors.
src/Exceptions/LockTimeoutException.php Adds typed lock-timeout exception with retryable flag.
src/Exceptions/ForeignKeyViolationException.php Adds typed FK violation exception with derived flags.
src/Exceptions/DuplicateKeyException.php Adds typed duplicate-key exception with helper constructor.
src/Exceptions/DeadlockException.php Adds typed deadlock exception with retry helpers.
src/Exceptions/DatabaseException.php Adds unified base database exception storing optional driver.
src/Exceptions/ConnectionLostException.php Adds typed connection-lost exception with retryable heuristic.
src/Exceptions/ConnectionFailedException.php Adds typed initial-connect failure exception with attempted-hosts context.
src/Exceptions/ConnectionException.php Adds base connection exception with endpoint accessor.
src/Exceptions/ConfigurationException.php Adds typed config exception with connection/key context and helpers.
src/Exceptions/ColumnNotFoundException.php Adds typed schema exception for missing columns.
src/Exceptions/AuthenticationException.php Adds typed authentication exception with username context helper.
src/Contracts/ConnectionPoolInterface.php Adds pool interface contract.
src/Contracts/ConnectionManagerInterface.php Adds manager interface contract for read/write splitting and lifecycle.
src/Contracts/ConnectionInterface.php Expands connection contract (lifecycle, driver/name, observability, transactions, execute/query).
src/Contracts/ConnectionEventDispatcherInterface.php Adds optional event dispatcher contract for lifecycle/query hooks.
src/Connection/LazyConnection.php Adds lazy proxy connection implementing ConnectionInterface.
src/Connection/ConnectionPool.php Adds in-memory connection pool implementation.
src/Connection/ConnectionManager.php Adds primary connection manager with lazy creation and replica selection.
src/Connection/Connection.php Adds unified concrete connection implementation with driver-specific defaults and classification.
src/Connection/AbstractConnection.php Removes legacy abstract connection base.
src/Config/ReadReplicaConfig.php Adds typed read-replica config and array parsing.
src/Config/PoolConfig.php Adds typed pool config and array parsing.
src/Config/DsnConfig.php Adds typed DSN config that builds DSNs for all supported drivers.
src/Config/DatabaseConfig.php Adds typed database config with backward-compatible array parsing and PDO option defaults.
phpunit.xml.dist Restricts test discovery to tests/Unit, enables fail-on-risky/warning, excludes src/Cache.
examples/basic_usage.php Updates example to v2 APIs (ConnectionManager/Connection) and typed exceptions.
composer.json Updates package description, dependencies, autoloading, branch alias, and PHPStan level.
.gitignore Adds .phpunit.cache/ directory to ignores.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +103 to +107
$this->idle->enqueue([
'connection' => $connection,
'createdAt' => microtime(true),
'lastUsedAt' => microtime(true),
]);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connection max-lifetime enforcement is effectively disabled because createdAt is being set to microtime(true) every time a connection is released back to the pool. This means exceedsMaxLifetime() measures time since last release, not since the connection was created, so old connections will never be evicted for age. Track a stable creation timestamp per connection (e.g., store it when the connection is created and preserve it across releases, or keep metadata in a map keyed by spl_object_id($connection)).

Copilot uses AI. Check for mistakes.
Comment on lines +256 to +266
public static function columnExists(
ConnectionInterface $conn,
string $table,
string $column,
?string $schema = null,
): bool {
$key = ($schema ?: '_') . '.' . $table . '.' . $column;

if (array_key_exists($key, self::$columnExistsCache)) {
return self::$columnExistsCache[$key];
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

columnExists() caches results globally under a key that does not include the connection identity (PDO object id) or driver, so calls across different connections/schemas can collide and return incorrect results. Incorporate at least driver + spl_object_id($conn->pdo()) + effective schema (e.g. detectSchema($conn)) into the cache key to keep the cache truly per-connection as described in the class docblock.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +32
/** @var array<string, string> pdoId → driver name */
private static array $driverCache = [];

/** @var array<string, string> pdoId → schema/database name */
private static array $schemaCache = [];

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$driverCache is declared and cleared but never read or written anywhere in this class. If driver caching is no longer needed, remove it to avoid dead state; otherwise, wire it into detectDriver()/cache-key generation so it provides value.

Copilot uses AI. Check for mistakes.
Comment on lines +195 to +214
return match ($driver) {
DatabaseDriver::MySQL => $code === 2006 || $code === 2013
|| str_contains($message, 'server has gone away'),
DatabaseDriver::PostgreSQL => $sqlState === '57P01'
|| str_contains($message, 'terminating connection'),
DatabaseDriver::SQLite => false,
};
}

private static function isConnectionFailed(
DatabaseDriver $driver,
mixed $sqlState,
?int $code,
string $message,
): bool {
return match ($driver) {
DatabaseDriver::MySQL => $code === 2002 || $code === 2003,
DatabaseDriver::PostgreSQL => $sqlState === '08001' || $sqlState === '08006',
DatabaseDriver::SQLite => str_contains($message, 'unable to open database'),
};
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several driver error detections rely on case-sensitive substring matches against PDOException messages (e.g. MySQL "server has gone away", SQLite "unable to open database"). PDO driver messages vary in capitalization, so these checks can silently fail when errorInfo codes are missing. Consider normalizing $message once (e.g. strtolower($message)) and matching against lowercase needles.

Copilot uses AI. Check for mistakes.
strategy: isset($config['strategy'])
? ReadReplicaStrategy::from($config['strategy'])
: ReadReplicaStrategy::RoundRobin,
stickyAfterWrite: (bool) ($config['sticky_after_write'] ?? true),
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadReplicaConfig::fromArray() reads the sticky flag from sticky_after_write, but common configs/documentation often use sticky for this behavior. As written, sticky would be ignored and there would be no way to disable stickiness unless the caller knows the internal key name. Consider supporting both keys (with one as an alias) or renaming to match the public config format consistently.

Suggested change
stickyAfterWrite: (bool) ($config['sticky_after_write'] ?? true),
stickyAfterWrite: (bool) (
array_key_exists('sticky', $config)
? $config['sticky']
: (array_key_exists('sticky_after_write', $config) ? $config['sticky_after_write'] : true)
),

Copilot uses AI. Check for mistakes.
Comment on lines +318 to +332
private function leastConnections(array $replicas): ConnectionInterface
{
// For lazy connections, prefer those not yet initialized
$uninitialized = array_filter(
$replicas,
static fn(ConnectionInterface $c) => $c instanceof LazyConnection && !$c->initialized,
);

if (!empty($uninitialized)) {
return $uninitialized[array_rand($uninitialized)];
}

// All initialized — fall back to random
return $replicas[array_rand($replicas)];
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leastConnections() does not actually select the replica with the fewest connections/lowest load; once all replicas are initialized it falls back to random selection. This makes the LeastConnections strategy name misleading and can lead to unexpected behavior for users choosing it. Either implement a real least-load heuristic (e.g. track active checkouts per replica) or rename the strategy to match what it does.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99ed513d09

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +103 to +106
$this->idle->enqueue([
'connection' => $connection,
'createdAt' => microtime(true),
'lastUsedAt' => microtime(true),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep original created time on pooled connections

Do not reset createdAt when releasing a connection back to idle, because max-lifetime checks in acquire() use this timestamp to evict old connections. With the current assignment, every release refreshes age and long-lived connections can stay in the pool indefinitely, defeating maxLifetimeSeconds and increasing risk of stale server-side sessions.

Useful? React with 👍 / 👎.

string $column,
?string $schema = null,
): bool {
$key = ($schema ?: '_') . '.' . $table . '.' . $column;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scope column-existence cache by connection identity

Include driver/PDO/schema identity in the cache key here; as written, columnExists() caches by only schema.table.column (often _.table.column), so results from one database connection can be reused for another unrelated connection. In multi-connection apps this can return false positives/negatives for schema checks after the first lookup.

Useful? React with 👍 / 👎.

Comment on lines +90 to +92
$dsnConfig = isset($config['dsn']) && $config['dsn'] instanceof DsnConfig
? $config['dsn']
: DsnConfig::fromArray($driver, $config);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse nested dsn arrays in fromArray

Handle array-valued dsn configs here instead of always falling back to the top-level flat array. Right now, passing ['dsn' => ['host' => ..., 'database' => ...]] silently ignores those DSN values, so connections may be built with missing host/database settings and target the wrong endpoint or fail to connect.

Useful? React with 👍 / 👎.

Copilot AI and others added 5 commits April 6, 2026 00:57
…, RetryHandler, warmUp, security fixes, PSR-3 logger

Agent-Logs-Url: https://github.com/MonkeysCloud/MonkeysLegion-Database/sessions/62f31e27-4c6f-4524-8a62-a5c62d02844b

Co-authored-by: yorchperaza <2913369+yorchperaza@users.noreply.github.com>
…ed timezone regex, clearer test comment

Agent-Logs-Url: https://github.com/MonkeysCloud/MonkeysLegion-Database/sessions/62f31e27-4c6f-4524-8a62-a5c62d02844b

Co-authored-by: yorchperaza <2913369+yorchperaza@users.noreply.github.com>
…se-v2

feat(v2): full PHP 8.4 adoption + security, bug, and missing-feature sweep
…, lastInsertId

- Add RetryHandler with truncated exponential back-off and jitter
- Extract RetryConfig to own file for PSR-4 autoloading
- Add ConnectionPool::warmUp() for pre-warming to minConnections
- Fix createdAt tracking: max-lifetime is relative to creation, not release
- Add lastInsertId() to ConnectionInterface, Connection, and LazyConnection
- Add LazyConnection::innerConnection() for collaborator propagation
- Fix propagateToOpenConnections to reach into resolved LazyConnection wrappers
- Add logger/eventDispatcher as public properties on Connection (PHP 8.4 set hooks on ConnectionManager)
- Remove duplicate class definitions from Connection.php and ConnectionManager.php
- Fix RetryConfig: readonly classes cannot have property hooks
- Use array_any()/array_find() in SchemaIntrospector and DatabaseDriver (PHP 8.5)
- Update readme: pool warmUp, SchemaIntrospector API, RetryHandler, PSR-3 logging
- 22 new tests (185 total, 400 assertions) — all green
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 79 out of 80 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +65 to +68
sslMode: isset($config['sslmode']) ? (string) $config['sslmode'] : null,
);
}

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DsnConfig::fromArray() doesn't populate the $extra DSN parameters even though the class supports them and buildMySqlDsn()/buildPgSqlDsn() append $this->extra. This means config-supplied extra DSN params (e.g. timeouts) are silently dropped. Consider reading an extra key (or another agreed key) from the input array and passing it into the constructor.

Suggested change
sslMode: isset($config['sslmode']) ? (string) $config['sslmode'] : null,
);
}
sslMode: isset($config['sslmode']) ? (string) $config['sslmode'] : null,
extra: self::normalizeExtra($config['extra'] ?? []),
);
}
/**
* @param mixed $extra
* @return array<string, string>
*/
private static function normalizeExtra(mixed $extra): array
{
if (!is_array($extra)) {
return [];
}
$normalized = [];
foreach ($extra as $key => $value) {
if (!is_string($key) && !is_int($key)) {
continue;
}
if (!is_scalar($value) && $value !== null) {
continue;
}
$normalized[(string) $key] = (string) ($value ?? '');
}
return $normalized;
}

Copilot uses AI. Check for mistakes.
Comment on lines +338 to +349
// Prefer uninitialized lazy connections (they have 0 load)
$uninitialized = array_filter(
$replicas,
static fn(ConnectionInterface $c) => $c instanceof LazyConnection && !$c->initialized,
);

if (!empty($uninitialized)) {
return $uninitialized[array_rand($uninitialized)];
}

// All initialized — fall back to random
return $replicas[array_rand($replicas)];
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadReplicaStrategy::LeastConnections is implemented as “prefer uninitialized, otherwise random”. Once replicas are initialized, this no longer selects the least-loaded replica, so the strategy name/contract is misleading and behavior won’t match expectations. Either track a load metric per replica (e.g. active query count / in-flight operations) and select the minimum, or rename/remove this strategy to reflect its actual behavior.

Suggested change
// Prefer uninitialized lazy connections (they have 0 load)
$uninitialized = array_filter(
$replicas,
static fn(ConnectionInterface $c) => $c instanceof LazyConnection && !$c->initialized,
);
if (!empty($uninitialized)) {
return $uninitialized[array_rand($uninitialized)];
}
// All initialized — fall back to random
return $replicas[array_rand($replicas)];
throw new ConfigurationException(
'ReadReplicaStrategy::LeastConnections requires a real per-replica load metric, '
. 'but this connection manager does not track active queries or in-flight operations. '
. 'Use a different replica strategy or implement load tracking before enabling LeastConnections.',
);

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +24
use MonkeysLegion\Database\Config\DatabaseConfig;
use MonkeysLegion\Database\Config\PoolConfig;
use MonkeysLegion\Database\Contracts\ConnectionInterface;
use MonkeysLegion\Database\Contracts\ConnectionPoolInterface;
use MonkeysLegion\Database\Exceptions\PoolException;
use MonkeysLegion\Database\Support\ConnectionPoolStats;
use MonkeysLegion\Database\Support\HealthChecker;
use SplQueue;

/**
* MonkeysLegion Framework — Database Package
*
* In-memory connection pool with health monitoring, idle eviction,
* and max-lifetime enforcement.
*
* PHP 8.4 features used:
* • `new` in property initialiser for `SplQueue` (no constructor needed)
* • `array_any()` for concise idle-queue checks
*
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import: HealthChecker is imported but never referenced. Also the docblock claims array_any() and “new in property initialiser” are used here, but the implementation doesn’t use either. Please remove the unused import and align the docblock with the actual implementation to avoid confusion.

Copilot uses AI. Check for mistakes.
namespace MonkeysLegion\Database\Connection;

use MonkeysLegion\Database\Config\DatabaseConfig;
use MonkeysLegion\Database\Config\DsnConfig;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import: MonkeysLegion\Database\Config\DsnConfig is imported but not used in this file.

Suggested change
use MonkeysLegion\Database\Config\DsnConfig;

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +25
* PHP 8.4 features used:
* • `readonly` class (`RetryConfig`) — all properties immutable after construction
* • `get` property hook on `RetryConfig::$maxDelayMs` for a clamped computed view
* • `array_any()` to test whether an exception type is in the retryable list
*
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docblock lists PHP 8.4 features (array_any(), a get hook on RetryConfig::$maxDelayMs, and RetryConfig being a readonly class) that the implementation does not actually use. This can mislead readers and reviewers; please update/remove these bullets to match the current code (or implement the features as described).

Copilot uses AI. Check for mistakes.
- DsnConfig::fromArray(): populate $extra from config with normalizeExtra()
- ReadReplicaConfig: support both 'sticky' and 'sticky_after_write' keys
- ConnectionManager: remove unused DsnConfig import
- ConnectionManager: LeastConnections now uses queryCount as load metric
  (single foreach, no array_filter allocation)
- ConnectionPool: remove unused HealthChecker import, fix stale docblock
- RetryHandler: remove inaccurate PHP 8.4 feature claims from docblock
- ErrorClassifier: normalize message to lowercase before str_contains()
  to prevent case-sensitivity mismatches across PDO driver versions
- SchemaIntrospector: remove unused $driverCache static property
- SchemaIntrospector: columnExists() cache key now includes driver +
  PDO object ID + schema to prevent cross-connection collisions

All 185 tests pass (400 assertions).
['dsn' => ['host' => ..., 'database' => ...]] is now correctly parsed
instead of silently falling back to top-level keys.
Uses match(true) — single expression, no extra branching cost.
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.

3 participants