From fdbbb76e11b493fe5a471aabf59d9b430930baee Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 9 Oct 2023 20:24:07 +1300 Subject: [PATCH 01/19] Add array contains support for SQL adapters --- src/Database/Adapter/MariaDB.php | 19 ++++++++---- src/Database/Adapter/Postgres.php | 15 +++++++--- src/Database/Adapter/SQL.php | 6 ++-- tests/Database/Base.php | 29 ++++++++++--------- tests/Database/QueryTest.php | 16 ---------- tests/Database/Validator/Query/FilterTest.php | 1 + 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index d4647f754..79521f50a 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -218,7 +218,7 @@ public function createAttribute(string $collection, string $id, string $type, in $type = $this->getSQLType($type, $size, $signed); if ($array) { - $type = 'LONGTEXT'; + $type = 'JSON'; } return $this->getPDO() @@ -246,7 +246,7 @@ public function updateAttribute(string $collection, string $id, string $type, in $type = $this->getSQLType($type, $size, $signed); if ($array) { - $type = 'LONGTEXT'; + $type = 'JSON'; } return $this->getPDO() @@ -1314,19 +1314,26 @@ protected function getSQLCondition(Query $query): string switch ($query->getMethod()) { case Query::TYPE_SEARCH: - return "MATCH(table_main.{$attribute}) AGAINST (:{$placeholder}_0 IN BOOLEAN MODE)"; + return "MATCH(`table_main`.{$attribute}) AGAINST (:{$placeholder}_0 IN BOOLEAN MODE)"; case Query::TYPE_BETWEEN: - return "table_main.{$attribute} BETWEEN :{$placeholder}_0 AND :{$placeholder}_1"; + return "`table_main`.{$attribute} BETWEEN :{$placeholder}_0 AND :{$placeholder}_1"; case Query::TYPE_IS_NULL: case Query::TYPE_IS_NOT_NULL: - return "table_main.{$attribute} {$this->getSQLOperator($query->getMethod())}"; + return "`table_main`.{$attribute} {$this->getSQLOperator($query->getMethod())}"; + case Query::TYPE_CONTAINS: + $conditions = []; + foreach ($query->getValues() as $key => $value) { + $conditions[] = "JSON_CONTAINS(`table_main`.{$attribute}, :{$placeholder}_{$key})";; + } + $condition = implode(' OR ', $conditions); + return empty($condition) ? '' : '(' . $condition . ')'; default: $conditions = []; foreach ($query->getValues() as $key => $value) { - $conditions[] = $attribute . ' ' . $this->getSQLOperator($query->getMethod()) . ' :' . $placeholder . '_' . $key; + $conditions[] = "{$attribute} {$this->getSQLOperator($query->getMethod())} :{$placeholder}_{$key}"; } $condition = implode(' OR ', $conditions); return empty($condition) ? '' : '(' . $condition . ')'; diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 8df026bda..3d4579ce8 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -219,7 +219,7 @@ public function createAttribute(string $collection, string $id, string $type, in $type = $this->getSQLType($type, $size, $signed); if ($array) { - $type = 'TEXT'; + $type = 'JSONB'; } return $this->getPDO() @@ -292,7 +292,7 @@ public function updateAttribute(string $collection, string $id, string $type, in $type = $this->getSQLType($type, $size, $signed); if ($array) { - $type = 'LONGTEXT'; + $type = 'JSONB'; } if ($type == 'TIMESTAMP(3)') { @@ -1045,7 +1045,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $where[] = $this->getSQLCondition($query); } - if (Authorization::$status) { $where[] = $this->getSQLPermissionsCondition($name, $roles); } @@ -1321,7 +1320,7 @@ protected function getSQLCondition(Query $query): string default => $query->getAttribute() }); - $attribute = "\"{$query->getAttribute()}\"" ; + $attribute = "\"{$query->getAttribute()}\""; $placeholder = $this->getSQLPlaceholder($query); switch ($query->getMethod()) { @@ -1335,6 +1334,14 @@ protected function getSQLCondition(Query $query): string case Query::TYPE_IS_NOT_NULL: return "table_main.{$attribute} {$this->getSQLOperator($query->getMethod())}"; + case Query::TYPE_CONTAINS: + $conditions = []; + foreach ($query->getValues() as $key => $value) { + $conditions[] = "{$attribute} @> :{$placeholder}_{$key}"; + } + $condition = implode(' OR ', $conditions); + return empty($condition) ? '' : '(' . $condition . ')'; + default: $conditions = []; foreach ($query->getValues() as $key => $value) { diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 8525d2738..f3e0fbca0 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -678,7 +678,7 @@ public function getSupportForCasting(): bool */ public function getSupportForQueryContains(): bool { - return false; + return true; } public function getSupportForRelationships(): bool @@ -703,10 +703,12 @@ protected function bindConditionValue(mixed $stmt, Query $query): void Query::TYPE_STARTS_WITH => $this->escapeWildcards($value) . '%', Query::TYPE_ENDS_WITH => '%' . $this->escapeWildcards($value), Query::TYPE_SEARCH => $this->getFulltextValue($value), + Query::TYPE_CONTAINS => \json_encode($value), default => $value }; - $placeholder = $this->getSQLPlaceholder($query).'_'.$key; + $placeholder = $this->getSQLPlaceholder($query) . '_' . $key; + $stmt->bindValue($placeholder, $value, $this->getPDOType($value)); } } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 34609d113..a595bc006 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1483,17 +1483,18 @@ public function testDeleteDocument(Document $document): void public function testFind(): array { Authorization::setRole(Role::any()->toString()); + static::getDatabase()->createCollection('movies', permissions: [ Permission::create(Role::any()), Permission::update(Role::users()) - ], documentSecurity: true); + ]); $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'name', Database::VAR_STRING, 128, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'director', Database::VAR_STRING, 128, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'year', Database::VAR_INTEGER, 0, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'price', Database::VAR_FLOAT, 0, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'active', Database::VAR_BOOLEAN, 0, true)); - $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'generes', Database::VAR_STRING, 32, true, null, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'genres', Database::VAR_STRING, 32, true, null, true, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'with-dash', Database::VAR_STRING, 128, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'nullable', Database::VAR_STRING, 128, false)); @@ -1518,7 +1519,7 @@ public function testFind(): array 'year' => 2013, 'price' => 39.50, 'active' => true, - 'generes' => ['animation', 'kids'], + 'genres' => ['animation', 'kids'], 'with-dash' => 'Works' ])); @@ -1542,7 +1543,7 @@ public function testFind(): array 'year' => 2019, 'price' => 39.50, 'active' => true, - 'generes' => ['animation', 'kids'], + 'genres' => ['animation', 'kids'], 'with-dash' => 'Works' ])); @@ -1566,7 +1567,7 @@ public function testFind(): array 'year' => 2011, 'price' => 25.94, 'active' => true, - 'generes' => ['science fiction', 'action', 'comics'], + 'genres' => ['science fiction', 'action', 'comics'], 'with-dash' => 'Works2' ])); @@ -1590,7 +1591,7 @@ public function testFind(): array 'year' => 2019, 'price' => 25.99, 'active' => true, - 'generes' => ['science fiction', 'action', 'comics'], + 'genres' => ['science fiction', 'action', 'comics'], 'with-dash' => 'Works2' ])); @@ -1614,7 +1615,7 @@ public function testFind(): array 'year' => 2025, 'price' => 0.0, 'active' => false, - 'generes' => [], + 'genres' => [], 'with-dash' => 'Works3' ])); @@ -1636,7 +1637,7 @@ public function testFind(): array 'year' => 2026, 'price' => 0.0, 'active' => false, - 'generes' => [], + 'genres' => [], 'with-dash' => 'Works3', 'nullable' => 'Not null' ])); @@ -1665,8 +1666,8 @@ public function testFindBasicChecks(): void $this->assertIsFloat($documents[0]->getAttribute('price')); $this->assertEquals(true, $documents[0]->getAttribute('active')); $this->assertIsBool($documents[0]->getAttribute('active')); - $this->assertEquals(['animation', 'kids'], $documents[0]->getAttribute('generes')); - $this->assertIsArray($documents[0]->getAttribute('generes')); + $this->assertEquals(['animation', 'kids'], $documents[0]->getAttribute('genres')); + $this->assertIsArray($documents[0]->getAttribute('genres')); $this->assertEquals('Works', $documents[0]->getAttribute('with-dash')); // Alphabetical order @@ -1836,7 +1837,7 @@ public function testFindContains(): void } $documents = static::getDatabase()->find('movies', [ - Query::contains('generes', ['comics']) + Query::contains('genres', ['comics']) ]); $this->assertEquals(2, count($documents)); @@ -1845,7 +1846,7 @@ public function testFindContains(): void * Array contains OR condition */ $documents = static::getDatabase()->find('movies', [ - Query::contains('generes', ['comics', 'kids']), + Query::contains('genres', ['comics', 'kids']), ]); $this->assertEquals(4, count($documents)); @@ -3786,7 +3787,7 @@ public function testUniqueIndexDuplicate(): void 'year' => 2013, 'price' => 39.50, 'active' => true, - 'generes' => ['animation', 'kids'], + 'genres' => ['animation', 'kids'], 'with-dash' => 'Works4' ])); } @@ -3818,7 +3819,7 @@ public function testUniqueIndexDuplicateUpdate(): void 'year' => 2013, 'price' => 39.50, 'active' => true, - 'generes' => ['animation', 'kids'], + 'genres' => ['animation', 'kids'], 'with-dash' => 'Works4' ])); diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index a7e497959..fc57bfea4 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -375,22 +375,6 @@ public function testParseV2(): void $this->assertEquals('"quoted":"colon"', $query->getValue()); } - /* - Tests for aliases if we enable them: - public function testAlias(): void { - $query = Query::parse('eq(1)'); - $this->assertEquals(Query::TYPE_EQUAL, $query->getMethod()); - $query = Query::parse('lt(1)'); - $this->assertEquals(Query::TYPE_LESSER, $query->getMethod()); - $query = Query::parse('lte(1)'); - $this->assertEquals(Query::TYPE_LESSEREQUAL, $query->getMethod()); - $query = Query::parse('gt(1)'); - $this->assertEquals(Query::TYPE_GREATER, $query->getMethod()); - $query = Query::parse('gte(1)'); - $this->assertEquals(Query::TYPE_GREATEREQUAL, $query->getMethod()); - } - */ - public function testParseComplex(): void { $queries = [ diff --git a/tests/Database/Validator/Query/FilterTest.php b/tests/Database/Validator/Query/FilterTest.php index 752dd97a3..1a948d6db 100644 --- a/tests/Database/Validator/Query/FilterTest.php +++ b/tests/Database/Validator/Query/FilterTest.php @@ -34,6 +34,7 @@ public function testSuccess(): void $this->assertTrue($this->validator->isValid(Query::isNull('attr'))); $this->assertTrue($this->validator->isValid(Query::startsWith('attr', 'super'))); $this->assertTrue($this->validator->isValid(Query::endsWith('attr', 'man'))); + $this->assertTrue($this->validator->isValid(Query::contains('attr', ['super']))); } public function testFailure(): void From fc6b19826686ceca9091220abf3ce9b0e34948a7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 9 Oct 2023 20:43:28 +1300 Subject: [PATCH 02/19] Remove dupe semicolon --- src/Database/Adapter/MariaDB.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 79521f50a..7f772d719 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1326,7 +1326,7 @@ protected function getSQLCondition(Query $query): string case Query::TYPE_CONTAINS: $conditions = []; foreach ($query->getValues() as $key => $value) { - $conditions[] = "JSON_CONTAINS(`table_main`.{$attribute}, :{$placeholder}_{$key})";; + $conditions[] = "JSON_CONTAINS(`table_main`.{$attribute}, :{$placeholder}_{$key})"; } $condition = implode(' OR ', $conditions); return empty($condition) ? '' : '(' . $condition . ')'; From e8295c1b94f306caa18630f985e90997fca07d12 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 9 Oct 2023 20:45:33 +1300 Subject: [PATCH 03/19] Disable SQLite array contains --- src/Database/Adapter/SQLite.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 932de4f3f..269134093 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -657,6 +657,11 @@ public function getSupportForSchemas(): bool return false; } + public function getSupportForQueryContains(): bool + { + return false; + } + /** * Is fulltext index supported? * From 2985269318f0e76dc12510f488c31f1b1b8a2f6d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Oct 2023 12:35:07 +1300 Subject: [PATCH 04/19] Disallow contains queries on non-array attributes --- src/Database/Database.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 8292bf6bf..39b0bb1ef 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -4108,6 +4108,16 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu foreach ($queries as $index => &$query) { switch ($query->getMethod()) { + case Query::TYPE_CONTAINS: + $attribute = $query->getAttribute(); + foreach ($collection->getAttribute('attributes', []) as $attr) { + $key = $attr->getAttribute('key', $attr->getAttribute('$id')); + $array = $attr->getAttribute('array', false); + if ($key === $attribute && !$array) { + throw new DatabaseException('Cannot query contains on attribute "' . $attribute . '" because it is not an array.'); + } + } + break; case Query::TYPE_SELECT: $values = $query->getValues(); foreach ($values as $valueIndex => $value) { From 15ba9b18808828535e1c0c3a03f77914af58e768 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 13 Oct 2023 12:40:41 +1300 Subject: [PATCH 05/19] Add test for non-array attribute --- tests/Database/Base.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index a595bc006..4786d050a 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -10,6 +10,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; @@ -1850,6 +1851,18 @@ public function testFindContains(): void ]); $this->assertEquals(4, count($documents)); + + $documents = static::getDatabase()->find('movies', [ + Query::contains('genres', ['non-existent']), + ]); + + $this->assertEquals(0, count($documents)); + + $this->expectException(DatabaseException::class); + + static::getDatabase()->find('movies', [ + Query::contains('name', ['Frozen']), + ]); } public function testFindFulltext(): void From 712200642ecbf51a0ee8750ed8aa548b27cab7c8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 20 Oct 2023 16:57:39 +1300 Subject: [PATCH 06/19] Commit benchmark results for comparisons --- .gitignore | 3 -- ...adb_myapp_642ba64fe5e9f_25_1680582832.json | 1 + ...adb_myapp_642baa6d33383_25_1680583630.json | 47 +++++++++++++++++ .../mariadb_testing_1000_1697771040.json | 52 +++++++++++++++++++ .../mariadb_testing_1000_1697773496.json | 47 +++++++++++++++++ .../mariadb_testing_1000_1697773900.json | 52 +++++++++++++++++++ .../mariadb_testing_1000_1697773977.json | 1 + 7 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 bin/view/results/mariadb_myapp_642ba64fe5e9f_25_1680582832.json create mode 100644 bin/view/results/mariadb_myapp_642baa6d33383_25_1680583630.json create mode 100644 bin/view/results/mariadb_testing_1000_1697771040.json create mode 100644 bin/view/results/mariadb_testing_1000_1697773496.json create mode 100644 bin/view/results/mariadb_testing_1000_1697773900.json create mode 100644 bin/view/results/mariadb_testing_1000_1697773977.json diff --git a/.gitignore b/.gitignore index 8f1497051..46daf3d31 100755 --- a/.gitignore +++ b/.gitignore @@ -5,12 +5,9 @@ mock.json data-tests.php loader.php .phpunit.result.cache -/bin/view/results/ .vscode .vscode/* database.sql - -## - Oh Wess! Makefile .envrc .vscode diff --git a/bin/view/results/mariadb_myapp_642ba64fe5e9f_25_1680582832.json b/bin/view/results/mariadb_myapp_642ba64fe5e9f_25_1680582832.json new file mode 100644 index 000000000..a465c3af4 --- /dev/null +++ b/bin/view/results/mariadb_myapp_642ba64fe5e9f_25_1680582832.json @@ -0,0 +1 @@ +[{"roles":2,"results":[0.004142045974731445,0.0007698535919189453,0.0006570816040039062,4.3037660121917725]},{"roles":102,"results":[0.0031499862670898438,0.0012857913970947266,0.0013210773468017578,4.130218029022217]},{"roles":491,"results":[0.4965331554412842,0.36292195320129395,0.30788612365722656,4.9501330852508545]},{"roles":964,"results":[0.44646310806274414,0.44009995460510254,0.37430691719055176,4.239892959594727]},{"roles":1829,"results":[0.6837189197540283,1.6691820621490479,1.3487520217895508,98.95817399024963]}] \ No newline at end of file diff --git a/bin/view/results/mariadb_myapp_642baa6d33383_25_1680583630.json b/bin/view/results/mariadb_myapp_642baa6d33383_25_1680583630.json new file mode 100644 index 000000000..6cbfa187b --- /dev/null +++ b/bin/view/results/mariadb_myapp_642baa6d33383_25_1680583630.json @@ -0,0 +1,47 @@ +[ + { + "roles": 2, + "results": [ + 0.004247903823852539, + 0.0007619857788085938, + 0.0008020401000976562, + 4.970219850540161 + ] + }, + { + "roles": 101, + "results": [ + 0.0033349990844726562, + 0.001294851303100586, + 0.001383066177368164, + 4.7076640129089355 + ] + }, + { + "roles": 485, + "results": [ + 0.4268150329589844, + 0.32375311851501465, + 0.35645008087158203, + 4.3803019523620605 + ] + }, + { + "roles": 946, + "results": [ + 0.4152810573577881, + 0.4178469181060791, + 0.4439430236816406, + 4.6542909145355225 + ] + }, + { + "roles": 1809, + "results": [ + 0.7865281105041504, + 1.7059669494628906, + 1.4522700309753418, + 98.20597195625305 + ] + } +] \ No newline at end of file diff --git a/bin/view/results/mariadb_testing_1000_1697771040.json b/bin/view/results/mariadb_testing_1000_1697771040.json new file mode 100644 index 000000000..d08e1ca68 --- /dev/null +++ b/bin/view/results/mariadb_testing_1000_1697771040.json @@ -0,0 +1,52 @@ +[ + { + "roles": 2, + "results": { + "greaterThan, equal[1], limit(1000)": 0.014531135559082031, + "equal[3], limit(1000)": 0.01854395866394043, + "greaterThan, limit(1000)": 0.03291916847229004, + "search, limit(1000)": 0.037921905517578125, + "contains[1], limit(1000)": 0.0019600391387939453 + } + }, + { + "roles": 102, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0018169879913330078, + "equal[3], limit(1000)": 0.012106895446777344, + "greaterThan, limit(1000)": 0.030903100967407227, + "search, limit(1000)": 0.03960585594177246, + "contains[1], limit(1000)": 0.0017769336700439453 + } + }, + { + "roles": 495, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0020799636840820312, + "equal[3], limit(1000)": 0.013564109802246094, + "greaterThan, limit(1000)": 0.032176971435546875, + "search, limit(1000)": 0.03503084182739258, + "contains[1], limit(1000)": 0.001474142074584961 + } + }, + { + "roles": 954, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0017261505126953125, + "equal[3], limit(1000)": 0.012243986129760742, + "greaterThan, limit(1000)": 0.03142595291137695, + "search, limit(1000)": 0.03658008575439453, + "contains[1], limit(1000)": 0.0016679763793945312 + } + }, + { + "roles": 1812, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0019099712371826172, + "equal[3], limit(1000)": 0.012614965438842773, + "greaterThan, limit(1000)": 0.030133962631225586, + "search, limit(1000)": 0.03749680519104004, + "contains[1], limit(1000)": 0.0017859935760498047 + } + } +] \ No newline at end of file diff --git a/bin/view/results/mariadb_testing_1000_1697773496.json b/bin/view/results/mariadb_testing_1000_1697773496.json new file mode 100644 index 000000000..6567e877a --- /dev/null +++ b/bin/view/results/mariadb_testing_1000_1697773496.json @@ -0,0 +1,47 @@ +[ + { + "roles": 2, + "results": { + "greaterThan, equal[1], limit(1000)": 0.017921924591064453, + "equal[3], limit(1000)": 0.018985986709594727, + "greaterThan, limit(1000)": 0.03374195098876953, + "contains[1], limit(1000)": 0.001703023910522461 + } + }, + { + "roles": 101, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0020508766174316406, + "equal[3], limit(1000)": 0.012971878051757812, + "greaterThan, limit(1000)": 0.032111167907714844, + "contains[1], limit(1000)": 0.0015919208526611328 + } + }, + { + "roles": 490, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0020859241485595703, + "equal[3], limit(1000)": 0.013467073440551758, + "greaterThan, limit(1000)": 0.032073974609375, + "contains[1], limit(1000)": 0.0016400814056396484 + } + }, + { + "roles": 946, + "results": { + "greaterThan, equal[1], limit(1000)": 0.002042055130004883, + "equal[3], limit(1000)": 0.013000011444091797, + "greaterThan, limit(1000)": 0.03235602378845215, + "contains[1], limit(1000)": 0.0015759468078613281 + } + }, + { + "roles": 1814, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0020787715911865234, + "equal[3], limit(1000)": 0.01301884651184082, + "greaterThan, limit(1000)": 0.030966997146606445, + "contains[1], limit(1000)": 0.001605987548828125 + } + } +] \ No newline at end of file diff --git a/bin/view/results/mariadb_testing_1000_1697773900.json b/bin/view/results/mariadb_testing_1000_1697773900.json new file mode 100644 index 000000000..8ea5ed6af --- /dev/null +++ b/bin/view/results/mariadb_testing_1000_1697773900.json @@ -0,0 +1,52 @@ +[ + { + "roles": 2, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0120849609375, + "equal[3], limit(1000)": 0.015228033065795898, + "greaterThan, limit(1000)": 0.03383207321166992, + "search, limit(1000)": 0.040261030197143555, + "contains[1], limit(1000)": 0.0017671585083007812 + } + }, + { + "roles": 101, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0018150806427001953, + "equal[3], limit(1000)": 0.01302790641784668, + "greaterThan, limit(1000)": 0.03197622299194336, + "search, limit(1000)": 0.03850388526916504, + "contains[1], limit(1000)": 0.0015590190887451172 + } + }, + { + "roles": 491, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0018579959869384766, + "equal[3], limit(1000)": 0.013176202774047852, + "greaterThan, limit(1000)": 0.03150510787963867, + "search, limit(1000)": 0.03767108917236328, + "contains[1], limit(1000)": 0.0016279220581054688 + } + }, + { + "roles": 952, + "results": { + "greaterThan, equal[1], limit(1000)": 0.001962900161743164, + "equal[3], limit(1000)": 0.013421058654785156, + "greaterThan, limit(1000)": 0.03141212463378906, + "search, limit(1000)": 0.03910017013549805, + "contains[1], limit(1000)": 0.0019600391387939453 + } + }, + { + "roles": 1825, + "results": { + "greaterThan, equal[1], limit(1000)": 0.0020799636840820312, + "equal[3], limit(1000)": 0.014293909072875977, + "greaterThan, limit(1000)": 0.0318300724029541, + "search, limit(1000)": 0.0378110408782959, + "contains[1], limit(1000)": 0.001756906509399414 + } + } +] \ No newline at end of file diff --git a/bin/view/results/mariadb_testing_1000_1697773977.json b/bin/view/results/mariadb_testing_1000_1697773977.json new file mode 100644 index 000000000..d19acc1a3 --- /dev/null +++ b/bin/view/results/mariadb_testing_1000_1697773977.json @@ -0,0 +1 @@ +[{"roles":2,"results":{"greaterThan, equal[1], limit(1000)":0.011281013488769531,"equal[3], limit(1000)":0.018298864364624023,"greaterThan, limit(1000)":0.03335213661193848,"search, limit(1000)":0.03667807579040527,"contains[1], limit(1000)":0.0016739368438720703}},{"roles":102,"results":{"greaterThan, equal[1], limit(1000)":0.001981973648071289,"equal[3], limit(1000)":0.012470006942749023,"greaterThan, limit(1000)":0.029846906661987305,"search, limit(1000)":0.036875009536743164,"contains[1], limit(1000)":0.001753091812133789}},{"roles":489,"results":{"greaterThan, equal[1], limit(1000)":0.0018579959869384766,"equal[3], limit(1000)":0.012539863586425781,"greaterThan, limit(1000)":0.03027510643005371,"search, limit(1000)":0.036364078521728516,"contains[1], limit(1000)":0.0017800331115722656}},{"roles":945,"results":{"greaterThan, equal[1], limit(1000)":0.0018270015716552734,"equal[3], limit(1000)":0.012778997421264648,"greaterThan, limit(1000)":0.0295259952545166,"search, limit(1000)":0.03641104698181152,"contains[1], limit(1000)":0.0015921592712402344}},{"roles":1811,"results":{"greaterThan, equal[1], limit(1000)":0.0018990039825439453,"equal[3], limit(1000)":0.012309074401855469,"greaterThan, limit(1000)":0.029526948928833008,"search, limit(1000)":0.03502988815307617,"contains[1], limit(1000)":0.0015959739685058594}}] \ No newline at end of file From f68bbef14036c4b90ba57c697706d5034439a89d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 20 Oct 2023 20:08:33 +1300 Subject: [PATCH 07/19] Add contains queries to benchmarks --- bin/cli.php | 3 +- bin/tasks/index.php | 20 +++++------ bin/tasks/load.php | 24 +++++++------ bin/tasks/query.php | 79 ++++++++++++++++++++--------------------- tests/Database/Base.php | 16 ++++----- 5 files changed, 73 insertions(+), 69 deletions(-) diff --git a/bin/cli.php b/bin/cli.php index bbb60df5a..d9932d0b2 100644 --- a/bin/cli.php +++ b/bin/cli.php @@ -1,10 +1,11 @@ task('index') ->desc('Index mock data for testing queries') - ->param('adapter', '', new Text(0), 'Database adapter', false) - ->param('name', '', new Text(0), 'Name of created database.', false) + ->param('adapter', '', new Text(0), 'Database adapter') + ->param('name', '', new Text(0), 'Name of created database.') ->action(function ($adapter, $name) { $namespace = '_ns'; $cache = new Cache(new NoCache()); @@ -74,36 +74,36 @@ $database->setDefaultDatabase($name); $database->setNamespace($namespace); - Console::info("For query: greaterThan(created, 2010-01-01 05:00:00)', 'equal(genre,travel)"); - + Console::info("greaterThan('created', ['2010-01-01 05:00:00']), equal('genre', ['travel'])"); $start = microtime(true); $database->createIndex('articles', 'createdGenre', Database::INDEX_KEY, ['created', 'genre'], [], [Database::ORDER_DESC, Database::ORDER_DESC]); $time = microtime(true) - $start; Console::success("{$time} seconds"); Console::info("equal('genre', ['fashion', 'finance', 'sports'])"); - $start = microtime(true); $database->createIndex('articles', 'genre', Database::INDEX_KEY, ['genre'], [], [Database::ORDER_ASC]); $time = microtime(true) - $start; Console::success("{$time} seconds"); - Console::info("greaterThan('views', 100000)"); - $start = microtime(true); $database->createIndex('articles', 'views', Database::INDEX_KEY, ['views'], [], [Database::ORDER_DESC]); $time = microtime(true) - $start; Console::success("{$time} seconds"); - Console::info("search('text', 'Alice')"); $start = microtime(true); $database->createIndex('articles', 'fulltextsearch', Database::INDEX_FULLTEXT, ['text']); $time = microtime(true) - $start; Console::success("{$time} seconds"); - }); + Console::info("contains('tags', ['tag1'])"); + $start = microtime(true); + $database->createIndex('articles', 'tags', Database::INDEX_KEY, ['tags']); + $time = microtime(true) - $start; + Console::success("{$time} seconds"); + }); $cli ->error() diff --git a/bin/tasks/load.php b/bin/tasks/load.php index 1a4804290..1827fdb58 100644 --- a/bin/tasks/load.php +++ b/bin/tasks/load.php @@ -27,7 +27,7 @@ /** * @Example - * docker-compose exec tests bin/load --adapter=mariadb --limit=1000 --name=testing + * docker compose exec tests bin/load --adapter=mariadb --limit=1000 --name=testing */ $cli @@ -44,6 +44,7 @@ Console::info("Filling {$adapter} with {$limit} records: {$name}"); Swoole\Runtime::enableCoroutine(); + switch ($adapter) { case 'mariadb': Co\run(function () use (&$start, $limit, $name, $namespace, $cache) { @@ -85,7 +86,7 @@ // A coroutine is assigned per 1000 documents for ($i=0; $i < $limit/1000; $i++) { - go(function () use ($pool, $faker, $name, $cache, $namespace) { + \go(function () use ($pool, $faker, $name, $cache, $namespace) { $pdo = $pool->get(); $database = new Database(new MariaDB($pdo), $cache); @@ -94,7 +95,7 @@ // Each coroutine loads 1000 documents for ($i=0; $i < 1000; $i++) { - addArticle($database, $faker); + createDocument($database, $faker); } // Reclaim resources @@ -146,7 +147,7 @@ // A coroutine is assigned per 1000 documents for ($i=0; $i < $limit/1000; $i++) { - go(function () use ($pool, $faker, $name, $cache, $namespace) { + \go(function () use ($pool, $faker, $name, $cache, $namespace) { $pdo = $pool->get(); $database = new Database(new MySQL($pdo), $cache); @@ -155,7 +156,7 @@ // Each coroutine loads 1000 documents for ($i=0; $i < 1000; $i++) { - addArticle($database, $faker); + createDocument($database, $faker); } // Reclaim resources @@ -197,7 +198,7 @@ // Each coroutine loads 1000 documents for ($i=0; $i < 1000; $i++) { - addArticle($database, $faker); + createDocument($database, $faker); } $database = null; @@ -233,25 +234,27 @@ function createSchema(Database $database): void $database->create(); Authorization::setRole(Role::any()->toString()); + + $database->createCollection('articles', permissions: [ Permission::create(Role::any()), Permission::read(Role::any()), ]); $database->createAttribute('articles', 'author', Database::VAR_STRING, 256, true); - $database->createAttribute('articles', 'created', Database::VAR_DATETIME, 0, true, null, false, false, null, [], ['datetime']); + $database->createAttribute('articles', 'created', Database::VAR_DATETIME, 0, true, filters: ['datetime']); $database->createAttribute('articles', 'text', Database::VAR_STRING, 5000, true); $database->createAttribute('articles', 'genre', Database::VAR_STRING, 256, true); $database->createAttribute('articles', 'views', Database::VAR_INTEGER, 0, true); + $database->createAttribute('articles', 'tags', Database::VAR_STRING, 0, true, array: true); $database->createIndex('articles', 'text', Database::INDEX_FULLTEXT, ['text']); } -function addArticle($database, Generator $faker): void +function createDocument($database, Generator $faker): void { $database->createDocument('articles', new Document([ // Five random users out of 10,000 get read access // Three random users out of 10,000 get mutate access - '$permissions' => [ Permission::read(Role::any()), Permission::read(Role::user($faker->randomNumber(9))), @@ -272,6 +275,7 @@ function addArticle($database, Generator $faker): void 'created' => \Utopia\Database\DateTime::format($faker->dateTime()), 'text' => $faker->realTextBetween(1000, 4000), 'genre' => $faker->randomElement(['fashion', 'food', 'travel', 'music', 'lifestyle', 'fitness', 'diy', 'sports', 'finance']), - 'views' => $faker->randomNumber(6) + 'views' => $faker->randomNumber(6), + 'tags' => $faker->randomElements(['short', 'quick', 'easy', 'medium', 'hard'], $faker->numberBetween(1, 5)), ])); } diff --git a/bin/tasks/query.php b/bin/tasks/query.php index ab7115563..e1ee5e27a 100644 --- a/bin/tasks/query.php +++ b/bin/tasks/query.php @@ -21,13 +21,13 @@ /** * @Example - * docker-compose exec tests bin/query --adapter=mariadb --limit=1000 --name=testing + * docker compose exec tests bin/query --adapter=mariadb --limit=1000 --name=testing */ $cli ->task('query') ->desc('Query mock data') - ->param('adapter', '', new Text(0), 'Database adapter', false) - ->param('name', '', new Text(0), 'Name of created database.', false) + ->param('adapter', '', new Text(0), 'Database adapter') + ->param('name', '', new Text(0), 'Name of created database.') ->param('limit', 25, new Numeric(), 'Limit on queried documents', true) ->action(function (string $adapter, string $name, int $limit) { $namespace = '_ns'; @@ -80,40 +80,39 @@ return; } - $faker = Factory::create(); $report = []; - $count = addRoles($faker, 1); + $count = setRoles($faker, 1); Console::info("\n{$count} roles:"); $report[] = [ 'roles' => $count, 'results' => runQueries($database, $limit) ]; - $count = addRoles($faker, 100); + $count = setRoles($faker, 100); Console::info("\n{$count} roles:"); $report[] = [ 'roles' => $count, 'results' => runQueries($database, $limit) ]; - $count = addRoles($faker, 400); + $count = setRoles($faker, 400); Console::info("\n{$count} roles:"); $report[] = [ 'roles' => $count, 'results' => runQueries($database, $limit) ]; - $count = addRoles($faker, 500); + $count = setRoles($faker, 500); Console::info("\n{$count} roles:"); $report[] = [ 'roles' => $count, 'results' => runQueries($database, $limit) ]; - $count = addRoles($faker, 1000); + $count = setRoles($faker, 1000); Console::info("\n{$count} roles:"); $report[] = [ 'roles' => $count, @@ -121,72 +120,72 @@ ]; if (!file_exists('bin/view/results')) { - mkdir('bin/view/results', 0777, true); + \mkdir('bin/view/results', 0777, true); } - $time = time(); - $f = fopen("bin/view/results/{$adapter}_{$name}_{$limit}_{$time}.json", 'w'); - fwrite($f, json_encode($report)); - fclose($f); + $time = \time(); + $results = \fopen("bin/view/results/{$adapter}_{$name}_{$limit}_{$time}.json", 'w'); + \fwrite($results, \json_encode($report)); + \fclose($results); }); - $cli -->error() -->inject('error') -->action(function (Exception $error) { - Console::error($error->getMessage()); -}); + ->error() + ->inject('error') + ->action(function (Exception $error) { + Console::error($error->getMessage()); + }); +function setRoles($faker, $count): int +{ + for ($i = 0; $i < $count; $i++) { + Authorization::setRole($faker->numerify('user####')); + } + return \count(Authorization::getRoles()); +} -function runQueries(Database $database, int $limit) +function runQueries(Database $database, int $limit): array { $results = []; - // Recent travel blogs - $results[] = runQuery([ + // Recent travel blogs + $results["Querying greater than, equal[1] and limit"] = runQuery([ Query::greaterThan('created', '2010-01-01 05:00:00'), Query::equal('genre', ['travel']), Query::limit($limit) ], $database); // Favorite genres - - $results[] = runQuery([ + $results["Querying equal[3] and limit"] = runQuery([ Query::equal('genre', ['fashion', 'finance', 'sports']), Query::limit($limit) ], $database); // Popular posts - - $results[] = runQuery([ + $results["Querying greaterThan, limit({$limit})"] = runQuery([ Query::greaterThan('views', 100000), Query::limit($limit) ], $database); // Fulltext search - - $results[] = runQuery([ + $results["Query search, limit({$limit})"] = runQuery([ Query::search('text', 'Alice'), Query::limit($limit) ], $database); - return $results; -} + // Tags contain query + $results["Querying contains[1], limit({$limit})"] = runQuery([ + Query::contains('tags', ['tag1']), + Query::limit($limit) + ], $database); -function addRoles($faker, $count) -{ - for ($i = 0; $i < $count; $i++) { - Authorization::setRole($faker->numerify('user####')); - } - return count(Authorization::getRoles()); + return $results; } function runQuery(array $query, Database $database) { - $info = array_map(function ($q) { - /** @var $q Query */ - return $q->getAttribute() . ' : ' . $q->getMethod() . ' : ' . implode(',', $q->getValues()); + $info = array_map(function (Query $q) { + return $q->getAttribute() . ': ' . $q->getMethod() . ' = ' . implode(',', $q->getValues()); }, $query); Console::log('Running query: [' . implode(', ', $info) . ']'); diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 4786d050a..bfb07fef4 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1852,17 +1852,17 @@ public function testFindContains(): void $this->assertEquals(4, count($documents)); - $documents = static::getDatabase()->find('movies', [ - Query::contains('genres', ['non-existent']), - ]); + $documents = static::getDatabase()->find('movies', [ + Query::contains('genres', ['non-existent']), + ]); - $this->assertEquals(0, count($documents)); + $this->assertEquals(0, count($documents)); - $this->expectException(DatabaseException::class); + $this->expectException(DatabaseException::class); - static::getDatabase()->find('movies', [ - Query::contains('name', ['Frozen']), - ]); + static::getDatabase()->find('movies', [ + Query::contains('name', ['Frozen']), + ]); } public function testFindFulltext(): void From 893da6097ab943dfba0c39223803668916343397 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 25 Dec 2023 10:41:11 +0200 Subject: [PATCH 08/19] lock file --- composer.lock | 106 +++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/composer.lock b/composer.lock index 3ab0753d4..db0f24431 100644 --- a/composer.lock +++ b/composer.lock @@ -268,16 +268,16 @@ }, { "name": "utopia-php/framework", - "version": "0.31.0", + "version": "0.31.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0" + "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/207f77378965fca9a9bc3783ea379d3549f86bc0", - "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", + "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", "shasum": "" }, "require": { @@ -307,9 +307,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.31.0" + "source": "https://github.com/utopia-php/framework/tree/0.31.1" }, - "time": "2023-08-30T16:10:04+00:00" + "time": "2023-12-08T18:47:29+00:00" }, { "name": "utopia-php/mongo", @@ -638,16 +638,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -688,9 +688,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "pcov/clobber", @@ -839,16 +839,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.35", + "version": "1.10.50", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e730e5facb75ffe09dfb229795e8c01a459f26c3" + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e730e5facb75ffe09dfb229795e8c01a459f26c3", - "reference": "e730e5facb75ffe09dfb229795e8c01a459f26c3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", "shasum": "" }, "require": { @@ -897,27 +897,27 @@ "type": "tidelift" } ], - "time": "2023-09-19T15:27:56+00:00" + "time": "2023-12-13T10:59:42+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -967,7 +967,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" }, "funding": [ { @@ -975,7 +975,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1220,16 +1220,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.13", + "version": "9.6.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", "shasum": "" }, "require": { @@ -1303,7 +1303,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" }, "funding": [ { @@ -1319,7 +1319,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T05:39:22+00:00" + "time": "2023-12-01T16:55:19+00:00" }, { "name": "psr/container", @@ -1663,20 +1663,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1708,7 +1708,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -1716,7 +1716,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -1990,20 +1990,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -2035,7 +2035,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -2043,7 +2043,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -2428,7 +2428,7 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -2475,7 +2475,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -2495,16 +2495,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -2533,7 +2533,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -2541,7 +2541,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "utopia-php/cli", @@ -2608,5 +2608,5 @@ "php": ">=8.0" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } From 96c6945eb987852dc95b27d39c75d3f6e2b62c82 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 25 Dec 2023 11:10:25 +0200 Subject: [PATCH 09/19] Query name changes --- tests/Database/QueryTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index fc57bfea4..71557171b 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -24,9 +24,9 @@ public function testCreate(): void $this->assertEquals('title', $query->getAttribute()); $this->assertEquals('Iron Man', $query->getValues()[0]); - $query = new Query(Query::TYPE_ORDERDESC, 'score'); + $query = new Query(Query::TYPE_ORDER_DESC, 'score'); - $this->assertEquals(Query::TYPE_ORDERDESC, $query->getMethod()); + $this->assertEquals(Query::TYPE_ORDER_DESC, $query->getMethod()); $this->assertEquals('score', $query->getAttribute()); $this->assertEquals([], $query->getValues()); @@ -56,7 +56,7 @@ public function testCreate(): void $query = Query::orderAsc('score'); - $this->assertEquals(Query::TYPE_ORDERASC, $query->getMethod()); + $this->assertEquals(Query::TYPE_ORDER_ASC, $query->getMethod()); $this->assertEquals('score', $query->getAttribute()); $this->assertEquals([], $query->getValues()); @@ -69,7 +69,7 @@ public function testCreate(): void $cursor = new Document(); $query = Query::cursorAfter($cursor); - $this->assertEquals(Query::TYPE_CURSORAFTER, $query->getMethod()); + $this->assertEquals(Query::TYPE_CURSOR_AFTER, $query->getMethod()); $this->assertEquals('', $query->getAttribute()); $this->assertEquals([$cursor], $query->getValues()); @@ -446,12 +446,12 @@ public function testisMethod(): void $this->assertTrue(Query::isMethod(Query::TYPE_GREATER_EQUAL)); $this->assertTrue(Query::isMethod(Query::TYPE_CONTAINS)); $this->assertTrue(Query::isMethod(QUERY::TYPE_SEARCH)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_ORDERASC)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_ORDERDESC)); + $this->assertTrue(Query::isMethod(QUERY::TYPE_ORDER_ASC)); + $this->assertTrue(Query::isMethod(QUERY::TYPE_ORDER_DESC)); $this->assertTrue(Query::isMethod(QUERY::TYPE_LIMIT)); $this->assertTrue(Query::isMethod(QUERY::TYPE_OFFSET)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_CURSORAFTER)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_CURSORBEFORE)); + $this->assertTrue(Query::isMethod(QUERY::TYPE_CURSOR_AFTER)); + $this->assertTrue(Query::isMethod(QUERY::TYPE_CURSOR_BEFORE)); $this->assertTrue(Query::isMethod(QUERY::TYPE_IS_NULL)); $this->assertTrue(Query::isMethod(QUERY::TYPE_IS_NOT_NULL)); $this->assertTrue(Query::isMethod(QUERY::TYPE_BETWEEN)); From 32d49fa908cf787f1ce89800c94813e9e6c8fb21 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 25 Dec 2023 14:19:56 +0200 Subject: [PATCH 10/19] Remove old parse tests --- tests/Database/QueryTest.php | 472 ----------------------------------- 1 file changed, 472 deletions(-) delete mode 100644 tests/Database/QueryTest.php diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php deleted file mode 100644 index 71557171b..000000000 --- a/tests/Database/QueryTest.php +++ /dev/null @@ -1,472 +0,0 @@ -assertEquals(Query::TYPE_EQUAL, $query->getMethod()); - $this->assertEquals('title', $query->getAttribute()); - $this->assertEquals('Iron Man', $query->getValues()[0]); - - $query = new Query(Query::TYPE_ORDER_DESC, 'score'); - - $this->assertEquals(Query::TYPE_ORDER_DESC, $query->getMethod()); - $this->assertEquals('score', $query->getAttribute()); - $this->assertEquals([], $query->getValues()); - - $query = new Query(Query::TYPE_LIMIT, values: [10]); - - $this->assertEquals(Query::TYPE_LIMIT, $query->getMethod()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals(10, $query->getValues()[0]); - - $query = Query::equal('title', ['Iron Man']); - - $this->assertEquals(Query::TYPE_EQUAL, $query->getMethod()); - $this->assertEquals('title', $query->getAttribute()); - $this->assertEquals('Iron Man', $query->getValues()[0]); - - $query = Query::greaterThan('score', 10); - - $this->assertEquals(Query::TYPE_GREATER, $query->getMethod()); - $this->assertEquals('score', $query->getAttribute()); - $this->assertEquals(10, $query->getValues()[0]); - - $query = Query::search('search', 'John Doe'); - - $this->assertEquals(Query::TYPE_SEARCH, $query->getMethod()); - $this->assertEquals('search', $query->getAttribute()); - $this->assertEquals('John Doe', $query->getValues()[0]); - - $query = Query::orderAsc('score'); - - $this->assertEquals(Query::TYPE_ORDER_ASC, $query->getMethod()); - $this->assertEquals('score', $query->getAttribute()); - $this->assertEquals([], $query->getValues()); - - $query = Query::limit(10); - - $this->assertEquals(Query::TYPE_LIMIT, $query->getMethod()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals([10], $query->getValues()); - - $cursor = new Document(); - $query = Query::cursorAfter($cursor); - - $this->assertEquals(Query::TYPE_CURSOR_AFTER, $query->getMethod()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals([$cursor], $query->getValues()); - - $query = Query::isNull('title'); - - $this->assertEquals(Query::TYPE_IS_NULL, $query->getMethod()); - $this->assertEquals('title', $query->getAttribute()); - $this->assertEquals([], $query->getValues()); - - $query = Query::isNotNull('title'); - - $this->assertEquals(Query::TYPE_IS_NOT_NULL, $query->getMethod()); - $this->assertEquals('title', $query->getAttribute()); - $this->assertEquals([], $query->getValues()); - } - - public function testParse(): void - { - $query = Query::parse('equal("title", "Iron Man")'); - - $this->assertEquals('equal', $query->getMethod()); - $this->assertEquals('title', $query->getAttribute()); - $this->assertEquals('Iron Man', $query->getValues()[0]); - - $query = Query::parse('lessThan("year", 2001)'); - - $this->assertEquals('lessThan', $query->getMethod()); - $this->assertEquals('year', $query->getAttribute()); - $this->assertEquals(2001, $query->getValues()[0]); - - $query = Query::parse('equal("published", true)'); - - $this->assertEquals('equal', $query->getMethod()); - $this->assertEquals('published', $query->getAttribute()); - $this->assertTrue($query->getValues()[0]); - - $query = Query::parse('equal("published", false)'); - - $this->assertEquals('equal', $query->getMethod()); - $this->assertEquals('published', $query->getAttribute()); - $this->assertFalse($query->getValues()[0]); - - $query = Query::parse('equal("actors", [ " Johnny Depp ", " Brad Pitt" , \'Al Pacino \' ])'); - - $this->assertEquals('equal', $query->getMethod()); - $this->assertEquals('actors', $query->getAttribute()); - $this->assertEquals(" Johnny Depp ", $query->getValues()[0]); - $this->assertEquals(" Brad Pitt", $query->getValues()[1]); - $this->assertEquals("Al Pacino ", $query->getValues()[2]); - - $query = Query::parse('equal("actors", ["Brad Pitt", "Johnny Depp"])'); - - $this->assertEquals('equal', $query->getMethod()); - $this->assertEquals('actors', $query->getAttribute()); - $this->assertEquals("Brad Pitt", $query->getValues()[0]); - $this->assertEquals("Johnny Depp", $query->getValues()[1]); - - $query = Query::parse('contains("writers","Tim O\'Reilly")'); - - $this->assertEquals('contains', $query->getMethod()); - $this->assertEquals('writers', $query->getAttribute()); - $this->assertEquals("Tim O'Reilly", $query->getValues()[0]); - - $query = Query::parse('greaterThan("score", 8.5)'); - - $this->assertEquals('greaterThan', $query->getMethod()); - $this->assertEquals('score', $query->getAttribute()); - $this->assertEquals(8.5, $query->getValues()[0]); - - $query = Query::parse('notEqual("director", "null")'); - - $this->assertEquals('notEqual', $query->getMethod()); - $this->assertEquals('director', $query->getAttribute()); - $this->assertEquals('null', $query->getValues()[0]); - - $query = Query::parse('notEqual("director", null)'); - - $this->assertEquals('notEqual', $query->getMethod()); - $this->assertEquals('director', $query->getAttribute()); - $this->assertEquals(null, $query->getValues()[0]); - - $query = Query::parse('isNull("director")'); - - $this->assertEquals('isNull', $query->getMethod()); - $this->assertEquals('director', $query->getAttribute()); - $this->assertEquals([], $query->getValues()); - - $query = Query::parse('isNotNull("director")'); - - $this->assertEquals('isNotNull', $query->getMethod()); - $this->assertEquals('director', $query->getAttribute()); - $this->assertEquals([], $query->getValues()); - - $query = Query::parse('startsWith("director", "Quentin")'); - - $this->assertEquals('startsWith', $query->getMethod()); - $this->assertEquals('director', $query->getAttribute()); - $this->assertEquals(['Quentin'], $query->getValues()); - - $query = Query::parse('endsWith("director", "Tarantino")'); - - $this->assertEquals('endsWith', $query->getMethod()); - $this->assertEquals('director', $query->getAttribute()); - $this->assertEquals(['Tarantino'], $query->getValues()); - - $query = Query::parse('select(["title", "director"])'); - - $this->assertEquals('select', $query->getMethod()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals('title', $query->getValues()[0]); - $this->assertEquals('director', $query->getValues()[1]); - - $query = Query::parse('between("age", 15, 18)'); - - $this->assertEquals('between', $query->getMethod()); - $this->assertEquals('age', $query->getAttribute()); - $this->assertEquals(15, $query->getValues()[0]); - $this->assertEquals(18, $query->getValues()[1]); - - $query = Query::parse('between("lastUpdate", "DATE1", "DATE2")'); - - $this->assertEquals('between', $query->getMethod()); - $this->assertEquals('lastUpdate', $query->getAttribute()); - $this->assertEquals('DATE1', $query->getValues()[0]); - $this->assertEquals('DATE2', $query->getValues()[1]); - } - - public function testParseV2(): void - { - $query = Query::parse('equal("attr", 1)'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("attr", $query->getAttribute()); - $this->assertEquals([1], $query->getValues()); - - $query = Query::parse('equal("attr", [0])'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("attr", $query->getAttribute()); - $this->assertEquals([0], $query->getValues()); - - $query = Query::parse('equal("attr", 0,)'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("attr", $query->getAttribute()); - $this->assertEquals([0], $query->getValues()); - - $query = Query::parse('equal("attr", ["0"])'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("attr", $query->getAttribute()); - $this->assertEquals(["0"], $query->getValues()); - - $query = Query::parse('equal(1, ["[Hello] World"])'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("[Hello] World", $query->getValues()[0]); - - $query = Query::parse('equal(1, , , ["[Hello] World"], , , )'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("[Hello] World", $query->getValues()[0]); - - $query = Query::parse('equal(1, ["(Hello) World"])'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("(Hello) World", $query->getValues()[0]); - - $query = Query::parse('equal(1, ["Hello , World"])'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("Hello , World", $query->getValues()[0]); - - $query = Query::parse('equal(1, ["Hello , World"])'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("Hello , World", $query->getValues()[0]); - - $query = Query::parse('equal(1, ["Hello /\ World"])'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("Hello /\ World", $query->getValues()[0]); - - $query = Query::parse('equal(1, ["I\'m [**awesome**], \"Dev\"eloper"])'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("I'm [**awesome**], \"Dev\"eloper", $query->getValues()[0]); - - $query = Query::parse('equal(1, "\\\\")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("\\\\", $query->getValues()[0]); - - $query = Query::parse('equal(1, "Hello\\\\")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("Hello\\\\", $query->getValues()[0]); - - $query = Query::parse('equal(1, "Hello\\\\", "World")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("Hello\\\\", $query->getValues()[0]); - - $query = Query::parse('equal(1, "Hello\\", World")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("Hello\", World", $query->getValues()[0]); - - $query = Query::parse('equal(1, "Hello\\\\\\", ", "World")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals(1, $query->getAttribute()); - $this->assertEquals("Hello\\\\\", ", $query->getValues()[0]); - - $query = Query::parse('equal()'); - $this->assertCount(0, $query->getValues()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals(null, $query->getValue()); - - $query = Query::parse('limit()'); - $this->assertCount(0, $query->getValues()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals(null, $query->getValue()); - - $query = Query::parse('offset()'); - $this->assertCount(0, $query->getValues()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals(null, $query->getValue()); - - $query = Query::parse('cursorAfter()'); - $this->assertCount(0, $query->getValues()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals(null, $query->getValue()); - - $query = Query::parse('orderDesc()'); - $this->assertCount(0, $query->getValues()); - $this->assertEquals('', $query->getAttribute()); - $this->assertEquals(null, $query->getValue()); - - $query = Query::parse('equal("count", 0)'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("count", $query->getAttribute()); - $this->assertEquals(0, $query->getValue()); - - $query = Query::parse('equal("value", "NormalString")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals("NormalString", $query->getValue()); - - $query = Query::parse('equal("value", "{"type":"json","somekey":"someval"}")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('{"type":"json","somekey":"someval"}', $query->getValue()); - - $query = Query::parse('equal("value", "{ NormalStringInBraces }")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('{ NormalStringInBraces }', $query->getValue()); - - $query = Query::parse('equal("value", ""NormalStringInDoubleQuotes"")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('"NormalStringInDoubleQuotes"', $query->getValue()); - - $query = Query::parse('equal("value", "{"NormalStringInDoubleQuotesAndBraces"}")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('{"NormalStringInDoubleQuotesAndBraces"}', $query->getValue()); - - $query = Query::parse('equal("value", "\'NormalStringInSingleQuotes\'")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('\'NormalStringInSingleQuotes\'', $query->getValue()); - - $query = Query::parse('equal("value", "{\'NormalStringInSingleQuotesAndBraces\'}")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('{\'NormalStringInSingleQuotesAndBraces\'}', $query->getValue()); - - $query = Query::parse('equal("value", "SingleQuote\'InMiddle")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('SingleQuote\'InMiddle', $query->getValue()); - - $query = Query::parse('equal("value", "DoubleQuote"InMiddle")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('DoubleQuote"InMiddle', $query->getValue()); - - $query = Query::parse('equal("value", "Slash/InMiddle")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('Slash/InMiddle', $query->getValue()); - - $query = Query::parse('equal("value", "Backslash\InMiddle")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('Backslash\InMiddle', $query->getValue()); - - $query = Query::parse('equal("value", "Colon:InMiddle")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('Colon:InMiddle', $query->getValue()); - - $query = Query::parse('equal("value", ""quoted":"colon"")'); - $this->assertCount(1, $query->getValues()); - $this->assertEquals("value", $query->getAttribute()); - $this->assertEquals('"quoted":"colon"', $query->getValue()); - } - - public function testParseComplex(): void - { - $queries = [ - Query::parse('equal("One",[55.55,\'Works\',true])'), - // Same query with random spaces - Query::parse('equal("One" , [55.55, \'Works\',true])') - ]; - - foreach ($queries as $query) { - $this->assertEquals('equal', $query->getMethod()); - - $this->assertIsString($query->getAttribute()); - $this->assertEquals('One', $query->getAttribute()); - - $this->assertCount(3, $query->getValues()); - - $this->assertIsNumeric($query->getValues()[0]); - $this->assertEquals(55.55, $query->getValues()[0]); - - $this->assertEquals('Works', $query->getValues()[1]); - - $this->assertTrue($query->getValues()[2]); - } - } - - public function testGetAttribute(): void - { - $query = Query::parse('equal("title", "Iron Man")'); - - $this->assertIsArray($query->getValues()); - $this->assertCount(1, $query->getValues()); - $this->assertEquals('title', $query->getAttribute()); - $this->assertEquals('Iron Man', $query->getValues()[0]); - } - - public function testGetMethod(): void - { - $query = Query::parse('equal("title", "Iron Man")'); - - $this->assertEquals('equal', $query->getMethod()); - } - - public function testisMethod(): void - { - $this->assertTrue(Query::isMethod('equal')); - $this->assertTrue(Query::isMethod('notEqual')); - $this->assertTrue(Query::isMethod('lessThan')); - $this->assertTrue(Query::isMethod('lessThanEqual')); - $this->assertTrue(Query::isMethod('greaterThan')); - $this->assertTrue(Query::isMethod('greaterThanEqual')); - $this->assertTrue(Query::isMethod('contains')); - $this->assertTrue(Query::isMethod('search')); - $this->assertTrue(Query::isMethod('orderDesc')); - $this->assertTrue(Query::isMethod('orderAsc')); - $this->assertTrue(Query::isMethod('limit')); - $this->assertTrue(Query::isMethod('offset')); - $this->assertTrue(Query::isMethod('cursorAfter')); - $this->assertTrue(Query::isMethod('cursorBefore')); - $this->assertTrue(Query::isMethod('isNull')); - $this->assertTrue(Query::isMethod('isNotNull')); - $this->assertTrue(Query::isMethod('between')); - $this->assertTrue(Query::isMethod('select')); - - $this->assertTrue(Query::isMethod(Query::TYPE_EQUAL)); - $this->assertTrue(Query::isMethod(Query::TYPE_NOT_EQUAL)); - $this->assertTrue(Query::isMethod(Query::TYPE_LESSER)); - $this->assertTrue(Query::isMethod(Query::TYPE_LESSER_EQUAL)); - $this->assertTrue(Query::isMethod(Query::TYPE_GREATER)); - $this->assertTrue(Query::isMethod(Query::TYPE_GREATER_EQUAL)); - $this->assertTrue(Query::isMethod(Query::TYPE_CONTAINS)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_SEARCH)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_ORDER_ASC)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_ORDER_DESC)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_LIMIT)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_OFFSET)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_CURSOR_AFTER)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_CURSOR_BEFORE)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_IS_NULL)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_IS_NOT_NULL)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_BETWEEN)); - $this->assertTrue(Query::isMethod(QUERY::TYPE_SELECT)); - - /* - Tests for aliases if we enable them: - $this->assertTrue(Query::isMethod('lt')); - $this->assertTrue(Query::isMethod('lte')); - $this->assertTrue(Query::isMethod('gt')); - $this->assertTrue(Query::isMethod('gte')); - $this->assertTrue(Query::isMethod('eq')); - */ - - $this->assertFalse(Query::isMethod('invalid')); - $this->assertFalse(Query::isMethod('lte ')); - } -} From 4149b7a480da73eda9b9d86323013bad84cf9ad2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 26 Dec 2023 11:05:43 +0200 Subject: [PATCH 11/19] array changes and new test --- bin/tasks/index.php | 10 +- bin/tasks/load.php | 10 +- phpunit.xml | 2 +- src/Database/Adapter/MariaDB.php | 28 +++--- src/Database/Adapter/Postgres.php | 28 +++--- src/Database/Adapter/SQLite.php | 8 +- src/Database/Database.php | 26 ++++-- src/Database/Validator/Query/Filter.php | 12 ++- tests/e2e/Adapter/Base.php | 108 +++++++++++++++++++++- tests/unit/Validator/Query/FilterTest.php | 9 +- 10 files changed, 179 insertions(+), 62 deletions(-) diff --git a/bin/tasks/index.php b/bin/tasks/index.php index ef1e45089..d84e36d93 100644 --- a/bin/tasks/index.php +++ b/bin/tasks/index.php @@ -98,11 +98,11 @@ $time = microtime(true) - $start; Console::success("{$time} seconds"); - Console::info("contains('tags', ['tag1'])"); - $start = microtime(true); - $database->createIndex('articles', 'tags', Database::INDEX_KEY, ['tags']); - $time = microtime(true) - $start; - Console::success("{$time} seconds"); + Console::info("contains('tags', ['tag1'])"); + $start = microtime(true); + $database->createIndex('articles', 'tags', Database::INDEX_KEY, ['tags']); + $time = microtime(true) - $start; + Console::success("{$time} seconds"); }); $cli diff --git a/bin/tasks/load.php b/bin/tasks/load.php index d5decdbd7..5c409da7a 100644 --- a/bin/tasks/load.php +++ b/bin/tasks/load.php @@ -85,7 +85,7 @@ ); // A coroutine is assigned per 1000 documents - for ($i=0; $i < $limit/1000; $i++) { + for ($i = 0; $i < $limit / 1000; $i++) { \go(function () use ($pool, $faker, $name, $cache, $namespace) { $pdo = $pool->get(); @@ -94,7 +94,7 @@ $database->setNamespace($namespace); // Each coroutine loads 1000 documents - for ($i=0; $i < 1000; $i++) { + for ($i = 0; $i < 1000; $i++) { createDocument($database, $faker); } @@ -146,7 +146,7 @@ ); // A coroutine is assigned per 1000 documents - for ($i=0; $i < $limit/1000; $i++) { + for ($i = 0; $i < $limit / 1000; $i++) { \go(function () use ($pool, $faker, $name, $cache, $namespace) { $pdo = $pool->get(); @@ -155,7 +155,7 @@ $database->setNamespace($namespace); // Each coroutine loads 1000 documents - for ($i=0; $i < 1000; $i++) { + for ($i = 0; $i < 1000; $i++) { createDocument($database, $faker); } @@ -197,7 +197,7 @@ $database->setNamespace($namespace); // Each coroutine loads 1000 documents - for ($i=0; $i < 1000; $i++) { + for ($i = 0; $i < 1000; $i++) { createDocument($database, $faker); } diff --git a/phpunit.xml b/phpunit.xml index ccdaa969e..783265d80 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/unit diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 7f60a9cae..b71a64ac0 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -83,11 +83,13 @@ public function createCollection(string $name, array $attributes = [], array $in foreach ($attributes as $key => $attribute) { $attrId = $this->filter($attribute->getId()); - $attrType = $this->getSQLType($attribute->getAttribute('type'), $attribute->getAttribute('size', 0), $attribute->getAttribute('signed', true)); - if ($attribute->getAttribute('array')) { - $attrType = 'LONGTEXT'; - } + $attrType = $this->getSQLType( + $attribute->getAttribute('type'), + $attribute->getAttribute('size', 0), + $attribute->getAttribute('signed', true), + $attribute->getAttribute('array', false) + ); $attributeStrings[$key] = "`{$attrId}` {$attrType}, "; } @@ -268,7 +270,7 @@ public function createAttribute(string $collection, string $id, string $type, in { $name = $this->filter($collection); $id = $this->filter($id); - $type = $this->getSQLType($type, $size, $signed); + $type = $this->getSQLType($type, $size, $signed, $array); if ($array) { $type = 'JSON'; @@ -299,11 +301,7 @@ public function updateAttribute(string $collection, string $id, string $type, in { $name = $this->filter($collection); $id = $this->filter($id); - $type = $this->getSQLType($type, $size, $signed); - - if ($array) { - $type = 'JSON'; - } + $type = $this->getSQLType($type, $size, $signed, $array); $sql = "ALTER TABLE {$this->getSQLTable($name)} MODIFY `{$id}` {$type};"; @@ -1609,7 +1607,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, "; $sql = $this->trigger(Database::EVENT_DOCUMENT_FIND, $sql); - $stmt = $this->getPDO()->prepare($sql); foreach ($queries as $query) { @@ -1943,11 +1940,16 @@ protected function getSQLCondition(Query $query): string * @param string $type * @param int $size * @param bool $signed + * @param bool $array * @return string - * @throws Exception + * @throws DatabaseException */ - protected function getSQLType(string $type, int $size, bool $signed = true): string + protected function getSQLType(string $type, int $size, bool $signed = true, bool $array = false): string { + if($array === true){ + return 'JSON'; + } + switch ($type) { case Database::VAR_STRING: // $size = $size * 4; // Convert utf8mb4 size to bytes diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index f007f4b5a..18cc8acb7 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -86,11 +86,13 @@ public function createCollection(string $name, array $attributes = [], array $in foreach ($attributes as &$attribute) { $attrId = $this->filter($attribute->getId()); - $attrType = $this->getSQLType($attribute->getAttribute('type'), $attribute->getAttribute('size', 0), $attribute->getAttribute('signed', true)); - if ($attribute->getAttribute('array')) { - $attrType = 'TEXT'; - } + $attrType = $this->getSQLType( + $attribute->getAttribute('type'), + $attribute->getAttribute('size', 0), + $attribute->getAttribute('signed', true), + $attribute->getAttribute('array', false) + ); $attribute = "\"{$attrId}\" {$attrType}, "; } @@ -261,11 +263,7 @@ public function createAttribute(string $collection, string $id, string $type, in { $name = $this->filter($collection); $id = $this->filter($id); - $type = $this->getSQLType($type, $size, $signed); - - if ($array) { - $type = 'JSONB'; - } + $type = $this->getSQLType($type, $size, $signed, $array); $sql = " ALTER TABLE {$this->getSQLTable($name)} @@ -350,11 +348,7 @@ public function updateAttribute(string $collection, string $id, string $type, in { $name = $this->filter($collection); $id = $this->filter($id); - $type = $this->getSQLType($type, $size, $signed); - - if ($array) { - $type = 'JSONB'; - } + $type = $this->getSQLType($type, $size, $signed, $array); if ($type == 'TIMESTAMP(3)') { $type = "TIMESTAMP(3) without time zone USING TO_TIMESTAMP(\"$id\", 'YYYY-MM-DD HH24:MI:SS.MS')"; @@ -1937,8 +1931,12 @@ protected function getFulltextValue(string $value): string * * @return string */ - protected function getSQLType(string $type, int $size, bool $signed = true): string + protected function getSQLType(string $type, int $size, bool $signed = true, bool $array = false): string { + if($array === true){ + return 'JSONB'; + } + switch ($type) { case Database::VAR_STRING: // $size = $size * 4; // Convert utf8mb4 size to bytes diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 703f06690..b8b9b9085 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -106,7 +106,6 @@ public function delete(string $name): bool */ public function createCollection(string $name, array $attributes = [], array $indexes = []): bool { - $namespace = $this->getNamespace(); $id = $this->filter($name); try { @@ -124,13 +123,10 @@ public function createCollection(string $name, array $attributes = [], array $in $attrType = $this->getSQLType( $attribute->getAttribute('type'), $attribute->getAttribute('size', 0), - $attribute->getAttribute('signed', true) + $attribute->getAttribute('signed', true), + $attribute->getAttribute('array', false) ); - if ($attribute->getAttribute('array')) { - $attrType = 'LONGTEXT'; - } - $attributeStrings[$key] = "`{$attrId}` {$attrType}, "; } diff --git a/src/Database/Database.php b/src/Database/Database.php index bca9d818a..dfc660e1a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -1175,6 +1175,9 @@ public function createAttribute(string $collection, string $id, string $type, in if ($size > $this->adapter->getLimitForString()) { throw new DatabaseException('Max size allowed for string is: ' . number_format($this->adapter->getLimitForString())); } + else if ($size < 1) { + throw new DatabaseException('Min size is 1'); + } break; case self::VAR_INTEGER: @@ -4559,6 +4562,8 @@ public function find(string $collection, array $queries = []): array $cursor = empty($cursor) ? [] : $this->encode($collection, $cursor)->getArrayCopy(); + /** @var array $queries */ + $queries = \array_merge( $selects, self::convertQueries($collection, $filters) @@ -4569,16 +4574,17 @@ public function find(string $collection, array $queries = []): array foreach ($queries as $index => &$query) { switch ($query->getMethod()) { - case Query::TYPE_CONTAINS: - $attribute = $query->getAttribute(); - foreach ($collection->getAttribute('attributes', []) as $attr) { - $key = $attr->getAttribute('key', $attr->getAttribute('$id')); - $array = $attr->getAttribute('array', false); - if ($key === $attribute && !$array) { - throw new DatabaseException('Cannot query contains on attribute "' . $attribute . '" because it is not an array.'); - } - } - break; + // todo: moved to validator.... + // case Query::TYPE_CONTAINS: + // $attribute = $query->getAttribute(); + // foreach ($collection->getAttribute('attributes', []) as $attr) { + // $key = $attr->getAttribute('key', $attr->getAttribute('$id')); + // $array = $attr->getAttribute('array', false); + // if ($key === $attribute && !$array) { + // throw new DatabaseException('Cannot query contains on attribute "' . $attribute . '" because it is not an array.'); + // } + // } + // break; case Query::TYPE_SELECT: $values = $query->getValues(); foreach ($values as $valueIndex => $value) { diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 7b576136c..f5b798de2 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -143,8 +143,18 @@ public function isValid($value): bool return false; } - return $this->isValidAttributeAndValues($attribute, $value->getValues()); + if(!$this->isValidAttributeAndValues($attribute, $value->getValues())) { + return false; + } + if(Query::TYPE_CONTAINS === $method) { + if($this->schema[$attribute]['array'] === false) { + $this->message = 'Cannot query contains on attribute "' . $attribute . '" because it is not an array.'; + return false; + } + } + + return true; case Query::TYPE_NOT_EQUAL: case Query::TYPE_LESSER: case Query::TYPE_LESSER_EQUAL: diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 195aefe0d..d31fb429d 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1591,6 +1591,100 @@ public function testDeleteDocument(Document $document): void } + /** + * @throws AuthorizationException + * @throws DuplicateException + * @throws ConflictException + * @throws LimitException + * @throws StructureException + * @throws DatabaseException + */ + public function testArrayAttribute(): array + { + Authorization::setRole(Role::any()->toString()); + + $collection = 'json'; + $permissions = [Permission::read(Role::any())]; + + static::getDatabase()->createCollection($collection, permissions: [ + Permission::create(Role::any()), + ]); + + $this->assertEquals(true, static::getDatabase()->createAttribute( + $collection, + 'age', + Database::VAR_INTEGER, + size: 0, + required: true, + )); + + $this->assertEquals(true, static::getDatabase()->createAttribute( + $collection, + 'active', + Database::VAR_BOOLEAN, + size: 0, + required: true, + array: true + )); + + $this->assertEquals(true, static::getDatabase()->createAttribute( + $collection, + 'names', + Database::VAR_STRING, + size: 50, + required: false, + array: true + )); + + $this->assertEquals(true, static::getDatabase()->createAttribute( + $collection, + 'numbers', + Database::VAR_INTEGER, + size: 0, + required: false, + signed: false, + array: true + )); + + try { + static::getDatabase()->createDocument($collection, new Document([])); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Invalid document structure: Missing required attribute "active"', $e->getMessage()); + } + + try { + static::getDatabase()->createDocument($collection, new Document([ + 'active' => [false], + 'names' => ['Joe', 100], + ])); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Invalid document structure: Attribute "names[\'1\']" has invalid type. Value must be a valid string and no longer than 50 chars', $e->getMessage()); + } + + try { + static::getDatabase()->createDocument($collection, new Document([ + 'active' => [false], + 'numbers' => [-100], + 'age' => [-20], + ])); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Should fails since it is Signed is false!', $e->getMessage()); + } + + $document = static::getDatabase()->createDocument($collection, new Document([ + '$permissions' => $permissions, + 'names' => ['Joe Baden', 'Antony Blinken', '100'], + 'numbers' => [0, -100, 999, 10.4], + ])); + + var_dump($document); + $this->assertEquals(0,1); + + } + /** * @return array */ @@ -1971,11 +2065,15 @@ public function testFindContains(): void $this->assertEquals(0, count($documents)); - $this->expectException(DatabaseException::class); - - static::getDatabase()->find('movies', [ - Query::contains('name', ['Frozen']), - ]); + try { + static::getDatabase()->find('movies', [ + Query::contains('name', ['Frozen']), + ]); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Invalid query: Cannot query contains on attribute "name" because it is not an array.', $e->getMessage()); + $this->assertTrue($e instanceof DatabaseException); + } } public function testFindFulltext(): void diff --git a/tests/unit/Validator/Query/FilterTest.php b/tests/unit/Validator/Query/FilterTest.php index 712dd9f49..c375033fd 100644 --- a/tests/unit/Validator/Query/FilterTest.php +++ b/tests/unit/Validator/Query/FilterTest.php @@ -23,6 +23,12 @@ public function setUp(): void 'type' => Database::VAR_STRING, 'array' => false, ]), + new Document([ + '$id' => 'attr_array', + 'key' => 'attr_array', + 'type' => Database::VAR_STRING, + 'array' => true, + ]), ], ); } @@ -34,7 +40,7 @@ public function testSuccess(): void $this->assertTrue($this->validator->isValid(Query::isNull('attr'))); $this->assertTrue($this->validator->isValid(Query::startsWith('attr', 'super'))); $this->assertTrue($this->validator->isValid(Query::endsWith('attr', 'man'))); - $this->assertTrue($this->validator->isValid(Query::contains('attr', ['super']))); + $this->assertTrue($this->validator->isValid(Query::contains('attr_array', ['super']))); } public function testFailure(): void @@ -57,6 +63,7 @@ public function testFailure(): void $this->assertFalse($this->validator->isValid(Query::orderDesc('attr'))); $this->assertFalse($this->validator->isValid(new Query(Query::TYPE_CURSOR_AFTER, values: ['asdf']))); $this->assertFalse($this->validator->isValid(new Query(Query::TYPE_CURSOR_BEFORE, values: ['asdf']))); + $this->assertFalse($this->validator->isValid(Query::contains('attr', ['super']))); } public function testEmptyValues(): void From 26a16f6d24631caa417018f190bdceedd0f139a6 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 26 Dec 2023 17:11:06 +0200 Subject: [PATCH 12/19] getSupportForIndexArray --- src/Database/Adapter.php | 7 ++++ src/Database/Adapter/MariaDB.php | 1 + src/Database/Adapter/Mongo.php | 10 ++++++ src/Database/Adapter/SQL.php | 10 ++++++ src/Database/Database.php | 5 +++ tests/e2e/Adapter/Base.php | 58 +++++++++++++++++++++++--------- 6 files changed, 75 insertions(+), 16 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 764f41df6..d50d7d8df 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -606,6 +606,13 @@ abstract public function getSupportForSchemas(): bool; */ abstract public function getSupportForIndex(): bool; + /** + * Is index array supported? + * + * @return bool + */ + abstract public function getSupportForIndexArray(): bool; + /** * Is unique index supported? * diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index b71a64ac0..720f04641 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1606,6 +1606,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, {$sqlLimit}; "; + var_dump($sql); $sql = $this->trigger(Database::EVENT_DOCUMENT_FIND, $sql); $stmt = $this->getPDO()->prepare($sql); diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index bf0971040..9be2b0429 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -1557,6 +1557,16 @@ public function getSupportForIndex(): bool return true; } + /** + * Is index array supported? + * + * @return bool + */ + public function getSupportForIndexArray(): bool + { + return true; + } + /** * Is unique index supported? * diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 755d5e2d5..f3ed751c2 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -229,6 +229,16 @@ public function getSupportForIndex(): bool return true; } + /** + * Is index supported? + * + * @return bool + */ + public function getSupportForIndexArray(): bool + { + return true; + } + /** * Is unique index supported? * diff --git a/src/Database/Database.php b/src/Database/Database.php index dfc660e1a..b276eded5 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -2366,6 +2366,11 @@ public function createIndex(string $collection, string $id, string $type, array } break; + case self::INDEX_ARRAY: + if (!$this->adapter->getSupportForIndexArray()) { + throw new DatabaseException('Key index array is not supported'); + } + break; default: throw new DatabaseException('Unknown index type: ' . $type . '. Must be one of ' . Database::INDEX_KEY . ', ' . Database::INDEX_UNIQUE . ', ' . Database::INDEX_ARRAY . ', ' . Database::INDEX_FULLTEXT); } diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index d31fb429d..de7b48726 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1599,7 +1599,7 @@ public function testDeleteDocument(Document $document): void * @throws StructureException * @throws DatabaseException */ - public function testArrayAttribute(): array + public function testArrayAttribute(): void { Authorization::setRole(Role::any()->toString()); @@ -1610,14 +1610,6 @@ public function testArrayAttribute(): array Permission::create(Role::any()), ]); - $this->assertEquals(true, static::getDatabase()->createAttribute( - $collection, - 'age', - Database::VAR_INTEGER, - size: 0, - required: true, - )); - $this->assertEquals(true, static::getDatabase()->createAttribute( $collection, 'active', @@ -1646,6 +1638,15 @@ public function testArrayAttribute(): array array: true )); + $this->assertEquals(true, static::getDatabase()->createAttribute( + $collection, + 'age', + Database::VAR_INTEGER, + size: 0, + required: false, + signed: false + )); + try { static::getDatabase()->createDocument($collection, new Document([])); $this->fail('Failed to throw exception'); @@ -1666,23 +1667,45 @@ public function testArrayAttribute(): array try { static::getDatabase()->createDocument($collection, new Document([ 'active' => [false], - 'numbers' => [-100], - 'age' => [-20], + 'age' => 1.5, ])); $this->fail('Failed to throw exception'); } catch(Throwable $e) { - $this->assertEquals('Should fails since it is Signed is false!', $e->getMessage()); + $this->assertEquals('Invalid document structure: Attribute "age" has invalid type. Value must be a valid integer', $e->getMessage()); + } + + try { + static::getDatabase()->createDocument($collection, new Document([ + 'active' => [false], + 'age' => -1, + ])); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + //$this->assertEquals('Should fail since it is Signed = false!!!!', $e->getMessage()); } $document = static::getDatabase()->createDocument($collection, new Document([ - '$permissions' => $permissions, - 'names' => ['Joe Baden', 'Antony Blinken', '100'], - 'numbers' => [0, -100, 999, 10.4], + 'active' => [false], + 'names' => ['Joe', 'Antony', '100'], + 'numbers' => [0, 100, 1000, -1], ])); + $this->assertEquals(false, $document->getAttribute('active')[0]); + $this->assertEquals('Antony', $document->getAttribute('names')[1]); + $this->assertEquals(100, $document->getAttribute('numbers')[1]); + +// try { +// todo: force create only INDEX_ARRAY for array???? +// static::getDatabase()->createIndex($collection, 'ind-names', Database::INDEX_FULLTEXT, ['names']); +// $this->fail('Failed to throw exception'); +// } catch(Throwable $e) { +// $this->assertEquals('Should this fail? can we create a fulltext index on array as users do today?', $e->getMessage()); +// } + + $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'ind-names', Database::INDEX_ARRAY, ['names'])); + var_dump($document); $this->assertEquals(0,1); - } /** @@ -2074,6 +2097,9 @@ public function testFindContains(): void $this->assertEquals('Invalid query: Cannot query contains on attribute "name" because it is not an array.', $e->getMessage()); $this->assertTrue($e instanceof DatabaseException); } + + $this->assertTrue(false); + } public function testFindFulltext(): void From 2ea81a127956d7c7bc122a013ffcb03994e56a25 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 26 Dec 2023 17:51:32 +0200 Subject: [PATCH 13/19] find test --- src/Database/Adapter/MariaDB.php | 8 ++ src/Database/Adapter/MySQL.php | 2 +- src/Database/Adapter/SQL.php | 1 - tests/e2e/Adapter/Base.php | 16 ++- tests/e2e/Adapter/MariaDBTest.php | 118 +++++++++--------- tests/e2e/Adapter/MongoDBTest.php | 192 ++++++++++++++--------------- tests/e2e/Adapter/PostgresTest.php | 116 ++++++++--------- tests/e2e/Adapter/SQLiteTest.php | 128 +++++++++---------- 8 files changed, 299 insertions(+), 282 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 720f04641..d31b0b0db 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1926,6 +1926,14 @@ protected function getSQLCondition(Query $query): string } $condition = implode(' OR ', $conditions); return empty($condition) ? '' : '(' . $condition . ')'; + + +// $conditions = []; +// foreach ($query->getValues() as $key => $value) { +// $conditions[] = "JSON_CONTAINS(`table_main`.{$attribute}, :{$placeholder}_{$key})"; +// } +// $condition = implode(' OR ', $conditions); +// return empty($condition) ? '' : '(' . $condition . ')'; default: $conditions = []; foreach ($query->getValues() as $key => $value) { diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index 23d98cadc..e7ee26857 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -31,7 +31,7 @@ protected function getSQLIndex(string $collection, string $id, string $type, arr $type = 'INDEX'; foreach ($attributes as $key => $value) { - $attributes[$key] = '(CAST(' . $value . ' AS char(255) ARRAY))'; + $attributes[$key] = '(CAST(`' . $value . '` AS char(255) ARRAY))'; } break; diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index f3ed751c2..007337ac2 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -955,7 +955,6 @@ public function getSQLConditions(array $queries = [], string $separator = 'AND') continue; } - /* @var $query Query */ if($query->isNested()) { $conditions[] = $this->getSQLConditions($query->getValues(), $query->getMethod()); } else { diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index de7b48726..c17dd4ef7 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1685,6 +1685,7 @@ public function testArrayAttribute(): void } $document = static::getDatabase()->createDocument($collection, new Document([ + '$permissions' => $permissions, 'active' => [false], 'names' => ['Joe', 'Antony', '100'], 'numbers' => [0, 100, 1000, -1], @@ -1702,10 +1703,19 @@ public function testArrayAttribute(): void // $this->assertEquals('Should this fail? can we create a fulltext index on array as users do today?', $e->getMessage()); // } - $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'ind-names', Database::INDEX_ARRAY, ['names'])); + $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'index-names', Database::INDEX_ARRAY, ['names'])); + + if ($this->getDatabase()->getAdapter()->getSupportForQueryContains()) { + $documents = static::getDatabase()->find($collection, [ + Query::contains('names', ['Jake', 'Joe']) + ]); + + $this->assertCount(1, $documents); + + var_dump($documents); + $this->assertEquals(true,false); + } - var_dump($document); - $this->assertEquals(0,1); } /** diff --git a/tests/e2e/Adapter/MariaDBTest.php b/tests/e2e/Adapter/MariaDBTest.php index e26ca69c2..fc3b8ce77 100644 --- a/tests/e2e/Adapter/MariaDBTest.php +++ b/tests/e2e/Adapter/MariaDBTest.php @@ -1,60 +1,60 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MariaDB($pdo), $cache); - $database->setDatabase('utopiaTests'); - $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Tests\E2E\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Cache\Cache; +//use Utopia\Database\Adapter\MariaDB; +//use Utopia\Database\Database; +// +//class MariaDBTest extends Base +//{ +// protected static ?Database $database = null; +// protected static string $namespace; +// +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mariadb"; +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(bool $fresh = false): Database +// { +// if (!is_null(self::$database) && !$fresh) { +// return self::$database; +// } +// +// $dbHost = 'mariadb'; +// $dbPort = '3306'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MariaDB($pdo), $cache); +// $database->setDatabase('utopiaTests'); +// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} diff --git a/tests/e2e/Adapter/MongoDBTest.php b/tests/e2e/Adapter/MongoDBTest.php index 887c26ec2..c348bd331 100644 --- a/tests/e2e/Adapter/MongoDBTest.php +++ b/tests/e2e/Adapter/MongoDBTest.php @@ -1,97 +1,97 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $schema = 'utopiaTests'; // same as $this->testDatabase - $client = new Client( - $schema, - 'mongo', - 27017, - 'root', - 'example', - false - ); - - $database = new Database(new Mongo($client), $cache); - $database->setDatabase($schema); - $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - /** - * @throws Exception - */ - public function testCreateExistsDelete(): void - { - // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. - $this->assertNotNull(static::getDatabase()->create()); - $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); - $this->assertEquals(true, static::getDatabase()->create()); - $this->assertEquals(static::getDatabase(), static::getDatabase()->setDatabase($this->testDatabase)); - } - - public function testRenameAttribute(): void - { - $this->assertTrue(true); - } - - public function testRenameAttributeExisting(): void - { - $this->assertTrue(true); - } - - public function testUpdateAttributeStructure(): void - { - $this->assertTrue(true); - } - - public function testKeywords(): void - { - $this->assertTrue(true); - } -} +// +//namespace Tests\E2E\Adapter; +// +//use Exception; +//use Redis; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Cache\Cache; +//use Utopia\Database\Adapter\Mongo; +//use Utopia\Database\Database; +//use Utopia\Mongo\Client; +// +//class MongoDBTest extends Base +//{ +// public static ?Database $database = null; +// protected static string $namespace; +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mongodb"; +// } +// +// /** +// * @return Database +// * @throws Exception +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $schema = 'utopiaTests'; // same as $this->testDatabase +// $client = new Client( +// $schema, +// 'mongo', +// 27017, +// 'root', +// 'example', +// false +// ); +// +// $database = new Database(new Mongo($client), $cache); +// $database->setDatabase($schema); +// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// /** +// * @throws Exception +// */ +// public function testCreateExistsDelete(): void +// { +// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. +// $this->assertNotNull(static::getDatabase()->create()); +// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); +// $this->assertEquals(true, static::getDatabase()->create()); +// $this->assertEquals(static::getDatabase(), static::getDatabase()->setDatabase($this->testDatabase)); +// } +// +// public function testRenameAttribute(): void +// { +// $this->assertTrue(true); +// } +// +// public function testRenameAttributeExisting(): void +// { +// $this->assertTrue(true); +// } +// +// public function testUpdateAttributeStructure(): void +// { +// $this->assertTrue(true); +// } +// +// public function testKeywords(): void +// { +// $this->assertTrue(true); +// } +//} diff --git a/tests/e2e/Adapter/PostgresTest.php b/tests/e2e/Adapter/PostgresTest.php index 6a12ecd8c..048801724 100644 --- a/tests/e2e/Adapter/PostgresTest.php +++ b/tests/e2e/Adapter/PostgresTest.php @@ -1,59 +1,59 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new Postgres($pdo), $cache); - $database->setDatabase('utopiaTests'); - $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Tests\E2E\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Cache\Cache; +//use Utopia\Database\Adapter\Postgres; +//use Utopia\Database\Database; +// +//class PostgresTest extends Base +//{ +// public static ?Database $database = null; +// protected static string $namespace; +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "postgres"; +// } +// +// /** +// * @reture Adapter +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'postgres'; +// $dbPort = '5432'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, Postgres::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new Postgres($pdo), $cache); +// $database->setDatabase('utopiaTests'); +// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} diff --git a/tests/e2e/Adapter/SQLiteTest.php b/tests/e2e/Adapter/SQLiteTest.php index 16c36f6fd..8451dfa91 100644 --- a/tests/e2e/Adapter/SQLiteTest.php +++ b/tests/e2e/Adapter/SQLiteTest.php @@ -1,65 +1,65 @@ connect('redis'); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new SQLite($pdo), $cache); - $database->setDatabase('utopiaTests'); - $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); - - if ($database->exists()) { - $database->delete(); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Tests\E2E\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Cache\Cache; +//use Utopia\Database\Adapter\SQLite; +//use Utopia\Database\Database; +// +//class SQLiteTest extends Base +//{ +// public static ?Database $database = null; +// protected static string $namespace; +// +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "sqlite"; +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $db = __DIR__."/database.sql"; +// +// if (file_exists($db)) { +// unlink($db); +// } +// +// $dsn = $db; +// //$dsn = 'memory'; // Overwrite for fast tests +// $pdo = new PDO("sqlite:" . $dsn, null, null, SQLite::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis'); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new SQLite($pdo), $cache); +// $database->setDatabase('utopiaTests'); +// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); +// +// if ($database->exists()) { +// $database->delete(); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} From 54f616625ea893c14eafcd5aa68ee54da51c01b8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 27 Dec 2023 18:52:54 +0200 Subject: [PATCH 14/19] tests --- composer.lock | 12 ++++++------ src/Database/Adapter/MariaDB.php | 2 +- src/Database/Adapter/MySQL.php | 2 +- src/Database/Database.php | 3 --- tests/e2e/Adapter/Base.php | 8 +++++--- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index 7afcb5ffe..1a6b42b8a 100644 --- a/composer.lock +++ b/composer.lock @@ -268,16 +268,16 @@ }, { "name": "utopia-php/framework", - "version": "0.31.1", + "version": "0.32.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68" + "reference": "ad6f7e6d6b38cf5bed4e3af9a1394c59d4bb9225" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", - "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/ad6f7e6d6b38cf5bed4e3af9a1394c59d4bb9225", + "reference": "ad6f7e6d6b38cf5bed4e3af9a1394c59d4bb9225", "shasum": "" }, "require": { @@ -307,9 +307,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.31.1" + "source": "https://github.com/utopia-php/framework/tree/0.32.0" }, - "time": "2023-12-08T18:47:29+00:00" + "time": "2023-12-26T14:18:36+00:00" }, { "name": "utopia-php/mongo", diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index d31b0b0db..ce341f601 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -641,7 +641,7 @@ public function createIndex(string $collection, string $id, string $type, array } $sql = $this->getSQLIndex($name, $id, $type, $attributes); - + var_dump($sql); $sql = $this->trigger(Database::EVENT_INDEX_CREATE, $sql); return $this->getPDO() diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index e7ee26857..23d98cadc 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -31,7 +31,7 @@ protected function getSQLIndex(string $collection, string $id, string $type, arr $type = 'INDEX'; foreach ($attributes as $key => $value) { - $attributes[$key] = '(CAST(`' . $value . '` AS char(255) ARRAY))'; + $attributes[$key] = '(CAST(' . $value . ' AS char(255) ARRAY))'; } break; diff --git a/src/Database/Database.php b/src/Database/Database.php index b276eded5..3f8f133bb 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -1175,9 +1175,6 @@ public function createAttribute(string $collection, string $id, string $type, in if ($size > $this->adapter->getLimitForString()) { throw new DatabaseException('Max size allowed for string is: ' . number_format($this->adapter->getLimitForString())); } - else if ($size < 1) { - throw new DatabaseException('Min size is 1'); - } break; case self::VAR_INTEGER: diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index c17dd4ef7..a413b7774 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1623,7 +1623,7 @@ public function testArrayAttribute(): void $collection, 'names', Database::VAR_STRING, - size: 50, + size: 255, required: false, array: true )); @@ -1661,7 +1661,7 @@ public function testArrayAttribute(): void ])); $this->fail('Failed to throw exception'); } catch(Throwable $e) { - $this->assertEquals('Invalid document structure: Attribute "names[\'1\']" has invalid type. Value must be a valid string and no longer than 50 chars', $e->getMessage()); + $this->assertEquals('Invalid document structure: Attribute "names[\'1\']" has invalid type. Value must be a valid string and no longer than 255 chars', $e->getMessage()); } try { @@ -1703,7 +1703,9 @@ public function testArrayAttribute(): void // $this->assertEquals('Should this fail? can we create a fulltext index on array as users do today?', $e->getMessage()); // } - $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'index-names', Database::INDEX_ARRAY, ['names'])); + $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-numbers', Database::INDEX_ARRAY, ['numbers'], [], [])); + + //$this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-names', Database::INDEX_ARRAY, ['age', 'names'], [], ['asc', 'desc'])); if ($this->getDatabase()->getAdapter()->getSupportForQueryContains()) { $documents = static::getDatabase()->find($collection, [ From 361f379710ec00a74d5491dd085d9fdc115c4cc8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 28 Dec 2023 12:25:00 +0200 Subject: [PATCH 15/19] try json_overlaps --- src/Database/Adapter/MariaDB.php | 9 +-- tests/e2e/Adapter/Base.php | 12 +++ tests/e2e/Adapter/MariaDBTest.php | 118 ++++++++++++++--------------- tests/e2e/Adapter/MySQLTest.php | 122 +++++++++++++++--------------- 4 files changed, 133 insertions(+), 128 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index ce341f601..6aed86337 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1920,20 +1920,13 @@ protected function getSQLCondition(Query $query): string return "`table_main`.{$attribute} {$this->getSQLOperator($query->getMethod())}"; case Query::TYPE_CONTAINS: + // todo: change to JSON_OVERLAPS when using mariaDB 10.9 $conditions = []; foreach ($query->getValues() as $key => $value) { $conditions[] = "JSON_CONTAINS(`table_main`.{$attribute}, :{$placeholder}_{$key})"; } $condition = implode(' OR ', $conditions); return empty($condition) ? '' : '(' . $condition . ')'; - - -// $conditions = []; -// foreach ($query->getValues() as $key => $value) { -// $conditions[] = "JSON_CONTAINS(`table_main`.{$attribute}, :{$placeholder}_{$key})"; -// } -// $condition = implode(' OR ', $conditions); -// return empty($condition) ? '' : '(' . $condition . ')'; default: $conditions = []; foreach ($query->getValues() as $key => $value) { diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index a413b7774..76a0b7d6e 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1711,10 +1711,22 @@ public function testArrayAttribute(): void $documents = static::getDatabase()->find($collection, [ Query::contains('names', ['Jake', 'Joe']) ]); + $this->assertCount(1, $documents); + + $documents = static::getDatabase()->find($collection, [ + Query::contains('numbers', [-1, 0]) + ]); + $this->assertCount(1, $documents); + + $documents = static::getDatabase()->find($collection, [ + Query::contains('active', [false]) + ]); $this->assertCount(1, $documents); var_dump($documents); + + $this->assertEquals(true,false); } diff --git a/tests/e2e/Adapter/MariaDBTest.php b/tests/e2e/Adapter/MariaDBTest.php index fc3b8ce77..e26ca69c2 100644 --- a/tests/e2e/Adapter/MariaDBTest.php +++ b/tests/e2e/Adapter/MariaDBTest.php @@ -1,60 +1,60 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new MariaDB($pdo), $cache); -// $database->setDatabase('utopiaTests'); -// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -//} + +namespace Tests\E2E\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Database; + +class MariaDBTest extends Base +{ + protected static ?Database $database = null; + protected static string $namespace; + + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mariadb"; + } + + /** + * @return Database + */ + public static function getDatabase(bool $fresh = false): Database + { + if (!is_null(self::$database) && !$fresh) { + return self::$database; + } + + $dbHost = 'mariadb'; + $dbPort = '3306'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MariaDB($pdo), $cache); + $database->setDatabase('utopiaTests'); + $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } +} diff --git a/tests/e2e/Adapter/MySQLTest.php b/tests/e2e/Adapter/MySQLTest.php index d204e8a40..11fcbfb02 100644 --- a/tests/e2e/Adapter/MySQLTest.php +++ b/tests/e2e/Adapter/MySQLTest.php @@ -1,62 +1,62 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MySQL($pdo), $cache); - $database->setDatabase('utopiaTests'); - $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Tests\E2E\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Cache\Cache; +//use Utopia\Database\Adapter\MySQL; +//use Utopia\Database\Database; +// +//class MySQLTest extends Base +//{ +// public static ?Database $database = null; +// protected static string $namespace; +// +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mysql"; +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mysql'; +// $dbPort = '3307'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MySQL($pdo), $cache); +// $database->setDatabase('utopiaTests'); +// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} From d4414016a87078e69bba6cab418309daa9557182 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 28 Dec 2023 12:35:36 +0200 Subject: [PATCH 16/19] tests --- tests/e2e/Adapter/Base.php | 5 +- tests/e2e/Adapter/MongoDBTest.php | 192 ++++++++++++++--------------- tests/e2e/Adapter/MySQLTest.php | 122 +++++++++--------- tests/e2e/Adapter/PostgresTest.php | 116 ++++++++--------- tests/e2e/Adapter/SQLiteTest.php | 128 +++++++++---------- 5 files changed, 280 insertions(+), 283 deletions(-) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 76a0b7d6e..6318cc967 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1727,7 +1727,7 @@ public function testArrayAttribute(): void var_dump($documents); - $this->assertEquals(true,false); + //$this->assertEquals(true,false); } } @@ -2121,9 +2121,6 @@ public function testFindContains(): void $this->assertEquals('Invalid query: Cannot query contains on attribute "name" because it is not an array.', $e->getMessage()); $this->assertTrue($e instanceof DatabaseException); } - - $this->assertTrue(false); - } public function testFindFulltext(): void diff --git a/tests/e2e/Adapter/MongoDBTest.php b/tests/e2e/Adapter/MongoDBTest.php index c348bd331..887c26ec2 100644 --- a/tests/e2e/Adapter/MongoDBTest.php +++ b/tests/e2e/Adapter/MongoDBTest.php @@ -1,97 +1,97 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $schema = 'utopiaTests'; // same as $this->testDatabase -// $client = new Client( -// $schema, -// 'mongo', -// 27017, -// 'root', -// 'example', -// false -// ); -// -// $database = new Database(new Mongo($client), $cache); -// $database->setDatabase($schema); -// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// /** -// * @throws Exception -// */ -// public function testCreateExistsDelete(): void -// { -// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. -// $this->assertNotNull(static::getDatabase()->create()); -// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); -// $this->assertEquals(true, static::getDatabase()->create()); -// $this->assertEquals(static::getDatabase(), static::getDatabase()->setDatabase($this->testDatabase)); -// } -// -// public function testRenameAttribute(): void -// { -// $this->assertTrue(true); -// } -// -// public function testRenameAttributeExisting(): void -// { -// $this->assertTrue(true); -// } -// -// public function testUpdateAttributeStructure(): void -// { -// $this->assertTrue(true); -// } -// -// public function testKeywords(): void -// { -// $this->assertTrue(true); -// } -//} + +namespace Tests\E2E\Adapter; + +use Exception; +use Redis; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\Mongo; +use Utopia\Database\Database; +use Utopia\Mongo\Client; + +class MongoDBTest extends Base +{ + public static ?Database $database = null; + protected static string $namespace; + + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mongodb"; + } + + /** + * @return Database + * @throws Exception + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $schema = 'utopiaTests'; // same as $this->testDatabase + $client = new Client( + $schema, + 'mongo', + 27017, + 'root', + 'example', + false + ); + + $database = new Database(new Mongo($client), $cache); + $database->setDatabase($schema); + $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + /** + * @throws Exception + */ + public function testCreateExistsDelete(): void + { + // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. + $this->assertNotNull(static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); + $this->assertEquals(true, static::getDatabase()->create()); + $this->assertEquals(static::getDatabase(), static::getDatabase()->setDatabase($this->testDatabase)); + } + + public function testRenameAttribute(): void + { + $this->assertTrue(true); + } + + public function testRenameAttributeExisting(): void + { + $this->assertTrue(true); + } + + public function testUpdateAttributeStructure(): void + { + $this->assertTrue(true); + } + + public function testKeywords(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/e2e/Adapter/MySQLTest.php b/tests/e2e/Adapter/MySQLTest.php index 11fcbfb02..d204e8a40 100644 --- a/tests/e2e/Adapter/MySQLTest.php +++ b/tests/e2e/Adapter/MySQLTest.php @@ -1,62 +1,62 @@ connect('redis', 6379); -// $redis->flushAll(); -// -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new MySQL($pdo), $cache); -// $database->setDatabase('utopiaTests'); -// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -//} + +namespace Tests\E2E\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\MySQL; +use Utopia\Database\Database; + +class MySQLTest extends Base +{ + public static ?Database $database = null; + protected static string $namespace; + + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mysql"; + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'mysql'; + $dbPort = '3307'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MySQL($pdo), $cache); + $database->setDatabase('utopiaTests'); + $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } +} diff --git a/tests/e2e/Adapter/PostgresTest.php b/tests/e2e/Adapter/PostgresTest.php index 048801724..6a12ecd8c 100644 --- a/tests/e2e/Adapter/PostgresTest.php +++ b/tests/e2e/Adapter/PostgresTest.php @@ -1,59 +1,59 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new Postgres($pdo), $cache); -// $database->setDatabase('utopiaTests'); -// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -//} + +namespace Tests\E2E\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\Postgres; +use Utopia\Database\Database; + +class PostgresTest extends Base +{ + public static ?Database $database = null; + protected static string $namespace; + + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "postgres"; + } + + /** + * @reture Adapter + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'postgres'; + $dbPort = '5432'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, Postgres::getPDOAttributes()); + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new Postgres($pdo), $cache); + $database->setDatabase('utopiaTests'); + $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } +} diff --git a/tests/e2e/Adapter/SQLiteTest.php b/tests/e2e/Adapter/SQLiteTest.php index 8451dfa91..16c36f6fd 100644 --- a/tests/e2e/Adapter/SQLiteTest.php +++ b/tests/e2e/Adapter/SQLiteTest.php @@ -1,65 +1,65 @@ connect('redis'); -// $redis->flushAll(); -// -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new SQLite($pdo), $cache); -// $database->setDatabase('utopiaTests'); -// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); -// -// if ($database->exists()) { -// $database->delete(); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -//} + +namespace Tests\E2E\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\SQLite; +use Utopia\Database\Database; + +class SQLiteTest extends Base +{ + public static ?Database $database = null; + protected static string $namespace; + + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "sqlite"; + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $db = __DIR__."/database.sql"; + + if (file_exists($db)) { + unlink($db); + } + + $dsn = $db; + //$dsn = 'memory'; // Overwrite for fast tests + $pdo = new PDO("sqlite:" . $dsn, null, null, SQLite::getPDOAttributes()); + + $redis = new Redis(); + $redis->connect('redis'); + $redis->flushAll(); + + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new SQLite($pdo), $cache); + $database->setDatabase('utopiaTests'); + $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); + + if ($database->exists()) { + $database->delete(); + } + + $database->create(); + + return self::$database = $database; + } +} From 5a3d56fdc53503425b87dfcfa53a36aa12392db2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 31 Dec 2023 11:14:06 +0200 Subject: [PATCH 17/19] createIndex order test --- tests/e2e/Adapter/Base.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 6318cc967..c0d4c584c 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1623,7 +1623,7 @@ public function testArrayAttribute(): void $collection, 'names', Database::VAR_STRING, - size: 255, + size: 255, // todo: this makes problems, array is Longtext/Json while length is data length, index problems required: false, array: true )); @@ -1705,7 +1705,8 @@ public function testArrayAttribute(): void $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-numbers', Database::INDEX_ARRAY, ['numbers'], [], [])); - //$this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-names', Database::INDEX_ARRAY, ['age', 'names'], [], ['asc', 'desc'])); + $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-names1', Database::INDEX_ARRAY, ['names'], [255], ['desc'])); + $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-names2', Database::INDEX_ARRAY, ['age', 'names'], [100,100], ['asc', 'desc'])); if ($this->getDatabase()->getAdapter()->getSupportForQueryContains()) { $documents = static::getDatabase()->find($collection, [ @@ -1725,11 +1726,10 @@ public function testArrayAttribute(): void $this->assertCount(1, $documents); var_dump($documents); - - - //$this->assertEquals(true,false); } + $this->assertEquals(true,false); + } /** From cc22abd26f4920f705497a39dfc110418def505f Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 Jan 2024 19:17:27 +0200 Subject: [PATCH 18/19] Order tests --- docker-compose.yml | 2 +- src/Database/Adapter/MariaDB.php | 4 - src/Database/Database.php | 2 +- src/Database/Validator/Index.php | 57 ++++++++- tests/e2e/Adapter/Base.php | 98 ++++++++++++--- tests/e2e/Adapter/MongoDBTest.php | 192 ++++++++++++++--------------- tests/e2e/Adapter/MySQLTest.php | 122 +++++++++--------- tests/e2e/Adapter/PostgresTest.php | 116 ++++++++--------- 8 files changed, 351 insertions(+), 242 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8b386c771..518c145f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,7 +38,7 @@ services: POSTGRES_PASSWORD: password mariadb: - image: mariadb:10.7 + image: mariadb:10.9 # Is it ok to upgrade Appwrite to >= 10.9 for JSON_OVERLAPS container_name: utopia-mariadb networks: - database diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 6aed86337..3a893be49 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -272,10 +272,6 @@ public function createAttribute(string $collection, string $id, string $type, in $id = $this->filter($id); $type = $this->getSQLType($type, $size, $signed, $array); - if ($array) { - $type = 'JSON'; - } - $sql = "ALTER TABLE {$this->getSQLTable($name)} ADD COLUMN `{$id}` {$type};"; $sql = $this->trigger(Database::EVENT_ATTRIBUTE_CREATE, $sql); diff --git a/src/Database/Database.php b/src/Database/Database.php index 3f8f133bb..22d5659d1 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -2362,7 +2362,7 @@ public function createIndex(string $collection, string $id, string $type, array throw new DatabaseException('Fulltext index is not supported'); } break; - + case self::INDEX_ARRAY: if (!$this->adapter->getSupportForIndexArray()) { throw new DatabaseException('Key index array is not supported'); diff --git a/src/Database/Validator/Index.php b/src/Database/Validator/Index.php index 6cdf9d89c..d1bfffd52 100644 --- a/src/Database/Validator/Index.php +++ b/src/Database/Validator/Index.php @@ -20,6 +20,7 @@ class Index extends Validator /** * @param array $attributes * @param int $maxLength + * @param bool $indexArraySupport * @throws DatabaseException */ public function __construct(array $attributes, int $maxLength) @@ -97,7 +98,6 @@ public function checkDuplicatedAttributes(Document $index): bool /** * @param Document $index * @return bool - * @throws DatabaseException */ public function checkFulltextIndexNonString(Document $index): bool { @@ -113,6 +113,42 @@ public function checkFulltextIndexNonString(Document $index): bool return true; } + /** + * @param Document $index + * @return bool + */ + public function checkArrayIndex(Document $index): bool + { + $attributes = $index->getAttribute('attributes', []); + $orders = $index->getAttribute('orders', []); + + $arrayAttributes = []; + foreach ($attributes as $key => $attribute) { + $attribute = $this->attributes[\strtolower($attribute)] ?? new Document(); + if($attribute->getAttribute('array') === true){ + // Database::INDEX_UNIQUE Is not allowed! since mariaDB VS MySQL makes the unique Different on values + if(!in_array($index->getAttribute('type'), [Database::INDEX_ARRAY, Database::INDEX_KEY])){ + $this->message = 'Invalid "' . ucfirst($index->getAttribute('type')) . '" index on array attributes'; + return false; + } + + var_dump($attribute); + $arrayAttributes[] = $attribute->getAttribute('key', ''); + if(count($arrayAttributes) > 1){ + $this->message = 'Only a single index can be created on array attributes found "' . implode(',', $arrayAttributes) . '"'; + return false; + } + + $direction = $orders[$key] ?? ''; + if(!empty($direction)){ + $this->message = 'Invalid index order "' . $direction . '" on array attribute "'. $attribute->getAttribute('key', '') .'"'; + return false; + } + } + } + return true; + } + /** * @param Document $index * @return bool @@ -129,10 +165,24 @@ public function checkIndexLength(Document $index): bool foreach ($index->getAttribute('attributes', []) as $attributePosition => $attributeName) { $attribute = $this->attributes[\strtolower($attributeName)]; + $isArray = $attribute->getAttribute('array', false); + + if($isArray && empty($lengths[$attributePosition])){ + $this->message = 'Index length for array not specified'; + return false; + } + + if(!$isArray && $attribute->getAttribute('type') !== Database::VAR_STRING && !empty($lengths[$attributePosition])){ + $this->message = 'Key part length are forbidden on "' . $attribute->getAttribute('type') . '" data-type'; + return false; + } + switch ($attribute->getAttribute('type')) { case Database::VAR_STRING: $attributeSize = $attribute->getAttribute('size', 0); $indexLength = $lengths[$attributePosition] ?? $attributeSize; + var_dump($attributeName); + var_dump($indexLength); break; case Database::VAR_FLOAT: $attributeSize = 2; // 8 bytes / 4 mb4 @@ -140,6 +190,7 @@ public function checkIndexLength(Document $index): bool break; default: $attributeSize = 1; // 4 bytes / 4 mb4 + // $attributeSize = $attribute->getAttribute('size', 1); // 4 bytes / 4 mb4 $indexLength = 1; break; } @@ -186,6 +237,10 @@ public function isValid($value): bool return false; } + if (!$this->checkArrayIndex($value)) { + return false; + } + if (!$this->checkIndexLength($value)) { return false; } diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index c0d4c584c..6fc337fd1 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1597,7 +1597,6 @@ public function testDeleteDocument(Document $document): void * @throws ConflictException * @throws LimitException * @throws StructureException - * @throws DatabaseException */ public function testArrayAttribute(): void { @@ -1612,7 +1611,7 @@ public function testArrayAttribute(): void $this->assertEquals(true, static::getDatabase()->createAttribute( $collection, - 'active', + 'booleans', Database::VAR_BOOLEAN, size: 0, required: true, @@ -1623,7 +1622,7 @@ public function testArrayAttribute(): void $collection, 'names', Database::VAR_STRING, - size: 255, // todo: this makes problems, array is Longtext/Json while length is data length, index problems + size: 255, // Does this mean each Element max is 255? We need to check this on Structure validation? required: false, array: true )); @@ -1651,12 +1650,12 @@ public function testArrayAttribute(): void static::getDatabase()->createDocument($collection, new Document([])); $this->fail('Failed to throw exception'); } catch(Throwable $e) { - $this->assertEquals('Invalid document structure: Missing required attribute "active"', $e->getMessage()); + $this->assertEquals('Invalid document structure: Missing required attribute "booleans"', $e->getMessage()); } try { static::getDatabase()->createDocument($collection, new Document([ - 'active' => [false], + 'booleans' => [false], 'names' => ['Joe', 100], ])); $this->fail('Failed to throw exception'); @@ -1666,7 +1665,7 @@ public function testArrayAttribute(): void try { static::getDatabase()->createDocument($collection, new Document([ - 'active' => [false], + 'booleans' => [false], 'age' => 1.5, ])); $this->fail('Failed to throw exception'); @@ -1676,7 +1675,7 @@ public function testArrayAttribute(): void try { static::getDatabase()->createDocument($collection, new Document([ - 'active' => [false], + 'booleans' => [false], 'age' => -1, ])); $this->fail('Failed to throw exception'); @@ -1684,29 +1683,88 @@ public function testArrayAttribute(): void //$this->assertEquals('Should fail since it is Signed = false!!!!', $e->getMessage()); } - $document = static::getDatabase()->createDocument($collection, new Document([ + static::getDatabase()->createDocument($collection, new Document([ + '$id' => 'joe', '$permissions' => $permissions, - 'active' => [false], + 'booleans' => [false], 'names' => ['Joe', 'Antony', '100'], 'numbers' => [0, 100, 1000, -1], ])); - $this->assertEquals(false, $document->getAttribute('active')[0]); + $document = static::getDatabase()->getDocument($collection, 'joe'); + + $this->assertEquals(false, $document->getAttribute('booleans')[0]); $this->assertEquals('Antony', $document->getAttribute('names')[1]); $this->assertEquals(100, $document->getAttribute('numbers')[1]); -// try { -// todo: force create only INDEX_ARRAY for array???? -// static::getDatabase()->createIndex($collection, 'ind-names', Database::INDEX_FULLTEXT, ['names']); -// $this->fail('Failed to throw exception'); -// } catch(Throwable $e) { -// $this->assertEquals('Should this fail? can we create a fulltext index on array as users do today?', $e->getMessage()); -// } + try { + static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_FULLTEXT, ['names']); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Invalid "Fulltext" index on array attributes', $e->getMessage()); + } + + try { + static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_KEY, ['numbers', 'names']); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Only a single index can be created on array attributes found "numbers,names"', $e->getMessage()); + } + + try { + static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_KEY, ['numbers', 'names']); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Only a single index can be created on array attributes found "numbers,names"', $e->getMessage()); + } + + + $this->assertEquals(true, static::getDatabase()->createAttribute( + $collection, + 'long_names', + Database::VAR_STRING, + size: 2000, + required: false, + array: true + )); + + try { + static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_KEY, ['long_names'], [], []); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Index length for array not specified', $e->getMessage()); + } + + try { + static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_KEY, ['long_names'], [1000], []); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Index length is longer than the maximum: 768', $e->getMessage()); + } + + try { + static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_KEY, ['names'], [255], ['desc']); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Invalid index order "desc" on array attribute "names"', $e->getMessage()); + } + + try { + static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_KEY, ['age', 'names'], [10, 255], []); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Key part length are forbidden on "integer" data-type', $e->getMessage()); + } + + $this->assertTrue(static::getDatabase()->createIndex($collection, 'indx_names', Database::INDEX_KEY, ['names'], [255], [])); + $this->assertTrue(static::getDatabase()->createIndex($collection, 'indx_age_names1', Database::INDEX_KEY, ['age', 'names'], [null, 255], [])); + $this->assertTrue(static::getDatabase()->createIndex($collection, 'indx_age_names2', Database::INDEX_KEY, ['age', 'booleans'], [0, 255], [])); + // $this->assertTrue(static::getDatabase()->createIndex($collection, 'indx_age_names', Database::INDEX_ARRAY, ['age', 'names'], [255, 255], [])); + + + $this->assertEquals(true,false); - $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-numbers', Database::INDEX_ARRAY, ['numbers'], [], [])); - $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-names1', Database::INDEX_ARRAY, ['names'], [255], ['desc'])); - $this->assertTrue(true, static::getDatabase()->createIndex($collection, 'indx-names2', Database::INDEX_ARRAY, ['age', 'names'], [100,100], ['asc', 'desc'])); if ($this->getDatabase()->getAdapter()->getSupportForQueryContains()) { $documents = static::getDatabase()->find($collection, [ diff --git a/tests/e2e/Adapter/MongoDBTest.php b/tests/e2e/Adapter/MongoDBTest.php index 887c26ec2..c348bd331 100644 --- a/tests/e2e/Adapter/MongoDBTest.php +++ b/tests/e2e/Adapter/MongoDBTest.php @@ -1,97 +1,97 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $schema = 'utopiaTests'; // same as $this->testDatabase - $client = new Client( - $schema, - 'mongo', - 27017, - 'root', - 'example', - false - ); - - $database = new Database(new Mongo($client), $cache); - $database->setDatabase($schema); - $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - /** - * @throws Exception - */ - public function testCreateExistsDelete(): void - { - // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. - $this->assertNotNull(static::getDatabase()->create()); - $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); - $this->assertEquals(true, static::getDatabase()->create()); - $this->assertEquals(static::getDatabase(), static::getDatabase()->setDatabase($this->testDatabase)); - } - - public function testRenameAttribute(): void - { - $this->assertTrue(true); - } - - public function testRenameAttributeExisting(): void - { - $this->assertTrue(true); - } - - public function testUpdateAttributeStructure(): void - { - $this->assertTrue(true); - } - - public function testKeywords(): void - { - $this->assertTrue(true); - } -} +// +//namespace Tests\E2E\Adapter; +// +//use Exception; +//use Redis; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Cache\Cache; +//use Utopia\Database\Adapter\Mongo; +//use Utopia\Database\Database; +//use Utopia\Mongo\Client; +// +//class MongoDBTest extends Base +//{ +// public static ?Database $database = null; +// protected static string $namespace; +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mongodb"; +// } +// +// /** +// * @return Database +// * @throws Exception +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $schema = 'utopiaTests'; // same as $this->testDatabase +// $client = new Client( +// $schema, +// 'mongo', +// 27017, +// 'root', +// 'example', +// false +// ); +// +// $database = new Database(new Mongo($client), $cache); +// $database->setDatabase($schema); +// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// /** +// * @throws Exception +// */ +// public function testCreateExistsDelete(): void +// { +// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. +// $this->assertNotNull(static::getDatabase()->create()); +// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); +// $this->assertEquals(true, static::getDatabase()->create()); +// $this->assertEquals(static::getDatabase(), static::getDatabase()->setDatabase($this->testDatabase)); +// } +// +// public function testRenameAttribute(): void +// { +// $this->assertTrue(true); +// } +// +// public function testRenameAttributeExisting(): void +// { +// $this->assertTrue(true); +// } +// +// public function testUpdateAttributeStructure(): void +// { +// $this->assertTrue(true); +// } +// +// public function testKeywords(): void +// { +// $this->assertTrue(true); +// } +//} diff --git a/tests/e2e/Adapter/MySQLTest.php b/tests/e2e/Adapter/MySQLTest.php index d204e8a40..11fcbfb02 100644 --- a/tests/e2e/Adapter/MySQLTest.php +++ b/tests/e2e/Adapter/MySQLTest.php @@ -1,62 +1,62 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MySQL($pdo), $cache); - $database->setDatabase('utopiaTests'); - $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Tests\E2E\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Cache\Cache; +//use Utopia\Database\Adapter\MySQL; +//use Utopia\Database\Database; +// +//class MySQLTest extends Base +//{ +// public static ?Database $database = null; +// protected static string $namespace; +// +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mysql"; +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mysql'; +// $dbPort = '3307'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MySQL($pdo), $cache); +// $database->setDatabase('utopiaTests'); +// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} diff --git a/tests/e2e/Adapter/PostgresTest.php b/tests/e2e/Adapter/PostgresTest.php index 6a12ecd8c..048801724 100644 --- a/tests/e2e/Adapter/PostgresTest.php +++ b/tests/e2e/Adapter/PostgresTest.php @@ -1,59 +1,59 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new Postgres($pdo), $cache); - $database->setDatabase('utopiaTests'); - $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Tests\E2E\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Cache\Cache; +//use Utopia\Database\Adapter\Postgres; +//use Utopia\Database\Database; +// +//class PostgresTest extends Base +//{ +// public static ?Database $database = null; +// protected static string $namespace; +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "postgres"; +// } +// +// /** +// * @reture Adapter +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'postgres'; +// $dbPort = '5432'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, Postgres::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new Postgres($pdo), $cache); +// $database->setDatabase('utopiaTests'); +// $database->setNamespace(static::$namespace = 'myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} From 9846af4836f54c9e47eac11d91dc11252d81860a Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 4 Jan 2024 10:53:22 +0200 Subject: [PATCH 19/19] Order tests --- src/Database/Validator/Index.php | 10 ++++------ tests/e2e/Adapter/Base.php | 20 ++++---------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/Database/Validator/Index.php b/src/Database/Validator/Index.php index d1bfffd52..f43a2b6c7 100644 --- a/src/Database/Validator/Index.php +++ b/src/Database/Validator/Index.php @@ -125,14 +125,15 @@ public function checkArrayIndex(Document $index): bool $arrayAttributes = []; foreach ($attributes as $key => $attribute) { $attribute = $this->attributes[\strtolower($attribute)] ?? new Document(); + var_dump($attribute); + if($attribute->getAttribute('array') === true){ // Database::INDEX_UNIQUE Is not allowed! since mariaDB VS MySQL makes the unique Different on values if(!in_array($index->getAttribute('type'), [Database::INDEX_ARRAY, Database::INDEX_KEY])){ - $this->message = 'Invalid "' . ucfirst($index->getAttribute('type')) . '" index on array attributes'; + $this->message = ucfirst($index->getAttribute('type')) . '" index is forbidden on array attributes'; return false; } - var_dump($attribute); $arrayAttributes[] = $attribute->getAttribute('key', ''); if(count($arrayAttributes) > 1){ $this->message = 'Only a single index can be created on array attributes found "' . implode(',', $arrayAttributes) . '"'; @@ -173,7 +174,7 @@ public function checkIndexLength(Document $index): bool } if(!$isArray && $attribute->getAttribute('type') !== Database::VAR_STRING && !empty($lengths[$attributePosition])){ - $this->message = 'Key part length are forbidden on "' . $attribute->getAttribute('type') . '" data-type'; + $this->message = 'Key part length are forbidden on "' . $attribute->getAttribute('type') . '" data-type for "' . $attributeName . '"'; return false; } @@ -181,8 +182,6 @@ public function checkIndexLength(Document $index): bool case Database::VAR_STRING: $attributeSize = $attribute->getAttribute('size', 0); $indexLength = $lengths[$attributePosition] ?? $attributeSize; - var_dump($attributeName); - var_dump($indexLength); break; case Database::VAR_FLOAT: $attributeSize = 2; // 8 bytes / 4 mb4 @@ -190,7 +189,6 @@ public function checkIndexLength(Document $index): bool break; default: $attributeSize = 1; // 4 bytes / 4 mb4 - // $attributeSize = $attribute->getAttribute('size', 1); // 4 bytes / 4 mb4 $indexLength = 1; break; } diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 6fc337fd1..dd7d47843 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1673,16 +1673,6 @@ public function testArrayAttribute(): void $this->assertEquals('Invalid document structure: Attribute "age" has invalid type. Value must be a valid integer', $e->getMessage()); } - try { - static::getDatabase()->createDocument($collection, new Document([ - 'booleans' => [false], - 'age' => -1, - ])); - $this->fail('Failed to throw exception'); - } catch(Throwable $e) { - //$this->assertEquals('Should fail since it is Signed = false!!!!', $e->getMessage()); - } - static::getDatabase()->createDocument($collection, new Document([ '$id' => 'joe', '$permissions' => $permissions, @@ -1701,7 +1691,7 @@ public function testArrayAttribute(): void static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_FULLTEXT, ['names']); $this->fail('Failed to throw exception'); } catch(Throwable $e) { - $this->assertEquals('Invalid "Fulltext" index on array attributes', $e->getMessage()); + $this->assertEquals('Fulltext" index is forbidden on array attributes', $e->getMessage()); } try { @@ -1753,17 +1743,17 @@ public function testArrayAttribute(): void static::getDatabase()->createIndex($collection, 'indx', Database::INDEX_KEY, ['age', 'names'], [10, 255], []); $this->fail('Failed to throw exception'); } catch(Throwable $e) { - $this->assertEquals('Key part length are forbidden on "integer" data-type', $e->getMessage()); + $this->assertEquals('Key part length are forbidden on "integer" data-type for "age"', $e->getMessage()); } $this->assertTrue(static::getDatabase()->createIndex($collection, 'indx_names', Database::INDEX_KEY, ['names'], [255], [])); $this->assertTrue(static::getDatabase()->createIndex($collection, 'indx_age_names1', Database::INDEX_KEY, ['age', 'names'], [null, 255], [])); $this->assertTrue(static::getDatabase()->createIndex($collection, 'indx_age_names2', Database::INDEX_KEY, ['age', 'booleans'], [0, 255], [])); - // $this->assertTrue(static::getDatabase()->createIndex($collection, 'indx_age_names', Database::INDEX_ARRAY, ['age', 'names'], [255, 255], [])); - $this->assertEquals(true,false); + $this->assertTrue(static::getDatabase()->createIndex($collection, 'test', Database::INDEX_ARRAY, ['names'], [255], [])); + $this->assertEquals(true,false); if ($this->getDatabase()->getAdapter()->getSupportForQueryContains()) { @@ -1777,7 +1767,6 @@ public function testArrayAttribute(): void ]); $this->assertCount(1, $documents); - $documents = static::getDatabase()->find($collection, [ Query::contains('active', [false]) ]); @@ -1787,7 +1776,6 @@ public function testArrayAttribute(): void } $this->assertEquals(true,false); - } /**