From 706f3b9e8c0c20d0795f343c932f00536fa9963b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Mar 2026 19:53:49 +1300 Subject: [PATCH 1/7] fix: change $tenant internal attribute type from VAR_INTEGER to VAR_ID VAR_ID is adapter-aware and uses the Sequence validator, which accepts integer strings for SQL adapters and UUID7 strings for MongoDB. This fixes StructureException when creating collections with shared tables on MongoDB, where tenant values are UUID7-format strings. Co-Authored-By: Claude Opus 4.6 --- src/Database/Database.php | 3 +-- src/Database/Validator/Structure.php | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 0984b33bd..32682716a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -251,8 +251,7 @@ class Database ], [ '$id' => '$tenant', - 'type' => self::VAR_INTEGER, - //'type' => self::VAR_ID, // Inconsistency with other VAR_ID since this is an INT + 'type' => self::VAR_ID, 'size' => 0, 'required' => false, 'default' => null, diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 417e10c27..a65734dbd 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -52,11 +52,11 @@ class Structure extends Validator ], [ '$id' => '$tenant', - 'type' => Database::VAR_INTEGER, // ? VAR_ID - 'size' => 8, + 'type' => Database::VAR_ID, + 'size' => 0, 'required' => false, 'default' => null, - 'signed' => false, + 'signed' => true, 'array' => false, 'filters' => [], ], From dfeacb1ac85c26d98b84d269c798dc62ec0574d7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Mar 2026 20:36:27 +1300 Subject: [PATCH 2/7] fix: accept integer values in Sequence validator for $tenant VAR_ID type SQL adapters return integer tenant values from getTenant(), but the Sequence validator only accepted strings. Cast int to string before validation to support both SQL (integer) and MongoDB (UUID string) tenants. Co-Authored-By: Claude Opus 4.6 --- src/Database/Validator/Sequence.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Database/Validator/Sequence.php b/src/Database/Validator/Sequence.php index d528cc4ea..103b8d457 100644 --- a/src/Database/Validator/Sequence.php +++ b/src/Database/Validator/Sequence.php @@ -41,6 +41,10 @@ public function isValid($value): bool return false; } + if (\is_int($value)) { + $value = (string) $value; + } + if (!\is_string($value)) { return false; } From c74b8f0f41927dccbefb21f2a8188a8de6766663 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Mar 2026 20:46:17 +1300 Subject: [PATCH 3/7] fix: resolve $tenant type mismatch in validation and update detection - Structure validator: always validate $tenant as integer regardless of adapter ID type, since tenant IDs are integers in all adapters - updateDocument: use raw getAttribute instead of getTenant() to preserve the original type and avoid false change detection from int/string mismatch Co-Authored-By: Claude Opus 4.6 --- src/Database/Database.php | 2 +- src/Database/Validator/Structure.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 32682716a..7d465ee5d 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -6054,7 +6054,7 @@ public function updateDocument(string $collection, string $id, Document $documen $document['$createdAt'] = ($createdAt === null || !$this->preserveDates) ? $old->getCreatedAt() : $createdAt; if ($this->adapter->getSharedTables()) { - $document['$tenant'] = $old->getTenant(); // Make sure user doesn't switch tenant + $document['$tenant'] = $old->getAttribute('$tenant'); // Make sure user doesn't switch tenant } $document = new Document($document); diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index a65734dbd..9d3ca59bc 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -340,7 +340,8 @@ protected function checkForInvalidAttributeValues(array $structure, array $keys) switch ($type) { case Database::VAR_ID: - $validators[] = new Sequence($this->idAttributeType, $attribute['$id'] === '$sequence'); + $idType = ($attribute['$id'] === '$tenant') ? Database::VAR_INTEGER : $this->idAttributeType; + $validators[] = new Sequence($idType, $attribute['$id'] === '$sequence'); break; case Database::VAR_VARCHAR: From 54e8e3684afe6b916727e1aefee20ac1157bf283 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Mar 2026 22:09:09 +1300 Subject: [PATCH 4/7] fix: force $sequence to VAR_INTEGER and accept integer IDs in UUID7 mode - Structure validator: force $sequence (not $tenant) to VAR_INTEGER since sequences are always auto-incrementing integers - Sequence validator: fall through from UUID7 to integer validation so VAR_ID attributes like $tenant can be integers in any adapter Co-Authored-By: Claude Opus 4.6 --- src/Database/Validator/Sequence.php | 8 ++++++-- src/Database/Validator/Structure.php | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Database/Validator/Sequence.php b/src/Database/Validator/Sequence.php index 103b8d457..0cb3ab937 100644 --- a/src/Database/Validator/Sequence.php +++ b/src/Database/Validator/Sequence.php @@ -50,8 +50,12 @@ public function isValid($value): bool } switch ($this->idAttributeType) { - case Database::VAR_UUID7: //UUID7 - return preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-7[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i', $value) === 1; + case Database::VAR_UUID7: + if (preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-7[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i', $value) === 1) { + return true; + } + // Also accept integer IDs (e.g. $tenant may be an integer in any adapter) + // no break case Database::VAR_INTEGER: $start = ($this->primary) ? 1 : 0; $validator = new Range($start, Database::MAX_BIG_INT, Database::VAR_INTEGER); diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 9d3ca59bc..74d757b32 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -340,7 +340,7 @@ protected function checkForInvalidAttributeValues(array $structure, array $keys) switch ($type) { case Database::VAR_ID: - $idType = ($attribute['$id'] === '$tenant') ? Database::VAR_INTEGER : $this->idAttributeType; + $idType = ($attribute['$id'] === '$sequence') ? Database::VAR_INTEGER : $this->idAttributeType; $validators[] = new Sequence($idType, $attribute['$id'] === '$sequence'); break; From b4e2bffa7a3b3d58030a506004ad384c4f1217cc Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Mar 2026 22:10:11 +1300 Subject: [PATCH 5/7] fix: use adapter idAttributeType for all VAR_ID attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both $tenant and $sequence should validate using the adapter's idAttributeType since tenant can be set to a sequence value. The Sequence validator's UUID7→integer fall-through handles integer values in any adapter. Co-Authored-By: Claude Opus 4.6 --- src/Database/Validator/Structure.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 74d757b32..a65734dbd 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -340,8 +340,7 @@ protected function checkForInvalidAttributeValues(array $structure, array $keys) switch ($type) { case Database::VAR_ID: - $idType = ($attribute['$id'] === '$sequence') ? Database::VAR_INTEGER : $this->idAttributeType; - $validators[] = new Sequence($idType, $attribute['$id'] === '$sequence'); + $validators[] = new Sequence($this->idAttributeType, $attribute['$id'] === '$sequence'); break; case Database::VAR_VARCHAR: From d195b21abec87117eb81ee5484e86cfd88ae5bb4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Mar 2026 23:47:53 +1300 Subject: [PATCH 6/7] fix: accept int|string natively in Sequence validator without casting Co-Authored-By: Claude Opus 4.6 --- src/Database/Validator/Sequence.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Database/Validator/Sequence.php b/src/Database/Validator/Sequence.php index 0cb3ab937..9ba028ee5 100644 --- a/src/Database/Validator/Sequence.php +++ b/src/Database/Validator/Sequence.php @@ -41,11 +41,7 @@ public function isValid($value): bool return false; } - if (\is_int($value)) { - $value = (string) $value; - } - - if (!\is_string($value)) { + if (!\is_string($value) && !\is_int($value)) { return false; } From 2522cb1421620d6874c2c14b101939e76ed42f23 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Mar 2026 23:54:49 +1300 Subject: [PATCH 7/7] fix: guard preg_match from integer input in UUID7 validation preg_match expects string but Sequence now accepts int|string. Add is_string check so integer values skip regex and fall through to Range validation. Co-Authored-By: Claude Opus 4.6 --- src/Database/Validator/Sequence.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Sequence.php b/src/Database/Validator/Sequence.php index 9ba028ee5..4b3b6c79b 100644 --- a/src/Database/Validator/Sequence.php +++ b/src/Database/Validator/Sequence.php @@ -47,7 +47,7 @@ public function isValid($value): bool switch ($this->idAttributeType) { case Database::VAR_UUID7: - if (preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-7[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i', $value) === 1) { + if (\is_string($value) && preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-7[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i', $value) === 1) { return true; } // Also accept integer IDs (e.g. $tenant may be an integer in any adapter)