diff --git a/3rdparty b/3rdparty index 020d0d3892bd3..09085cefc4085 160000 --- a/3rdparty +++ b/3rdparty @@ -1 +1 @@ -Subproject commit 020d0d3892bd3b7296db8ed21448c834d33d5723 +Subproject commit 09085cefc4085f8748eaef6fb1740e92fb0bf5f0 diff --git a/core/register_command.php b/core/register_command.php index 98a653aed7e41..d7a365d1426cb 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -115,6 +115,15 @@ $application->add(new OC\Core\Command\Db\Migrations\MigrateCommand(\OC::$server->get(\OC\DB\Connection::class))); $application->add(new OC\Core\Command\Db\Migrations\GenerateCommand(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getAppManager())); $application->add(new OC\Core\Command\Db\Migrations\ExecuteCommand(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getConfig())); + Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($application, new class implements \Doctrine\ORM\Tools\Console\EntityManagerProvider { + public function getDefaultManager(): \Doctrine\ORM\EntityManagerInterface { + return \OCP\Server::get(\OCP\DB\ORM\IEntityManager::class)->get(); + } + + public function getManager(string $name): \Doctrine\ORM\EntityManagerInterface { + return \OCP\Server::get(\OCP\DB\ORM\IEntityManager::class)->get(); + } + }); } $application->add(new OC\Core\Command\Encryption\Disable(\OC::$server->getConfig())); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 004c569d21bec..5f3e39a1240d4 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -178,6 +178,15 @@ 'OCP\\DB\\IPreparedStatement' => $baseDir . '/lib/public/DB/IPreparedStatement.php', 'OCP\\DB\\IResult' => $baseDir . '/lib/public/DB/IResult.php', 'OCP\\DB\\ISchemaWrapper' => $baseDir . '/lib/public/DB/ISchemaWrapper.php', + 'OCP\\DB\\ORM\\IEntityManager' => $baseDir . '/lib/public/DB/ORM/IEntityManager.php', + 'OCP\\DB\\ORM\\IEntityRepository' => $baseDir . '/lib/public/DB/ORM/IEntityRepository.php', + 'OCP\\DB\\ORM\\IParameter' => $baseDir . '/lib/public/DB/ORM/IParameter.php', + 'OCP\\DB\\ORM\\IQuery' => $baseDir . '/lib/public/DB/ORM/IQuery.php', + 'OCP\\DB\\ORM\\LockMode' => $baseDir . '/lib/public/DB/ORM/LockMode.php', + 'OCP\\DB\\ORM\\NoResultException' => $baseDir . '/lib/public/DB/ORM/NoResultException.php', + 'OCP\\DB\\ORM\\NonUniqueResultException' => $baseDir . '/lib/public/DB/ORM/NonUniqueResultException.php', + 'OCP\\DB\\ORM\\OptimisticLockException' => $baseDir . '/lib/public/DB/ORM/OptimisticLockException.php', + 'OCP\\DB\\ORM\\PessimisticLockException' => $baseDir . '/lib/public/DB/ORM/PessimisticLockException.php', 'OCP\\DB\\QueryBuilder\\ICompositeExpression' => $baseDir . '/lib/public/DB/QueryBuilder/ICompositeExpression.php', 'OCP\\DB\\QueryBuilder\\IExpressionBuilder' => $baseDir . '/lib/public/DB/QueryBuilder/IExpressionBuilder.php', 'OCP\\DB\\QueryBuilder\\IFunctionBuilder' => $baseDir . '/lib/public/DB/QueryBuilder/IFunctionBuilder.php', @@ -1066,6 +1075,12 @@ 'OC\\DB\\MySQLMigrator' => $baseDir . '/lib/private/DB/MySQLMigrator.php', 'OC\\DB\\MySqlTools' => $baseDir . '/lib/private/DB/MySqlTools.php', 'OC\\DB\\OCSqlitePlatform' => $baseDir . '/lib/private/DB/OCSqlitePlatform.php', + 'OC\\DB\\ORM\\EntityManagerAdapter' => $baseDir . '/lib/private/DB/ORM/EntityManagerAdapter.php', + 'OC\\DB\\ORM\\EntityRepositoryAdapter' => $baseDir . '/lib/private/DB/ORM/EntityRepositoryAdapter.php', + 'OC\\DB\\ORM\\ParameterAdapter' => $baseDir . '/lib/private/DB/ORM/ParameterAdapter.php', + 'OC\\DB\\ORM\\Psr6CacheAdapter' => $baseDir . '/lib/private/DB/ORM/Psr6CacheAdapter.php', + 'OC\\DB\\ORM\\QueryAdapter' => $baseDir . '/lib/private/DB/ORM/QueryAdapter.php', + 'OC\\DB\\ORM\\TablePrefix' => $baseDir . '/lib/private/DB/ORM/TablePrefix.php', 'OC\\DB\\ObjectParameter' => $baseDir . '/lib/private/DB/ObjectParameter.php', 'OC\\DB\\OracleConnection' => $baseDir . '/lib/private/DB/OracleConnection.php', 'OC\\DB\\OracleMigrator' => $baseDir . '/lib/private/DB/OracleMigrator.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 8c13e047b3eee..433631000c0f0 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -211,6 +211,15 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\DB\\IPreparedStatement' => __DIR__ . '/../../..' . '/lib/public/DB/IPreparedStatement.php', 'OCP\\DB\\IResult' => __DIR__ . '/../../..' . '/lib/public/DB/IResult.php', 'OCP\\DB\\ISchemaWrapper' => __DIR__ . '/../../..' . '/lib/public/DB/ISchemaWrapper.php', + 'OCP\\DB\\ORM\\IEntityManager' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/IEntityManager.php', + 'OCP\\DB\\ORM\\IEntityRepository' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/IEntityRepository.php', + 'OCP\\DB\\ORM\\IParameter' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/IParameter.php', + 'OCP\\DB\\ORM\\IQuery' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/IQuery.php', + 'OCP\\DB\\ORM\\LockMode' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/LockMode.php', + 'OCP\\DB\\ORM\\NoResultException' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/NoResultException.php', + 'OCP\\DB\\ORM\\NonUniqueResultException' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/NonUniqueResultException.php', + 'OCP\\DB\\ORM\\OptimisticLockException' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/OptimisticLockException.php', + 'OCP\\DB\\ORM\\PessimisticLockException' => __DIR__ . '/../../..' . '/lib/public/DB/ORM/PessimisticLockException.php', 'OCP\\DB\\QueryBuilder\\ICompositeExpression' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/ICompositeExpression.php', 'OCP\\DB\\QueryBuilder\\IExpressionBuilder' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IExpressionBuilder.php', 'OCP\\DB\\QueryBuilder\\IFunctionBuilder' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IFunctionBuilder.php', @@ -1099,6 +1108,12 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\DB\\MySQLMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/MySQLMigrator.php', 'OC\\DB\\MySqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/MySqlTools.php', 'OC\\DB\\OCSqlitePlatform' => __DIR__ . '/../../..' . '/lib/private/DB/OCSqlitePlatform.php', + 'OC\\DB\\ORM\\EntityManagerAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ORM/EntityManagerAdapter.php', + 'OC\\DB\\ORM\\EntityRepositoryAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ORM/EntityRepositoryAdapter.php', + 'OC\\DB\\ORM\\ParameterAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ORM/ParameterAdapter.php', + 'OC\\DB\\ORM\\Psr6CacheAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ORM/Psr6CacheAdapter.php', + 'OC\\DB\\ORM\\QueryAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ORM/QueryAdapter.php', + 'OC\\DB\\ORM\\TablePrefix' => __DIR__ . '/../../..' . '/lib/private/DB/ORM/TablePrefix.php', 'OC\\DB\\ObjectParameter' => __DIR__ . '/../../..' . '/lib/private/DB/ObjectParameter.php', 'OC\\DB\\OracleConnection' => __DIR__ . '/../../..' . '/lib/private/DB/OracleConnection.php', 'OC\\DB\\OracleMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/OracleMigrator.php', diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php index 95f3185bcdb17..c427e4fdb8bd4 100644 --- a/lib/private/DB/ConnectionFactory.php +++ b/lib/private/DB/ConnectionFactory.php @@ -29,7 +29,7 @@ namespace OC\DB; use Doctrine\Common\EventManager; -use Doctrine\DBAL\Configuration; +use Doctrine\ORM\Configuration; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Event\Listeners\OracleSessionInit; use Doctrine\DBAL\Event\Listeners\SQLSessionInit; diff --git a/lib/private/DB/ORM/EntityManagerAdapter.php b/lib/private/DB/ORM/EntityManagerAdapter.php new file mode 100644 index 0000000000000..bcfc936f094eb --- /dev/null +++ b/lib/private/DB/ORM/EntityManagerAdapter.php @@ -0,0 +1,92 @@ + \OC_App::getAppPath($appId) . '/lib/Entity/', \OC_App::getEnabledApps()), fn ($path) => is_dir($path)); + $isDevMode = true; + $proxyDir = null; + $cache = null; + + + $evm = $connection->getInner()->getEventManager(); + $tablePrefix = new TablePrefix('oc_'); + + $evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix); + // TODO actually use our cache with a psr6 cache wrapper or at least our cache config + $config = ORMSetup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir, $cache); + + $this->em = EntityManager::create($connection->getInner(), $config, $evm); + $this->connection = $connection; + } + + public function createQueryBuilder(): IQueryBuilder { + return new QueryBuilderAdapter($this->em->createQueryBuilder()); + } + + public function createQuery($dql = ''): IQuery + { + return new QueryAdapter($this->em->createQuery($dql)); + } + + public function flush(): void { + $this->em->flush(); + } + + public function find(string $className, $id, ?int $lockMode = null, ?int $lockVersion = null): ?object { + return $this->em->find($className, $id, $lockMode, $lockVersion); + } + + public function clear(): void { + $this->em->clear(); + } + + public function persist(object $entity): void { + $this->em->persist($entity); + } + + public function remove(object $entity): void { + $this->em->remove($entity); + } + + public function lock(object $entity, int $lockMode, $lockVersion = null): void { + $this->em->lock($entity, $lockMode, $lockVersion); + } + + public function getRepository($className): IEntityRepository { + /** @var EntityRepository $internalRepo */ + $internalRepo = $this->em->getRepository($className); + return new EntityRepositoryAdapter($internalRepo); + } + + public function contains(object $entity): bool { + return $this->em->contains($entity); + } + + public function getConnection(): IDBConnection { + return $this->connection; + } + + /** + * Only for internal use + */ + public function get(): EntityManager { + return $this->em; + } + +} diff --git a/lib/private/DB/ORM/EntityRepositoryAdapter.php b/lib/private/DB/ORM/EntityRepositoryAdapter.php new file mode 100644 index 0000000000000..24e17d1f19b73 --- /dev/null +++ b/lib/private/DB/ORM/EntityRepositoryAdapter.php @@ -0,0 +1,35 @@ +entityRepository = $entityRepository; + } + + public function find($id): ?object { + return $this->entityRepository->find($id); + } + + public function findAll(): array { + return $this->entityRepository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array { + return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?object { + return $this->entityRepository->findOneBy($criteria); + } + + public function getClassName(): string { + return $this->entityRepository->getClassName(); + } +} diff --git a/lib/private/DB/ORM/ExpressionAdapter.php b/lib/private/DB/ORM/ExpressionAdapter.php new file mode 100644 index 0000000000000..1efd3aa4aaad7 --- /dev/null +++ b/lib/private/DB/ORM/ExpressionAdapter.php @@ -0,0 +1,25 @@ + +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace OC\DB\ORM; + +use Doctrine\Common\EventManager; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\ORMSetup; +use Doctrine\ORM\Query\Expr; +use OC\DB\ConnectionAdapter; +use OCP\DB\ORM\Query\IExpression; +use OCP\IDBConnection; + +class ExpressionAdapter implements IExpression { + private Expr $expr; + + public function __construct(Expr $expr) { + $this->expr = $expr; + } +} diff --git a/lib/private/DB/ORM/ParameterAdapter.php b/lib/private/DB/ORM/ParameterAdapter.php new file mode 100644 index 0000000000000..9927cb2145362 --- /dev/null +++ b/lib/private/DB/ORM/ParameterAdapter.php @@ -0,0 +1,34 @@ +parameter = $parameter; + } + + public function getName(): string { + return $this->parameter->getName(); + } + + public function getValue() { + return $this->parameter->getValue(); + } + + public function getType() { + return $this->parameter->getType(); + } + + public function setValue($value, $type = null): void { + $this->parameter->setValue($value, $type); + } + + public function typeWasSpecified(): bool { + return $this->parameter->typeWasSpecified(); + } +} diff --git a/lib/private/DB/ORM/Psr6CacheAdapter.php b/lib/private/DB/ORM/Psr6CacheAdapter.php new file mode 100644 index 0000000000000..9d324e33b2ed2 --- /dev/null +++ b/lib/private/DB/ORM/Psr6CacheAdapter.php @@ -0,0 +1,116 @@ +cache = $cache; + $this->key = $key; + } + + private function fetch(): void { + if (!$this->fetched) { + $this->value = $this->cache->get($this->key); + $this->fetched = true; + } + } + + public function getKey() { + return $this->key; + } + + public function get() { + $this->fetch(); + return $this->value; + } + + public function isHit() { + $this->fetch(); + return $this->value !== null; + } + + public function set($value) { + $this->value = $value; + } + + public function expiresAt($expiration) { + $this->_expireAt = $expiration; + } + + public function expiresAfter($time) { + $this->_expireAfter = $time; + } + + public function getExpireAt(): ?\DateTime + { + return $this->_expireAt; + } + + public function getExpireAfter(): int + { + return $this->_expireAfter; + } +} + +class Psr6CacheAdapter implements CacheItemPoolInterface { + private ICache $cache; + + public function __construct(ICache $cache) { + $this->cache = $cache; + } + + public function getItem($key) { + return new CacheItemAdapter($this->cache, $key); + } + + public function getItems(array $keys = array()) { + for (int ) + // TODO: Implement getItems() method. + } + + public function hasItem($key) + { + // TODO: Implement hasItem() method. + } + + public function clear() + { + // TODO: Implement clear() method. + } + + public function deleteItem($key) + { + // TODO: Implement deleteItem() method. + } + + public function deleteItems(array $keys) + { + // TODO: Implement deleteItems() method. + } + + public function save(CacheItemInterface $item) + { + // TODO: Implement save() method. + } + + public function saveDeferred(CacheItemInterface $item) + { + // TODO: Implement saveDeferred() method. + } + + public function commit() + { + // TODO: Implement commit() method. + } +} diff --git a/lib/private/DB/ORM/QueryAdapter.php b/lib/private/DB/ORM/QueryAdapter.php new file mode 100644 index 0000000000000..3c68600ad3ac0 --- /dev/null +++ b/lib/private/DB/ORM/QueryAdapter.php @@ -0,0 +1,96 @@ +query = $query; + } + + public function setCacheable(bool $cacheable): IQuery { + $this->query->setCacheable($cacheable); + return $this; + } + + public function isCacheable(): bool { + return $this->query->isCacheable(); + } + + public function getParameter($key): ?IParameter { + $internal = $this->query->getParameter($key); + return $internal === null ? new ParameterAdapter($internal) : null; + } + + public function setParameters($parameters): IQuery { + $this->query->setParameters($parameters); + return $this; + } + + public function setParameter($key, $value, $type = null): IQuery { + $this->query->setParameter($key, $value, $type); + return $this; + } + + public function setMaxResults(?int $maxResults): IQuery { + $this->query->setMaxResults($maxResults); + return $this; + } + + public function getResult() { + return $this->query->getResult(); + } + + public function getArrayResult() { + return $this->query->getArrayResult(); + } + + public function getOneOrNullResult() { + return $this->query->getOneOrNullResult(); + } + + public function getSingleResult() { + try { + return $this->query->getSingleResult(); + } catch (\Doctrine\ORM\NoResultException $e) { + throw new NoResultException($e); + } catch (\Doctrine\ORM\NonUniqueResultException $e) { + throw new NonUniqueResultException($e); + } + } + + public function getSingleScalarResult() { + try { + return $this->query->getSingleScalarResult(); + } catch (\Doctrine\ORM\NoResultException $e) { + throw new NoResultException($e); + } catch (\Doctrine\ORM\NonUniqueResultException $e) { + throw new NonUniqueResultException($e); + } + } + + public function getSql(): string { + return $this->query->getSQL(); + } + + /** + * Get all defined parameters. + * + * @return ArrayCollection The defined query parameters. + * @psalm-return ArrayCollection + */ + public function getParameters(): ArrayCollection + { + return $this->query->getParameters() + ->map(fn (Query\Parameter $parameter) => new Parameter($parameter)); + } +} diff --git a/lib/private/DB/ORM/QueryBuilderAdapter.php b/lib/private/DB/ORM/QueryBuilderAdapter.php new file mode 100644 index 0000000000000..d7a979c08bc21 --- /dev/null +++ b/lib/private/DB/ORM/QueryBuilderAdapter.php @@ -0,0 +1,527 @@ + +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace OC\DB\ORM; + +use Doctrine\Common\EventManager; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Query\Parameter; +use OC\DB\ConnectionAdapter; +use OCP\DB\ORM\IQueryBuilder; +use OCP\IDBConnection; + +class QueryBuilderAdapter implements IQueryBuilder { + private QueryBuilder $qb; + + public function __construct(QueryBuilder $queryBuilder) { + $this->qb = $queryBuilder; + } + + public function expr(): Query\IExpression { + return new ExpressionAdapter($this->qb->expr()); + } + + public function setParameter($key, $value, $type = null): self { + $this->qb->setParameter($key, $value, $type); + return $this; + } + + public function setParameters(array $parameters): self { + $ormParameters = [] + foreach ($parameters as $key => $value) { + $ormParameters[] = new Parameter($key, $value); + } + $this->qb->setParameters(new ArrayCollection($ormParameters)); + return $this; + } + + public function getParameters(): array { + $ormParameters = [] + foreach ($this->qb->getParameters() as $value) { + $ormParameters[] = new ParameterAdapter($value); + } + return $ormParameters; + } + + /** + * Gets a (previously set) query parameter of the query being constructed. + * + * @param string|int $key The key (index or name) of the bound parameter. + * + * @return ?IParameter The value of the bound parameter. + */ + public function getParameter($key): ?IParameter; + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param int|null $firstResult The first result to return. + * + * @return $this + */ + public function setFirstResult(?int $firstResult): self + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. + * + * @return int|null The position of the first result. + */ + public function getFirstResult(): ?int; + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @param int|null $maxResults The maximum number of results to retrieve. + */ + public function setMaxResults(?int $maxResults): self; + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if {@link setMaxResults} was not applied to this query builder. + * + * @return int|null Maximum number of results. + */ + public function getMaxResults(): ?int; + + /** + * Specifies an item that is to be returned in the query result. + * Replaces any previously specified selections, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u', 'p') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p'); + * + * + * @param mixed $select The selection expressions. + */ + public function select($select = null): self; + + /** + * Adds a DISTINCT flag to this query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->distinct() + * ->from('User', 'u'); + * + */ + public function distinct(bool $flag = true): self; + + /** + * Adds an item that is to be returned in the query result. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->addSelect('p') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p'); + * + * + * @param mixed $select The selection expression. + */ + public function addSelect($select = null): self; + + /** + * Turns the query being built into a bulk delete query that ranges over + * a certain entity type. + * + * + * $qb = $em->createQueryBuilder() + * ->delete('User', 'u') + * ->where('u.id = :user_id') + * ->setParameter('user_id', 1); + * + * + * @param string|null $delete The class/type whose instances are subject to the deletion. + * @param string|null $alias The class/type alias used in the constructed query. + */ + public function delete(?string $delete = null, ?string $alias = null): self; + + /** + * Turns the query being built into a bulk update query that ranges over + * a certain entity type. + * + * + * $qb = $em->createQueryBuilder() + * ->update('User', 'u') + * ->set('u.password', '?1') + * ->where('u.id = ?2'); + * + * + * @param string|null $update The class/type whose instances are subject to the update. + * @param string|null $alias The class/type alias used in the constructed query. + */ + public function update(?string $update = null, ?string $alias = null): self; + + /** + * Creates and adds a query root corresponding to the entity identified by the given alias, + * forming a cartesian product with any existing query roots. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * + * + * @param string $from The class name. + * @param string $alias The alias of the class. + * @param string|null $indexBy The index for the from. + * + * @return $this + */ + public function from(string $from, string $alias, ?string $indexBy = null); + + /** + * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with + * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it + * setting an index by. + * + * + * $qb = $userRepository->createQueryBuilder('u') + * ->indexBy('u', 'u.id'); + * + * // Is equivalent to... + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u', 'u.id'); + * + * + * @param string $alias The root alias of the class. + * @param string $indexBy The index for the from. + * + * @throws Query\QueryException + */ + public function indexBy(string $alias, string $indexBy): self; + + /** + * Creates and adds a join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * + * @param string $join The relationship to join. + * @param string $alias The alias of the join. + * @param string|null $conditionType The condition type constant. Either ON or WITH. + * @param string|Expr\Comparison|Expr\Composite|null $condition The condition for the join. + * @param string|null $indexBy The index for the join. + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + */ + public function join(string $join, string $alias, ?string $conditionType = null, $condition = null, ?string $indexBy = null): self; + + /** + * Creates and adds a join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * [php] + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * @param string $join The relationship to join. + * @param string $alias The alias of the join. + * @param string|null $conditionType The condition type constant. Either ON or WITH. + * @param string|Expr\Comparison|Expr\Composite|null $condition The condition for the join. + * @param string|null $indexBy The index for the join. + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + */ + public function innerJoin(string $join, string $alias, ?string $conditionType = null, $condition = null, ?string $indexBy = null): self; + + /** + * Creates and adds a left join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * + * @param string $join The relationship to join. + * @param string $alias The alias of the join. + * @param string|null $conditionType The condition type constant. Either ON or WITH. + * @param string|Expr\Comparison|Expr\Composite|null $condition The condition for the join. + * @param string|null $indexBy The index for the join. + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + * + * @return $this + */ + public function leftJoin(string $join, string $alias, $conditionType = null, $condition = null, ?string $indexBy = null); + + /** + * Sets a new value for a field in a bulk update query. + * + * + * $qb = $em->createQueryBuilder() + * ->update('User', 'u') + * ->set('u.password', '?1') + * ->where('u.id = ?2'); + * + * + * @param string $key The key/field to set. + * @param mixed $value The value, expression, placeholder, etc. + */ + public function set(string $key, $value): self; + + /** + * Specifies one or more restrictions to the query result. + * Replaces any previously specified restrictions, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = ?'); + * + * // You can optionally programmatically build and/or expressions + * $qb = $em->createQueryBuilder(); + * + * $or = $qb->expr()->orX(); + * $or->add($qb->expr()->eq('u.id', 1)); + * $or->add($qb->expr()->eq('u.id', 2)); + * + * $qb->update('User', 'u') + * ->set('u.password', '?') + * ->where($or); + * + * + * @param mixed $predicates The restriction predicates. + * + * @return $this + */ + public function where($predicates) + { + if (! (func_num_args() === 1 && $predicates instanceof Expr\Composite)) { + $predicates = new Expr\Andx(func_get_args()); + } + + return $this->add('where', $predicates); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * conjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.username LIKE ?') + * ->andWhere('u.is_active = 1'); + * + * + * @see where() + * + * @param mixed $where The query restrictions. + * + * @return $this + */ + public function andWhere() + { + $args = func_get_args(); + $where = $this->getDQLPart('where'); + + if ($where instanceof Expr\Andx) { + $where->addMultiple($args); + } else { + array_unshift($args, $where); + $where = new Expr\Andx($args); + } + + return $this->add('where', $where); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * disjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = 1') + * ->orWhere('u.id = 2'); + * + * + * @see where() + * + * @param mixed $where The WHERE statement. + * + * @return $this + */ + public function orWhere() + { + $args = func_get_args(); + $where = $this->getDQLPart('where'); + + if ($where instanceof Expr\Orx) { + $where->addMultiple($args); + } else { + array_unshift($args, $where); + $where = new Expr\Orx($args); + } + + return $this->add('where', $where); + } + + /** + * Specifies a grouping over the results of the query. + * Replaces any previously specified groupings, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->groupBy('u.id'); + * + * + * @param string $groupBy The grouping expression. + * + * @return $this + */ + public function groupBy($groupBy) + { + return $this->add('groupBy', new Expr\GroupBy(func_get_args())); + } + + /** + * Adds a grouping expression to the query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->groupBy('u.lastLogin') + * ->addGroupBy('u.createdAt'); + * + * + * @param string $groupBy The grouping expression. + * + * @return $this + */ + public function addGroupBy($groupBy) + { + return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true); + } + + /** + * Specifies a restriction over the groups of the query. + * Replaces any previous having restrictions, if any. + * + * @param mixed $having The restriction over the groups. + * + * @return $this + */ + public function having($having) + { + if (! (func_num_args() === 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) { + $having = new Expr\Andx(func_get_args()); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * conjunction with any existing having restrictions. + * + * @param mixed $having The restriction to append. + * + * @return $this + */ + public function andHaving($having) + { + $args = func_get_args(); + $having = $this->getDQLPart('having'); + + if ($having instanceof Expr\Andx) { + $having->addMultiple($args); + } else { + array_unshift($args, $having); + $having = new Expr\Andx($args); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * disjunction with any existing having restrictions. + * + * @param mixed $having The restriction to add. + * + * @return $this + */ + public function orHaving($having) + { + $args = func_get_args(); + $having = $this->getDQLPart('having'); + + if ($having instanceof Expr\Orx) { + $having->addMultiple($args); + } else { + array_unshift($args, $having); + $having = new Expr\Orx($args); + } + + return $this->add('having', $having); + } + + /** + * Specifies an ordering for the query results. + * Replaces any previously specified orderings, if any. + * + * @param string|Expr\OrderBy $sort The ordering expression. + * @param string|null $order The ordering direction. + * + * @return $this + */ + public function orderBy($sort, $order = null) + { + $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order); + + return $this->add('orderBy', $orderBy); + } + + /** + * Adds an ordering to the query results. + * + * @param string|Expr\OrderBy $sort The ordering expression. + * @param string|null $order The ordering direction. + * + * @return $this + */ + public function addOrderBy($sort, $order = null) + { + $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order); + + return $this->add('orderBy', $orderBy, true); + } +} diff --git a/lib/private/DB/ORM/TablePrefix.php b/lib/private/DB/ORM/TablePrefix.php new file mode 100644 index 0000000000000..168568bee03ae --- /dev/null +++ b/lib/private/DB/ORM/TablePrefix.php @@ -0,0 +1,34 @@ +prefix = (string)$prefix; + } + + public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) + { + $classMetadata = $eventArgs->getClassMetadata(); + + if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) { + $classMetadata->setPrimaryTable([ + 'name' => $this->prefix . $classMetadata->getTableName() + ]); + } + + foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { + if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) { + $mappedTableName = $mapping['joinTable']['name']; + $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName; + } + } + } + +} diff --git a/lib/private/Server.php b/lib/private/Server.php index 7223c3b8ae374..d996e99838ba3 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -80,6 +80,7 @@ use OC\Dashboard\DashboardManager; use OC\DB\Connection; use OC\DB\ConnectionAdapter; +use OC\DB\ORM\EntityManagerAdapter; use OC\Diagnostics\EventLogger; use OC\Diagnostics\QueryLogger; use OC\EventDispatcher\SymfonyAdapter; @@ -167,6 +168,7 @@ use OCP\Contacts\ContactsMenu\IActionFactory; use OCP\Contacts\ContactsMenu\IContactsStore; use OCP\Dashboard\IDashboardManager; +use OCP\DB\ORM\IEntityManager; use OCP\Defaults; use OCP\Diagnostics\IEventLogger; use OCP\Diagnostics\IQueryLogger; @@ -878,6 +880,13 @@ public function __construct($webRoot, \OC\Config $config) { $connection = $factory->getConnection($type, $connectionParams); return $connection; }); + + $this->registerService(IEntityManager::class, function (ContainerInterface $c): IEntityManager { + /** @var ConnectionAdapter $connection */ + $connection = $c->get(IDBConnection::class); + return new EntityManagerAdapter($connection); + }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('DatabaseConnection', IDBConnection::class); diff --git a/lib/public/DB/ORM/IEntityManager.php b/lib/public/DB/ORM/IEntityManager.php new file mode 100644 index 0000000000000..9ce2e6e350c55 --- /dev/null +++ b/lib/public/DB/ORM/IEntityManager.php @@ -0,0 +1,145 @@ + +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace OCP\DB\ORM; + +use DateTimeInterface; +use OCP\IDBConnection; + +/** + * @since 25.0.0 + */ +interface IEntityManager { + /** + * Creates a new Query object. + * + * @param string $dql The DQL string. + * @since 25.0.0 + */ + public function createQuery(string $dql = ''): IQuery; + + /** + * Creates a new QueryBuilder object. + * + * @since 25.0.0 + */ + public function createQueryBuilder(): IQueryBuilder; + + /** + * Flushes all changes to objects that have been queued up to now to the database. + * This effectively synchronizes the in-memory state of managed objects with the + * database. + * + * @throws OptimisticLockException If a version check on an entity that + * makes use of optimistic locking fails. + * @throws \OCP\DB\Exception + * @since 25.0.0 + */ + public function flush(): void; + + /** + * Finds an Entity by its identifier. + * + * @param string $className The class name of the entity to find. + * @param mixed $id The identity of the entity to find. + * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * during the search. + * @param int|null $lockVersion The version of the entity to find when using + * optimistic locking. + * @psalm-param class-string $className + * @psalm-param LockMode::*|null $lockMode + * + * @return object|null The entity instance or NULL if the entity can not be found. + * @psalm-return ?T + * + * @throws OptimisticLockException + * @throws \OCP\DB\Exception + * + * @template T + * @since 25.0.0 + */ + public function find(string $className, $id, ?int $lockMode = null, ?int $lockVersion = null): ?object; + + /** + * Clears the EntityManager. All entities that are currently managed + * by this EntityManager become detached. + * + * @throws \OCP\DB\Exception If a $entityName is given, but that entity is not + * found in the mappings. + * @since 25.0.0 + */ + public function clear(): void; + + /** + * Tells the EntityManager to make an instance managed and persistent. + * + * The entity will be entered into the database at or before transaction + * commit or as a result of the flush operation. + * + * NOTE: The persist operation always considers entities that are not yet known to + * this EntityManager as NEW. Do not pass detached entities to the persist operation. + * + * @param object $entity The instance to make managed and persistent. + * + * @throws \OCP\DB\Exception + * @since 25.0.0 + */ + public function persist(object $entity): void; + + /** + * Removes an entity instance. + * + * A removed entity will be removed from the database at or before transaction commit + * or as a result of the flush operation. + * + * @param object $entity The entity instance to remove. + * + @throws \OCP\DB\Exception + * @since 25.0.0 + */ + public function remove(object $entity): void; + + /** + * Acquire a lock on the given entity. + * + * @param int|DateTimeInterface|null $lockVersion + * @psalm-param LockMode::* $lockMode + * + * + * @throws OptimisticLockException + * @throws PessimisticLockException + * @since 25.0.0 + */ + public function lock(object $entity, int $lockMode, $lockVersion = null): void; + + /** + * {@inheritdoc} + * + * @psalm-param class-string $className + * + * @psalm-return IEntityRepository + * + * @template T of object + * @since 25.0.0 + */ + public function getRepository($className): IEntityRepository; + + /** + * Determines whether an entity instance is managed in this EntityManager. + * + * @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise. + * @since 25.0.0 + */ + public function contains(object $entity): bool; + + /** + * @return IDBConnection + * @since 25.0.0 + */ + public function getConnection(): IDBConnection; +} diff --git a/lib/public/DB/ORM/IEntityRepository.php b/lib/public/DB/ORM/IEntityRepository.php new file mode 100644 index 0000000000000..2d43932a6139b --- /dev/null +++ b/lib/public/DB/ORM/IEntityRepository.php @@ -0,0 +1,67 @@ + The objects. + * @psalm-return T[] + */ + public function findAll(): array; + + /** + * Finds objects by a set of criteria. + * + * Optionally sorting and limiting details can be passed. An implementation may throw + * an UnexpectedValueException if certain values of the sorting or limiting details are + * not supported. + * + * @param array $criteria + * @param array|null $orderBy + * @psalm-param array|null $orderBy + * + * @return array The objects. + * @psalm-return T[] + * + * @throws \RuntimeException + */ + public function findBy( + array $criteria, + ?array $orderBy = null, + ?int $limit = null, + ?int $offset = null + ): array; + + /** + * Finds a single object by a set of criteria. + * + * @param array $criteria The criteria. + * + * @return T|null + */ + public function findOneBy(array $criteria): ?object; + + /** + * Returns the class name of the object managed by the repository. + * + * @psalm-return class-string + */ + public function getClassName(): string; +} diff --git a/lib/public/DB/ORM/IParameter.php b/lib/public/DB/ORM/IParameter.php new file mode 100644 index 0000000000000..3fc7880ec0aa2 --- /dev/null +++ b/lib/public/DB/ORM/IParameter.php @@ -0,0 +1,37 @@ + + */ + public function getParameters(): ArrayCollection; +} diff --git a/lib/public/DB/ORM/IQueryBuilder.php b/lib/public/DB/ORM/IQueryBuilder.php new file mode 100644 index 0000000000000..52840d677cc44 --- /dev/null +++ b/lib/public/DB/ORM/IQueryBuilder.php @@ -0,0 +1,558 @@ + +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace OCP\DB\ORM; + +use DateTimeInterface; +use OCP\IDBConnection; + +/** + * @since 25.0.0 + */ +interface IQueryBuilder { + /** + * Gets an ExpressionBuilder used for object-oriented construction of query expressions. + * This producer method is intended for convenient inline usage. Example: + * + * + * $qb = $em->createQueryBuilder(); + * $qb + * ->select('u') + * ->from('User', 'u') + * ->where($qb->expr()->eq('u.id', 1)); + * + * + * For more complex expression construction, consider storing the expression + * builder object in a local variable. + * + * @since 25.0.0 + */ + public function expr(): Query\IExpression; + + /** + * Sets a query parameter for the query being constructed. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = :user_id') + * ->setParameter('user_id', 1); + * + * + * @param string|int $key The parameter position or name. + * @param mixed $value The parameter value. + * @param string|int|null $type ParameterType::* or \Doctrine\DBAL\Types\Type::* constant + */ + public function setParameter($key, $value, $type = null): self; + + /** + * Sets a collection of query parameters for the query being constructed. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = :user_id1 OR u.id = :user_id2') + * ->setParameters([ + * 'user_id1' => 1, + * 'user_id2', 2 + * ]); + * + * + * @param array $parameters The query parameters to set. + * @since 25.0.0 + */ + public function setParameters(array $parameters): self; + + /** + * Gets all defined query parameters for the query being constructed. + * + * @return IParameter[] The currently defined query parameters. + * + * @since 25.0.0 + */ + public function getParameters(): array; + + /** + * Gets a (previously set) query parameter of the query being constructed. + * + * @param string|int $key The key (index or name) of the bound parameter. + * + * @return ?Parameter The value of the bound parameter. + * + * @since 25.0.0 + */ + public function getParameter($key): ?IParameter; + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param int|null $firstResult The first result to return. + * + * @since 25.0.0 + */ + public function setFirstResult(?int $firstResult): self + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. + * + * @return int|null The position of the first result. + */ + public function getFirstResult(): ?int; + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @param int|null $maxResults The maximum number of results to retrieve. + */ + public function setMaxResults(?int $maxResults): self; + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if {@link setMaxResults} was not applied to this query builder. + * + * @return int|null Maximum number of results. + */ + public function getMaxResults(): ?int; + + /** + * Specifies an item that is to be returned in the query result. + * Replaces any previously specified selections, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u', 'p') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p'); + * + * + * @param mixed $select The selection expressions. + */ + public function select($select = null): self; + + /** + * Adds a DISTINCT flag to this query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->distinct() + * ->from('User', 'u'); + * + */ + public function distinct(bool $flag = true): self; + + /** + * Adds an item that is to be returned in the query result. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->addSelect('p') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p'); + * + * + * @param mixed $select The selection expression. + */ + public function addSelect($select = null): self; + + /** + * Turns the query being built into a bulk delete query that ranges over + * a certain entity type. + * + * + * $qb = $em->createQueryBuilder() + * ->delete('User', 'u') + * ->where('u.id = :user_id') + * ->setParameter('user_id', 1); + * + * + * @param string|null $delete The class/type whose instances are subject to the deletion. + * @param string|null $alias The class/type alias used in the constructed query. + */ + public function delete(?string $delete = null, ?string $alias = null): self; + + /** + * Turns the query being built into a bulk update query that ranges over + * a certain entity type. + * + * + * $qb = $em->createQueryBuilder() + * ->update('User', 'u') + * ->set('u.password', '?1') + * ->where('u.id = ?2'); + * + * + * @param string|null $update The class/type whose instances are subject to the update. + * @param string|null $alias The class/type alias used in the constructed query. + */ + public function update(?string $update = null, ?string $alias = null): self; + + /** + * Creates and adds a query root corresponding to the entity identified by the given alias, + * forming a cartesian product with any existing query roots. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * + * + * @param string $from The class name. + * @param string $alias The alias of the class. + * @param string|null $indexBy The index for the from. + * + * @return $this + */ + public function from(string $from, string $alias, ?string $indexBy = null); + + /** + * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with + * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it + * setting an index by. + * + * + * $qb = $userRepository->createQueryBuilder('u') + * ->indexBy('u', 'u.id'); + * + * // Is equivalent to... + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u', 'u.id'); + * + * + * @param string $alias The root alias of the class. + * @param string $indexBy The index for the from. + * + * @throws Query\QueryException + */ + public function indexBy(string $alias, string $indexBy): self; + + /** + * Creates and adds a join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * + * @param string $join The relationship to join. + * @param string $alias The alias of the join. + * @param string|null $conditionType The condition type constant. Either ON or WITH. + * @param string|Expr\Comparison|Expr\Composite|null $condition The condition for the join. + * @param string|null $indexBy The index for the join. + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + */ + public function join(string $join, string $alias, ?string $conditionType = null, $condition = null, ?string $indexBy = null): self; + + /** + * Creates and adds a join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * [php] + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * @param string $join The relationship to join. + * @param string $alias The alias of the join. + * @param string|null $conditionType The condition type constant. Either ON or WITH. + * @param string|Expr\Comparison|Expr\Composite|null $condition The condition for the join. + * @param string|null $indexBy The index for the join. + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + */ + public function innerJoin(string $join, string $alias, ?string $conditionType = null, $condition = null, ?string $indexBy = null): self; + + /** + * Creates and adds a left join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * + * + * @param string $join The relationship to join. + * @param string $alias The alias of the join. + * @param string|null $conditionType The condition type constant. Either ON or WITH. + * @param string|Expr\Comparison|Expr\Composite|null $condition The condition for the join. + * @param string|null $indexBy The index for the join. + * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType + * + * @return $this + */ + public function leftJoin(string $join, string $alias, $conditionType = null, $condition = null, ?string $indexBy = null); + + /** + * Sets a new value for a field in a bulk update query. + * + * + * $qb = $em->createQueryBuilder() + * ->update('User', 'u') + * ->set('u.password', '?1') + * ->where('u.id = ?2'); + * + * + * @param string $key The key/field to set. + * @param mixed $value The value, expression, placeholder, etc. + */ + public function set(string $key, $value): self; + + /** + * Specifies one or more restrictions to the query result. + * Replaces any previously specified restrictions, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = ?'); + * + * // You can optionally programmatically build and/or expressions + * $qb = $em->createQueryBuilder(); + * + * $or = $qb->expr()->orX(); + * $or->add($qb->expr()->eq('u.id', 1)); + * $or->add($qb->expr()->eq('u.id', 2)); + * + * $qb->update('User', 'u') + * ->set('u.password', '?') + * ->where($or); + * + * + * @param mixed $predicates The restriction predicates. + * + * @return $this + */ + public function where($predicates) + { + if (! (func_num_args() === 1 && $predicates instanceof Expr\Composite)) { + $predicates = new Expr\Andx(func_get_args()); + } + + return $this->add('where', $predicates); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * conjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.username LIKE ?') + * ->andWhere('u.is_active = 1'); + * + * + * @see where() + * + * @param mixed $where The query restrictions. + * + * @return $this + */ + public function andWhere() + { + $args = func_get_args(); + $where = $this->getDQLPart('where'); + + if ($where instanceof Expr\Andx) { + $where->addMultiple($args); + } else { + array_unshift($args, $where); + $where = new Expr\Andx($args); + } + + return $this->add('where', $where); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * disjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->where('u.id = 1') + * ->orWhere('u.id = 2'); + * + * + * @see where() + * + * @param mixed $where The WHERE statement. + * + * @return $this + */ + public function orWhere() + { + $args = func_get_args(); + $where = $this->getDQLPart('where'); + + if ($where instanceof Expr\Orx) { + $where->addMultiple($args); + } else { + array_unshift($args, $where); + $where = new Expr\Orx($args); + } + + return $this->add('where', $where); + } + + /** + * Specifies a grouping over the results of the query. + * Replaces any previously specified groupings, if any. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->groupBy('u.id'); + * + * + * @param string $groupBy The grouping expression. + * + * @return $this + */ + public function groupBy($groupBy) + { + return $this->add('groupBy', new Expr\GroupBy(func_get_args())); + } + + /** + * Adds a grouping expression to the query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * ->groupBy('u.lastLogin') + * ->addGroupBy('u.createdAt'); + * + * + * @param string $groupBy The grouping expression. + * + * @return $this + */ + public function addGroupBy($groupBy) + { + return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true); + } + + /** + * Specifies a restriction over the groups of the query. + * Replaces any previous having restrictions, if any. + * + * @param mixed $having The restriction over the groups. + * + * @return $this + */ + public function having($having) + { + if (! (func_num_args() === 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) { + $having = new Expr\Andx(func_get_args()); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * conjunction with any existing having restrictions. + * + * @param mixed $having The restriction to append. + * + * @return $this + */ + public function andHaving($having) + { + $args = func_get_args(); + $having = $this->getDQLPart('having'); + + if ($having instanceof Expr\Andx) { + $having->addMultiple($args); + } else { + array_unshift($args, $having); + $having = new Expr\Andx($args); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * disjunction with any existing having restrictions. + * + * @param mixed $having The restriction to add. + * + * @return $this + */ + public function orHaving($having) + { + $args = func_get_args(); + $having = $this->getDQLPart('having'); + + if ($having instanceof Expr\Orx) { + $having->addMultiple($args); + } else { + array_unshift($args, $having); + $having = new Expr\Orx($args); + } + + return $this->add('having', $having); + } + + /** + * Specifies an ordering for the query results. + * Replaces any previously specified orderings, if any. + * + * @param string|Expr\OrderBy $sort The ordering expression. + * @param string|null $order The ordering direction. + * + * @return $this + */ + public function orderBy($sort, $order = null) + { + $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order); + + return $this->add('orderBy', $orderBy); + } + + /** + * Adds an ordering to the query results. + * + * @param string|Expr\OrderBy $sort The ordering expression. + * @param string|null $order The ordering direction. + * + * @return $this + */ + public function addOrderBy($sort, $order = null) + { + $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order); + + return $this->add('orderBy', $orderBy, true); + } +} diff --git a/lib/public/DB/ORM/LockMode.php b/lib/public/DB/ORM/LockMode.php new file mode 100644 index 0000000000000..322ff74b7d38d --- /dev/null +++ b/lib/public/DB/ORM/LockMode.php @@ -0,0 +1,8 @@ + +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace OCP\DB\ORM\Query; + +interface IExpression { + +} diff --git a/lib/public/IContainer.php b/lib/public/IContainer.php index 08634ad508fd9..aaa2cafeb67fb 100644 --- a/lib/public/IContainer.php +++ b/lib/public/IContainer.php @@ -40,7 +40,6 @@ * IContainer is the basic interface to be used for any internal dependency injection mechanism * * @since 6.0.0 - * @deprecated 20.0.0 use \Psr\Container\ContainerInterface */ interface IContainer extends ContainerInterface {