diff --git a/.github/workflows/phpunit-oci.yml b/.github/workflows/phpunit-oci.yml index 041d2175271d7..0fd06f7879d68 100644 --- a/.github/workflows/phpunit-oci.yml +++ b/.github/workflows/phpunit-oci.yml @@ -51,15 +51,21 @@ jobs: runs-on: ubuntu-latest needs: changes - if: needs.changes.outputs.src != 'false' && ${{ github.repository_owner != 'nextcloud-gmbh' }} + if: ${{ needs.changes.outputs.src != 'false' && github.repository_owner != 'nextcloud-gmbh' }} strategy: + fail-fast: false matrix: - oracle-versions: ['11'] - php-versions: ['8.1', '8.2', '8.3'] include: - - php-versions: '8.3' + - oracle-versions: '11' + php-versions: '8.1' + - oracle-versions: '18' + php-versions: '8.1' coverage: ${{ github.event_name != 'pull_request' }} + - oracle-versions: '21' + php-versions: '8.2' + - oracle-versions: '23' + php-versions: '8.3' name: Oracle ${{ matrix.oracle-versions }} (PHP ${{ matrix.php-versions }}) - database tests @@ -71,23 +77,21 @@ jobs: options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 oracle: - image: ghcr.io/gvenzl/oracle-xe:${{ matrix.oracle-versions }} + image: ghcr.io/gvenzl/oracle-${{ matrix.oracle-versions < 23 && 'xe' || 'free' }}:${{ matrix.oracle-versions }} # Provide passwords and other environment variables to container env: - ORACLE_RANDOM_PASSWORD: true - APP_USER: oc_autotest - APP_USER_PASSWORD: nextcloud + ORACLE_PASSWORD: oracle # Forward Oracle port ports: - - 4444:1521/tcp + - 1521:1521 # Provide healthcheck script options for startup options: >- --health-cmd healthcheck.sh - --health-interval 10s - --health-timeout 5s + --health-interval 20s + --health-timeout 10s --health-retries 10 steps: @@ -111,13 +115,11 @@ jobs: run: composer i - name: Set up Nextcloud - env: - DB_PORT: 4444 run: | mkdir data cp tests/redis.config.php config/ cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=oci --database-name=XE --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=oc_autotest --database-pass=nextcloud --admin-user admin --admin-pass admin + ./occ maintenance:install --verbose --database=oci --database-name=${{ matrix.oracle-versions < 23 && 'XE' || 'FREE' }} --database-host=127.0.0.1 --database-port=1521 --database-user=system --database-pass=oracle --admin-user admin --admin-pass admin php -f tests/enable_all.php | grep -i -C9999 error && echo "Error during app setup" && exit 1 || exit 0 - name: PHPUnit diff --git a/apps/contactsinteraction/lib/Db/CardSearchDao.php b/apps/contactsinteraction/lib/Db/CardSearchDao.php index 962f9ff768bcf..0929cb7efa098 100644 --- a/apps/contactsinteraction/lib/Db/CardSearchDao.php +++ b/apps/contactsinteraction/lib/Db/CardSearchDao.php @@ -29,24 +29,24 @@ public function findExisting(IUser $user, $cardQuery = $this->db->getQueryBuilder(); $propQuery = $this->db->getQueryBuilder(); - $propOr = $propQuery->expr()->orX(); + $additionalWheres = []; if ($uid !== null) { - $propOr->add($propQuery->expr()->andX( + $additionalWheres[] = $propQuery->expr()->andX( $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('UID')), $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($uid)) - )); + ); } if ($email !== null) { - $propOr->add($propQuery->expr()->andX( + $additionalWheres[] = $propQuery->expr()->andX( $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('EMAIL')), $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($email)) - )); + ); } if ($cloudId !== null) { - $propOr->add($propQuery->expr()->andX( + $additionalWheres[] = $propQuery->expr()->andX( $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('CLOUD')), $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($cloudId)) - )); + ); } $addressbooksQuery->selectDistinct('id') ->from('addressbooks') @@ -54,8 +54,12 @@ public function findExisting(IUser $user, $propQuery->selectDistinct('cardid') ->from('cards_properties') ->where($propQuery->expr()->in('addressbookid', $propQuery->createFunction($addressbooksQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY)) - ->andWhere($propOr) ->groupBy('cardid'); + + if (!empty($additionalWheres)) { + $propQuery->andWhere($propQuery->expr()->orX(...$additionalWheres)); + } + $cardQuery->select('carddata') ->from('cards') ->where($cardQuery->expr()->in('id', $cardQuery->createFunction($propQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY)) diff --git a/apps/contactsinteraction/lib/Db/RecentContactMapper.php b/apps/contactsinteraction/lib/Db/RecentContactMapper.php index e0b2ac723fbea..c835b5287c87f 100644 --- a/apps/contactsinteraction/lib/Db/RecentContactMapper.php +++ b/apps/contactsinteraction/lib/Db/RecentContactMapper.php @@ -61,23 +61,25 @@ public function findMatch(IUser $user, ?string $cloudId): array { $qb = $this->db->getQueryBuilder(); - $or = $qb->expr()->orX(); + $additionalWheres = []; if ($uid !== null) { - $or->add($qb->expr()->eq('uid', $qb->createNamedParameter($uid))); + $additionalWheres[] = $qb->expr()->eq('uid', $qb->createNamedParameter($uid)); } if ($email !== null) { - $or->add($qb->expr()->eq('email', $qb->createNamedParameter($email))); + $additionalWheres[] = $qb->expr()->eq('email', $qb->createNamedParameter($email)); } if ($cloudId !== null) { - $or->add($qb->expr()->eq('federated_cloud_id', $qb->createNamedParameter($cloudId))); + $additionalWheres[] = $qb->expr()->eq('federated_cloud_id', $qb->createNamedParameter($cloudId)); } $select = $qb ->select('*') ->from($this->getTableName()) - ->where($or) - ->andWhere($qb->expr()->eq('actor_uid', $qb->createNamedParameter($user->getUID()))); + ->where($qb->expr()->eq('actor_uid', $qb->createNamedParameter($user->getUID()))); + if (!empty($additionalWheres)) { + $select->andWhere($select->expr()->orX(...$additionalWheres)); + } return $this->findEntities($select); } diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index d25276fe16d29..49e99201df187 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -1875,12 +1875,12 @@ public function search( } if (!empty($searchProperties)) { - $or = $innerQuery->expr()->orX(); + $or = []; foreach ($searchProperties as $searchProperty) { - $or->add($innerQuery->expr()->eq('op.name', - $outerQuery->createNamedParameter($searchProperty))); + $or[] = $innerQuery->expr()->eq('op.name', + $outerQuery->createNamedParameter($searchProperty)); } - $innerQuery->andWhere($or); + $innerQuery->andWhere($innerQuery->expr()->orX(...$or)); } if ($pattern !== '') { @@ -1924,12 +1924,12 @@ public function search( } if (!empty($options['types'])) { - $or = $outerQuery->expr()->orX(); + $or = []; foreach ($options['types'] as $type) { - $or->add($outerQuery->expr()->eq('componenttype', - $outerQuery->createNamedParameter($type))); + $or[] = $outerQuery->expr()->eq('componenttype', + $outerQuery->createNamedParameter($type)); } - $outerQuery->andWhere($or); + $outerQuery->andWhere($outerQuery->expr()->orX(...$or)); } $outerQuery->andWhere($outerQuery->expr()->in('c.id', $outerQuery->createFunction($innerQuery->getSQL()))); @@ -2150,16 +2150,17 @@ public function searchPrincipalUri(string $principalUri, $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false; $calendarObjectIdQuery = $this->db->getQueryBuilder(); - $calendarOr = $calendarObjectIdQuery->expr()->orX(); - $searchOr = $calendarObjectIdQuery->expr()->orX(); + $calendarOr = []; + $searchOr = []; // Fetch calendars and subscription $calendars = $this->getCalendarsForUser($principalUri); $subscriptions = $this->getSubscriptionsForUser($principalUri); foreach ($calendars as $calendar) { - $calendarAnd = $calendarObjectIdQuery->expr()->andX(); - $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id']))); - $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR))); + $calendarAnd = $calendarObjectIdQuery->expr()->andX( + $calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])), + $calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)), + ); // If it's shared, limit search to public events if (isset($calendar['{http://owncloud.org/ns}owner-principal']) @@ -2167,12 +2168,13 @@ public function searchPrincipalUri(string $principalUri, $calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC))); } - $calendarOr->add($calendarAnd); + $calendarOr[] = $calendarAnd; } foreach ($subscriptions as $subscription) { - $subscriptionAnd = $calendarObjectIdQuery->expr()->andX(); - $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id']))); - $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION))); + $subscriptionAnd = $calendarObjectIdQuery->expr()->andX( + $calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])), + $calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)), + ); // If it's shared, limit search to public events if (isset($subscription['{http://owncloud.org/ns}owner-principal']) @@ -2180,28 +2182,30 @@ public function searchPrincipalUri(string $principalUri, $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC))); } - $calendarOr->add($subscriptionAnd); + $calendarOr[] = $subscriptionAnd; } foreach ($searchProperties as $property) { - $propertyAnd = $calendarObjectIdQuery->expr()->andX(); - $propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR))); - $propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter')); + $propertyAnd = $calendarObjectIdQuery->expr()->andX( + $calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)), + $calendarObjectIdQuery->expr()->isNull('cob.parameter'), + ); - $searchOr->add($propertyAnd); + $searchOr[] = $propertyAnd; } foreach ($searchParameters as $property => $parameter) { - $parameterAnd = $calendarObjectIdQuery->expr()->andX(); - $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR))); - $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY))); + $parameterAnd = $calendarObjectIdQuery->expr()->andX( + $calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)), + $calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)), + ); - $searchOr->add($parameterAnd); + $searchOr[] = $parameterAnd; } - if ($calendarOr->count() === 0) { + if (empty($calendarOr)) { return []; } - if ($searchOr->count() === 0) { + if (empty($searchOr)) { return []; } @@ -2209,8 +2213,8 @@ public function searchPrincipalUri(string $principalUri, ->from($this->dbObjectPropertiesTable, 'cob') ->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid')) ->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY))) - ->andWhere($calendarOr) - ->andWhere($searchOr) + ->andWhere($calendarObjectIdQuery->expr()->orX(...$calendarOr)) + ->andWhere($calendarObjectIdQuery->expr()->orX(...$searchOr)) ->andWhere($calendarObjectIdQuery->expr()->isNull('deleted_at')); if ($pattern !== '') { diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index cdbbc228047f8..9d787c917d34f 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -1148,20 +1148,20 @@ private function searchByAddressBookIds(array $addressBookIds, /** * FIXME Find a way to match only 4 last digits * BDAY can be --1018 without year or 20001019 with it - * $bDayOr = $query2->expr()->orX(); + * $bDayOr = []; * if ($options['since'] instanceof DateTimeFilter) { - * $bDayOr->add( + * $bDayOr[] = * $query2->expr()->gte('SUBSTR(cp_bday.value, -4)', - * $query2->createNamedParameter($options['since']->get()->format('md'))) + * $query2->createNamedParameter($options['since']->get()->format('md')) * ); * } * if ($options['until'] instanceof DateTimeFilter) { - * $bDayOr->add( + * $bDayOr[] = * $query2->expr()->lte('SUBSTR(cp_bday.value, -4)', - * $query2->createNamedParameter($options['until']->get()->format('md'))) + * $query2->createNamedParameter($options['until']->get()->format('md')) * ); * } - * $query2->andWhere($bDayOr); + * $query2->andWhere($query2->expr()->orX(...$bDayOr)); */ } diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index ab62ae36c2cc4..f4dd9b2d03857 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -411,7 +411,7 @@ private function getUserProperties(string $path, array $requestedProperties) { // request only a subset $sql .= ' AND `propertyname` in (?)'; $whereValues[] = $requestedProperties; - $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY; + $whereTypes[] = IQueryBuilder::PARAM_STR_ARRAY; } $result = $this->connection->executeQuery( diff --git a/apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php b/apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php index cbce847a298c2..c7f57dcb11718 100644 --- a/apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php +++ b/apps/dav/lib/Migration/CalDAVRemoveEmptyValue.php @@ -5,7 +5,6 @@ */ namespace OCA\DAV\Migration; -use Doctrine\DBAL\Platforms\OraclePlatform; use OCA\DAV\CalDAV\CalDavBackend; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -75,7 +74,7 @@ public function run(IOutput $output) { } protected function getInvalidObjects($pattern) { - if ($this->db->getDatabasePlatform() instanceof OraclePlatform) { + if ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) { $rows = []; $chunkSize = 500; $query = $this->db->getQueryBuilder(); diff --git a/apps/settings/tests/SetupChecks/SupportedDatabaseTest.php b/apps/settings/tests/SetupChecks/SupportedDatabaseTest.php index 2492379b557ca..0ba1621c5febb 100644 --- a/apps/settings/tests/SetupChecks/SupportedDatabaseTest.php +++ b/apps/settings/tests/SetupChecks/SupportedDatabaseTest.php @@ -8,7 +8,6 @@ */ namespace OCA\Settings\Tests; -use Doctrine\DBAL\Platforms\SqlitePlatform; use OCA\Settings\SetupChecks\SupportedDatabase; use OCP\IDBConnection; use OCP\IL10N; @@ -41,8 +40,7 @@ protected function setUp(): void { } public function testPass(): void { - $platform = $this->connection->getDatabasePlatform(); - if ($platform instanceof SqlitePlatform) { + if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE) { /** SQlite always gets a warning */ $this->assertEquals(SetupResult::WARNING, $this->check->run()->getSeverity()); } else { diff --git a/apps/user_ldap/lib/Mapping/AbstractMapping.php b/apps/user_ldap/lib/Mapping/AbstractMapping.php index c243731eaf7a3..79d23b8d6187d 100644 --- a/apps/user_ldap/lib/Mapping/AbstractMapping.php +++ b/apps/user_ldap/lib/Mapping/AbstractMapping.php @@ -8,9 +8,9 @@ namespace OCA\User_LDAP\Mapping; use Doctrine\DBAL\Exception; -use Doctrine\DBAL\Platforms\SqlitePlatform; use OCP\DB\IPreparedStatement; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; use Psr\Log\LoggerInterface; /** @@ -216,7 +216,7 @@ protected function collectResultsFromListOfIdsQuery(IQueryBuilder $qb, array &$r public function getListOfIdsByDn(array $fdns): array { $totalDBParamLimit = 65000; $sliceSize = 1000; - $maxSlices = $this->dbc->getDatabasePlatform() instanceof SqlitePlatform ? 9 : $totalDBParamLimit / $sliceSize; + $maxSlices = $this->dbc->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE ? 9 : $totalDBParamLimit / $sliceSize; $results = []; $slice = 1; diff --git a/core/Command/Db/ConvertFilecacheBigInt.php b/core/Command/Db/ConvertFilecacheBigInt.php index 5f3e790e9ceb8..d16e6d302312f 100644 --- a/core/Command/Db/ConvertFilecacheBigInt.php +++ b/core/Command/Db/ConvertFilecacheBigInt.php @@ -5,11 +5,11 @@ */ namespace OC\Core\Command\Db; -use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Types\Type; use OC\DB\Connection; use OC\DB\SchemaWrapper; use OCP\DB\Types; +use OCP\IDBConnection; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -53,7 +53,7 @@ public static function getColumnsByTable(): array { protected function execute(InputInterface $input, OutputInterface $output): int { $schema = new SchemaWrapper($this->connection); - $isSqlite = $this->connection->getDatabasePlatform() instanceof SqlitePlatform; + $isSqlite = $this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE; $updates = []; $tables = static::getColumnsByTable(); diff --git a/core/Command/Db/ConvertMysqlToMB4.php b/core/Command/Db/ConvertMysqlToMB4.php index 679cdd5f61611..618e15de3f45a 100644 --- a/core/Command/Db/ConvertMysqlToMB4.php +++ b/core/Command/Db/ConvertMysqlToMB4.php @@ -5,7 +5,6 @@ */ namespace OC\Core\Command\Db; -use Doctrine\DBAL\Platforms\MySQLPlatform; use OC\DB\MySqlTools; use OC\Migration\ConsoleOutput; use OC\Repair\Collation; @@ -34,7 +33,7 @@ protected function configure() { } protected function execute(InputInterface $input, OutputInterface $output): int { - if (!$this->connection->getDatabasePlatform() instanceof MySQLPlatform) { + if ($this->connection->getDatabaseProvider() !== IDBConnection::PLATFORM_MYSQL) { $output->writeln("This command is only valid for MySQL/MariaDB databases."); return 1; } diff --git a/core/Command/Db/ConvertType.php b/core/Command/Db/ConvertType.php index 333f29625f62f..592285a8309d4 100644 --- a/core/Command/Db/ConvertType.php +++ b/core/Command/Db/ConvertType.php @@ -245,7 +245,7 @@ protected function clearSchema(Connection $db, InputInterface $input, OutputInte $output->writeln('Clearing schema in new database'); } foreach ($toTables as $table) { - $db->getSchemaManager()->dropTable($table); + $db->createSchemaManager()->dropTable($table); } } @@ -258,7 +258,7 @@ protected function getTables(Connection $db) { } return preg_match($filterExpression, $asset) !== false; }); - return $db->getSchemaManager()->listTableNames(); + return $db->createSchemaManager()->listTableNames(); } /** diff --git a/core/Migrations/Version13000Date20170718121200.php b/core/Migrations/Version13000Date20170718121200.php index c485e025c3709..3985bdaeb57e6 100644 --- a/core/Migrations/Version13000Date20170718121200.php +++ b/core/Migrations/Version13000Date20170718121200.php @@ -5,7 +5,6 @@ */ namespace OC\Core\Migrations; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use OCP\DB\ISchemaWrapper; use OCP\DB\Types; use OCP\IDBConnection; @@ -238,7 +237,7 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op $table->addIndex(['name'], 'fs_name_hash'); $table->addIndex(['mtime'], 'fs_mtime'); $table->addIndex(['size'], 'fs_size'); - if (!$schema->getDatabasePlatform() instanceof PostgreSQL94Platform) { + if ($this->connection->getDatabaseProvider() !== IDBConnection::PLATFORM_POSTGRES) { $table->addIndex(['storage', 'path'], 'fs_storage_path_prefix', [], ['lengths' => [null, 64]]); } } diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php index d05fe440202dd..58eee772fbf91 100644 --- a/lib/private/AllConfig.php +++ b/lib/private/AllConfig.php @@ -6,7 +6,6 @@ */ namespace OC; -use Doctrine\DBAL\Platforms\OraclePlatform; use OCP\Cache\CappedMemoryCache; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; @@ -470,7 +469,7 @@ public function getUsersForUserValue($appName, $key, $value) { $this->fixDIInit(); $qb = $this->connection->getQueryBuilder(); - $configValueColumn = ($this->connection->getDatabasePlatform() instanceof OraclePlatform) + $configValueColumn = ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR) : 'configvalue'; $result = $qb->select('userid') @@ -509,7 +508,7 @@ public function getUsersForUserValueCaseInsensitive($appName, $key, $value) { } $qb = $this->connection->getQueryBuilder(); - $configValueColumn = ($this->connection->getDatabasePlatform() instanceof OraclePlatform) + $configValueColumn = ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR) : 'configvalue'; diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php index 201263320e378..b09124281ea69 100644 --- a/lib/private/BackgroundJob/JobList.php +++ b/lib/private/BackgroundJob/JobList.php @@ -7,7 +7,6 @@ */ namespace OC\BackgroundJob; -use Doctrine\DBAL\Platforms\MySQLPlatform; use OCP\AppFramework\QueryException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\AutoloadNotAllowedException; @@ -98,7 +97,7 @@ public function remove($job, $argument = null): void { // Add galera safe delete chunking if using mysql // Stops us hitting wsrep_max_ws_rows when large row counts are deleted - if ($this->connection->getDatabasePlatform() instanceof MySQLPlatform) { + if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_MYSQL) { // Then use chunked delete $max = IQueryBuilder::MAX_ROW_DELETION; @@ -204,12 +203,12 @@ public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = nu $query->andWhere($query->expr()->eq('time_sensitive', $query->createNamedParameter(IJob::TIME_SENSITIVE, IQueryBuilder::PARAM_INT))); } - if ($jobClasses !== null && count($jobClasses) > 0) { - $orClasses = $query->expr()->orx(); + if (!empty($jobClasses)) { + $orClasses = []; foreach ($jobClasses as $jobClass) { - $orClasses->add($query->expr()->eq('class', $query->createNamedParameter($jobClass, IQueryBuilder::PARAM_STR))); + $orClasses[] = $query->expr()->eq('class', $query->createNamedParameter($jobClass, IQueryBuilder::PARAM_STR)); } - $query->andWhere($orClasses); + $query->andWhere($query->expr()->orX(...$orClasses)); } $result = $query->executeQuery(); diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index c886cb202353f..e584690f95d3a 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -13,10 +13,12 @@ use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\ConnectionLost; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\Schema; @@ -25,6 +27,7 @@ use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Diagnostics\IEventLogger; +use OCP\IDBConnection; use OCP\IRequestId; use OCP\PreConditionNotMetException; use OCP\Profiler\IProfiler; @@ -318,10 +321,7 @@ private function getQueriedTables(string $sql): array { * @throws Exception */ public function executeUpdate(string $sql, array $params = [], array $types = []): int { - $sql = $this->finishQuery($sql); - $this->queriesExecuted++; - $this->logQueryToFile($sql); - return parent::executeUpdate($sql, $params, $types); + return $this->executeStatement($sql, $params, $types); } /** @@ -472,22 +472,22 @@ public function setValues(string $table, array $keys, array $values, array $upda foreach ($values as $name => $value) { $updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value))); } - $where = $updateQb->expr()->andX(); + $where = []; $whereValues = array_merge($keys, $updatePreconditionValues); foreach ($whereValues as $name => $value) { if ($value === '') { - $where->add($updateQb->expr()->emptyString( + $where[] = $updateQb->expr()->emptyString( $name - )); + ); } else { - $where->add($updateQb->expr()->eq( + $where[] = $updateQb->expr()->eq( $name, $updateQb->createNamedParameter($value, $this->getType($value)), $this->getType($value) - )); + ); } } - $updateQb->where($where); + $updateQb->where($updateQb->expr()->andX(...$where)); $affected = $updateQb->executeStatement(); if ($affected === 0 && !empty($updatePreconditionValues)) { @@ -561,7 +561,7 @@ public function errorInfo() { */ public function dropTable($table) { $table = $this->tablePrefix . trim($table); - $schema = $this->getSchemaManager(); + $schema = $this->createSchemaManager(); if ($schema->tablesExist([$table])) { $schema->dropTable($table); } @@ -577,7 +577,7 @@ public function dropTable($table) { */ public function tableExists($table) { $table = $this->tablePrefix . trim($table); - $schema = $this->getSchemaManager(); + $schema = $this->createSchemaManager(); return $schema->tablesExist([$table]); } @@ -732,4 +732,31 @@ private function reconnectIfNeeded(): void { private function getConnectionName(): string { return $this->isConnectedToPrimary() ? 'primary' : 'replica'; } + + /** + * @return IDBConnection::PLATFORM_MYSQL|IDBConnection::PLATFORM_ORACLE|IDBConnection::PLATFORM_POSTGRES|IDBConnection::PLATFORM_SQLITE + */ + public function getDatabaseProvider(): string { + $platform = $this->getDatabasePlatform(); + if ($platform instanceof MySQLPlatform) { + return IDBConnection::PLATFORM_MYSQL; + } elseif ($platform instanceof OraclePlatform) { + return IDBConnection::PLATFORM_ORACLE; + } elseif ($platform instanceof PostgreSQLPlatform) { + return IDBConnection::PLATFORM_POSTGRES; + } elseif ($platform instanceof SqlitePlatform) { + return IDBConnection::PLATFORM_SQLITE; + } else { + throw new \Exception('Database ' . $platform::class . ' not supported'); + } + } + + /** + * @internal Should only be used inside the QueryBuilder, ExpressionBuilder and FunctionBuilder + * All apps and API code should not need this and instead use provided functionality from the above. + */ + public function getServerVersion(): string { + /** @var ServerInfoAwareConnection $this->_conn */ + return $this->_conn->getServerVersion(); + } } diff --git a/lib/private/DB/ConnectionAdapter.php b/lib/private/DB/ConnectionAdapter.php index 86a901a7de33d..b7225169e4c28 100644 --- a/lib/private/DB/ConnectionAdapter.php +++ b/lib/private/DB/ConnectionAdapter.php @@ -10,10 +10,6 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\MySQLPlatform; -use Doctrine\DBAL\Platforms\OraclePlatform; -use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Schema; use OC\DB\Exceptions\DbalException; use OCP\DB\IPreparedStatement; @@ -230,18 +226,18 @@ public function getInner(): Connection { return $this->inner; } + /** + * @return self::PLATFORM_MYSQL|self::PLATFORM_ORACLE|self::PLATFORM_POSTGRES|self::PLATFORM_SQLITE + */ public function getDatabaseProvider(): string { - $platform = $this->inner->getDatabasePlatform(); - if ($platform instanceof MySQLPlatform) { - return IDBConnection::PLATFORM_MYSQL; - } elseif ($platform instanceof OraclePlatform) { - return IDBConnection::PLATFORM_ORACLE; - } elseif ($platform instanceof PostgreSQLPlatform) { - return IDBConnection::PLATFORM_POSTGRES; - } elseif ($platform instanceof SqlitePlatform) { - return IDBConnection::PLATFORM_SQLITE; - } else { - throw new \Exception('Database ' . $platform::class . ' not supported'); - } + return $this->inner->getDatabaseProvider(); + } + + /** + * @internal Should only be used inside the QueryBuilder, ExpressionBuilder and FunctionBuilder + * All apps and API code should not need this and instead use provided functionality from the above. + */ + public function getServerVersion(): string { + return $this->inner->getServerVersion(); } } diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 19d1b2407369b..0a3b0d1dcc773 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -6,20 +6,19 @@ */ namespace OC\DB; -use Doctrine\DBAL\Platforms\OraclePlatform; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; -use Doctrine\DBAL\Types\Types; use OC\App\InfoParser; use OC\IntegrityCheck\Helpers\AppLocator; use OC\Migration\SimpleOutput; use OCP\AppFramework\App; use OCP\AppFramework\QueryException; use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\IDBConnection; use OCP\Migration\IMigrationStep; use OCP\Migration\IOutput; use OCP\Server; @@ -599,7 +598,7 @@ public function ensureOracleConstraints(Schema $sourceSchema, Schema $targetSche $indexName = strtolower($primaryKey->getName()); $isUsingDefaultName = $indexName === 'primary'; - if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform) { + if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_POSTGRES) { $defaultName = $table->getName() . '_pkey'; $isUsingDefaultName = strtolower($defaultName) === $indexName; @@ -609,7 +608,7 @@ public function ensureOracleConstraints(Schema $sourceSchema, Schema $targetSche return $sequence->getName() !== $sequenceName; }); } - } elseif ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { + } elseif ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) { $defaultName = $table->getName() . '_seq'; $isUsingDefaultName = strtolower($defaultName) === $indexName; } diff --git a/lib/private/DB/OracleConnection.php b/lib/private/DB/OracleConnection.php index abfb69f129b9f..5ffb65d801dbe 100644 --- a/lib/private/DB/OracleConnection.php +++ b/lib/private/DB/OracleConnection.php @@ -68,7 +68,7 @@ public function delete($table, array $criteria, array $types = []) { public function dropTable($table) { $table = $this->tablePrefix . trim($table); $table = $this->quoteIdentifier($table); - $schema = $this->getSchemaManager(); + $schema = $this->createSchemaManager(); if ($schema->tablesExist([$table])) { $schema->dropTable($table); } @@ -83,7 +83,7 @@ public function dropTable($table) { public function tableExists($table) { $table = $this->tablePrefix . trim($table); $table = $this->quoteIdentifier($table); - $schema = $this->getSchemaManager(); + $schema = $this->createSchemaManager(); return $schema->tablesExist([$table]); } } diff --git a/lib/private/DB/PgSqlTools.php b/lib/private/DB/PgSqlTools.php index 35e8016191c74..d529cb26b09d5 100644 --- a/lib/private/DB/PgSqlTools.php +++ b/lib/private/DB/PgSqlTools.php @@ -43,7 +43,7 @@ public function resynchronizeDatabaseSequences(Connection $conn) { return preg_match($filterExpression, $asset) !== false; }); - foreach ($conn->getSchemaManager()->listSequences() as $sequence) { + foreach ($conn->createSchemaManager()->listSequences() as $sequence) { $sequenceName = $sequence->getName(); $sqlInfo = 'SELECT table_schema, table_name, column_name FROM information_schema.columns diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php index c4ac350dee574..b70e20e4d0d86 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php @@ -21,6 +21,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryFunction; use OCP\IDBConnection; +use Psr\Log\LoggerInterface; class ExpressionBuilder implements IExpressionBuilder { /** @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder */ @@ -32,17 +33,15 @@ class ExpressionBuilder implements IExpressionBuilder { /** @var IDBConnection */ protected $connection; + /** @var LoggerInterface */ + protected $logger; + /** @var FunctionBuilder */ protected $functionBuilder; - /** - * Initializes a new ExpressionBuilder. - * - * @param ConnectionAdapter $connection - * @param IQueryBuilder $queryBuilder - */ - public function __construct(ConnectionAdapter $connection, IQueryBuilder $queryBuilder) { + public function __construct(ConnectionAdapter $connection, IQueryBuilder $queryBuilder, LoggerInterface $logger) { $this->connection = $connection; + $this->logger = $logger; $this->helper = new QuoteHelper(); $this->expressionBuilder = new DoctrineExpressionBuilder($connection->getInner()); $this->functionBuilder = $queryBuilder->func(); @@ -63,6 +62,9 @@ public function __construct(ConnectionAdapter $connection, IQueryBuilder $queryB * @return \OCP\DB\QueryBuilder\ICompositeExpression */ public function andX(...$x): ICompositeExpression { + if (empty($x)) { + $this->logger->debug('Calling ' . IQueryBuilder::class . '::' . __FUNCTION__ . ' without parameters is deprecated and will throw soon.', ['exception' => new \Exception('No parameters in call to ' . __METHOD__)]); + } return new CompositeExpression(CompositeExpression::TYPE_AND, $x); } @@ -81,6 +83,9 @@ public function andX(...$x): ICompositeExpression { * @return \OCP\DB\QueryBuilder\ICompositeExpression */ public function orX(...$x): ICompositeExpression { + if (empty($x)) { + $this->logger->debug('Calling ' . IQueryBuilder::class . '::' . __FUNCTION__ . ' without parameters is deprecated and will throw soon.', ['exception' => new \Exception('No parameters in call to ' . __METHOD__)]); + } return new CompositeExpression(CompositeExpression::TYPE_OR, $x); } diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php index cd2fb5a5cc30e..7216fd8807ba3 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php @@ -11,17 +11,13 @@ use OC\DB\QueryBuilder\QueryFunction; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryFunction; +use Psr\Log\LoggerInterface; class MySqlExpressionBuilder extends ExpressionBuilder { - /** @var string */ - protected $collation; + protected string $collation; - /** - * @param ConnectionAdapter $connection - * @param IQueryBuilder $queryBuilder - */ - public function __construct(ConnectionAdapter $connection, IQueryBuilder $queryBuilder) { - parent::__construct($connection, $queryBuilder); + public function __construct(ConnectionAdapter $connection, IQueryBuilder $queryBuilder, LoggerInterface $logger) { + parent::__construct($connection, $queryBuilder, $logger); $params = $connection->getInner()->getParams(); $this->collation = $params['collation'] ?? (($params['charset'] ?? 'utf8') . '_general_ci'); diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php index b168d2c1a8449..2466493c1fa54 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php @@ -5,6 +5,7 @@ */ namespace OC\DB\QueryBuilder\FunctionBuilder; +use OC\DB\Connection; use OC\DB\QueryBuilder\QueryFunction; use OC\DB\QueryBuilder\QuoteHelper; use OCP\DB\QueryBuilder\IFunctionBuilder; @@ -13,7 +14,7 @@ use OCP\IDBConnection; class FunctionBuilder implements IFunctionBuilder { - /** @var IDBConnection */ + /** @var IDBConnection|Connection */ protected $connection; /** @var IQueryBuilder */ diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php index d0258eafea879..a8dc4d8cf14f4 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php @@ -12,6 +12,9 @@ class OCIFunctionBuilder extends FunctionBuilder { public function md5($input): IQueryFunction { + if (version_compare($this->connection->getServerVersion(), '20', '>=')) { + return new QueryFunction('LOWER(STANDARD_HASH(' . $this->helper->quoteColumnName($input) . ", 'MD5'))"); + } return new QueryFunction('LOWER(DBMS_OBFUSCATION_TOOLKIT.md5 (input => UTL_RAW.cast_to_raw(' . $this->helper->quoteColumnName($input) .')))'); } diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index 82127078d0618..0ea223cd89cd4 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -7,14 +7,9 @@ */ namespace OC\DB\QueryBuilder; -use Doctrine\DBAL\Platforms\MySQLPlatform; -use Doctrine\DBAL\Platforms\OraclePlatform; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform; -use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Query\QueryException; use OC\DB\ConnectionAdapter; use OC\DB\Exceptions\DbalException; -use OC\DB\QueryBuilder\ExpressionBuilder\ExpressionBuilder; use OC\DB\QueryBuilder\ExpressionBuilder\MySqlExpressionBuilder; use OC\DB\QueryBuilder\ExpressionBuilder\OCIExpressionBuilder; use OC\DB\QueryBuilder\ExpressionBuilder\PgSqlExpressionBuilder; @@ -50,6 +45,7 @@ class QueryBuilder implements IQueryBuilder { /** @var bool */ private $automaticTablePrefix = true; + private bool $nonEmptyWhere = false; /** @var string */ protected $lastInsertedTable; @@ -96,20 +92,12 @@ public function automaticTablePrefix($enabled) { * @return \OCP\DB\QueryBuilder\IExpressionBuilder */ public function expr() { - if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { - return new OCIExpressionBuilder($this->connection, $this); - } - if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform) { - return new PgSqlExpressionBuilder($this->connection, $this); - } - if ($this->connection->getDatabasePlatform() instanceof MySQLPlatform) { - return new MySqlExpressionBuilder($this->connection, $this); - } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { - return new SqliteExpressionBuilder($this->connection, $this); - } - - return new ExpressionBuilder($this->connection, $this); + return match($this->connection->getDatabaseProvider()) { + IDBConnection::PLATFORM_ORACLE => new OCIExpressionBuilder($this->connection, $this, $this->logger), + IDBConnection::PLATFORM_POSTGRES => new PgSqlExpressionBuilder($this->connection, $this, $this->logger), + IDBConnection::PLATFORM_MYSQL => new MySqlExpressionBuilder($this->connection, $this, $this->logger), + IDBConnection::PLATFORM_SQLITE => new SqliteExpressionBuilder($this->connection, $this, $this->logger), + }; } /** @@ -129,17 +117,12 @@ public function expr() { * @return \OCP\DB\QueryBuilder\IFunctionBuilder */ public function func() { - if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { - return new OCIFunctionBuilder($this->connection, $this, $this->helper); - } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { - return new SqliteFunctionBuilder($this->connection, $this, $this->helper); - } - if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform) { - return new PgSqlFunctionBuilder($this->connection, $this, $this->helper); - } - - return new FunctionBuilder($this->connection, $this, $this->helper); + return match($this->connection->getDatabaseProvider()) { + IDBConnection::PLATFORM_ORACLE => new OCIFunctionBuilder($this->connection, $this, $this->helper), + IDBConnection::PLATFORM_POSTGRES => new PgSqlFunctionBuilder($this->connection, $this, $this->helper), + IDBConnection::PLATFORM_MYSQL => new FunctionBuilder($this->connection, $this, $this->helper), + IDBConnection::PLATFORM_SQLITE => new SqliteFunctionBuilder($this->connection, $this, $this->helper), + }; } /** @@ -163,9 +146,12 @@ public function getConnection() { /** * Gets the state of this query builder instance. * - * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. + * @return int Always returns 0 which is former `QueryBuilder::STATE_DIRTY` + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. */ public function getState() { + $this->logger->debug(IQueryBuilder::class . '::' . __FUNCTION__ . ' is deprecated and will be removed soon.', ['exception' => new \Exception('Deprecated call to ' . __METHOD__)]); return $this->queryBuilder->getState(); } @@ -200,24 +186,24 @@ private function prepareForExecute() { } } - if (!empty($this->getQueryPart('select'))) { - $select = $this->getQueryPart('select'); - $hasSelectAll = array_filter($select, static function ($s) { - return $s === '*'; - }); - $hasSelectSpecific = array_filter($select, static function ($s) { - return $s !== '*'; - }); - - if (empty($hasSelectAll) === empty($hasSelectSpecific)) { - $exception = new QueryException('Query is selecting * and specific values in the same query. This is not supported in Oracle.'); - $this->logger->error($exception->getMessage(), [ - 'query' => $this->getSQL(), - 'app' => 'core', - 'exception' => $exception, - ]); - } - } + // if (!empty($this->getQueryPart('select'))) { + // $select = $this->getQueryPart('select'); + // $hasSelectAll = array_filter($select, static function ($s) { + // return $s === '*'; + // }); + // $hasSelectSpecific = array_filter($select, static function ($s) { + // return $s !== '*'; + // }); + + // if (empty($hasSelectAll) === empty($hasSelectSpecific)) { + // $exception = new QueryException('Query is selecting * and specific values in the same query. This is not supported in Oracle.'); + // $this->logger->error($exception->getMessage(), [ + // 'query' => $this->getSQL(), + // 'app' => 'core', + // 'exception' => $exception, + // ]); + // } + // } $numberOfParameters = 0; $hasTooLargeArrayParameter = false; @@ -583,8 +569,13 @@ public function addSelect(...$selects) { * @param string $alias The table alias used in the constructed query. * * @return $this This QueryBuilder instance. + * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update */ public function delete($delete = null, $alias = null) { + if ($alias !== null) { + $this->logger->debug('DELETE queries with alias are no longer supported and the provided alias is ignored', ['exception' => new \InvalidArgumentException('Table alias provided for DELETE query')]); + } + $this->queryBuilder->delete( $this->getTableName($delete), $alias @@ -608,8 +599,13 @@ public function delete($delete = null, $alias = null) { * @param string $alias The table alias used in the constructed query. * * @return $this This QueryBuilder instance. + * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update */ public function update($update = null, $alias = null) { + if ($alias !== null) { + $this->logger->debug('UPDATE queries with alias are no longer supported and the provided alias is ignored', ['exception' => new \InvalidArgumentException('Table alias provided for UPDATE query')]); + } + $this->queryBuilder->update( $this->getTableName($update), $alias @@ -820,9 +816,10 @@ public function set($key, $value) { * // You can optionally programmatically build and/or expressions * $qb = $conn->getQueryBuilder(); * - * $or = $qb->expr()->orx(); - * $or->add($qb->expr()->eq('u.id', 1)); - * $or->add($qb->expr()->eq('u.id', 2)); + * $or = $qb->expr()->orx( + * $qb->expr()->eq('u.id', 1), + * $qb->expr()->eq('u.id', 2), + * ); * * $qb->update('users', 'u') * ->set('u.password', md5('password')) @@ -834,12 +831,14 @@ public function set($key, $value) { * @return $this This QueryBuilder instance. */ public function where(...$predicates) { - if ($this->getQueryPart('where') !== null && $this->systemConfig->getValue('debug', false)) { + if ($this->nonEmptyWhere && $this->systemConfig->getValue('debug', false)) { // Only logging a warning, not throwing for now. $e = new QueryException('Using where() on non-empty WHERE part, please verify it is intentional to not call andWhere() or orWhere() instead. Otherwise consider creating a new query builder object or call resetQueryPart(\'where\') first.'); $this->logger->warning($e->getMessage(), ['exception' => $e]); } + $this->nonEmptyWhere = true; + call_user_func_array( [$this->queryBuilder, 'where'], $predicates @@ -867,6 +866,7 @@ public function where(...$predicates) { * @see where() */ public function andWhere(...$where) { + $this->nonEmptyWhere = true; call_user_func_array( [$this->queryBuilder, 'andWhere'], $where @@ -894,6 +894,7 @@ public function andWhere(...$where) { * @see where() */ public function orWhere(...$where) { + $this->nonEmptyWhere = true; call_user_func_array( [$this->queryBuilder, 'orWhere'], $where @@ -1104,8 +1105,11 @@ public function addOrderBy($sort, $order = null) { * @param string $queryPartName * * @return mixed + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please track the details you need, outside the object. */ public function getQueryPart($queryPartName) { + $this->logger->debug(IQueryBuilder::class . '::' . __FUNCTION__ . ' is deprecated and will be removed soon.', ['exception' => new \Exception('Deprecated call to ' . __METHOD__)]); return $this->queryBuilder->getQueryPart($queryPartName); } @@ -1113,8 +1117,11 @@ public function getQueryPart($queryPartName) { * Gets all query parts. * * @return array + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please track the details you need, outside the object. */ public function getQueryParts() { + $this->logger->debug(IQueryBuilder::class . '::' . __FUNCTION__ . ' is deprecated and will be removed soon.', ['exception' => new \Exception('Deprecated call to ' . __METHOD__)]); return $this->queryBuilder->getQueryParts(); } @@ -1124,8 +1131,11 @@ public function getQueryParts() { * @param array|null $queryPartNames * * @return $this This QueryBuilder instance. + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please create a new IQueryBuilder instead. */ public function resetQueryParts($queryPartNames = null) { + $this->logger->debug(IQueryBuilder::class . '::' . __FUNCTION__ . ' is deprecated and will be removed soon.', ['exception' => new \Exception('Deprecated call to ' . __METHOD__)]); $this->queryBuilder->resetQueryParts($queryPartNames); return $this; @@ -1137,8 +1147,11 @@ public function resetQueryParts($queryPartNames = null) { * @param string $queryPartName * * @return $this This QueryBuilder instance. + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please create a new IQueryBuilder instead. */ public function resetQueryPart($queryPartName) { + $this->logger->debug(IQueryBuilder::class . '::' . __FUNCTION__ . ' is deprecated and will be removed soon.', ['exception' => new \Exception('Deprecated call to ' . __METHOD__)]); $this->queryBuilder->resetQueryPart($queryPartName); return $this; diff --git a/lib/private/DB/ResultAdapter.php b/lib/private/DB/ResultAdapter.php index 8b004d471ec8e..95a7620e0ffb8 100644 --- a/lib/private/DB/ResultAdapter.php +++ b/lib/private/DB/ResultAdapter.php @@ -30,14 +30,21 @@ public function closeCursor(): bool { } public function fetch(int $fetchMode = PDO::FETCH_ASSOC) { - return $this->inner->fetch($fetchMode); + return match ($fetchMode) { + PDO::FETCH_ASSOC => $this->inner->fetchAssociative(), + PDO::FETCH_NUM => $this->inner->fetchNumeric(), + PDO::FETCH_COLUMN => $this->inner->fetchOne(), + default => throw new \Exception('Fetch mode needs to be assoc, num or column.'), + }; } public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array { - if ($fetchMode !== PDO::FETCH_ASSOC && $fetchMode !== PDO::FETCH_NUM && $fetchMode !== PDO::FETCH_COLUMN) { - throw new \Exception('Fetch mode needs to be assoc, num or column.'); - } - return $this->inner->fetchAll($fetchMode); + return match ($fetchMode) { + PDO::FETCH_ASSOC => $this->inner->fetchAllAssociative(), + PDO::FETCH_NUM => $this->inner->fetchAllNumeric(), + PDO::FETCH_COLUMN => $this->inner->fetchFirstColumn(), + default => throw new \Exception('Fetch mode needs to be assoc, num or column.'), + }; } public function fetchColumn($columnIndex = 0) { diff --git a/lib/private/Repair/Collation.php b/lib/private/Repair/Collation.php index 0affb3b1ca980..a01a684151b08 100644 --- a/lib/private/Repair/Collation.php +++ b/lib/private/Repair/Collation.php @@ -8,7 +8,6 @@ namespace OC\Repair; use Doctrine\DBAL\Exception\DriverException; -use Doctrine\DBAL\Platforms\MySQLPlatform; use OCP\IConfig; use OCP\IDBConnection; use OCP\Migration\IOutput; @@ -50,7 +49,7 @@ public function getName() { * Fix mime types */ public function run(IOutput $output) { - if (!$this->connection->getDatabasePlatform() instanceof MySQLPlatform) { + if ($this->connection->getDatabaseProvider() !== IDBConnection::PLATFORM_MYSQL) { $output->info('Not a mysql database -> nothing to do'); return; } diff --git a/lib/private/TaskProcessing/Db/TaskMapper.php b/lib/private/TaskProcessing/Db/TaskMapper.php index 86b2a2fcc590a..da3910dcb3d89 100644 --- a/lib/private/TaskProcessing/Db/TaskMapper.php +++ b/lib/private/TaskProcessing/Db/TaskMapper.php @@ -59,16 +59,16 @@ public function findOldestScheduledByType(array $taskTypes, array $taskIdsToIgno ->setMaxResults(1) ->orderBy('last_updated', 'ASC'); - if (count($taskTypes) > 0) { - $filter = $qb->expr()->orX(); + if (!empty($taskTypes)) { + $filter = []; foreach ($taskTypes as $taskType) { - $filter->add($qb->expr()->eq('type', $qb->createPositionalParameter($taskType))); + $filter[] = $qb->expr()->eq('type', $qb->createPositionalParameter($taskType)); } - $qb->andWhere($filter); + $qb->andWhere($qb->expr()->orX(...$filter)); } - if (count($taskIdsToIgnore) > 0) { + if (!empty($taskIdsToIgnore)) { $qb->andWhere($qb->expr()->notIn('id', $qb->createNamedParameter($taskIdsToIgnore, IQueryBuilder::PARAM_INT_ARRAY))); } diff --git a/lib/public/DB/QueryBuilder/IExpressionBuilder.php b/lib/public/DB/QueryBuilder/IExpressionBuilder.php index 6df9949cb75c2..26c7a36a6af37 100644 --- a/lib/public/DB/QueryBuilder/IExpressionBuilder.php +++ b/lib/public/DB/QueryBuilder/IExpressionBuilder.php @@ -55,6 +55,7 @@ interface IExpressionBuilder { * * @return \OCP\DB\QueryBuilder\ICompositeExpression * @since 8.2.0 + * @since 30.0.0 Calling the method without any arguments is deprecated and will throw with the next Doctrine/DBAL update * * @psalm-taint-sink sql $x */ @@ -74,6 +75,7 @@ public function andX(...$x): ICompositeExpression; * * @return \OCP\DB\QueryBuilder\ICompositeExpression * @since 8.2.0 + * @since 30.0.0 Calling the method without any arguments is deprecated and will throw with the next Doctrine/DBAL update * * @psalm-taint-sink sql $x */ diff --git a/lib/public/DB/QueryBuilder/IQueryBuilder.php b/lib/public/DB/QueryBuilder/IQueryBuilder.php index c736d3094e553..11f9737ba2f54 100644 --- a/lib/public/DB/QueryBuilder/IQueryBuilder.php +++ b/lib/public/DB/QueryBuilder/IQueryBuilder.php @@ -134,6 +134,8 @@ public function getConnection(); * * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. */ public function getState(); @@ -395,8 +397,8 @@ public function addSelect(...$select); * * * $qb = $conn->getQueryBuilder() - * ->delete('users', 'u') - * ->where('u.id = :user_id'); + * ->delete('users') + * ->where('id = :user_id'); * ->setParameter(':user_id', 1); * * @@ -405,6 +407,7 @@ public function addSelect(...$select); * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update * * @psalm-taint-sink sql $delete */ @@ -416,9 +419,10 @@ public function delete($delete = null, $alias = null); * * * $qb = $conn->getQueryBuilder() - * ->update('users', 'u') - * ->set('u.password', md5('password')) - * ->where('u.id = ?'); + * ->update('users') + * ->set('email', ':email') + * ->where('id = :user_id'); + * ->setParameter(':user_id', 1); * * * @param string $update The table whose rows are subject to the update. @@ -426,6 +430,7 @@ public function delete($delete = null, $alias = null); * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update * * @psalm-taint-sink sql $update */ @@ -609,9 +614,10 @@ public function set($key, $value); * // You can optionally programmatically build and/or expressions * $qb = $conn->getQueryBuilder(); * - * $or = $qb->expr()->orx(); - * $or->add($qb->expr()->eq('u.id', 1)); - * $or->add($qb->expr()->eq('u.id', 2)); + * $or = $qb->expr()->orx( + * $qb->expr()->eq('u.id', 1), + * $qb->expr()->eq('u.id', 2), + * ); * * $qb->update('users', 'u') * ->set('u.password', md5('password')) @@ -837,6 +843,8 @@ public function addOrderBy($sort, $order = null); * * @return mixed * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please track the details you need, outside the object. */ public function getQueryPart($queryPartName); @@ -845,6 +853,8 @@ public function getQueryPart($queryPartName); * * @return array * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please track the details you need, outside the object. */ public function getQueryParts(); @@ -855,6 +865,8 @@ public function getQueryParts(); * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please create a new IQueryBuilder instead. */ public function resetQueryParts($queryPartNames = null); @@ -865,6 +877,8 @@ public function resetQueryParts($queryPartNames = null); * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please create a new IQueryBuilder instead. */ public function resetQueryPart($queryPartName); diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php index a5df85896e2a1..09bd1a564cd46 100644 --- a/lib/public/IDBConnection.php +++ b/lib/public/IDBConnection.php @@ -278,6 +278,7 @@ public function quote($input, $type = IQueryBuilder::PARAM_STR); * * @return \Doctrine\DBAL\Platforms\AbstractPlatform The database platform. * @since 8.0.0 + * @deprecated 30.0.0 Please use {@see self::getDatabaseProvider()} and compare to self::PLATFORM_* constants */ public function getDatabasePlatform(); @@ -341,7 +342,7 @@ public function migrateToSchema(Schema $toSchema): void; * Returns the database provider name * @link https://github.com/nextcloud/server/issues/30877 * @since 28.0.0 - * @return IDBConnection::PLATFORM_* + * @return self::PLATFORM_MYSQL|self::PLATFORM_ORACLE|self::PLATFORM_POSTGRES|self::PLATFORM_SQLITE */ public function getDatabaseProvider(): string; } diff --git a/tests/lib/DB/MigrationsTest.php b/tests/lib/DB/MigrationsTest.php index 6cd489258bffe..2bdd705ff5d45 100644 --- a/tests/lib/DB/MigrationsTest.php +++ b/tests/lib/DB/MigrationsTest.php @@ -8,8 +8,6 @@ namespace Test\DB; -use Doctrine\DBAL\Platforms\OraclePlatform; -use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; @@ -326,9 +324,9 @@ public function testEnsureOracleConstraintsValidWithPrimaryKey() { public function testEnsureOracleConstraintsValidWithPrimaryKeyDefault() { $defaultName = 'PRIMARY'; - if ($this->db->getDatabasePlatform() instanceof PostgreSqlPlatform) { + if ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_POSTGRES) { $defaultName = \str_repeat('a', 26) . '_' . \str_repeat('b', 30) . '_seq'; - } elseif ($this->db->getDatabasePlatform() instanceof OraclePlatform) { + } elseif ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) { $defaultName = \str_repeat('a', 26) . '_seq'; } @@ -407,9 +405,9 @@ public function testEnsureOracleConstraintsTooLongPrimaryWithDefault() { $this->expectException(\InvalidArgumentException::class); $defaultName = 'PRIMARY'; - if ($this->db->getDatabasePlatform() instanceof PostgreSqlPlatform) { + if ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_POSTGRES) { $defaultName = \str_repeat('a', 27) . '_' . \str_repeat('b', 30) . '_seq'; - } elseif ($this->db->getDatabasePlatform() instanceof OraclePlatform) { + } elseif ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) { $defaultName = \str_repeat('a', 27) . '_seq'; } diff --git a/tests/lib/DB/MigratorTest.php b/tests/lib/DB/MigratorTest.php index eaa6540b93bd6..d12b70725ed39 100644 --- a/tests/lib/DB/MigratorTest.php +++ b/tests/lib/DB/MigratorTest.php @@ -10,8 +10,6 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; -use Doctrine\DBAL\Platforms\OraclePlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaConfig; use OC\DB\Migrator; @@ -19,7 +17,7 @@ use OC\DB\SQLiteMigrator; use OCP\DB\Types; use OCP\IConfig; -use OCP\Security\ISecureRandom; +use OCP\IDBConnection; /** * Class MigratorTest @@ -56,12 +54,10 @@ protected function setUp(): void { } private function getMigrator(): Migrator { - $platform = $this->connection->getDatabasePlatform(); - $random = \OC::$server->get(ISecureRandom::class); $dispatcher = \OC::$server->get(\OCP\EventDispatcher\IEventDispatcher::class); - if ($platform instanceof SqlitePlatform) { + if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE) { return new SQLiteMigrator($this->connection, $this->config, $dispatcher); - } elseif ($platform instanceof OraclePlatform) { + } elseif ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) { return new OracleMigrator($this->connection, $this->config, $dispatcher); } return new Migrator($this->connection, $this->config, $dispatcher); @@ -300,7 +296,7 @@ public function testNotNullEmptyValuesFailOracle(int $parameterType, $value, str $migrator = $this->getMigrator(); $migrator->migrate($startSchema); - if ($oracleThrows && $this->connection->getDatabasePlatform() instanceof OraclePlatform) { + if ($oracleThrows && $this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) { // Oracle can not store false|empty string in notnull columns $this->expectException(\Doctrine\DBAL\Exception\NotNullConstraintViolationException::class); } diff --git a/tests/lib/DB/OCPostgreSqlPlatformTest.php b/tests/lib/DB/OCPostgreSqlPlatformTest.php index 4f83e866a7c51..3ed420df501db 100644 --- a/tests/lib/DB/OCPostgreSqlPlatformTest.php +++ b/tests/lib/DB/OCPostgreSqlPlatformTest.php @@ -7,7 +7,7 @@ namespace Test\DB; -use Doctrine\DBAL\Platforms\PostgreSQL100Platform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Schema\Comparator; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Types\Types; @@ -24,7 +24,7 @@ */ class OCPostgreSqlPlatformTest extends \Test\TestCase { public function testAlterBigint() { - $platform = new PostgreSQL100Platform(); + $platform = new PostgreSQLPlatform(); $sourceSchema = new Schema(); $targetSchema = new Schema(); diff --git a/tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php b/tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php index d8147035b1696..5c9482419d97c 100644 --- a/tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php +++ b/tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php @@ -10,6 +10,7 @@ use Doctrine\DBAL\Query\Expression\ExpressionBuilder as DoctrineExpressionBuilder; use OC\DB\QueryBuilder\ExpressionBuilder\ExpressionBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; +use Psr\Log\LoggerInterface; use Test\TestCase; /** @@ -32,15 +33,19 @@ class ExpressionBuilderTest extends TestCase { /** @var \Doctrine\DBAL\Connection */ protected $internalConnection; + /** @var LoggerInterface */ + protected $logger; + protected function setUp(): void { parent::setUp(); $this->connection = \OC::$server->getDatabaseConnection(); $this->internalConnection = \OC::$server->get(\OC\DB\Connection::class); + $this->logger = $this->createMock(LoggerInterface::class); $queryBuilder = $this->createMock(IQueryBuilder::class); - $this->expressionBuilder = new ExpressionBuilder($this->connection, $queryBuilder); + $this->expressionBuilder = new ExpressionBuilder($this->connection, $queryBuilder, $this->logger); $this->doctrineExpressionBuilder = new DoctrineExpressionBuilder($this->internalConnection); } diff --git a/tests/lib/Files/Cache/CacheTest.php b/tests/lib/Files/Cache/CacheTest.php index faecbf54491c0..a36607eb9659e 100644 --- a/tests/lib/Files/Cache/CacheTest.php +++ b/tests/lib/Files/Cache/CacheTest.php @@ -7,12 +7,12 @@ namespace Test\Files\Cache; -use Doctrine\DBAL\Platforms\MySqlPlatform; use OC\Files\Cache\Cache; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchQuery; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Search\ISearchComparison; +use OCP\IDBConnection; use OCP\IUser; class LongId extends \OC\Files\Storage\Temporary { @@ -142,7 +142,7 @@ public function testFolder($folder) { if (strpos($folder, 'F09F9890')) { // 4 byte UTF doesn't work on mysql $params = \OC::$server->get(\OC\DB\Connection::class)->getParams(); - if (\OC::$server->getDatabaseConnection()->getDatabasePlatform() instanceof MySqlPlatform && $params['charset'] !== 'utf8mb4') { + if (\OC::$server->getDatabaseConnection()->getDatabaseProvider() === IDBConnection::PLATFORM_MYSQL && $params['charset'] !== 'utf8mb4') { $this->markTestSkipped('MySQL doesn\'t support 4 byte UTF-8'); } } diff --git a/tests/lib/Repair/RepairCollationTest.php b/tests/lib/Repair/RepairCollationTest.php index 55bda8337c978..6d3946b2a85b8 100644 --- a/tests/lib/Repair/RepairCollationTest.php +++ b/tests/lib/Repair/RepairCollationTest.php @@ -7,7 +7,6 @@ namespace Test\Repair; -use Doctrine\DBAL\Platforms\MySqlPlatform; use OC\Repair\Collation; use OCP\IDBConnection; use OCP\Migration\IOutput; @@ -61,7 +60,7 @@ protected function setUp(): void { $this->connection = \OC::$server->get(IDBConnection::class); $this->logger = $this->createMock(LoggerInterface::class); $this->config = \OC::$server->getConfig(); - if (!$this->connection->getDatabasePlatform() instanceof MySqlPlatform) { + if ($this->connection->getDatabaseProvider() !== IDBConnection::PLATFORM_MYSQL) { $this->markTestSkipped("Test only relevant on MySql"); } @@ -73,7 +72,7 @@ protected function setUp(): void { } protected function tearDown(): void { - $this->connection->getInner()->getSchemaManager()->dropTable($this->tableName); + $this->connection->getInner()->createSchemaManager()->dropTable($this->tableName); parent::tearDown(); } diff --git a/tests/lib/SystemTag/SystemTagObjectMapperTest.php b/tests/lib/SystemTag/SystemTagObjectMapperTest.php index c9aff9f51835a..7beb1c1f1e167 100644 --- a/tests/lib/SystemTag/SystemTagObjectMapperTest.php +++ b/tests/lib/SystemTag/SystemTagObjectMapperTest.php @@ -152,6 +152,7 @@ public function testGetObjectsForTags() { [$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()], 'testtype' ); + sort($objectIds); $this->assertEquals([ '1',