Skip to content

Commit ef6f9bb

Browse files
committed
Abstract get SQL
1 parent edc09d3 commit ef6f9bb

12 files changed

Lines changed: 471 additions & 527 deletions

File tree

bin/tasks/operators.php

Lines changed: 257 additions & 135 deletions
Large diffs are not rendered by default.

src/Database/Adapter/MariaDB.php

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Utopia\Database\Exception\Duplicate as DuplicateException;
1111
use Utopia\Database\Exception\Limit as LimitException;
1212
use Utopia\Database\Exception\NotFound as NotFoundException;
13+
use Utopia\Database\Exception\Operator as OperatorException;
1314
use Utopia\Database\Exception\Query as QueryException;
1415
use Utopia\Database\Exception\Timeout as TimeoutException;
1516
use Utopia\Database\Exception\Truncate as TruncateException;
@@ -1923,7 +1924,7 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
19231924
$values = $operator->getValues();
19241925

19251926
switch ($method) {
1926-
// Numeric operators with NULL handling and overflow prevention
1927+
// Numeric operators
19271928
case Operator::TYPE_INCREMENT:
19281929
$bindKey = "op_{$bindIndex}";
19291930
$bindIndex++;
@@ -2000,62 +2001,89 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
20002001
}
20012002
return "{$quotedColumn} = POWER(COALESCE({$quotedColumn}, 0), :$bindKey)";
20022003

2003-
// Boolean operator with NULL handling
2004+
// String operators
2005+
case Operator::TYPE_STRING_CONCAT:
2006+
$bindKey = "op_{$bindIndex}";
2007+
$bindIndex++;
2008+
return "{$quotedColumn} = CONCAT(COALESCE({$quotedColumn}, ''), :$bindKey)";
2009+
2010+
case Operator::TYPE_STRING_REPLACE:
2011+
$searchKey = "op_{$bindIndex}";
2012+
$bindIndex++;
2013+
$replaceKey = "op_{$bindIndex}";
2014+
$bindIndex++;
2015+
return "{$quotedColumn} = REPLACE({$quotedColumn}, :$searchKey, :$replaceKey)";
2016+
2017+
// Boolean operators
20042018
case Operator::TYPE_TOGGLE:
20052019
return "{$quotedColumn} = NOT COALESCE({$quotedColumn}, FALSE)";
20062020

2007-
case Operator::TYPE_CONCAT:
2021+
// Array operators
2022+
case Operator::TYPE_ARRAY_APPEND:
20082023
$bindKey = "op_{$bindIndex}";
20092024
$bindIndex++;
2010-
return "{$quotedColumn} = CONCAT(COALESCE({$quotedColumn}, ''), :$bindKey)";
2025+
return "{$quotedColumn} = JSON_MERGE_PRESERVE(IFNULL({$quotedColumn}, JSON_ARRAY()), :$bindKey)";
2026+
2027+
case Operator::TYPE_ARRAY_PREPEND:
2028+
$bindKey = "op_{$bindIndex}";
2029+
$bindIndex++;
2030+
return "{$quotedColumn} = JSON_MERGE_PRESERVE(:$bindKey, IFNULL({$quotedColumn}, JSON_ARRAY()))";
20112031

20122032
case Operator::TYPE_ARRAY_INSERT:
2013-
// Use JSON_ARRAY_INSERT for proper array insertion
20142033
$indexKey = "op_{$bindIndex}";
20152034
$bindIndex++;
20162035
$valueKey = "op_{$bindIndex}";
20172036
$bindIndex++;
2018-
return "{$quotedColumn} = JSON_ARRAY_INSERT({$quotedColumn}, CONCAT('$[', :$indexKey, ']'), JSON_EXTRACT(:$valueKey, '$'))";
2037+
return "{$quotedColumn} = JSON_ARRAY_INSERT(
2038+
{$quotedColumn},
2039+
CONCAT('$[', :$indexKey, ']'),
2040+
JSON_EXTRACT(:$valueKey, '$')
2041+
)";
20192042

2020-
case Operator::TYPE_ARRAY_INTERSECT:
2043+
case Operator::TYPE_ARRAY_REMOVE:
20212044
$bindKey = "op_{$bindIndex}";
20222045
$bindIndex++;
20232046
return "{$quotedColumn} = IFNULL((
2024-
SELECT JSON_ARRAYAGG(jt1.value)
2025-
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt1
2026-
WHERE jt1.value IN (SELECT value FROM JSON_TABLE(:$bindKey, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt2)
2047+
SELECT JSON_ARRAYAGG(value)
2048+
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt
2049+
WHERE value != :$bindKey
20272050
), JSON_ARRAY())";
20282051

2029-
case Operator::TYPE_ARRAY_DIFF:
2052+
case Operator::TYPE_ARRAY_UNIQUE:
2053+
return "{$quotedColumn} = IFNULL((
2054+
SELECT JSON_ARRAYAGG(DISTINCT jt.value)
2055+
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt
2056+
), JSON_ARRAY())";
2057+
2058+
case Operator::TYPE_ARRAY_INTERSECT:
20302059
$bindKey = "op_{$bindIndex}";
20312060
$bindIndex++;
20322061
return "{$quotedColumn} = IFNULL((
20332062
SELECT JSON_ARRAYAGG(jt1.value)
20342063
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt1
2035-
WHERE jt1.value NOT IN (SELECT value FROM JSON_TABLE(:$bindKey, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt2)
2036-
), JSON_ARRAY())";
2037-
2038-
case Operator::TYPE_ARRAY_UNIQUE:
2039-
return "{$quotedColumn} = IFNULL((
2040-
SELECT JSON_ARRAYAGG(DISTINCT jt.value)
2041-
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt
2064+
WHERE jt1.value IN (
2065+
SELECT value
2066+
FROM JSON_TABLE(:$bindKey, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt2
2067+
)
20422068
), JSON_ARRAY())";
20432069

2044-
case Operator::TYPE_ARRAY_REMOVE:
2070+
case Operator::TYPE_ARRAY_DIFF:
20452071
$bindKey = "op_{$bindIndex}";
20462072
$bindIndex++;
20472073
return "{$quotedColumn} = IFNULL((
2048-
SELECT JSON_ARRAYAGG(value)
2049-
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt
2050-
WHERE value != :$bindKey
2074+
SELECT JSON_ARRAYAGG(jt1.value)
2075+
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt1
2076+
WHERE jt1.value NOT IN (
2077+
SELECT value
2078+
FROM JSON_TABLE(:$bindKey, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt2
2079+
)
20512080
), JSON_ARRAY())";
20522081

20532082
case Operator::TYPE_ARRAY_FILTER:
20542083
$conditionKey = "op_{$bindIndex}";
20552084
$bindIndex++;
20562085
$valueKey = "op_{$bindIndex}";
20572086
$bindIndex++;
2058-
// Note: parent binds value as JSON-encoded, so we need to unquote it for TEXT comparison
20592087
return "{$quotedColumn} = IFNULL((
20602088
SELECT JSON_ARRAYAGG(value)
20612089
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt
@@ -2072,9 +2100,22 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
20722100
END
20732101
), JSON_ARRAY())";
20742102

2103+
// Date operators
2104+
case Operator::TYPE_DATE_ADD_DAYS:
2105+
$bindKey = "op_{$bindIndex}";
2106+
$bindIndex++;
2107+
return "{$quotedColumn} = DATE_ADD({$quotedColumn}, INTERVAL :$bindKey DAY)";
2108+
2109+
case Operator::TYPE_DATE_SUB_DAYS:
2110+
$bindKey = "op_{$bindIndex}";
2111+
$bindIndex++;
2112+
return "{$quotedColumn} = DATE_SUB({$quotedColumn}, INTERVAL :$bindKey DAY)";
2113+
2114+
case Operator::TYPE_DATE_SET_NOW:
2115+
return "{$quotedColumn} = NOW()";
2116+
20752117
default:
2076-
// Fall back to parent implementation for other operators
2077-
return parent::getOperatorSQL($column, $operator, $bindIndex);
2118+
throw new OperatorException("Invalid operator: {$method}");
20782119
}
20792120
}
20802121

src/Database/Adapter/Mongo.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1498,7 +1498,7 @@ public function updateDocuments(Document $collection, Document $updates, array $
14981498
$name,
14991499
$filters,
15001500
$updateQuery,
1501-
options: $options,
1501+
$options,
15021502
multi: true,
15031503
);
15041504
} catch (MongoException $e) {

src/Database/Adapter/Postgres.php

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@
1111
use Utopia\Database\Exception\Duplicate as DuplicateException;
1212
use Utopia\Database\Exception\Limit as LimitException;
1313
use Utopia\Database\Exception\NotFound as NotFoundException;
14+
use Utopia\Database\Exception\Operator as OperatorException;
1415
use Utopia\Database\Exception\Timeout as TimeoutException;
1516
use Utopia\Database\Exception\Transaction as TransactionException;
1617
use Utopia\Database\Exception\Truncate as TruncateException;
1718
use Utopia\Database\Helpers\ID;
1819
use Utopia\Database\Operator;
1920
use Utopia\Database\Query;
2021

22+
/**
23+
* Differences between MariaDB and Postgres
24+
*
25+
* 1. Need to use CASCADE to DROP schema
26+
* 2. Quotes are different ` vs "
27+
* 3. DATETIME is TIMESTAMP
28+
* 4. Full-text search is different - to_tsvector() and to_tsquery()
29+
*/
2130
class Postgres extends SQL
2231
{
23-
/**
24-
* Differences between MariaDB and Postgres
25-
*
26-
* 1. Need to use CASCADE to DROP schema
27-
* 2. Quotes are different ` vs "
28-
* 3. DATETIME is TIMESTAMP
29-
* 4. Full-text search is different - to_tsvector() and to_tsquery()
30-
*/
31-
3232
/**
3333
* @inheritDoc
3434
*/
@@ -2413,7 +2413,7 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
24132413
$values = $operator->getValues();
24142414

24152415
switch ($method) {
2416-
// Numeric operators with NULL handling
2416+
// Numeric operators
24172417
case Operator::TYPE_INCREMENT:
24182418
$bindKey = "op_{$bindIndex}";
24192419
$bindIndex++;
@@ -2473,7 +2473,6 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
24732473
case Operator::TYPE_MODULO:
24742474
$bindKey = "op_{$bindIndex}";
24752475
$bindIndex++;
2476-
// PostgreSQL MOD requires compatible types - cast to numeric
24772476
return "{$quotedColumn} = MOD(COALESCE({$columnRef}::numeric, 0), :$bindKey::numeric)";
24782477

24792478
case Operator::TYPE_POWER:
@@ -2491,11 +2490,24 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
24912490
}
24922491
return "{$quotedColumn} = POWER(COALESCE({$columnRef}, 0), :$bindKey)";
24932492

2494-
case Operator::TYPE_CONCAT:
2493+
// String operators
2494+
case Operator::TYPE_STRING_CONCAT:
24952495
$bindKey = "op_{$bindIndex}";
24962496
$bindIndex++;
24972497
return "{$quotedColumn} = CONCAT(COALESCE({$columnRef}, ''), :$bindKey)";
24982498

2499+
case Operator::TYPE_STRING_REPLACE:
2500+
$searchKey = "op_{$bindIndex}";
2501+
$bindIndex++;
2502+
$replaceKey = "op_{$bindIndex}";
2503+
$bindIndex++;
2504+
return "{$quotedColumn} = REPLACE(COALESCE({$columnRef}, ''), :$searchKey, :$replaceKey)";
2505+
2506+
// Boolean operators
2507+
case Operator::TYPE_TOGGLE:
2508+
return "{$quotedColumn} = NOT COALESCE({$columnRef}, FALSE)";
2509+
2510+
// Array operators
24992511
case Operator::TYPE_ARRAY_APPEND:
25002512
$bindKey = "op_{$bindIndex}";
25012513
$bindIndex++;
@@ -2507,7 +2519,6 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
25072519
return "{$quotedColumn} = :$bindKey::jsonb || COALESCE({$columnRef}, '[]'::jsonb)";
25082520

25092521
case Operator::TYPE_ARRAY_UNIQUE:
2510-
// PostgreSQL-specific implementation for array unique
25112522
return "{$quotedColumn} = COALESCE((
25122523
SELECT jsonb_agg(DISTINCT value)
25132524
FROM jsonb_array_elements({$columnRef}) AS value
@@ -2527,7 +2538,6 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
25272538
$bindIndex++;
25282539
$valueKey = "op_{$bindIndex}";
25292540
$bindIndex++;
2530-
// PostgreSQL implementation using jsonb functions with row numbering
25312541
return "{$quotedColumn} = (
25322542
SELECT jsonb_agg(value ORDER BY idx)
25332543
FROM (
@@ -2566,7 +2576,6 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
25662576
$bindIndex++;
25672577
$valueKey = "op_{$bindIndex}";
25682578
$bindIndex++;
2569-
// PostgreSQL-specific implementation using jsonb_array_elements
25702579
return "{$quotedColumn} = COALESCE((
25712580
SELECT jsonb_agg(value)
25722581
FROM jsonb_array_elements({$columnRef}) AS value
@@ -2583,16 +2592,7 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
25832592
END
25842593
), '[]'::jsonb)";
25852594

2586-
case Operator::TYPE_REPLACE:
2587-
$searchKey = "op_{$bindIndex}";
2588-
$bindIndex++;
2589-
$replaceKey = "op_{$bindIndex}";
2590-
$bindIndex++;
2591-
return "{$quotedColumn} = REPLACE(COALESCE({$columnRef}, ''), :$searchKey, :$replaceKey)";
2592-
2593-
case Operator::TYPE_TOGGLE:
2594-
return "{$quotedColumn} = NOT COALESCE({$columnRef}, FALSE)";
2595-
2595+
// Date operators
25962596
case Operator::TYPE_DATE_ADD_DAYS:
25972597
$bindKey = "op_{$bindIndex}";
25982598
$bindIndex++;
@@ -2607,8 +2607,7 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
26072607
return "{$quotedColumn} = NOW()";
26082608

26092609
default:
2610-
// Fall back to parent implementation for other operators
2611-
return parent::getOperatorSQL($column, $operator, $bindIndex);
2610+
throw new OperatorException("Invalid operator: {$method}");
26122611
}
26132612
}
26142613

0 commit comments

Comments
 (0)