Skip to content

Commit

Permalink
feat(AppFramework): Add full support for date / time / datetime columns
Browse files Browse the repository at this point in the history
This adds support for all Doctrine supported types, for the column types only the immutable variants needed to be added.
But especially those types are the important ones, as our **Entity** class works by detecting changes through setters.
Meaning if it is mutable, changes like `$entity->date->modfiy()` can not be detected, so the immutable types make more sense here.

Similar the parameter types needed to be added.

`Enity` and `QBMapper` needed to be adjusted so they support (auto map) those types, required when insert or update an entity.

Also added more tests, especially to make sure the mapper really serializes the values correctly.

Co-authored-by: Ferdinand Thiessen <opensource@fthiessen.de>
Co-authored-by: Côme Chilliet <91878298+come-nc@users.noreply.github.com>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux and come-nc committed Aug 20, 2024
1 parent 560282a commit b338e58
Show file tree
Hide file tree
Showing 14 changed files with 457 additions and 94 deletions.
4 changes: 2 additions & 2 deletions apps/federatedfilesharing/lib/FederatedShareProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ui
->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
->setValue('permissions', $qb->createNamedParameter($permissions))
->setValue('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATE))
->setValue('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME))
->setValue('token', $qb->createNamedParameter($token))
->setValue('stime', $qb->createNamedParameter(time()));

Expand Down Expand Up @@ -333,7 +333,7 @@ public function update(IShare $share) {
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATETIME))
->execute();

// send the updated permission to the owner/initiator, if they are not the same
Expand Down
2 changes: 1 addition & 1 deletion apps/files_reminders/lib/Db/ReminderMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public function findNotified(DateTime $buffer, ?int $limit = null) {
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
->from($this->getTableName())
->where($qb->expr()->eq('notified', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->lt('due_date', $qb->createNamedParameter($buffer, IQueryBuilder::PARAM_DATE)))
->andWhere($qb->expr()->lt('due_date', $qb->createNamedParameter($buffer, IQueryBuilder::PARAM_DATETIME)))
->orderBy('due_date', 'ASC')
->setMaxResults($limit);

Expand Down
8 changes: 4 additions & 4 deletions apps/sharebymail/lib/ShareByMailProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ protected function addShareToDB(
->setValue('permissions', $qb->createNamedParameter($permissions))
->setValue('token', $qb->createNamedParameter($token))
->setValue('password', $qb->createNamedParameter($password))
->setValue('password_expiration_time', $qb->createNamedParameter($passwordExpirationTime, IQueryBuilder::PARAM_DATE))
->setValue('password_expiration_time', $qb->createNamedParameter($passwordExpirationTime, IQueryBuilder::PARAM_DATETIME))
->setValue('password_by_talk', $qb->createNamedParameter($sendPasswordByTalk, IQueryBuilder::PARAM_BOOL))
->setValue('stime', $qb->createNamedParameter(time()))
->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT))
Expand All @@ -710,7 +710,7 @@ protected function addShareToDB(

$qb->setValue('attributes', $qb->createNamedParameter($shareAttributes));
if ($expirationTime !== null) {
$qb->setValue('expiration', $qb->createNamedParameter($expirationTime, IQueryBuilder::PARAM_DATE));
$qb->setValue('expiration', $qb->createNamedParameter($expirationTime, IQueryBuilder::PARAM_DATETIME));
}

$qb->executeStatement();
Expand Down Expand Up @@ -747,10 +747,10 @@ public function update(IShare $share, ?string $plainTextPassword = null): IShare
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
->set('password', $qb->createNamedParameter($share->getPassword()))
->set('password_expiration_time', $qb->createNamedParameter($share->getPasswordExpirationTime(), IQueryBuilder::PARAM_DATE))
->set('password_expiration_time', $qb->createNamedParameter($share->getPasswordExpirationTime(), IQueryBuilder::PARAM_DATETIME))
->set('label', $qb->createNamedParameter($share->getLabel()))
->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATETIME))
->set('note', $qb->createNamedParameter($share->getNote()))
->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT))
->set('attributes', $qb->createNamedParameter($shareAttributes))
Expand Down
20 changes: 10 additions & 10 deletions lib/private/Comments/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -440,14 +440,14 @@ public function getCommentsWithVerbForObjectSinceComment(
$query->expr()->orX(
$query->expr()->lt(
'creation_timestamp',
$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
IQueryBuilder::PARAM_DATE
$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATETIME),
IQueryBuilder::PARAM_DATETIME
),
$query->expr()->andX(
$query->expr()->eq(
'creation_timestamp',
$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
IQueryBuilder::PARAM_DATE
$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATETIME),
IQueryBuilder::PARAM_DATETIME
),
$idComparison
)
Expand All @@ -463,14 +463,14 @@ public function getCommentsWithVerbForObjectSinceComment(
$query->expr()->orX(
$query->expr()->gt(
'creation_timestamp',
$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
IQueryBuilder::PARAM_DATE
$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATETIME),
IQueryBuilder::PARAM_DATETIME
),
$query->expr()->andX(
$query->expr()->eq(
'creation_timestamp',
$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
IQueryBuilder::PARAM_DATE
$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATETIME),
IQueryBuilder::PARAM_DATETIME
),
$idComparison
)
Expand Down Expand Up @@ -740,7 +740,7 @@ public function getLastCommentBeforeDate(string $objectType, string $objectId, \
->from('comments')
->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATE)))
->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATETIME)))
->orderBy('creation_timestamp', 'desc');

if ($verb !== '') {
Expand Down Expand Up @@ -1551,7 +1551,7 @@ public function deleteCommentsExpiredAtObject(string $objectType, string $object
$qb = $this->dbConn->getQueryBuilder();
$qb->delete('comments')
->where($qb->expr()->lte('expire_date',
$qb->createNamedParameter($this->timeFactory->getDateTime(), IQueryBuilder::PARAM_DATE)))
$qb->createNamedParameter($this->timeFactory->getDateTime(), IQueryBuilder::PARAM_DATETIME)))
->andWhere($qb->expr()->eq('object_type', $qb->createNamedParameter($objectType)));

if ($objectId !== '') {
Expand Down
40 changes: 28 additions & 12 deletions lib/public/AppFramework/Db/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/
namespace OCP\AppFramework\Db;

use OCP\DB\Types;

use function lcfirst;
use function substr;

Expand Down Expand Up @@ -95,24 +97,38 @@ protected function setter(string $name, array $args): void {
// if type definition exists, cast to correct type
if ($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) {
$type = $this->_fieldTypes[$name];
if ($type === 'blob') {
if ($type === Types::BLOB) {
// (B)LOB is treated as string when we read from the DB
if (is_resource($args[0])) {
$args[0] = stream_get_contents($args[0]);
}
$type = 'string';
$type = Types::STRING;
}

if ($type === 'datetime') {
if (!$args[0] instanceof \DateTime) {
$args[0] = new \DateTime($args[0]);
}
} elseif ($type === 'json') {
if (!is_array($args[0])) {
$args[0] = json_decode($args[0], true);
}
} else {
settype($args[0], $type);
switch ($type) {
case Types::TIME:
case Types::DATE:
case Types::DATETIME:
case Types::DATETIME_TZ:
if (!$args[0] instanceof \DateTime) {
$args[0] = new \DateTime($args[0]);
}
break;
case Types::TIME_IMMUTABLE:
case Types::DATE_IMMUTABLE:
case Types::DATETIME_IMMUTABLE:
case Types::DATETIME_TZ_IMMUTABLE:
if (!$args[0] instanceof \DateTimeImmutable) {
$args[0] = new \DateTimeImmutable($args[0]);
}
break;
case Types::JSON:
if (!is_array($args[0])) {
$args[0] = json_decode($args[0], true);
}
break;
default:
settype($args[0], $type);
}
}
$this->$name = $args[0];
Expand Down
28 changes: 22 additions & 6 deletions lib/public/AppFramework/Db/QBMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Generator;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\Types;
use OCP\IDBConnection;

/**
Expand Down Expand Up @@ -218,18 +219,33 @@ protected function getParameterTypeForProperty(Entity $entity, string $property)

switch ($types[ $property ]) {
case 'int':
case 'integer':
case Types::INTEGER:
case Types::SMALLINT:
return IQueryBuilder::PARAM_INT;
case 'string':
case Types::STRING:
return IQueryBuilder::PARAM_STR;
case 'bool':
case 'boolean':
case Types::BOOLEAN:
return IQueryBuilder::PARAM_BOOL;
case 'blob':
case Types::BLOB:
return IQueryBuilder::PARAM_LOB;
case 'datetime':
case Types::DATE:
return IQueryBuilder::PARAM_DATE;
case 'json':
case Types::DATETIME:
return IQueryBuilder::PARAM_DATETIME;
case Types::DATETIME_TZ:
return IQueryBuilder::PARAM_DATETIME_TZ;
case Types::DATE_IMMUTABLE:
return IQueryBuilder::PARAM_DATE_IMMUTABLE;
case Types::DATETIME_IMMUTABLE:
return IQueryBuilder::PARAM_DATETIME_IMMUTABLE;
case Types::DATETIME_TZ_IMMUTABLE:
return IQueryBuilder::PARAM_DATETIME_TZ_IMMUTABLE;
case Types::TIME:
return IQueryBuilder::PARAM_TIME;
case Types::TIME_IMMUTABLE:
return IQueryBuilder::PARAM_TIME_IMMUTABLE;
case Types::JSON:
return IQueryBuilder::PARAM_JSON;
}

Expand Down
47 changes: 46 additions & 1 deletion lib/public/DB/QueryBuilder/IQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\Types;
use OCP\DB\Exception;
use OCP\DB\IResult;
use OCP\IDBConnection;
Expand Down Expand Up @@ -41,10 +42,54 @@ interface IQueryBuilder {
* @since 9.0.0
*/
public const PARAM_LOB = ParameterType::LARGE_OBJECT;

/**
* For passing a \DateTime instance when only interested in the time part (without timezone support)
* @since 31.0.0
*/
public const PARAM_TIME = Types::TIME_MUTABLE;

/**
* For passing a \DateTime instance when only interested in the date part (without timezone support)
* @since 9.0.0
*/
public const PARAM_DATE = 'datetime';
public const PARAM_DATE = Types::DATE_MUTABLE;

/**
* For passing a \DateTime instance (without timezone support)
* @since 31.0.0
*/
public const PARAM_DATETIME = Types::DATETIME_MUTABLE;

/**
* For passing a \DateTime instance with timezone support
* @since 31.0.0
*/
public const PARAM_DATETIME_TZ = Types::DATETIMETZ_MUTABLE;

/**
* For passing a \DateTimeImmutable instance when only interested in the time part (without timezone support)
* @since 31.0.0
*/
public const PARAM_TIME_IMMUTABLE = Types::TIME_MUTABLE;

/**
* For passing a \DateTime instance when only interested in the date part (without timezone support)
* @since 9.0.0
*/
public const PARAM_DATE_IMMUTABLE = Types::DATE_IMMUTABLE;

/**
* For passing a \DateTime instance (without timezone support)
* @since 31.0.0
*/
public const PARAM_DATETIME_IMMUTABLE = Types::DATETIME_IMMUTABLE;

/**
* For passing a \DateTime instance with timezone support
* @since 31.0.0
*/
public const PARAM_DATETIME_TZ_IMMUTABLE = Types::DATETIMETZ_IMMUTABLE;

/**
* @since 24.0.0
Expand Down
77 changes: 77 additions & 0 deletions lib/public/DB/Types.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,76 @@ final class Types {
public const BOOLEAN = 'boolean';

/**
* A datetime instance with only the date set.
* This will be (de)serialized into a \DateTime instance,
* it is recommended to instead use the `DATE_IMMUTABLE` instead.
*
* Warning: When deserialized the timezone will be set to UTC.
* @var string
* @since 21.0.0
*/
public const DATE = 'date';

/**
* An immutable datetime instance with only the date set.
* This will be (de)serialized into a \DateTimeImmutable instance,
* It is recommended to use this over the `DATE` type because
* out `Entity` class works detecting changes through the setter,
* changes on mutable objects can not be detected.
*
* Warning: When deserialized the timezone will be set to UTC.
* @var string
* @since 31.0.0
*/
public const DATE_IMMUTABLE = 'date_immutable';

/**
* A datetime instance with date and time support.
* This will be (de)serialized into a \DateTime instance,
* it is recommended to instead use the `DATETIME_IMMUTABLE` instead.
*
* Warning: When deserialized the timezone will be set to UTC.
* @var string
* @since 21.0.0
*/
public const DATETIME = 'datetime';

/**
* An immutable datetime instance with date and time set.
* This will be (de)serialized into a \DateTimeImmutable instance,
* It is recommended to use this over the `DATETIME` type because
* out `Entity` class works detecting changes through the setter,
* changes on mutable objects can not be detected.
*
* Warning: When deserialized the timezone will be set to UTC.
* @var string
* @since 31.0.0
*/
public const DATETIME_IMMUTABLE = 'datetime_immutable';


/**
* A datetime instance with timezone support
* This will be (de)serialized into a \DateTime instance,
* it is recommended to instead use the `DATETIME_TZ_IMMUTABLE` instead.
*
* @var string
* @since 31.0.0
*/
public const DATETIME_TZ = 'datetimetz';

/**
* An immutable timezone aware datetime instance with date and time set.
* This will be (de)serialized into a \DateTimeImmutable instance,
* It is recommended to use this over the `DATETIME_TZ` type because
* out `Entity` class works detecting changes through the setter,
* changes on mutable objects can not be detected.
*
* @var string
* @since 31.0.0
*/
public const DATETIME_TZ_IMMUTABLE = 'datetimetz_immutable';

/**
* @var string
* @since 21.0.0
Expand Down Expand Up @@ -89,11 +148,29 @@ final class Types {
public const TEXT = 'text';

/**
* A datetime instance with only the time set.
* This will be (de)serialized into a \DateTime instance,
* it is recommended to instead use the `TIME_IMMUTABLE` instead.
*
* Warning: When deserialized the timezone will be set to UTC.
* @var string
* @since 21.0.0
*/
public const TIME = 'time';

/**
* A datetime instance with only the time set.
* This will be (de)serialized into a \DateTime instance.
*
* It is recommended to use this over the `DATETIME_TZ` type because
* out `Entity` class works detecting changes through the setter,
* changes on mutable objects can not be detected.
*
* @var string
* @since 31.0.0
*/
public const TIME_IMMUTABLE = 'time_immutable';

/**
* @var string
* @since 24.0.0
Expand Down
Loading

0 comments on commit b338e58

Please sign in to comment.