Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cast datetime columns in sqlite before comparing #26540

Merged
merged 2 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ public function orX(...$x): ICompositeExpression {
* @return string
*/
public function comparison($x, string $operator, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->comparison($x, $operator, $y);
}

Expand All @@ -140,8 +140,8 @@ public function comparison($x, string $operator, $y, $type = null): string {
* @return string
*/
public function eq($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->eq($x, $y);
}

Expand All @@ -162,8 +162,8 @@ public function eq($x, $y, $type = null): string {
* @return string
*/
public function neq($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->neq($x, $y);
}

Expand All @@ -184,8 +184,8 @@ public function neq($x, $y, $type = null): string {
* @return string
*/
public function lt($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->lt($x, $y);
}

Expand All @@ -206,8 +206,8 @@ public function lt($x, $y, $type = null): string {
* @return string
*/
public function lte($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->lte($x, $y);
}

Expand All @@ -228,8 +228,8 @@ public function lte($x, $y, $type = null): string {
* @return string
*/
public function gt($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->gt($x, $y);
}

Expand All @@ -250,8 +250,8 @@ public function gt($x, $y, $type = null): string {
* @return string
*/
public function gte($x, $y, $type = null): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->gte($x, $y);
}

Expand Down Expand Up @@ -435,4 +435,13 @@ public function castColumn($column, $type): IQueryFunction {
$this->helper->quoteColumnName($column)
);
}

/**
* @param mixed $column
* @param mixed|null $type
* @return array|IQueryFunction|string
*/
protected function prepareColumn($column, $type) {
return $this->helper->quoteColumnNames($column);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,80 +39,9 @@ class OCIExpressionBuilder extends ExpressionBuilder {
protected function prepareColumn($column, $type) {
if ($type === IQueryBuilder::PARAM_STR && !is_array($column) && !($column instanceof IParameter) && !($column instanceof ILiteral)) {
$column = $this->castColumn($column, $type);
} else {
$column = $this->helper->quoteColumnNames($column);
}
return $column;
}

/**
* @inheritdoc
*/
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);
}

/**
* @inheritdoc
*/
public function eq($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);

return $this->expressionBuilder->eq($x, $y);
}

/**
* @inheritdoc
*/
public function neq($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);

return $this->expressionBuilder->neq($x, $y);
}

/**
* @inheritdoc
*/
public function lt($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);

return $this->expressionBuilder->lt($x, $y);
}

/**
* @inheritdoc
*/
public function lte($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);

return $this->expressionBuilder->lte($x, $y);
}

/**
* @inheritdoc
*/
public function gt($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);

return $this->expressionBuilder->gt($x, $y);
}

/**
* @inheritdoc
*/
public function gte($x, $y, $type = null): string {
$x = $this->prepareColumn($x, $type);
$y = $this->prepareColumn($y, $type);

return $this->expressionBuilder->gte($x, $y);
return parent::prepareColumn($column, $type);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
*/
namespace OC\DB\QueryBuilder\ExpressionBuilder;

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;

class SqliteExpressionBuilder extends ExpressionBuilder {
/**
* @inheritdoc
Expand All @@ -34,4 +40,33 @@ public function like($x, $y, $type = null): string {
public function iLike($x, $y, $type = null): string {
return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y), $type);
}

/**
* @param mixed $column
* @param mixed|null $type
* @return array|IQueryFunction|string
*/
protected function prepareColumn($column, $type) {
if ($type === IQueryBuilder::PARAM_DATE && !is_array($column) && !($column instanceof IParameter) && !($column instanceof ILiteral)) {
return $this->castColumn($column, $type);
}

return parent::prepareColumn($column, $type);
}

/**
* 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 {
if ($type === IQueryBuilder::PARAM_DATE) {
$column = $this->helper->quoteColumnName($column);
return new QueryFunction('DATETIME(' . $column . ')');
}

return parent::castColumn($column, $type);
}
}
86 changes: 86 additions & 0 deletions tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@

namespace Test\DB\QueryBuilder;

use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Types\Types;
use OC\DB\QueryBuilder\Literal;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\Server;
use Test\TestCase;

/**
Expand All @@ -31,11 +35,13 @@
class ExpressionBuilderDBTest extends TestCase {
/** @var \Doctrine\DBAL\Connection|\OCP\IDBConnection */
protected $connection;
protected $schemaSetup = false;

protected function setUp(): void {
parent::setUp();

$this->connection = \OC::$server->getDatabaseConnection();
$this->prepareTestingTable();
}

public function likeProvider() {
Expand Down Expand Up @@ -150,6 +156,59 @@ public function testLongText(): void {
self::assertEquals('myvalue', $entries[0]['configvalue']);
}

public function testDateTimeEquals() {
$dateTime = new \DateTime('2023-01-01');
$insert = $this->connection->getQueryBuilder();
$insert->insert('testing')
->values(['datetime' => $insert->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE)])
->executeStatement();

$query = $this->connection->getQueryBuilder();
$result = $query->select('*')
->from('testing')
->where($query->expr()->eq('datetime', $query->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE)))
->executeQuery();
$entries = $result->fetchAll();
$result->closeCursor();
self::assertCount(1, $entries);
}

public function testDateTimeLess() {
$dateTime = new \DateTime('2022-01-01');
$dateTimeCompare = new \DateTime('2022-01-02');
$insert = $this->connection->getQueryBuilder();
$insert->insert('testing')
->values(['datetime' => $insert->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE)])
->executeStatement();

$query = $this->connection->getQueryBuilder();
$result = $query->select('*')
->from('testing')
->where($query->expr()->lt('datetime', $query->createNamedParameter($dateTimeCompare, IQueryBuilder::PARAM_DATE)))
->executeQuery();
$entries = $result->fetchAll();
$result->closeCursor();
self::assertCount(1, $entries);
}

public function testDateTimeGreater() {
$dateTime = new \DateTime('2023-01-02');
$dateTimeCompare = new \DateTime('2023-01-01');
$insert = $this->connection->getQueryBuilder();
$insert->insert('testing')
->values(['datetime' => $insert->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE)])
->executeStatement();

$query = $this->connection->getQueryBuilder();
$result = $query->select('*')
->from('testing')
->where($query->expr()->gt('datetime', $query->createNamedParameter($dateTimeCompare, IQueryBuilder::PARAM_DATE)))
->executeQuery();
$entries = $result->fetchAll();
$result->closeCursor();
self::assertCount(1, $entries);
}

protected function createConfig($appId, $key, $value) {
$query = $this->connection->getQueryBuilder();
$query->insert('appconfig')
Expand All @@ -160,4 +219,31 @@ protected function createConfig($appId, $key, $value) {
])
->execute();
}

protected function prepareTestingTable(): void {
if ($this->schemaSetup) {
$this->connection->getQueryBuilder()->delete('testing')->executeStatement();
}

$prefix = Server::get(IConfig::class)->getSystemValueString('dbtableprefix', 'oc_');
$schema = $this->connection->createSchema();
try {
$schema->getTable($prefix . 'testing');
$this->connection->getQueryBuilder()->delete('testing')->executeStatement();
} catch (SchemaException $e) {
$this->schemaSetup = true;
$table = $schema->createTable($prefix . 'testing');
$table->addColumn('id', Types::BIGINT, [
'autoincrement' => true,
'notnull' => true,
]);

$table->addColumn('datetime', Types::DATETIME_MUTABLE, [
'notnull' => false,
]);

$table->setPrimaryKey(['id']);
$this->connection->migrateToSchema($schema);
}
}
}
Loading