diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 88fd7d64f..b13d2d127 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -747,7 +747,7 @@ abstract public function deleteDocument(string $collection, string $id): bool; * Delete Documents * * @param string $collection - * @param array $sequences + * @param array $sequences * @param array $permissionIds * * @return int @@ -854,6 +854,13 @@ abstract public function getMaxIndexLength(): int; */ abstract public function getMinDateTime(): \DateTime; + /** + * Get Id's attribute primitive type int or string + * + * @return string + */ + abstract public function getIdAttributeType(): string; + /** * Get the maximum supported DateTime value * diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 2cd8551f5..43716be0f 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1904,6 +1904,9 @@ protected function getSQLType(string $type, int $size, bool $signed = true, bool } switch ($type) { + case Database::VAR_ID: + return 'bigint UNSIGNED'; + case Database::VAR_STRING: // $size = $size * 4; // Convert utf8mb4 size to bytes if ($size > 16777215) { diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 302338aa9..1fe2c1d37 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -489,4 +489,9 @@ protected function execute(mixed $stmt): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } + + public function getIdAttributeType(): string + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 2fc4827b5..982244d2b 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1817,6 +1817,9 @@ protected function getSQLType(string $type, int $size, bool $signed = true, bool } switch ($type) { + case Database::VAR_ID: + return 'bigint'; + case Database::VAR_STRING: // $size = $size * 4; // Convert utf8mb4 size to bytes if ($size > $this->getMaxVarcharLength()) { diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 7edb6a0c1..c4ec47eb5 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -369,7 +369,7 @@ public function getDocument(string $collection, string $id, array $queries = [], $document = $document[0]; if (\array_key_exists('_id', $document)) { - $document['$sequence'] = $document['_id']; + $document['$sequence'] = (int)$document['_id']; unset($document['_id']); } if (\array_key_exists('_uid', $document)) { @@ -642,7 +642,7 @@ public function updateDocuments(string $collection, Document $updates, array $do * Delete Documents * * @param string $collection - * @param array $sequences + * @param array $sequences * @param array $permissionIds * * @return int @@ -1003,6 +1003,10 @@ public function getAttributeWidth(Document $collection): int } switch ($attribute['type']) { + case Database::VAR_ID: + $total += 8; // BIGINT 8 bytes + break; + case Database::VAR_STRING: /** * Text / Mediumtext / Longtext @@ -1577,6 +1581,14 @@ public function getMaxVarcharLength(): int return 16381; // Floor value for Postgres:16383 | MySQL:16381 | MariaDB:16382 } + /** + * @return string + */ + public function getIdAttributeType(): string + { + return Database::VAR_ID_INT; + } + /** * @return int */ diff --git a/src/Database/Database.php b/src/Database/Database.php index 8b51798f9..7b2eefb6e 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -40,6 +40,11 @@ class Database public const VAR_FLOAT = 'double'; public const VAR_BOOLEAN = 'boolean'; public const VAR_DATETIME = 'datetime'; + public const VAR_ID = 'id'; + + // VAR_ID types + public const VAR_ID_INT = '_integer'; + public const VAR_ID_MONGO = '_mongo'; public const INT_MAX = 2147483647; public const BIG_INT_MAX = PHP_INT_MAX; @@ -166,8 +171,8 @@ class Database ], [ '$id' => '$sequence', - 'type' => self::VAR_STRING, - 'size' => Database::LENGTH_KEY, + 'type' => self::VAR_ID, + 'size' => 0, 'required' => true, 'signed' => true, 'array' => false, @@ -184,7 +189,7 @@ class Database ], [ '$id' => '$tenant', - 'type' => self::VAR_INTEGER, + 'type' => self::VAR_ID, 'size' => 0, 'required' => false, 'default' => null, @@ -1787,6 +1792,9 @@ private function validateAttribute( $this->checkAttribute($collection, $attribute); switch ($type) { + case self::VAR_ID: + + break; case self::VAR_STRING: if ($size > $this->adapter->getLimitForString()) { throw new DatabaseException('Max size allowed for string is: ' . number_format($this->adapter->getLimitForString())); @@ -3646,6 +3654,7 @@ public function createDocument(string $collection, Document $document): Document $structure = new Structure( $collection, + $this->adapter->getIdAttributeType(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), ); @@ -3664,6 +3673,7 @@ public function createDocument(string $collection, Document $document): Document $document = $this->silent(fn () => $this->populateDocumentRelationships($collection, $document)); } + $document = $this->casting($collection, $document); $document = $this->decode($collection, $document); $this->trigger(self::EVENT_DOCUMENT_CREATE, $document); @@ -3730,10 +3740,12 @@ public function createDocuments( } } + $document = $this->casting($collection, $document); $document = $this->encode($collection, $document); $validator = new Structure( $collection, + $this->adapter->getIdAttributeType(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), ); @@ -4264,6 +4276,7 @@ public function updateDocument(string $collection, string $id, Document $documen $structureValidator = new Structure( $collection, + $this->adapter->getIdAttributeType(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), ); @@ -4346,6 +4359,7 @@ public function updateDocuments( $validator = new DocumentsValidator( $attributes, $indexes, + $this->adapter->getIdAttributeType(), $this->maxQueryValues, $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), @@ -4381,6 +4395,7 @@ public function updateDocuments( // Check new document structure $validator = new PartialStructure( $collection, + $this->adapter->getIdAttributeType(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), ); @@ -5018,6 +5033,7 @@ public function createOrUpdateDocumentsWithIncrease( $validator = new Structure( $collection, + $this->adapter->getIdAttributeType(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), ); @@ -5794,6 +5810,7 @@ public function deleteDocuments( $validator = new DocumentsValidator( $attributes, $indexes, + $this->adapter->getIdAttributeType(), $this->maxQueryValues, $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime() @@ -5985,6 +6002,7 @@ public function find(string $collection, array $queries = [], string $forPermiss $validator = new DocumentsValidator( $attributes, $indexes, + $this->adapter->getIdAttributeType(), $this->maxQueryValues, $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), @@ -6238,6 +6256,7 @@ public function count(string $collection, array $queries = [], ?int $max = null) $validator = new DocumentsValidator( $attributes, $indexes, + $this->adapter->getIdAttributeType(), $this->maxQueryValues, $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), @@ -6288,6 +6307,7 @@ public function sum(string $collection, string $attribute, array $queries = [], $validator = new DocumentsValidator( $attributes, $indexes, + $this->adapter->getIdAttributeType(), $this->maxQueryValues, $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), @@ -6494,6 +6514,14 @@ public function casting(Document $collection, Document $document): Document case self::VAR_BOOLEAN: $node = (bool)$node; break; + case self::VAR_ID: + if ($this->adapter->getIdAttributeType() === Database::VAR_ID_INT) { + $node = (int)$node; + } + else { + $node = (string)$node; + } + break; case self::VAR_INTEGER: $node = (int)$node; break; diff --git a/src/Database/Document.php b/src/Database/Document.php index 2ed634f46..edec7a671 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -62,11 +62,11 @@ public function getId(): string } /** - * @return string + * @return int|string|null */ - public function getSequence(): string + public function getSequence(): int|string|null { - return $this->getAttribute('$sequence', ''); + return $this->getAttribute('$sequence'); } /** diff --git a/src/Database/Validator/Queries/Documents.php b/src/Database/Validator/Queries/Documents.php index 4e5c13f5f..289ccbe5b 100644 --- a/src/Database/Validator/Queries/Documents.php +++ b/src/Database/Validator/Queries/Documents.php @@ -20,11 +20,13 @@ class Documents extends IndexedQueries * * @param array $attributes * @param array $indexes + * @param string $idAttributeType * @throws Exception */ public function __construct( array $attributes, array $indexes, + string $idAttributeType, int $maxValuesCount = 100, \DateTime $minAllowedDate = new \DateTime('0000-01-01'), \DateTime $maxAllowedDate = new \DateTime('9999-12-31'), @@ -38,7 +40,7 @@ public function __construct( $attributes[] = new Document([ '$id' => '$sequence', 'key' => '$sequence', - 'type' => Database::VAR_STRING, + 'type' => Database::VAR_ID, 'array' => false, ]); $attributes[] = new Document([ @@ -60,6 +62,7 @@ public function __construct( new Cursor(), new Filter( $attributes, + $idAttributeType, $maxValuesCount, $minAllowedDate, $maxAllowedDate, diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 9fb3fe32e..9c2533558 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -6,6 +6,7 @@ use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Database\Validator\Sequence; use Utopia\Validator\Boolean; use Utopia\Validator\FloatValidator; use Utopia\Validator\Integer; @@ -25,7 +26,8 @@ class Filter extends Base * @param \DateTime $maxAllowedDate */ public function __construct( - array $attributes = [], + array $attributes, + private readonly string $idAttributeType, private readonly int $maxValuesCount = 100, private readonly \DateTime $minAllowedDate = new \DateTime('0000-01-01'), private readonly \DateTime $maxAllowedDate = new \DateTime('9999-12-31'), @@ -106,6 +108,10 @@ protected function isValidAttributeAndValues(string $attribute, array $values, s $validator = null; switch ($attributeType) { + case Database::VAR_ID: + $validator = new Sequence($this->idAttributeType, $attribute === '$sequence'); + break; + case Database::VAR_STRING: $validator = new Text(0, 0); break; diff --git a/src/Database/Validator/Sequence.php b/src/Database/Validator/Sequence.php new file mode 100644 index 000000000..aa63ebe04 --- /dev/null +++ b/src/Database/Validator/Sequence.php @@ -0,0 +1,67 @@ +primary = $primary; + $this->idAttributeType = $idAttributeType; + } + + public function isArray(): bool + { + return false; + } + + public function getType(): string + { + return self::TYPE_STRING; + } + + public function isValid($value): bool + { + if ($this->primary && empty($value)) { + return false; + } + + switch ($this->idAttributeType) { + case Database::VAR_ID_MONGO: + return preg_match('/^[a-f0-9]{24}$/i', $value) === 1; + + case Database::VAR_ID_INT: + if (gettype($value) !== 'integer') { + return false; + } + + $validator = new Integer(loose: true); + if (!$validator->isValid($value)) { + return false; + } + + $start = ($this->primary) ? 1 : 0; + $validator = new Range($start, Database::BIG_INT_MAX, Database::VAR_INTEGER); + return $validator->isValid($value); + + default: + return false; + } + } +} diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 478557697..adcc28441 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -32,8 +32,8 @@ class Structure extends Validator ], [ '$id' => '$sequence', - 'type' => Database::VAR_STRING, - 'size' => 255, + 'type' => Database::VAR_ID, + 'size' => 0, 'required' => false, 'signed' => true, 'array' => false, @@ -50,7 +50,7 @@ class Structure extends Validator ], [ '$id' => '$tenant', - 'type' => Database::VAR_INTEGER, + 'type' => Database::VAR_INTEGER, // ????? 'size' => 8, 'required' => false, 'default' => null, @@ -103,6 +103,7 @@ class Structure extends Validator */ public function __construct( protected readonly Document $collection, + private readonly string $idAttributeType, private readonly \DateTime $minAllowedDate = new \DateTime('0000-01-01'), private readonly \DateTime $maxAllowedDate = new \DateTime('9999-12-31'), ) { @@ -315,6 +316,10 @@ protected function checkForInvalidAttributeValues(array $structure, array $keys) $validators = []; switch ($type) { + case Database::VAR_ID: + $validators[] = new Sequence($this->idAttributeType, $attribute['$id'] === '$sequence'); + break; + case Database::VAR_STRING: $validators[] = new Text($size, min: 0); break; diff --git a/tests/e2e/Adapter/Scopes/AttributeTests.php b/tests/e2e/Adapter/Scopes/AttributeTests.php index fa401db2a..20fd695a2 100644 --- a/tests/e2e/Adapter/Scopes/AttributeTests.php +++ b/tests/e2e/Adapter/Scopes/AttributeTests.php @@ -71,14 +71,16 @@ public function testCreateDeleteAttribute(): void $this->assertEquals(true, $database->createAttribute('attributes', 'bigint', Database::VAR_INTEGER, 8, true)); $this->assertEquals(true, $database->createAttribute('attributes', 'float', Database::VAR_FLOAT, 0, true)); $this->assertEquals(true, $database->createAttribute('attributes', 'boolean', Database::VAR_BOOLEAN, 0, true)); + $this->assertEquals(true, $database->createAttribute('attributes', 'id', Database::VAR_ID, 0, true)); + $this->assertEquals(true, $database->createIndex('attributes', 'id_index', Database::INDEX_KEY, ['id'])); $this->assertEquals(true, $database->createIndex('attributes', 'string1_index', Database::INDEX_KEY, ['string1'])); $this->assertEquals(true, $database->createIndex('attributes', 'string2_index', Database::INDEX_KEY, ['string2'], [255])); $this->assertEquals(true, $database->createIndex('attributes', 'multi_index', Database::INDEX_KEY, ['string1', 'string2', 'string3'], [128, 128, 128])); $collection = $database->getCollection('attributes'); - $this->assertCount(8, $collection->getAttribute('attributes')); - $this->assertCount(3, $collection->getAttribute('indexes')); + $this->assertCount(9, $collection->getAttribute('attributes')); + $this->assertCount(4, $collection->getAttribute('indexes')); // Array $this->assertEquals(true, $database->createAttribute('attributes', 'string_list', Database::VAR_STRING, 128, true, null, true, true)); @@ -87,7 +89,7 @@ public function testCreateDeleteAttribute(): void $this->assertEquals(true, $database->createAttribute('attributes', 'boolean_list', Database::VAR_BOOLEAN, 0, true, null, true, true)); $collection = $database->getCollection('attributes'); - $this->assertCount(12, $collection->getAttribute('attributes')); + $this->assertCount(13, $collection->getAttribute('attributes')); // Default values $this->assertEquals(true, $database->createAttribute('attributes', 'string_default', Database::VAR_STRING, 256, false, 'test')); @@ -97,7 +99,7 @@ public function testCreateDeleteAttribute(): void $this->assertEquals(true, $database->createAttribute('attributes', 'datetime_default', Database::VAR_DATETIME, 0, false, '2000-06-12T14:12:55.000+00:00', true, false, null, [], ['datetime'])); $collection = $database->getCollection('attributes'); - $this->assertCount(17, $collection->getAttribute('attributes')); + $this->assertCount(18, $collection->getAttribute('attributes')); // Delete $this->assertEquals(true, $database->deleteAttribute('attributes', 'string1')); @@ -108,6 +110,7 @@ public function testCreateDeleteAttribute(): void $this->assertEquals(true, $database->deleteAttribute('attributes', 'bigint')); $this->assertEquals(true, $database->deleteAttribute('attributes', 'float')); $this->assertEquals(true, $database->deleteAttribute('attributes', 'boolean')); + $this->assertEquals(true, $database->deleteAttribute('attributes', 'id')); $collection = $database->getCollection('attributes'); $this->assertCount(9, $collection->getAttribute('attributes')); diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index 731525f81..346cfb5db 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -107,6 +107,15 @@ public function testCreateCollectionWithSchema(): void 'array' => false, 'filters' => [], ]), + new Document([ + '$id' => ID::custom('attribute4'), + 'type' => Database::VAR_ID, + 'size' => 0, + 'required' => false, + 'signed' => false, + 'array' => false, + 'filters' => [], + ]), ]; $indexes = [ @@ -131,6 +140,13 @@ public function testCreateCollectionWithSchema(): void 'lengths' => [], 'orders' => ['DESC', 'ASC'], ]), + new Document([ + '$id' => ID::custom('index4'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['attribute4'], + 'lengths' => [], + 'orders' => ['DESC'], + ]), ]; $collection = $database->createCollection('withSchema', $attributes, $indexes); @@ -139,22 +155,27 @@ public function testCreateCollectionWithSchema(): void $this->assertEquals('withSchema', $collection->getId()); $this->assertIsArray($collection->getAttribute('attributes')); - $this->assertCount(3, $collection->getAttribute('attributes')); + $this->assertCount(4, $collection->getAttribute('attributes')); $this->assertEquals('attribute1', $collection->getAttribute('attributes')[0]['$id']); $this->assertEquals(Database::VAR_STRING, $collection->getAttribute('attributes')[0]['type']); $this->assertEquals('attribute2', $collection->getAttribute('attributes')[1]['$id']); $this->assertEquals(Database::VAR_INTEGER, $collection->getAttribute('attributes')[1]['type']); $this->assertEquals('attribute3', $collection->getAttribute('attributes')[2]['$id']); $this->assertEquals(Database::VAR_BOOLEAN, $collection->getAttribute('attributes')[2]['type']); + $this->assertEquals('attribute4', $collection->getAttribute('attributes')[3]['$id']); + $this->assertEquals(Database::VAR_ID, $collection->getAttribute('attributes')[3]['type']); $this->assertIsArray($collection->getAttribute('indexes')); - $this->assertCount(3, $collection->getAttribute('indexes')); + $this->assertCount(4, $collection->getAttribute('indexes')); $this->assertEquals('index1', $collection->getAttribute('indexes')[0]['$id']); $this->assertEquals(Database::INDEX_KEY, $collection->getAttribute('indexes')[0]['type']); $this->assertEquals('index2', $collection->getAttribute('indexes')[1]['$id']); $this->assertEquals(Database::INDEX_KEY, $collection->getAttribute('indexes')[1]['type']); $this->assertEquals('index3', $collection->getAttribute('indexes')[2]['$id']); $this->assertEquals(Database::INDEX_KEY, $collection->getAttribute('indexes')[2]['type']); + $this->assertEquals('index4', $collection->getAttribute('indexes')[3]['$id']); + $this->assertEquals(Database::INDEX_KEY, $collection->getAttribute('indexes')[3]['type']); + $database->deleteCollection('withSchema'); diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 50bbcba57..72e5a01c1 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -39,6 +39,7 @@ public function testCreateDocument(): Document $this->assertEquals(true, $database->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, null, true, true)); $this->assertEquals(true, $database->createAttribute('documents', 'empty', Database::VAR_STRING, 32, false, null, true, true)); $this->assertEquals(true, $database->createAttribute('documents', 'with-dash', Database::VAR_STRING, 128, false, null)); + $this->assertEquals(true, $database->createAttribute('documents', 'id', Database::VAR_ID, 0, false, null)); $document = $database->createDocument('documents', new Document([ '$permissions' => [ @@ -66,6 +67,7 @@ public function testCreateDocument(): Document 'colors' => ['pink', 'green', 'blue'], 'empty' => [], 'with-dash' => 'Works', + 'id' => 1000000, ])); $this->assertNotEmpty(true, $document->getId()); @@ -89,11 +91,13 @@ public function testCreateDocument(): Document $this->assertEquals(['pink', 'green', 'blue'], $document->getAttribute('colors')); $this->assertEquals([], $document->getAttribute('empty')); $this->assertEquals('Works', $document->getAttribute('with-dash')); + $this->assertIsInt($document->getAttribute('id')); + $this->assertEquals(1000000, $document->getAttribute('id')); // Test create document with manual internal id $manualIdDocument = $database->createDocument('documents', new Document([ '$id' => '56000', - '$sequence' => '56000', + '$sequence' => 56000, '$permissions' => [ Permission::read(Role::any()), Permission::read(Role::user(ID::custom('1'))), @@ -143,6 +147,7 @@ public function testCreateDocument(): Document $this->assertEquals(['pink', 'green', 'blue'], $manualIdDocument->getAttribute('colors')); $this->assertEquals([], $manualIdDocument->getAttribute('empty')); $this->assertEquals('Works', $manualIdDocument->getAttribute('with-dash')); + $this->assertEquals(null, $manualIdDocument->getAttribute('id')); $manualIdDocument = $database->getDocument('documents', '56000'); @@ -207,6 +212,94 @@ public function testCreateDocument(): Document $this->assertEquals('Invalid document structure: Attribute "bigint_unsigned" has invalid type. Value must be a valid range between 0 and 9,223,372,036,854,775,807', $e->getMessage()); } + try { + $database->createDocument('documents', new Document([ + '$sequence' => 0, + '$permissions' => [], + 'string' => '', + 'integer_signed' => 1, + 'integer_unsigned' => 1, + 'bigint_signed' => 1, + 'bigint_unsigned' => 1, + 'float_signed' => 1, + 'float_unsigned' => 1, + 'boolean' => true, + 'colors' => [], + 'empty' => [], + 'with-dash' => '', + ])); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertTrue($e instanceof StructureException); + $this->assertEquals('Invalid document structure: Attribute "$sequence" has invalid type. Invalid sequence value', $e->getMessage()); + } + + /** + * Insert ID attribute with NULL + */ + + $documentIdNull = $database->createDocument('documents', new Document([ + 'id' => null, + '$permissions' => [Permission::read(Role::any())], + 'string' => '', + 'integer_signed' => 1, + 'integer_unsigned' => 1, + 'bigint_signed' => 1, + 'bigint_unsigned' => 1, + 'float_signed' => 1, + 'float_unsigned' => 1, + 'boolean' => true, + 'colors' => [], + 'empty' => [], + 'with-dash' => '', + ])); + $this->assertArrayHasKey('$sequence', $documentIdNull); + $this->assertNull($documentIdNull->getAttribute('id')); + + $documentIdNull = $database->getDocument('documents', $documentIdNull->getId()); + $this->assertNotEmpty(true, $documentIdNull->getId()); + $this->assertNull($documentIdNull->getAttribute('id')); + + $documentIdNull = $database->findOne('documents', [ + query::isNull('id') + ]); + $this->assertNotEmpty(true, $documentIdNull->getId()); + $this->assertNull($documentIdNull->getAttribute('id')); + + /** + * Insert ID attribute with '0' + */ + $documentId0 = $database->createDocument('documents', new Document([ + 'id' => 0, + '$permissions' => [Permission::read(Role::any())], + 'string' => '', + 'integer_signed' => 1, + 'integer_unsigned' => 1, + 'bigint_signed' => 1, + 'bigint_unsigned' => 1, + 'float_signed' => 1, + 'float_unsigned' => 1, + 'boolean' => true, + 'colors' => [], + 'empty' => [], + 'with-dash' => '', + ])); + $this->assertArrayHasKey('$sequence', $documentId0); + $this->assertIsInt($documentId0->getAttribute('id')); + $this->assertEquals(0, $documentId0->getAttribute('id')); + + $documentId0 = $database->getDocument('documents', $documentId0->getId()); + $this->assertArrayHasKey('$sequence', $documentId0); + $this->assertIsInt($documentId0->getAttribute('id')); + $this->assertEquals(0, $documentId0->getAttribute('id')); + + $documentId0 = $database->findOne('documents', [ + query::equal('id', [0]) + ]); + $this->assertArrayHasKey('$sequence', $documentId0); + $this->assertIsInt($documentId0->getAttribute('id')); + $this->assertEquals(0, $documentId0->getAttribute('id')); + return $document; } @@ -292,7 +385,7 @@ public function testCreateDocumentsWithAutoIncrement(): void for ($i = $sequence; $i <= ($sequence + $count); $i++) { $documents[] = new Document([ - '$sequence' => (string)$i, + '$sequence' => $i, '$permissions' => [ Permission::read(Role::any()), Permission::create(Role::any()), @@ -4277,7 +4370,7 @@ public function testExceptionCaseInsensitiveDuplicate(Document $document): Docum $database = static::getDatabase(); $document->setAttribute('$id', 'caseSensitive'); - $document->setAttribute('$sequence', '200'); + $document->setAttribute('$sequence', 200); $database->createDocument($document->getCollection(), $document); $document->setAttribute('$id', 'CaseSensitive'); diff --git a/tests/unit/Validator/DocumentsQueriesTest.php b/tests/unit/Validator/DocumentsQueriesTest.php index 45ae23933..17dc9bf1f 100644 --- a/tests/unit/Validator/DocumentsQueriesTest.php +++ b/tests/unit/Validator/DocumentsQueriesTest.php @@ -76,6 +76,16 @@ public function setUp(): void 'signed' => false, 'array' => false, 'filters' => [], + ]), + new Document([ + '$id' => 'id', + 'key' => 'id', + 'type' => Database::VAR_ID, + 'size' => 0, + 'required' => false, + 'signed' => false, + 'array' => false, + 'filters' => [], ]) ], 'indexes' => [ @@ -113,9 +123,14 @@ public function tearDown(): void */ public function testValidQueries(): void { - $validator = new Documents($this->collection['attributes'], $this->collection['indexes']); + $validator = new Documents( + $this->collection['attributes'], + $this->collection['indexes'], + Database::VAR_ID_INT + ); $queries = [ + Query::notEqual('id', 1000000), Query::equal('description', ['Best movie ever']), Query::equal('description', ['']), Query::equal('is_bool', [false]), @@ -146,7 +161,11 @@ public function testValidQueries(): void */ public function testInvalidQueries(): void { - $validator = new Documents($this->collection['attributes'], $this->collection['indexes']); + $validator = new Documents( + $this->collection['attributes'], + $this->collection['indexes'], + Database::VAR_ID_INT + ); $queries = ['{"method":"notEqual","attribute":"title","values":["Iron Man","Ant Man"]}']; $this->assertEquals(false, $validator->isValid($queries)); @@ -167,5 +186,7 @@ public function testInvalidQueries(): void $queries = [Query::equal('title', [])]; // empty array $this->assertEquals(false, $validator->isValid($queries)); $this->assertEquals('Invalid query: Equal queries require at least one value.', $validator->getDescription()); + + } } diff --git a/tests/unit/Validator/IndexedQueriesTest.php b/tests/unit/Validator/IndexedQueriesTest.php index 69ed9aeb1..e602a0454 100644 --- a/tests/unit/Validator/IndexedQueriesTest.php +++ b/tests/unit/Validator/IndexedQueriesTest.php @@ -80,7 +80,7 @@ public function testValid(): void $indexes, [ new Cursor(), - new Filter($attributes), + new Filter($attributes, Database::VAR_ID_INT), new Limit(), new Offset(), new Order($attributes) @@ -143,7 +143,7 @@ public function testMissingIndex(): void $indexes, [ new Cursor(), - new Filter($attributes), + new Filter($attributes, Database::VAR_ID_INT), new Limit(), new Offset(), new Order($attributes) @@ -196,7 +196,7 @@ public function testTwoAttributesFulltext(): void $indexes, [ new Cursor(), - new Filter($attributes), + new Filter($attributes, Database::VAR_ID_INT), new Limit(), new Offset(), new Order($attributes) diff --git a/tests/unit/Validator/QueriesTest.php b/tests/unit/Validator/QueriesTest.php index 86158014a..de991716f 100644 --- a/tests/unit/Validator/QueriesTest.php +++ b/tests/unit/Validator/QueriesTest.php @@ -63,7 +63,7 @@ public function testValid(): void $validator = new Queries( [ new Cursor(), - new Filter($attributes), + new Filter($attributes, Database::VAR_ID_INT), new Limit(), new Offset(), new Order($attributes) diff --git a/tests/unit/Validator/Query/FilterTest.php b/tests/unit/Validator/Query/FilterTest.php index 1388dbd7c..508573d03 100644 --- a/tests/unit/Validator/Query/FilterTest.php +++ b/tests/unit/Validator/Query/FilterTest.php @@ -18,34 +18,34 @@ class FilterTest extends TestCase */ public function setUp(): void { - $this->validator = new Filter( - attributes: [ - new Document([ - '$id' => 'string', - 'key' => 'string', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - new Document([ - '$id' => 'string_array', - 'key' => 'string_array', - 'type' => Database::VAR_STRING, - 'array' => true, - ]), - new Document([ - '$id' => 'integer_array', - 'key' => 'integer_array', - 'type' => Database::VAR_INTEGER, - 'array' => true, - ]), - new Document([ - '$id' => 'integer', - 'key' => 'integer', - 'type' => Database::VAR_INTEGER, - 'array' => false, - ]), - ], - ); + $attributes = [ + new Document([ + '$id' => 'string', + 'key' => 'string', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + new Document([ + '$id' => 'string_array', + 'key' => 'string_array', + 'type' => Database::VAR_STRING, + 'array' => true, + ]), + new Document([ + '$id' => 'integer_array', + 'key' => 'integer_array', + 'type' => Database::VAR_INTEGER, + 'array' => true, + ]), + new Document([ + '$id' => 'integer', + 'key' => 'integer', + 'type' => Database::VAR_INTEGER, + 'array' => false, + ]), + ]; + + $this->validator = new Filter($attributes, Database::VAR_ID_INT); } public function testSuccess(): void diff --git a/tests/unit/Validator/QueryTest.php b/tests/unit/Validator/QueryTest.php index 7b4125145..7ebacaed2 100644 --- a/tests/unit/Validator/QueryTest.php +++ b/tests/unit/Validator/QueryTest.php @@ -108,7 +108,7 @@ public function tearDown(): void */ public function testQuery(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $this->assertEquals(true, $validator->isValid([Query::equal('$id', ['Iron Man', 'Ant Man'])])); $this->assertEquals(true, $validator->isValid([Query::equal('$id', ['Iron Man'])])); @@ -138,7 +138,7 @@ public function testQuery(): void */ public function testAttributeNotFound(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $response = $validator->isValid([Query::equal('name', ['Iron Man'])]); $this->assertEquals(false, $response); @@ -154,7 +154,7 @@ public function testAttributeNotFound(): void */ public function testAttributeWrongType(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $response = $validator->isValid([Query::equal('title', [1776])]); $this->assertEquals(false, $response); @@ -166,7 +166,7 @@ public function testAttributeWrongType(): void */ public function testQueryDate(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $response = $validator->isValid([Query::greaterThan('birthDay', '1960-01-01 10:10:10')]); $this->assertEquals(true, $response); @@ -177,7 +177,7 @@ public function testQueryDate(): void */ public function testQueryLimit(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $response = $validator->isValid([Query::limit(25)]); $this->assertEquals(true, $response); @@ -191,7 +191,7 @@ public function testQueryLimit(): void */ public function testQueryOffset(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $response = $validator->isValid([Query::offset(25)]); $this->assertEquals(true, $response); @@ -205,7 +205,7 @@ public function testQueryOffset(): void */ public function testQueryOrder(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $response = $validator->isValid([Query::orderAsc('title')]); $this->assertEquals(true, $response); @@ -225,7 +225,7 @@ public function testQueryOrder(): void */ public function testQueryCursor(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $response = $validator->isValid([Query::cursorAfter(new Document(['$id' => 'asdf']))]); $this->assertEquals(true, $response); @@ -255,7 +255,7 @@ public function testQueryGetByType(): void */ public function testQueryEmpty(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $response = $validator->isValid([Query::equal('title', [''])]); $this->assertEquals(true, $response); @@ -284,7 +284,7 @@ public function testQueryEmpty(): void */ public function testOrQuery(): void { - $validator = new Documents($this->attributes, []); + $validator = new Documents($this->attributes, [], Database::VAR_ID_INT); $this->assertFalse($validator->isValid( [Query::or( diff --git a/tests/unit/Validator/StructureTest.php b/tests/unit/Validator/StructureTest.php index a9f641038..1d87cd3ad 100644 --- a/tests/unit/Validator/StructureTest.php +++ b/tests/unit/Validator/StructureTest.php @@ -90,6 +90,16 @@ class StructureTest extends TestCase 'array' => true, 'filters' => [], ], + [ + '$id' => 'id', + 'type' => Database::VAR_ID, + 'format' => '', + 'size' => 0, + 'required' => false, + 'signed' => false, + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [], ]; @@ -121,7 +131,10 @@ public function tearDown(): void public function testDocumentInstance(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid('string')); $this->assertEquals(false, $validator->isValid(null)); @@ -133,7 +146,10 @@ public function testDocumentInstance(): void public function testCollectionAttribute(): void { - $validator = new Structure(new Document()); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document())); @@ -142,7 +158,10 @@ public function testCollectionAttribute(): void public function testCollection(): void { - $validator = new Structure(new Document()); + $validator = new Structure( + new Document(), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -160,7 +179,10 @@ public function testCollection(): void public function testRequiredKeys(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -177,7 +199,10 @@ public function testRequiredKeys(): void public function testNullValues(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(true, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -188,6 +213,7 @@ public function testNullValues(): void 'published' => true, 'tags' => ['dog', 'cat', 'mouse'], 'feedback' => 'team@appwrite.io', + 'id' => 1000, ]))); $this->assertEquals(true, $validator->isValid(new Document([ @@ -204,7 +230,10 @@ public function testNullValues(): void public function testUnknownKeys(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -223,7 +252,10 @@ public function testUnknownKeys(): void public function testIntegerAsString(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -241,7 +273,10 @@ public function testIntegerAsString(): void public function testValidDocument(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(true, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -257,7 +292,10 @@ public function testValidDocument(): void public function testStringValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -275,7 +313,10 @@ public function testStringValidation(): void public function testArrayOfStringsValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -333,7 +374,10 @@ public function testArrayOfStringsValidation(): void */ public function testArrayAsObjectValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -349,7 +393,10 @@ public function testArrayAsObjectValidation(): void public function testArrayOfObjectsValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -365,7 +412,10 @@ public function testArrayOfObjectsValidation(): void public function testIntegerValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -396,7 +446,10 @@ public function testIntegerValidation(): void public function testArrayOfIntegersValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(true, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -451,7 +504,10 @@ public function testArrayOfIntegersValidation(): void public function testFloatValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -482,7 +538,10 @@ public function testFloatValidation(): void public function testBooleanValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -513,7 +572,10 @@ public function testBooleanValidation(): void public function testFormatValidation(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -531,7 +593,10 @@ public function testFormatValidation(): void public function testIntegerMaxRange(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -549,7 +614,10 @@ public function testIntegerMaxRange(): void public function testDoubleUnsigned(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -567,7 +635,10 @@ public function testDoubleUnsigned(): void public function testDoubleMaxRange(): void { - $validator = new Structure(new Document($this->collection)); + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -581,4 +652,75 @@ public function testDoubleMaxRange(): void ]))); } + public function testId(): void + { + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_INT + ); + + $id = 1000; + $mongoid = '507f1f77bcf86cd799439011'; + + /** + * Sql + */ + + $this->assertEquals(true, $validator->isValid(new Document([ + '$collection' => ID::custom('posts'), + 'title' => 'My Title', + 'description' => null, + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + 'id' => $id, + ]))); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => ID::custom('posts'), + 'title' => 'My Title', + 'description' => null, + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + 'id' => $mongoid, + ]))); + + /** + * Mongo + */ + $validator = new Structure( + new Document($this->collection), + Database::VAR_ID_MONGO + ); + + $this->assertEquals(true, $validator->isValid(new Document([ + '$collection' => ID::custom('posts'), + 'title' => 'My Title', + 'description' => null, + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + 'id' => $mongoid, + ]))); + + $this->assertEquals(true, $validator->isValid(new Document([ + '$collection' => ID::custom('posts'), + 'title' => 'My Title', + 'description' => null, + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + 'id' => $mongoid, + ]))); + } + }