diff --git a/composer.json b/composer.json index a14bf270..205ac941 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "doctrine/annotations": "^1.10", "laminas/laminas-code": "^4.7", "psr/container": "^1 || ^2", - "brain-diminished/schema-version-control": "dev-master", + "brain-diminished/schema-version-control": "^1.0.5", "ext-PDO": "*", "ext-json": "*", "ext-hash": "*", @@ -54,9 +54,6 @@ "bamarni/composer-bin-plugin": "^1.4.1", "phpbench/phpbench": "^1.1" }, - "conflict": { - "mouf/database.tdbm": "~5.0.0" - }, "autoload" : { "psr-4" : { "TheCodingMachine\\TDBM\\" : "src/" diff --git a/src/EmptyResultIterator.php b/src/EmptyResultIterator.php new file mode 100644 index 00000000..662a09f6 --- /dev/null +++ b/src/EmptyResultIterator.php @@ -0,0 +1,7 @@ +fetchStarted && $this->tdbmService->getConnection()->getDatabasePlatform() instanceof MySqlPlatform) { // Optimisation: we don't need a separate "count" SQL request in MySQL. - assert($this->statement instanceof Statement); + assert($this->statement instanceof Statement || $this->statement instanceof Result); $this->count = (int)$this->statement->rowCount(); return $this->count; } diff --git a/src/NativeWeakrefObjectStorage.php b/src/NativeWeakrefObjectStorage.php index b2265d0b..2d6c1d48 100644 --- a/src/NativeWeakrefObjectStorage.php +++ b/src/NativeWeakrefObjectStorage.php @@ -94,6 +94,14 @@ public function remove(string $tableName, $id): void unset($this->objects[$tableName][$id]); } + /** + * Removes all objects from the storage. + */ + public function clear(): void + { + $this->objects = array(); + } + private function cleanupDanglingWeakRefs(): void { foreach ($this->objects as $tableName => $table) { diff --git a/src/ObjectStorageInterface.php b/src/ObjectStorageInterface.php index 53064bec..f762e604 100644 --- a/src/ObjectStorageInterface.php +++ b/src/ObjectStorageInterface.php @@ -41,4 +41,9 @@ public function get(string $tableName, $id): ?DbRow; * @param string|int $id */ public function remove(string $tableName, $id): void; + + /** + * Removes all objects from the storage. + */ + public function clear(): void; } diff --git a/src/PageIterator.php b/src/PageIterator.php index cd1da899..c5d27b52 100644 --- a/src/PageIterator.php +++ b/src/PageIterator.php @@ -121,7 +121,7 @@ public static function createEmpyIterator(ResultIterator $parentResult): self public function getIterator() { if ($this->innerResultIterator === null) { - if ($this->parentResult->count() === 0) { + if ($this->parentResult instanceof EmptyResultIterator) { $this->innerResultIterator = new EmptyInnerResultIterator(); } elseif ($this->mode === TDBMService::MODE_CURSOR) { $this->innerResultIterator = InnerResultIterator::createInnerResultIterator($this->magicSql, $this->parameters, $this->limit, $this->offset, $this->columnDescriptors, $this->objectStorage, $this->className, $this->tdbmService, $this->magicQuery, $this->logger); diff --git a/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php b/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php index 6f1de3ed..b04398b0 100644 --- a/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php +++ b/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php @@ -6,6 +6,8 @@ use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Schema\Schema; +use PHPSQLParser\builders\OrderByBuilder; +use PHPSQLParser\builders\SelectStatementBuilder; use TheCodingMachine\TDBM\TDBMException; use TheCodingMachine\TDBM\TDBMService; use PHPSQLParser\PHPSQLCreator; @@ -106,7 +108,7 @@ private function compute(string $sql, ?string $sqlCount): array * @param mixed[] $parsedSql * @param null|string $sqlCount * @return mixed[] An array of 3 elements: [$processedSql, $processedSqlCount, $columnDescriptors] - * @throws \PHPSQLParser\exceptions\UnsupportedFeatureException + * @throws \PHPSQLParser\exceptions\UnsupportedFeatureException|\PHPSQLParser\exceptions\UnableToCreateSQLException */ private function processParsedUnionQuery(array $parsedSql, ?string $sqlCount): array { @@ -120,9 +122,9 @@ private function processParsedUnionQuery(array $parsedSql, ?string $sqlCount): a // Let's reparse the returned SQL (not the most efficient way of doing things) $parser = new PHPSQLParser(); - $parsedSql = $parser->parse($selectProcessedSql); + $parsedSelectSql = $parser->parse($selectProcessedSql); - $parsedSqlList[] = $parsedSql; + $parsedSqlList[] = $parsedSelectSql; } // Let's rebuild the UNION query @@ -133,12 +135,31 @@ private function processParsedUnionQuery(array $parsedSql, ?string $sqlCount): a $generator = new PHPSQLCreator(); - $processedSql = $generator->create($query); + // Replaced the default generator by our own to add parenthesis around each SELECT + $processedSql = $this->buildUnion($query); $processedSqlCount = $generator->create($countQuery); + // Let's add the ORDER BY if any + if (isset($parsedSql['0']['ORDER'])) { + $orderByBuilder = new OrderByBuilder(); + $processedSql .= " " . $orderByBuilder->build($parsedSql['0']['ORDER']); + } + return [$processedSql, $sqlCount ?? $processedSqlCount, $columnDescriptors]; } + /** + * @param mixed[] $parsed + */ + private function buildUnion(array $parsed): string + { + $selectBuilder = new SelectStatementBuilder(); + + return implode(' UNION ', array_map(function ($clause) use ($selectBuilder) { + return '(' . $selectBuilder->build($clause) . ')'; + }, $parsed['UNION'])); + } + /** * @param mixed[] $parsedSql * @param null|string $sqlCount @@ -382,7 +403,7 @@ private function generateGroupedSqlCount(array $parsedSql): array $item['delim'] = ','; $innerColumns[] = $item; } - $innerColumns[count($innerColumns)-1]['delim'] = false; + $innerColumns[count($innerColumns) - 1]['delim'] = false; $parsedSql['SELECT'] = $innerColumns; $parsedSql = [ diff --git a/src/ResultIterator.php b/src/ResultIterator.php index 595c37a3..85fe1af5 100644 --- a/src/ResultIterator.php +++ b/src/ResultIterator.php @@ -184,7 +184,7 @@ public function getIterator() */ public function take($offset, $limit) { - if ($this->totalCount === 0) { + if ($this instanceof EmptyResultIterator) { return PageIterator::createEmpyIterator($this); } return PageIterator::createResultIterator($this, $this->queryFactory->getMagicSql(), $this->parameters, $limit, $offset, $this->queryFactory->getColumnDescriptors(), $this->objectStorage, $this->className, $this->tdbmService, $this->magicQuery, $this->mode, $this->logger); diff --git a/src/TDBMService.php b/src/TDBMService.php index ecfa1be5..a1ff6a14 100644 --- a/src/TDBMService.php +++ b/src/TDBMService.php @@ -1559,4 +1559,14 @@ public function setLogLevel(string $level): void { $this->logger = new LevelFilter($this->rootLogger, $level); } + + /** + * Clear TDBM's bean cache + * + * This can be used in long-running processes to cleanup everything. + */ + public function clearBeanCache(): void + { + $this->objectStorage->clear(); + } } diff --git a/src/Utils/BeanDescriptor.php b/src/Utils/BeanDescriptor.php index cb092208..fb30d023 100644 --- a/src/Utils/BeanDescriptor.php +++ b/src/Utils/BeanDescriptor.php @@ -1333,7 +1333,7 @@ private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespa $parameter = new ParameterGenerator(ltrim($element->getSafeVariableName(), '$')); if (!$first && !($element->isCompulsory() && $index->isUnique())) { $parameterType = '?'; - //$functionParameter = '?'; + //$functionParameter = '?'; } else { $parameterType = ''; //$functionParameter = ''; @@ -1765,7 +1765,7 @@ private function generateGetForeignKeys(array $fks): MethodGenerator * @param string $indent * @return string */ - private function psr2VarExport($var, string $indent=''): string + private function psr2VarExport($var, string $indent = ''): string { if (is_array($var)) { $indexed = array_keys($var) === range(0, count($var) - 1); diff --git a/tests/AbstractTDBMObjectTest.php b/tests/AbstractTDBMObjectTest.php index 3a79559d..a0e79532 100644 --- a/tests/AbstractTDBMObjectTest.php +++ b/tests/AbstractTDBMObjectTest.php @@ -44,7 +44,7 @@ public function testEmptyResultIterator() public function testEmptyPageIterator() { - $a = ResultIterator::createEmpyIterator(); + $a = EmptyResultIterator::createEmpyIterator(); $b = $a->take(0, 10); foreach ($b as $empty) { throw new \LogicException("Not supposed to iterate on an empty page iterator."); diff --git a/tests/NativeWeakrefObjectStorageTest.php b/tests/NativeWeakrefObjectStorageTest.php index e6f6730a..86344e99 100644 --- a/tests/NativeWeakrefObjectStorageTest.php +++ b/tests/NativeWeakrefObjectStorageTest.php @@ -30,7 +30,7 @@ public function testDanglingPointers(): void $objectStorage = new NativeWeakrefObjectStorage(); $dbRow = $this->createMock(DbRow::class); - for ($i=0; $i<10001; $i++) { + for ($i = 0; $i < 10001; $i++) { $objectStorage->set('foo', $i, clone $dbRow); } $this->assertNull($objectStorage->get('foo', 42)); diff --git a/tests/Performance/ManyToOneBench.php b/tests/Performance/ManyToOneBench.php index 07d1ec70..aaf485c6 100644 --- a/tests/Performance/ManyToOneBench.php +++ b/tests/Performance/ManyToOneBench.php @@ -73,18 +73,18 @@ private static function initSchema(Connection $connection): void $connection->exec($sqlStmt); } - for ($i = 1; $i<200; $i++) { + for ($i = 1; $i < 200; $i++) { TDBMAbstractServiceTest::insert($connection, 'countries', [ 'id' => $i, 'label' => 'Country '.$i, ]); } - for ($i = 1; $i<1000; $i++) { + for ($i = 1; $i < 1000; $i++) { TDBMAbstractServiceTest::insert($connection, 'users', [ 'id' => $i, 'name' => 'User '.$i, - 'country_id' => ($i%199) +1, + 'country_id' => ($i % 199) + 1, ]); } } diff --git a/tests/TDBMAbstractServiceTest.php b/tests/TDBMAbstractServiceTest.php index 25ac2675..ff275e82 100644 --- a/tests/TDBMAbstractServiceTest.php +++ b/tests/TDBMAbstractServiceTest.php @@ -211,9 +211,9 @@ private static function initSchema(Connection $connection): void ->column('manager_id')->references('contact')->null(); $db->table('users') - ->addAnnotation('AddTrait', ['name'=>TestUserTrait::class], false) - ->addAnnotation('AddTrait', ['name'=>TestOtherUserTrait::class, 'modifiers'=>['\\'.TestOtherUserTrait::class.'::method1 insteadof \\'.TestUserTrait::class, '\\'.TestUserTrait::class.'::method1 as method1renamed']], false) - ->addAnnotation('AddTraitOnDao', ['name'=>TestUserDaoTrait::class], false) + ->addAnnotation('AddTrait', ['name' => TestUserTrait::class], false) + ->addAnnotation('AddTrait', ['name' => TestOtherUserTrait::class, 'modifiers' => ['\\'.TestOtherUserTrait::class.'::method1 insteadof \\'.TestUserTrait::class, '\\'.TestUserTrait::class.'::method1 as method1renamed']], false) + ->addAnnotation('AddTraitOnDao', ['name' => TestUserDaoTrait::class], false) ->implementsInterface(TestUserInterface::class) ->implementsInterfaceOnDao(TestUserDaoInterface::class) ->extends('contact') @@ -721,7 +721,7 @@ private static function initSchema(Connection $connection): void self::insert($connection, 'tracks', [ 'album_id' => 1, - 'title' =>'Pigs on the Wing 1', + 'title' => 'Pigs on the Wing 1', // Note: Oracle does not have a TIME column type 'duration' => $timeType->convertToDatabaseValue(new DateTimeImmutable('1970-01-01 00:01:25'), $connection->getDatabasePlatform()), ]); diff --git a/tests/Utils/Annotation/AnnotationParserTest.php b/tests/Utils/Annotation/AnnotationParserTest.php index 35428302..35c934e5 100644 --- a/tests/Utils/Annotation/AnnotationParserTest.php +++ b/tests/Utils/Annotation/AnnotationParserTest.php @@ -19,7 +19,7 @@ public function testParse(): void 'UUID' => UUID::class, 'Autoincrement' => Autoincrement::class ]); - $column = new Column('foo', Type::getType(Type::STRING), ['comment'=>'@UUID']); + $column = new Column('foo', Type::getType(Type::STRING), ['comment' => '@UUID']); $table = new Table('bar'); $annotations = $parser->getColumnAnnotations($column, $table); @@ -40,7 +40,7 @@ public function testParseMultiLine(): void 'UUID' => UUID::class, 'Autoincrement' => Autoincrement::class ]); - $column = new Column('foo', Type::getType(Type::STRING), ['comment'=>"\n@UUID"]); + $column = new Column('foo', Type::getType(Type::STRING), ['comment' => "\n@UUID"]); $table = new Table('bar'); $annotations = $parser->getColumnAnnotations($column, $table); @@ -54,7 +54,7 @@ public function testParseMultiAnnotations(): void 'UUID' => UUID::class, 'Autoincrement' => Autoincrement::class ]); - $column = new Column('foo', Type::getType(Type::STRING), ['comment'=>"\n@UUID\n@Autoincrement"]); + $column = new Column('foo', Type::getType(Type::STRING), ['comment' => "\n@UUID\n@Autoincrement"]); $table = new Table('bar'); $annotations = $parser->getColumnAnnotations($column, $table); @@ -68,7 +68,7 @@ public function testException(): void 'UUID' => UUID::class, 'Autoincrement' => Autoincrement::class ]); - $table = new Table('bar', [], [], [], 0, ['comment'=>"@UUID\n@UUID"]); + $table = new Table('bar', [], [], [], 0, ['comment' => "@UUID\n@UUID"]); $annotations = $parser->getTableAnnotations($table); $this->expectException(TDBMException::class); @@ -81,7 +81,7 @@ public function testParseParameters(): void 'UUID' => UUID::class, 'Autoincrement' => Autoincrement::class ]); - $table = new Table('bar', [], [], [], 0, ['comment'=>'@UUID("v4")']); + $table = new Table('bar', [], [], [], 0, ['comment' => '@UUID("v4")']); $annotations = $parser->getTableAnnotations($table); $annotation = $annotations->findAnnotation(UUID::class); @@ -94,7 +94,7 @@ public function testParseOldUUID(): void 'UUID' => UUID::class, ]); // First generation UUID did not use the Doctrine syntax. - $table = new Table('bar', [], [], [], 0, ['comment'=>'@UUID v4']); + $table = new Table('bar', [], [], [], 0, ['comment' => '@UUID v4']); $annotations = $parser->getTableAnnotations($table); $annotation = $annotations->findAnnotation(UUID::class); diff --git a/tests/Utils/DbalUtilsTest.php b/tests/Utils/DbalUtilsTest.php index 14dbb74b..7f39b50c 100644 --- a/tests/Utils/DbalUtilsTest.php +++ b/tests/Utils/DbalUtilsTest.php @@ -17,6 +17,6 @@ public function testGenerateTypes(): void 'key4' => 1, ]; - $this->assertSame(['key2'=>Connection::PARAM_INT_ARRAY, 'key3'=>Connection::PARAM_STR_ARRAY, 'key4'=>ParameterType::INTEGER], DbalUtils::generateTypes($params)); + $this->assertSame(['key2' => Connection::PARAM_INT_ARRAY, 'key3' => Connection::PARAM_STR_ARRAY, 'key4' => ParameterType::INTEGER], DbalUtils::generateTypes($params)); } } diff --git a/tests/Utils/DefaultNamingStrategyTest.php b/tests/Utils/DefaultNamingStrategyTest.php index 20318a92..58e7163e 100644 --- a/tests/Utils/DefaultNamingStrategyTest.php +++ b/tests/Utils/DefaultNamingStrategyTest.php @@ -131,7 +131,7 @@ public function testExceptions(): void public function testBeanAnnotation(): void { - $table = new Table('chevaux', [], [], [], 0, ['comment'=>'@Bean(name="Cheval")']); + $table = new Table('chevaux', [], [], [], 0, ['comment' => '@Bean(name="Cheval")']); $strategy = $this->getDefaultNamingStrategyWithStubTables([$table]); $this->assertSame('ChevalDao', $strategy->getDaoClassName('chevaux')); }