diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index 3c840094ec..9f9bba75da 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -55,7 +55,8 @@ return [ '$id' => ID::custom('type'), 'type' => Database::VAR_STRING, 'size' => 128, - 'required' => true, + 'required' => false, + 'default' => 'grids', 'signed' => true, 'array' => false, 'filters' => [], diff --git a/composer.lock b/composer.lock index 18d0a85073..154d3df907 100644 --- a/composer.lock +++ b/composer.lock @@ -3545,16 +3545,16 @@ }, { "name": "utopia-php/database", - "version": "0.77.0", + "version": "0.77.4", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "a8ab3a7b25a5a1edf7e7afc83eda9f7aec5ba57d" + "reference": "eaa4e275cefdeeb90bcece2f056e05b59f5b1473" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/a8ab3a7b25a5a1edf7e7afc83eda9f7aec5ba57d", - "reference": "a8ab3a7b25a5a1edf7e7afc83eda9f7aec5ba57d", + "url": "https://api.github.com/repos/utopia-php/database/zipball/eaa4e275cefdeeb90bcece2f056e05b59f5b1473", + "reference": "eaa4e275cefdeeb90bcece2f056e05b59f5b1473", "shasum": "" }, "require": { @@ -3595,9 +3595,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.77.0" + "source": "https://github.com/utopia-php/database/tree/0.77.4" }, - "time": "2025-08-07T09:30:32+00:00" + "time": "2025-08-11T13:56:31+00:00" }, { "name": "utopia-php/detector", @@ -4118,7 +4118,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "0.71.*", + "utopia-php/database": "0.*.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" diff --git a/src/Appwrite/Utopia/Response/Model/Database.php b/src/Appwrite/Utopia/Response/Model/Database.php index b38f828047..44a0d52af8 100644 --- a/src/Appwrite/Utopia/Response/Model/Database.php +++ b/src/Appwrite/Utopia/Response/Model/Database.php @@ -43,8 +43,8 @@ class Database extends Model ->addRule('type', [ 'type' => self::TYPE_STRING, 'description' => 'Database type.', - 'default' => 'grids', - 'example' => 'grids', + 'default' => 'legacy', + 'example' => 'legacy', ]) ; } diff --git a/tests/e2e/Services/Databases/Grids/DatabasesBase.php b/tests/e2e/Services/Databases/Grids/DatabasesBase.php index 1ec957a50e..1e637f8d81 100644 --- a/tests/e2e/Services/Databases/Grids/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Grids/DatabasesBase.php @@ -1382,8 +1382,7 @@ trait DatabasesBase 'columns' => ['actors'], ]); - // Indexes on array attributes are disabled due to MySQL bug - $this->assertEquals(400, $actorsArray['headers']['status-code']); + $this->assertEquals(202, $actorsArray['headers']['status-code']); $twoLevelsArray = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1396,8 +1395,7 @@ trait DatabasesBase 'orders' => ['DESC', 'DESC'], ]); - // Indexes on array attributes are disabled due to MySQL bug - $this->assertEquals(400, $twoLevelsArray['headers']['status-code']); + $this->assertEquals(202, $twoLevelsArray['headers']['status-code']); $unknown = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1423,8 +1421,7 @@ trait DatabasesBase 'orders' => ['DESC'], // Check order is removed in API ]); - // Indexes on array attributes are disabled due to MySQL bug - $this->assertEquals(400, $index1['headers']['status-code']); + $this->assertEquals(202, $index1['headers']['status-code']); $index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1436,8 +1433,7 @@ trait DatabasesBase 'columns' => ['integers'], // array attribute ]); - // Indexes on array attributes are disabled due to MySQL bug - $this->assertEquals(400, $index2['headers']['status-code']); + $this->assertEquals(202, $index2['headers']['status-code']); /** * Create Indexes by worker @@ -1451,7 +1447,7 @@ trait DatabasesBase ]), []); $this->assertIsArray($movies['body']['indexes']); - $this->assertCount(4, $movies['body']['indexes']); + $this->assertCount(8, $movies['body']['indexes']); $this->assertEquals($titleIndex['body']['key'], $movies['body']['indexes'][0]['key']); $this->assertEquals($releaseYearIndex['body']['key'], $movies['body']['indexes'][1]['key']); $this->assertEquals($releaseWithDate1['body']['key'], $movies['body']['indexes'][2]['key']); @@ -5373,6 +5369,1310 @@ trait DatabasesBase $this->assertCount(2, $rows['body']['rows']); } + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testNotContains(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'NotContains test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('NotContains test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Movies', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $movies['headers']['status-code']); + $this->assertEquals($movies['body']['name'], 'Movies'); + + // Create Attributes + $title = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $movies['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $title['headers']['status-code']); + + $genre = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $movies['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'genre', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $genre['headers']['status-code']); + + // Wait for worker + sleep(2); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $movies['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Spider-Man: Homecoming', + 'genre' => 'Action', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $movies['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'The Avengers', + 'genre' => 'Action', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $movies['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Romantic Comedy', + 'genre' => 'Romance', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals(201, $row3['headers']['status-code']); + + // Test notContains query - should return movies that don't contain "Spider" in title + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $movies['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::select(['title', 'genre'])->toString(), + Query::notContains('title', ['Spider'])->toString(), + Query::limit(999)->toString(), + Query::offset(0)->toString() + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(2, $rows['body']['rows']); + $this->assertEquals('The Avengers', $rows['body']['rows'][0]['title']); + $this->assertEquals('Romantic Comedy', $rows['body']['rows'][1]['title']); + } + + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testNotSearch(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'NotSearch test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('NotSearch test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $books = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Books', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $books['headers']['status-code']); + $this->assertEquals($books['body']['name'], 'Books'); + + // Create Attributes + $title = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $books['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $title['headers']['status-code']); + + $description = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $books['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'description', + 'size' => 2048, + 'required' => true, + ]); + + $this->assertEquals(202, $description['headers']['status-code']); + + \sleep(2); + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $books['body']['$id'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'fts_description', + 'type' => Database::INDEX_FULLTEXT, + 'columns' => ['description'], + ]); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $books['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Science Fiction Adventures', + 'description' => 'A thrilling journey through space and time', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $books['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Romance Novel', + 'description' => 'A love story set in modern times', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $books['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Mystery Thriller', + 'description' => 'A detective solves complex crimes', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals(201, $row3['headers']['status-code']); + + // Test notSearch query - should return books that don't have "space" in the description + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $books['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::notSearch('description', 'space')->toString(), + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(2, $rows['body']['rows']); + $this->assertEquals('Romance Novel', $rows['body']['rows'][0]['title']); + $this->assertEquals('Mystery Thriller', $rows['body']['rows'][1]['title']); + } + + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testNotBetween(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'NotBetween test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('NotBetween test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $products = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Products', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $products['headers']['status-code']); + $this->assertEquals($products['body']['name'], 'Products'); + + // Create Attributes + $name = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $products['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $name['headers']['status-code']); + + $price = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $products['body']['$id'] . '/columns/float', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'price', + 'required' => true, + ]); + + $this->assertEquals(202, $price['headers']['status-code']); + + // Wait for worker + sleep(2); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $products['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Cheap Product', + 'price' => 5.99, + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $products['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Mid Product', + 'price' => 25.00, + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $products['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Expensive Product', + 'price' => 150.00, + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals(201, $row3['headers']['status-code']); + + // Test notBetween query - should return products NOT priced between 10 and 50 + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $products['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::notBetween('price', 10, 50)->toString(), + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(2, $rows['body']['rows']); + $this->assertEquals('Cheap Product', $rows['body']['rows'][0]['name']); + $this->assertEquals('Expensive Product', $rows['body']['rows'][1]['name']); + } + + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testNotStartsWith(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'NotStartsWith test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('NotStartsWith test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $employees = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Employees', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $employees['headers']['status-code']); + $this->assertEquals($employees['body']['name'], 'Employees'); + + // Create Attributes + $name = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $employees['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $name['headers']['status-code']); + + $department = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $employees['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'department', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $department['headers']['status-code']); + + // Wait for worker + sleep(2); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $employees['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'John Smith', + 'department' => 'Engineering', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $employees['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Jane Doe', + 'department' => 'Marketing', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $employees['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Bob Johnson', + 'department' => 'Sales', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals(201, $row3['headers']['status-code']); + + // Test notStartsWith query - should return employees whose names don't start with "John" + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $employees['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::notStartsWith('name', 'John')->toString(), + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(2, $rows['body']['rows']); + $this->assertEquals('Jane Doe', $rows['body']['rows'][0]['name']); + $this->assertEquals('Bob Johnson', $rows['body']['rows'][1]['name']); + } + + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testNotEndsWith(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'NotEndsWith test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('NotEndsWith test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $files = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Files', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $files['headers']['status-code']); + $this->assertEquals($files['body']['name'], 'Files'); + + // Create Attributes + $filename = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $files['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'filename', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $filename['headers']['status-code']); + + $type = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $files['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'type', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $type['headers']['status-code']); + + // Wait for worker + sleep(2); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $files['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'filename' => 'document.pdf', + 'type' => 'PDF', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $files['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'filename' => 'image.jpg', + 'type' => 'Image', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $files['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'filename' => 'presentation.pptx', + 'type' => 'Presentation', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals(201, $row3['headers']['status-code']); + + // Test notEndsWith query - should return files that don't end with ".pdf" + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $files['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::notEndsWith('filename', '.pdf')->toString(), + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(2, $rows['body']['rows']); + $this->assertEquals('image.jpg', $rows['body']['rows'][0]['filename']); + $this->assertEquals('presentation.pptx', $rows['body']['rows'][1]['filename']); + } + + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testCreatedBefore(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'CreatedBefore test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('CreatedBefore test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $posts = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Posts', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $posts['headers']['status-code']); + $this->assertEquals($posts['body']['name'], 'Posts'); + + // Create Attributes + $title = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $posts['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $title['headers']['status-code']); + + $content = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $posts['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'content', + 'size' => 512, + 'required' => true, + ]); + + $this->assertEquals(202, $content['headers']['status-code']); + + // Wait for worker + sleep(2); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $posts['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Old Post', + 'content' => 'This is an old post content', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + + // Sleep to ensure different creation times + sleep(1); + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $posts['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Recent Post', + 'content' => 'This is a recent post content', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + + // Get the creation time of the second post to use as boundary + $secondPostCreatedAt = $row2['body']['$createdAt']; + + // Sleep again + sleep(1); + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $posts['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Newest Post', + 'content' => 'This is the newest post content', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals(201, $row3['headers']['status-code']); + + // Test createdBefore query - should return posts created before the second post + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $posts['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::createdBefore($secondPostCreatedAt)->toString(), + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(1, $rows['body']['rows']); + $this->assertEquals('Old Post', $rows['body']['rows'][0]['title']); + } + + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testCreatedAfter(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'CreatedAfter test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('CreatedAfter test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $events = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Events', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $events['headers']['status-code']); + $this->assertEquals($events['body']['name'], 'Events'); + + // Create Attributes + $name = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $events['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $name['headers']['status-code']); + + $description = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $events['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'description', + 'size' => 512, + 'required' => true, + ]); + + $this->assertEquals(202, $description['headers']['status-code']); + + // Wait for worker + sleep(2); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $events['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Early Event', + 'description' => 'This is an early event', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + + // Sleep to ensure different creation times + sleep(1); + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $events['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Middle Event', + 'description' => 'This is a middle event', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + + // Get the creation time of the second event to use as boundary + $secondEventCreatedAt = $row2['body']['$createdAt']; + + // Sleep again + sleep(1); + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $events['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'name' => 'Latest Event', + 'description' => 'This is the latest event', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals(201, $row3['headers']['status-code']); + + // Test createdAfter query - should return events created after the second event + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $events['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::createdAfter($secondEventCreatedAt)->toString(), + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(1, $rows['body']['rows']); + $this->assertEquals('Latest Event', $rows['body']['rows'][0]['name']); + } + + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testUpdatedBefore(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'UpdatedBefore test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('UpdatedBefore test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $tasks = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Tasks', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $tasks['headers']['status-code']); + $this->assertEquals($tasks['body']['name'], 'Tasks'); + + // Create Attributes + $title = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $title['headers']['status-code']); + + $status = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $status['headers']['status-code']); + + // Wait for worker + sleep(2); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Task One', + 'status' => 'pending', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + $taskOneId = $row1['body']['$id']; + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Task Two', + 'status' => 'pending', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + $taskTwoId = $row2['body']['$id']; + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'title' => 'Task Three', + 'status' => 'pending', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row3['headers']['status-code']); + $taskThreeId = $row3['body']['$id']; + + // Update first task + sleep(1); + $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/rows/' . $taskOneId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'status' => 'completed', + ] + ]); + + // Update second task and get its updated time + sleep(1); + $updatedTaskTwo = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/rows/' . $taskTwoId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'status' => 'in_progress', + ] + ]); + $secondTaskUpdatedAt = $updatedTaskTwo['body']['$updatedAt']; + + // Update third task + sleep(1); + $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/rows/' . $taskThreeId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'status' => 'review', + ] + ]); + + // Test updatedBefore query - should return tasks updated before the second task's update time + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $tasks['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::updatedBefore($secondTaskUpdatedAt)->toString(), + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(1, $rows['body']['rows']); + $this->assertEquals('Task One', $rows['body']['rows'][0]['title']); + $this->assertEquals('completed', $rows['body']['rows'][0]['status']); + } + + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testUpdatedAfter(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'UpdatedAfter test' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('UpdatedAfter test', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $orders = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'tableId' => ID::unique(), + 'name' => 'Orders', + 'rowSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $orders['headers']['status-code']); + $this->assertEquals($orders['body']['name'], 'Orders'); + + // Create Attributes + $orderNumber = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'orderNumber', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $orderNumber['headers']['status-code']); + + $status = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/columns/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $status['headers']['status-code']); + + // Wait for worker + sleep(2); + + $row1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'orderNumber' => 'ORD-001', + 'status' => 'pending', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row1['headers']['status-code']); + $orderOneId = $row1['body']['$id']; + + $row2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'orderNumber' => 'ORD-002', + 'status' => 'pending', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row2['headers']['status-code']); + $orderTwoId = $row2['body']['$id']; + + $row3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'rowId' => ID::unique(), + 'data' => [ + 'orderNumber' => 'ORD-003', + 'status' => 'pending', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $row3['headers']['status-code']); + $orderThreeId = $row3['body']['$id']; + + // Update first order + sleep(1); + $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/rows/' . $orderOneId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'status' => 'processing', + ] + ]); + + // Update second order and get its updated time + sleep(1); + $updatedOrderTwo = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/rows/' . $orderTwoId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'status' => 'shipped', + ] + ]); + $secondOrderUpdatedAt = $updatedOrderTwo['body']['$updatedAt']; + + // Update third order + sleep(1); + $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/rows/' . $orderThreeId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'status' => 'delivered', + ] + ]); + + // Test updatedAfter query - should return orders updated after the second order's update time + $rows = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/grids/tables/' . $orders['body']['$id'] . '/rows', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::updatedAfter($secondOrderUpdatedAt)->toString(), + ], + ] + ); + + $this->assertEquals(200, $rows['headers']['status-code']); + $this->assertCount(1, $rows['body']['rows']); + $this->assertEquals('ORD-003', $rows['body']['rows'][0]['orderNumber']); + $this->assertEquals('delivered', $rows['body']['rows'][0]['status']); + } + /** * @depends testCreateDatabase * @param array $data diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 3fbb589fb0..0fd8fb6e3d 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -1382,8 +1382,7 @@ trait DatabasesBase 'attributes' => ['actors'], ]); - // Indexes on array attributes are disabled due to MySQL bug - $this->assertEquals(400, $actorsArray['headers']['status-code']); + $this->assertEquals(202, $actorsArray['headers']['status-code']); $twoLevelsArray = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1396,8 +1395,7 @@ trait DatabasesBase 'orders' => ['DESC', 'DESC'], ]); - // Indexes on array attributes are disabled due to MySQL bug - $this->assertEquals(400, $twoLevelsArray['headers']['status-code']); + $this->assertEquals(202, $twoLevelsArray['headers']['status-code']); $unknown = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1423,8 +1421,7 @@ trait DatabasesBase 'orders' => ['DESC'], // Check order is removed in API ]); - // Indexes on array attributes are disabled due to MySQL bug - $this->assertEquals(400, $index1['headers']['status-code']); + $this->assertEquals(202, $index1['headers']['status-code']); $index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1436,8 +1433,7 @@ trait DatabasesBase 'attributes' => ['integers'], // array attribute ]); - // Indexes on array attributes are disabled due to MySQL bug - $this->assertEquals(400, $index2['headers']['status-code']); + $this->assertEquals(202, $index2['headers']['status-code']); /** * Create Indexes by worker @@ -1451,7 +1447,7 @@ trait DatabasesBase ]), []); $this->assertIsArray($movies['body']['indexes']); - $this->assertCount(4, $movies['body']['indexes']); + $this->assertCount(8, $movies['body']['indexes']); $this->assertEquals($titleIndex['body']['key'], $movies['body']['indexes'][0]['key']); $this->assertEquals($releaseYearIndex['body']['key'], $movies['body']['indexes'][1]['key']); $this->assertEquals($releaseWithDate1['body']['key'], $movies['body']['indexes'][2]['key']);