Skip to content

Commit 4240449

Browse files
Merge remote-tracking branch 'upstream/feat-mongodb' into feat-documentsdb
2 parents a9e4f07 + 2f60ed8 commit 4240449

File tree

12 files changed

+961
-464
lines changed

12 files changed

+961
-464
lines changed

src/Database/Adapter.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,13 @@ abstract public function getLimitForIndexes(): int;
875875
*/
876876
abstract public function getMaxIndexLength(): int;
877877

878+
/**
879+
* Get the maximum UID length for this adapter
880+
*
881+
* @return int
882+
*/
883+
abstract public function getMaxUIDLength(): int;
884+
878885
/**
879886
* Get the minimum supported DateTime value
880887
*

src/Database/Adapter/Mongo.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3019,6 +3019,14 @@ public function getMaxIndexLength(): int
30193019
return 1024;
30203020
}
30213021

3022+
/**
3023+
* @return int
3024+
*/
3025+
public function getMaxUIDLength(): int
3026+
{
3027+
return 255;
3028+
}
3029+
30223030
public function getConnectionId(): string
30233031
{
30243032
return '0';

src/Database/Adapter/Pool.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ public function getMaxIndexLength(): int
310310
return $this->delegate(__FUNCTION__, \func_get_args());
311311
}
312312

313+
public function getMaxUIDLength(): int
314+
{
315+
return $this->delegate(__FUNCTION__, \func_get_args());
316+
}
317+
313318
public function getMinDateTime(): \DateTime
314319
{
315320
return $this->delegate(__FUNCTION__, \func_get_args());

src/Database/Adapter/SQL.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,14 @@ public function getMaxIndexLength(): int
18501850
return $this->sharedTables ? 767 : 768;
18511851
}
18521852

1853+
/**
1854+
* @return int
1855+
*/
1856+
public function getMaxUIDLength(): int
1857+
{
1858+
return 36;
1859+
}
1860+
18531861
/**
18541862
* @param Query $query
18551863
* @param array<string, mixed> $binds

src/Database/Database.php

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3882,7 +3882,7 @@ private function populateOneToManyRelationshipsBatch(
38823882
return [];
38833883
}
38843884

3885-
// For batch relationship population, we need to fetch documents with all fields
3885+
// For batch relationship population, we need to fetch documents with all attributes
38863886
// to enable proper grouping by back-reference, then apply selects afterward
38873887
$selectQueries = [];
38883888
$otherQueries = [];
@@ -4154,30 +4154,30 @@ private function applySelectFiltersToDocuments(array $documents, array $selectQu
41544154
return;
41554155
}
41564156

4157-
// Collect all fields to keep from select queries
4158-
$fieldsToKeep = [];
4157+
// Collect all attributes to keep from select queries
4158+
$attributesToKeep = [];
41594159
foreach ($selectQueries as $selectQuery) {
41604160
foreach ($selectQuery->getValues() as $value) {
4161-
$fieldsToKeep[$value] = true;
4161+
$attributesToKeep[$value] = true;
41624162
}
41634163
}
41644164

41654165
// Early return if wildcard selector present
4166-
if (isset($fieldsToKeep['*'])) {
4166+
if (isset($attributesToKeep['*'])) {
41674167
return;
41684168
}
41694169

41704170
// Always preserve internal attributes (use hashmap for O(1) lookup)
41714171
$internalKeys = \array_map(fn ($attr) => $attr['$id'], $this->getInternalAttributes());
41724172
foreach ($internalKeys as $key) {
4173-
$fieldsToKeep[$key] = true;
4173+
$attributesToKeep[$key] = true;
41744174
}
41754175

41764176
foreach ($documents as $doc) {
41774177
$allKeys = \array_keys($doc->getArrayCopy());
41784178
foreach ($allKeys as $attrKey) {
41794179
// Keep if: explicitly selected OR is internal attribute ($ prefix)
4180-
if (!isset($fieldsToKeep[$attrKey]) && !\str_starts_with($attrKey, '$')) {
4180+
if (!isset($attributesToKeep[$attrKey]) && !\str_starts_with($attrKey, '$')) {
41814181
$doc->removeAttribute($attrKey);
41824182
}
41834183
}
@@ -5051,7 +5051,12 @@ public function updateDocuments(
50515051
$updatedAt = $updates->getUpdatedAt();
50525052
$updates['$updatedAt'] = ($updatedAt === null || !$this->preserveDates) ? DateTime::now() : $updatedAt;
50535053

5054-
$updates = $this->encode($collection, $updates);
5054+
$updates = $this->encode(
5055+
$collection,
5056+
$updates,
5057+
applyDefaults: false
5058+
);
5059+
50555060
// Check new document structure
50565061
$validator = new PartialStructure(
50575062
$collection,
@@ -7119,11 +7124,12 @@ public static function addFilter(string $name, callable $encode, callable $decod
71197124
*
71207125
* @param Document $collection
71217126
* @param Document $document
7127+
* @param bool $applyDefaults Whether to apply default values to null attributes
71227128
*
71237129
* @return Document
71247130
* @throws DatabaseException
71257131
*/
7126-
public function encode(Document $collection, Document $document): Document
7132+
public function encode(Document $collection, Document $document, bool $applyDefaults = true): Document
71277133
{
71287134
$attributes = $collection->getAttribute('attributes', []);
71297135
$internalDateAttributes = ['$createdAt', '$updatedAt'];
@@ -7156,6 +7162,10 @@ public function encode(Document $collection, Document $document): Document
71567162
// False positive "Call to function is_null() with mixed will always evaluate to false"
71577163
// @phpstan-ignore-next-line
71587164
if (is_null($value) && !is_null($default)) {
7165+
// Skip applying defaults during updates to avoid resetting unspecified attributes
7166+
if (!$applyDefaults) {
7167+
continue;
7168+
}
71597169
$value = ($array) ? $default : [$default];
71607170
} else {
71617171
$value = ($array) ? $value : [$value];
@@ -7711,7 +7721,7 @@ private function processRelationshipQueries(
77117721

77127722
$nestingPath = \implode('.', $nesting);
77137723

7714-
// If nestingPath is empty, it means we want all fields (*) for this relationship
7724+
// If nestingPath is empty, it means we want all attributes (*) for this relationship
77157725
if (empty($nestingPath)) {
77167726
$nestedSelections[$selectedKey][] = Query::select(['*']);
77177727
} else {
@@ -7787,7 +7797,7 @@ private function processNestedRelationshipPath(string $startCollection, array $q
77877797
}
77887798
$pathGroups[$pathKey][] = [
77897799
'method' => $query->getMethod(),
7790-
'field' => \end($parts), // The actual field to query
7800+
'attribute' => \end($parts), // The actual attribute to query
77917801
'values' => $query->getValues(),
77927802
];
77937803
}
@@ -7833,7 +7843,7 @@ private function processNestedRelationshipPath(string $startCollection, array $q
78337843
// Now walk backwards from the deepest collection to the starting collection
78347844
$leafQueries = [];
78357845
foreach ($queryGroup as $q) {
7836-
$leafQueries[] = new Query($q['method'], $q['field'], $q['values']);
7846+
$leafQueries[] = new Query($q['method'], $q['attribute'], $q['values']);
78377847
}
78387848

78397849
// Query the deepest collection
@@ -7935,7 +7945,7 @@ private function processNestedRelationshipPath(string $startCollection, array $q
79357945
* The method works by:
79367946
* 1. Parsing dot-path queries (e.g., "project.employee.company.name")
79377947
* 2. Extracting the first relationship (e.g., "project")
7938-
* 3. If the nested field still contains dots, using iterative processing
7948+
* 3. If the nested attribute still contains dots, using iterative processing
79397949
* 4. Finding matching documents in the related collection
79407950
* 5. Converting to filters on the parent collection
79417951
*
@@ -7985,7 +7995,7 @@ private function convertRelationshipQueries(
79857995
// Parse the relationship path
79867996
$parts = \explode('.', $attribute);
79877997
$relationshipKey = \array_shift($parts);
7988-
$nestedField = \implode('.', $parts);
7998+
$nestedAttribute = \implode('.', $parts);
79897999
$relationship = $relationshipsByKey[$relationshipKey] ?? null;
79908000

79918001
if (!$relationship) {
@@ -8003,7 +8013,7 @@ private function convertRelationshipQueries(
80038013

80048014
$groupedQueries[$relationshipKey]['queries'][] = [
80058015
'method' => $method,
8006-
'field' => $nestedField,
8016+
'attribute' => $nestedAttribute,
80078017
'values' => $query->getValues()
80088018
];
80098019

@@ -8022,7 +8032,7 @@ private function convertRelationshipQueries(
80228032
foreach ($group['queries'] as $queryData) {
80238033
$relatedQueries[] = new Query(
80248034
$queryData['method'],
8025-
$queryData['field'],
8035+
$queryData['attribute'],
80268036
$queryData['values']
80278037
);
80288038
}
@@ -8126,7 +8136,7 @@ private function convertRelationshipQueries(
81268136
return null;
81278137
}
81288138
} else {
8129-
// For other types, filter by the relationship field
8139+
// For other types, filter by the relationship attribute
81308140
if (!empty($matchingIds)) {
81318141
$additionalQueries[] = Query::equal($relationshipKey, $matchingIds);
81328142
} else {

src/Database/Validator/Index.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,10 @@ public function isValid($value): bool
326326
return false;
327327
}
328328

329+
if (!$this->checkMultipleFulltextIndex($value)) {
330+
return false;
331+
}
332+
329333
if (!$this->checkFulltextIndexNonString($value)) {
330334
return false;
331335
}
@@ -346,10 +350,6 @@ public function isValid($value): bool
346350
return false;
347351
}
348352

349-
if (!$this->checkMultipleFulltextIndex($value)) {
350-
return false;
351-
}
352-
353353
if (!$this->checkIdenticalIndex($value)) {
354354
return false;
355355
}

src/Database/Validator/Key.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ class Key extends Validator
1111
/**
1212
* Maximum length for Key validation
1313
*/
14-
protected const KEY_MAX_LENGTH = 255;
14+
protected int $maxLength;
1515

1616
/**
1717
* @var string
1818
*/
19-
protected string $message = 'Parameter must contain at most ' . self::KEY_MAX_LENGTH . ' chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char';
19+
protected string $message;
2020

2121
/**
2222
* Get Description.
@@ -33,9 +33,11 @@ public function getDescription(): string
3333
/**
3434
* Expression constructor
3535
*/
36-
public function __construct(bool $allowInternal = false)
36+
public function __construct(bool $allowInternal = false, int $maxLength = 255)
3737
{
3838
$this->allowInternal = $allowInternal;
39+
$this->maxLength = $maxLength;
40+
$this->message = 'Parameter must contain at most ' . $this->maxLength . ' chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char';
3941
}
4042

4143
/**
@@ -81,8 +83,8 @@ public function isValid($value): bool
8183
if (\preg_match('/[^A-Za-z0-9\_\-\.]/', $value)) {
8284
return false;
8385
}
84-
// At most KEY_MAX_LENGTH chars
85-
if (\mb_strlen($value) > self::KEY_MAX_LENGTH) {
86+
// At most maxLength chars
87+
if (\mb_strlen($value) > $this->maxLength) {
8688
return false;
8789
}
8890

src/Database/Validator/Label.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
class Label extends Key
66
{
7-
protected string $message = 'Value must be a valid string between 1 and ' . self::KEY_MAX_LENGTH . ' chars containing only alphanumeric chars';
7+
public function __construct(bool $allowInternal = false, int $maxLength = 255)
8+
{
9+
parent::__construct($allowInternal, $maxLength);
10+
$this->message = 'Value must be a valid string between 1 and ' . $this->maxLength . ' chars containing only alphanumeric chars';
11+
}
812

913
/**
1014
* Is valid.

src/Database/Validator/Query/Order.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ protected function isValidAttribute(string $attribute): bool
3838
// For relationships, just validate the top level.
3939
// Will validate each nested level during the recursive calls.
4040
$attribute = \explode('.', $attribute)[0];
41+
42+
if (isset($this->schema[$attribute])) {
43+
$this->message = 'Cannot order by nested attribute: ' . $attribute;
44+
return false;
45+
}
4146
}
4247

4348
// Search for attribute in schema

src/Database/Validator/UID.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
class UID extends Key
66
{
7+
/**
8+
* Expression constructor
9+
*/
10+
public function __construct(int $maxLength = 255)
11+
{
12+
parent::__construct(false, $maxLength);
13+
}
14+
715
/**
816
* Get Description.
917
*
@@ -13,6 +21,6 @@ class UID extends Key
1321
*/
1422
public function getDescription(): string
1523
{
16-
return 'UID must contain at most ' . self::KEY_MAX_LENGTH . ' chars. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a leading underscore';
24+
return 'UID must contain at most ' . $this->maxLength . ' chars. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a leading underscore';
1725
}
1826
}

0 commit comments

Comments
 (0)