From 4ccb7059519ab3624e0f7458543d816373f49c7b Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Wed, 16 Jul 2025 00:33:08 -0500 Subject: [PATCH 01/10] Continues code alignment with PhpDb 0.0.1 alignment. Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- composer.json | 2 +- composer.lock | 48 +-- src/ConfigProvider.php | 58 ++- src/Container/AdapterManagerDelegator.php | 28 ++ src/Container/MetadataInterfaceFactory.php | 24 ++ src/Container/PdoConnectionFactory.php | 26 ++ src/Container/PdoDriverFactory.php | 4 +- src/Container/PdoResultFactory.php | 17 + src/Container/PdoStatementFactory.php | 26 ++ src/Container/PlatformFactory.php | 23 -- src/Container/PlatformInterfaceFactory.php | 35 ++ src/Driver/Pdo/Feature/SqliteRowCounter.php | 7 +- src/Driver/Pdo/Pdo.php | 2 +- src/Metadata/Source/SqliteMetadata.php | 393 ++++++++++++++++++++ 14 files changed, 630 insertions(+), 63 deletions(-) create mode 100644 src/Container/AdapterManagerDelegator.php create mode 100644 src/Container/MetadataInterfaceFactory.php create mode 100644 src/Container/PdoConnectionFactory.php create mode 100644 src/Container/PdoResultFactory.php create mode 100644 src/Container/PdoStatementFactory.php delete mode 100644 src/Container/PlatformFactory.php create mode 100644 src/Container/PlatformInterfaceFactory.php create mode 100644 src/Metadata/Source/SqliteMetadata.php diff --git a/composer.json b/composer.json index 93d864f..f7108c0 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ }, "require": { "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", - "php-db/phpdb": "0.0.x-dev" + "php-db/phpdb": "dev-x.x.2" }, "require-dev": { "ext-pdo": "*", diff --git a/composer.lock b/composer.lock index 39653d0..0be22f1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f079faaaa3fddc12fdc8698b2c15dd48", + "content-hash": "63c61fa08f2ebe2ac86cc93a49898fab", "packages": [ { "name": "brick/varexporter", @@ -259,16 +259,16 @@ }, { "name": "php-db/phpdb", - "version": "0.0.x-dev", + "version": "dev-x.x.2", "source": { "type": "git", "url": "https://github.com/php-db/phpdb.git", - "reference": "82b26147f3aab64c5f19402923a18acbb2991942" + "reference": "72985fad1331076ea8dee20890b7227e6460f1fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-db/phpdb/zipball/82b26147f3aab64c5f19402923a18acbb2991942", - "reference": "82b26147f3aab64c5f19402923a18acbb2991942", + "url": "https://api.github.com/repos/php-db/phpdb/zipball/72985fad1331076ea8dee20890b7227e6460f1fc", + "reference": "72985fad1331076ea8dee20890b7227e6460f1fc", "shasum": "" }, "require": { @@ -322,7 +322,7 @@ "issues": "https://github.com/php-db/phpdb/issues", "source": "https://github.com/php-db/phpdb" }, - "time": "2025-07-07T04:06:26+00:00" + "time": "2025-07-14T06:08:40+00:00" }, { "name": "psr/container", @@ -2498,16 +2498,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.1.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", "shasum": "" }, "require": { @@ -2539,9 +2539,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" }, - "time": "2025-02-19T13:28:12+00:00" + "time": "2025-07-13T07:04:09+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2880,16 +2880,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.26", + "version": "11.5.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4ad8fe263a0b55b54a8028c38a18e3c5bef312e0" + "reference": "446d43867314781df7e9adf79c3ec7464956fd8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4ad8fe263a0b55b54a8028c38a18e3c5bef312e0", - "reference": "4ad8fe263a0b55b54a8028c38a18e3c5bef312e0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/446d43867314781df7e9adf79c3ec7464956fd8f", + "reference": "446d43867314781df7e9adf79c3ec7464956fd8f", "shasum": "" }, "require": { @@ -2899,7 +2899,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.3", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", @@ -2961,7 +2961,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.26" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.27" }, "funding": [ { @@ -2985,7 +2985,7 @@ "type": "tidelift" } ], - "time": "2025-07-04T05:58:21+00:00" + "time": "2025-07-11T04:10:06+00:00" }, { "name": "psalm/plugin-phpunit", @@ -5326,16 +5326,16 @@ }, { "name": "vimeo/psalm", - "version": "6.12.1", + "version": "6.13.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "e71404b0465be25cf7f8a631b298c01c5ddd864f" + "reference": "70cdf647255a1362b426bb0f522a85817b8c791c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/e71404b0465be25cf7f8a631b298c01c5ddd864f", - "reference": "e71404b0465be25cf7f8a631b298c01c5ddd864f", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/70cdf647255a1362b426bb0f522a85817b8c791c", + "reference": "70cdf647255a1362b426bb0f522a85817b8c791c", "shasum": "" }, "require": { @@ -5440,7 +5440,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2025-07-04T09:56:28+00:00" + "time": "2025-07-14T09:59:17+00:00" }, { "name": "webimpress/coding-standard", diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 04cb9f2..55e8f41 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -4,19 +4,29 @@ namespace PhpDb\Adapter\Sqlite; +use Laminas\ServiceManager\Factory\InvokableFactory; use PhpDb\Adapter\AdapterInterface; +use PhpDb\Adapter\Driver\ConnectionInterface; use PhpDb\Adapter\Driver\DriverInterface; +use PhpDb\Adapter\Driver\PdoDriverInterface; +use PhpDb\Adapter\Driver\ResultInterface; +use PhpDb\Adapter\Driver\StatementInterface; +use PhpDb\Adapter\Driver\Pdo\Result; +use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Adapter\Profiler\Profiler; use PhpDb\Adapter\Profiler\ProfilerInterface; -use Laminas\ServiceManager\Factory\InvokableFactory; +use PhpDb\Container\AdapterManager; +use PhpDb\Metadata\MetadataInterface; +use PhpDb\ResultSet; readonly class ConfigProvider { public function __invoke(): array { return [ - 'dependencies' => $this->getDependencies(), + 'dependencies' => $this->getDependencies(), + AdapterManager::class => $this->getAdapterManagerConfig(), ]; } @@ -24,14 +34,46 @@ public function getDependencies(): array { return [ 'aliases' => [ - PlatformInterface::class => Platform\Sqlite::class, - ProfilerInterface::class => Profiler::class, + MetadataInterface::class => Metadata\Source\SqliteMetadata::class, + ], + 'factories' => [ + Metadata\Source\SqliteMetadata::class => Container\MetadataInterfaceFactory::class, + ], + 'delegators' => [ + AdapterManager::class => [Container\AdapterManagerDelegator::class], + ], + ]; + } + + public function getAdapterManagerConfig(): array + { + return [ + 'aliases' => [ + 'SQLite' => Driver\Pdo\Pdo::class, + 'Sqlite' => Driver\Pdo\Pdo::class, + 'sqlite' => Driver\Pdo\Pdo::class, + 'pdo' => Driver\Pdo\Pdo::class, + 'pdo_sqlite' => Driver\Pdo\Pdo::class, + 'pdosqlite' => Driver\Pdo\Pdo::class, + 'pdodriver' => Driver\Pdo\Pdo::class, + ConnectionInterface::class => Driver\Pdo\Connection::class, + DriverInterface::class => Driver\Pdo\Pdo::class, + PdoDriverInterface::class => Driver\Pdo\Pdo::class, + PlatformInterface::class => Platform\Sqlite::class, + ProfilerInterface::class => Profiler::class, + ResultInterface::class => Result::class, + ResultSet\ResultSetInterface::class => ResultSet\ResultSet::class, + StatementInterface::class => Statement::class, ], 'factories' => [ - AdapterInterface::class => AdapterServiceFactory::class, - DriverInterface::class => Driver\Pdo\DriverFactory::class, - Platform\Sqlite::class => InvokableFactory::class, - Profiler::class => InvokableFactory::class, + AdapterInterface::class => Container\AdapterFactory::class, + Driver\Pdo\Connection::class => Container\PdoConnectionFactory::class, + Driver\Pdo\Pdo::class => Container\PdoDriverFactory::class, + Result::class => Container\PdoResultFactory::class, + Statement::class => Container\PdoStatementFactory::class, + Platform\Sqlite::class => Container\PlatformInterfaceFactory::class, + Profiler::class => InvokableFactory::class, + ResultSet\ResultSet::class => InvokableFactory::class, ], ]; } diff --git a/src/Container/AdapterManagerDelegator.php b/src/Container/AdapterManagerDelegator.php new file mode 100644 index 0000000..8e5fb7c --- /dev/null +++ b/src/Container/AdapterManagerDelegator.php @@ -0,0 +1,28 @@ +configure( + (new ConfigProvider())->getAdapterManagerConfig() + ); + + return $adapterManager; + } +} diff --git a/src/Container/MetadataInterfaceFactory.php b/src/Container/MetadataInterfaceFactory.php new file mode 100644 index 0000000..4d7194c --- /dev/null +++ b/src/Container/MetadataInterfaceFactory.php @@ -0,0 +1,24 @@ +get(AdapterInterface::class); + + return new SqliteMetadata( + $adapter + ); + } +} diff --git a/src/Container/PdoConnectionFactory.php b/src/Container/PdoConnectionFactory.php new file mode 100644 index 0000000..7499c1e --- /dev/null +++ b/src/Container/PdoConnectionFactory.php @@ -0,0 +1,26 @@ +get('config'); + + /** @var array $dbConfig */ + $dbConfig = $config['db'] ?? []; + + /** @var array $connectionConfig */ + $connectionConfig = $dbConfig['connection'] ?? []; + + return new Connection($connectionConfig); + } +} diff --git a/src/Container/PdoDriverFactory.php b/src/Container/PdoDriverFactory.php index 71fd400..9e06103 100644 --- a/src/Container/PdoDriverFactory.php +++ b/src/Container/PdoDriverFactory.php @@ -10,6 +10,7 @@ use PhpDb\Adapter\Driver\PdoDriverInterface; use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\Driver\StatementInterface; +use PhpDb\Adapter\Sqlite\Driver\Pdo\Feature\SqliteRowCounter; use PhpDb\Adapter\Sqlite\Driver\Pdo\Connection; use PhpDb\Adapter\Sqlite\Driver\Pdo\Pdo as PdoDriver; use PhpDb\Container\AdapterManager; @@ -34,7 +35,8 @@ public function __invoke(ContainerInterface $container): PdoDriverInterface&PdoD return new PdoDriver( $connectionInstance, $statementInstance, - $resultInstance + $resultInstance, + [new SqliteRowCounter()], ); } } diff --git a/src/Container/PdoResultFactory.php b/src/Container/PdoResultFactory.php new file mode 100644 index 0000000..109bba6 --- /dev/null +++ b/src/Container/PdoResultFactory.php @@ -0,0 +1,17 @@ +get('config'); + + /** @var array $dbConfig */ + $dbConfig = $config['db'] ?? []; + + /** @var array $options */ + $options = $dbConfig['options'] ?? []; + + return new Statement(options: $options); + } +} diff --git a/src/Container/PlatformFactory.php b/src/Container/PlatformFactory.php deleted file mode 100644 index feb8654..0000000 --- a/src/Container/PlatformFactory.php +++ /dev/null @@ -1,23 +0,0 @@ -get(AdapterManager::class); + + /** @var array $config */ + $config = $container->get('config'); + + /** @var array $dbConfig */ + $dbConfig = $config['db'] ?? []; + + /** @var string $driver */ + $driver = $dbConfig['driver']; + + /** @var PdoDriverInterface|PDO $driverInstance */ + $driverInstance = $adapterManager->get($driver); + + return new Sqlite($driverInstance); + } +} diff --git a/src/Driver/Pdo/Feature/SqliteRowCounter.php b/src/Driver/Pdo/Feature/SqliteRowCounter.php index b3c03a4..014b66b 100644 --- a/src/Driver/Pdo/Feature/SqliteRowCounter.php +++ b/src/Driver/Pdo/Feature/SqliteRowCounter.php @@ -1,5 +1,7 @@ resultPrototype; /** @var Feature\SqliteRowCounter $sqliteRowCounter */ - $sqliteRowCounter = $this->getFeature('SqliteRowCounter'); + $sqliteRowCounter = $this->getFeature(Feature\SqliteRowCounter::class); $rowCount = null; if ($sqliteRowCounter && $resource->columnCount() > 0) { diff --git a/src/Metadata/Source/SqliteMetadata.php b/src/Metadata/Source/SqliteMetadata.php new file mode 100644 index 0000000..368d19c --- /dev/null +++ b/src/Metadata/Source/SqliteMetadata.php @@ -0,0 +1,393 @@ +data['schemas'])) { + return; + } + $this->prepareDataHierarchy('schemas'); + + $results = $this->fetchPragma('database_list'); + foreach ($results as $row) { + $schemas[] = $row['name']; + } + $this->data['schemas'] = $schemas; + } + + protected function loadTableNameData(string $schema): void + { + if (isset($this->data['table_names'][$schema])) { + return; + } + $this->prepareDataHierarchy('table_names', $schema); + + // FEATURE: Filename? + + $p = $this->adapter->getPlatform(); + + $sql = 'SELECT "name", "type", "sql" FROM ' . $p->quoteIdentifierChain([$schema, 'sqlite_master']) + . ' WHERE "type" IN (\'table\',\'view\') AND "name" NOT LIKE \'sqlite_%\''; + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + $tables = []; + foreach ($results->toArray() as $row) { + if ('table' === $row['type']) { + $table = [ + 'table_type' => 'BASE TABLE', + 'view_definition' => null, // VIEW only + 'check_option' => null, // VIEW only + 'is_updatable' => null, // VIEW only + ]; + } else { + $table = [ + 'table_type' => 'VIEW', + 'view_definition' => null, + 'check_option' => 'NONE', + 'is_updatable' => false, + ]; + + // Parse out extra data + if (null !== ($data = $this->parseView($row['sql']))) { + $table = array_merge($table, $data); + } + } + $tables[$row['name']] = $table; + } + $this->data['table_names'][$schema] = $tables; + } + + protected function loadColumnData(string $table, string $schema): void + { + if (isset($this->data['columns'][$schema][$table])) { + return; + } + $this->prepareDataHierarchy('columns', $schema, $table); + $this->prepareDataHierarchy('sqlite_columns', $schema, $table); + + $results = $this->fetchPragma('table_info', $table, $schema); + + $columns = []; + + foreach ($results as $row) { + $columns[$row['name']] = [ + // cid appears to be zero-based, ordinal position needs to be one-based + 'ordinal_position' => $row['cid'] + 1, + 'column_default' => $row['dflt_value'], + 'is_nullable' => ! (bool) $row['notnull'], + 'data_type' => $row['type'], + 'character_maximum_length' => null, + 'character_octet_length' => null, + 'numeric_precision' => null, + 'numeric_scale' => null, + 'numeric_unsigned' => null, + 'erratas' => [], + ]; + // TODO: populate character_ and numeric_values with correct info + } + + $this->data['columns'][$schema][$table] = $columns; + $this->data['sqlite_columns'][$schema][$table] = $results; + } + + protected function loadConstraintData(string $table, string $schema): void + { + if (isset($this->data['constraints'][$schema][$table])) { + return; + } + + $this->prepareDataHierarchy('constraints', $schema, $table); + + $this->loadColumnData($table, $schema); + $primaryKey = []; + + foreach ($this->data['sqlite_columns'][$schema][$table] as $col) { + if ((bool) $col['pk']) { + $primaryKey[] = $col['name']; + } + } + + if (empty($primaryKey)) { + $primaryKey = null; + } + $constraints = []; + $indexes = $this->fetchPragma('index_list', $table, $schema); + foreach ($indexes as $index) { + if (! (bool) $index['unique']) { + continue; + } + $constraint = [ + 'constraint_name' => $index['name'], + 'constraint_type' => 'UNIQUE', + 'table_name' => $table, + 'columns' => [], + ]; + + $info = $this->fetchPragma('index_info', $index['name'], $schema); + + foreach ($info as $column) { + $constraint['columns'][] = $column['name']; + } + if ($primaryKey === $constraint['columns']) { + $constraint['constraint_type'] = 'PRIMARY KEY'; + $primaryKey = null; + } + $constraints[$constraint['constraint_name']] = $constraint; + } + + if (null !== $primaryKey) { + $constraintName = '_laminas_' . $table . '_PRIMARY'; + $constraints[$constraintName] = [ + 'constraint_name' => $constraintName, + 'constraint_type' => 'PRIMARY KEY', + 'table_name' => $table, + 'columns' => $primaryKey, + ]; + } + + $foreignKeys = $this->fetchPragma('foreign_key_list', $table, $schema); + + $id = $name = null; + foreach ($foreignKeys as $fk) { + if ($id !== $fk['id']) { + $id = $fk['id']; + $name = '_laminas_' . $table . '_FOREIGN_KEY_' . ($id + 1); + $constraints[$name] = [ + 'constraint_name' => $name, + 'constraint_type' => 'FOREIGN KEY', + 'table_name' => $table, + 'columns' => [], + 'referenced_table_schema' => $schema, + 'referenced_table_name' => $fk['table'], + 'referenced_columns' => [], + // TODO: Verify match, on_update, and on_delete values conform to SQL Standard + 'match_option' => strtoupper($fk['match']), + 'update_rule' => strtoupper($fk['on_update']), + 'delete_rule' => strtoupper($fk['on_delete']), + ]; + } + $constraints[$name]['columns'][] = $fk['from']; + $constraints[$name]['referenced_columns'][] = $fk['to']; + } + + $this->data['constraints'][$schema][$table] = $constraints; + } + + protected function loadTriggerData(string $schema): void + { + if (isset($this->data['triggers'][$schema])) { + return; + } + + $this->prepareDataHierarchy('triggers', $schema); + + $p = $this->adapter->getPlatform(); + + $sql = 'SELECT "name", "tbl_name", "sql" FROM ' + . $p->quoteIdentifierChain([$schema, 'sqlite_master']) + . ' WHERE "type" = \'trigger\''; + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + $triggers = []; + foreach ($results->toArray() as $row) { + $trigger = [ + 'trigger_name' => $row['name'], + 'event_manipulation' => null, // in $row['sql'] + 'event_object_catalog' => null, + 'event_object_schema' => $schema, + 'event_object_table' => $row['tbl_name'], + 'action_order' => 0, + 'action_condition' => null, // in $row['sql'] + 'action_statement' => null, // in $row['sql'] + 'action_orientation' => 'ROW', + 'action_timing' => null, // in $row['sql'] + 'action_reference_old_table' => null, + 'action_reference_new_table' => null, + 'action_reference_old_row' => 'OLD', + 'action_reference_new_row' => 'NEW', + 'created' => null, + ]; + + // Parse out extra data + if (null !== ($data = $this->parseTrigger($row['sql']))) { + $trigger = array_merge($trigger, $data); + } + $triggers[$trigger['trigger_name']] = $trigger; + } + + $this->data['triggers'][$schema] = $triggers; + } + + protected function fetchPragma(string $name, ?string $value = null, ?string $schema = null): array + { + $p = $this->adapter->getPlatform(); + + $sql = 'PRAGMA '; + + if (null !== $schema) { + $sql .= $p->quoteIdentifier($schema) . '.'; + } + $sql .= $name; + + if (null !== $value) { + $sql .= '(' . $p->quoteTrustedValue($value) . ')'; + } + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + if ($results instanceof ResultSetInterface) { + return $results->toArray(); + } + return []; + } + + /** @return null|array */ + protected function parseView(string $sql): ?array + { + static $re = null; + if (null === $re) { + $identifierChain = $this->getIdentifierChainRegularExpression(); + $re = $this->buildRegularExpression([ + 'CREATE', + ['TEMP|TEMPORARY'], + 'VIEW', + ['IF', 'NOT', 'EXISTS'], + $identifierChain, + 'AS', + '(?.+)', + [';'], + ]); + } + + if (! preg_match($re, $sql, $matches)) { + return null; + } + + return [ + 'view_definition' => $matches['view_definition'], + ]; + } + + /** @return null|array */ + protected function parseTrigger(string $sql): ?array + { + static $re = null; + if (null === $re) { + $identifier = $this->getIdentifierRegularExpression(); + $identifierList = $this->getIdentifierListRegularExpression(); + $identifierChain = $this->getIdentifierChainRegularExpression(); + $re = $this->buildRegularExpression([ + 'CREATE', + ['TEMP|TEMPORARY'], + 'TRIGGER', + ['IF', 'NOT', 'EXISTS'], + $identifierChain, + ['(?BEFORE|AFTER|INSTEAD\\s+OF)'], + '(?DELETE|INSERT|UPDATE)', + ['OF', '(?' . $identifierList . ')'], + 'ON', + '(?' . $identifier . ')', + ['FOR', 'EACH', 'ROW'], + ['WHEN', '(?.+)'], + '(?BEGIN', + '.+', + 'END)', + [';'], + ]); + } + + if (! preg_match($re, $sql, $matches)) { + return null; + } + $data = []; + + foreach ($matches as $key => $value) { + if (is_string($key)) { + $data[$key] = $value; + } + } + + // Normalize data and populate defaults, if necessary + + $data['event_manipulation'] = strtoupper($data['event_manipulation']); + if (empty($data['action_condition'])) { + $data['action_condition'] = null; + } + if (! empty($data['action_timing'])) { + $data['action_timing'] = strtoupper($data['action_timing']); + if ('I' === $data['action_timing'][0]) { + // normalize the white-space between the two words + $data['action_timing'] = 'INSTEAD OF'; + } + } else { + $data['action_timing'] = 'AFTER'; + } + unset($data['column_usage']); + + return $data; + } + + protected function buildRegularExpression(array $re): string + { + foreach ($re as &$value) { + if (is_array($value)) { + $value = '(?:' . implode('\\s*+', $value) . '\\s*+)?'; + } else { + $value .= '\\s*+'; + } + } + unset($value); + $re = '/^' . implode('\\s*+', $re) . '$/'; + return $re; + } + + protected function getIdentifierRegularExpression(): string + { + static $re = null; + if (null === $re) { + $re = '(?:' . implode('|', [ + '"(?:[^"\\\\]++|\\\\.)*+"', + '`(?:[^`]++|``)*+`', + '\\[[^\\]]+\\]', + '[^\\s\\.]+', + ]) . ')'; + } + + return $re; + } + + protected function getIdentifierChainRegularExpression(): string + { + static $re = null; + if (null === $re) { + $identifier = $this->getIdentifierRegularExpression(); + $re = $identifier . '(?:\\s*\\.\\s*' . $identifier . ')*+'; + } + return $re; + } + + protected function getIdentifierListRegularExpression(): string + { + static $re = null; + if (null === $re) { + $identifier = $this->getIdentifierRegularExpression(); + $re = $identifier . '(?:\\s*,\\s*' . $identifier . ')*+'; + } + return $re; + } +} From 7f3803f4eac7da543539fcebf9f7ec1a53afa3d7 Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Thu, 17 Jul 2025 00:25:48 -0500 Subject: [PATCH 02/10] Semi working code. Adapter initializes and will write data to sqlite. Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- composer.json | 2 +- src/ConfigProvider.php | 6 ++-- src/Driver/Pdo/Connection.php | 66 +++++++++-------------------------- src/Platform/Sqlite.php | 20 ++--------- 4 files changed, 24 insertions(+), 70 deletions(-) diff --git a/composer.json b/composer.json index f7108c0..e5cd408 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ } }, "extra": { - "php-db": { + "laminas": { "config-provider": "PhpDb\\Adapter\\Sqlite\\ConfigProvider" } }, diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 55e8f41..628a506 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -20,7 +20,7 @@ use PhpDb\Metadata\MetadataInterface; use PhpDb\ResultSet; -readonly class ConfigProvider +final class ConfigProvider { public function __invoke(): array { @@ -40,7 +40,9 @@ public function getDependencies(): array Metadata\Source\SqliteMetadata::class => Container\MetadataInterfaceFactory::class, ], 'delegators' => [ - AdapterManager::class => [Container\AdapterManagerDelegator::class], + AdapterManager::class => [ + Container\AdapterManagerDelegator::class, + ], ], ]; } diff --git a/src/Driver/Pdo/Connection.php b/src/Driver/Pdo/Connection.php index 7f70f3a..27de25c 100644 --- a/src/Driver/Pdo/Connection.php +++ b/src/Driver/Pdo/Connection.php @@ -5,7 +5,6 @@ namespace PhpDb\Adapter\Sqlite\Driver\Pdo; use Override; -use PDO; use PDOException; use PDOStatement; use PhpDb\Adapter\Driver\ConnectionInterface; @@ -44,6 +43,11 @@ public function getCurrentSchema(): string|bool * * @throws Exception\InvalidConnectionParametersException * @throws Exception\RuntimeException + * + * This connection class only supports the 'dsn', 'dir', or 'path' parameters. + * If 'dsn' is not provided, it will attempt to construct one from 'dir' or 'path'. + * If neither is provided, an exception will be thrown. + * If 'driver_options' is provided, it will merge with existing options. */ #[Override] public function connect(): ConnectionInterface @@ -52,70 +56,33 @@ public function connect(): ConnectionInterface return $this; } - $dsn = $username = $password = $hostname = $database = null; + $dsn = null; $options = []; foreach ($this->connectionParameters as $key => $value) { switch (strtolower($key)) { case 'dsn': $dsn = $value; break; - case 'user': - case 'username': - $username = (string) $value; - break; - case 'pass': - case 'password': - $password = (string) $value; - break; - case 'host': - case 'hostname': - $hostname = (string) $value; - break; - case 'database': - case 'dbname': - $database = (string) $value; - break; - case 'unix_socket': - $unixSocket = (string) $value; + case 'dir': + case 'path': + $path = (string) $value; break; case 'driver_options': $value = (array) $value; $options = array_diff_key($options, $value) + $value; break; default: - $options[$key] = $value; + //$options[$key] = $value; break; } } - if (isset($hostname) && isset($unixSocket)) { - throw new Exception\InvalidConnectionParametersException( - 'Ambiguous connection parameters, both hostname and unix_socket parameters were set', - (int) $this->connectionParameters - ); - } - if (! isset($dsn)) { $dsn = []; - if (isset($database)) { - $dsn[] = "dbname={$database}"; - } - if (isset($hostname)) { - $dsn[] = "host={$hostname}"; - } - if (isset($port)) { - $dsn[] = "port={$port}"; - } - if (isset($charset)) { - $dsn[] = "charset={$charset}"; + if (isset($path)) { + $dsn[] = $path; } - if (isset($unixSocket)) { - $dsn[] = "unix_socket={$unixSocket}"; - } - if (isset($version)) { - $dsn[] = "version={$version}"; - } - $dsn = 'sqlite:' . implode(';', $dsn); + $dsn = 'sqlite:' . implode('', $dsn); } if (! is_string($dsn)) { @@ -128,9 +95,10 @@ public function connect(): ConnectionInterface $this->dsn = $dsn; try { - $this->resource = new PDO($dsn, $username, $password, $options); - $this->resource->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->driverName = strtolower($this->resource->getAttribute(PDO::ATTR_DRIVER_NAME)); + + $this->resource = new \PDO($dsn); + $this->resource->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->driverName = strtolower($this->resource->getAttribute(\PDO::ATTR_DRIVER_NAME)); } catch (PDOException $e) { $code = $e->getCode(); if (! is_int($code)) { diff --git a/src/Platform/Sqlite.php b/src/Platform/Sqlite.php index ef7daa0..0071fb1 100644 --- a/src/Platform/Sqlite.php +++ b/src/Platform/Sqlite.php @@ -28,24 +28,8 @@ class Sqlite extends AbstractPlatform public function __construct( protected readonly PdoDriverInterface|\PDO $driver - ) { - if ( - ( - $this->driver instanceof \PDO - && $this->driver->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'sqlite' - ) - || ( - $this->driver instanceof Pdo\Pdo - && $this->driver->getDatabasePlatformName() === 'Sqlite' - ) - ) { - $this->resource = $this->driver; - } - - throw new Exception\InvalidArgumentException( - '$driver must be a Sqlite PDO PhpDb\Adapter\Driver, Sqlite PDO instance' - ); - } + ) { + } /** * {@inheritDoc} From 118323229406570d4c2da77e1fb343edf4515cbf Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Sat, 19 Jul 2025 22:15:49 -0500 Subject: [PATCH 03/10] closes #7 Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- composer.json | 3 +- composer.lock | 118 +++++++++++++++++----------------- src/ConfigProvider.php | 2 +- src/Driver/Pdo/Connection.php | 52 +++++++-------- 4 files changed, 87 insertions(+), 88 deletions(-) diff --git a/composer.json b/composer.json index e5cd408..4694f59 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,8 @@ }, "require": { "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", - "php-db/phpdb": "dev-x.x.2" + "php-db/phpdb": "dev-x.x.2", + "webmozart/assert": "^1.11" }, "require-dev": { "ext-pdo": "*", diff --git a/composer.lock b/composer.lock index 0be22f1..1100934 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "63c61fa08f2ebe2ac86cc93a49898fab", + "content-hash": "684451c6d162458dea8a30023a84d89d", "packages": [ { "name": "brick/varexporter", @@ -376,6 +376,64 @@ "source": "https://github.com/php-fig/container/tree/2.0.2" }, "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" } ], "packages-dev": [ @@ -5496,64 +5554,6 @@ } ], "time": "2024-10-16T06:55:17+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" } ], "aliases": [], diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 628a506..d17b46c 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -74,7 +74,7 @@ public function getAdapterManagerConfig(): array Result::class => Container\PdoResultFactory::class, Statement::class => Container\PdoStatementFactory::class, Platform\Sqlite::class => Container\PlatformInterfaceFactory::class, - Profiler::class => InvokableFactory::class, + //Profiler::class => InvokableFactory::class, ResultSet\ResultSet::class => InvokableFactory::class, ], ]; diff --git a/src/Driver/Pdo/Connection.php b/src/Driver/Pdo/Connection.php index 27de25c..d221b39 100644 --- a/src/Driver/Pdo/Connection.php +++ b/src/Driver/Pdo/Connection.php @@ -6,19 +6,20 @@ use Override; use PDOException; -use PDOStatement; use PhpDb\Adapter\Driver\ConnectionInterface; use PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection; use PhpDb\Adapter\Exception; +use Webmozart\Assert\Assert; use function array_diff_key; -use function implode; use function is_int; use function is_string; +use function str_starts_with; use function strtolower; class Connection extends AbstractPdoConnection { + public final const CURRENT_SCHEMA = 'main'; /** * {@inheritDoc} */ @@ -29,13 +30,7 @@ public function getCurrentSchema(): string|bool $this->connect(); } - /** @var PDOStatement $result */ - $result = $this->resource->query('PRAGMA database_list'); - if ($result instanceof PDOStatement) { - return $result->fetchColumn(); - } - - return false; + return self::CURRENT_SCHEMA; } /** @@ -44,8 +39,8 @@ public function getCurrentSchema(): string|bool * @throws Exception\InvalidConnectionParametersException * @throws Exception\RuntimeException * - * This connection class only supports the 'dsn', 'dir', or 'path' parameters. - * If 'dsn' is not provided, it will attempt to construct one from 'dir' or 'path'. + * This connection class only supports the 'dsn', or 'path' parameters. + * If 'dsn' is not provided, it will attempt to construct one from 'path'. * If neither is provided, an exception will be thrown. * If 'driver_options' is provided, it will merge with existing options. */ @@ -63,40 +58,43 @@ public function connect(): ConnectionInterface case 'dsn': $dsn = $value; break; - case 'dir': - case 'path': - $path = (string) $value; - break; case 'driver_options': $value = (array) $value; $options = array_diff_key($options, $value) + $value; break; default: - //$options[$key] = $value; + $options[$key] = $value; break; } } - if (! isset($dsn)) { - $dsn = []; - if (isset($path)) { - $dsn[] = $path; - } - $dsn = 'sqlite:' . implode('', $dsn); - } - if (! is_string($dsn)) { throw new Exception\InvalidConnectionParametersException( - 'A dsn was not provided or could not be constructed from your parameters', + 'A dsn was not provided', $this->connectionParameters ); } + if (! str_starts_with($dsn, 'sqlite:')) { + Assert::fileExists( + $dsn, + 'The provided DSN does not point to a valid file.' + ); + Assert::readable( + $dsn, + 'The provided DSN does not point to a readable file.' + ); + Assert::writable( + $dsn, + 'The provided DSN does not point to a writable file.' + ); + $dsn = 'sqlite:' . $dsn; + } + $this->dsn = $dsn; try { - - $this->resource = new \PDO($dsn); + $this->resource = new \PDO(dsn: $dsn, options: $options); $this->resource->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->driverName = strtolower($this->resource->getAttribute(\PDO::ATTR_DRIVER_NAME)); } catch (PDOException $e) { From 2eb6f47bd85e3e9bf801690d57e9e22bf5827dcc Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Sun, 20 Jul 2025 02:34:19 -0500 Subject: [PATCH 04/10] Fixes PHPUnit config so test will run Starts work to fix unit and integration test Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- composer.json | 2 + phpunit.xml.dist | 12 +- psalm-baseline.xml | 585 +++++++++++++++++- src/ConfigProvider.php | 2 + .../Container/AdapterFactoryTest.php | 25 + .../Container/PdoConnectionFactoryTest.php | 32 + .../Container/PdoDriverFactoryTest.php | 31 + .../Container/PdoResultFactoryTest.php | 31 + .../Container/PdoStatementFactoryTest.php | 30 + .../PlatformInterfaceFactoryTest.php | 30 + .../Container/SqliteMetadataFactoryTest.php | 27 + .../Container/TestAsset/SetupTrait.php | 103 +++ .../Driver/Pdo/AbstractAdapterTestCase.php | 3 +- test/integration/Driver/Pdo/AdapterTrait.php | 25 - .../Driver/Pdo/ConnectionIntegrationTest.php | 9 +- test/integration/Driver/Pdo/StatementTest.php | 5 +- .../Driver/Pdo/TestAsset/SqliteMemoryPdo.php | 35 -- .../unit/Sql/Platform/SelectDecoratorTest.php | 35 +- 18 files changed, 937 insertions(+), 85 deletions(-) create mode 100644 test/integration/Container/AdapterFactoryTest.php create mode 100644 test/integration/Container/PdoConnectionFactoryTest.php create mode 100644 test/integration/Container/PdoDriverFactoryTest.php create mode 100644 test/integration/Container/PdoResultFactoryTest.php create mode 100644 test/integration/Container/PdoStatementFactoryTest.php create mode 100644 test/integration/Container/PlatformInterfaceFactoryTest.php create mode 100644 test/integration/Container/SqliteMetadataFactoryTest.php create mode 100644 test/integration/Container/TestAsset/SetupTrait.php delete mode 100644 test/integration/Driver/Pdo/AdapterTrait.php delete mode 100644 test/integration/Driver/Pdo/TestAsset/SqliteMemoryPdo.php diff --git a/composer.json b/composer.json index 4694f59..a4c8cdb 100644 --- a/composer.json +++ b/composer.json @@ -68,6 +68,8 @@ "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", "test-integration": "phpunit --colors=always --testsuite \"integration test\"", "static-analysis": "psalm --shepherd --stats", + "sa-update-baseline": "psalm --update-baseline", + "sa-no-baseline": "psalm --shepherd --stats --ignore-baseline", "upload-coverage": "coveralls -v" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index afc65b3..d4d75ab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -14,18 +14,16 @@ failOnDeprecation="true" failOnWarning="true"> - - test/unit + + ./test/unit - - test/integration + + ./test/integration - - - src + ./src \ No newline at end of file diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 23267c4..e147652 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,8 +1,587 @@ - - - + + + + + + + + + + + + + + + + + + + + connectionParameters]]> + + + resource->getAttribute(\PDO::ATTR_DRIVER_NAME)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + data['sqlite_columns'][$schema]]]> + data['sqlite_columns'][$schema][$table]]]> + + + + data['columns'][$schema]]]> + data['columns'][$schema][$table]]]> + data['constraints'][$schema]]]> + data['constraints'][$schema][$table]]]> + data['sqlite_columns'][$schema]]]> + data['sqlite_columns'][$schema][$table]]]> + data['table_names'][$schema]]]> + data['triggers'][$schema]]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + toArray()]]> + + + + + + + quoteTrustedValue($value)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + processInfo['paramPrefix']]]> + processInfo['paramPrefix']]]> + + + + + + + + + + + statement]]> + + + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + statement]]> + + + + + + + + + + + + + + + + + + errorInfo()]]> + + + + + adapters['pdo_sqlite']))]]> + + + + + + + + + + + factory]]> + + + + + + + + factory]]> + factory]]> + factory]]> + factory]]> + + + + + + + + + + + + getMockBuilder(Driver::class) + ->setConstructorArgs([ + $this->mockConnection, + $this->mockStatement, + ]) + ->getMock()]]> + getMockBuilder(Statement::class)->getMock()]]> + + + + + + + + + + + + adapter]]> + + + + + + driver]]> + platform]]> + + + + + + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter->DrivER]]> + adapter->PlatForm]]> + adapter->driver]]> + adapter->foo]]> + adapter->platform]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ + PlatformInterface::class => Platform\Sqlite::class, + ProfilerInterface::class => Profiler::class, + ], + 'factories' => [ + AdapterInterface::class => AdapterServiceFactory::class, + DriverInterface::class => Driver\Pdo\DriverFactory::class, + Platform\Sqlite::class => InvokableFactory::class, + Profiler::class => InvokableFactory::class, + ], + ]]]> + + + + + + + + + + pdo]]> + + + + + + pdo]]> + pdo]]> + pdo]]> + pdo]]> + pdo]]> + pdo]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ + PlatformInterface::class => Platform\Sqlite::class, + ProfilerInterface::class => Profiler::class, + ], + 'factories' => [ + AdapterInterface::class => AdapterServiceFactory::class, + DriverInterface::class => Driver\Pdo\DriverFactory::class, + Platform\Sqlite::class => InvokableFactory::class, + Profiler::class => InvokableFactory::class, + ], + ]]]> + + + + + + + + + assertEquals(E_USER_NOTICE, $errno); + $this->assertEquals( + $errstr, + 'Attempting to quote a value in PhpDb\Adapter\Sqlite\Platform\Sqlite without extension/driver support can ' + . 'introduce security vulnerabilities in a production environment' + ); + $raisedNotice = true; + }]]> + assertEquals(E_USER_NOTICE, $errno); + $this->assertEquals( + $errstr, + 'Attempting to quote a value in PhpDb\Adapter\Sqlite\Platform\Sqlite without extension/driver support can ' + . 'introduce security vulnerabilities in a production environment' + ); + $raisedNotice = true; + }]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index d17b46c..a2ccf79 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -7,6 +7,7 @@ use Laminas\ServiceManager\Factory\InvokableFactory; use PhpDb\Adapter\AdapterInterface; use PhpDb\Adapter\Driver\ConnectionInterface; +use PhpDb\Adapter\Driver\PdoConnectionInterface; use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Driver\PdoDriverInterface; use PhpDb\Adapter\Driver\ResultInterface; @@ -59,6 +60,7 @@ public function getAdapterManagerConfig(): array 'pdosqlite' => Driver\Pdo\Pdo::class, 'pdodriver' => Driver\Pdo\Pdo::class, ConnectionInterface::class => Driver\Pdo\Connection::class, + PdoConnectionInterface::class => Driver\Pdo\Connection::class, DriverInterface::class => Driver\Pdo\Pdo::class, PdoDriverInterface::class => Driver\Pdo\Pdo::class, PlatformInterface::class => Platform\Sqlite::class, diff --git a/test/integration/Container/AdapterFactoryTest.php b/test/integration/Container/AdapterFactoryTest.php new file mode 100644 index 0000000..272a092 --- /dev/null +++ b/test/integration/Container/AdapterFactoryTest.php @@ -0,0 +1,25 @@ +container); + self::assertInstanceOf(AdapterInterface::class, $adapter); + } +} diff --git a/test/integration/Container/PdoConnectionFactoryTest.php b/test/integration/Container/PdoConnectionFactoryTest.php new file mode 100644 index 0000000..6095dcd --- /dev/null +++ b/test/integration/Container/PdoConnectionFactoryTest.php @@ -0,0 +1,32 @@ +container); + self::assertInstanceOf(ConnectionInterface::class, $instance); + self::assertInstanceOf(PdoConnectionInterface::class, $instance); + self::assertInstanceOf(Connection::class, $instance); + } +} diff --git a/test/integration/Container/PdoDriverFactoryTest.php b/test/integration/Container/PdoDriverFactoryTest.php new file mode 100644 index 0000000..bc16fc5 --- /dev/null +++ b/test/integration/Container/PdoDriverFactoryTest.php @@ -0,0 +1,31 @@ +container); + + self::assertInstanceOf(PdoDriverInterface::class, $instance); + self::assertInstanceOf(Pdo::class, $instance); + } +} diff --git a/test/integration/Container/PdoResultFactoryTest.php b/test/integration/Container/PdoResultFactoryTest.php new file mode 100644 index 0000000..4b8d7ac --- /dev/null +++ b/test/integration/Container/PdoResultFactoryTest.php @@ -0,0 +1,31 @@ +container); + + self::assertInstanceOf(ResultInterface::class, $result); + self::assertInstanceOf(Result::class, $result); + } +} diff --git a/test/integration/Container/PdoStatementFactoryTest.php b/test/integration/Container/PdoStatementFactoryTest.php new file mode 100644 index 0000000..00b6bf2 --- /dev/null +++ b/test/integration/Container/PdoStatementFactoryTest.php @@ -0,0 +1,30 @@ +container); + self::assertInstanceOf(StatementInterface::class, $statement); + self::assertInstanceOf(Statement::class, $statement); + } +} diff --git a/test/integration/Container/PlatformInterfaceFactoryTest.php b/test/integration/Container/PlatformInterfaceFactoryTest.php new file mode 100644 index 0000000..fb3e29e --- /dev/null +++ b/test/integration/Container/PlatformInterfaceFactoryTest.php @@ -0,0 +1,30 @@ +container); + self::assertInstanceOf(PlatformInterface::class, $instance); + self::assertInstanceOf(Sqlite::class, $instance); + } +} diff --git a/test/integration/Container/SqliteMetadataFactoryTest.php b/test/integration/Container/SqliteMetadataFactoryTest.php new file mode 100644 index 0000000..ac0e550 --- /dev/null +++ b/test/integration/Container/SqliteMetadataFactoryTest.php @@ -0,0 +1,27 @@ +container); + self::assertInstanceOf(MetadataInterface::class, $metadata); + self::assertInstanceOf(SqliteMetadata::class, $metadata); + } +} diff --git a/test/integration/Container/TestAsset/SetupTrait.php b/test/integration/Container/TestAsset/SetupTrait.php new file mode 100644 index 0000000..45a77ab --- /dev/null +++ b/test/integration/Container/TestAsset/SetupTrait.php @@ -0,0 +1,103 @@ + []]; + + protected ?AdapterInterface $adapter; + + protected AdapterManager $adapterManager; + + protected ContainerInterface $container; + + protected DriverInterface|string|null $driver; + + protected function setUp(): void + { + $this->getAdapter(); + parent::setUp(); + } + + protected function getAdapter(array $config = []): AdapterInterface + { + $connectionConfig = [ + 'db' => [ + 'driver' => $this->driver ?? Pdo::class, + 'connection' => [ + 'dsn' => 'sqlite::memory:', + 'charset' => 'utf8', + 'driver_options' => [], + ], + 'options' => [ + //'buffer_results' => false, + ], + ], + ]; + + // merge service config from both PhpDb and PhpDb\Adapter\Mysql + $serviceManagerConfig = ArrayUtils::merge( + (new LaminasDbConfigProvider())()['dependencies'], + (new ConfigProvider())()['dependencies'] + ); + + $serviceManagerConfig = ArrayUtils::merge( + $serviceManagerConfig, + $connectionConfig + ); + + // prefer passed config over environment variables + if ($config !== []) { + $serviceManagerConfig = ArrayUtils::merge($serviceManagerConfig, $config); + } + + $serviceManagerConfig = ArrayUtils::merge( + $serviceManagerConfig, + [ + 'services' => [ + 'config' => $serviceManagerConfig, + ], + ] + ); + + $this->config = $serviceManagerConfig; + $this->container = new ServiceManager($this->config); + $this->adapterManager = $this->container->get(AdapterManager::class); + $this->adapter = $this->adapterManager->get(AdapterInterface::class); + + return $this->adapter; + } + + protected function getConfig(): array + { + return $this->config; + } + + protected function getHostname(): string + { + return $this->getConfig()['db']['connection']['hostname']; + } +} diff --git a/test/integration/Driver/Pdo/AbstractAdapterTestCase.php b/test/integration/Driver/Pdo/AbstractAdapterTestCase.php index ec2750e..66228b5 100644 --- a/test/integration/Driver/Pdo/AbstractAdapterTestCase.php +++ b/test/integration/Driver/Pdo/AbstractAdapterTestCase.php @@ -3,6 +3,7 @@ namespace PhpDbIntegrationTest\Adapter\Sqlite\Driver\Pdo; use PhpDb\Adapter\AdapterInterface; +use PhpDbIntegrationTest\Adapter\Sqlite\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\TestCase; @@ -12,7 +13,7 @@ #[CoversMethod(AdapterInterface::class, '__construct()')] abstract class AbstractAdapterTestCase extends TestCase { - use AdapterTrait; + use SetupTrait; public ?int $port = null; diff --git a/test/integration/Driver/Pdo/AdapterTrait.php b/test/integration/Driver/Pdo/AdapterTrait.php deleted file mode 100644 index 160b496..0000000 --- a/test/integration/Driver/Pdo/AdapterTrait.php +++ /dev/null @@ -1,25 +0,0 @@ -adapter === null) { - $this->fail('Adapter not initialized'); - } - - return $this->adapter; - } - - protected function getHostname(): ?string - { - return $this->hostname; - } -} diff --git a/test/integration/Driver/Pdo/ConnectionIntegrationTest.php b/test/integration/Driver/Pdo/ConnectionIntegrationTest.php index da42fdf..782d66b 100644 --- a/test/integration/Driver/Pdo/ConnectionIntegrationTest.php +++ b/test/integration/Driver/Pdo/ConnectionIntegrationTest.php @@ -4,8 +4,8 @@ namespace PhpDbIntegrationTest\Adapter\Sqlite\Driver\Pdo; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Connection; -use PhpDbIntegrationTest\Adapter\Sqlite\Driver\Pdo\TestAsset\SqliteMemoryPdo; +use PhpDb\Adapter\Driver\PdoConnectionInterface; +use PhpDbIntegrationTest\Adapter\Sqlite\Container\TestAsset\SetupTrait; use PDO; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; @@ -27,12 +27,11 @@ #[Group('integration-pdo')] final class ConnectionIntegrationTest extends TestCase { - /** @var array */ - protected array $variables = ['driver' => 'pdo_sqlite', 'database' => ':memory:']; + use SetupTrait; public function testGetCurrentSchema(): void { - $connection = new Connection($this->variables); + $connection = $this->container->get(PdoConnectionInterface::class); self::assertIsString($connection->getCurrentSchema()); } diff --git a/test/integration/Driver/Pdo/StatementTest.php b/test/integration/Driver/Pdo/StatementTest.php index df2bf9a..070b969 100644 --- a/test/integration/Driver/Pdo/StatementTest.php +++ b/test/integration/Driver/Pdo/StatementTest.php @@ -5,10 +5,10 @@ namespace PhpDbIntegrationTest\Adapter\Sqlite\Driver\Pdo; use PhpDb\Adapter\Driver\StatementInterface; +use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Adapter\Sqlite\Driver\Pdo\Connection; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Driver; +use PhpDb\Adapter\Sqlite\Driver\Pdo\Pdo; use PhpDb\Adapter\Sqlite\Driver\Pdo\Result; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Statement; use PhpDbIntegrationTest\Adapter\Sqlite\Driver\Pdo\TestAsset\SqliteMemoryPdo; use Override; use PHPUnit\Framework\Attributes\CoversMethod; @@ -25,6 +25,7 @@ #[CoversMethod(Statement::class, 'execute')] final class StatementTest extends TestCase { + protected Statement $statement; public function testGetResource(): void diff --git a/test/integration/Driver/Pdo/TestAsset/SqliteMemoryPdo.php b/test/integration/Driver/Pdo/TestAsset/SqliteMemoryPdo.php deleted file mode 100644 index 03e953c..0000000 --- a/test/integration/Driver/Pdo/TestAsset/SqliteMemoryPdo.php +++ /dev/null @@ -1,35 +0,0 @@ -exec($sql)) { - throw new Exception(sprintf( - "Error: %s, %s", - $this->errorCode() ?? '', - implode(",", $this->errorInfo()) - )); - } - } -} diff --git a/test/unit/Sql/Platform/SelectDecoratorTest.php b/test/unit/Sql/Platform/SelectDecoratorTest.php index 34446b5..b93cf03 100644 --- a/test/unit/Sql/Platform/SelectDecoratorTest.php +++ b/test/unit/Sql/Platform/SelectDecoratorTest.php @@ -1,30 +1,51 @@ driver = $this->getMockBuilder(PdoDriverInterface::class) + ->getMock(); + + $this->platform = $this->getMockBuilder(SqlitePlatform::class) + ->onlyMethods([]) + ->setConstructorArgs([$this->driver]) + ->getMock(); + + parent::setUp(); + } + public function testLimitAndOffset(): void { $select = new Select(); $select->offset(5); $selectDecorator = new SelectDecorator(); $selectDecorator->setSubject($select); - $sqlString = $selectDecorator->getSqlString(new SqlitePlatform()); + $sqlString = $selectDecorator->getSqlString($this->platform); self::assertEquals('SELECT * LIMIT 18446744073709551615 OFFSET 5', $sqlString); } @@ -40,11 +61,11 @@ public function testPrepareStatementPreparesUnionSyntaxFromCombine( $driver->expects($this->any())->method('formatParameterName')->willReturn('?'); // test - $adapter = $this->getMockBuilder(Adapter::class) + $adapter = $this->getMockBuilder(AdapterInterface::class) ->onlyMethods([]) ->setConstructorArgs([ $driver, - new SqlitePlatform(), + $this->platform, ]) ->getMock(); @@ -76,7 +97,7 @@ public function testGetSqlStringPreparesUnionSyntaxFromCombine( $selectDecorator = new SelectDecorator(); $selectDecorator->setSubject($select); - self::assertEquals($expectedSql, $selectDecorator->getSqlString(new SqlitePlatform())); + self::assertEquals($expectedSql, $selectDecorator->getSqlString($this->platform)); } /** From 6ca6775e5a5a85d461ef122e726e69f08076155a Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Sun, 20 Jul 2025 02:39:36 -0500 Subject: [PATCH 05/10] enable github actions Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- .github/workflows/continuous-integration.yml | 47 ++++++++++++++++++++ .laminas-ci.json | 16 +++++++ 2 files changed, 63 insertions(+) create mode 100644 .github/workflows/continuous-integration.yml create mode 100644 .laminas-ci.json diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..7ed76f2 --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,47 @@ +name: "Continuous Integration" + +on: + pull_request: + push: + branches: + tags: + +jobs: + matrix: + name: Generate job matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - name: Gather CI configuration + id: matrix + uses: laminas/laminas-ci-matrix-action@v1 + + qa: + name: QA Checks + needs: [matrix] + runs-on: ${{ matrix.operatingSystem }} + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }} + steps: + - name: ${{ matrix.name }} + uses: laminas/laminas-continuous-integration-action@v1 + with: + job: ${{ matrix.job }} + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ROOT_PASSWORD: 'password' + MYSQL_ROOT_HOST: '%' + MYSQL_USER: 'gha' + MYSQL_PASSWORD: 'password' + MYSQL_DATABASE: 'laminasdb_test' + options: >- + --health-cmd="mysqladmin ping" + --health-interval 10s + --health-timeout 5s + --health-retries 3 + ports: + - 3306 \ No newline at end of file diff --git a/.laminas-ci.json b/.laminas-ci.json new file mode 100644 index 0000000..4e488ab --- /dev/null +++ b/.laminas-ci.json @@ -0,0 +1,16 @@ +{ + "ignore_php_platform_requirements": { + "8.4": true + }, + "exclude": [ + { + "name": "PHPUnit", + "php": "8.1" + }, + { + "name": "PHPUnit", + "php": "8.4", + "dependencies": "lowest" + } + ] +} \ No newline at end of file From a7789889bb675c7c4ff61eb4f1ccc211081aaafc Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Sun, 20 Jul 2025 02:41:36 -0500 Subject: [PATCH 06/10] Signed-off-by: Joey Smith --- .github/workflows/continuous-integration.yml | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 7ed76f2..25bc29b 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -28,20 +28,4 @@ jobs: - name: ${{ matrix.name }} uses: laminas/laminas-continuous-integration-action@v1 with: - job: ${{ matrix.job }} - services: - mysql: - image: mysql:5.7 - env: - MYSQL_ROOT_PASSWORD: 'password' - MYSQL_ROOT_HOST: '%' - MYSQL_USER: 'gha' - MYSQL_PASSWORD: 'password' - MYSQL_DATABASE: 'laminasdb_test' - options: >- - --health-cmd="mysqladmin ping" - --health-interval 10s - --health-timeout 5s - --health-retries 3 - ports: - - 3306 \ No newline at end of file + job: ${{ matrix.job }} \ No newline at end of file From 75cb1bfdbe5486a37b9d510a1e017c04dcb5ee24 Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Tue, 22 Jul 2025 00:23:07 -0500 Subject: [PATCH 07/10] Fixes integration test for the moment. Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- phpunit.xml.dist | 2 +- .../Container/TestAsset/SetupTrait.php | 6 +- .../Driver/Pdo/ConnectionIntegrationTest.php | 42 ++++++----- test/integration/Driver/Pdo/StatementTest.php | 73 +++++++++---------- test/integration/Platform/SqliteTest.php | 43 ++--------- 5 files changed, 68 insertions(+), 98 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d4d75ab..819d95b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,7 +21,7 @@ ./test/integration - + ./src diff --git a/test/integration/Container/TestAsset/SetupTrait.php b/test/integration/Container/TestAsset/SetupTrait.php index 45a77ab..1acd229 100644 --- a/test/integration/Container/TestAsset/SetupTrait.php +++ b/test/integration/Container/TestAsset/SetupTrait.php @@ -4,6 +4,7 @@ namespace PhpDbIntegrationTest\Adapter\Sqlite\Container\TestAsset; +use Override; use Laminas\ServiceManager\ServiceManager; use Laminas\Stdlib\ArrayUtils; use PhpDb\Adapter\AdapterInterface; @@ -13,8 +14,7 @@ use PhpDb\Container\AdapterManager; use PhpDb\Container\ConfigProvider as LaminasDbConfigProvider; use Psr\Container\ContainerInterface; - -use function getenv; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; /** * This trait provides a setup method for integration tests that require @@ -24,6 +24,7 @@ * allowing for the creation of an adapter manager and the retrieval * of an adapter instance. */ +#[RequiresPhpExtension('pdo_sqlite')] trait SetupTrait { protected array $config = ['db' => []]; @@ -36,6 +37,7 @@ trait SetupTrait protected DriverInterface|string|null $driver; + #[Override] protected function setUp(): void { $this->getAdapter(); diff --git a/test/integration/Driver/Pdo/ConnectionIntegrationTest.php b/test/integration/Driver/Pdo/ConnectionIntegrationTest.php index 782d66b..7acc4c3 100644 --- a/test/integration/Driver/Pdo/ConnectionIntegrationTest.php +++ b/test/integration/Driver/Pdo/ConnectionIntegrationTest.php @@ -4,9 +4,10 @@ namespace PhpDbIntegrationTest\Adapter\Sqlite\Driver\Pdo; +use PDO; use PhpDb\Adapter\Driver\PdoConnectionInterface; +use PhpDb\Adapter\Sqlite\Driver\Pdo\Connection; use PhpDbIntegrationTest\Adapter\Sqlite\Container\TestAsset\SetupTrait; -use PDO; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; @@ -31,56 +32,54 @@ final class ConnectionIntegrationTest extends TestCase public function testGetCurrentSchema(): void { - $connection = $this->container->get(PdoConnectionInterface::class); + $connection = $this->getAdapter()->getDriver()->getConnection(); self::assertIsString($connection->getCurrentSchema()); } - public function testSetResource(): void - { - $resource = new SqliteMemoryPdo(); - $connection = new Connection([]); - self::assertSame($connection, $connection->setResource($resource)); + // public function testSetResource(): void + // { + // $resource = $this->getAdapter()->getDriver()->getConnection()->getResource(); + // $connection = new Connection([]); + // self::assertSame($connection, $connection->setResource($resource)); - $connection->disconnect(); - unset($connection); - unset($resource); - } + // $connection->disconnect(); + // unset($connection); + // unset($resource); + // } public function testGetResource(): void { - $connection = new Connection($this->variables); + $connection = $this->getAdapter()->getDriver()->getConnection(); $connection->connect(); self::assertInstanceOf(PDO::class, $connection->getResource()); $connection->disconnect(); - unset($connection); } public function testConnect(): void { - $connection = new Connection($this->variables); + $connection = $this->getAdapter()->getDriver()->getConnection(); self::assertSame($connection, $connection->connect()); self::assertTrue($connection->isConnected()); $connection->disconnect(); - unset($connection); } public function testIsConnected(): void { - $connection = new Connection($this->variables); + $connection = $this->getAdapter()->getDriver()->getConnection(); self::assertFalse($connection->isConnected()); self::assertSame($connection, $connection->connect()); self::assertTrue($connection->isConnected()); $connection->disconnect(); - unset($connection); + //unset($connection); } public function testDisconnect(): void { - $connection = new Connection($this->variables); + $connection = $this->getAdapter()->getDriver()->getConnection(); $connection->connect(); self::assertTrue($connection->isConnected()); $connection->disconnect(); @@ -129,8 +128,11 @@ public function testGetLastGeneratedValue(): never public function testConnectReturnsConnectionWhenResourceSet(): void { - $resource = new SqliteMemoryPdo(); - $connection = new Connection([]); + /** @var PDO $resource */ + $resource = $this->getAdapter()->getDriver()->getConnection()->getResource(); + /** @var PdoConnectionInterface&Connection $connection */ + $connection = $this->getAdapter()->getDriver()->getConnection(); + self::assertInstanceOf(PdoConnectionInterface::class, $connection); $connection->setResource($resource); self::assertSame($connection, $connection->connect()); diff --git a/test/integration/Driver/Pdo/StatementTest.php b/test/integration/Driver/Pdo/StatementTest.php index 070b969..ab73c02 100644 --- a/test/integration/Driver/Pdo/StatementTest.php +++ b/test/integration/Driver/Pdo/StatementTest.php @@ -4,13 +4,12 @@ namespace PhpDbIntegrationTest\Adapter\Sqlite\Driver\Pdo; +use PDO; +use PDOStatement; use PhpDb\Adapter\Driver\StatementInterface; use PhpDb\Adapter\Driver\Pdo\Statement; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Connection; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Pdo; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Result; -use PhpDbIntegrationTest\Adapter\Sqlite\Driver\Pdo\TestAsset\SqliteMemoryPdo; -use Override; +use PhpDb\Adapter\Driver\Pdo\Result; +use PhpDbIntegrationTest\Adapter\Sqlite\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\TestCase; @@ -25,28 +24,34 @@ #[CoversMethod(Statement::class, 'execute')] final class StatementTest extends TestCase { - - protected Statement $statement; + use SetupTrait; public function testGetResource(): void { - $pdo = new SqliteMemoryPdo(); + /** @var PDO $pdo */ + $pdo = $this->getAdapter()->getDriver()->getConnection()->getResource(); + /** @var StatementInterface&Statement $statement */ + $statement = $this->getAdapter()->getDriver()->createStatement(); + /** @var PDOStatement $stmt */ $stmt = $pdo->prepare('SELECT 1'); - $this->statement->setResource($stmt); + $statement->setResource($stmt); - self::assertSame($stmt, $this->statement->getResource()); + self::assertSame($stmt, $statement->getResource()); } public function testSetSql(): void { - $this->statement->setSql('SELECT 1'); - self::assertEquals('SELECT 1', $this->statement->getSql()); + /** @var StatementInterface&Statement $statement */ + $statement = $this->getAdapter()->getDriver()->createStatement(); + $statement->setSql('SELECT 1'); + self::assertEquals('SELECT 1', $statement->getSql()); } public function testGetSql(): void { - $this->statement->setSql('SELECT 1'); - self::assertEquals('SELECT 1', $this->statement->getSql()); + $statement = $this->getAdapter()->getDriver()->createStatement(); + $statement->setSql('SELECT 1'); + self::assertEquals('SELECT 1', $statement->getSql()); } /** @@ -54,39 +59,27 @@ public function testGetSql(): void */ public function testPrepare(): void { - $this->statement->initialize(new SqliteMemoryPdo()); - self::assertInstanceOf(StatementInterface::class, $this->statement->prepare('SELECT 1')); + /** @var StatementInterface&Statement $statement */ + $statement = $this->getAdapter()->getDriver()->createStatement(); + self::assertInstanceOf(StatementInterface::class, $statement->prepare('SELECT 1')); } public function testIsPrepared(): void { - self::assertFalse($this->statement->isPrepared()); - $this->statement->initialize(new SqliteMemoryPdo()); - $this->statement->prepare('SELECT 1'); - self::assertTrue($this->statement->isPrepared()); + /** @var StatementInterface&Statement $statement */ + $statement = $this->getAdapter()->getDriver()->createStatement(); + self::assertFalse($statement->isPrepared()); + //$statement->initialize($resource); + $statement->prepare('SELECT 1'); + self::assertTrue($statement->isPrepared()); } public function testExecute(): void { - $this->statement->setDriver(new Driver(new Connection($pdo = new SqliteMemoryPdo()))); - $this->statement->initialize($pdo); - $this->statement->prepare('SELECT 1'); - self::assertInstanceOf(Result::class, $this->statement->execute()); + /** @var StatementInterface&Statement $statement */ + $statement = $this->getAdapter()->getDriver()->createStatement(); + //$statement->initialize($pdo); + $statement->prepare('SELECT 1'); + self::assertInstanceOf(Result::class, $statement->execute()); } - - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void - { - $this->statement = new Statement(); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown(): void {} } diff --git a/test/integration/Platform/SqliteTest.php b/test/integration/Platform/SqliteTest.php index c9e0986..c87ef12 100644 --- a/test/integration/Platform/SqliteTest.php +++ b/test/integration/Platform/SqliteTest.php @@ -1,55 +1,28 @@ */ - public array|PDO $adapters = []; + use SetupTrait; - /** - * @return void - */ - public function testQuoteValueWithPdoSqlite() + public function testQuoteValueWithPdoSqlite(): void { - if (! $this->adapters['pdo_sqlite'] instanceof PDO) { - $this->markTestSkipped('SQLite (PDO_SQLITE) not configured in unit test configuration file'); - } - - $sqlite = new Sqlite($this->adapters['pdo_sqlite']); - $value = $sqlite->quoteValue('value'); - self::assertEquals('\'value\'', $value); - - $sqlite = new Sqlite(new Driver(new Connection($this->adapters['pdo_sqlite']))); + $sqlite = $this->getAdapter()->getPlatform(); $value = $sqlite->quoteValue('value'); self::assertEquals('\'value\'', $value); } - - #[Override] - protected function setUp(): void - { - if (! extension_loaded('pdo')) { - $this->markTestSkipped(self::class . ' integration tests are not enabled!'); - } - - if (extension_loaded('pdo')) { - $this->adapters['pdo_sqlite'] = new PDO( - 'sqlite::memory' - ); - } - } } From a0c86a7b75e1c5e75fb2e401d366a3361889986c Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Thu, 24 Jul 2025 02:00:14 -0500 Subject: [PATCH 08/10] Continues work to fix unit test Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- psalm-baseline.xml | 113 ++++++++---------- test/unit/AdapterTest.php | 6 +- test/unit/ConfigProviderTest.php | 78 ++++++++++-- test/unit/Driver/Pdo/PdoTest.php | 26 ++-- .../Driver/Pdo/StatementIntegrationTest.php | 25 ++-- test/unit/Driver/Pdo/StatementTest.php | 2 +- test/unit/ModuleTest.php | 42 ------- test/unit/Platform/SqliteTest.php | 2 +- 8 files changed, 142 insertions(+), 152 deletions(-) delete mode 100644 test/unit/ModuleTest.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index e147652..6a39fe2 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -285,57 +285,56 @@ - - - - - - statement]]> - - - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - statement]]> - - - - - - - - - - - - - - - - - - errorInfo()]]> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - adapters['pdo_sqlite']))]]> + + - - - + + getConfig()['db']['connection']]]> + @@ -568,20 +567,4 @@ - - - - - - - - - - - - - - - - diff --git a/test/unit/AdapterTest.php b/test/unit/AdapterTest.php index abdfca2..077b82c 100644 --- a/test/unit/AdapterTest.php +++ b/test/unit/AdapterTest.php @@ -13,7 +13,7 @@ use PhpDb\ResultSet\ResultSet; use PhpDb\ResultSet\ResultSetInterface; use PhpDb\Adapter\Sqlite\Adapter; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Driver; +use PhpDb\Adapter\Sqlite\Driver\Pdo\Pdo; use PhpDb\Adapter\Sqlite\Driver\Pdo\Statement; use PhpDb\Adapter\Sqlite\Platform\Sqlite as SqlitePlatform; use PhpDbTest\Adapter\Sqlite\TestAsset\TemporaryResultSet; @@ -72,7 +72,7 @@ public function testCreateDriver(): void { if (extension_loaded('pdo')) { $adapter = new Adapter(['driver' => 'pdo_sqlite'], $this->mockPlatform); - self::assertInstanceOf(Driver::class, $adapter->driver); + self::assertInstanceOf(Pdo::class, $adapter->driver); unset($adapter); } } @@ -246,7 +246,7 @@ protected function setUp(): void $this->mockConnection = $this->createMock(ConnectionInterface::class); $this->mockPlatform = new SqlitePlatform(); $this->mockStatement = $this->getMockBuilder(Statement::class)->getMock(); - $this->mockDriver = $this->getMockBuilder(Driver::class) + $this->mockDriver = $this->getMockBuilder(Pdo::class) ->setConstructorArgs([ $this->mockConnection, $this->mockStatement, diff --git a/test/unit/ConfigProviderTest.php b/test/unit/ConfigProviderTest.php index 2e6cfea..a73109f 100644 --- a/test/unit/ConfigProviderTest.php +++ b/test/unit/ConfigProviderTest.php @@ -1,16 +1,29 @@ > */ private array $config = [ 'aliases' => [ - PlatformInterface::class => Platform\Sqlite::class, - ProfilerInterface::class => Profiler::class, + MetadataInterface::class => Metadata\Source\SqliteMetadata::class, + ], + 'factories' => [ + Metadata\Source\SqliteMetadata::class => Container\MetadataInterfaceFactory::class, + ], + 'delegators' => [ + AdapterManager::class => [ + Container\AdapterManagerDelegator::class, + ], + ], + ]; + + private array $adapterManagerConfig = [ + 'aliases' => [ + 'SQLite' => Driver\Pdo\Pdo::class, + 'Sqlite' => Driver\Pdo\Pdo::class, + 'sqlite' => Driver\Pdo\Pdo::class, + 'pdo' => Driver\Pdo\Pdo::class, + 'pdo_sqlite' => Driver\Pdo\Pdo::class, + 'pdosqlite' => Driver\Pdo\Pdo::class, + 'pdodriver' => Driver\Pdo\Pdo::class, + ConnectionInterface::class => Driver\Pdo\Connection::class, + PdoConnectionInterface::class => Driver\Pdo\Connection::class, + DriverInterface::class => Driver\Pdo\Pdo::class, + PdoDriverInterface::class => Driver\Pdo\Pdo::class, + PlatformInterface::class => Platform\Sqlite::class, + ProfilerInterface::class => Profiler::class, + ResultInterface::class => Result::class, + ResultSet\ResultSetInterface::class => ResultSet\ResultSet::class, + StatementInterface::class => Statement::class, ], 'factories' => [ - AdapterInterface::class => AdapterServiceFactory::class, - DriverInterface::class => Driver\Pdo\DriverFactory::class, - Platform\Sqlite::class => InvokableFactory::class, - Profiler::class => InvokableFactory::class, + AdapterInterface::class => Container\AdapterFactory::class, + Driver\Pdo\Connection::class => Container\PdoConnectionFactory::class, + Driver\Pdo\Pdo::class => Container\PdoDriverFactory::class, + Result::class => Container\PdoResultFactory::class, + Statement::class => Container\PdoStatementFactory::class, + Platform\Sqlite::class => Container\PlatformInterfaceFactory::class, + //Profiler::class => InvokableFactory::class, + ResultSet\ResultSet::class => InvokableFactory::class, ], ]; - public function testProvidesExpectedConfiguration(): ConfigProvider + public function testProvidesExpectedDependencies(): ConfigProvider { $provider = new ConfigProvider(); self::assertEquals($this->config, $provider->getDependencies()); @@ -42,9 +88,21 @@ public function testProvidesExpectedConfiguration(): ConfigProvider return $provider; } - #[Depends('testProvidesExpectedConfiguration')] + public function testProvidesExpectedAdapterManagerConfiguration(): void + { + $provider = new ConfigProvider(); + self::assertEquals($this->adapterManagerConfig, $provider->getAdapterManagerConfig()); + } + + #[Depends('testProvidesExpectedDependencies')] public function testInvocationProvidesDependencyConfiguration(ConfigProvider $provider): void { - self::assertEquals(['dependencies' => $provider->getDependencies()], $provider()); + self::assertEquals( + [ + 'dependencies' => $provider->getDependencies(), + AdapterManager::class => $provider->getAdapterManagerConfig(), + ], + $provider() + ); } } diff --git a/test/unit/Driver/Pdo/PdoTest.php b/test/unit/Driver/Pdo/PdoTest.php index bafd913..7fabbb4 100644 --- a/test/unit/Driver/Pdo/PdoTest.php +++ b/test/unit/Driver/Pdo/PdoTest.php @@ -4,21 +4,21 @@ namespace PhpDbTest\Adapter\Sqlite\Sqlite\Driver\Pdo; -use PhpDb\Adapter\Driver\DriverInterface; +use Override; +use PhpDb\Adapter\Driver\PdoDriverInterface; use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Exception\RuntimeException; use PhpDb\Adapter\Sqlite\Driver\Pdo\Connection; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Driver; -use Override; +use PhpDb\Adapter\Sqlite\Driver\Pdo\Pdo; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -#[CoversMethod(Driver::class, 'getDatabasePlatformName')] -#[CoversMethod(Driver::class, 'getResultPrototype')] +#[CoversMethod(Pdo::class, 'getDatabasePlatformName')] +#[CoversMethod(Pdo::class, 'getResultPrototype')] final class PdoTest extends TestCase { - protected Driver $pdo; + protected Pdo $pdo; /** * Sets up the fixture, for example, opens a network connection. @@ -29,14 +29,14 @@ protected function setUp(): void { $connection = new Connection(); - $this->pdo = new Driver($connection); + $this->pdo = new Pdo($connection); } public function testGetDatabasePlatformName(): void { $this->pdo->getConnection()->setConnectionParameters(['pdodriver' => 'pdo_sqlite']); self::assertEquals('Sqlite', $this->pdo->getDatabasePlatformName()); - self::assertEquals('SQLite', $this->pdo->getDatabasePlatformName(DriverInterface::NAME_FORMAT_NATURAL)); + self::assertEquals('SQLite', $this->pdo->getDatabasePlatformName(PdoDriverInterface::NAME_FORMAT_NATURAL)); } /** @psalm-return array */ @@ -48,11 +48,11 @@ public static function getParamsAndType(): array ['123foo', null, ':123foo'], [1, null, '?'], ['1', null, '?'], - ['foo', DriverInterface::PARAMETERIZATION_NAMED, ':foo'], - ['foo_bar', DriverInterface::PARAMETERIZATION_NAMED, ':foo_bar'], - ['123foo', DriverInterface::PARAMETERIZATION_NAMED, ':123foo'], - [1, DriverInterface::PARAMETERIZATION_NAMED, ':1'], - ['1', DriverInterface::PARAMETERIZATION_NAMED, ':1'], + ['foo', PdoDriverInterface::PARAMETERIZATION_NAMED, ':foo'], + ['foo_bar', PdoDriverInterface::PARAMETERIZATION_NAMED, ':foo_bar'], + ['123foo', PdoDriverInterface::PARAMETERIZATION_NAMED, ':123foo'], + [1, PdoDriverInterface::PARAMETERIZATION_NAMED, ':1'], + ['1', PdoDriverInterface::PARAMETERIZATION_NAMED, ':1'], [':foo', null, ':foo'], ]; } diff --git a/test/unit/Driver/Pdo/StatementIntegrationTest.php b/test/unit/Driver/Pdo/StatementIntegrationTest.php index 94b28a4..b8d8d7f 100644 --- a/test/unit/Driver/Pdo/StatementIntegrationTest.php +++ b/test/unit/Driver/Pdo/StatementIntegrationTest.php @@ -4,12 +4,11 @@ namespace PhpDbTest\Adapter\Sqlite\Driver\Pdo; -use PhpDb\Adapter\Driver\Pdo\Statement; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Driver; -use PhpDbTest\Adapter\Sqlite\Driver\Pdo\TestAsset\CtorlessPdo; use Override; -use PDO; use PDOStatement; +use PhpDb\Adapter\Driver\Pdo\Statement; +use PhpDb\Adapter\Sqlite\Driver\Pdo\Pdo; +use PhpDbTest\Adapter\Sqlite\Driver\Pdo\TestAsset\CtorlessPdo; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -26,7 +25,7 @@ public function testStatementExecuteWillConvertPhpBoolToPdoBoolWhenBinding(): vo $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( $this->equalTo(':foo'), $this->equalTo(false), - $this->equalTo(PDO::PARAM_BOOL) + $this->equalTo(\PDO::PARAM_BOOL) ); $this->statement->execute(['foo' => false]); } @@ -36,7 +35,7 @@ public function testStatementExecuteWillUsePdoStrByDefaultWhenBinding(): void $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( $this->equalTo(':foo'), $this->equalTo('bar'), - $this->equalTo(PDO::PARAM_STR) + $this->equalTo(\PDO::PARAM_STR) ); $this->statement->execute(['foo' => 'bar']); } @@ -46,7 +45,7 @@ public function testStatementExecuteWillUsePdoStrForStringIntegerWhenBinding(): $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( $this->equalTo(':foo'), $this->equalTo('123'), - $this->equalTo(PDO::PARAM_STR) + $this->equalTo(\PDO::PARAM_STR) ); $this->statement->execute(['foo' => '123']); } @@ -56,7 +55,7 @@ public function testStatementExecuteWillUsePdoIntForIntWhenBinding(): void $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( $this->equalTo(':foo'), $this->equalTo(123), - $this->equalTo(PDO::PARAM_INT) + $this->equalTo(\PDO::PARAM_INT) ); $this->statement->execute(['foo' => 123]); } @@ -68,7 +67,7 @@ public function testStatementExecuteWillUsePdoIntForIntWhenBinding(): void #[Override] protected function setUp(): void { - $driver = $this->getMockBuilder(Driver::class) + $driver = $this->getMockBuilder(Pdo::class) ->onlyMethods(['createResult']) ->disableOriginalConstructor() ->getMock(); @@ -81,12 +80,4 @@ protected function setUp(): void ->getMock() )); } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown(): void - { - } } diff --git a/test/unit/Driver/Pdo/StatementTest.php b/test/unit/Driver/Pdo/StatementTest.php index 2a0552b..ccf0951 100644 --- a/test/unit/Driver/Pdo/StatementTest.php +++ b/test/unit/Driver/Pdo/StatementTest.php @@ -7,7 +7,7 @@ use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Adapter\ParameterContainer; use PhpDb\Adapter\Sqlite\Driver\Pdo\Connection; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Driver; +use PhpDb\Adapter\Sqlite\Driver\Pdo\Pdo; use Override; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\TestCase; diff --git a/test/unit/ModuleTest.php b/test/unit/ModuleTest.php deleted file mode 100644 index 091ff6e..0000000 --- a/test/unit/ModuleTest.php +++ /dev/null @@ -1,42 +0,0 @@ -> */ - private array $config = [ - 'aliases' => [ - PlatformInterface::class => Platform\Sqlite::class, - ProfilerInterface::class => Profiler::class, - ], - 'factories' => [ - AdapterInterface::class => AdapterServiceFactory::class, - DriverInterface::class => Driver\Pdo\DriverFactory::class, - Platform\Sqlite::class => InvokableFactory::class, - Profiler::class => InvokableFactory::class, - ], - ]; - - public function testProvidesExpectedConfiguration(): Module - { - $provider = new Module(); - self::assertEquals(['service_manager' => $this->config], $provider->getConfig()); - - return $provider; - } -} diff --git a/test/unit/Platform/SqliteTest.php b/test/unit/Platform/SqliteTest.php index ca5e9be..8053a11 100644 --- a/test/unit/Platform/SqliteTest.php +++ b/test/unit/Platform/SqliteTest.php @@ -3,7 +3,7 @@ namespace PhpDbTest\Adapter\Sqlite\Sqlite\Platform; use PhpDb\Adapter\Sqlite\Driver\Pdo\Connection; -use PhpDb\Adapter\Sqlite\Driver\Pdo\Driver; +use PhpDb\Adapter\Sqlite\Driver\Pdo\Pdo; use PhpDb\Adapter\Sqlite\Platform\Sqlite; use Override; use PHPUnit\Framework\Attributes\CoversMethod; From 3ab93e6932c4763d31758828313e467cd5dba157 Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Tue, 29 Jul 2025 22:41:11 -0500 Subject: [PATCH 09/10] stash latest revisions Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- src/Metadata/Source/SqliteMetadata.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Metadata/Source/SqliteMetadata.php b/src/Metadata/Source/SqliteMetadata.php index 368d19c..0d09a4e 100644 --- a/src/Metadata/Source/SqliteMetadata.php +++ b/src/Metadata/Source/SqliteMetadata.php @@ -4,6 +4,7 @@ namespace PhpDb\Adapter\Sqlite\Metadata\Source; +use Override; use PhpDb\Adapter\AdapterInterface; use PhpDb\Metadata\Source\AbstractSource; use PhpDb\ResultSet\ResultSetInterface; @@ -15,8 +16,9 @@ use function preg_match; use function strtoupper; -class SqliteMetadata extends AbstractSource +final class SqliteMetadata extends AbstractSource { + #[Override] protected function loadSchemaData(): void { if (isset($this->data['schemas'])) { @@ -31,6 +33,7 @@ protected function loadSchemaData(): void $this->data['schemas'] = $schemas; } + #[Override] protected function loadTableNameData(string $schema): void { if (isset($this->data['table_names'][$schema])) { @@ -73,6 +76,7 @@ protected function loadTableNameData(string $schema): void $this->data['table_names'][$schema] = $tables; } + #[Override] protected function loadColumnData(string $table, string $schema): void { if (isset($this->data['columns'][$schema][$table])) { @@ -106,6 +110,7 @@ protected function loadColumnData(string $table, string $schema): void $this->data['sqlite_columns'][$schema][$table] = $results; } + #[Override] protected function loadConstraintData(string $table, string $schema): void { if (isset($this->data['constraints'][$schema][$table])) { @@ -167,6 +172,7 @@ protected function loadConstraintData(string $table, string $schema): void foreach ($foreignKeys as $fk) { if ($id !== $fk['id']) { $id = $fk['id']; + // todo: decide on whether to continue to use _laminas_ $name = '_laminas_' . $table . '_FOREIGN_KEY_' . ($id + 1); $constraints[$name] = [ 'constraint_name' => $name, @@ -189,6 +195,7 @@ protected function loadConstraintData(string $table, string $schema): void $this->data['constraints'][$schema][$table] = $constraints; } + #[Override] protected function loadTriggerData(string $schema): void { if (isset($this->data['triggers'][$schema])) { From 0d2c1c8358396a752fffe6aa58aacd751ef8933b Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Tue, 19 Aug 2025 16:11:53 -0500 Subject: [PATCH 10/10] Latest revisions before adding automatic release workflow Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- src/ConfigProvider.php | 2 +- src/Container/PdoStatementFactory.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index a2ccf79..ce40636 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -76,7 +76,7 @@ public function getAdapterManagerConfig(): array Result::class => Container\PdoResultFactory::class, Statement::class => Container\PdoStatementFactory::class, Platform\Sqlite::class => Container\PlatformInterfaceFactory::class, - //Profiler::class => InvokableFactory::class, + Profiler::class => InvokableFactory::class, ResultSet\ResultSet::class => InvokableFactory::class, ], ]; diff --git a/src/Container/PdoStatementFactory.php b/src/Container/PdoStatementFactory.php index 1b33117..7b8c343 100644 --- a/src/Container/PdoStatementFactory.php +++ b/src/Container/PdoStatementFactory.php @@ -6,6 +6,7 @@ use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Adapter\Driver\StatementInterface; +use PhpDb\Adapter\ParameterContainer; use Psr\Container\ContainerInterface; final class PdoStatementFactory