Compare commits

..

30 Commits

Author SHA1 Message Date
Carl Schwan 3caa1467b1 refactor: Improve log message
Co-authored-by: Josh <josh.t.richards@gmail.com>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2026-03-02 13:19:08 +01:00
Robin Appelman 2c2335b8b4 fix: improve logging around failed chunked object store uploads
Signed-off-by: Robin Appelman <robin@icewind.nl>
2026-02-27 14:50:49 +01:00
Kent Delante d6eade0119 Merge pull request #58582 from nextcloud/leftybournes/fix/files_external_delete_objects
fix: pass only object key to deleteObjects call
2026-02-27 11:28:19 +08:00
Kent Delante 8d1cb50048 fix: pass only object key to deleteObjects call
Some S3-compatible object storage hosts don't like the ETag being included in
the request and return a MalformedXML response. In the AWS API documentation,
only the object key is required so just pass that in.

Signed-off-by: Kent Delante <kent.delante@proton.me>
2026-02-27 10:54:23 +08:00
Nextcloud bot b4b328cf61 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-02-27 00:19:48 +00:00
Carl Schwan e47195a334 Merge pull request #58597 from nextcloud/bugfix/58594/files_sharing-disabled
fix(repair): Fix repair steps when files_sharing is disabled
2026-02-27 00:35:03 +01:00
github-actions[bot] 4d00f49757 Merge pull request #58556 from nextcloud/dependabot/npm_and_yarn/build/frontend-legacy/multi-5543462fab
chore(deps): Bump bn.js in /build/frontend-legacy
2026-02-26 21:58:10 +01:00
Andy Scherzinger 83d795dd18 Merge pull request #58595 from nextcloud/fix/db-occ-pending-migrations-typo
fix(db): pending migrations in `occ migrations:status`
2026-02-26 21:56:27 +01:00
Joas Schilling a6b9483a5f fix(repair): Fix repair steps when files_sharing is disabled
Signed-off-by: Joas Schilling <coding@schilljs.com>
2026-02-26 21:51:13 +01:00
Josh 636345bac8 fix(db): pending migrations in occ migrations:status
Fixes #58569

Signed-off-by: Josh <josh.t.richards@gmail.com>
2026-02-26 12:38:00 -05:00
Benjamin Gaussorgues 79d4953e64 Merge pull request #58535 from nextcloud/fix/do-not-send-headers-on-cli 2026-02-26 16:20:52 +01:00
Sebastian Krupinski ca8050b94e Merge pull request #58008 from nextcloud/feat/calendar-federation-readwrite
feat: calendar read and write federation
2026-02-26 09:01:25 -05:00
Carl Schwan 47b08a07d8 Merge pull request #58586 from nextcloud/carl/sharehelper-typing
refactor(typing): Correct typing of IShareHelper
2026-02-26 14:21:53 +01:00
Kate 77c070bc93 Merge pull request #58559 from nextcloud/fix/itypedquerybuilder/chained-calls 2026-02-26 13:44:40 +01:00
Kent Delante 09c9241b30 Merge pull request #58042 from nextcloud/feat/clear_password
feat(occ): allow admins to clear account passwords
2026-02-26 20:29:26 +08:00
Kent Delante 711bd2bc6d feat(occ): allow admins to clear account passwords
Signed-off-by: Kent Delante <kent.delante@proton.me>
2026-02-26 19:36:22 +08:00
Carl Schwan 2a81cba978 refactor(typing): Correct typing of IShareHelper
Signed-off-by: Carl Schwan <carlschwan@kde.org>
2026-02-26 12:22:44 +01:00
Andy Scherzinger 6df490942c Merge pull request #58525 from nextcloud/fix/fix-decryption-failure-false-positive
fix(encryption): Improve type strictness on decryption check
2026-02-26 12:10:01 +01:00
Andy Scherzinger 0b8e7bb4f0 Merge pull request #58205 from nextcloud/bug-show-configuration-options-for-again
fix: show configuration options for external storage backends
2026-02-26 11:56:41 +01:00
Salvatore Martire 7e264ba58e Merge pull request #58571 from nextcloud/fix/regional-languages
fix(L10N): stop stripping _ from language codes
2026-02-25 16:54:18 +01:00
SebastianKrupinski 64f319ab4e feat: calendar federation write
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
2026-02-25 10:35:27 -05:00
Salvatore Martire ec0ed788fa fix(L10N): stop stripping _ from language codes
Stripping the underscore breaks support for all languages like de_AT,
de_DE and so on...

Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>
2026-02-25 15:28:34 +01:00
Daniel Kesselberg 67d1fac6f6 fix: show configuration options for external storage backends
The occ files_external_backends command is supposed to list available backends along with their configuration options.

For the SMB backend, only the timeout option is currently shown, while options like host, share, root, and domain are missing. This makes configuring external storage via occ complicated.

Since the timeout option is marked as hidden but still shown, while the other options are not, the logic needs to be inverted so that all relevant configuration options are displayed correctly.

Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
2026-02-25 15:24:20 +01:00
Kate af98eed523 Merge pull request #58548 from nextcloud/artonge/feat/ai_pr_template 2026-02-25 11:52:12 +01:00
Louis Chmn bfac9e7023 feat: Add AI checkbox to pull request template
Request committers to be transparent with their usage of AI.

Signed-off-by: Louis <louis@chmn.me>
Signed-off-by: Louis Chmn <louis@chmn.me>
2026-02-25 10:41:38 +01:00
provokateurin 237d5156b6 fix(ITypedQueryBuilder): Add correct return type and add note about Psalm bug
Signed-off-by: provokateurin <kate@provokateurin.de>
2026-02-25 09:52:48 +01:00
provokateurin 40c39270c0 fix((ITypedQueryBuilder): Fix chained calls of non-select methods
Signed-off-by: provokateurin <kate@provokateurin.de>
2026-02-25 09:51:59 +01:00
dependabot[bot] f5b18dd7fd chore(deps): Bump bn.js in /build/frontend-legacy
Bumps  and [bn.js](https://github.com/indutny/bn.js). These dependencies needed to be updated together.

Updates `bn.js` from 5.2.2 to 5.2.3
- [Release notes](https://github.com/indutny/bn.js/releases)
- [Changelog](https://github.com/indutny/bn.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/indutny/bn.js/compare/v5.2.2...v5.2.3)

Updates `bn.js` from 4.12.2 to 4.12.3
- [Release notes](https://github.com/indutny/bn.js/releases)
- [Changelog](https://github.com/indutny/bn.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/indutny/bn.js/compare/v5.2.2...v5.2.3)

---
updated-dependencies:
- dependency-name: bn.js
  dependency-version: 5.2.3
  dependency-type: indirect
- dependency-name: bn.js
  dependency-version: 4.12.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-24 21:02:56 +00:00
Côme Chilliet f885d7292f fix(occ): Do not attempt to send headers on CLI
This avoids errors like 'Cannot modify header information - headers already sent',
 when using --debug-log with occ.

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2026-02-24 10:22:13 +01:00
Côme Chilliet e4244c5fc8 fix(encryption): Improve type strictness on decryption check
Otherwise decrypting a falsy value like '0' would be seen as a
 decryption failure.

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2026-02-23 14:49:03 +01:00
77 changed files with 3721 additions and 1397 deletions
+4
View File
@@ -23,3 +23,7 @@
- [ ] [Backports requested](https://github.com/nextcloud/backportbot/#usage) where applicable (ex: critical bugfixes)
- [ ] [Labels added](https://github.com/nextcloud/server/labels) where applicable (ex: bug/enhancement, `3. to review`, feature component)
- [ ] [Milestone added](https://github.com/nextcloud/server/milestones) for target branch/version (ex: 32.x for `stable32`)
## AI (if applicable)
- [ ] The content of this PR was partly or fully generated using AI
@@ -77,6 +77,7 @@ return array(
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarObject' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarObject.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => $baseDir . '/../lib/CalDAV/Federation/FederationSharingService.php',
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarFederationProtocolV1' => $baseDir . '/../lib/CalDAV/Federation/Protocol/CalendarFederationProtocolV1.php',
@@ -92,6 +92,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarObject.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederationSharingService.php',
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarFederationProtocolV1' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/Protocol/CalendarFederationProtocolV1.php',
+2 -4
View File
@@ -43,9 +43,9 @@ class CalendarProvider implements ICalendarProvider {
});
}
$additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos);
$iCalendars = [];
$additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos);
foreach ($calendarInfos as $calendarInfo) {
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
@@ -60,9 +60,7 @@ class CalendarProvider implements ICalendarProvider {
);
}
$additionalFederatedProps = $this->getAdditionalPropertiesForCalendars(
$federatedCalendarInfos,
);
$additionalFederatedProps = $this->getAdditionalPropertiesForCalendars($federatedCalendarInfos);
foreach ($federatedCalendarInfos as $calendarInfo) {
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
@@ -104,9 +104,10 @@ class CalendarFederationProvider implements ICloudFederationProvider {
);
}
// TODO: implement read-write sharing
// convert access to permissions
$permissions = match ($access) {
DavSharingBackend::ACCESS_READ => Constants::PERMISSION_READ,
DavSharingBackend::ACCESS_READ_WRITE => Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE,
default => throw new ProviderCouldNotAddShareException(
"Unsupported access value: $access",
'',
@@ -122,20 +123,27 @@ class CalendarFederationProvider implements ICloudFederationProvider {
$sharedWithPrincipal = 'principals/users/' . $share->getShareWith();
// Delete existing incoming federated share first
$this->federatedCalendarMapper->deleteByUri($sharedWithPrincipal, $calendarUri);
$calendar = $this->federatedCalendarMapper->findByUri($sharedWithPrincipal, $calendarUri);
$calendar = new FederatedCalendarEntity();
$calendar->setPrincipaluri($sharedWithPrincipal);
$calendar->setUri($calendarUri);
$calendar->setRemoteUrl($calendarUrl);
$calendar->setDisplayName($displayName);
$calendar->setColor($color);
$calendar->setToken($share->getShareSecret());
$calendar->setSharedBy($share->getSharedBy());
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
$calendar->setPermissions($permissions);
$calendar->setComponents($components);
$calendar = $this->federatedCalendarMapper->insert($calendar);
if ($calendar === null) {
$calendar = new FederatedCalendarEntity();
$calendar->setPrincipaluri($sharedWithPrincipal);
$calendar->setUri($calendarUri);
$calendar->setRemoteUrl($calendarUrl);
$calendar->setDisplayName($displayName);
$calendar->setColor($color);
$calendar->setToken($share->getShareSecret());
$calendar->setSharedBy($share->getSharedBy());
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
$calendar->setPermissions($permissions);
$calendar->setComponents($components);
$calendar = $this->federatedCalendarMapper->insert($calendar);
} else {
$calendar->setToken($share->getShareSecret());
$calendar->setPermissions($permissions);
$calendar->setComponents($components);
$this->federatedCalendarMapper->update($calendar);
}
$this->jobList->add(FederatedCalendarSyncJob::class, [
FederatedCalendarSyncJob::ARGUMENT_ID => $calendar->getId(),
@@ -10,29 +10,289 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV\Federation;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Calendar;
use OCP\IConfig;
use OCP\IL10N;
use Psr\Log\LoggerInterface;
use Sabre\CalDAV\Backend;
use OCP\Constants;
use Sabre\CalDAV\ICalendar;
use Sabre\CalDAV\Plugin;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\IMultiGet;
use Sabre\DAV\INode;
use Sabre\DAV\IProperties;
use Sabre\DAV\PropPatch;
class FederatedCalendar implements ICalendar, IProperties, IMultiGet {
private const CALENDAR_TYPE = CalDavBackend::CALENDAR_TYPE_FEDERATED;
private const DAV_PROPERTY_CALENDAR_LABEL = '{DAV:}displayname';
private const DAV_PROPERTY_CALENDAR_COLOR = '{http://apple.com/ns/ical/}calendar-color';
private string $principalUri;
private string $calendarUri;
private ?array $calendarACL = null;
private FederatedCalendarEntity $federationInfo;
class FederatedCalendar extends Calendar {
public function __construct(
Backend\BackendInterface $caldavBackend,
$calendarInfo,
IL10N $l10n,
IConfig $config,
LoggerInterface $logger,
private readonly FederatedCalendarMapper $federatedCalendarMapper,
private readonly FederatedCalendarSyncService $federatedCalendarService,
private readonly CalDavBackend $caldavBackend,
$calendarInfo,
) {
parent::__construct($caldavBackend, $calendarInfo, $l10n, $config, $logger);
$this->principalUri = $calendarInfo['principaluri'];
$this->calendarUri = $calendarInfo['uri'];
$this->federationInfo = $federatedCalendarMapper->findByUri($this->principalUri, $this->calendarUri);
}
public function delete() {
$this->federatedCalendarMapper->deleteById($this->getResourceId());
public function getResourceId(): int {
return $this->federationInfo->getId();
}
public function getName(): string {
return $this->federationInfo->getUri();
}
/**
* @param string $name Name of the file
*/
public function setName($name): void {
throw new MethodNotAllowed('Renaming federated calendars is not allowed');
}
protected function getCalendarType(): int {
return CalDavBackend::CALENDAR_TYPE_FEDERATED;
return self::CALENDAR_TYPE;
}
public function getPrincipalURI(): string {
return $this->federationInfo->getPrincipaluri();
}
public function getOwner(): ?string {
return $this->federationInfo->getSharedByPrincipal();
}
public function getGroup(): ?string {
return null;
}
/**
* @return array<array-key, mixed>
*/
public function getACL(): array {
if ($this->calendarACL !== null) {
return $this->calendarACL;
}
$permissions = $this->federationInfo->getPermissions();
// default permission
$acl = [
// read object permission
[
'privilege' => '{DAV:}read',
'principal' => $this->principalUri,
'protected' => true,
],
// read acl permission
[
'privilege' => '{DAV:}read-acl',
'principal' => $this->principalUri,
'protected' => true,
],
// write properties permission (calendar name, color)
[
'privilege' => '{DAV:}write-properties',
'principal' => $this->principalUri,
'protected' => true,
],
];
// create permission
if ($permissions & Constants::PERMISSION_CREATE) {
$acl[] = [
'privilege' => '{DAV:}bind',
'principal' => $this->principalUri,
'protected' => true,
];
}
// update permission
if ($permissions & Constants::PERMISSION_UPDATE) {
$acl[] = [
'privilege' => '{DAV:}write-content',
'principal' => $this->principalUri,
'protected' => true,
];
}
// delete permission
if ($permissions & Constants::PERMISSION_DELETE) {
$acl[] = [
'privilege' => '{DAV:}unbind',
'principal' => $this->principalUri,
'protected' => true,
];
}
// cache the calculated ACL for later use
$this->calendarACL = $acl;
return $acl;
}
public function setACL(array $acl): void {
throw new MethodNotAllowed('Changing ACLs on federated calendars is not allowed');
}
public function getSupportedPrivilegeSet(): ?array {
return null;
}
/**
* @return array<string, mixed> properties array, with property name as key
*/
public function getProperties($properties): array {
return [
self::DAV_PROPERTY_CALENDAR_LABEL => $this->federationInfo->getDisplayName(),
self::DAV_PROPERTY_CALENDAR_COLOR => $this->federationInfo->getColor(),
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(explode(',', $this->federationInfo->getComponents())),
];
}
public function propPatch(PropPatch $propPatch): void {
$mutations = $propPatch->getMutations();
if (count($mutations) > 0) {
// evaluate if name was changed
if (isset($mutations[self::DAV_PROPERTY_CALENDAR_LABEL])) {
$this->federationInfo->setDisplayName($mutations[self::DAV_PROPERTY_CALENDAR_LABEL]);
$propPatch->setResultCode(self::DAV_PROPERTY_CALENDAR_LABEL, 200);
}
// evaluate if color was changed
if (isset($mutations[self::DAV_PROPERTY_CALENDAR_COLOR])) {
$this->federationInfo->setColor($mutations[self::DAV_PROPERTY_CALENDAR_COLOR]);
$propPatch->setResultCode(self::DAV_PROPERTY_CALENDAR_COLOR, 200);
}
$this->federatedCalendarMapper->update($this->federationInfo);
}
}
public function getChildACL(): array {
return $this->getACL();
}
public function getLastModified(): ?int {
return $this->federationInfo->getLastSync();
}
public function delete(): void {
$this->federatedCalendarMapper->deleteById($this->getResourceId());
}
/**
* @param string $name Name of the file
*/
public function createDirectory($name): void {
throw new MethodNotAllowed('Creating nested collection is not allowed');
}
public function calendarQuery(array $filters): array {
$uris = $this->caldavBackend->calendarQuery($this->federationInfo->getId(), $filters, $this->getCalendarType());
return $uris;
}
/**
* @param string $name Name of the file
*/
public function getChild($name): INode {
$obj = $this->caldavBackend->getCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
if ($obj === null) {
throw new NotFound('Calendar object not found');
}
return new FederatedCalendarObject($this, $obj);
}
/**
* @return array<INode>
*/
public function getChildren(): array {
$objs = $this->caldavBackend->getCalendarObjects($this->federationInfo->getId(), $this->getCalendarType());
$children = [];
foreach ($objs as $obj) {
$children[] = new FederatedCalendarObject($this, $obj);
}
return $children;
}
/**
* @param array<string> $paths Names of the files
*
* @return array<INode>
*/
public function getMultipleChildren(array $paths): array {
$objs = $this->caldavBackend->getMultipleCalendarObjects($this->federationInfo->getId(), $paths, $this->getCalendarType());
$children = [];
foreach ($objs as $obj) {
$children[] = new FederatedCalendarObject($this, $obj);
}
return $children;
}
/**
* @param string $name Name of the file
*/
public function childExists($name): bool {
$obj = $this->caldavBackend->getCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
return $obj !== null;
}
/**
* @param string $name Name of the file
* @param resource|string $data Initial payload
*/
public function createFile($name, $data = null): string {
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Create on remote server first
$etag = $this->federatedCalendarService->createCalendarObject($this->federationInfo, $name, $data);
if (empty($etag)) {
throw new \Exception('Failed to create calendar object on remote server');
}
// Then store locally
return $this->caldavBackend->createCalendarObject($this->federationInfo->getId(), $name, $data, $this->getCalendarType());
}
/**
* @param string $name Name of the file
* @param resource|string $data Initial payload
*/
public function updateFile($name, $data): string {
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Update remote calendar first
$etag = $this->federatedCalendarService->updateCalendarObject($this->federationInfo, $name, $data);
if (empty($etag)) {
throw new \Exception('Failed to update calendar object on remote server');
}
// Then update locally
return $this->caldavBackend->updateCalendarObject($this->federationInfo->getId(), $name, $data, $this->getCalendarType());
}
public function deleteFile(string $name): void {
// Delete from remote server first
$this->federatedCalendarService->deleteCalendarObject($this->federationInfo, $name);
// Then delete locally
$this->caldavBackend->deleteCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
}
}
@@ -94,8 +94,8 @@ class FederatedCalendarEntity extends Entity {
'{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => $this->getSyncTokenForSabre(),
'{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => $this->getSupportedCalendarComponentSet(),
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->getSharedByPrincipal(),
// TODO: implement read-write sharing
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => 1
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => ($this->getPermissions() & \OCP\Constants::PERMISSION_UPDATE) === 0 ? 1 : 0,
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}permissions' => $this->getPermissions(),
];
}
}
@@ -9,34 +9,23 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV\Federation;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\IConfig;
use OCP\IL10N;
use OCP\L10N\IFactory as IL10NFactory;
use Psr\Log\LoggerInterface;
class FederatedCalendarFactory {
private readonly IL10N $l10n;
public function __construct(
private readonly CalDavBackend $caldavBackend,
private readonly IConfig $config,
private readonly LoggerInterface $logger,
private readonly FederatedCalendarMapper $federatedCalendarMapper,
IL10NFactory $l10nFactory,
private readonly FederatedCalendarSyncService $federatedCalendarService,
private readonly CalDavBackend $caldavBackend,
) {
$this->l10n = $l10nFactory->get(Application::APP_ID);
}
public function createFederatedCalendar(array $calendarInfo): FederatedCalendar {
return new FederatedCalendar(
$this->federatedCalendarMapper,
$this->federatedCalendarService,
$this->caldavBackend,
$calendarInfo,
$this->l10n,
$this->config,
$this->logger,
$this->federatedCalendarMapper,
);
}
}
@@ -51,8 +51,7 @@ class FederatedCalendarImpl implements ICalendar, ICalendarIsShared, ICalendarIs
}
public function getPermissions(): int {
// TODO: implement read-write sharing
return Constants::PERMISSION_READ;
return $this->calendarInfo['{http://owncloud.org/ns}permissions'] ?? Constants::PERMISSION_READ;
}
public function isDeleted(): bool {
@@ -64,7 +63,8 @@ class FederatedCalendarImpl implements ICalendar, ICalendarIsShared, ICalendarIs
}
public function isWritable(): bool {
return false;
$permissions = $this->getPermissions();
return ($permissions & Constants::PERMISSION_UPDATE) !== 0;
}
public function isEnabled(): bool {
@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\CalDAV\Federation;
use Sabre\CalDAV\ICalendarObject;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAVACL\IACL;
class FederatedCalendarObject implements ICalendarObject, IACL {
public function __construct(
protected FederatedCalendar $calendarObject,
protected $objectData,
) {
}
public function getName(): string {
return $this->objectData['uri'];
}
/**
* @param string $name Name of the file
*/
public function setName($name) {
throw new \Exception('Not implemented');
}
public function get(): string {
return $this->objectData['calendardata'];
}
/**
* @param resource|string $data contents of the file
*/
public function put($data): string {
$etag = $this->calendarObject->updateFile($this->objectData['uri'], $data);
$this->objectData['calendardata'] = $data;
$this->objectData['etag'] = $etag;
return $etag;
}
public function delete(): void {
$this->calendarObject->deleteFile($this->objectData['uri']);
}
public function getContentType(): ?string {
$mime = 'text/calendar; charset=utf-8';
if (isset($this->objectData['component']) && $this->objectData['component']) {
$mime .= '; component=' . $this->objectData['component'];
}
return $mime;
}
public function getETag(): string {
if (isset($this->objectData['etag'])) {
return $this->objectData['etag'];
} else {
return '"' . md5($this->get()) . '"';
}
}
public function getLastModified(): int {
return $this->objectData['lastmodified'];
}
public function getSize(): int {
if (isset($this->objectData['size'])) {
return $this->objectData['size'];
} else {
return strlen($this->get());
}
}
public function getOwner(): ?string {
return $this->calendarObject->getPrincipalURI();
}
public function getGroup(): ?string {
return null;
}
/**
* @return array<array-key, mixed>
*/
public function getACL(): array {
return $this->calendarObject->getACL();
}
public function setACL(array $acl): void {
throw new MethodNotAllowed('Changing ACLs on federated events is not allowed');
}
public function getSupportedPrivilegeSet(): ?array {
return null;
}
}
@@ -9,20 +9,52 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV\Federation;
use OCA\DAV\CalDAV\SyncService as CalDavSyncService;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\Service\ASyncService;
use OCP\AppFramework\Db\TTransactional;
use OCP\AppFramework\Http;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IDBConnection;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Log\LoggerInterface;
class FederatedCalendarSyncService {
class FederatedCalendarSyncService extends ASyncService {
use TTransactional;
private const SYNC_TOKEN_PREFIX = 'http://sabre.io/ns/sync/';
public function __construct(
IClientService $clientService,
IConfig $config,
private readonly FederatedCalendarMapper $federatedCalendarMapper,
private readonly LoggerInterface $logger,
private readonly CalDavSyncService $syncService,
private readonly CalDavBackend $backend,
private readonly IDBConnection $dbConnection,
private readonly ICloudIdManager $cloudIdManager,
) {
parent::__construct($clientService, $config);
}
/**
* Extract and encode credentials from a federated calendar entity.
*
* @return array{username: string, remoteUrl: string, token: string}
*/
private function getCredentials(FederatedCalendarEntity $calendar): array {
[,, $sharedWith] = explode('/', $calendar->getPrincipaluri());
$calDavUser = $this->cloudIdManager->getCloudId($sharedWith, null)->getId();
// Need to encode the cloud id as it might contain a colon which is not allowed in basic
// auth according to RFC 7617
$calDavUser = base64_encode($calDavUser);
return [
'username' => $calDavUser,
'remoteUrl' => $calendar->getRemoteUrl(),
'token' => $calendar->getToken(),
];
}
/**
@@ -31,29 +63,77 @@ class FederatedCalendarSyncService {
* @throws ClientExceptionInterface If syncing the calendar fails.
*/
public function syncOne(FederatedCalendarEntity $calendar): int {
[,, $sharedWith] = explode('/', $calendar->getPrincipaluri());
$calDavUser = $this->cloudIdManager->getCloudId($sharedWith, null)->getId();
$remoteUrl = $calendar->getRemoteUrl();
$credentials = $this->getCredentials($calendar);
$syncToken = $calendar->getSyncTokenForSabre();
// Need to encode the cloud id as it might contain a colon which is not allowed in basic
// auth according to RFC 7617
$calDavUser = base64_encode($calDavUser);
try {
$response = $this->requestSyncReport(
$credentials['remoteUrl'],
$credentials['username'],
$credentials['token'],
$syncToken,
);
} catch (ClientExceptionInterface $ex) {
if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
// Remote server revoked access to the calendar => remove it
$this->federatedCalendarMapper->delete($calendar);
$this->logger->warning("Authorization failed, remove federated calendar: {$credentials['remoteUrl']}", [
'app' => 'dav',
]);
return 0;
}
$this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]);
throw $ex;
}
$syncResponse = $this->syncService->syncRemoteCalendar(
$remoteUrl,
$calDavUser,
$calendar->getToken(),
$syncToken,
$calendar,
);
// Process changes from remote
$downloadedEvents = 0;
foreach ($response['response'] as $resource => $status) {
$objectUri = basename($resource);
if (isset($status[200])) {
// Object created or updated
$absoluteUrl = $this->prepareUri($credentials['remoteUrl'], $resource);
$calendarData = $this->download($absoluteUrl, $credentials['username'], $credentials['token']);
$this->atomic(function () use ($calendar, $objectUri, $calendarData): void {
$existingObject = $this->backend->getCalendarObject(
$calendar->getId(),
$objectUri,
CalDavBackend::CALENDAR_TYPE_FEDERATED
);
if (!$existingObject) {
$this->backend->createCalendarObject(
$calendar->getId(),
$objectUri,
$calendarData,
CalDavBackend::CALENDAR_TYPE_FEDERATED
);
} else {
$this->backend->updateCalendarObject(
$calendar->getId(),
$objectUri,
$calendarData,
CalDavBackend::CALENDAR_TYPE_FEDERATED
);
}
}, $this->dbConnection);
$downloadedEvents++;
} else {
// Object deleted
$this->backend->deleteCalendarObject(
$calendar->getId(),
$objectUri,
CalDavBackend::CALENDAR_TYPE_FEDERATED,
true
);
}
}
$newSyncToken = $syncResponse->getSyncToken();
$newSyncToken = $response['token'];
// Check sync token format and extract the actual sync token integer
$matches = [];
if (!preg_match('/^http:\/\/sabre\.io\/ns\/sync\/([0-9]+)$/', $newSyncToken, $matches)) {
$this->logger->error("Failed to sync federated calendar at $remoteUrl: New sync token has unexpected format: $newSyncToken", [
$this->logger->error("Failed to sync federated calendar at {$credentials['remoteUrl']}: New sync token has unexpected format: $newSyncToken", [
'calendar' => $calendar->toCalendarInfo(),
'newSyncToken' => $newSyncToken,
]);
@@ -67,10 +147,58 @@ class FederatedCalendarSyncService {
$newSyncToken,
);
} else {
$this->logger->debug("Sync Token for $remoteUrl unchanged from previous sync");
$this->logger->debug("Sync Token for {$credentials['remoteUrl']} unchanged from previous sync");
$this->federatedCalendarMapper->updateSyncTime($calendar->getId());
}
return $syncResponse->getDownloadedEvents();
return $downloadedEvents;
}
/**
* Create a calendar object on the remote server.
*
* @throws ClientExceptionInterface If the remote request fails.
*/
public function createCalendarObject(FederatedCalendarEntity $calendar, string $name, string $data): string {
$credentials = $this->getCredentials($calendar);
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
return $this->requestPut(
$objectUrl,
$credentials['username'],
$credentials['token'],
$data,
'text/calendar; charset=utf-8'
);
}
/**
* Update a calendar object on the remote server.
*
* @throws ClientExceptionInterface If the remote request fails.
*/
public function updateCalendarObject(FederatedCalendarEntity $calendar, string $name, string $data): string {
$credentials = $this->getCredentials($calendar);
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
return $this->requestPut(
$objectUrl,
$credentials['username'],
$credentials['token'],
$data,
'text/calendar; charset=utf-8'
);
}
/**
* Delete a calendar object on the remote server.
*
* @throws ClientExceptionInterface If the remote request fails.
*/
public function deleteCalendarObject(FederatedCalendarEntity $calendar, string $name): void {
$credentials = $this->getCredentials($calendar);
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
$this->requestDelete($objectUrl, $credentials['username'], $credentials['token']);
}
}
+9 -1
View File
@@ -12,6 +12,7 @@ use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\CalendarHome;
use OCA\DAV\CalDAV\CalendarObject;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\CalDAV\Federation\FederatedCalendar;
use OCA\DAV\CalDAV\TipBroker;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
@@ -173,8 +174,15 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
return;
}
/** @var Calendar $calendarNode */
/** @var Calendar&ICalendar $calendarNode */
$calendarNode = $this->server->tree->getNodeForPath($calendarPath);
// abort if calendar is federated
if ($calendarNode instanceof FederatedCalendar) {
$this->logger->debug('Not processing scheduling for federated calendar at path: ' . $calendarPath);
return;
}
// extract addresses for owner
$addresses = $this->getAddressesForPrincipal($calendarNode->getOwner());
// determine if request is from a sharee
+1 -1
View File
@@ -1020,7 +1020,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
'synctoken' => $query->createNamedParameter($syncToken),
'addressbookid' => $query->createNamedParameter($addressBookId),
'operation' => $query->createNamedParameter($operation),
'created_at' => $query->createNamedParameter(time()),
'created_at' => time(),
])
->executeStatement();
@@ -164,6 +164,7 @@ class SharingMapper {
->andWhere($query->expr()->eq(
'type',
$query->createNamedParameter($resourceType, IQueryBuilder::PARAM_STR)),
IQueryBuilder::PARAM_STR,
)
->executeQuery();
+66
View File
@@ -191,4 +191,70 @@ abstract class ASyncService {
rtrim($responseUri, '/'),
);
}
/**
* Push data to the remote server via HTTP PUT.
* Used for creating or updating CalDAV/CardDAV objects.
*
* @param string $url The absolute URL to PUT to
* @param string $username The username for authentication
* @param string $token The authentication token/password
* @param string $data The data to upload
* @param string $contentType The Content-Type header (e.g., 'text/calendar' or 'text/vcard')
*
* @return string The ETag returned by the server
*/
protected function requestPut(
string $url,
string $username,
string $token,
string $data,
string $contentType = 'text/calendar; charset=utf-8',
): string {
$client = $this->getClient();
$options = [
'auth' => [$username, $token],
'body' => $data,
'headers' => [
'Content-Type' => $contentType,
],
'verify' => !$this->config->getSystemValue(
'sharing.federation.allowSelfSignedCertificates',
false,
),
];
$response = $client->put($url, $options);
// Extract and return the ETag from the response
$etag = $response->getHeader('ETag');
return $etag;
}
/**
* Delete a resource from the remote server via HTTP DELETE.
* Used for deleting CalDAV/CardDAV objects.
*
* @param string $url The absolute URL to DELETE
* @param string $username The username for authentication
* @param string $token The authentication token/password
*/
protected function requestDelete(
string $url,
string $username,
string $token,
): void {
$client = $this->getClient();
$options = [
'auth' => [$username, $token],
'verify' => !$this->config->getSystemValue(
'sharing.federation.allowSelfSignedCertificates',
false,
),
];
$client->delete($url, $options);
}
}
@@ -11,7 +11,6 @@ namespace OCA\DAV\Tests\unit\BackgroundJob;
use OCA\DAV\BackgroundJob\CleanupInvitationTokenJob;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\QueryBuilder\IExpressionBuilder;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use PHPUnit\Framework\MockObject\MockObject;
@@ -47,12 +46,9 @@ class CleanupInvitationTokenJobTest extends TestCase {
->willReturn($queryBuilder);
$queryBuilder->method('expr')
->willReturn($expr);
$parameter = $this->createMock(IParameter::class);
$parameter->method('__toString')
->willReturn('namedParameter1337');
$queryBuilder->method('createNamedParameter')
->willReturnMap([
[1337, \PDO::PARAM_STR, null, $parameter],
[1337, \PDO::PARAM_STR, null, 'namedParameter1337']
]);
$function = 'function1337';
@@ -92,11 +92,12 @@ class CalendarFederationProviderTest extends TestCase {
->willReturn(true);
$this->federatedCalendarMapper->expects(self::once())
->method('deleteByUri')
->method('findByUri')
->with(
'principals/users/sharee1',
'ae4b8ab904076fff2b955ea21b1a0d92',
);
)
->willReturn(null);
$this->federatedCalendarMapper->expects(self::once())
->method('insert')
@@ -123,6 +124,68 @@ class CalendarFederationProviderTest extends TestCase {
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
}
public function testShareReceivedWithExistingCalendar(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
->willReturn('user');
$share->method('getProtocol')
->willReturn([
'version' => 'v1',
'url' => 'https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1',
'displayName' => 'Calendar 1',
'color' => '#ff0000',
'access' => 3,
'components' => 'VEVENT,VTODO',
]);
$share->method('getShareWith')
->willReturn('sharee1');
$share->method('getShareSecret')
->willReturn('new-token');
$share->method('getSharedBy')
->willReturn('user1@nextcloud.remote');
$share->method('getSharedByDisplayName')
->willReturn('User 1');
$this->calendarFederationConfig->expects(self::once())
->method('isFederationEnabled')
->willReturn(true);
$existingCalendar = new FederatedCalendarEntity();
$existingCalendar->setId(10);
$existingCalendar->setPrincipaluri('principals/users/sharee1');
$existingCalendar->setUri('ae4b8ab904076fff2b955ea21b1a0d92');
$existingCalendar->setRemoteUrl('https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1');
$existingCalendar->setToken('old-token');
$existingCalendar->setPermissions(1);
$existingCalendar->setComponents('VEVENT');
$this->federatedCalendarMapper->expects(self::once())
->method('findByUri')
->with(
'principals/users/sharee1',
'ae4b8ab904076fff2b955ea21b1a0d92',
)
->willReturn($existingCalendar);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->federatedCalendarMapper->expects(self::once())
->method('update')
->willReturnCallback(function (FederatedCalendarEntity $calendar) {
$this->assertEquals('new-token', $calendar->getToken());
$this->assertEquals(1, $calendar->getPermissions());
$this->assertEquals('VEVENT,VTODO', $calendar->getComponents());
return $calendar;
});
$this->jobList->expects(self::once())
->method('add')
->with(FederatedCalendarSyncJob::class, ['id' => 10]);
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
}
public function testShareReceivedWithInvalidProtocolVersion(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
@@ -270,7 +333,7 @@ class CalendarFederationProviderTest extends TestCase {
$this->calendarFederationProvider->shareReceived($share);
}
public function testShareReceivedWithUnsupportedAccess(): void {
public function testShareReceivedWithReadWriteAccess(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
->willReturn('user');
@@ -296,6 +359,65 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::once())
->method('findByUri')
->with(
'principals/users/sharee1',
'ae4b8ab904076fff2b955ea21b1a0d92',
)
->willReturn(null);
$this->federatedCalendarMapper->expects(self::once())
->method('insert')
->willReturnCallback(function (FederatedCalendarEntity $calendar) {
$this->assertEquals('principals/users/sharee1', $calendar->getPrincipaluri());
$this->assertEquals('ae4b8ab904076fff2b955ea21b1a0d92', $calendar->getUri());
$this->assertEquals('https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1', $calendar->getRemoteUrl());
$this->assertEquals('Calendar 1', $calendar->getDisplayName());
$this->assertEquals('#ff0000', $calendar->getColor());
$this->assertEquals('token', $calendar->getToken());
$this->assertEquals('user1@nextcloud.remote', $calendar->getSharedBy());
$this->assertEquals('User 1', $calendar->getSharedByDisplayName());
$this->assertEquals(15, $calendar->getPermissions()); // READ | CREATE | UPDATE | DELETE
$this->assertEquals('VEVENT,VTODO', $calendar->getComponents());
$calendar->setId(10);
return $calendar;
});
$this->jobList->expects(self::once())
->method('add')
->with(FederatedCalendarSyncJob::class, ['id' => 10]);
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
}
public function testShareReceivedWithUnsupportedAccess(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
->willReturn('user');
$share->method('getProtocol')
->willReturn([
'version' => 'v1',
'url' => 'https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1',
'displayName' => 'Calendar 1',
'color' => '#ff0000',
'access' => 999, // Invalid access value
'components' => 'VEVENT,VTODO',
]);
$share->method('getShareWith')
->willReturn('sharee1');
$share->method('getShareSecret')
->willReturn('token');
$share->method('getSharedBy')
->willReturn('user1@nextcloud.remote');
$share->method('getSharedByDisplayName')
->willReturn('User 1');
$this->calendarFederationConfig->expects(self::once())
->method('isFederationEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->jobList->expects(self::never())
@@ -9,13 +9,17 @@ declare(strict_types=1);
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
use OCA\DAV\CalDAV\SyncService as CalDavSyncService;
use OCA\DAV\CalDAV\SyncServiceResult;
use OCP\Federation\ICloudId;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
use OCP\IConfig;
use OCP\IDBConnection;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
@@ -26,21 +30,30 @@ class FederatedCalendarSyncServiceTest extends TestCase {
private FederatedCalendarMapper&MockObject $federatedCalendarMapper;
private LoggerInterface&MockObject $logger;
private CalDavSyncService&MockObject $calDavSyncService;
private CalDavBackend&MockObject $backend;
private IDBConnection&MockObject $dbConnection;
private ICloudIdManager&MockObject $cloudIdManager;
private IClientService&MockObject $clientService;
private IConfig&MockObject $config;
protected function setUp(): void {
parent::setUp();
$this->federatedCalendarMapper = $this->createMock(FederatedCalendarMapper::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->calDavSyncService = $this->createMock(CalDavSyncService::class);
$this->backend = $this->createMock(CalDavBackend::class);
$this->dbConnection = $this->createMock(IDBConnection::class);
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
$this->clientService = $this->createMock(IClientService::class);
$this->config = $this->createMock(IConfig::class);
$this->federatedCalendarSyncService = new FederatedCalendarSyncService(
$this->clientService,
$this->config,
$this->federatedCalendarMapper,
$this->logger,
$this->calDavSyncService,
$this->backend,
$this->dbConnection,
$this->cloudIdManager,
);
}
@@ -61,16 +74,24 @@ class FederatedCalendarSyncServiceTest extends TestCase {
->with('user1')
->willReturn($cloudId);
$this->calDavSyncService->expects(self::once())
->method('syncRemoteCalendar')
->with(
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
'token',
'http://sabre.io/ns/sync/100',
$calendar,
)
->willReturn(new SyncServiceResult('http://sabre.io/ns/sync/101', 10));
// Mock HTTP client for sync report
$client = $this->createMock(IClient::class);
$response = $this->createMock(IResponse::class);
$response->method('getBody')
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>http://sabre.io/ns/sync/101</d:sync-token></d:multistatus>');
$client->expects(self::once())
->method('request')
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
->willReturn($response);
$this->clientService->method('newClient')
->willReturn($client);
$this->config->method('getSystemValueInt')
->willReturn(30);
$this->config->method('getSystemValue')
->willReturn(false);
$this->federatedCalendarMapper->expects(self::once())
->method('updateSyncTokenAndTime')
@@ -78,7 +99,7 @@ class FederatedCalendarSyncServiceTest extends TestCase {
$this->federatedCalendarMapper->expects(self::never())
->method('updateSyncTime');
$this->assertEquals(10, $this->federatedCalendarSyncService->syncOne($calendar));
$this->assertEquals(0, $this->federatedCalendarSyncService->syncOne($calendar));
}
public function testSyncOneUnchanged(): void {
@@ -97,16 +118,24 @@ class FederatedCalendarSyncServiceTest extends TestCase {
->with('user1')
->willReturn($cloudId);
$this->calDavSyncService->expects(self::once())
->method('syncRemoteCalendar')
->with(
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
'token',
'http://sabre.io/ns/sync/100',
$calendar,
)
->willReturn(new SyncServiceResult('http://sabre.io/ns/sync/100', 0));
// Mock HTTP client for sync report
$client = $this->createMock(IClient::class);
$response = $this->createMock(IResponse::class);
$response->method('getBody')
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>http://sabre.io/ns/sync/100</d:sync-token></d:multistatus>');
$client->expects(self::once())
->method('request')
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
->willReturn($response);
$this->clientService->method('newClient')
->willReturn($client);
$this->config->method('getSystemValueInt')
->willReturn(30);
$this->config->method('getSystemValue')
->willReturn(false);
$this->federatedCalendarMapper->expects(self::never())
->method('updateSyncTokenAndTime');
@@ -143,16 +172,24 @@ class FederatedCalendarSyncServiceTest extends TestCase {
->with('user1')
->willReturn($cloudId);
$this->calDavSyncService->expects(self::once())
->method('syncRemoteCalendar')
->with(
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
'token',
'http://sabre.io/ns/sync/100',
$calendar,
)
->willReturn(new SyncServiceResult($syncToken, 10));
// Mock HTTP client for sync report with unexpected token format
$client = $this->createMock(IClient::class);
$response = $this->createMock(IResponse::class);
$response->method('getBody')
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>' . $syncToken . '</d:sync-token></d:multistatus>');
$client->expects(self::once())
->method('request')
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
->willReturn($response);
$this->clientService->method('newClient')
->willReturn($client);
$this->config->method('getSystemValueInt')
->willReturn(30);
$this->config->method('getSystemValue')
->willReturn(false);
$this->federatedCalendarMapper->expects(self::never())
->method('updateSyncTokenAndTime');
@@ -0,0 +1,404 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Federation\FederatedCalendar;
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
use OCA\DAV\CalDAV\Federation\FederatedCalendarObject;
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
use OCP\Constants;
use PHPUnit\Framework\MockObject\MockObject;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\PropPatch;
use Test\TestCase;
class FederatedCalendarTest extends TestCase {
private FederatedCalendar $federatedCalendar;
private FederatedCalendarMapper&MockObject $federatedCalendarMapper;
private FederatedCalendarSyncService&MockObject $federatedCalendarService;
private CalDavBackend&MockObject $caldavBackend;
private FederatedCalendarEntity $federationInfo;
protected function setUp(): void {
parent::setUp();
$this->federatedCalendarMapper = $this->createMock(FederatedCalendarMapper::class);
$this->federatedCalendarService = $this->createMock(FederatedCalendarSyncService::class);
$this->caldavBackend = $this->createMock(CalDavBackend::class);
$this->federationInfo = new FederatedCalendarEntity();
$this->federationInfo->setId(10);
$this->federationInfo->setPrincipaluri('principals/users/user1');
$this->federationInfo->setUri('calendar-uri');
$this->federationInfo->setDisplayName('Federated Calendar');
$this->federationInfo->setColor('#ff0000');
$this->federationInfo->setSharedBy('user2@nextcloud.remote');
$this->federationInfo->setSharedByDisplayName('User 2');
$this->federationInfo->setPermissions(Constants::PERMISSION_READ);
$this->federationInfo->setLastSync(1234567890);
$this->federatedCalendarMapper->method('findByUri')
->with('principals/users/user1', 'calendar-uri')
->willReturn($this->federationInfo);
$calendarInfo = [
'principaluri' => 'principals/users/user1',
'id' => 10,
'uri' => 'calendar-uri',
];
$this->federatedCalendar = new FederatedCalendar(
$this->federatedCalendarMapper,
$this->federatedCalendarService,
$this->caldavBackend,
$calendarInfo,
);
}
public function testGetResourceId(): void {
$this->assertEquals(10, $this->federatedCalendar->getResourceId());
}
public function testGetName(): void {
$this->assertEquals('calendar-uri', $this->federatedCalendar->getName());
}
public function testSetName(): void {
$this->expectException(MethodNotAllowed::class);
$this->expectExceptionMessage('Renaming federated calendars is not allowed');
$this->federatedCalendar->setName('new-name');
}
public function testGetPrincipalURI(): void {
$this->assertEquals('principals/users/user1', $this->federatedCalendar->getPrincipalURI());
}
public function testGetOwner(): void {
$expected = 'principals/remote-users/' . base64_encode('user2@nextcloud.remote');
$this->assertEquals($expected, $this->federatedCalendar->getOwner());
}
public function testGetGroup(): void {
$this->assertNull($this->federatedCalendar->getGroup());
}
public function testGetACLWithReadOnlyPermissions(): void {
$this->federationInfo->setPermissions(Constants::PERMISSION_READ);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(3, $acl);
// Check basic read permissions
$this->assertEquals('{DAV:}read', $acl[0]['privilege']);
$this->assertTrue($acl[0]['protected']);
$this->assertEquals('{DAV:}read-acl', $acl[1]['privilege']);
$this->assertTrue($acl[1]['protected']);
$this->assertEquals('{DAV:}write-properties', $acl[2]['privilege']);
$this->assertTrue($acl[2]['protected']);
}
public function testGetACLWithCreatePermission(): void {
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_CREATE);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(4, $acl);
// Check that create permission is added
$privileges = array_column($acl, 'privilege');
$this->assertContains('{DAV:}bind', $privileges);
}
public function testGetACLWithUpdatePermission(): void {
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(4, $acl);
// Check that update permission is added (write-content, not write-properties which is already in base ACL)
$privileges = array_column($acl, 'privilege');
$this->assertContains('{DAV:}write-content', $privileges);
}
public function testGetACLWithDeletePermission(): void {
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_DELETE);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(4, $acl);
// Check that delete permission is added
$privileges = array_column($acl, 'privilege');
$this->assertContains('{DAV:}unbind', $privileges);
}
public function testGetACLWithAllPermissions(): void {
$this->federationInfo->setPermissions(
Constants::PERMISSION_READ
| Constants::PERMISSION_CREATE
| Constants::PERMISSION_UPDATE
| Constants::PERMISSION_DELETE
);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(6, $acl);
$privileges = array_column($acl, 'privilege');
$this->assertContains('{DAV:}read', $privileges);
$this->assertContains('{DAV:}bind', $privileges);
$this->assertContains('{DAV:}write-content', $privileges);
$this->assertContains('{DAV:}write-properties', $privileges);
$this->assertContains('{DAV:}unbind', $privileges);
}
public function testSetACL(): void {
$this->expectException(MethodNotAllowed::class);
$this->expectExceptionMessage('Changing ACLs on federated calendars is not allowed');
$this->federatedCalendar->setACL([]);
}
public function testGetSupportedPrivilegeSet(): void {
$this->assertNull($this->federatedCalendar->getSupportedPrivilegeSet());
}
public function testGetProperties(): void {
$properties = $this->federatedCalendar->getProperties([
'{DAV:}displayname',
'{http://apple.com/ns/ical/}calendar-color',
]);
$this->assertEquals('Federated Calendar', $properties['{DAV:}displayname']);
$this->assertEquals('#ff0000', $properties['{http://apple.com/ns/ical/}calendar-color']);
}
public function testPropPatchWithDisplayName(): void {
$propPatch = $this->createMock(PropPatch::class);
$propPatch->method('getMutations')
->willReturn([
'{DAV:}displayname' => 'New Calendar Name',
]);
$this->federatedCalendarMapper->expects(self::once())
->method('update')
->willReturnCallback(function (FederatedCalendarEntity $entity) {
$this->assertEquals('New Calendar Name', $entity->getDisplayName());
return $entity;
});
$propPatch->expects(self::once())
->method('setResultCode')
->with('{DAV:}displayname', 200);
$this->federatedCalendar->propPatch($propPatch);
}
public function testPropPatchWithColor(): void {
$propPatch = $this->createMock(PropPatch::class);
$propPatch->method('getMutations')
->willReturn([
'{http://apple.com/ns/ical/}calendar-color' => '#00ff00',
]);
$this->federatedCalendarMapper->expects(self::once())
->method('update')
->willReturnCallback(function (FederatedCalendarEntity $entity) {
$this->assertEquals('#00ff00', $entity->getColor());
return $entity;
});
$propPatch->expects(self::once())
->method('setResultCode')
->with('{http://apple.com/ns/ical/}calendar-color', 200);
$this->federatedCalendar->propPatch($propPatch);
}
public function testPropPatchWithNoMutations(): void {
$propPatch = $this->createMock(PropPatch::class);
$propPatch->method('getMutations')
->willReturn([]);
$this->federatedCalendarMapper->expects(self::never())
->method('update');
$propPatch->expects(self::never())
->method('handle');
$this->federatedCalendar->propPatch($propPatch);
}
public function testGetChildACL(): void {
$this->assertEquals($this->federatedCalendar->getACL(), $this->federatedCalendar->getChildACL());
}
public function testGetLastModified(): void {
$this->assertEquals(1234567890, $this->federatedCalendar->getLastModified());
}
public function testDelete(): void {
$this->federatedCalendarMapper->expects(self::once())
->method('deleteById')
->with(10);
$this->federatedCalendar->delete();
}
public function testCreateDirectory(): void {
$this->expectException(MethodNotAllowed::class);
$this->expectExceptionMessage('Creating nested collection is not allowed');
$this->federatedCalendar->createDirectory('test');
}
public function testCalendarQuery(): void {
$filters = ['comp-filter' => ['name' => 'VEVENT']];
$expectedUris = ['event1.ics', 'event2.ics'];
$this->caldavBackend->expects(self::once())
->method('calendarQuery')
->with(10, $filters, 2) // 2 is CALENDAR_TYPE_FEDERATED
->willReturn($expectedUris);
$result = $this->federatedCalendar->calendarQuery($filters);
$this->assertEquals($expectedUris, $result);
}
public function testGetChild(): void {
$objectData = [
'id' => 1,
'uri' => 'event1.ics',
'calendardata' => 'BEGIN:VCALENDAR...',
];
$this->caldavBackend->expects(self::once())
->method('getCalendarObject')
->with(10, 'event1.ics', 2) // 2 is CALENDAR_TYPE_FEDERATED
->willReturn($objectData);
$child = $this->federatedCalendar->getChild('event1.ics');
$this->assertInstanceOf(FederatedCalendarObject::class, $child);
}
public function testGetChildNotFound(): void {
$this->caldavBackend->expects(self::once())
->method('getCalendarObject')
->with(10, 'nonexistent.ics', 2)
->willReturn(null);
$this->expectException(NotFound::class);
$this->federatedCalendar->getChild('nonexistent.ics');
}
public function testGetChildren(): void {
$objects = [
['id' => 1, 'uri' => 'event1.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
['id' => 2, 'uri' => 'event2.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
];
$this->caldavBackend->expects(self::once())
->method('getCalendarObjects')
->with(10, 2) // 2 is CALENDAR_TYPE_FEDERATED
->willReturn($objects);
$children = $this->federatedCalendar->getChildren();
$this->assertCount(2, $children);
$this->assertInstanceOf(FederatedCalendarObject::class, $children[0]);
$this->assertInstanceOf(FederatedCalendarObject::class, $children[1]);
}
public function testGetMultipleChildren(): void {
$paths = ['event1.ics', 'event2.ics'];
$objects = [
['id' => 1, 'uri' => 'event1.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
['id' => 2, 'uri' => 'event2.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
];
$this->caldavBackend->expects(self::once())
->method('getMultipleCalendarObjects')
->with(10, $paths, 2) // 2 is CALENDAR_TYPE_FEDERATED
->willReturn($objects);
$children = $this->federatedCalendar->getMultipleChildren($paths);
$this->assertCount(2, $children);
$this->assertInstanceOf(FederatedCalendarObject::class, $children[0]);
$this->assertInstanceOf(FederatedCalendarObject::class, $children[1]);
}
public function testChildExists(): void {
$this->caldavBackend->expects(self::once())
->method('getCalendarObject')
->with(10, 'event1.ics', 2)
->willReturn(['id' => 1, 'uri' => 'event1.ics']);
$result = $this->federatedCalendar->childExists('event1.ics');
$this->assertTrue($result);
}
public function testChildNotExists(): void {
$this->caldavBackend->expects(self::once())
->method('getCalendarObject')
->with(10, 'nonexistent.ics', 2)
->willReturn(null);
$result = $this->federatedCalendar->childExists('nonexistent.ics');
$this->assertFalse($result);
}
public function testCreateFile(): void {
$calendarData = 'BEGIN:VCALENDAR...END:VCALENDAR';
$remoteEtag = '"remote-etag-123"';
$localEtag = '"local-etag-456"';
$this->federatedCalendarService->expects(self::once())
->method('createCalendarObject')
->with($this->federationInfo, 'event1.ics', $calendarData)
->willReturn($remoteEtag);
$this->caldavBackend->expects(self::once())
->method('createCalendarObject')
->with(10, 'event1.ics', $calendarData, 2)
->willReturn($localEtag);
$result = $this->federatedCalendar->createFile('event1.ics', $calendarData);
$this->assertEquals($localEtag, $result);
}
public function testUpdateFile(): void {
$calendarData = 'BEGIN:VCALENDAR...UPDATED...END:VCALENDAR';
$remoteEtag = '"remote-etag-updated"';
$localEtag = '"local-etag-updated"';
$this->federatedCalendarService->expects(self::once())
->method('updateCalendarObject')
->with($this->federationInfo, 'event1.ics', $calendarData)
->willReturn($remoteEtag);
$this->caldavBackend->expects(self::once())
->method('updateCalendarObject')
->with(10, 'event1.ics', $calendarData, 2)
->willReturn($localEtag);
$result = $this->federatedCalendar->updateFile('event1.ics', $calendarData);
$this->assertEquals($localEtag, $result);
}
public function testDeleteFile(): void {
$this->federatedCalendarService->expects(self::once())
->method('deleteCalendarObject')
->with($this->federationInfo, 'event1.ics');
$this->caldavBackend->expects(self::once())
->method('deleteCalendarObject')
->with(10, 'event1.ics', 2);
$this->federatedCalendar->deleteFile('event1.ics');
}
}
@@ -15,7 +15,6 @@ use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\IResult;
use OCP\DB\QueryBuilder\IExpressionBuilder;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IRequest;
@@ -416,12 +415,9 @@ EOF;
->willReturn($queryBuilder);
$queryBuilder->method('expr')
->willReturn($expr);
$parameter = $this->createMock(IParameter::class);
$parameter->method('__toString')
->willReturn('namedParameterToken');
$queryBuilder->method('createNamedParameter')
->willReturnMap([
[$token, \PDO::PARAM_STR, null, $parameter]
[$token, \PDO::PARAM_STR, null, 'namedParameterToken']
]);
$stmt->expects($this->once())
+1 -1
View File
@@ -545,7 +545,7 @@ class Crypt {
$options,
$iv);
if ($plainContent) {
if ($plainContent !== false) {
return $plainContent;
} else {
throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string());
+1 -1
View File
@@ -95,7 +95,7 @@ class Backends extends Base {
*/
private function formatConfiguration(array $parameters): array {
$configuration = array_filter($parameters, function (DefinitionParameter $parameter) {
return $parameter->isFlagSet(DefinitionParameter::FLAG_HIDDEN);
return !$parameter->isFlagSet(DefinitionParameter::FLAG_HIDDEN);
});
return array_map(function (DefinitionParameter $parameter) {
return $parameter->getTypeName();
@@ -270,8 +270,8 @@ class AmazonS3 extends Common {
$connection->deleteObjects([
'Bucket' => $this->bucket,
'Delete' => [
'Quiet' => true,
'Objects' => array_map(fn (array $object) => [
'ETag' => $object['ETag'],
'Key' => $object['Key'],
], $objects['Contents'])
]
+1 -1
View File
@@ -233,7 +233,7 @@ OC.L10N.register(
"Create a new share link" : "Креирајте нов линк за споделување",
"Quick share options, the current selected is \"{selectedOption}\"" : "Опции за брзо споделување за , тековната избрана е \"{selectedOption}\"",
"View only" : "Само за гледање",
"Can edit" : "Може да се уредува",
"Can edit" : "Може да уредува",
"Custom permissions" : "Прилагодени дозволи",
"Resharing is not allowed" : "Повторно споделување не е дозволено",
"Name or email …" : "Име или е-пошта …",
+1 -1
View File
@@ -231,7 +231,7 @@
"Create a new share link" : "Креирајте нов линк за споделување",
"Quick share options, the current selected is \"{selectedOption}\"" : "Опции за брзо споделување за , тековната избрана е \"{selectedOption}\"",
"View only" : "Само за гледање",
"Can edit" : "Може да се уредува",
"Can edit" : "Може да уредува",
"Custom permissions" : "Прилагодени дозволи",
"Resharing is not allowed" : "Повторно споделување не е дозволено",
"Name or email …" : "Име или е-пошта …",
+21 -21
View File
@@ -6730,9 +6730,9 @@
}
},
"node_modules/asn1.js/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
"license": "MIT"
},
"node_modules/assert": {
@@ -7084,9 +7084,9 @@
"license": "MIT"
},
"node_modules/bn.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
"integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz",
"integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==",
"license": "MIT"
},
"node_modules/body-parser": {
@@ -8114,9 +8114,9 @@
}
},
"node_modules/create-ecdh/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
"license": "MIT"
},
"node_modules/create-hash": {
@@ -8690,9 +8690,9 @@
}
},
"node_modules/diffie-hellman/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
"license": "MIT"
},
"node_modules/dijkstrajs": {
@@ -8889,9 +8889,9 @@
}
},
"node_modules/elliptic/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
"license": "MIT"
},
"node_modules/emoji-mart-vue-fast": {
@@ -13028,9 +13028,9 @@
}
},
"node_modules/miller-rabin/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
"license": "MIT"
},
"node_modules/mime": {
@@ -14750,9 +14750,9 @@
}
},
"node_modules/public-encrypt/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
"license": "MIT"
},
"node_modules/punycode": {
+48
View File
@@ -3371,6 +3371,45 @@
<code><![CDATA[0]]></code>
</TypeDoesNotContainType>
</file>
<file src="lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php">
<ImplicitToStringCast>
<code><![CDATA[$this->functionBuilder->lower($x)]]></code>
</ImplicitToStringCast>
<InvalidArgument>
<code><![CDATA[$y]]></code>
<code><![CDATA[$y]]></code>
</InvalidArgument>
</file>
<file src="lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php">
<InternalMethod>
<code><![CDATA[getParams]]></code>
</InternalMethod>
<InvalidArrayOffset>
<code><![CDATA[$params['collation']]]></code>
</InvalidArrayOffset>
</file>
<file src="lib/private/DB/QueryBuilder/QueryBuilder.php">
<InvalidNullableReturnType>
<code><![CDATA[string]]></code>
</InvalidNullableReturnType>
<NullableReturnStatement>
<code><![CDATA[$alias]]></code>
</NullableReturnStatement>
</file>
<file src="lib/private/DB/QueryBuilder/QuoteHelper.php">
<InvalidNullableReturnType>
<code><![CDATA[string]]></code>
</InvalidNullableReturnType>
<NullableReturnStatement>
<code><![CDATA[$string]]></code>
</NullableReturnStatement>
</file>
<file src="lib/private/DB/QueryBuilder/TypedQueryBuilder.php">
<InternalMethod>
<code><![CDATA[select]]></code>
<code><![CDATA[selectDistinct]]></code>
</InternalMethod>
</file>
<file src="lib/private/DateTimeFormatter.php">
<FalsableReturnStatement>
<code><![CDATA[$l->l($type, $timestamp, [
@@ -3970,4 +4009,13 @@
<code><![CDATA[getAppValue]]></code>
</DeprecatedMethod>
</file>
<file src="tests/lib/TestCase.php">
<DeprecatedMethod>
<code><![CDATA[$container]]></code>
</DeprecatedMethod>
<InternalMethod>
<code><![CDATA[lockFile]]></code>
<code><![CDATA[unlockFile]]></code>
</InternalMethod>
</file>
</files>
+32
View File
@@ -21,6 +21,38 @@ $qb->selectColumnsDistinct('f');
$qb->selectAlias('g', 'h');
$qb->selectAlias($qb->func()->lower('i'), 'j');
$qb
->setParameter('k', 'l')
->setParameters([])
->setFirstResult(0)
->setMaxResults(0)
->delete()
->update()
->insert()
->from('m')
->join('n', 'o', 'p')
->innerJoin('q', 'r', 's')
->leftJoin('t', 'u', 'v')
->rightJoin('w', 'x', 'y')
->set('z', '1')
->where()
->andWhere()
->orWhere()
->groupBy()
->addGroupBy()
->setValue('2', '3')
->values([])
->having()
->andHaving()
->orHaving()
->orderBy('4')
->addOrderBy('5')
->resetQueryParts()
->resetQueryPart('6')
->hintShardKey('7', '8')
->runAcrossAllShards()
->forUpdate();
/** @psalm-check-type-exact $result = \OCP\DB\IResult<'a'|'b'|'c'|'d'|'e'|'f'|'h'|'j'> */
$result = $qb->executeQuery();
+1 -1
View File
@@ -82,7 +82,7 @@ class StatusCommand extends Command implements CompletionAwareInterface {
$numExecutedUnavailableMigrations = count($executedUnavailableMigrations);
$numNewMigrations = count(array_diff(array_keys($availableMigrations), $executedMigrations));
$pending = $ms->describeMigrationStep('lastest');
$pending = $ms->describeMigrationStep();
$infos = [
'App' => $ms->getApp(),
+29 -13
View File
@@ -43,6 +43,12 @@ class ResetPassword extends Base {
InputOption::VALUE_NONE,
'read password from environment variable NC_PASS/OC_PASS'
)
->addOption(
'no-password',
null,
InputOption::VALUE_NONE,
'Sets the password to blank'
)
;
}
@@ -76,22 +82,32 @@ class ResetPassword extends Base {
}
}
$question = new Question('Enter a new password: ');
$question->setHidden(true);
$password = $helper->ask($input, $output, $question);
if ($input->getOption('no-password')) {
$question = new ConfirmationQuestion('Are you sure you want to clear the password for ' . $username . '?');
if ($password === null) {
$output->writeln('<error>Password cannot be empty!</error>');
return 1;
}
if (!$helper->ask($input, $output, $question)) {
return 1;
}
$question = new Question('Confirm the new password: ');
$question->setHidden(true);
$confirm = $helper->ask($input, $output, $question);
$password = '';
} else {
$question = new Question('Enter a new password: ');
$question->setHidden(true);
$password = $helper->ask($input, $output, $question);
if ($password !== $confirm) {
$output->writeln('<error>Passwords did not match!</error>');
return 1;
if ($password === null) {
$output->writeln('<error>Password cannot be empty!</error>');
return 1;
}
$question = new Question('Confirm the new password: ');
$question->setHidden(true);
$confirm = $helper->ask($input, $output, $question);
if ($password !== $confirm) {
$output->writeln('<error>Passwords did not match!</error>');
return 1;
}
}
} else {
$output->writeln('<error>Interactive input or --password-from-env is needed for entering a new password!</error>');
+4 -3
View File
@@ -766,9 +766,10 @@ class OC {
self::checkConfig();
self::checkInstalled($systemConfig);
self::addSecurityHeaders();
self::performSameSiteCookieProtection($config);
if (!self::$CLI) {
self::addSecurityHeaders();
self::performSameSiteCookieProtection($config);
}
if (!defined('OC_CONSOLE')) {
$eventLogger->start('check_server', 'Run a few configuration checks');
@@ -16,10 +16,6 @@ class CompositeExpression implements ICompositeExpression, \Countable {
public const TYPE_AND = 'AND';
public const TYPE_OR = 'OR';
/**
* @param self::TYPE_* $type
* @param array<ICompositeExpression|string> $parts
*/
public function __construct(
private string $type,
private array $parts = [],
@@ -5,9 +5,6 @@
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare(strict_types=1);
namespace OC\DB\QueryBuilder\ExpressionBuilder;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder as DoctrineExpressionBuilder;
@@ -23,7 +20,6 @@ use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use Override;
use Psr\Log\LoggerInterface;
class ExpressionBuilder implements IExpressionBuilder {
@@ -41,178 +37,385 @@ class ExpressionBuilder implements IExpressionBuilder {
$this->functionBuilder = $queryBuilder->func();
}
#[Override]
public function andX(ICompositeExpression|string ...$x): ICompositeExpression {
/**
* Creates a conjunction of the given boolean expressions.
*
* Example:
*
* [php]
* // (u.type = ?) AND (u.role = ?)
* $expr->andX('u.type = ?', 'u.role = ?'));
*
* @param mixed ...$x Optional clause. Defaults = null, but requires
* at least one defined when converting to string.
*
* @return 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);
}
#[Override]
public function orX(ICompositeExpression|string ...$x): ICompositeExpression {
/**
* Creates a disjunction of the given boolean expressions.
*
* Example:
*
* [php]
* // (u.type = ?) OR (u.role = ?)
* $qb->where($qb->expr()->orX('u.type = ?', 'u.role = ?'));
*
* @param mixed ...$x Optional clause. Defaults = null, but requires
* at least one defined when converting to string.
*
* @return 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);
}
#[Override]
public function comparison(string|ILiteral|IQueryFunction|IParameter $x, string $operator, $y, int|string|null $type = null): string {
/**
* Creates a comparison expression.
*
* @param mixed $x The left expression.
* @param string $operator One of the IExpressionBuilder::* constants.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function comparison($x, string $operator, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->comparison($x, $operator, $y);
}
#[Override]
public function eq(string|ILiteral|IQueryFunction|IParameter $x, IQueryFunction|ILiteral|IParameter|string $y, int|string|null $type = null): string {
/**
* Creates an equality comparison expression with the given arguments.
*
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> = <right expr>. Example:
*
* [php]
* // u.id = ?
* $expr->eq('u.id', '?');
*
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function eq($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->eq($x, $y);
}
#[Override]
public function neq(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, int|string|null $type = null): string {
/**
* Creates a non equality comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> <> <right expr>. Example:
*
* [php]
* // u.id <> 1
* $q->where($q->expr()->neq('u.id', '1'));
*
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function neq($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->neq($x, $y);
}
#[Override]
public function lt(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, int|string|null $type = null): string {
/**
* Creates a lower-than comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> < <right expr>. Example:
*
* [php]
* // u.id < ?
* $q->where($q->expr()->lt('u.id', '?'));
*
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function lt($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->lt($x, $y);
}
#[Override]
public function lte(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, int|string|null $type = null): string {
/**
* Creates a lower-than-equal comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> <= <right expr>. Example:
*
* [php]
* // u.id <= ?
* $q->where($q->expr()->lte('u.id', '?'));
*
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function lte($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->lte($x, $y);
}
#[Override]
public function gt(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, int|string|null $type = null): string {
/**
* Creates a greater-than comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> > <right expr>. Example:
*
* [php]
* // u.id > ?
* $q->where($q->expr()->gt('u.id', '?'));
*
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function gt($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->gt($x, $y);
}
#[Override]
public function gte(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, int|string|null $type = null): string {
/**
* Creates a greater-than-equal comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> >= <right expr>. Example:
*
* [php]
* // u.id >= ?
* $q->where($q->expr()->gte('u.id', '?'));
*
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function gte($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->gte($x, $y);
}
#[Override]
public function isNull(string|ILiteral|IParameter|IQueryFunction $x): string {
/**
* Creates an IS NULL expression with the given arguments.
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be restricted by IS NULL.
*
* @return string
*/
public function isNull($x): string {
$x = $this->helper->quoteColumnName($x);
return $this->expressionBuilder->isNull($x);
}
#[Override]
public function isNotNull(string|ILiteral|IParameter|IQueryFunction $x): string {
/**
* Creates an IS NOT NULL expression with the given arguments.
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be restricted by IS NOT NULL.
*
* @return string
*/
public function isNotNull($x): string {
$x = $this->helper->quoteColumnName($x);
return $this->expressionBuilder->isNotNull($x);
}
#[Override]
public function like(
string|IParameter|ILiteral|IQueryFunction $x,
string|IParameter|ILiteral|IQueryFunction $y,
int|string|null $type = null,
): string {
/**
* Creates a LIKE() comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by LIKE() comparison.
* @param mixed $y Argument to be used in LIKE() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function like($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
return $this->expressionBuilder->like($x, $y);
}
#[Override]
public function iLike(
string|IParameter|ILiteral|IQueryFunction $x,
string|IParameter|ILiteral|IQueryFunction $y,
int|string|null $type = null,
): string {
return $this->expressionBuilder->like((string)$this->functionBuilder->lower($x), (string)$this->functionBuilder->lower($y));
/**
* Creates a ILIKE() comparison expression with the given arguments.
*
* @param string $x Field in string format to be inspected by ILIKE() comparison.
* @param mixed $y Argument to be used in ILIKE() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 9.0.0
*/
public function iLike($x, $y, $type = null): string {
return $this->expressionBuilder->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y));
}
#[Override]
public function notLike(
string|IParameter|ILiteral|IQueryFunction $x,
string|IParameter|ILiteral|IQueryFunction $y,
int|string|null $type = null,
): string {
/**
* Creates a NOT LIKE() comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by NOT LIKE() comparison.
* @param mixed $y Argument to be used in NOT LIKE() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function notLike($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
return $this->expressionBuilder->notLike($x, $y);
}
#[Override]
public function in(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string|array $y,
int|string|null $type = null,
): string {
/**
* Creates a IN () comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by IN() comparison.
* @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by IN() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function in($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnNames($y);
return $this->expressionBuilder->in($x, $y);
}
#[Override]
public function notIn(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string|array $y,
int|string|null $type = null,
): string {
/**
* Creates a NOT IN () comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by NOT IN() comparison.
* @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by NOT IN() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
*/
public function notIn($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnNames($y);
return $this->expressionBuilder->notIn($x, $y);
}
#[Override]
public function emptyString(string|ILiteral|IParameter|IQueryFunction $x): string {
/**
* Creates a $x = '' statement, because Oracle needs a different check
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be inspected by the comparison.
* @return string
* @since 13.0.0
*/
public function emptyString($x): string {
return $this->eq($x, $this->literal('', IQueryBuilder::PARAM_STR));
}
#[Override]
public function nonEmptyString(string|ILiteral|IParameter|IQueryFunction $x): string {
/**
* Creates a `$x <> ''` statement, because Oracle needs a different check
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be inspected by the comparison.
* @return string
* @since 13.0.0
*/
public function nonEmptyString($x): string {
return $this->neq($x, $this->literal('', IQueryBuilder::PARAM_STR));
}
#[Override]
public function bitwiseAnd(string|ILiteral $x, int $y): IQueryFunction {
/**
* Binary AND Operator copies a bit to the result if it exists in both operands.
*
* @param string|ILiteral $x The field or value to check
* @param int $y Bitmap that must be set
* @return IQueryFunction
* @since 12.0.0
*/
public function bitwiseAnd($x, int $y): IQueryFunction {
return new QueryFunction($this->connection->getDatabasePlatform()->getBitAndComparisonExpression(
$this->helper->quoteColumnName($x),
(string)$y
$y
));
}
#[Override]
public function bitwiseOr(string|ILiteral $x, int $y): IQueryFunction {
/**
* Binary OR Operator copies a bit if it exists in either operand.
*
* @param string|ILiteral $x The field or value to check
* @param int $y Bitmap that must be set
* @return IQueryFunction
* @since 12.0.0
*/
public function bitwiseOr($x, int $y): IQueryFunction {
return new QueryFunction($this->connection->getDatabasePlatform()->getBitOrComparisonExpression(
$this->helper->quoteColumnName($x),
(string)$y
$y
));
}
#[Override]
public function literal($input, int|string $type = IQueryBuilder::PARAM_STR): ILiteral {
/**
* Quotes a given input parameter.
*
* @param mixed $input The parameter to be quoted.
* @param int $type One of the IQueryBuilder::PARAM_* constants
*
* @return ILiteral
*/
public function literal($input, $type = IQueryBuilder::PARAM_STR): ILiteral {
return new Literal($this->expressionBuilder->literal($input, $type));
}
#[Override]
public function castColumn(string|IQueryFunction|ILiteral|IParameter $column, int|string $type): IQueryFunction {
/**
* Returns a IQueryFunction that casts the column to the given type
*
* @param string|IQueryFunction $column
* @param mixed $type One of IQueryBuilder::PARAM_*
* @psalm-param IQueryBuilder::PARAM_* $type
* @return IQueryFunction
*/
public function castColumn($column, $type): IQueryFunction {
return new QueryFunction(
$this->helper->quoteColumnName($column)
);
}
/**
* @param IQueryBuilder::PARAM_* $type
* @param mixed $column
* @param mixed|null $type
* @return array|IQueryFunction|string
*/
protected function prepareColumn(IQueryFunction|ILiteral|IParameter|string|array $column, int|string|null $type): array|string {
protected function prepareColumn($column, $type) {
return $this->helper->quoteColumnNames($column);
}
}
@@ -5,18 +5,12 @@
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare(strict_types=1);
namespace OC\DB\QueryBuilder\ExpressionBuilder;
use OC\DB\ConnectionAdapter;
use OC\DB\QueryBuilder\QueryFunction;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use Override;
use Psr\Log\LoggerInterface;
class MySqlExpressionBuilder extends ExpressionBuilder {
@@ -25,25 +19,35 @@ class MySqlExpressionBuilder extends ExpressionBuilder {
public function __construct(ConnectionAdapter $connection, IQueryBuilder $queryBuilder, LoggerInterface $logger) {
parent::__construct($connection, $queryBuilder, $logger);
/** @psalm-suppress InternalMethod */
$params = $connection->getInner()->getParams();
/** @psalm-suppress InvalidArrayOffset collation is sometime defined */
$this->collation = $params['collation'] ?? (($params['charset'] ?? 'utf8') . '_general_ci');
}
#[Override]
public function iLike(string|IParameter|ILiteral|IQueryFunction $x, string|IParameter|ILiteral|IQueryFunction $y, int|string|null $type = null): string {
/**
* @inheritdoc
*/
public function iLike($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
return $this->expressionBuilder->comparison($x, ' COLLATE ' . $this->collation . ' LIKE', $y);
}
#[Override]
public function castColumn(string|IParameter|ILiteral|IQueryFunction $column, int|string $type): IQueryFunction {
return match ($type) {
IQueryBuilder::PARAM_STR => new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS CHAR)'),
IQueryBuilder::PARAM_JSON => new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS JSON)'),
default => parent::castColumn($column, $type),
};
/**
* Returns a IQueryFunction that casts the column to the given type
*
* @param string|IQueryFunction $column
* @param mixed $type One of IQueryBuilder::PARAM_*
* @psalm-param IQueryBuilder::PARAM_* $type
* @return IQueryFunction
*/
public function castColumn($column, $type): IQueryFunction {
switch ($type) {
case IQueryBuilder::PARAM_STR:
return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS CHAR)');
case IQueryBuilder::PARAM_JSON:
return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS JSON)');
default:
return parent::castColumn($column, $type);
}
}
}
@@ -12,11 +12,14 @@ use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use Override;
class OCIExpressionBuilder extends ExpressionBuilder {
#[Override]
protected function prepareColumn(IQueryFunction|ILiteral|IParameter|string|array $column, int|string|null $type): array|string {
/**
* @param mixed $column
* @param mixed|null $type
* @return array|IQueryFunction|string
*/
protected function prepareColumn($column, $type) {
if ($type === IQueryBuilder::PARAM_STR && !is_array($column) && !($column instanceof IParameter) && !($column instanceof ILiteral)) {
$column = $this->castColumn($column, $type);
}
@@ -24,8 +27,10 @@ class OCIExpressionBuilder extends ExpressionBuilder {
return parent::prepareColumn($column, $type);
}
#[Override]
public function eq(IQueryFunction|ILiteral|IParameter|string $x, IQueryFunction|ILiteral|IParameter|string $y, int|string|null $type = null): string {
/**
* @inheritdoc
*/
public function eq($x, $y, $type = null): string {
if ($type === IQueryBuilder::PARAM_JSON) {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
@@ -35,8 +40,10 @@ class OCIExpressionBuilder extends ExpressionBuilder {
return parent::eq($x, $y, $type);
}
#[Override]
public function neq(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, int|string|null $type = null): string {
/**
* @inheritdoc
*/
public function neq($x, $y, $type = null): string {
if ($type === IQueryBuilder::PARAM_JSON) {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
@@ -46,34 +53,57 @@ class OCIExpressionBuilder extends ExpressionBuilder {
return parent::neq($x, $y, $type);
}
#[Override]
public function in(ILiteral|IParameter|IQueryFunction|string $x, ILiteral|IParameter|IQueryFunction|string|array $y, int|string|null $type = null): string {
/**
* @inheritdoc
*/
public function in($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->in($x, $y);
}
#[Override]
public function notIn(ILiteral|IParameter|IQueryFunction|string $x, ILiteral|IParameter|IQueryFunction|string|array $y, int|string|null $type = null): string {
/**
* @inheritdoc
*/
public function notIn($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->notIn($x, $y);
}
#[Override]
public function emptyString(string|ILiteral|IParameter|IQueryFunction $x): string {
/**
* Creates a $x = '' statement, because Oracle needs a different check
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be inspected by the comparison.
* @return string
* @since 13.0.0
*/
public function emptyString($x): string {
return $this->isNull($x);
}
#[Override]
public function nonEmptyString(string|ILiteral|IParameter|IQueryFunction $x): string {
/**
* Creates a `$x <> ''` statement, because Oracle needs a different check
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be inspected by the comparison.
* @return string
* @since 13.0.0
*/
public function nonEmptyString($x): string {
return $this->isNotNull($x);
}
#[Override]
public function castColumn(string|IQueryFunction|ILiteral|IParameter $column, int|string|null $type): IQueryFunction {
/**
* Returns a IQueryFunction that casts the column to the given type
*
* @param string|IQueryFunction $column
* @param mixed $type One of IQueryBuilder::PARAM_*
* @psalm-param IQueryBuilder::PARAM_* $type
* @return IQueryFunction
*/
public function castColumn($column, $type): IQueryFunction {
if ($type === IQueryBuilder::PARAM_STR) {
$column = $this->helper->quoteColumnName($column);
return new QueryFunction('to_char(' . $column . ')');
@@ -86,21 +116,17 @@ class OCIExpressionBuilder extends ExpressionBuilder {
return parent::castColumn($column, $type);
}
#[Override]
public function like(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string $y,
int|string|null $type = null,
): string {
/**
* @inheritdoc
*/
public function like($x, $y, $type = null): string {
return parent::like($x, $y, $type) . " ESCAPE '\\'";
}
#[Override]
public function iLike(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string $y,
int|string|null $type = null,
): string {
/**
* @inheritdoc
*/
public function iLike($x, $y, $type = null): string {
return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y));
}
}
@@ -12,20 +12,32 @@ use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use Override;
class PgSqlExpressionBuilder extends ExpressionBuilder {
#[Override]
public function castColumn(string|IQueryFunction|ILiteral|IParameter $column, string|int $type): IQueryFunction {
return match ($type) {
IQueryBuilder::PARAM_INT => new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS BIGINT)'),
IQueryBuilder::PARAM_STR, IQueryBuilder::PARAM_JSON => new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS TEXT)'),
default => parent::castColumn($column, $type),
};
/**
* Returns a IQueryFunction that casts the column to the given type
*
* @param string|IQueryFunction $column
* @param mixed $type One of IQueryBuilder::PARAM_*
* @psalm-param IQueryBuilder::PARAM_* $type
* @return IQueryFunction
*/
public function castColumn($column, $type): IQueryFunction {
switch ($type) {
case IQueryBuilder::PARAM_INT:
return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS BIGINT)');
case IQueryBuilder::PARAM_STR:
case IQueryBuilder::PARAM_JSON:
return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS TEXT)');
default:
return parent::castColumn($column, $type);
}
}
#[Override]
protected function prepareColumn(IQueryFunction|ILiteral|IParameter|string|array $column, int|string|null $type): string|array {
/**
* @inheritdoc
*/
protected function prepareColumn($column, $type) {
if ($type === IQueryBuilder::PARAM_JSON && !is_array($column) && !($column instanceof IParameter) && !($column instanceof ILiteral)) {
$column = $this->castColumn($column, $type);
}
@@ -33,8 +45,10 @@ class PgSqlExpressionBuilder extends ExpressionBuilder {
return parent::prepareColumn($column, $type);
}
#[Override]
public function iLike(string|IParameter|ILiteral|IQueryFunction $x, mixed $y, mixed $type = null): string {
/**
* @inheritdoc
*/
public function iLike($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
return $this->expressionBuilder->comparison($x, 'ILIKE', $y);
@@ -11,35 +11,44 @@ use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use Override;
class SqliteExpressionBuilder extends ExpressionBuilder {
#[Override]
public function like(ILiteral|IParameter|IQueryFunction|string $x, mixed $y, int|string|null $type = null): string {
/**
* @inheritdoc
*/
public function like($x, $y, $type = null): string {
return parent::like($x, $y, $type) . " ESCAPE '\\'";
}
#[Override]
public function iLike(string|IParameter|ILiteral|IQueryFunction $x, mixed $y, int|string|null $type = null): string {
public function iLike($x, $y, $type = null): string {
return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y), $type);
}
#[Override]
protected function prepareColumn(IQueryFunction|ILiteral|IParameter|string|array $column, int|string|null $type): string|array {
if (!($column instanceof IParameter)
&& !($column instanceof IQueryFunction)
&& !($column instanceof ILiteral)
/**
* @param mixed $column
* @param mixed|null $type
* @return array|IQueryFunction|string
*/
protected function prepareColumn($column, $type) {
if ($type !== null
&& !is_array($column)
&& is_string($type)
&& !($column instanceof IParameter)
&& !($column instanceof ILiteral)
&& (str_starts_with($type, 'date') || str_starts_with($type, 'time'))) {
return (string)$this->castColumn($column, $type);
return $this->castColumn($column, $type);
}
return parent::prepareColumn($column, $type);
}
#[Override]
public function castColumn(string|IQueryFunction|ILiteral|IParameter $column, string|int $type): IQueryFunction {
/**
* Returns a IQueryFunction that casts the column to the given type
*
* @param string $column
* @param mixed $type One of IQueryBuilder::PARAM_*
* @return IQueryFunction
*/
public function castColumn($column, $type): IQueryFunction {
switch ($type) {
case IQueryBuilder::PARAM_DATE_MUTABLE:
case IQueryBuilder::PARAM_DATE_IMMUTABLE:
@@ -10,349 +10,284 @@ namespace OC\DB\QueryBuilder;
use OCP\DB\IResult;
use OCP\DB\QueryBuilder\ConflictResolutionMode;
use OCP\DB\QueryBuilder\ICompositeExpression;
use OCP\DB\QueryBuilder\IExpressionBuilder;
use OCP\DB\QueryBuilder\IFunctionBuilder;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use OCP\IDBConnection;
use Override;
/**
* Base class for creating classes that extend the builtin query builder
*/
abstract class ExtendedQueryBuilder extends TypedQueryBuilder {
public function __construct(
protected readonly IQueryBuilder $builder,
protected IQueryBuilder $builder,
) {
}
#[Override]
public function automaticTablePrefix(bool $enabled): void {
public function automaticTablePrefix($enabled) {
$this->builder->automaticTablePrefix($enabled);
return $this;
}
#[Override]
public function expr(): IExpressionBuilder {
public function expr() {
return $this->builder->expr();
}
#[Override]
public function func(): IFunctionBuilder {
public function func() {
return $this->builder->func();
}
#[Override]
public function getType(): int {
public function getType() {
return $this->builder->getType();
}
#[Override]
public function getConnection(): IDBConnection {
public function getConnection() {
return $this->builder->getConnection();
}
#[Override]
public function getState(): int {
public function getState() {
return $this->builder->getState();
}
#[Override]
public function getSQL(): string {
public function getSQL() {
return $this->builder->getSQL();
}
#[Override]
public function setParameter(string|int $key, mixed $value, string|null|int $type = null): self {
public function setParameter($key, $value, $type = null) {
$this->builder->setParameter($key, $value, $type);
return $this;
}
#[Override]
public function setParameters(array $params, array $types = []): self {
public function setParameters(array $params, array $types = []) {
$this->builder->setParameters($params, $types);
return $this;
}
#[Override]
public function getParameters(): array {
public function getParameters() {
return $this->builder->getParameters();
}
#[Override]
public function getParameter(int|string $key): mixed {
public function getParameter($key) {
return $this->builder->getParameter($key);
}
#[Override]
public function getParameterTypes(): array {
public function getParameterTypes() {
return $this->builder->getParameterTypes();
}
#[Override]
public function getParameterType(int|string $key): int|string {
public function getParameterType($key) {
return $this->builder->getParameterType($key);
}
#[Override]
public function setFirstResult(int $firstResult): self {
public function setFirstResult($firstResult) {
$this->builder->setFirstResult($firstResult);
return $this;
}
#[Override]
public function getFirstResult(): int {
public function getFirstResult() {
return $this->builder->getFirstResult();
}
#[Override]
public function setMaxResults(?int $maxResults): self {
public function setMaxResults($maxResults) {
$this->builder->setMaxResults($maxResults);
return $this;
}
#[Override]
public function getMaxResults(): ?int {
public function getMaxResults() {
return $this->builder->getMaxResults();
}
#[Override]
public function select(...$selects): self {
public function select(...$selects) {
$this->builder->select(...$selects);
return $this;
}
#[Override]
public function selectAlias(string|IQueryFunction|IParameter|ILiteral $select, string $alias): self {
public function selectAlias($select, $alias): self {
$this->builder->selectAlias($select, $alias);
return $this;
}
#[Override]
public function selectDistinct(string|array $select): self {
public function selectDistinct($select) {
$this->builder->selectDistinct($select);
return $this;
}
#[Override]
public function addSelect(...$select): self {
public function addSelect(...$select) {
$this->builder->addSelect(...$select);
return $this;
}
#[Override]
public function delete(string $delete, ?string $alias = null): self {
public function delete($delete = null, $alias = null) {
$this->builder->delete($delete, $alias);
return $this;
}
#[Override]
public function update(string $update, ?string $alias = null): self {
public function update($update = null, $alias = null) {
$this->builder->update($update, $alias);
return $this;
}
#[Override]
public function insert(string $insert): self {
public function insert($insert = null) {
$this->builder->insert($insert);
return $this;
}
#[Override]
public function from(string|IQueryFunction $from, ?string $alias = null): self {
public function from($from, $alias = null) {
$this->builder->from($from, $alias);
return $this;
}
#[Override]
public function join(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function join($fromAlias, $join, $alias, $condition = null) {
$this->builder->join($fromAlias, $join, $alias, $condition);
return $this;
}
#[Override]
public function innerJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function innerJoin($fromAlias, $join, $alias, $condition = null) {
$this->builder->innerJoin($fromAlias, $join, $alias, $condition);
return $this;
}
#[Override]
public function leftJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function leftJoin($fromAlias, $join, $alias, $condition = null) {
$this->builder->leftJoin($fromAlias, $join, $alias, $condition);
return $this;
}
#[Override]
public function rightJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function rightJoin($fromAlias, $join, $alias, $condition = null) {
$this->builder->rightJoin($fromAlias, $join, $alias, $condition);
return $this;
}
#[Override]
public function set(string $key, ILiteral|IParameter|IQueryFunction|string $value): self {
public function set($key, $value) {
$this->builder->set($key, $value);
return $this;
}
#[Override]
public function where(...$predicates): self {
public function where(...$predicates) {
$this->builder->where(...$predicates);
return $this;
}
#[Override]
public function andWhere(...$where): self {
public function andWhere(...$where) {
$this->builder->andWhere(...$where);
return $this;
}
#[Override]
public function orWhere(...$where): self {
public function orWhere(...$where) {
$this->builder->orWhere(...$where);
return $this;
}
#[Override]
public function groupBy(...$groupBys): self {
public function groupBy(...$groupBys) {
$this->builder->groupBy(...$groupBys);
return $this;
}
#[Override]
public function addGroupBy(...$groupBy): self {
public function addGroupBy(...$groupBy) {
$this->builder->addGroupBy(...$groupBy);
return $this;
}
#[Override]
public function setValue(string $column, ILiteral|IParameter|IQueryFunction|string $value): self {
public function setValue($column, $value) {
$this->builder->setValue($column, $value);
return $this;
}
#[Override]
public function values(array $values): self {
public function values(array $values) {
$this->builder->values($values);
return $this;
}
#[Override]
public function having(...$having): self {
public function having(...$having) {
$this->builder->having(...$having);
return $this;
}
#[Override]
public function andHaving(...$having): self {
public function andHaving(...$having) {
$this->builder->andHaving(...$having);
return $this;
}
#[Override]
public function orHaving(...$having): self {
public function orHaving(...$having) {
$this->builder->orHaving(...$having);
return $this;
}
#[Override]
public function orderBy(string|ILiteral|IParameter|IQueryFunction $sort, ?string $order = null): self {
public function orderBy($sort, $order = null) {
$this->builder->orderBy($sort, $order);
return $this;
}
#[Override]
public function addOrderBy(string|ILiteral|IParameter|IQueryFunction $sort, ?string $order = null): self {
public function addOrderBy($sort, $order = null) {
$this->builder->addOrderBy($sort, $order);
return $this;
}
#[Override]
public function getQueryPart(string $queryPartName): mixed {
public function getQueryPart($queryPartName) {
return $this->builder->getQueryPart($queryPartName);
}
#[Override]
public function getQueryParts(): array {
public function getQueryParts() {
return $this->builder->getQueryParts();
}
#[Override]
public function resetQueryParts(?array $queryPartNames = null): self {
public function resetQueryParts($queryPartNames = null) {
$this->builder->resetQueryParts($queryPartNames);
return $this;
}
#[Override]
public function resetQueryPart(string $queryPartName): self {
public function resetQueryPart($queryPartName) {
$this->builder->resetQueryPart($queryPartName);
return $this;
}
#[Override]
public function createNamedParameter(mixed $value, mixed $type = self::PARAM_STR, $placeHolder = null): IParameter {
public function createNamedParameter($value, $type = self::PARAM_STR, $placeHolder = null) {
return $this->builder->createNamedParameter($value, $type, $placeHolder);
}
#[Override]
public function createPositionalParameter(mixed $value, mixed $type = self::PARAM_STR): IParameter {
public function createPositionalParameter($value, $type = self::PARAM_STR) {
return $this->builder->createPositionalParameter($value, $type);
}
#[Override]
public function createParameter(string $name): IParameter {
public function createParameter($name) {
return $this->builder->createParameter($name);
}
#[Override]
public function createFunction(string $call): IQueryFunction {
public function createFunction($call) {
return $this->builder->createFunction($call);
}
#[Override]
public function getLastInsertId(): int {
return $this->builder->getLastInsertId();
}
#[Override]
public function getTableName(string|IQueryFunction $table): string {
public function getTableName($table) {
return $this->builder->getTableName($table);
}
#[Override]
public function getColumnName(string $column, string $tableAlias = ''): string {
public function getColumnName($column, $tableAlias = '') {
return $this->builder->getColumnName($column, $tableAlias);
}
#[Override]
public function executeQuery(?IDBConnection $connection = null): IResult {
return $this->builder->executeQuery($connection);
}
#[Override]
public function executeStatement(?IDBConnection $connection = null): int {
return $this->builder->executeStatement($connection);
}
#[Override]
public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self {
$this->builder->hintShardKey($column, $value, $overwrite);
return $this;
}
#[Override]
public function runAcrossAllShards(): self {
$this->builder->runAcrossAllShards();
return $this;
}
#[Override]
public function getOutputColumns(): array {
return $this->builder->getOutputColumns();
}
#[Override]
public function prefixTableName(string $table): string {
return $this->builder->prefixTableName($table);
}
@@ -6,31 +6,27 @@
*/
namespace OC\DB\QueryBuilder\FunctionBuilder;
use OC\DB\ConnectionAdapter;
use OC\DB\QueryBuilder\QueryFunction;
use OC\DB\QueryBuilder\QuoteHelper;
use OCP\DB\QueryBuilder\IFunctionBuilder;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use OCP\IDBConnection;
use Override;
class FunctionBuilder implements IFunctionBuilder {
public function __construct(
protected readonly ConnectionAdapter $connection,
protected readonly IQueryBuilder $queryBuilder,
protected readonly QuoteHelper $helper,
protected IDBConnection $connection,
protected IQueryBuilder $queryBuilder,
protected QuoteHelper $helper,
) {
}
#[Override]
public function md5(string|ILiteral|IParameter|IQueryFunction $input): IQueryFunction {
public function md5($input): IQueryFunction {
return new QueryFunction('MD5(' . $this->helper->quoteColumnName($input) . ')');
}
#[Override]
public function concat(string|ILiteral|IParameter|IQueryFunction $x, string|ILiteral|IParameter|IQueryFunction ...$expr): IQueryFunction {
public function concat($x, ...$expr): IQueryFunction {
$args = func_get_args();
$list = [];
foreach ($args as $item) {
@@ -39,18 +35,12 @@ class FunctionBuilder implements IFunctionBuilder {
return new QueryFunction(sprintf('CONCAT(%s)', implode(', ', $list)));
}
#[Override]
public function groupConcat(string|ILiteral|IParameter|IQueryFunction $expr, ?string $separator = ','): IQueryFunction {
public function groupConcat($expr, ?string $separator = ','): IQueryFunction {
$separator = $this->connection->quote($separator);
return new QueryFunction('GROUP_CONCAT(' . $this->helper->quoteColumnName($expr) . ' SEPARATOR ' . $separator . ')');
}
#[Override]
public function substring(
string|ILiteral|IParameter|IQueryFunction $input,
string|ILiteral|IParameter|IQueryFunction $start,
null|ILiteral|IParameter|IQueryFunction $length = null,
): IQueryFunction {
public function substring($input, $start, $length = null): IQueryFunction {
if ($length) {
return new QueryFunction('SUBSTR(' . $this->helper->quoteColumnName($input) . ', ' . $this->helper->quoteColumnName($start) . ', ' . $this->helper->quoteColumnName($length) . ')');
} else {
@@ -58,76 +48,53 @@ class FunctionBuilder implements IFunctionBuilder {
}
}
#[Override]
public function sum(string|ILiteral|IParameter|IQueryFunction $field): IQueryFunction {
public function sum($field): IQueryFunction {
return new QueryFunction('SUM(' . $this->helper->quoteColumnName($field) . ')');
}
#[Override]
public function lower(string|ILiteral|IParameter|IQueryFunction $field): IQueryFunction {
public function lower($field): IQueryFunction {
return new QueryFunction('LOWER(' . $this->helper->quoteColumnName($field) . ')');
}
#[Override]
public function add(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction {
public function add($x, $y): IQueryFunction {
return new QueryFunction($this->helper->quoteColumnName($x) . ' + ' . $this->helper->quoteColumnName($y));
}
#[Override]
public function subtract(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction {
public function subtract($x, $y): IQueryFunction {
return new QueryFunction($this->helper->quoteColumnName($x) . ' - ' . $this->helper->quoteColumnName($y));
}
#[Override]
public function count(string|ILiteral|IParameter|IQueryFunction $count = '', string $alias = ''): IQueryFunction {
public function count($count = '', $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $count === '' ? '*' : $this->helper->quoteColumnName($count);
return new QueryFunction('COUNT(' . $quotedName . ')' . $alias);
}
#[Override]
public function octetLength(string|ILiteral|IParameter|IQueryFunction $field, string $alias = ''): IQueryFunction {
public function octetLength($field, $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $this->helper->quoteColumnName($field);
return new QueryFunction('OCTET_LENGTH(' . $quotedName . ')' . $alias);
}
#[Override]
public function charLength(string|ILiteral|IParameter|IQueryFunction $field, string $alias = ''): IQueryFunction {
public function charLength($field, $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $this->helper->quoteColumnName($field);
return new QueryFunction('CHAR_LENGTH(' . $quotedName . ')' . $alias);
}
#[Override]
public function max(string|ILiteral|IParameter|IQueryFunction $field): IQueryFunction {
public function max($field): IQueryFunction {
return new QueryFunction('MAX(' . $this->helper->quoteColumnName($field) . ')');
}
#[Override]
public function min(string|ILiteral|IParameter|IQueryFunction $field): IQueryFunction {
public function min($field): IQueryFunction {
return new QueryFunction('MIN(' . $this->helper->quoteColumnName($field) . ')');
}
#[Override]
public function greatest(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction {
public function greatest($x, $y): IQueryFunction {
return new QueryFunction('GREATEST(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')');
}
#[Override]
public function least(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction {
public function least($x, $y): IQueryFunction {
return new QueryFunction('LEAST(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')');
}
@@ -6,15 +6,17 @@
*/
namespace OC\DB\QueryBuilder\FunctionBuilder;
use OC\DB\ConnectionAdapter;
use OC\DB\QueryBuilder\QueryFunction;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryFunction;
use Override;
class OCIFunctionBuilder extends FunctionBuilder {
public function md5(string|ILiteral|IParameter|IQueryFunction $input): IQueryFunction {
if (version_compare($this->connection->getServerVersion(), '20', '>=')) {
public function md5($input): IQueryFunction {
/** @var ConnectionAdapter $co */
$co = $this->connection;
if (version_compare($co->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) . ')))');
@@ -28,12 +30,11 @@ class OCIFunctionBuilder extends FunctionBuilder {
* the second parameter is a function or column, we have to put that as
* first parameter.
*
* @param string|ILiteral|IParameter|IQueryFunction $x
* @param string|ILiteral|IParameter|IQueryFunction $y
* @return IQueryFunction
*/
#[Override]
public function greatest(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction {
public function greatest($x, $y): IQueryFunction {
if (is_string($y) || $y instanceof IQueryFunction) {
return parent::greatest($y, $x);
}
@@ -48,12 +49,12 @@ class OCIFunctionBuilder extends FunctionBuilder {
* math, it will cast the expression to int and continue with a "0". So when
* the second parameter is a function or column, we have to put that as
* first parameter.
*
* @param string|ILiteral|IParameter|IQueryFunction $x
* @param string|ILiteral|IParameter|IQueryFunction $y
* @return IQueryFunction
*/
#[Override]
public function least(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction {
public function least($x, $y): IQueryFunction {
if (is_string($y) || $y instanceof IQueryFunction) {
return parent::least($y, $x);
}
@@ -61,8 +62,7 @@ class OCIFunctionBuilder extends FunctionBuilder {
return parent::least($x, $y);
}
#[Override]
public function concat(string|ILiteral|IParameter|IQueryFunction $x, string|ILiteral|IParameter|IQueryFunction ...$expr): IQueryFunction {
public function concat($x, ...$expr): IQueryFunction {
$args = func_get_args();
$list = [];
foreach ($args as $item) {
@@ -71,8 +71,7 @@ class OCIFunctionBuilder extends FunctionBuilder {
return new QueryFunction(sprintf('(%s)', implode(' || ', $list)));
}
#[Override]
public function groupConcat(string|ILiteral|IParameter|IQueryFunction $expr, ?string $separator = ','): IQueryFunction {
public function groupConcat($expr, ?string $separator = ','): IQueryFunction {
$orderByClause = ' WITHIN GROUP(ORDER BY NULL)';
if (is_null($separator)) {
return new QueryFunction('LISTAGG(' . $this->helper->quoteColumnName($expr) . ')' . $orderByClause);
@@ -82,15 +81,13 @@ class OCIFunctionBuilder extends FunctionBuilder {
return new QueryFunction('LISTAGG(' . $this->helper->quoteColumnName($expr) . ', ' . $separator . ')' . $orderByClause);
}
#[Override]
public function octetLength(string|ILiteral|IParameter|IQueryFunction $field, string $alias = ''): IQueryFunction {
public function octetLength($field, $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $this->helper->quoteColumnName($field);
return new QueryFunction('COALESCE(LENGTHB(' . $quotedName . '), 0)' . $alias);
}
#[Override]
public function charLength(string|ILiteral|IParameter|IQueryFunction $field, string $alias = ''): IQueryFunction {
public function charLength($field, $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $this->helper->quoteColumnName($field);
return new QueryFunction('COALESCE(LENGTH(' . $quotedName . '), 0)' . $alias);
@@ -9,10 +9,8 @@ namespace OC\DB\QueryBuilder\FunctionBuilder;
use OC\DB\QueryBuilder\QueryFunction;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use Override;
class PgSqlFunctionBuilder extends FunctionBuilder {
#[Override]
public function concat($x, ...$expr): IQueryFunction {
$args = func_get_args();
$list = [];
@@ -22,7 +20,6 @@ class PgSqlFunctionBuilder extends FunctionBuilder {
return new QueryFunction(sprintf('(%s)', implode(' || ', $list)));
}
#[Override]
public function groupConcat($expr, ?string $separator = ','): IQueryFunction {
$castedExpression = $this->queryBuilder->expr()->castColumn($expr, IQueryBuilder::PARAM_STR);
@@ -7,10 +7,7 @@
namespace OC\DB\QueryBuilder\FunctionBuilder;
use OC\DB\QueryBuilder\QueryFunction;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryFunction;
use Override;
class SqliteFunctionBuilder extends FunctionBuilder {
public function concat($x, ...$expr): IQueryFunction {
@@ -27,19 +24,11 @@ class SqliteFunctionBuilder extends FunctionBuilder {
return new QueryFunction('GROUP_CONCAT(' . $this->helper->quoteColumnName($expr) . ', ' . $separator . ')');
}
#[Override]
public function greatest(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction {
public function greatest($x, $y): IQueryFunction {
return new QueryFunction('MAX(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')');
}
#[Override]
public function least(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction {
public function least($x, $y): IQueryFunction {
return new QueryFunction('MIN(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')');
}
+4 -1
View File
@@ -12,8 +12,11 @@ namespace OC\DB\QueryBuilder;
use OCP\DB\QueryBuilder\ILiteral;
class Literal implements ILiteral {
/**
* @param mixed $literal
*/
public function __construct(
protected readonly mixed $literal,
protected $literal,
) {
}
@@ -10,7 +10,6 @@ namespace OC\DB\QueryBuilder\Partitioned;
use OC\DB\QueryBuilder\CompositeExpression;
use OC\DB\QueryBuilder\QueryFunction;
use OCP\DB\QueryBuilder\ICompositeExpression;
use OCP\DB\QueryBuilder\IQueryFunction;
/**
@@ -67,9 +66,14 @@ class JoinCondition {
}
/**
* @param null|string|CompositeExpression $condition
* @param string $join
* @param string $alias
* @param string $fromAlias
* @return JoinCondition
* @throws InvalidPartitionedQueryException
*/
public static function parse(null|string|ICompositeExpression $condition, string $join, string $alias, string $fromAlias): JoinCondition {
public static function parse($condition, string $join, string $alias, string $fromAlias): JoinCondition {
if ($condition === null) {
throw new InvalidPartitionedQueryException("Can't join on $join without a condition");
}
@@ -81,7 +85,7 @@ class JoinCondition {
return $result;
}
private static function parseSubCondition(string|ICompositeExpression $condition, string $join, string $alias, string $fromAlias): JoinCondition {
private static function parseSubCondition($condition, string $join, string $alias, string $fromAlias): JoinCondition {
if ($condition instanceof CompositeExpression) {
if ($condition->getType() === CompositeExpression::TYPE_OR) {
throw new InvalidPartitionedQueryException("Cannot join on $join with an OR expression");
@@ -14,13 +14,9 @@ use OC\DB\QueryBuilder\Sharded\AutoIncrementHandler;
use OC\DB\QueryBuilder\Sharded\ShardConnectionManager;
use OC\DB\QueryBuilder\Sharded\ShardedQueryBuilder;
use OCP\DB\IResult;
use OCP\DB\QueryBuilder\ICompositeExpression;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use OCP\IDBConnection;
use Override;
/**
* A special query builder that automatically splits queries that span across multiple database partitions[1].
@@ -45,7 +41,7 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
/** @var list<PartitionSplit> */
private array $partitions = [];
/** @var array{'select': string|IQueryFunction|IParameter|ILiteral, 'alias': ?string}[] */
/** @var array{'select': string|array, 'alias': ?string}[] */
private array $selects = [];
private ?PartitionSplit $mainPartition = null;
private bool $hasPositionalParameter = false;
@@ -79,7 +75,7 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
}
// we need to save selects until we know all the table aliases
public function select(...$selects): self {
public function select(...$selects) {
if (count($selects) === 1 && is_array($selects[0])) {
$selects = $selects[0];
}
@@ -88,13 +84,15 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
return $this;
}
public function addSelect(...$select): self {
$select = array_map(static fn ($select) => ['select' => $select, 'alias' => null], $select);
public function addSelect(...$select) {
$select = array_map(function ($select) {
return ['select' => $select, 'alias' => null];
}, $select);
$this->selects = array_merge($this->selects, $select);
return $this;
}
public function selectAlias(string|IQueryFunction|IParameter|ILiteral $select, string $alias): self {
public function selectAlias($select, $alias): self {
$this->selects[] = ['select' => $select, 'alias' => $alias];
return $this;
}
@@ -103,6 +101,9 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
* Ensure that a column is being selected by the query
*
* This is mainly used to ensure that the returned rows from both sides of a partition contains the columns of the join predicate
*
* @param string|IQueryFunction $column
* @return void
*/
private function ensureSelect(string|IQueryFunction $column, ?string $alias = null): void {
$checkColumn = $alias ?: $column;
@@ -189,30 +190,25 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
return null;
}
#[Override]
public function from($from, $alias = null): self {
public function from($from, $alias = null) {
if (is_string($from) && $partition = $this->getPartition($from)) {
$this->mainPartition = $partition;
if ($alias) {
$this->mainPartition->addAlias($from, $alias);
}
}
parent::from($from, $alias);
return $this;
return parent::from($from, $alias);
}
#[Override]
public function innerJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function innerJoin($fromAlias, $join, $alias, $condition = null): self {
return $this->join($fromAlias, $join, $alias, $condition);
}
#[Override]
public function leftJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function leftJoin($fromAlias, $join, $alias, $condition = null): self {
return $this->join($fromAlias, $join, $alias, $condition, PartitionQuery::JOIN_MODE_LEFT);
}
#[Override]
public function join(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null, string $joinMode = PartitionQuery::JOIN_MODE_INNER): self {
public function join($fromAlias, $join, $alias, $condition = null, $joinMode = PartitionQuery::JOIN_MODE_INNER): self {
if ($join instanceof IQueryFunction) {
$partition = null;
$fromPartition = null;
@@ -225,7 +221,7 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
/** @var string $join */
// join from the main db to a partition
$joinCondition = JoinCondition::parse((string)$condition, $join, $alias, $fromAlias);
$joinCondition = JoinCondition::parse($condition, $join, $alias, $fromAlias);
$partition->addAlias($join, $alias);
if (!isset($this->splitQueries[$partition->name])) {
@@ -252,7 +248,7 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
/** @var string $join */
// join from partition, to the main db
$joinCondition = JoinCondition::parse((string)$condition, $join, $alias, $fromAlias);
$joinCondition = JoinCondition::parse($condition, $join, $alias, $fromAlias);
if (str_starts_with($fromPartition->name, 'from_')) {
$partitionName = $fromPartition->name;
} else {
@@ -285,14 +281,11 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
} else {
// join within the main db or a partition
if ($joinMode === PartitionQuery::JOIN_MODE_INNER) {
parent::innerJoin($fromAlias, $join, $alias, $condition);
return $this;
return parent::innerJoin($fromAlias, $join, $alias, $condition);
} elseif ($joinMode === PartitionQuery::JOIN_MODE_LEFT) {
parent::leftJoin($fromAlias, $join, $alias, $condition);
return $this;
return parent::leftJoin($fromAlias, $join, $alias, $condition);
} elseif ($joinMode === PartitionQuery::JOIN_MODE_RIGHT) {
parent::rightJoin($fromAlias, $join, $alias, $condition);
return $this;
return parent::rightJoin($fromAlias, $join, $alias, $condition);
} else {
throw new \InvalidArgumentException("Invalid join mode: $joinMode");
}
@@ -340,11 +333,11 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
return $partitionPredicates;
}
public function where(...$predicates): self {
public function where(...$predicates) {
return $this->andWhere(...$predicates);
}
public function andWhere(...$where): self {
public function andWhere(...$where) {
if ($where) {
foreach ($this->splitPredicatesByParts($where) as $alias => $predicates) {
if (isset($this->splitQueries[$alias])) {
@@ -387,35 +380,30 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
return null;
}
public function update(string $update, ?string $alias = null): self {
parent::update($update, $alias);
return $this;
public function update($update = null, $alias = null) {
return parent::update($update, $alias);
}
public function insert(string $insert): self {
parent::insert($insert);
return $this;
public function insert($insert = null) {
return parent::insert($insert);
}
public function delete(string $delete, ?string $alias = null): self {
parent::delete($delete, $alias);
return $this;
public function delete($delete = null, $alias = null) {
return parent::delete($delete, $alias);
}
public function setMaxResults(?int $maxResults): self {
if ($maxResults !== null && $maxResults > 0) {
$this->limit = $maxResults;
public function setMaxResults($maxResults) {
if ($maxResults > 0) {
$this->limit = (int)$maxResults;
}
parent::setMaxResults($maxResults);
return $this;
return parent::setMaxResults($maxResults);
}
public function setFirstResult(int $firstResult): self {
public function setFirstResult($firstResult) {
if ($firstResult > 0) {
$this->offset = $firstResult;
$this->offset = (int)$firstResult;
}
parent::setFirstResult($firstResult);
return $this;
return parent::setFirstResult($firstResult);
}
public function executeQuery(?IDBConnection $connection = null): IResult {
@@ -456,7 +444,7 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder {
return parent::executeStatement($connection);
}
public function getSQL(): string {
public function getSQL() {
$this->applySelects();
return parent::getSQL();
}
File diff suppressed because it is too large Load Diff
+16 -4
View File
@@ -12,7 +12,11 @@ use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryFunction;
class QuoteHelper {
public function quoteColumnNames(array|string|ILiteral|IParameter|IQueryFunction $strings): array|string {
/**
* @param array|string|ILiteral|IParameter|IQueryFunction $strings string, Literal or Parameter
* @return array|string
*/
public function quoteColumnNames($strings) {
if (!is_array($strings)) {
return $this->quoteColumnName($strings);
}
@@ -25,18 +29,26 @@ class QuoteHelper {
return $return;
}
public function quoteColumnName(string|ILiteral|IParameter|IQueryFunction $string): string {
/**
* @param string|ILiteral|IParameter|IQueryFunction $string string, Literal or Parameter
* @return string
*/
public function quoteColumnName($string) {
if ($string instanceof IParameter || $string instanceof ILiteral || $string instanceof IQueryFunction) {
return (string)$string;
}
if ($string === 'null' || $string === '*') {
if ($string === null || $string === 'null' || $string === '*') {
return $string;
}
if (!is_string($string)) {
throw new \InvalidArgumentException('Only strings, Literals and Parameters are allowed');
}
$string = str_replace(' AS ', ' as ', $string);
if (substr_count($string, ' as ')) {
return implode(' as ', array_map($this->quoteColumnName(...), explode(' as ', $string, 2)));
return implode(' as ', array_map([$this, 'quoteColumnName'], explode(' as ', $string, 2)));
}
if (substr_count($string, '.')) {
@@ -12,13 +12,8 @@ use OC\DB\QueryBuilder\CompositeExpression;
use OC\DB\QueryBuilder\ExtendedQueryBuilder;
use OC\DB\QueryBuilder\Parameter;
use OCP\DB\IResult;
use OCP\DB\QueryBuilder\ICompositeExpression;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use OCP\IDBConnection;
use Override;
/**
* A special query builder that automatically distributes queries over multiple database shards.
@@ -88,13 +83,11 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
}
}
#[Override]
public function where(...$predicates): self {
public function where(...$predicates) {
return $this->andWhere(...$predicates);
}
#[Override]
public function andWhere(...$where): self {
public function andWhere(...$where) {
if ($where) {
foreach ($where as $predicate) {
$this->tryLoadShardKey($predicate);
@@ -165,18 +158,14 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
return [];
}
#[Override]
public function set($key, $value): self {
public function set($key, $value) {
if ($this->shardDefinition && $key === $this->shardDefinition->shardKey) {
// TODO dead code?
$updateShardKey = $value;
}
parent::set($key, $value);
return $this;
return parent::set($key, $value);
}
#[Override]
public function setValue(string $column, ILiteral|IParameter|IQueryFunction|string $value): self {
public function setValue($column, $value) {
if ($this->shardDefinition) {
if ($this->shardDefinition->isKey($column)) {
$this->primaryKeys[] = $value;
@@ -185,12 +174,10 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
$this->shardKeys[] = $value;
}
}
parent::setValue($column, $value);
return $this;
return parent::setValue($column, $value);
}
#[Override]
public function values(array $values): self {
public function values(array $values) {
foreach ($values as $column => $value) {
$this->setValue($column, $value);
}
@@ -206,35 +193,33 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
}
}
#[Override]
public function from(string|IQueryFunction $from, ?string $alias = null): self {
if (is_string($from)) {
public function from($from, $alias = null) {
if (is_string($from) && $from) {
$this->actOnTable($from);
}
parent::from($from, $alias);
return $this;
return parent::from($from, $alias);
}
#[Override]
public function update(string $update, ?string $alias = null): self {
$this->actOnTable($update);
parent::update($update, $alias);
return $this;
public function update($update = null, $alias = null) {
if (is_string($update) && $update) {
$this->actOnTable($update);
}
return parent::update($update, $alias);
}
#[Override]
public function insert(string $insert): self {
$this->insertTable = $insert;
$this->actOnTable($insert);
parent::insert($insert);
return $this;
public function insert($insert = null) {
if (is_string($insert) && $insert) {
$this->insertTable = $insert;
$this->actOnTable($insert);
}
return parent::insert($insert);
}
#[Override]
public function delete(string $delete, ?string $alias = null): self {
$this->actOnTable($delete);
parent::delete($delete, $alias);
return $this;
public function delete($delete = null, $alias = null) {
if (is_string($delete) && $delete) {
$this->actOnTable($delete);
}
return parent::delete($delete, $alias);
}
private function checkJoin(string $table): void {
@@ -250,83 +235,67 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
}
}
#[Override]
public function innerJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function innerJoin($fromAlias, $join, $alias, $condition = null) {
if (is_string($join)) {
$this->checkJoin($join);
}
parent::innerJoin($fromAlias, $join, $alias, $condition);
return $this;
return parent::innerJoin($fromAlias, $join, $alias, $condition);
}
#[Override]
public function leftJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function leftJoin($fromAlias, $join, $alias, $condition = null) {
if (is_string($join)) {
$this->checkJoin($join);
}
parent::leftJoin($fromAlias, $join, $alias, $condition);
return $this;
return parent::leftJoin($fromAlias, $join, $alias, $condition);
}
#[Override]
public function rightJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
public function rightJoin($fromAlias, $join, $alias, $condition = null) {
if ($this->shardDefinition) {
throw new InvalidShardedQueryException("Sharded query on {$this->shardDefinition->table} isn't allowed to right join");
}
parent::rightJoin($fromAlias, $join, $alias, $condition);
return $this;
return parent::rightJoin($fromAlias, $join, $alias, $condition);
}
#[Override]
public function join(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self {
$this->innerJoin($fromAlias, $join, $alias, $condition);
return $this;
public function join($fromAlias, $join, $alias, $condition = null) {
return $this->innerJoin($fromAlias, $join, $alias, $condition);
}
#[Override]
public function setMaxResults(?int $maxResults): self {
if ($maxResults !== null && $maxResults > 0) {
$this->limit = $maxResults;
public function setMaxResults($maxResults) {
if ($maxResults > 0) {
$this->limit = (int)$maxResults;
}
parent::setMaxResults($maxResults);
return $this;
return parent::setMaxResults($maxResults);
}
#[Override]
public function setFirstResult(int $firstResult): self {
public function setFirstResult($firstResult) {
if ($firstResult > 0) {
$this->offset = $firstResult;
$this->offset = (int)$firstResult;
}
if ($this->shardDefinition && count($this->shardDefinition->shards) > 1) {
// we have to emulate offset
return $this;
} else {
parent::setFirstResult($firstResult);
return $this;
return parent::setFirstResult($firstResult);
}
}
#[Override]
public function addOrderBy($sort, $order = null): self {
public function addOrderBy($sort, $order = null) {
if ($order !== null && !in_array(strtoupper((string)$order), ['ASC', 'DESC'], true)) {
$order = null;
}
$this->registerOrder((string)$sort, (string)($order ?? 'ASC'));
parent::addOrderBy($sort, $order);
return $this;
return parent::addOrderBy($sort, $order);
}
#[Override]
public function orderBy($sort, $order = null): self {
public function orderBy($sort, $order = null) {
if ($order !== null && !in_array(strtoupper((string)$order), ['ASC', 'DESC'], true)) {
$order = null;
}
$this->sortList = [];
$this->registerOrder((string)$sort, (string)($order ?? 'ASC'));
parent::orderBy($sort, $order);
return $this;
return parent::orderBy($sort, $order);
}
private function registerOrder(string $column, string $order): void {
@@ -339,7 +308,6 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
];
}
#[Override]
public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self {
if ($overwrite) {
$this->primaryKeys = [];
@@ -354,7 +322,6 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
return $this;
}
#[Override]
public function runAcrossAllShards(): self {
$this->allShards = true;
return $this;
@@ -397,7 +364,6 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
}
}
#[Override]
public function executeQuery(?IDBConnection $connection = null): IResult {
$this->validate();
if ($this->shardDefinition) {
@@ -407,7 +373,6 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
return parent::executeQuery($connection);
}
#[Override]
public function executeStatement(?IDBConnection $connection = null): int {
$this->validate();
if ($this->shardDefinition) {
@@ -438,7 +403,6 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
return parent::executeStatement($connection);
}
#[Override]
public function getLastInsertId(): int {
if ($this->lastInsertId) {
return $this->lastInsertId;
@@ -450,4 +414,6 @@ class ShardedQueryBuilder extends ExtendedQueryBuilder {
return parent::getLastInsertId();
}
}
}
@@ -27,9 +27,7 @@ abstract class TypedQueryBuilder implements ITypedQueryBuilder {
$this->validateColumn($column);
}
/** @psalm-suppress InternalMethod */
$this->select(...$columns);
return $this;
return $this->select(...$columns);
}
public function selectColumnsDistinct(string ...$columns): static {
@@ -37,8 +35,6 @@ abstract class TypedQueryBuilder implements ITypedQueryBuilder {
$this->validateColumn($column);
}
/** @psalm-suppress InternalMethod */
$this->selectDistinct($columns);
return $this;
return $this->selectDistinct($columns);
}
}
@@ -45,7 +45,7 @@ class CacheQueryBuilder extends ExtendedQueryBuilder {
return $this;
}
public function selectFileCache(?string $alias = null, bool $joinExtendedCache = true): self {
public function selectFileCache(?string $alias = null, bool $joinExtendedCache = true) {
$name = $alias ?: 'filecache';
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
'storage_mtime', 'encrypted', "$name.etag", "$name.permissions", 'checksum', 'unencrypted_size')
@@ -61,13 +61,13 @@ class CacheQueryBuilder extends ExtendedQueryBuilder {
return $this;
}
public function whereStorageId(int $storageId): self {
public function whereStorageId(int $storageId) {
$this->andWhere($this->expr()->eq('storage', $this->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
return $this;
}
public function whereFileId(int $fileId): self {
public function whereFileId(int $fileId) {
$alias = $this->alias;
if ($alias) {
$alias .= '.';
@@ -86,7 +86,7 @@ class CacheQueryBuilder extends ExtendedQueryBuilder {
return $this;
}
public function whereParent(int $parent): self {
public function whereParent(int $parent) {
$alias = $this->alias;
if ($alias) {
$alias .= '.';
@@ -99,7 +99,7 @@ class CacheQueryBuilder extends ExtendedQueryBuilder {
return $this;
}
public function whereParentInParameter(string $parameter): self {
public function whereParentInParameter(string $parameter) {
$alias = $this->alias;
if ($alias) {
$alias .= '.';
@@ -800,14 +800,24 @@ class ObjectStoreStorage extends Common implements IChunkedFileWrite {
$this->getCache()->update($stat['fileid'], $stat);
}
} catch (S3MultipartUploadException|S3Exception $e) {
$this->objectStore->abortMultipartUpload($urn, $writeToken);
$this->logger->error(
'Could not complete multipart upload ' . $urn . ' with uploadId ' . $writeToken,
'Unable to complete multipart upload for "' . $urn . '" (uploadId: "' . $writeToken . '")',
[
'app' => 'objectstore',
'exception' => $e,
]
);
try {
$this->objectStore->abortMultipartUpload($urn, $writeToken);
} catch (S3Exception $e) {
$this->logger->error(
'Unable to abort multipart upload for "' . $urn . '" (uploadId: "' . $writeToken . '") after completion error',
[
'app' => 'objectstore',
'exception' => $e,
]
);
}
throw new GenericFileException('Could not write chunked file');
}
return $size;
+1 -1
View File
@@ -145,7 +145,7 @@ class Factory implements IFactory {
if ($lang === null) {
return null;
}
$lang = preg_replace('/[^a-zA-Z0-9.;,=-]/', '', $lang);
$lang = preg_replace('/[^a-zA-Z0-9.;,=_-]/', '', $lang);
return str_replace('..', '', $lang);
}
+7 -2
View File
@@ -199,14 +199,19 @@ class Repair implements IOutput {
* @return list<IRepairStep>
*/
public static function getExpensiveRepairSteps(): array {
return [
$expensiveSteps = [
Server::get(OldGroupMembershipShares::class),
Server::get(RemoveBrokenProperties::class),
Server::get(RepairMimeTypes::class),
Server::get(DeleteSchedulingObjects::class),
Server::get(RemoveObjectProperties::class),
Server::get(CleanupShareTarget::class),
];
if (class_exists(CleanupShareTarget::class)) {
$expensiveSteps[] = Server::get(CleanupShareTarget::class);
}
return $expensiveSteps;
}
/**
+1 -1
View File
@@ -968,7 +968,7 @@ class DefaultShareProvider implements
->setFirstResult(0);
if ($limit !== -1) {
$qb->setMaxResults(max($limit - count($shares), 1));
$qb->setMaxResults($limit - count($shares));
}
// Filter by node if provided
+31 -43
View File
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -12,28 +14,27 @@ use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Share\IManager;
use OCP\Share\IShareHelper;
use Override;
class ShareHelper implements IShareHelper {
public function __construct(
private IManager $shareManager,
private readonly IManager $shareManager,
) {
}
/**
* @param Node $node
* @return array [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]]
*/
public function getPathsForAccessList(Node $node) {
#[Override]
public function getPathsForAccessList(Node $node): array {
$result = [
'users' => [],
'remotes' => [],
];
$accessList = $this->shareManager->getAccessList($node, true, true);
if (!empty($accessList['users'])) {
if (isset($accessList['users']) && $accessList['users'] !== []) {
$result['users'] = $this->getPathsForUsers($node, $accessList['users']);
}
if (!empty($accessList['remote'])) {
if (isset($accessList['remote']) && $accessList['remote'] !== []) {
$result['remotes'] = $this->getPathsForRemotes($node, $accessList['remote']);
}
@@ -60,20 +61,20 @@ class ShareHelper implements IShareHelper {
* 'test3' => '/cat',
* ],
*
* @param Node $node
* @param array[] $users
* @return array
* @param non-empty-array<string, array{node_id: int, node_path: string}> $users
* @return array<string, string>
*/
protected function getPathsForUsers(Node $node, array $users) {
/** @var array[] $byId */
protected function getPathsForUsers(Node $node, array $users): array {
/** @var array<int, array<string, string>> $byId */
$byId = [];
/** @var array[] $results */
/** @var array<string, string> $results */
$results = [];
foreach ($users as $uid => $info) {
if (!isset($byId[$info['node_id']])) {
$byId[$info['node_id']] = [];
}
$byId[$info['node_id']][$uid] = $info['node_path'];
}
@@ -82,15 +83,14 @@ class ShareHelper implements IShareHelper {
foreach ($byId[$node->getId()] as $uid => $path) {
$results[$uid] = $path;
}
unset($byId[$node->getId()]);
}
} catch (NotFoundException $e) {
return $results;
} catch (InvalidPathException $e) {
} catch (NotFoundException|InvalidPathException) {
return $results;
}
if (empty($byId)) {
if ($byId === []) {
return $results;
}
@@ -98,22 +98,18 @@ class ShareHelper implements IShareHelper {
$appendix = '/' . $node->getName();
while (!empty($byId)) {
try {
/** @var Node $item */
$item = $item->getParent();
if (!empty($byId[$item->getId()])) {
if ($byId[$item->getId()] !== []) {
foreach ($byId[$item->getId()] as $uid => $path) {
$results[$uid] = $path . $appendix;
}
unset($byId[$item->getId()]);
}
$appendix = '/' . $item->getName() . $appendix;
} catch (NotFoundException $e) {
return $results;
} catch (InvalidPathException $e) {
return $results;
} catch (NotPermittedException $e) {
} catch (NotFoundException|InvalidPathException|NotPermittedException) {
return $results;
}
}
@@ -141,27 +137,27 @@ class ShareHelper implements IShareHelper {
* 'test3' => ['token' => 't3', 'node_path' => '/SixTeen/TwentyThree/FortyTwo'],
* ],
*
* @param Node $node
* @param array[] $remotes
* @return array
* @param non-empty-array<string, array{node_id: int, token: string}> $remotes
* @return array<string, array{token: string, node_path: string}>
*/
protected function getPathsForRemotes(Node $node, array $remotes) {
/** @var array[] $byId */
protected function getPathsForRemotes(Node $node, array $remotes): array {
/** @var array<int, array<string, string>> $byId */
$byId = [];
/** @var array[] $results */
/** @var array<string, array{token: string, node_path: string}> $results */
$results = [];
foreach ($remotes as $cloudId => $info) {
if (!isset($byId[$info['node_id']])) {
$byId[$info['node_id']] = [];
}
$byId[$info['node_id']][$cloudId] = $info['token'];
}
$item = $node;
while (!empty($byId)) {
try {
if (!empty($byId[$item->getId()])) {
if ($byId[$item->getId()] !== []) {
$path = $this->getMountedPath($item);
foreach ($byId[$item->getId()] as $uid => $token) {
$results[$uid] = [
@@ -169,16 +165,12 @@ class ShareHelper implements IShareHelper {
'token' => $token,
];
}
unset($byId[$item->getId()]);
}
/** @var Node $item */
$item = $item->getParent();
} catch (NotFoundException $e) {
return $results;
} catch (InvalidPathException $e) {
return $results;
} catch (NotPermittedException $e) {
} catch (NotFoundException|InvalidPathException|NotPermittedException) {
return $results;
}
}
@@ -186,11 +178,7 @@ class ShareHelper implements IShareHelper {
return $results;
}
/**
* @param Node $node
* @return string
*/
protected function getMountedPath(Node $node) {
protected function getMountedPath(Node $node): string {
$path = $node->getPath();
$sections = explode('/', $path, 4);
return '/' . $sections[3];
+1 -1
View File
@@ -285,7 +285,7 @@ class Database extends ABackend implements
->orderBy($query->func()->lower('displayname'), 'ASC')
->addOrderBy('uid_lower', 'ASC')
->setMaxResults($limit)
->setFirstResult($offset ?? 0);
->setFirstResult($offset);
$result = $query->executeQuery();
$displayNames = [];
+8 -3
View File
@@ -36,7 +36,6 @@ use OCP\User\Events\BeforeUserCreatedEvent;
use OCP\User\Events\UserCreatedEvent;
use OCP\UserInterface;
use OCP\Util;
use Override;
use Psr\Log\LoggerInterface;
/**
@@ -729,10 +728,16 @@ class Manager extends PublicEmitter implements IUserManager {
}
}
#[Override]
/**
* Gets the list of user ids sorted by lastLogin, from most recent to least recent
*
* @param int|null $limit how many users to fetch (default: 25, max: 100)
* @param int $offset from which offset to fetch
* @param string $search search users based on search params
* @return list<string> list of user IDs
*/
public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
// We can't load all users who already logged in
/** @var int<1, 100> */
$limit = min(100, $limit ?: 25);
$connection = Server::get(IDBConnection::class);
@@ -47,10 +47,4 @@ interface ICompositeExpression {
* @since 8.2.0
*/
public function getType(): string;
/**
* Case the composite expression to string.
* @since 34.0.0
*/
public function __toString(): string;
}
+120 -134
View File
@@ -8,7 +8,6 @@
namespace OCP\DB\QueryBuilder;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use OCP\AppFramework\Attribute\Consumable;
/**
* This class provides a wrapper around Doctrine's ExpressionBuilder
@@ -16,7 +15,6 @@ use OCP\AppFramework\Attribute\Consumable;
*
* @psalm-taint-specialize
*/
#[Consumable(since: '8.2.0')]
interface IExpressionBuilder {
/**
* @since 9.0.0
@@ -52,15 +50,16 @@ interface IExpressionBuilder {
* // (u.type = ?) AND (u.role = ?)
* $expr->andX('u.type = ?', 'u.role = ?'));
*
* @param ICompositeExpression|string ...$x Optional clause. Defaults = null, but requires
* at least one defined when converting to string.
* @param mixed ...$x Optional clause. Defaults = null, but requires
* at least one defined when converting to string.
*
* @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
*/
public function andX(ICompositeExpression|string ...$x): ICompositeExpression;
public function andX(...$x): ICompositeExpression;
/**
* Creates a disjunction of the given boolean expressions.
@@ -71,24 +70,25 @@ interface IExpressionBuilder {
* // (u.type = ?) OR (u.role = ?)
* $qb->where($qb->expr()->orX('u.type = ?', 'u.role = ?'));
*
* @param ICompositeExpression|string ...$x Optional clause. Defaults = null, but requires
* at least one defined when converting to string.
* @param mixed ...$x Optional clause. Defaults = null, but requires
* at least one defined when converting to string.
*
* @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
*/
public function orX(ICompositeExpression|string ...$x): ICompositeExpression;
public function orX(...$x): ICompositeExpression;
/**
* Creates a comparison expression.
*
* @param string|ILiteral|IQueryFunction|IParameter $x The left expression.
* @param mixed $x The left expression.
* @param string $operator One of the IExpressionBuilder::* constants.
* @param string|ILiteral|IQueryFunction|IParameter $y The right expression.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
@@ -98,44 +98,45 @@ interface IExpressionBuilder {
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function comparison(string|ILiteral|IQueryFunction|IParameter $x, string $operator, string|ILiteral|IQueryFunction|IParameter $y, string|int|null $type = null): string;
public function comparison($x, string $operator, $y, $type = null): string;
/**
* Creates an equality comparison expression with the given arguments.
*
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generate a <left expr> = <right expr>. Example:
* When converted to string, it will generated a <left expr> = <right expr>. Example:
*
* [php]
* // u.id = ?
* $expr->eq('u.id', '?');
*
* @param string|ILiteral|IQueryFunction|IParameter $x The left expression.
* @param string|ILiteral|IQueryFunction|IParameter $y The right expression.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function eq(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, string|int|null $type = null): string;
public function eq($x, $y, $type = null): string;
/**
* Creates a non equality comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generate a <left expr> <> <right expr>. Example:
* When converted to string, it will generated a <left expr> <> <right expr>. Example:
*
* [php]
* // u.id <> 1
* $q->where($q->expr()->neq('u.id', '1'));
*
* @param string|ILiteral|IQueryFunction|IParameter $x The left expression.
* @param string|ILiteral|IQueryFunction|IParameter $y The right expression.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
@@ -144,21 +145,21 @@ interface IExpressionBuilder {
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function neq(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, string|int|null $type = null): string;
public function neq($x, $y, $type = null): string;
/**
* Creates a lower-than comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generate a <left expr> < <right expr>. Example:
* When converted to string, it will generated a <left expr> < <right expr>. Example:
*
* [php]
* // u.id < ?
* $q->where($q->expr()->lt('u.id', '?'));
*
* @param string|ILiteral|IQueryFunction|IParameter $x The left expression.
* @param string|ILiteral|IQueryFunction|IParameter $y The right expression.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
@@ -167,81 +168,76 @@ interface IExpressionBuilder {
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function lt(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, string|int|null $type = null): string;
public function lt($x, $y, $type = null): string;
/**
* Creates a lower-than-equal comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generate a <left expr> <= <right expr>. Example:
* When converted to string, it will generated a <left expr> <= <right expr>. Example:
*
* [php]
* // u.id <= ?
* $q->where($q->expr()->lte('u.id', '?'));
*
* @param string|ILiteral|IQueryFunction|IParameter $x The left expression.
* @param string|ILiteral|IQueryFunction|IParameter $y The right expression.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function lte(string|ILiteral|IQueryFunction|IParameter $x, string|ILiteral|IQueryFunction|IParameter $y, string|int|null $type = null): string;
public function lte($x, $y, $type = null): string;
/**
* Creates a greater-than comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generate a <left expr> > <right expr>. Example:
* When converted to string, it will generated a <left expr> > <right expr>. Example:
*
* [php]
* // u.id > ?
* $q->where($q->expr()->gt('u.id', '?'));
*
* @param string|ILiteral|IQueryFunction|IParameter $x The left expression.
* @param string|ILiteral|IQueryFunction|IParameter $y The right expression.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function gt(
string|ILiteral|IQueryFunction|IParameter $x,
string|ILiteral|IQueryFunction|IParameter $y,
string|int|null $type = null,
): string;
public function gt($x, $y, $type = null): string;
/**
* Creates a greater-than-equal comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generate a <left expr> >= <right expr>. Example:
* When converted to string, it will generated a <left expr> >= <right expr>. Example:
*
* [php]
* // u.id >= ?
* $q->where($q->expr()->gte('u.id', '?'));
*
* @param string|ILiteral|IQueryFunction|IParameter $x The left expression.
* @param string|ILiteral|IQueryFunction|IParameter $y The right expression.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed $x The left expression.
* @param mixed $y The right expression.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function gte(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
int|string|null $type = null,
): string;
public function gte($x, $y, $type = null): string;
/**
* Creates an IS NULL expression with the given arguments.
@@ -253,46 +249,27 @@ interface IExpressionBuilder {
*
* @psalm-taint-sink sql $x
*/
public function isNull(string|ILiteral|IParameter|IQueryFunction $x): string;
public function isNull($x): string;
/**
* Creates an IS NOT NULL expression with the given arguments.
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be restricted by IS NOT NULL.
*
* @return string
* @since 8.2.0
*
* @psalm-taint-sink sql $x
*/
public function isNotNull(string|ILiteral|IParameter|IQueryFunction $x): string;
public function isNotNull($x): string;
/**
* Creates a LIKE() comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by LIKE() comparison.
* @param ILiteral|IParameter|IQueryFunction|string $y Argument to be used in LIKE() comparison.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @since 8.2.0 - Parameter $type was added in 9.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function like(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string $y,
int|string|null $type = null,
): string;
/**
* Creates a NOT LIKE() comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by NOT LIKE() comparison.
* @param ILiteral|IParameter|IQueryFunction|string $y Argument to be used in NOT LIKE() comparison.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed $y Argument to be used in LIKE() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
@@ -301,19 +278,32 @@ interface IExpressionBuilder {
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function notLike(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string $y,
int|string|null $type = null,
): string;
public function like($x, $y, $type = null): string;
/**
* Creates an ILIKE() comparison expression with the given arguments.
* Creates a NOT LIKE() comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by ILIKE() comparison.
* @param ILiteral|IParameter|IQueryFunction|string $y Argument to be used in ILIKE() comparison.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by NOT LIKE() comparison.
* @param mixed $y Argument to be used in NOT LIKE() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function notLike($x, $y, $type = null): string;
/**
* Creates a ILIKE() comparison expression with the given arguments.
*
* @param string $x Field in string format to be inspected by ILIKE() comparison.
* @param mixed $y Argument to be used in ILIKE() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 9.0.0
@@ -322,39 +312,15 @@ interface IExpressionBuilder {
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function iLike(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string $y,
int|string|null $type = null,
): string;
public function iLike($x, $y, $type = null): string;
/**
* Creates an IN () comparison expression with the given arguments.
* Creates a IN () comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by IN() comparison.
* @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by IN() comparison.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @since 8.2.0 - Parameter $type was added in 9.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function in(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string|array $y,
int|string|null $type = null,
): string;
/**
* Creates a NOT IN () comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by NOT IN() comparison.
* @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by NOT IN() comparison.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
@@ -363,31 +329,46 @@ interface IExpressionBuilder {
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function notIn(
ILiteral|IParameter|IQueryFunction|string $x,
ILiteral|IParameter|IQueryFunction|string|array $y,
int|string|null $type = null,
): string;
public function in($x, $y, $type = null): string;
/**
* Creates a `$x = ''` statement, because Oracle needs a different check
* Creates a NOT IN () comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by NOT IN() comparison.
* @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by NOT IN() comparison.
* @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 8.2.0 - Parameter $type was added in 9.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function notIn($x, $y, $type = null): string;
/**
* Creates a $x = '' statement, because Oracle needs a different check
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be inspected by the comparison.
* @return string
* @since 13.0.0
*
* @psalm-taint-sink sql $x
*/
public function emptyString(string|ILiteral|IParameter|IQueryFunction $x): string;
public function emptyString($x): string;
/**
* Creates a `$x <> ''` statement, because Oracle needs a different check
*
* @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be inspected by the comparison.
* @return string
* @since 13.0.0
*
* @psalm-taint-sink sql $x
*/
public function nonEmptyString(string|ILiteral|IParameter|IQueryFunction $x): string;
public function nonEmptyString($x): string;
/**
@@ -401,41 +382,46 @@ interface IExpressionBuilder {
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
*/
public function bitwiseAnd(string|ILiteral $x, int $y): IQueryFunction;
public function bitwiseAnd($x, int $y): IQueryFunction;
/**
* Creates a bitwise OR comparison
*
* @param string|ILiteral $x The field or value to check
* @param int $y Bitmap that must be set
* @return IQueryFunction
* @since 12.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
*/
public function bitwiseOr(string|ILiteral $x, int $y): IQueryFunction;
public function bitwiseOr($x, int $y): IQueryFunction;
/**
* Quotes a given input parameter.
*
* @param mixed $input The parameter to be quoted.
* @param IQueryBuilder::PARAM_* $type One of the IQueryBuilder::PARAM_* constants
* @param int $type One of the IQueryBuilder::PARAM_* constants
*
* @return ILiteral
* @since 8.2.0
*
* @psalm-taint-sink sql $input
* @psalm-taint-sink sql $type
*/
public function literal(mixed $input, int|string $type = IQueryBuilder::PARAM_STR): ILiteral;
public function literal($input, $type = IQueryBuilder::PARAM_STR): ILiteral;
/**
* Returns a IQueryFunction that casts the column to the given type
*
* @param IQueryBuilder::PARAM_* $type
* @param string|IQueryFunction $column
* @param mixed $type One of IQueryBuilder::PARAM_*
* @psalm-param IQueryBuilder::PARAM_* $type
* @return IQueryFunction
* @since 9.0.0
*
* @psalm-taint-sink sql $column
* @psalm-taint-sink sql $type
*/
public function castColumn(string|IQueryFunction|ILiteral|IParameter $column, int|string $type): IQueryFunction;
public function castColumn($column, $type): IQueryFunction;
}
+28 -34
View File
@@ -6,14 +6,11 @@
*/
namespace OCP\DB\QueryBuilder;
use OCP\AppFramework\Attribute\Consumable;
/**
* This class provides a builder for sql some functions
*
* @since 12.0.0
*/
#[Consumable(since: '12.0.0')]
interface IFunctionBuilder {
/**
* Calculates the MD5 hash of a given input
@@ -23,7 +20,7 @@ interface IFunctionBuilder {
* @return IQueryFunction
* @since 12.0.0
*/
public function md5(string|ILiteral|IParameter|IQueryFunction $input): IQueryFunction;
public function md5($input): IQueryFunction;
/**
* Combines two input strings
@@ -34,7 +31,7 @@ interface IFunctionBuilder {
* @return IQueryFunction
* @since 12.0.0
*/
public function concat(string|ILiteral|IParameter|IQueryFunction $x, string|ILiteral|IParameter|IQueryFunction ...$expr): IQueryFunction;
public function concat($x, ...$expr): IQueryFunction;
/**
* Returns a string which is the concatenation of all non-NULL values of X
@@ -50,7 +47,7 @@ interface IFunctionBuilder {
* @return IQueryFunction
* @since 24.0.0
*/
public function groupConcat(string|IQueryFunction $expr, ?string $separator = ','): IQueryFunction;
public function groupConcat($expr, ?string $separator = ','): IQueryFunction;
/**
* Takes a substring from the input string
@@ -59,29 +56,29 @@ interface IFunctionBuilder {
* @param string|ILiteral|IParameter|IQueryFunction $start The start of the substring, note that counting starts at 1
* @param null|ILiteral|IParameter|IQueryFunction $length The length of the substring
*
* @return IQueryFunction
* @since 12.0.0
*/
public function substring(
string|ILiteral|IParameter|IQueryFunction $input,
string|ILiteral|IParameter|IQueryFunction $start,
null|ILiteral|IParameter|IQueryFunction $length = null,
): IQueryFunction;
public function substring($input, $start, $length = null): IQueryFunction;
/**
* Takes the sum of all rows in a column
*
* @param string|ILiteral|IParameter|IQueryFunction $field the column to sum
*
* @return IQueryFunction
* @since 12.0.0
*/
public function sum(string|ILiteral|IParameter|IQueryFunction $field): IQueryFunction;
public function sum($field): IQueryFunction;
/**
* Transforms a string field or value to lower case
*
* @param string|ILiteral|IParameter|IQueryFunction $field
* @return IQueryFunction
* @since 14.0.0
*/
public function lower(string|ILiteral|IParameter|IQueryFunction $field): IQueryFunction;
public function lower($field): IQueryFunction;
/**
* @param string|ILiteral|IParameter|IQueryFunction $x The first input field or number
@@ -89,10 +86,7 @@ interface IFunctionBuilder {
* @return IQueryFunction
* @since 14.0.0
*/
public function add(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction;
public function add($x, $y): IQueryFunction;
/**
* @param string|ILiteral|IParameter|IQueryFunction $x The first input field or number
@@ -100,10 +94,7 @@ interface IFunctionBuilder {
* @return IQueryFunction
* @since 14.0.0
*/
public function subtract(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction;
public function subtract($x, $y): IQueryFunction;
/**
* @param string|ILiteral|IParameter|IQueryFunction $count The input to be counted
@@ -112,7 +103,7 @@ interface IFunctionBuilder {
* @return IQueryFunction
* @since 14.0.0
*/
public function count(string|ILiteral|IParameter|IQueryFunction $count = '', string $alias = ''): IQueryFunction;
public function count($count = '', $alias = ''): IQueryFunction;
/**
* @param string|ILiteral|IParameter|IQueryFunction $field The input to be measured
@@ -121,15 +112,16 @@ interface IFunctionBuilder {
* @return IQueryFunction
* @since 24.0.0
*/
public function octetLength(string|ILiteral|IParameter|IQueryFunction $field, string $alias = ''): IQueryFunction;
public function octetLength($field, $alias = ''): IQueryFunction;
/**
* @param string|ILiteral|IParameter|IQueryFunction $field The input to be measured
* @param string $alias Alias for the length
*
* @return IQueryFunction
* @since 24.0.0
*/
public function charLength(string|ILiteral|IParameter|IQueryFunction $field, string $alias = ''): IQueryFunction;
public function charLength($field, $alias = ''): IQueryFunction;
/**
* Takes the maximum of all rows in a column
@@ -138,9 +130,10 @@ interface IFunctionBuilder {
*
* @param string|ILiteral|IParameter|IQueryFunction $field the column to maximum
*
* @return IQueryFunction
* @since 18.0.0
*/
public function max(string|ILiteral|IParameter|IQueryFunction $field): IQueryFunction;
public function max($field): IQueryFunction;
/**
* Takes the minimum of all rows in a column
@@ -149,33 +142,34 @@ interface IFunctionBuilder {
*
* @param string|ILiteral|IParameter|IQueryFunction $field the column to minimum
*
* @return IQueryFunction
* @since 18.0.0
*/
public function min(string|ILiteral|IParameter|IQueryFunction $field): IQueryFunction;
public function min($field): IQueryFunction;
/**
* Takes the maximum of multiple values
*
* If you want to get the maximum value of all rows in a column, use `max` instead
*
* @param string|ILiteral|IParameter|IQueryFunction $x
* @param string|ILiteral|IParameter|IQueryFunction $y
* @return IQueryFunction
* @since 18.0.0
*/
public function greatest(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction;
public function greatest($x, $y): IQueryFunction;
/**
* Takes the minimum of multiple values
*
* If you want to get the minimum value of all rows in a column, use `min` instead
*
* @param string|ILiteral|IParameter|IQueryFunction $x
* @param string|ILiteral|IParameter|IQueryFunction $y
* @return IQueryFunction
* @since 18.0.0
*/
public function least(
string|ILiteral|IParameter|IQueryFunction $x,
string|ILiteral|IParameter|IQueryFunction $y,
): IQueryFunction;
public function least($x, $y): IQueryFunction;
/**
* Get the current date and time as a UNIX timestamp.
+125 -111
View File
@@ -11,7 +11,6 @@ use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\Types;
use OCP\AppFramework\Attribute\Consumable;
use OCP\DB\Exception;
use OCP\DB\IResult;
use OCP\IDBConnection;
@@ -22,7 +21,6 @@ use OCP\IDBConnection;
*
* @psalm-taint-specialize
*/
#[Consumable(since: '8.2.0')]
interface IQueryBuilder {
/**
* @since 9.0.0
@@ -128,7 +126,7 @@ interface IQueryBuilder {
* owncloud database prefix automatically.
* @since 8.2.0
*/
public function automaticTablePrefix(bool $enabled): void;
public function automaticTablePrefix($enabled);
/**
* Gets an ExpressionBuilder used for object-oriented construction of query expressions.
@@ -144,9 +142,10 @@ interface IQueryBuilder {
* For more complex expression construction, consider storing the expression
* builder object in a local variable.
*
* @return \OCP\DB\QueryBuilder\IExpressionBuilder
* @since 8.2.0
*/
public function expr(): IExpressionBuilder;
public function expr();
/**
* Gets an FunctionBuilder used for object-oriented construction of query functions.
@@ -162,24 +161,26 @@ interface IQueryBuilder {
* For more complex function construction, consider storing the function
* builder object in a local variable.
*
* @return \OCP\DB\QueryBuilder\IFunctionBuilder
* @since 12.0.0
*/
public function func(): IFunctionBuilder;
public function func();
/**
* Gets the type of the currently built query.
*
* @deprecated 34.0.0 If necessary, track the type of the query being built outside of the builder.
* @return integer
* @since 8.2.0
*/
public function getType(): int;
public function getType();
/**
* Gets the associated DBAL Connection for this query builder.
*
* @return \OCP\IDBConnection
* @since 8.2.0
*/
public function getConnection(): IDBConnection;
public function getConnection();
/**
* Gets the state of this query builder instance.
@@ -189,7 +190,7 @@ interface IQueryBuilder {
* @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(): int;
public function getState();
/**
* Execute for select statements
@@ -228,7 +229,7 @@ interface IQueryBuilder {
* @return string The SQL query string.
* @since 8.2.0
*/
public function getSQL(): string;
public function getSQL();
/**
* Sets a query parameter for the query being constructed.
@@ -244,11 +245,11 @@ interface IQueryBuilder {
* @param string|integer $key The parameter position or name.
* @param mixed $value The parameter value.
* @param string|null|int $type One of the IQueryBuilder::PARAM_* constants.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setParameter(string|int $key, mixed $value, string|null|int $type = null): self;
public function setParameter($key, $value, $type = null);
/**
* Sets a collection of query parameters for the query being constructed.
@@ -264,13 +265,13 @@ interface IQueryBuilder {
* ));
* </code>
*
* @param array<string|int, mixed> $params The query parameters to set.
* @param array<string|int, self::PARAM_*> $types The query parameters types to set.
* @return $this This QueryBuilder instance.
* @param array $params The query parameters to set.
* @param array $types The query parameters types to set.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setParameters(array $params, array $types = []): self;
public function setParameters(array $params, array $types = []);
/**
* Gets all defined query parameters for the query being constructed indexed by parameter index or name.
@@ -278,73 +279,73 @@ interface IQueryBuilder {
* @return array The currently defined query parameters indexed by parameter index or name.
* @since 8.2.0
*/
public function getParameters(): array;
public function getParameters();
/**
* Gets a (previously set) query parameter of the query being constructed.
*
* @param int|string $key The key (index or name) of the bound parameter.
* @param mixed $key The key (index or name) of the bound parameter.
*
* @return mixed The value of the bound parameter.
* @since 8.2.0
*/
public function getParameter(int|string $key): mixed;
public function getParameter($key);
/**
* Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
*
* @return array<string|int, self::PARAM_*> The currently defined query parameter types indexed by parameter index or name.
* @return array The currently defined query parameter types indexed by parameter index or name.
* @since 8.2.0
*/
public function getParameterTypes(): array;
public function getParameterTypes();
/**
* Gets a (previously set) query parameter type of the query being constructed.
*
* @param int|string $key The key (index or name) of the bound parameter type.
* @param mixed $key The key (index or name) of the bound parameter type.
*
* @return self::PARAM_* The value of the bound parameter type.
* @return mixed The value of the bound parameter type.
* @since 8.2.0
*/
public function getParameterType(int|string $key): int|string;
public function getParameterType($key);
/**
* Sets the position of the first result to retrieve (the "offset").
*
* @param non-negative-int $firstResult The first result to return.
* @param int $firstResult The first result to return.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setFirstResult(int $firstResult): self;
public function setFirstResult($firstResult);
/**
* Gets the position of the first result the query object was set to retrieve (the "offset").
* Returns 0 if {@link setFirstResult} was not applied to this QueryBuilder.
*
* @return non-negative-int The position of the first result.
* @return int The position of the first result.
* @since 8.2.0
*/
public function getFirstResult(): int;
public function getFirstResult();
/**
* Sets the maximum number of results to retrieve (the "limit").
*
* @param positive-int|null $maxResults The maximum number of results to retrieve.
* @return $this This QueryBuilder instance.
* @param int|null $maxResults The maximum number of results to retrieve.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setMaxResults(?int $maxResults): self;
public function setMaxResults($maxResults);
/**
* 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 positive-int|null The maximum number of results.
* @return int|null The maximum number of results.
* @since 8.2.0
*/
public function getMaxResults(): ?int;
public function getMaxResults();
/**
* Specifies an item that is to be returned in the query result.
@@ -357,14 +358,14 @@ interface IQueryBuilder {
* ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
* </code>
*
* @param string|list<string>|IQueryFunction|ILiteral ...$selects The selection expressions.
* @return $this This QueryBuilder instance.
* @param mixed ...$selects The selection expressions.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $selects
*/
public function select(mixed...$selects): self;
public function select(...$selects);
/**
* Specifies an item that is to be returned with a different name in the query result.
@@ -376,16 +377,16 @@ interface IQueryBuilder {
* ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
* </code>
*
* @param string|IParameter|IQueryFunction $select The selection expressions.
* @param mixed $select The selection expressions.
* @param string $alias The column alias used in the constructed query.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.1
*
* @psalm-taint-sink sql $select
* @psalm-taint-sink sql $alias
*/
public function selectAlias(string|IParameter|IQueryFunction|ILiteral $select, string $alias): self;
public function selectAlias($select, $alias): self;
/**
* Specifies an item that is to be returned uniquely in the query result.
@@ -396,14 +397,14 @@ interface IQueryBuilder {
* ->from('users');
* </code>
*
* @param string|string[] $select The selection expressions.
* @return $this This QueryBuilder instance.
* @param mixed $select The selection expressions.
*
* @return $this This QueryBuilder instance.
* @since 9.0.0
*
* @psalm-taint-sink sql $select
*/
public function selectDistinct(string|array $select): self;
public function selectDistinct($select);
/**
* Adds an item that is to be returned in the query result.
@@ -416,14 +417,14 @@ interface IQueryBuilder {
* ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
* </code>
*
* @param string|IParameter|IQueryFunction|ILiteral|list<string> ...$select The selection expression.
* @return $this This QueryBuilder instance.
* @param mixed ...$select The selection expression.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $select
*/
public function addSelect(mixed ...$select): self;
public function addSelect(...$select);
/**
* Turns the query being built into a bulk delete query that ranges over
@@ -437,15 +438,15 @@ interface IQueryBuilder {
* </code>
*
* @param string $delete The table whose rows are subject to the deletion.
* @param ?string $alias The table alias used in the constructed query.
* @param string $alias The table alias used in the constructed query.
*
* @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
* @return $this This QueryBuilder instance.
*
* @psalm-taint-sink sql $delete
*/
public function delete(string $delete, ?string $alias = null): self;
public function delete($delete = null, $alias = null);
/**
* Turns the query being built into a bulk update query that ranges over
@@ -460,15 +461,15 @@ interface IQueryBuilder {
* </code>
*
* @param string $update The table whose rows are subject to the update.
* @param ?string $alias The table alias used in the constructed query.
* @return $this This QueryBuilder instance.
* @param string $alias The table alias used in the constructed query.
*
* @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
*/
public function update(string $update, ?string $alias = null): self;
public function update($update = null, $alias = null);
/**
* Turns the query being built into an insert query that inserts into
@@ -486,13 +487,13 @@ interface IQueryBuilder {
* </code>
*
* @param string $insert The table into which the rows should be inserted.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $insert
*/
public function insert(string $insert): self;
public function insert($insert = null);
/**
* Creates and adds a query root corresponding to the table identified by the
@@ -506,13 +507,13 @@ interface IQueryBuilder {
*
* @param string|IQueryFunction $from The table.
* @param string|null $alias The alias of the table.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $from
*/
public function from(string|IQueryFunction $from, ?string $alias = null): self;
public function from($from, $alias = null);
/**
* Creates and adds a join to the query.
@@ -526,10 +527,10 @@ interface IQueryBuilder {
*
* @param string $fromAlias The alias that points to a from clause.
* @param string|IQueryFunction $join The table name to join.
* @param ?string $alias The alias of the join table.
* @param string $alias The alias of the join table.
* @param string|ICompositeExpression|null $condition The condition for the join.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $fromAlias
@@ -537,7 +538,7 @@ interface IQueryBuilder {
* @psalm-taint-sink sql $alias
* @psalm-taint-sink sql $condition
*/
public function join(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self;
public function join($fromAlias, $join, $alias, $condition = null);
/**
* Creates and adds a join to the query.
@@ -551,10 +552,10 @@ interface IQueryBuilder {
*
* @param string $fromAlias The alias that points to a from clause.
* @param string|IQueryFunction $join The table name to join.
* @param ?string $alias The alias of the join table.
* @param string $alias The alias of the join table.
* @param string|ICompositeExpression|null $condition The condition for the join.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $fromAlias
@@ -562,7 +563,7 @@ interface IQueryBuilder {
* @psalm-taint-sink sql $alias
* @psalm-taint-sink sql $condition
*/
public function innerJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self;
public function innerJoin($fromAlias, $join, $alias, $condition = null);
/**
* Creates and adds a left join to the query.
@@ -576,10 +577,10 @@ interface IQueryBuilder {
*
* @param string $fromAlias The alias that points to a from clause.
* @param string|IQueryFunction $join The table name to join.
* @param ?string $alias The alias of the join table.
* @param string $alias The alias of the join table.
* @param string|ICompositeExpression|null $condition The condition for the join.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
* @since 30.0.0 Allow passing IQueryFunction as parameter for `$join` to allow join with a sub-query.
*
@@ -588,7 +589,7 @@ interface IQueryBuilder {
* @psalm-taint-sink sql $alias
* @psalm-taint-sink sql $condition
*/
public function leftJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self;
public function leftJoin($fromAlias, $join, $alias, $condition = null);
/**
* Creates and adds a right join to the query.
@@ -602,10 +603,10 @@ interface IQueryBuilder {
*
* @param string $fromAlias The alias that points to a from clause.
* @param string|IQueryFunction $join The table name to join.
* @param ?string $alias The alias of the join table.
* @param string $alias The alias of the join table.
* @param string|ICompositeExpression|null $condition The condition for the join.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $fromAlias
@@ -613,7 +614,7 @@ interface IQueryBuilder {
* @psalm-taint-sink sql $alias
* @psalm-taint-sink sql $condition
*/
public function rightJoin(string $fromAlias, string|IQueryFunction $join, ?string $alias, string|ICompositeExpression|null $condition = null): self;
public function rightJoin($fromAlias, $join, $alias, $condition = null);
/**
* Sets a new value for a column in a bulk update query.
@@ -627,14 +628,14 @@ interface IQueryBuilder {
*
* @param string $key The column to set.
* @param ILiteral|IParameter|IQueryFunction|string $value The value, expression, placeholder, etc.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $key
* @psalm-taint-sink sql $value
*/
public function set(string $key, ILiteral|IParameter|IQueryFunction|string $value): self;
public function set($key, $value);
/**
* Specifies one or more restrictions to the query result.
@@ -659,14 +660,14 @@ interface IQueryBuilder {
* ->where($or);
* </code>
*
* @param string|IParameter|IQueryFunction|ILiteral|ICompositeExpression $predicates The restriction predicates.
* @return $this This QueryBuilder instance.
* @param mixed $predicates The restriction predicates.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $predicates
*/
public function where(mixed ...$predicates): self;
public function where(...$predicates);
/**
* Adds one or more restrictions to the query results, forming a logical
@@ -680,7 +681,8 @@ interface IQueryBuilder {
* ->andWhere('u.is_active = 1');
* </code>
*
* @param string|IParameter|IQueryFunction|ILiteral|ICompositeExpression ...$where The query restrictions.
* @param mixed ...$where The query restrictions.
*
* @return $this This QueryBuilder instance.
*
* @see where()
@@ -688,7 +690,7 @@ interface IQueryBuilder {
*
* @psalm-taint-sink sql $where
*/
public function andWhere(mixed ...$where): self;
public function andWhere(...$where);
/**
* Adds one or more restrictions to the query results, forming a logical
@@ -702,7 +704,8 @@ interface IQueryBuilder {
* ->orWhere('u.id = 2');
* </code>
*
* @param string|IParameter|IQueryFunction|ILiteral|ICompositeExpression ...$where The WHERE statement.
* @param mixed ...$where The WHERE statement.
*
* @return $this This QueryBuilder instance.
*
* @see where()
@@ -710,7 +713,7 @@ interface IQueryBuilder {
*
* @psalm-taint-sink sql $where
*/
public function orWhere(mixed ...$where): self;
public function orWhere(...$where);
/**
* Specifies a grouping over the results of the query.
@@ -723,14 +726,14 @@ interface IQueryBuilder {
* ->groupBy('u.id');
* </code>
*
* @param string|IParameter|IQueryFunction|ILiteral|list<string> ...$groupBys The grouping expression.
* @return $this This QueryBuilder instance.
* @param mixed ...$groupBys The grouping expression.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $groupBys
*/
public function groupBy(mixed ...$groupBys): self;
public function groupBy(...$groupBys);
/**
* Adds a grouping expression to the query.
@@ -743,14 +746,14 @@ interface IQueryBuilder {
* ->addGroupBy('u.createdAt')
* </code>
*
* @param string|IParameter|IQueryFunction|ILiteral|list<string> ...$groupBy The grouping expression.
* @return $this This QueryBuilder instance.
* @param mixed ...$groupBy The grouping expression.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $groupby
*/
public function addGroupBy(mixed ...$groupBy): self;
public function addGroupBy(...$groupBy);
/**
* Sets a value for a column in an insert query.
@@ -768,14 +771,14 @@ interface IQueryBuilder {
*
* @param string $column The column into which the value should be inserted.
* @param IParameter|IQueryFunction|string $value The value that should be inserted into the column.
* @return $this This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $column
* @psalm-taint-sink sql $value
*/
public function setValue(string $column, ILiteral|IParameter|IQueryFunction|string $value): self;
public function setValue($column, $value);
/**
* Specifies values for an insert query indexed by column names.
@@ -792,60 +795,60 @@ interface IQueryBuilder {
* );
* </code>
*
* @param array<string, IParameter|ILiteral|IFunctionBuilder|string|int> $values The values to specify for the insert query indexed by column names.
* @return $this This QueryBuilder instance.
* @param array $values The values to specify for the insert query indexed by column names.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $values
*/
public function values(array $values): self;
public function values(array $values);
/**
* Specifies a restriction over the groups of the query.
* Replaces any previous having restrictions, if any.
*
* @param string|IParameter|IQueryFunction|ILiteral ...$having The restriction over the groups.
* @return $this This QueryBuilder instance.
* @param mixed ...$having The restriction over the groups.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $having
*/
public function having(mixed ...$having): self;
public function 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 This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $andHaving
*/
public function andHaving(...$having): self;
public function andHaving(...$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 This QueryBuilder instance.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
*
* @psalm-taint-sink sql $having
*/
public function orHaving(...$having): self;
public function orHaving(...$having);
/**
* Specifies an ordering for the query results.
* Replaces any previously specified orderings, if any.
*
* @param string|IQueryFunction|ILiteral|IParameter $sort The ordering expression.
* @param 'ASC'|'DESC'|'asc'|'desc'|null $order The ordering direction.
* @param string $order The ordering direction.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
@@ -853,13 +856,13 @@ interface IQueryBuilder {
* @psalm-taint-sink sql $sort
* @psalm-taint-sink sql $order
*/
public function orderBy(string|IQueryFunction|ILiteral|IParameter $sort, ?string $order = null): self;
public function orderBy($sort, $order = null);
/**
* Adds an ordering to the query results.
*
* @param string|ILiteral|IParameter|IQueryFunction $sort The ordering expression.
* @param 'ASC'|'DESC'|'asc'|'desc'|null $order The ordering direction.
* @param string $order The ordering direction.
*
* @return $this This QueryBuilder instance.
* @since 8.2.0
@@ -867,25 +870,29 @@ interface IQueryBuilder {
* @psalm-taint-sink sql $sort
* @psalm-taint-sink sql $order
*/
public function addOrderBy(string|ILiteral|IParameter|IQueryFunction $sort, ?string $order = null): self;
public function addOrderBy($sort, $order = null);
/**
* Gets a query part by its name.
*
* @param string $queryPartName
*
* @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(string $queryPartName): mixed;
public function getQueryPart($queryPartName);
/**
* Gets all query parts.
*
* @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(): array;
public function getQueryParts();
/**
* Resets SQL parts.
@@ -897,7 +904,7 @@ interface IQueryBuilder {
* @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(?array $queryPartNames = null): self;
public function resetQueryParts($queryPartNames = null);
/**
* Resets a single SQL part.
@@ -909,7 +916,7 @@ interface IQueryBuilder {
* @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(string $queryPartName): self;
public function resetQueryPart($queryPartName);
/**
* Creates a new named parameter and bind the value $value to it.
@@ -943,7 +950,7 @@ interface IQueryBuilder {
*
* @psalm-taint-escape sql
*/
public function createNamedParameter(mixed $value, $type = self::PARAM_STR, ?string $placeHolder = null): IParameter;
public function createNamedParameter($value, $type = self::PARAM_STR, $placeHolder = null);
/**
* Creates a new positional parameter and bind the given value to it.
@@ -970,7 +977,7 @@ interface IQueryBuilder {
*
* @psalm-taint-escape sql
*/
public function createPositionalParameter(mixed $value, $type = self::PARAM_STR): IParameter;
public function createPositionalParameter($value, $type = self::PARAM_STR);
/**
* Creates a new parameter
@@ -991,7 +998,7 @@ interface IQueryBuilder {
*
* @psalm-taint-escape sql
*/
public function createParameter(string $name): IParameter;
public function createParameter($name);
/**
* Creates a new function.
@@ -1020,7 +1027,7 @@ interface IQueryBuilder {
*
* @psalm-taint-sink sql $call
*/
public function createFunction(string $call): IQueryFunction;
public function createFunction($call);
/**
* Used to get the id of the last inserted element
@@ -1040,11 +1047,13 @@ interface IQueryBuilder {
* @since 9.0.0
* @since 24.0.0 accepts IQueryFunction as parameter
*/
public function getTableName(string|IQueryFunction $table): string;
public function getTableName($table);
/**
* Returns the table name with database prefix as needed by the implementation
*
* @param string $table
* @return string
* @since 30.0.0
*/
public function prefixTableName(string $table): string;
@@ -1052,14 +1061,19 @@ interface IQueryBuilder {
/**
* Returns the column name quoted and with table alias prefix as needed by the implementation
*
* @param string $column
* @param string $tableAlias
* @return string
* @since 9.0.0
*/
public function getColumnName(string $column, string $tableAlias = ''): string;
public function getColumnName($column, $tableAlias = '');
/**
* Provide a hint for the shard key for queries where this can't be detected otherwise
*
* @return $this This QueryBuilder instance.
* @param string $column
* @param mixed $value
* @return $this
* @since 30.0.0
*/
public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self;
@@ -1067,7 +1081,7 @@ interface IQueryBuilder {
/**
* Set the query to run across all shards if sharding is enabled.
*
* @return $this This QueryBuilder instance.
* @return $this
* @since 30.0.0
*/
public function runAcrossAllShards(): self;
@@ -1075,7 +1089,7 @@ interface IQueryBuilder {
/**
* Get a list of column names that are expected in the query output
*
* @return string[]
* @return array
* @since 30.0.0
*/
public function getOutputColumns(): array;
@@ -31,13 +31,15 @@ interface ITypedQueryBuilder extends IQueryBuilder {
* @internal This method does not work with {@see self}. Use {@see self::selectColumns()} or {@see self::selectAlias()} instead.
*/
#[Override]
public function select(...$selects): self;
public function select(...$selects);
/**
* @template NewS of string
* @param NewS ...$columns The columns to select. They are not allowed to contain table names or aliases, or asterisks. Use {@see self::selectAlias()} for that.
* @psalm-this-out self<S|NewS>
* @return $this
* @since 34.0.0
* @note Psalm has a bug that prevents inferring the correct type in chained calls: https://github.com/vimeo/psalm/issues/8803. Convert the chained calls to standalone calls or switch to PHPStan, which suffered the same bug in the past, but fixed it in 2.1.5: https://github.com/phpstan/phpstan/issues/8439
*/
public function selectColumns(string ...$columns): self;
@@ -46,13 +48,15 @@ interface ITypedQueryBuilder extends IQueryBuilder {
* @internal This method does not work with {@see self}. Use {@see self::selectColumnDistinct()} or {@see self::selectAlias()} instead.
*/
#[Override]
public function selectDistinct(string|array $select): self;
public function selectDistinct($select);
/**
* @template NewS of string
* @param NewS ...$columns The columns to select distinct. They are not allowed to contain table names or aliases, or asterisks. Use {@see self::selectAlias()} for that.
* @psalm-this-out self<S|NewS>
* @return $this
* @since 34.0.0
* @note Psalm has a bug that prevents inferring the correct type in chained calls: https://github.com/vimeo/psalm/issues/8803. Convert the chained calls to standalone calls or switch to PHPStan, which suffered the same bug in the past, but fixed it in 2.1.5: https://github.com/phpstan/phpstan/issues/8439
*/
public function selectColumnsDistinct(string ...$columns): self;
@@ -61,16 +65,252 @@ interface ITypedQueryBuilder extends IQueryBuilder {
* @internal This method does not work with {@see self}. Use {@see self::selectColumns()} or {@see self::selectAlias()} instead.
*/
#[Override]
public function addSelect(...$select): self;
public function addSelect(...$select);
/**
* @inheritDoc
* @param string|IParameter|IQueryFunction|ILiteral $select
* @param mixed $select
* @template NewS of string
* @param NewS $alias
* @psalm-this-out self<S|NewS>
* @psalm-suppress LessSpecificImplementedReturnType
* @return $this
* @note Psalm has a bug that prevents inferring the correct type in chained calls: https://github.com/vimeo/psalm/issues/8803. Convert the chained calls to standalone calls or switch to PHPStan, which suffered the same bug in the past, but fixed it in 2.1.5: https://github.com/phpstan/phpstan/issues/8439
*/
#[Override]
public function selectAlias(string|IParameter|IQueryFunction|ILiteral $select, string $alias): self;
public function selectAlias($select, $alias): self;
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function setParameter($key, $value, $type = null);
/**
* @inheritDoc
* @return $this
*/
#[Override]
public function setParameters(array $params, array $types = []);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function setFirstResult($firstResult);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function setMaxResults($maxResults);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function delete($delete = null, $alias = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function update($update = null, $alias = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function insert($insert = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function from($from, $alias = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function join($fromAlias, $join, $alias, $condition = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function innerJoin($fromAlias, $join, $alias, $condition = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function leftJoin($fromAlias, $join, $alias, $condition = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function rightJoin($fromAlias, $join, $alias, $condition = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function set($key, $value);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function where(...$predicates);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function andWhere(...$where);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function orWhere(...$where);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function groupBy(...$groupBys);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function addGroupBy(...$groupBy);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function setValue($column, $value);
/**
* @inheritDoc
* @return $this
*/
#[Override]
public function values(array $values);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function having(...$having);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function andHaving(...$having);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function orHaving(...$having);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function orderBy($sort, $order = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function addOrderBy($sort, $order = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function resetQueryParts($queryPartNames = null);
/**
* @inheritDoc
* @return $this
* @psalm-suppress MissingParamType
*/
#[Override]
public function resetQueryPart($queryPartName);
/**
* @inheritDoc
* @return $this
*/
#[Override]
public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self;
/**
* @inheritDoc
* @return $this
*/
#[Override]
public function runAcrossAllShards(): self;
/**
* @inheritDoc
* @return $this
*/
#[Override]
public function forUpdate(ConflictResolutionMode $conflictResolutionMode = ConflictResolutionMode::Ordinary): self;
}
+2 -2
View File
@@ -258,9 +258,9 @@ interface IManager {
* @return ($currentAccess is true
* ? array{
* users?: array<string, array{node_id: int, node_path: string}>,
* remote?: array<string, array{node_id: int, node_path: string}>,
* remote?: array<string, array{node_id: int, token: string}>,
* public?: bool,
* mail?: array<string, array{node_id: int, node_path: string}>
* mail?: array<string, array{node_id: int, token: string}>
* }
* : array{users?: list<string>, remote?: bool, public?: bool, mail?: list<string>})
* @since 12.0.0
+7 -4
View File
@@ -1,23 +1,26 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Share;
use OCP\AppFramework\Attribute\Consumable;
use OCP\Files\Node;
/**
* Interface IShareHelper
*
* @since 12
* @since 12.0.0
*/
#[Consumable(since: '12.0.0')]
interface IShareHelper {
/**
* @param Node $node
* @return array [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]]
* @return array{users: array<string, string>, remotes: array<string, array{token: string, node_path: string}>} [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]]
* @since 12
*/
public function getPathsForAccessList(Node $node);
public function getPathsForAccessList(Node $node): array;
}
+2
View File
@@ -28,6 +28,8 @@
<file name="build/psalm/ITypedQueryBuilderTest.php"/>
<file name="lib/private/DB/QueryBuilder/TypedQueryBuilder.php"/>
<file name="lib/public/DB/QueryBuilder/ITypedQueryBuilder.php"/>
<file name="lib/private/Share20/ShareHelper.php"/>
<file name="lib/public/Share/IShareHelper.php"/>
<ignoreFiles>
<directory name="apps/**/composer"/>
<directory name="apps/**/tests"/>
-10
View File
@@ -55,7 +55,6 @@
<directory name="lib"/>
<directory name="ocs"/>
<directory name="tests/lib/Comments"/>
<directory name="tests/lib/DB/QueryBuilder"/>
<directory name="ocs-provider"/>
<file name="cron.php"/>
<file name="index.php"/>
@@ -173,61 +172,52 @@
<errorLevel type="suppress">
<directory name="lib" />
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</DeprecatedClass>
<DeprecatedConstant>
<errorLevel type="suppress">
<directory name="lib" />
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</DeprecatedConstant>
<DeprecatedFunction>
<errorLevel type="suppress">
<directory name="lib" />
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</DeprecatedFunction>
<DeprecatedInterface>
<errorLevel type="suppress">
<directory name="lib" />
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</DeprecatedInterface>
<DeprecatedMethod>
<errorLevel type="suppress">
<directory name="lib" />
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</DeprecatedMethod>
<DeprecatedProperty>
<errorLevel type="suppress">
<directory name="lib" />
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</DeprecatedProperty>
<DeprecatedTrait>
<errorLevel type="suppress">
<directory name="lib" />
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</DeprecatedTrait>
<InternalMethod>
<errorLevel type="suppress">
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</InternalMethod>
<InternalClass>
<errorLevel type="suppress">
<directory name="apps/*/tests" />
<directory name="tests" />
</errorLevel>
</InternalClass>
</issueHandlers>
+25 -27
View File
@@ -10,7 +10,6 @@ namespace Test\AppFramework\Db;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IExpressionBuilder;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\Types;
use OCP\IDBConnection;
@@ -29,8 +28,6 @@ use PHPUnit\Framework\MockObject\MockObject;
* @method void setIntegerProp(integer $integerProp)
* @method ?\DateTimeImmutable getDatetimeProp()
* @method void setDatetimeProp(?\DateTimeImmutable $datetime)
* @method array getJsonProp()
* @method void setJsonProp(array $jsonProp)
*/
class QBTestEntity extends Entity {
protected $intProp;
@@ -95,12 +92,15 @@ class QBMapperTest extends \Test\TestCase {
->disableOriginalConstructor()
->getMock();
$this->qb->method('expr')->willReturn($this->expr);
$this->db->method('getQueryBuilder')->willReturn($this->qb);
$this->mapper = new QBTestMapper($this->db);
}
public function testInsertEntityParameterTypeMapping(): void {
$datetime = new \DateTimeImmutable();
$entity = new QBTestEntity();
@@ -119,19 +119,18 @@ class QBMapperTest extends \Test\TestCase {
$datetimeParam = $this->qb->createNamedParameter('datetime_prop', IQueryBuilder::PARAM_DATETIME_IMMUTABLE);
$createNamedParameterCalls = [
[123, IQueryBuilder::PARAM_INT, null, $intParam],
[true, IQueryBuilder::PARAM_BOOL, null, $boolParam],
['string', IQueryBuilder::PARAM_STR, null, $stringParam],
[456, IQueryBuilder::PARAM_INT, null, $integerParam],
[false, IQueryBuilder::PARAM_BOOL, null, $booleanParam],
[$datetime, IQueryBuilder::PARAM_DATETIME_IMMUTABLE, null, $datetimeParam],
[123, IQueryBuilder::PARAM_INT, null],
[true, IQueryBuilder::PARAM_BOOL, null],
['string', IQueryBuilder::PARAM_STR, null],
[456, IQueryBuilder::PARAM_INT, null],
[false, IQueryBuilder::PARAM_BOOL, null],
[$datetime, IQueryBuilder::PARAM_DATETIME_IMMUTABLE, null],
];
$this->qb->expects($this->exactly(6))
->method('createNamedParameter')
->willReturnCallback(function () use (&$createNamedParameterCalls): IParameter {
->willReturnCallback(function () use (&$createNamedParameterCalls): void {
$expected = array_shift($createNamedParameterCalls);
$this->assertEquals([$expected[0], $expected[1], $expected[2]], func_get_args());
return $expected[3];
$this->assertEquals($expected, func_get_args());
});
$setValueCalls = [
@@ -144,15 +143,15 @@ class QBMapperTest extends \Test\TestCase {
];
$this->qb->expects($this->exactly(6))
->method('setValue')
->willReturnCallback(function () use (&$setValueCalls): IQueryBuilder {
->willReturnCallback(function () use (&$setValueCalls): void {
$expected = array_shift($setValueCalls);
$this->assertEquals($expected, func_get_args());
return $this->qb;
});
$this->mapper->insert($entity);
}
public function testUpdateEntityParameterTypeMapping(): void {
$datetime = new \DateTimeImmutable();
$entity = new QBTestEntity();
@@ -175,21 +174,20 @@ class QBMapperTest extends \Test\TestCase {
$datetimeParam = $this->qb->createNamedParameter('datetime_prop', IQueryBuilder::PARAM_DATETIME_IMMUTABLE);
$createNamedParameterCalls = [
[123, IQueryBuilder::PARAM_INT, null, $intParam],
[true, IQueryBuilder::PARAM_BOOL, null, $boolParam],
['string', IQueryBuilder::PARAM_STR, null, $stringParam],
[456, IQueryBuilder::PARAM_INT, null, $integerParam],
[false, IQueryBuilder::PARAM_BOOL, null, $booleanParam],
[['hello' => 'world'], IQueryBuilder::PARAM_JSON, null, $jsonParam],
[$datetime, IQueryBuilder::PARAM_DATETIME_IMMUTABLE, null, $datetimeParam],
[789, IQueryBuilder::PARAM_INT, null, $idParam],
[123, IQueryBuilder::PARAM_INT, null],
[true, IQueryBuilder::PARAM_BOOL, null],
['string', IQueryBuilder::PARAM_STR, null],
[456, IQueryBuilder::PARAM_INT, null],
[false, IQueryBuilder::PARAM_BOOL, null],
[['hello' => 'world'], IQueryBuilder::PARAM_JSON, null],
[$datetime, IQueryBuilder::PARAM_DATETIME_IMMUTABLE, null],
[789, IQueryBuilder::PARAM_INT, null],
];
$this->qb->expects($this->exactly(8))
->method('createNamedParameter')
->willReturnCallback(function () use (&$createNamedParameterCalls): IParameter {
->willReturnCallback(function () use (&$createNamedParameterCalls): void {
$expected = array_shift($createNamedParameterCalls);
$this->assertEquals([$expected[0], $expected[1], $expected[2]], func_get_args());
return $expected[3];
$this->assertEquals($expected, func_get_args());
});
$setCalls = [
@@ -203,16 +201,16 @@ class QBMapperTest extends \Test\TestCase {
];
$this->qb->expects($this->exactly(7))
->method('set')
->willReturnCallback(function () use (&$setCalls): IQueryBuilder {
->willReturnCallback(function () use (&$setCalls): void {
$expected = array_shift($setCalls);
$this->assertEquals($expected, func_get_args());
return $this->qb;
});
$this->expr->expects($this->once())
->method('eq')
->with($this->equalTo('id'), $this->equalTo($idParam));
$this->mapper->update($entity);
}
+1 -6
View File
@@ -54,12 +54,7 @@ class ManagerTest extends TestCase {
$this->connection->prepare($sql)->execute();
}
/**
* @param int|null|string $objectId
*
* @psalm-param 'file1'|'file2'|'file3'|int|null $objectId
*/
protected function addDatabaseEntry(?string $parentId, ?string $topmostParentId, ?\DateTimeInterface $creationDT = null, ?\DateTimeInterface $latestChildDT = null, string|int|null $objectId = null, ?\DateTimeInterface $expireDate = null): string {
protected function addDatabaseEntry(?string $parentId, ?string $topmostParentId, ?\DateTimeInterface $creationDT = null, ?\DateTimeInterface $latestChildDT = null, $objectId = null, ?\DateTimeInterface $expireDate = null): string {
$creationDT ??= new \DateTime();
$latestChildDT ??= new \DateTime('yesterday');
$objectId ??= 'file64';
@@ -14,14 +14,13 @@ use OCP\DB\Types;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Server;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use Test\TestCase;
#[Group(name: 'DB')]
#[\PHPUnit\Framework\Attributes\Group('DB')]
class ExpressionBuilderDBTest extends TestCase {
protected IDBConnection $connection;
protected bool $schemaSetup = false;
/** @var \Doctrine\DBAL\Connection|IDBConnection */
protected $connection;
protected $schemaSetup = false;
protected function setUp(): void {
parent::setUp();
@@ -46,8 +45,14 @@ class ExpressionBuilderDBTest extends TestCase {
];
}
#[DataProvider(methodName: 'likeProvider')]
public function testLike(string $param1, string $param2, bool $match): void {
/**
*
* @param string $param1
* @param string $param2
* @param boolean $match
*/
#[\PHPUnit\Framework\Attributes\DataProvider('likeProvider')]
public function testLike($param1, $param2, $match): void {
$query = $this->connection->getQueryBuilder();
$query->select(new Literal('1'))
@@ -77,8 +82,14 @@ class ExpressionBuilderDBTest extends TestCase {
];
}
#[DataProvider(methodName: 'ilikeProvider')]
public function testILike(string $param1, string $param2, bool $match): void {
/**
*
* @param string $param1
* @param string $param2
* @param boolean $match
*/
#[\PHPUnit\Framework\Attributes\DataProvider('ilikeProvider')]
public function testILike($param1, $param2, $match): void {
$query = $this->connection->getQueryBuilder();
$query->select(new Literal('1'))
@@ -222,11 +233,7 @@ class ExpressionBuilderDBTest extends TestCase {
self::assertCount(1, $entries);
}
/**
* @psalm-param '1'|'mykey' $key
* @psalm-param '4'|'myvalue' $value
*/
protected function createConfig(string $appId, string $key, string $value) {
protected function createConfig($appId, $key, $value) {
$query = $this->connection->getQueryBuilder();
$query->insert('appconfig')
->values([
@@ -13,6 +13,7 @@ namespace Test\DB\QueryBuilder;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder as DoctrineExpressionBuilder;
use OC\DB\Connection;
use OC\DB\QueryBuilder\ExpressionBuilder\ExpressionBuilder;
use OC\DB\QueryBuilder\Literal;
use OCP\DB\QueryBuilder\IFunctionBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@@ -23,7 +24,12 @@ use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
#[Group(name: 'DB')]
/**
* Class ExpressionBuilderTest
*
* @package Test\DB\QueryBuilder
*/
#[Group('DB')]
class ExpressionBuilderTest extends TestCase {
protected ExpressionBuilder $expressionBuilder;
protected DoctrineExpressionBuilder $doctrineExpressionBuilder;
@@ -60,8 +66,8 @@ class ExpressionBuilderTest extends TestCase {
return $testSets;
}
#[DataProvider(methodName: 'dataComparison')]
public function testComparison(string $comparison, mixed $input1, bool $isInput1Literal, mixed $input2, bool $isInput2Literal): void {
#[DataProvider('dataComparison')]
public function testComparison(string $comparison, string $input1, bool $isInput1Literal, string $input2, bool $isInput2Literal): void {
[$doctrineInput1, $ocInput1] = $this->helpWithLiteral($input1, $isInput1Literal);
[$doctrineInput2, $ocInput2] = $this->helpWithLiteral($input2, $isInput2Literal);
@@ -80,7 +86,7 @@ class ExpressionBuilderTest extends TestCase {
];
}
#[DataProvider(methodName: 'dataComparisons')]
#[DataProvider('dataComparisons')]
public function testEquals(string $input1, bool $isInput1Literal, string $input2, bool $isInput2Literal): void {
[$doctrineInput1, $ocInput1] = $this->helpWithLiteral($input1, $isInput1Literal);
[$doctrineInput2, $ocInput2] = $this->helpWithLiteral($input2, $isInput2Literal);
@@ -91,7 +97,7 @@ class ExpressionBuilderTest extends TestCase {
);
}
#[DataProvider(methodName: 'dataComparisons')]
#[DataProvider('dataComparisons')]
public function testNotEquals(string $input1, bool $isInput1Literal, string $input2, bool $isInput2Literal): void {
[$doctrineInput1, $ocInput1] = $this->helpWithLiteral($input1, $isInput1Literal);
[$doctrineInput2, $ocInput2] = $this->helpWithLiteral($input2, $isInput2Literal);
@@ -102,7 +108,7 @@ class ExpressionBuilderTest extends TestCase {
);
}
#[DataProvider(methodName: 'dataComparisons')]
#[DataProvider('dataComparisons')]
public function testLowerThan(string $input1, bool $isInput1Literal, string $input2, bool $isInput2Literal): void {
[$doctrineInput1, $ocInput1] = $this->helpWithLiteral($input1, $isInput1Literal);
[$doctrineInput2, $ocInput2] = $this->helpWithLiteral($input2, $isInput2Literal);
@@ -113,7 +119,7 @@ class ExpressionBuilderTest extends TestCase {
);
}
#[DataProvider(methodName: 'dataComparisons')]
#[DataProvider('dataComparisons')]
public function testLowerThanEquals(string $input1, bool $isInput1Literal, string $input2, bool $isInput2Literal): void {
[$doctrineInput1, $ocInput1] = $this->helpWithLiteral($input1, $isInput1Literal);
[$doctrineInput2, $ocInput2] = $this->helpWithLiteral($input2, $isInput2Literal);
@@ -124,7 +130,7 @@ class ExpressionBuilderTest extends TestCase {
);
}
#[DataProvider(methodName: 'dataComparisons')]
#[DataProvider('dataComparisons')]
public function testGreaterThan(string $input1, bool $isInput1Literal, string $input2, bool $isInput2Literal): void {
[$doctrineInput1, $ocInput1] = $this->helpWithLiteral($input1, $isInput1Literal);
[$doctrineInput2, $ocInput2] = $this->helpWithLiteral($input2, $isInput2Literal);
@@ -135,7 +141,7 @@ class ExpressionBuilderTest extends TestCase {
);
}
#[DataProvider(methodName: 'dataComparisons')]
#[DataProvider('dataComparisons')]
public function testGreaterThanEquals(string $input1, bool $isInput1Literal, string $input2, bool $isInput2Literal): void {
[$doctrineInput1, $ocInput1] = $this->helpWithLiteral($input1, $isInput1Literal);
[$doctrineInput2, $ocInput2] = $this->helpWithLiteral($input2, $isInput2Literal);
@@ -167,7 +173,7 @@ class ExpressionBuilderTest extends TestCase {
];
}
#[DataProvider(methodName: 'dataLike')]
#[DataProvider('dataLike')]
public function testLike(string $input, bool $isLiteral): void {
[$doctrineInput, $ocInput] = $this->helpWithLiteral($input, $isLiteral);
@@ -177,7 +183,7 @@ class ExpressionBuilderTest extends TestCase {
);
}
#[DataProvider(methodName: 'dataLike')]
#[DataProvider('dataLike')]
public function testNotLike(string $input, bool $isLiteral): void {
[$doctrineInput, $ocInput] = $this->helpWithLiteral($input, $isLiteral);
@@ -196,7 +202,7 @@ class ExpressionBuilderTest extends TestCase {
];
}
#[DataProvider(methodName: 'dataIn')]
#[DataProvider('dataIn')]
public function testIn(string|array $input, bool $isLiteral): void {
[$doctrineInput, $ocInput] = $this->helpWithLiteral($input, $isLiteral);
@@ -206,7 +212,7 @@ class ExpressionBuilderTest extends TestCase {
);
}
#[DataProvider(methodName: 'dataIn')]
#[DataProvider('dataIn')]
public function testNotIn(string|array $input, bool $isLiteral): void {
[$doctrineInput, $ocInput] = $this->helpWithLiteral($input, $isLiteral);
@@ -216,7 +222,7 @@ class ExpressionBuilderTest extends TestCase {
);
}
protected function helpWithLiteral(string|array $input, bool $isLiteral): array {
protected function helpWithLiteral(string|array $input, bool $isLiteral) {
if ($isLiteral) {
if (is_array($input)) {
$doctrineInput = array_map(function ($ident) {
@@ -255,14 +261,12 @@ class ExpressionBuilderTest extends TestCase {
];
}
#[DataProvider(methodName: 'dataLiteral')]
#[DataProvider('dataLiteral')]
public function testLiteral(string|int $input, string|int|null $type): void {
if ($type === null) {
$actual = $this->expressionBuilder->literal($input);
} else {
$actual = $this->expressionBuilder->literal($input, $type);
}
/** @var Literal $actual */
$actual = $this->expressionBuilder->literal($input, $type);
$this->assertInstanceOf('\OC\DB\QueryBuilder\Literal', $actual);
$this->assertEquals(
$this->doctrineExpressionBuilder->literal($input, $type),
$actual->__toString()
@@ -296,11 +300,8 @@ class ExpressionBuilderTest extends TestCase {
];
}
/**
* @param IQueryBuilder::PARAM_* $type
*/
#[DataProvider(methodName: 'dataClobComparisons')]
public function testClobComparisons(string $function, string|array $value, mixed $type, bool $compareKeyToValue, int $expected): void {
#[DataProvider('dataClobComparisons')]
public function testClobComparisons(string $function, string|array $value, int $type, bool $compareKeyToValue, int $expected): void {
$appId = $this->getUniqueID('testing');
$this->createConfig($appId, 1, 4);
$this->createConfig($appId, 2, 5);
@@ -334,7 +335,8 @@ class ExpressionBuilderTest extends TestCase {
->where($query->expr()->eq('appid', $query->createNamedParameter($appId)))
->executeStatement();
}
protected function createConfig(string $appId, int $key, string|int $value): void {
protected function createConfig(string $appId, int $key, int|string $value) {
$query = $this->connection->getQueryBuilder();
$query->insert('appconfig')
->values([
@@ -11,13 +11,18 @@ use OC\DB\QueryBuilder\Literal;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Server;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use Test\TestCase;
#[Group(name: 'DB')]
/**
* Class FunctionBuilderTest
*
*
* @package Test\DB\QueryBuilder
*/
#[\PHPUnit\Framework\Attributes\Group('DB')]
class FunctionBuilderTest extends TestCase {
protected IDBConnection $connection;
/** @var \Doctrine\DBAL\Connection|IDBConnection */
protected $connection;
protected function setUp(): void {
parent::setUp();
@@ -25,8 +30,8 @@ class FunctionBuilderTest extends TestCase {
$this->connection = Server::get(IDBConnection::class);
}
#[DataProvider(methodName: 'providerTestConcatString')]
public function testConcatString(callable $closure): void {
#[\PHPUnit\Framework\Attributes\DataProvider('providerTestConcatString')]
public function testConcatString($closure): void {
$query = $this->connection->getQueryBuilder();
[$real, $arguments, $return] = $closure($query);
if ($real) {
@@ -48,35 +53,35 @@ class FunctionBuilderTest extends TestCase {
public static function providerTestConcatString(): array {
return [
'1 column: string param unicode'
=> [function (IQueryBuilder $q) {
=> [function ($q) {
return [false, [$q->createNamedParameter('👍')], '👍'];
}],
'2 columns: string param and string param'
=> [function (IQueryBuilder $q) {
=> [function ($q) {
return [false, [$q->createNamedParameter('foo'), $q->createNamedParameter('bar')], 'foobar'];
}],
'2 columns: string param and int literal'
=> [function (IQueryBuilder $q) {
=> [function ($q) {
return [false, [$q->createNamedParameter('foo'), $q->expr()->literal(1)], 'foo1'];
}],
'2 columns: string param and string literal'
=> [function (IQueryBuilder $q) {
=> [function ($q) {
return [false, [$q->createNamedParameter('foo'), $q->expr()->literal('bar')], 'foobar'];
}],
'2 columns: string real and int literal'
=> [function (IQueryBuilder $q) {
=> [function ($q) {
return [true, ['configkey', $q->expr()->literal(2)], '12'];
}],
'4 columns: string literal'
=> [function (IQueryBuilder $q) {
=> [function ($q) {
return [false, [$q->expr()->literal('foo'), $q->expr()->literal('bar'), $q->expr()->literal('foo'), $q->expr()->literal('bar')], 'foobarfoobar'];
}],
'4 columns: int literal'
=> [function (IQueryBuilder $q) {
=> [function ($q) {
return [false, [$q->expr()->literal(1), $q->expr()->literal(2), $q->expr()->literal(3), $q->expr()->literal(4)], '1234'];
}],
'5 columns: string param with special chars used in the function'
=> [function (IQueryBuilder $q) {
=> [function ($q) {
return [false, [$q->createNamedParameter('b'), $q->createNamedParameter("'"), $q->createNamedParameter('||'), $q->createNamedParameter(','), $q->createNamedParameter('a')], "b'||,a"];
}],
];
@@ -328,7 +333,7 @@ class FunctionBuilderTest extends TestCase {
];
}
#[DataProvider(methodName: 'octetLengthProvider')]
#[\PHPUnit\Framework\Attributes\DataProvider('octetLengthProvider')]
public function testOctetLength(string $str, int $bytes): void {
$query = $this->connection->getQueryBuilder();
@@ -351,7 +356,7 @@ class FunctionBuilderTest extends TestCase {
];
}
#[DataProvider(methodName: 'charLengthProvider')]
#[\PHPUnit\Framework\Attributes\DataProvider('charLengthProvider')]
public function testCharLength(string $str, int $bytes): void {
$query = $this->connection->getQueryBuilder();
@@ -366,10 +371,7 @@ class FunctionBuilderTest extends TestCase {
$this->assertEquals($bytes, $column);
}
/**
* @psalm-param 10|11|20 $value
*/
private function setUpMinMax(int $value) {
private function setUpMinMax($value) {
$query = $this->connection->getQueryBuilder();
$query->insert('appconfig')
@@ -14,7 +14,6 @@ use OC\DB\QueryBuilder\QueryBuilder;
use OC\SystemConfig;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use PHPUnit\Framework\Attributes\DataProvider;
use Psr\Log\LoggerInterface;
use Test\TestCase;
@@ -42,7 +41,7 @@ class JoinConditionTest extends TestCase {
);
}
#[DataProvider(methodName: 'platformProvider')]
#[\PHPUnit\Framework\Attributes\DataProvider('platformProvider')]
public function testParseCondition(string $platform): void {
$query = $this->getBuilder($platform);
$param1 = $query->createNamedParameter('files');
@@ -62,7 +61,7 @@ class JoinConditionTest extends TestCase {
], $parsed->toConditions);
}
#[DataProvider(methodName: 'platformProvider')]
#[\PHPUnit\Framework\Attributes\DataProvider('platformProvider')]
public function testParseCastCondition(string $platform): void {
$query = $this->getBuilder($platform);
@@ -16,10 +16,9 @@ use OC\DB\QueryBuilder\Sharded\ShardConnectionManager;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Server;
use PHPUnit\Framework\Attributes\Group;
use Test\TestCase;
#[Group(name: 'DB')]
#[\PHPUnit\Framework\Attributes\Group('DB')]
class PartitionedQueryBuilderTest extends TestCase {
private IDBConnection $connection;
private ShardConnectionManager $shardConnectionManager;
@@ -28,6 +27,7 @@ class PartitionedQueryBuilderTest extends TestCase {
protected function setUp(): void {
if (PHP_INT_SIZE < 8) {
$this->markTestSkipped('Test requires 64bit');
return;
}
$this->connection = Server::get(IDBConnection::class);
$this->shardConnectionManager = Server::get(ShardConnectionManager::class);
+216 -86
View File
@@ -10,7 +10,6 @@ declare(strict_types=1);
namespace Test\DB\QueryBuilder;
use BadMethodCallException;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Doctrine\DBAL\Query\QueryException;
use OC\DB\ConnectionAdapter;
@@ -19,18 +18,20 @@ use OC\DB\QueryBuilder\Parameter;
use OC\DB\QueryBuilder\QueryBuilder;
use OC\SystemConfig;
use OCP\DB\IResult;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Server;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use function str_starts_with;
#[Group(name: 'DB')]
/**
* Class QueryBuilderTest
*
*
* @package Test\DB\QueryBuilder
*/
#[\PHPUnit\Framework\Attributes\Group('DB')]
class QueryBuilderTest extends \Test\TestCase {
private SystemConfig&MockObject $config;
private LoggerInterface&MockObject $logger;
@@ -47,10 +48,7 @@ class QueryBuilderTest extends \Test\TestCase {
$this->queryBuilder = new QueryBuilder($this->connection, $this->config, $this->logger);
}
/**
* @psalm-param 'testFirstResult'|'testFirstResult1'|'testFirstResult2' $appId
*/
protected function createTestingRows(string $appId = 'testFirstResult'): void {
protected function createTestingRows($appId = 'testFirstResult') {
$qB = $this->connection->getQueryBuilder();
for ($i = 1; $i < 10; $i++) {
$qB->insert('*PREFIX*appconfig')
@@ -63,7 +61,7 @@ class QueryBuilderTest extends \Test\TestCase {
}
}
protected function getTestingRows(QueryBuilder $queryBuilder): array {
protected function getTestingRows(QueryBuilder $queryBuilder) {
$queryBuilder->select('configvalue')
->from('*PREFIX*appconfig')
->where($queryBuilder->expr()->eq(
@@ -82,10 +80,7 @@ class QueryBuilderTest extends \Test\TestCase {
return $rows;
}
/**
* @psalm-param 'testFirstResult'|'testFirstResult1'|'testFirstResult2' $appId
*/
protected function deleteTestingRows(string $appId = 'testFirstResult'): void {
protected function deleteTestingRows($appId = 'testFirstResult') {
$qB = $this->connection->getQueryBuilder();
$qB->delete('*PREFIX*appconfig')
@@ -102,8 +97,13 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataFirstResult')]
public function testFirstResult(?int $firstResult, array $expectedSet): void {
/**
*
* @param int|null $firstResult
* @param array $expectedSet
*/
#[DataProvider('dataFirstResult')]
public function testFirstResult($firstResult, $expectedSet): void {
$this->deleteTestingRows();
$this->createTestingRows();
@@ -134,8 +134,13 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataMaxResults')]
public function testMaxResults(?int $maxResult, array $expectedSet): void {
/**
*
* @param int $maxResult
* @param array $expectedSet
*/
#[DataProvider('dataMaxResults')]
public function testMaxResults($maxResult, $expectedSet): void {
$this->deleteTestingRows();
$this->createTestingRows();
@@ -178,7 +183,7 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataSelect')]
#[DataProvider('dataSelect')]
public function testSelect(array $selectArguments, array $expected, string $expectedLiteral = ''): void {
$this->deleteTestingRows();
$this->createTestingRows();
@@ -186,7 +191,7 @@ class QueryBuilderTest extends \Test\TestCase {
array_walk_recursive(
$selectArguments,
function (string &$arg): void {
if (str_starts_with($arg, 'l::')) {
if (\str_starts_with($arg, 'l::')) {
$arg = $this->queryBuilder->expr()->literal(substr($arg, 3));
}
},
@@ -227,7 +232,7 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataSelectAlias')]
#[DataProvider('dataSelectAlias')]
public function testSelectAlias(string $select, string $alias, array $expected): void {
if (str_starts_with($select, 'l::')) {
$select = $this->queryBuilder->expr()->literal(substr($select, 3));
@@ -238,7 +243,7 @@ class QueryBuilderTest extends \Test\TestCase {
$this->queryBuilder->selectAlias($select, $alias);
$this->queryBuilder->from('appconfig')
$this->queryBuilder->from('*PREFIX*appconfig')
->where($this->queryBuilder->expr()->eq(
'appid',
$this->queryBuilder->expr()->literal('testFirstResult')
@@ -346,7 +351,7 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataAddSelect')]
#[DataProvider('dataAddSelect')]
public function testAddSelect(array $selectArguments, array $expected, string $expectedLiteral = ''): void {
$this->deleteTestingRows();
$this->createTestingRows();
@@ -354,7 +359,7 @@ class QueryBuilderTest extends \Test\TestCase {
array_walk_recursive(
$selectArguments,
function (string &$arg): void {
if (str_starts_with($arg, 'l::')) {
if (\str_starts_with($arg, 'l::')) {
$arg = $this->queryBuilder->expr()->literal(substr($arg, 3));
}
},
@@ -401,8 +406,15 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataDelete')]
public function testDelete(string $tableName, ?string $tableAlias, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $tableName
* @param string $tableAlias
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataDelete')]
public function testDelete($tableName, $tableAlias, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->delete($tableName, $tableAlias);
$this->assertSame(
@@ -423,8 +435,15 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataUpdate')]
public function testUpdate(string $tableName, ?string $tableAlias, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $tableName
* @param string $tableAlias
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataUpdate')]
public function testUpdate($tableName, $tableAlias, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->update($tableName, $tableAlias);
$this->assertSame(
@@ -444,8 +463,14 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataInsert')]
public function testInsert(string $tableName, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $tableName
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataInsert')]
public function testInsert($tableName, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->insert($tableName);
$this->assertSame(
@@ -477,7 +502,7 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataFrom')]
#[DataProvider('dataFrom')]
public function testFrom(string $table1Name, ?string $table1Alias, ?string $table2Name, ?string $table2Alias, array $expectedQueryPart, string $expectedQuery): void {
$config = $this->createMock(SystemConfig::class);
$logger = $this->createMock(LoggerInterface::class);
@@ -523,8 +548,17 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataJoin')]
public function testJoin(string $fromAlias, string $tableName, ?string $tableAlias, ?string $condition, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $fromAlias
* @param string $tableName
* @param string $tableAlias
* @param string $condition
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataJoin')]
public function testJoin($fromAlias, $tableName, $tableAlias, $condition, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->from('data1', 'd1');
$this->queryBuilder->join(
$fromAlias,
@@ -544,8 +578,17 @@ class QueryBuilderTest extends \Test\TestCase {
);
}
#[DataProvider(methodName: 'dataJoin')]
public function testInnerJoin(string $fromAlias, string $tableName, ?string $tableAlias, ?string $condition, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $fromAlias
* @param string $tableName
* @param string $tableAlias
* @param string $condition
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataJoin')]
public function testInnerJoin($fromAlias, $tableName, $tableAlias, $condition, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->from('data1', 'd1');
$this->queryBuilder->innerJoin(
$fromAlias,
@@ -585,8 +628,17 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataLeftJoin')]
public function testLeftJoin(string $fromAlias, string $tableName, ?string $tableAlias, ?string $condition, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $fromAlias
* @param string $tableName
* @param string $tableAlias
* @param string $condition
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataLeftJoin')]
public function testLeftJoin($fromAlias, $tableName, $tableAlias, $condition, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->from('data1', 'd1');
$this->queryBuilder->leftJoin(
$fromAlias,
@@ -626,8 +678,17 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataRightJoin')]
public function testRightJoin(string $fromAlias, string $tableName, ?string $tableAlias, ?string $condition, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $fromAlias
* @param string $tableName
* @param string $tableAlias
* @param string $condition
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataRightJoin')]
public function testRightJoin($fromAlias, $tableName, $tableAlias, $condition, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->from('data1', 'd1');
$this->queryBuilder->rightJoin(
$fromAlias,
@@ -656,8 +717,17 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataSet')]
public function testSet(string $partOne1, string|ILiteral|IParameter $partOne2, ?string $partTwo1, ?ILiteral $partTwo2, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $partOne1
* @param string $partOne2
* @param string $partTwo1
* @param string $partTwo2
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataSet')]
public function testSet($partOne1, $partOne2, $partTwo1, $partTwo2, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->update('data');
$this->queryBuilder->set($partOne1, $partOne2);
if ($partTwo1 !== null) {
@@ -682,8 +752,14 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataWhere')]
public function testWhere(array $whereArguments, CompositeExpression $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param array $whereArguments
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataWhere')]
public function testWhere($whereArguments, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->select('column');
call_user_func_array(
[$this->queryBuilder, 'where'],
@@ -701,8 +777,14 @@ class QueryBuilderTest extends \Test\TestCase {
);
}
#[DataProvider(methodName: 'dataWhere')]
public function testAndWhere(array $whereArguments, CompositeExpression $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param array $whereArguments
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataWhere')]
public function testAndWhere($whereArguments, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->select('column');
call_user_func_array(
[$this->queryBuilder, 'andWhere'],
@@ -727,8 +809,14 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataOrWhere')]
public function testOrWhere(array $whereArguments, CompositeExpression $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param array $whereArguments
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataOrWhere')]
public function testOrWhere($whereArguments, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->select('column');
call_user_func_array(
[$this->queryBuilder, 'orWhere'],
@@ -753,8 +841,14 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataGroupBy')]
public function testGroupBy(array $groupByArguments, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param array $groupByArguments
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataGroupBy')]
public function testGroupBy($groupByArguments, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->select('column');
call_user_func_array(
[$this->queryBuilder, 'groupBy'],
@@ -779,8 +873,14 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataAddGroupBy')]
public function testAddGroupBy(array $groupByArguments, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param array $groupByArguments
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataAddGroupBy')]
public function testAddGroupBy($groupByArguments, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->select('column');
$this->queryBuilder->groupBy('column1');
call_user_func_array(
@@ -805,8 +905,15 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataSetValue')]
public function testSetValue(string $column, string $value, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $column
* @param string $value
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataSetValue')]
public function testSetValue($column, $value, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->insert('data');
$this->queryBuilder->setValue($column, $value);
@@ -821,8 +928,15 @@ class QueryBuilderTest extends \Test\TestCase {
);
}
#[DataProvider(methodName: 'dataSetValue')]
public function testValues(string $column, string $value, array $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param string $column
* @param string $value
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataSetValue')]
public function testValues($column, $value, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->insert('data');
$this->queryBuilder->values([
$column => $value,
@@ -856,8 +970,14 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataHaving')]
public function testHaving(array $havingArguments, CompositeExpression $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param array $havingArguments
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataHaving')]
public function testHaving($havingArguments, $expectedQueryPart, $expectedQuery): void {
call_user_func_array(
[$this->queryBuilder, 'having'],
$havingArguments
@@ -891,8 +1011,14 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataAndHaving')]
public function testAndHaving(array $havingArguments, CompositeExpression $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param array $havingArguments
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataAndHaving')]
public function testAndHaving($havingArguments, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->having('condition1');
call_user_func_array(
[$this->queryBuilder, 'andHaving'],
@@ -927,8 +1053,14 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataOrHaving')]
public function testOrHaving(array $havingArguments, CompositeExpression $expectedQueryPart, string $expectedQuery): void {
/**
*
* @param array $havingArguments
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider('dataOrHaving')]
public function testOrHaving($havingArguments, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->having('condition1');
call_user_func_array(
[$this->queryBuilder, 'orHaving'],
@@ -955,10 +1087,14 @@ class QueryBuilderTest extends \Test\TestCase {
}
/**
* @param string|'ASC'|'DESC'|null $order
*
* @param string $sort
* @param string $order
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider(methodName: 'dataOrderBy')]
public function testOrderBy(string $sort, ?string $order, array $expectedQueryPart, string $expectedQuery): void {
#[DataProvider('dataOrderBy')]
public function testOrderBy($sort, $order, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->orderBy($sort, $order);
$this->assertEquals(
@@ -987,11 +1123,15 @@ class QueryBuilderTest extends \Test\TestCase {
}
/**
* @param string|'ASC'|'DESC'|null $order2
* @param string|'ASC'|'DESC'|null $order1
*
* @param string $sort2
* @param string $order2
* @param string $order1
* @param array $expectedQueryPart
* @param string $expectedQuery
*/
#[DataProvider(methodName: 'dataAddOrderBy')]
public function testAddOrderBy(string $sort2, ?string $order2, ?string $order1, array $expectedQueryPart, string $expectedQuery): void {
#[DataProvider('dataAddOrderBy')]
public function testAddOrderBy($sort2, $order2, $order1, $expectedQueryPart, $expectedQuery): void {
$this->queryBuilder->orderBy('column1', $order1);
$this->queryBuilder->addOrderBy($sort2, $order2);
@@ -1012,7 +1152,7 @@ class QueryBuilderTest extends \Test\TestCase {
try {
$qB->getLastInsertId();
$this->fail('getLastInsertId() should throw an exception, when being called before insert()');
} catch (BadMethodCallException) {
} catch (\BadMethodCallException $e) {
$this->addToAssertionCount(1);
}
@@ -1027,6 +1167,8 @@ class QueryBuilderTest extends \Test\TestCase {
$actual = $qB->getLastInsertId();
$this->assertNotNull($actual);
$this->assertIsInt($actual);
$this->assertEquals($this->connection->lastInsertId('*PREFIX*properties'), $actual);
$qB->delete('properties')
@@ -1036,7 +1178,7 @@ class QueryBuilderTest extends \Test\TestCase {
try {
$qB->getLastInsertId();
$this->fail('getLastInsertId() should throw an exception, when being called after delete()');
} catch (BadMethodCallException) {
} catch (\BadMethodCallException $e) {
$this->addToAssertionCount(1);
}
}
@@ -1057,7 +1199,7 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataGetTableName')]
#[DataProvider('dataGetTableName')]
public function testGetTableName(string $tableName, ?bool $automatic, string $expected): void {
if ($tableName === 'function') {
$tableName = $this->queryBuilder->createFunction('(' . $this->queryBuilder->select('*')->from('table')->getSQL() . ')');
@@ -1080,7 +1222,7 @@ class QueryBuilderTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataGetColumnName')]
#[DataProvider('dataGetColumnName')]
public function testGetColumnName(string $column, string $prefix, string $expected): void {
$this->assertSame(
$expected,
@@ -1088,7 +1230,7 @@ class QueryBuilderTest extends \Test\TestCase {
);
}
private function getConnection(): ConnectionAdapter&MockObject {
private function getConnection(): MockObject&ConnectionAdapter {
$connection = $this->createMock(ConnectionAdapter::class);
$connection->method('executeStatement')
->willReturn(3);
@@ -1099,9 +1241,6 @@ class QueryBuilderTest extends \Test\TestCase {
public function testExecuteWithoutLogger(): void {
$queryBuilder = $this->createMock(\Doctrine\DBAL\Query\QueryBuilder::class);
$queryBuilder
->method('getType')
->willReturn(\Doctrine\DBAL\Query\QueryBuilder::INSERT);
$queryBuilder
->method('getSQL')
->willReturn('');
@@ -1127,9 +1266,6 @@ class QueryBuilderTest extends \Test\TestCase {
public function testExecuteWithLoggerAndNamedArray(): void {
$queryBuilder = $this->createMock(\Doctrine\DBAL\Query\QueryBuilder::class);
$queryBuilder
->method('getType')
->willReturn(\Doctrine\DBAL\Query\QueryBuilder::INSERT);
$queryBuilder
->expects($this->any())
->method('getParameters')
@@ -1171,9 +1307,6 @@ class QueryBuilderTest extends \Test\TestCase {
public function testExecuteWithLoggerAndUnnamedArray(): void {
$queryBuilder = $this->createMock(\Doctrine\DBAL\Query\QueryBuilder::class);
$queryBuilder
->method('getType')
->willReturn(\Doctrine\DBAL\Query\QueryBuilder::INSERT);
$queryBuilder
->expects($this->any())
->method('getParameters')
@@ -1209,9 +1342,6 @@ class QueryBuilderTest extends \Test\TestCase {
public function testExecuteWithLoggerAndNoParams(): void {
$queryBuilder = $this->createMock(\Doctrine\DBAL\Query\QueryBuilder::class);
$queryBuilder
->method('getType')
->willReturn(\Doctrine\DBAL\Query\QueryBuilder::INSERT);
$queryBuilder
->expects($this->any())
->method('getParameters')
+38 -10
View File
@@ -13,10 +13,10 @@ use OC\DB\QueryBuilder\Parameter;
use OC\DB\QueryBuilder\QuoteHelper;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use PHPUnit\Framework\Attributes\DataProvider;
class QuoteHelperTest extends \Test\TestCase {
protected QuoteHelper $helper;
/** @var QuoteHelper */
protected $helper;
protected function setUp(): void {
parent::setUp();
@@ -37,8 +37,12 @@ class QuoteHelperTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataQuoteColumnName')]
public function testQuoteColumnName(string|Literal|Parameter $input, string $expected): void {
/**
* @param mixed $input
* @param string $expected
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataQuoteColumnName')]
public function testQuoteColumnName($input, $expected): void {
$this->assertSame(
$expected,
$this->helper->quoteColumnName($input)
@@ -68,15 +72,23 @@ class QuoteHelperTest extends \Test\TestCase {
];
}
#[DataProvider(methodName: 'dataQuoteColumnNames')]
public function testQuoteColumnNames(string|Literal|Parameter|array $input, string|array $expected): void {
/**
* @param mixed $input
* @param string $expected
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataQuoteColumnNames')]
public function testQuoteColumnNames($input, $expected): void {
$this->assertSame(
$expected,
$this->helper->quoteColumnNames($input)
);
}
public function quoteColumnNames(array|string|ILiteral|IParameter $strings): array|string {
/**
* @param array|string|ILiteral|IParameter $strings string, Literal or Parameter
* @return array|string
*/
public function quoteColumnNames($strings) {
if (!is_array($strings)) {
return $this->quoteColumnName($strings);
}
@@ -89,9 +101,25 @@ class QuoteHelperTest extends \Test\TestCase {
return $return;
}
public function quoteColumnName(string|ILiteral|IParameter $string): string {
if ($string instanceof ILiteral || $string instanceof IParameter) {
return (string)$string;
/**
* @param string|ILiteral|IParameter $string string, Literal or Parameter
* @return string
*/
public function quoteColumnName($string) {
if ($string instanceof IParameter) {
return $string->getName();
}
if ($string instanceof ILiteral) {
return $string->getLiteral();
}
if ($string === null) {
return $string;
}
if (!is_string($string)) {
throw new \InvalidArgumentException('Only strings, Literals and Parameters are allowed');
}
if (substr_count($string, '.')) {
@@ -17,10 +17,9 @@ use OC\DB\QueryBuilder\Sharded\ShardedQueryBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Server;
use PHPUnit\Framework\Attributes\Group;
use Test\TestCase;
#[Group(name: 'DB')]
#[\PHPUnit\Framework\Attributes\Group('DB')]
class SharedQueryBuilderTest extends TestCase {
private IDBConnection $connection;
private AutoIncrementHandler $autoIncrementHandler;
+1
View File
@@ -95,6 +95,7 @@ class FactoryTest extends TestCase {
return [
'null shortcut' => [null, null],
'default language' => ['de', 'de'],
'regional language' => ['de_DE', 'de_DE'],
'malicious language' => ['de/../fr', 'defr'],
'request language' => ['kab;q=0.8,ka;q=0.7,de;q=0.6', 'kab;q=0.8,ka;q=0.7,de;q=0.6'],
];