Skip to content

Commit 390c6b8

Browse files
committed
Improve binding
1 parent 9d194a3 commit 390c6b8

File tree

5 files changed

+95
-80
lines changed

5 files changed

+95
-80
lines changed

src/Database/Adapter/MariaDB.php

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,8 @@ public function updateDocument(Document $collection, string $id, Document $docum
11121112
/**
11131113
* Update Attributes
11141114
*/
1115-
$bindIndex = 0;
1115+
$keyIndex = 0;
1116+
$opIndex = 0;
11161117
$operators = [];
11171118

11181119
// Separate regular attributes from operators
@@ -1127,17 +1128,17 @@ public function updateDocument(Document $collection, string $id, Document $docum
11271128

11281129
// Check if this is an operator or regular attribute
11291130
if (isset($operators[$attribute])) {
1130-
$operatorSQL = $this->getOperatorSQL($column, $operators[$attribute], $bindIndex);
1131+
$operatorSQL = $this->getOperatorSQL($column, $operators[$attribute], $opIndex);
11311132
$columns .= $operatorSQL . ',';
11321133
} else {
1133-
$bindKey = 'key_' . $bindIndex;
1134+
$bindKey = 'key_' . $keyIndex;
11341135

11351136
if (in_array($attribute, $spatialAttributes)) {
11361137
$columns .= "`{$column}`" . '=' . $this->getSpatialGeomFromText(':' . $bindKey) . ',';
11371138
} else {
11381139
$columns .= "`{$column}`" . '=:' . $bindKey . ',';
11391140
}
1140-
$bindIndex++;
1141+
$keyIndex++;
11411142
}
11421143
}
11431144

@@ -1159,11 +1160,12 @@ public function updateDocument(Document $collection, string $id, Document $docum
11591160
$stmt->bindValue(':_tenant', $this->tenant);
11601161
}
11611162

1162-
$attributeIndex = 0;
1163+
$keyIndex = 0;
1164+
$opIndexForBinding = 0;
11631165
foreach ($attributes as $attribute => $value) {
11641166
// Handle operators separately
11651167
if (isset($operators[$attribute])) {
1166-
$this->bindOperatorParams($stmt, $operators[$attribute], $attributeIndex);
1168+
$this->bindOperatorParams($stmt, $operators[$attribute], $opIndexForBinding);
11671169
} else {
11681170
// Convert spatial arrays to WKT, json_encode non-spatial arrays
11691171
if (\in_array($attribute, $spatialAttributes, true)) {
@@ -1174,10 +1176,10 @@ public function updateDocument(Document $collection, string $id, Document $docum
11741176
$value = json_encode($value);
11751177
}
11761178

1177-
$bindKey = 'key_' . $attributeIndex;
1179+
$bindKey = 'key_' . $keyIndex;
11781180
$value = (is_bool($value)) ? (int)$value : $value;
11791181
$stmt->bindValue(':' . $bindKey, $value, $this->getPDOType($value));
1180-
$attributeIndex++;
1182+
$keyIndex++;
11811183
}
11821184
}
11831185

@@ -1234,7 +1236,7 @@ public function getUpsertStatement(
12341236
};
12351237

12361238
$updateColumns = [];
1237-
$bindIndex = count($bindValues);
1239+
$opIndex = 0;
12381240

12391241
if (!empty($attribute)) {
12401242
// Increment specific column by its new value in place
@@ -1252,7 +1254,7 @@ public function getUpsertStatement(
12521254

12531255
// Check if this attribute has an operator
12541256
if (isset($operators[$attr])) {
1255-
$operatorSQL = $this->getOperatorSQL($filteredAttr, $operators[$attr], $bindIndex);
1257+
$operatorSQL = $this->getOperatorSQL($filteredAttr, $operators[$attr], $opIndex);
12561258
if ($operatorSQL !== null) {
12571259
$updateColumns[] = $operatorSQL;
12581260
}
@@ -1277,12 +1279,12 @@ public function getUpsertStatement(
12771279
$stmt->bindValue($key, $binding, $this->getPDOType($binding));
12781280
}
12791281

1280-
$bindIndex = count($bindValues);
1282+
$opIndexForBinding = 0;
12811283

12821284
// Bind operator parameters in the same order used to build SQL
12831285
foreach (array_keys($attributes) as $attr) {
12841286
if (isset($operators[$attr])) {
1285-
$this->bindOperatorParams($stmt, $operators[$attr], $bindIndex);
1287+
$this->bindOperatorParams($stmt, $operators[$attr], $opIndexForBinding);
12861288
}
12871289
}
12881290

@@ -2053,12 +2055,12 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
20532055
SELECT JSON_ARRAYAGG(value)
20542056
FROM JSON_TABLE({$quotedColumn}, '\$[*]' COLUMNS(value TEXT PATH '\$')) AS jt
20552057
WHERE CASE :$conditionKey
2056-
WHEN 'equals' THEN value = JSON_UNQUOTE(:$valueKey)
2057-
WHEN 'notEquals' THEN value != JSON_UNQUOTE(:$valueKey)
2058+
WHEN 'equal' THEN value = JSON_UNQUOTE(:$valueKey)
2059+
WHEN 'notEqual' THEN value != JSON_UNQUOTE(:$valueKey)
20582060
WHEN 'greaterThan' THEN CAST(value AS DECIMAL(65,30)) > CAST(JSON_UNQUOTE(:$valueKey) AS DECIMAL(65,30))
20592061
WHEN 'lessThan' THEN CAST(value AS DECIMAL(65,30)) < CAST(JSON_UNQUOTE(:$valueKey) AS DECIMAL(65,30))
2060-
WHEN 'null' THEN value IS NULL
2061-
WHEN 'notNull' THEN value IS NOT NULL
2062+
WHEN 'isNull' THEN value IS NULL
2063+
WHEN 'isNotNull' THEN value IS NOT NULL
20622064
ELSE TRUE
20632065
END
20642066
), JSON_ARRAY())";

src/Database/Adapter/Postgres.php

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,8 @@ public function updateDocument(Document $collection, string $id, Document $docum
12401240
* Update Attributes
12411241
*/
12421242

1243-
$bindIndex = 0;
1243+
$keyIndex = 0;
1244+
$opIndex = 0;
12441245
$operators = [];
12451246

12461247
// Separate regular attributes from operators
@@ -1255,16 +1256,16 @@ public function updateDocument(Document $collection, string $id, Document $docum
12551256

12561257
// Check if this is an operator, spatial attribute, or regular attribute
12571258
if (isset($operators[$attribute])) {
1258-
$operatorSQL = $this->getOperatorSQL($column, $operators[$attribute], $bindIndex);
1259+
$operatorSQL = $this->getOperatorSQL($column, $operators[$attribute], $opIndex);
12591260
$columns .= $operatorSQL . ',';
12601261
} elseif (\in_array($attribute, $spatialAttributes, true)) {
1261-
$bindKey = 'key_' . $bindIndex;
1262+
$bindKey = 'key_' . $keyIndex;
12621263
$columns .= "\"{$column}\" = " . $this->getSpatialGeomFromText(':' . $bindKey) . ',';
1263-
$bindIndex++;
1264+
$keyIndex++;
12641265
} else {
1265-
$bindKey = 'key_' . $bindIndex;
1266+
$bindKey = 'key_' . $keyIndex;
12661267
$columns .= "\"{$column}\"" . '=:' . $bindKey . ',';
1267-
$bindIndex++;
1268+
$keyIndex++;
12681269
}
12691270
}
12701271

@@ -1286,11 +1287,12 @@ public function updateDocument(Document $collection, string $id, Document $docum
12861287
$stmt->bindValue(':_tenant', $this->tenant);
12871288
}
12881289

1289-
$attributeIndex = 0;
1290+
$keyIndex = 0;
1291+
$opIndexForBinding = 0;
12901292
foreach ($attributes as $attribute => $value) {
12911293
// Handle operators separately
12921294
if (isset($operators[$attribute])) {
1293-
$this->bindOperatorParams($stmt, $operators[$attribute], $attributeIndex);
1295+
$this->bindOperatorParams($stmt, $operators[$attribute], $opIndexForBinding);
12941296
} else {
12951297
// Convert spatial arrays to WKT, json_encode non-spatial arrays
12961298
if (\in_array($attribute, $spatialAttributes, true)) {
@@ -1301,10 +1303,10 @@ public function updateDocument(Document $collection, string $id, Document $docum
13011303
$value = json_encode($value);
13021304
}
13031305

1304-
$bindKey = 'key_' . $attributeIndex;
1306+
$bindKey = 'key_' . $keyIndex;
13051307
$value = (is_bool($value)) ? ($value == true ? "true" : "false") : $value;
13061308
$stmt->bindValue(':' . $bindKey, $value, $this->getPDOType($value));
1307-
$attributeIndex++;
1309+
$keyIndex++;
13081310
}
13091311
}
13101312

@@ -1357,7 +1359,7 @@ protected function getUpsertStatement(
13571359
return "{$attribute} = {$new}";
13581360
};
13591361

1360-
$bindIndex = count($bindValues);
1362+
$opIndex = 0;
13611363

13621364
if (!empty($attribute)) {
13631365
// Increment specific column by its new value in place
@@ -1376,7 +1378,7 @@ protected function getUpsertStatement(
13761378

13771379
// Check if this attribute has an operator
13781380
if (isset($operators[$attr])) {
1379-
$operatorSQL = $this->getOperatorSQL($filteredAttr, $operators[$attr], $bindIndex, useTargetPrefix: true);
1381+
$operatorSQL = $this->getOperatorSQL($filteredAttr, $operators[$attr], $opIndex, useTargetPrefix: true);
13801382
if ($operatorSQL !== null) {
13811383
$updateColumns[] = $operatorSQL;
13821384
}
@@ -1402,12 +1404,12 @@ protected function getUpsertStatement(
14021404
$stmt->bindValue($key, $binding, $this->getPDOType($binding));
14031405
}
14041406

1405-
$bindIndex = count($bindValues);
1407+
$opIndexForBinding = 0;
14061408

14071409
// Bind operator parameters in the same order used to build SQL
14081410
foreach (array_keys($attributes) as $attr) {
14091411
if (isset($operators[$attr])) {
1410-
$this->bindOperatorParams($stmt, $operators[$attr], $bindIndex);
1412+
$this->bindOperatorParams($stmt, $operators[$attr], $opIndexForBinding);
14111413
}
14121414
}
14131415

@@ -2566,12 +2568,12 @@ protected function getOperatorSQL(string $column, Operator $operator, int &$bind
25662568
SELECT jsonb_agg(value)
25672569
FROM jsonb_array_elements({$columnRef}) AS value
25682570
WHERE CASE :$conditionKey
2569-
WHEN 'equals' THEN value = :$valueKey::jsonb
2570-
WHEN 'notEquals' THEN value != :$valueKey::jsonb
2571+
WHEN 'equal' THEN value = :$valueKey::jsonb
2572+
WHEN 'notEqual' THEN value != :$valueKey::jsonb
25712573
WHEN 'greaterThan' THEN (value::text)::numeric > trim(both '\"' from :$valueKey::text)::numeric
25722574
WHEN 'lessThan' THEN (value::text)::numeric < trim(both '\"' from :$valueKey::text)::numeric
2573-
WHEN 'null' THEN value = 'null'::jsonb
2574-
WHEN 'notNull' THEN value != 'null'::jsonb
2575+
WHEN 'isNull' THEN value = 'null'::jsonb
2576+
WHEN 'isNotNull' THEN value != 'null'::jsonb
25752577
ELSE TRUE
25762578
END
25772579
), '[]'::jsonb)";

src/Database/Adapter/SQL.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,8 @@ public function updateDocuments(Document $collection, Document $updates, array $
483483
return 0;
484484
}
485485

486-
$bindIndex = 0;
486+
$keyIndex = 0;
487+
$opIndex = 0;
487488
$columns = '';
488489
$operators = [];
489490

@@ -499,13 +500,13 @@ public function updateDocuments(Document $collection, Document $updates, array $
499500

500501
// Check if this is an operator, spatial attribute, or regular attribute
501502
if (isset($operators[$attribute])) {
502-
$columns .= $this->getOperatorSQL($column, $operators[$attribute], $bindIndex);
503+
$columns .= $this->getOperatorSQL($column, $operators[$attribute], $opIndex);
503504
} elseif (\in_array($attribute, $spatialAttributes)) {
504-
$columns .= "{$this->quote($column)} = " . $this->getSpatialGeomFromText(":key_{$bindIndex}");
505-
$bindIndex++;
505+
$columns .= "{$this->quote($column)} = " . $this->getSpatialGeomFromText(":key_{$keyIndex}");
506+
$keyIndex++;
506507
} else {
507-
$columns .= "{$this->quote($column)} = :key_{$bindIndex}";
508-
$bindIndex++;
508+
$columns .= "{$this->quote($column)} = :key_{$keyIndex}";
509+
$keyIndex++;
509510
}
510511

511512
if ($attribute !== \array_key_last($attributes)) {
@@ -541,11 +542,12 @@ public function updateDocuments(Document $collection, Document $updates, array $
541542
$stmt->bindValue(":_id_{$id}", $value);
542543
}
543544

544-
$attributeIndex = 0;
545+
$keyIndex = 0;
546+
$opIndexForBinding = 0;
545547
foreach ($attributes as $attributeName => $value) {
546548
// Skip operators as they don't need value binding
547549
if (isset($operators[$attributeName])) {
548-
$this->bindOperatorParams($stmt, $operators[$attributeName], $attributeIndex);
550+
$this->bindOperatorParams($stmt, $operators[$attributeName], $opIndexForBinding);
549551
continue;
550552
}
551553

@@ -558,13 +560,13 @@ public function updateDocuments(Document $collection, Document $updates, array $
558560
$value = \json_encode($value);
559561
}
560562

561-
$bindKey = 'key_' . $attributeIndex;
563+
$bindKey = 'key_' . $keyIndex;
562564
// For PostgreSQL, preserve boolean values directly
563565
if (!($this instanceof \Utopia\Database\Adapter\Postgres && \is_bool($value))) {
564566
$value = (\is_bool($value)) ? (int)$value : $value;
565567
}
566568
$stmt->bindValue(':' . $bindKey, $value, $this->getPDOType($value));
567-
$attributeIndex++;
569+
$keyIndex++;
568570
}
569571

570572
try {
@@ -2183,11 +2185,10 @@ protected function bindOperatorParams(\PDOStatement $stmt, Operator $operator, i
21832185
$value = $values[1] ?? null;
21842186

21852187
// SECURITY: Whitelist validation to prevent SQL injection in CASE statements
2186-
// Support all variations used across adapters (SQL, MariaDB, Postgres, SQLite)
21872188
$validConditions = [
2188-
'equal', 'notEqual', 'equals', 'notEquals', // Comparison
2189-
'greaterThan', 'greaterThanOrEqual', 'lessThan', 'lessThanOrEqual', // Numeric
2190-
'isNull', 'isNotNull', 'null', 'notNull' // Null checks
2189+
'equal', 'notEqual', // Comparison
2190+
'greaterThan', 'greaterThanEqual', 'lessThan', 'lessThanEqual', // Numeric
2191+
'isNull', 'isNotNull' // Null checks
21912192
];
21922193
if (!in_array($condition, $validConditions, true)) {
21932194
throw new DatabaseException("Invalid filter condition: {$condition}. Must be one of: " . implode(', ', $validConditions));

0 commit comments

Comments
 (0)