From 45c81234534de38b7973e2000ae09945e4ddd33b Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 29 May 2023 10:34:06 +0300 Subject: [PATCH 01/11] getSQLValue move to SQL.php --- src/Database/Adapter/SQL.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 5b585b82b..f0ea25ba1 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -745,6 +745,33 @@ protected function getSQLPlaceholder(Query $query): string return \md5($json); } + public function escapeWildcards(string $value): string + { + $wildcards = [ + '%', + '_', + '[', + ']', + '^', + '-', + '.', + '*', + '+', + '?', + '(', + ')', + '{', + '}', + '|' + ]; + + foreach ($wildcards as $wildcard) { + $value = \str_replace($wildcard, "\\$wildcard", $value); + } + + return $value; + } + protected function getSQLValue(string $method, mixed $value): mixed { switch ($method) { From 02c0cc85534b57e4b0f2523a5fc0abc8e6a76330 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 30 May 2023 15:12:12 +0300 Subject: [PATCH 02/11] Bind value changing --- phpunit.xml | 2 +- src/Database/Adapter/MariaDB.php | 10 +---- src/Database/Adapter/Postgres.php | 14 +++++-- src/Database/Adapter/SQL.php | 61 +++++++++++++------------------ 4 files changed, 38 insertions(+), 49 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 31b947dd6..3833748e0 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/ diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index a676f9acb..e507ffa55 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1228,15 +1228,7 @@ protected function getSQLCondition(Query $query): string switch ($query->getMethod()) { case Query::TYPE_SEARCH: - /** - * Replace reserved chars with space. - */ - $value = trim(str_replace(['@', '+', '-', '*'], ' ', $query->getValues()[0])); - /** - * Prepend wildcard by default on the back. - */ - $value = $this->getSQLValue($query->getMethod(), $value); - return 'MATCH('.$attribute.') AGAINST ('.$this->getPDO()->quote($value).' IN BOOLEAN MODE)'; + return "MATCH({$attribute}) AGAINST (:{$placeholder}_0 IN BOOLEAN MODE)"; case Query::TYPE_BETWEEN: return "table_main.{$attribute} BETWEEN :{$placeholder}_0 AND :{$placeholder}_1"; diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index fb569c85f..7be8b1d7b 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1236,9 +1236,7 @@ protected function getSQLCondition(Query $query): string switch ($query->getMethod()) { case Query::TYPE_SEARCH: - $value = trim(str_replace(['@', '+', '-', '*', '.'], '|', $query->getValues()[0])); - $value = $this->getSQLValue($query->getMethod(), $value); - return "to_tsvector(regexp_replace({$attribute}, '[^\w]+',' ','g')) @@ to_tsquery(trim(REGEXP_REPLACE({$value}, '\|+','|','g'),'|'))"; + return "to_tsvector(regexp_replace({$attribute}, '[^\w]+',' ','g')) @@ to_tsquery(trim(REGEXP_REPLACE(:{$placeholder}_0, '\|+','|','g'),'|'))"; case Query::TYPE_BETWEEN: return "table_main.{$attribute} BETWEEN :{$placeholder}_0 AND :{$placeholder}_1"; @@ -1257,6 +1255,16 @@ protected function getSQLCondition(Query $query): string } } + /** + * @param string $value + * @return string + */ + protected function getFulltextValue(string $value): string + { + $value = str_replace(['@', '+', '-', '*', '.'], '|', $value); + return trim($value); + } + /** * Get SQL Type * diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index f0ea25ba1..5684da0d8 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -681,20 +681,42 @@ public function getSupportForRelationships(): bool * @param mixed $stmt * @param Query $query * @return void + * @throws Exception */ protected function bindConditionValue(mixed $stmt, Query $query): void { - if (in_array($query->getMethod(), [Query::TYPE_SEARCH, Query::TYPE_SELECT])) { + if ($query->getMethod() == Query::TYPE_SELECT) { return; } foreach ($query->getValues() as $key => $value) { + $value = match ($query->getMethod()) { + Query::TYPE_STARTS_WITH => $this->escapeWildcards($value) . '%', + Query::TYPE_ENDS_WITH => '%' . $this->escapeWildcards($value), + Query::TYPE_SEARCH => $this->getFulltextValue($value), + default => $value + }; + $placeholder = $this->getSQLPlaceholder($query).'_'.$key; - $value = $this->getSQLValue($query->getMethod(), $value); $stmt->bindValue($placeholder, $value, $this->getPDOType($value)); } } + /** + * @param string $value + * @return string + */ + protected function getFulltextValue(string $value): string + { + /** Replace reserved chars with space. */ + $value = str_replace(explode(',', '@,+,-,*,),(,<,>,@,~'), ' ', $value); + + /** Prepend wildcard by default on the back. */ + $value .= '*'; + + return trim($value); + } + /** * Get SQL Operator * @@ -747,23 +769,7 @@ protected function getSQLPlaceholder(Query $query): string public function escapeWildcards(string $value): string { - $wildcards = [ - '%', - '_', - '[', - ']', - '^', - '-', - '.', - '*', - '+', - '?', - '(', - ')', - '{', - '}', - '|' - ]; + $wildcards = ['%', '_', '[', ']', '^', '-', '.', '*', '+', '?', '(', ')', '{', '}', '|']; foreach ($wildcards as $wildcard) { $value = \str_replace($wildcard, "\\$wildcard", $value); @@ -772,23 +778,6 @@ public function escapeWildcards(string $value): string return $value; } - protected function getSQLValue(string $method, mixed $value): mixed - { - switch ($method) { - case Query::TYPE_STARTS_WITH: - $value = $this->escapeWildcards($value); - return "$value%"; - case Query::TYPE_ENDS_WITH: - $value = $this->escapeWildcards($value); - return "%$value"; - case Query::TYPE_SEARCH: - $value = $this->escapeWildcards($value); - return "'$value*'"; - default: - return $value; - } - } - /** * Get SQL Index Type * From 7a2b5dcaf50061eaea032e983cd95da683eeb9dd Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 31 May 2023 17:11:20 +0300 Subject: [PATCH 03/11] fulltext --- src/Database/Adapter/MariaDB.php | 2 +- src/Database/Adapter/Postgres.php | 14 +- src/Database/Adapter/SQL.php | 17 +- tests/Database/Base.php | 286 ++++++++++++++++++------------ 4 files changed, 200 insertions(+), 119 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index e507ffa55..e99cb12f0 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1228,7 +1228,7 @@ protected function getSQLCondition(Query $query): string switch ($query->getMethod()) { case Query::TYPE_SEARCH: - return "MATCH({$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"; diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 7be8b1d7b..d8acf0b85 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1236,7 +1236,7 @@ protected function getSQLCondition(Query $query): string switch ($query->getMethod()) { case Query::TYPE_SEARCH: - return "to_tsvector(regexp_replace({$attribute}, '[^\w]+',' ','g')) @@ to_tsquery(trim(REGEXP_REPLACE(:{$placeholder}_0, '\|+','|','g'),'|'))"; + return "to_tsvector(regexp_replace({$attribute}, '[^\w]+',' ','g')) @@ websearch_to_tsquery(:{$placeholder}_0)"; case Query::TYPE_BETWEEN: return "table_main.{$attribute} BETWEEN :{$placeholder}_0 AND :{$placeholder}_1"; @@ -1261,8 +1261,16 @@ protected function getSQLCondition(Query $query): string */ protected function getFulltextValue(string $value): string { - $value = str_replace(['@', '+', '-', '*', '.'], '|', $value); - return trim($value); + $exact = str_ends_with($value, '"') && str_starts_with($value, '"'); + $value = str_replace(['@', '+', '-', '*', '.', "'", '"'], ' ', $value); + $value = preg_replace('/\s+/', ' ', $value); // Remove multiple whitespaces + $value = trim($value); + + if (!$exact) { + $value = str_replace(' ', ' or ', $value); + } + + return "'" . $value . "'"; } /** diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 5684da0d8..a6ef3de56 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -708,13 +708,22 @@ protected function bindConditionValue(mixed $stmt, Query $query): void */ protected function getFulltextValue(string $value): string { + $exact = str_ends_with($value, '"') && str_starts_with($value, '"'); + /** Replace reserved chars with space. */ - $value = str_replace(explode(',', '@,+,-,*,),(,<,>,@,~'), ' ', $value); + $specialChars = '@,+,-,*,),(,<,>,~,"'; + $value = str_replace(explode(',', $specialChars), ' ', $value); + $value = preg_replace('/\s+/', ' ', $value); // Remove multiple whitespaces + $value = trim($value); - /** Prepend wildcard by default on the back. */ - $value .= '*'; + if ($exact) { + $value = '"' . $value . '"'; + } else { + /** Prepend wildcard by default on the back. */ + $value .= '*'; + } - return trim($value); + return $value; } /** diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 9697fabb7..b3cdd9a2e 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1416,6 +1416,70 @@ public function testFindFulltext(): void $this->assertEquals(true, true); // Test must do an assertion } + public function testFindFulltextSpecialChars(): void + { + if (!static::getDatabase()->getAdapter()->getSupportForFulltextIndex()) { + $this->expectNotToPerformAssertions(); + return; + } + + $collection = 'full_text'; + static::getDatabase()->createCollection($collection, permissions: [ + Permission::create(Role::any()), + Permission::update(Role::users()) + ]); + + $this->assertTrue(static::getDatabase()->createAttribute($collection, 'ft', Database::VAR_STRING, 128, true)); + $this->assertTrue(static::getDatabase()->createIndex($collection, 'ft-index', Database::INDEX_FULLTEXT, ['ft'])); + + static::getDatabase()->createDocument($collection, new Document([ + '$permissions' => [Permission::read(Role::any())], + 'ft' => 'Alf: chapter_4' + ])); + + $documents = static::getDatabase()->find($collection, [ + Query::search('ft', 'chapter_4'), + ]); + $this->assertEquals(1, count($documents)); + + static::getDatabase()->createDocument($collection, new Document([ + '$permissions' => [Permission::read(Role::any())], + 'ft' => 'al@ba.io +-*)(<>~' + ])); + + $documents = static::getDatabase()->find($collection, [ + Query::search('ft', 'al@ba.io'), // === al ba io* + ]); + + if (static::getDatabase()->getAdapter()->getSupportForFulltextWildcardIndex()) { + $this->assertEquals(0, count($documents)); + } else { + $this->assertEquals(1, count($documents)); + } + + static::getDatabase()->createDocument($collection, new Document([ + '$permissions' => [Permission::read(Role::any())], + 'ft' => 'donald duck' + ])); + + static::getDatabase()->createDocument($collection, new Document([ + '$permissions' => [Permission::read(Role::any())], + 'ft' => 'donald trump' + ])); + + $documents = static::getDatabase()->find($collection, [ + Query::search('ft', 'donald trump'), + ]); + + $this->assertEquals(2, count($documents)); + + $documents = static::getDatabase()->find($collection, [ + Query::search('ft', '"donald trump"'), // Exact match + ]); + + $this->assertEquals(1, count($documents)); + } + public function testFindMultipleConditions(): void { /** @@ -3551,117 +3615,117 @@ public function testCreateDateTimeAttributeFailure(): void $this->expectException(Exception::class); static::getDatabase()->createAttribute('datetime_fail', 'date_fail', Database::VAR_DATETIME, 0, false); } - - public function testKeywords(): void - { - $database = static::getDatabase(); - $keywords = $database->getKeywords(); - - // Collection name tests - $attributes = [ - new Document([ - '$id' => ID::custom('attribute1'), - 'type' => Database::VAR_STRING, - 'size' => 256, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - ]; - - $indexes = [ - new Document([ - '$id' => ID::custom('index1'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['attribute1'], - 'lengths' => [256], - 'orders' => ['ASC'], - ]), - ]; - - foreach ($keywords as $keyword) { - $collection = $database->createCollection($keyword, $attributes, $indexes); - $this->assertEquals($keyword, $collection->getId()); - - $document = $database->createDocument($keyword, new Document([ - '$permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - '$id' => ID::custom('helloWorld'), - 'attribute1' => 'Hello World', - ])); - $this->assertEquals('helloWorld', $document->getId()); - - $document = $database->getDocument($keyword, 'helloWorld'); - $this->assertEquals('helloWorld', $document->getId()); - - $documents = $database->find($keyword); - $this->assertCount(1, $documents); - $this->assertEquals('helloWorld', $documents[0]->getId()); - - $collection = $database->deleteCollection($keyword); - $this->assertTrue($collection); - } - - // TODO: updateCollection name tests - - // Attribute name tests - foreach ($keywords as $keyword) { - $collectionName = 'rk' . $keyword; // rk is short-hand for reserved-keyword. We do this sicne there are some limits (64 chars max) - - $collection = $database->createCollection($collectionName); - $this->assertEquals($collectionName, $collection->getId()); - - $attribute = static::getDatabase()->createAttribute($collectionName, $keyword, Database::VAR_STRING, 128, true); - $this->assertEquals(true, $attribute); - - $document = new Document([ - '$permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - '$id' => 'reservedKeyDocument' - ]); - $document->setAttribute($keyword, 'Reserved:' . $keyword); - - $document = $database->createDocument($collectionName, $document); - $this->assertEquals('reservedKeyDocument', $document->getId()); - $this->assertEquals('Reserved:' . $keyword, $document->getAttribute($keyword)); - - $document = $database->getDocument($collectionName, 'reservedKeyDocument'); - $this->assertEquals('reservedKeyDocument', $document->getId()); - $this->assertEquals('Reserved:' . $keyword, $document->getAttribute($keyword)); - - $documents = $database->find($collectionName); - $this->assertCount(1, $documents); - $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); - $this->assertEquals('Reserved:' . $keyword, $documents[0]->getAttribute($keyword)); - - $documents = $database->find($collectionName, [Query::equal($keyword, ["Reserved:${keyword}"])]); - $this->assertCount(1, $documents); - $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); - - $documents = $database->find($collectionName, [ - Query::orderDesc($keyword) - ]); - $this->assertCount(1, $documents); - $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); - - - $collection = $database->deleteCollection($collectionName); - $this->assertTrue($collection); - - // TODO: updateAttribute name tests - } - - // TODO: Index name tests - } +// +// public function testKeywords(): void +// { +// $database = static::getDatabase(); +// $keywords = $database->getKeywords(); +// +// // Collection name tests +// $attributes = [ +// new Document([ +// '$id' => ID::custom('attribute1'), +// 'type' => Database::VAR_STRING, +// 'size' => 256, +// 'required' => false, +// 'signed' => true, +// 'array' => false, +// 'filters' => [], +// ]), +// ]; +// +// $indexes = [ +// new Document([ +// '$id' => ID::custom('index1'), +// 'type' => Database::INDEX_KEY, +// 'attributes' => ['attribute1'], +// 'lengths' => [256], +// 'orders' => ['ASC'], +// ]), +// ]; +// +// foreach ($keywords as $keyword) { +// $collection = $database->createCollection($keyword, $attributes, $indexes); +// $this->assertEquals($keyword, $collection->getId()); +// +// $document = $database->createDocument($keyword, new Document([ +// '$permissions' => [ +// Permission::read(Role::any()), +// Permission::create(Role::any()), +// Permission::update(Role::any()), +// Permission::delete(Role::any()), +// ], +// '$id' => ID::custom('helloWorld'), +// 'attribute1' => 'Hello World', +// ])); +// $this->assertEquals('helloWorld', $document->getId()); +// +// $document = $database->getDocument($keyword, 'helloWorld'); +// $this->assertEquals('helloWorld', $document->getId()); +// +// $documents = $database->find($keyword); +// $this->assertCount(1, $documents); +// $this->assertEquals('helloWorld', $documents[0]->getId()); +// +// $collection = $database->deleteCollection($keyword); +// $this->assertTrue($collection); +// } +// +// // TODO: updateCollection name tests +// +// // Attribute name tests +// foreach ($keywords as $keyword) { +// $collectionName = 'rk' . $keyword; // rk is short-hand for reserved-keyword. We do this sicne there are some limits (64 chars max) +// +// $collection = $database->createCollection($collectionName); +// $this->assertEquals($collectionName, $collection->getId()); +// +// $attribute = static::getDatabase()->createAttribute($collectionName, $keyword, Database::VAR_STRING, 128, true); +// $this->assertEquals(true, $attribute); +// +// $document = new Document([ +// '$permissions' => [ +// Permission::read(Role::any()), +// Permission::create(Role::any()), +// Permission::update(Role::any()), +// Permission::delete(Role::any()), +// ], +// '$id' => 'reservedKeyDocument' +// ]); +// $document->setAttribute($keyword, 'Reserved:' . $keyword); +// +// $document = $database->createDocument($collectionName, $document); +// $this->assertEquals('reservedKeyDocument', $document->getId()); +// $this->assertEquals('Reserved:' . $keyword, $document->getAttribute($keyword)); +// +// $document = $database->getDocument($collectionName, 'reservedKeyDocument'); +// $this->assertEquals('reservedKeyDocument', $document->getId()); +// $this->assertEquals('Reserved:' . $keyword, $document->getAttribute($keyword)); +// +// $documents = $database->find($collectionName); +// $this->assertCount(1, $documents); +// $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); +// $this->assertEquals('Reserved:' . $keyword, $documents[0]->getAttribute($keyword)); +// +// $documents = $database->find($collectionName, [Query::equal($keyword, ["Reserved:${keyword}"])]); +// $this->assertCount(1, $documents); +// $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); +// +// $documents = $database->find($collectionName, [ +// Query::orderDesc($keyword) +// ]); +// $this->assertCount(1, $documents); +// $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); +// +// +// $collection = $database->deleteCollection($collectionName); +// $this->assertTrue($collection); +// +// // TODO: updateAttribute name tests +// } +// +// // TODO: Index name tests +// } public function testWritePermissions(): void { From 07b315b4151e7c23f930c2b457c62ad94dfc5edb Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 31 May 2023 20:16:56 +0300 Subject: [PATCH 04/11] stopOnFailure --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 3833748e0..31b947dd6 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true"> + stopOnFailure="false"> ./tests/ From 72537691d4fe255cbd0ee9a8bb66c07097868f8b Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 31 May 2023 20:50:41 +0300 Subject: [PATCH 05/11] Some more tests --- tests/Database/Base.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index b3cdd9a2e..fc847aeb7 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1434,7 +1434,7 @@ public function testFindFulltextSpecialChars(): void static::getDatabase()->createDocument($collection, new Document([ '$permissions' => [Permission::read(Role::any())], - 'ft' => 'Alf: chapter_4' + 'ft' => 'Alf: chapter_4@nasa.com' ])); $documents = static::getDatabase()->find($collection, [ @@ -1442,6 +1442,11 @@ public function testFindFulltextSpecialChars(): void ]); $this->assertEquals(1, count($documents)); + $documents = static::getDatabase()->find($collection, [ + Query::search('ft', 'chapter_'), + ]); + $this->assertEquals(1, count($documents)); + static::getDatabase()->createDocument($collection, new Document([ '$permissions' => [Permission::read(Role::any())], 'ft' => 'al@ba.io +-*)(<>~' @@ -1458,26 +1463,31 @@ public function testFindFulltextSpecialChars(): void } static::getDatabase()->createDocument($collection, new Document([ + '$id' => 'donald duck', '$permissions' => [Permission::read(Role::any())], 'ft' => 'donald duck' ])); static::getDatabase()->createDocument($collection, new Document([ + '$id' => 'donald trump', '$permissions' => [Permission::read(Role::any())], 'ft' => 'donald trump' ])); $documents = static::getDatabase()->find($collection, [ Query::search('ft', 'donald trump'), + Query::orderAsc('ft'), ]); - $this->assertEquals(2, count($documents)); + $this->assertEquals('donald duck', $documents[0]->getId()); + $this->assertEquals('donald trump', $documents[1]->getId()); $documents = static::getDatabase()->find($collection, [ Query::search('ft', '"donald trump"'), // Exact match ]); $this->assertEquals(1, count($documents)); + $this->assertEquals('donald trump', $documents[0]->getId()); } public function testFindMultipleConditions(): void From 1b551a620ea26173a1dfbe09819f835385db9e50 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 31 May 2023 20:58:08 +0300 Subject: [PATCH 06/11] Some more tests --- tests/Database/Base.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index fc847aeb7..499c98c9e 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1441,12 +1441,7 @@ public function testFindFulltextSpecialChars(): void Query::search('ft', 'chapter_4'), ]); $this->assertEquals(1, count($documents)); - - $documents = static::getDatabase()->find($collection, [ - Query::search('ft', 'chapter_'), - ]); - $this->assertEquals(1, count($documents)); - + static::getDatabase()->createDocument($collection, new Document([ '$permissions' => [Permission::read(Role::any())], 'ft' => 'al@ba.io +-*)(<>~' From 49b55b1f2eb9490a7a15c0b0fac356543bbc544e Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 31 May 2023 22:59:58 +0300 Subject: [PATCH 07/11] uncomment testKeywords --- tests/Database/Base.php | 229 ++++++++++++++++++++-------------------- 1 file changed, 112 insertions(+), 117 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 499c98c9e..a3fe0cd61 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1441,7 +1441,7 @@ public function testFindFulltextSpecialChars(): void Query::search('ft', 'chapter_4'), ]); $this->assertEquals(1, count($documents)); - + static::getDatabase()->createDocument($collection, new Document([ '$permissions' => [Permission::read(Role::any())], 'ft' => 'al@ba.io +-*)(<>~' @@ -1458,13 +1458,11 @@ public function testFindFulltextSpecialChars(): void } static::getDatabase()->createDocument($collection, new Document([ - '$id' => 'donald duck', '$permissions' => [Permission::read(Role::any())], 'ft' => 'donald duck' ])); static::getDatabase()->createDocument($collection, new Document([ - '$id' => 'donald trump', '$permissions' => [Permission::read(Role::any())], 'ft' => 'donald trump' ])); @@ -1474,15 +1472,12 @@ public function testFindFulltextSpecialChars(): void Query::orderAsc('ft'), ]); $this->assertEquals(2, count($documents)); - $this->assertEquals('donald duck', $documents[0]->getId()); - $this->assertEquals('donald trump', $documents[1]->getId()); $documents = static::getDatabase()->find($collection, [ Query::search('ft', '"donald trump"'), // Exact match ]); $this->assertEquals(1, count($documents)); - $this->assertEquals('donald trump', $documents[0]->getId()); } public function testFindMultipleConditions(): void @@ -3620,117 +3615,117 @@ public function testCreateDateTimeAttributeFailure(): void $this->expectException(Exception::class); static::getDatabase()->createAttribute('datetime_fail', 'date_fail', Database::VAR_DATETIME, 0, false); } -// -// public function testKeywords(): void -// { -// $database = static::getDatabase(); -// $keywords = $database->getKeywords(); -// -// // Collection name tests -// $attributes = [ -// new Document([ -// '$id' => ID::custom('attribute1'), -// 'type' => Database::VAR_STRING, -// 'size' => 256, -// 'required' => false, -// 'signed' => true, -// 'array' => false, -// 'filters' => [], -// ]), -// ]; -// -// $indexes = [ -// new Document([ -// '$id' => ID::custom('index1'), -// 'type' => Database::INDEX_KEY, -// 'attributes' => ['attribute1'], -// 'lengths' => [256], -// 'orders' => ['ASC'], -// ]), -// ]; -// -// foreach ($keywords as $keyword) { -// $collection = $database->createCollection($keyword, $attributes, $indexes); -// $this->assertEquals($keyword, $collection->getId()); -// -// $document = $database->createDocument($keyword, new Document([ -// '$permissions' => [ -// Permission::read(Role::any()), -// Permission::create(Role::any()), -// Permission::update(Role::any()), -// Permission::delete(Role::any()), -// ], -// '$id' => ID::custom('helloWorld'), -// 'attribute1' => 'Hello World', -// ])); -// $this->assertEquals('helloWorld', $document->getId()); -// -// $document = $database->getDocument($keyword, 'helloWorld'); -// $this->assertEquals('helloWorld', $document->getId()); -// -// $documents = $database->find($keyword); -// $this->assertCount(1, $documents); -// $this->assertEquals('helloWorld', $documents[0]->getId()); -// -// $collection = $database->deleteCollection($keyword); -// $this->assertTrue($collection); -// } -// -// // TODO: updateCollection name tests -// -// // Attribute name tests -// foreach ($keywords as $keyword) { -// $collectionName = 'rk' . $keyword; // rk is short-hand for reserved-keyword. We do this sicne there are some limits (64 chars max) -// -// $collection = $database->createCollection($collectionName); -// $this->assertEquals($collectionName, $collection->getId()); -// -// $attribute = static::getDatabase()->createAttribute($collectionName, $keyword, Database::VAR_STRING, 128, true); -// $this->assertEquals(true, $attribute); -// -// $document = new Document([ -// '$permissions' => [ -// Permission::read(Role::any()), -// Permission::create(Role::any()), -// Permission::update(Role::any()), -// Permission::delete(Role::any()), -// ], -// '$id' => 'reservedKeyDocument' -// ]); -// $document->setAttribute($keyword, 'Reserved:' . $keyword); -// -// $document = $database->createDocument($collectionName, $document); -// $this->assertEquals('reservedKeyDocument', $document->getId()); -// $this->assertEquals('Reserved:' . $keyword, $document->getAttribute($keyword)); -// -// $document = $database->getDocument($collectionName, 'reservedKeyDocument'); -// $this->assertEquals('reservedKeyDocument', $document->getId()); -// $this->assertEquals('Reserved:' . $keyword, $document->getAttribute($keyword)); -// -// $documents = $database->find($collectionName); -// $this->assertCount(1, $documents); -// $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); -// $this->assertEquals('Reserved:' . $keyword, $documents[0]->getAttribute($keyword)); -// -// $documents = $database->find($collectionName, [Query::equal($keyword, ["Reserved:${keyword}"])]); -// $this->assertCount(1, $documents); -// $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); -// -// $documents = $database->find($collectionName, [ -// Query::orderDesc($keyword) -// ]); -// $this->assertCount(1, $documents); -// $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); -// -// -// $collection = $database->deleteCollection($collectionName); -// $this->assertTrue($collection); -// -// // TODO: updateAttribute name tests -// } -// -// // TODO: Index name tests -// } + + public function testKeywords(): void + { + $database = static::getDatabase(); + $keywords = $database->getKeywords(); + + // Collection name tests + $attributes = [ + new Document([ + '$id' => ID::custom('attribute1'), + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => false, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]), + ]; + + $indexes = [ + new Document([ + '$id' => ID::custom('index1'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['attribute1'], + 'lengths' => [256], + 'orders' => ['ASC'], + ]), + ]; + + foreach ($keywords as $keyword) { + $collection = $database->createCollection($keyword, $attributes, $indexes); + $this->assertEquals($keyword, $collection->getId()); + + $document = $database->createDocument($keyword, new Document([ + '$permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + '$id' => ID::custom('helloWorld'), + 'attribute1' => 'Hello World', + ])); + $this->assertEquals('helloWorld', $document->getId()); + + $document = $database->getDocument($keyword, 'helloWorld'); + $this->assertEquals('helloWorld', $document->getId()); + + $documents = $database->find($keyword); + $this->assertCount(1, $documents); + $this->assertEquals('helloWorld', $documents[0]->getId()); + + $collection = $database->deleteCollection($keyword); + $this->assertTrue($collection); + } + + // TODO: updateCollection name tests + + // Attribute name tests + foreach ($keywords as $keyword) { + $collectionName = 'rk' . $keyword; // rk is short-hand for reserved-keyword. We do this sicne there are some limits (64 chars max) + + $collection = $database->createCollection($collectionName); + $this->assertEquals($collectionName, $collection->getId()); + + $attribute = static::getDatabase()->createAttribute($collectionName, $keyword, Database::VAR_STRING, 128, true); + $this->assertEquals(true, $attribute); + + $document = new Document([ + '$permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + '$id' => 'reservedKeyDocument' + ]); + $document->setAttribute($keyword, 'Reserved:' . $keyword); + + $document = $database->createDocument($collectionName, $document); + $this->assertEquals('reservedKeyDocument', $document->getId()); + $this->assertEquals('Reserved:' . $keyword, $document->getAttribute($keyword)); + + $document = $database->getDocument($collectionName, 'reservedKeyDocument'); + $this->assertEquals('reservedKeyDocument', $document->getId()); + $this->assertEquals('Reserved:' . $keyword, $document->getAttribute($keyword)); + + $documents = $database->find($collectionName); + $this->assertCount(1, $documents); + $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); + $this->assertEquals('Reserved:' . $keyword, $documents[0]->getAttribute($keyword)); + + $documents = $database->find($collectionName, [Query::equal($keyword, ["Reserved:${keyword}"])]); + $this->assertCount(1, $documents); + $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); + + $documents = $database->find($collectionName, [ + Query::orderDesc($keyword) + ]); + $this->assertCount(1, $documents); + $this->assertEquals('reservedKeyDocument', $documents[0]->getId()); + + + $collection = $database->deleteCollection($collectionName); + $this->assertTrue($collection); + + // TODO: updateAttribute name tests + } + + // TODO: Index name tests + } public function testWritePermissions(): void { From 0cf5565a2db1e3fff0292c4e7670b1e1a721ef0a Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 4 Jun 2023 09:41:52 +0300 Subject: [PATCH 08/11] addArticle change --- bin/tasks/load.php | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/bin/tasks/load.php b/bin/tasks/load.php index 2a04a9290..54b83c510 100644 --- a/bin/tasks/load.php +++ b/bin/tasks/load.php @@ -230,7 +230,13 @@ function createSchema(Database $database): void $database->delete($database->getDefaultDatabase()); } $database->create(); - $database->createCollection('articles'); + + 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', 'text', Database::VAR_STRING, 5000, true); @@ -246,20 +252,20 @@ function addArticle($database, Generator $faker): void // Three random users out of 10,000 get mutate access '$permissions' => [ - Permission::read(Role::user($faker->randomNumber(4))), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::create(Role::user($faker->randomNumber(4))), - Permission::create(Role::user($faker->randomNumber(4))), - Permission::create(Role::user($faker->randomNumber(4))), - Permission::update(Role::user($faker->randomNumber(4))), - Permission::update(Role::user($faker->randomNumber(4))), - Permission::update(Role::user($faker->randomNumber(4))), - Permission::delete(Role::user($faker->randomNumber(4))), - Permission::delete(Role::user($faker->randomNumber(4))), - Permission::delete(Role::user($faker->randomNumber(4))), + Permission::read(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::read(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::read(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::read(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::read(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::create(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::create(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::create(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::update(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::update(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::update(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::delete(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::delete(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), + Permission::delete(Role::user('6477a84d4cf9a381' . $faker->randomNumber(4))), ], 'author' => $faker->name(), 'created' => \Utopia\Database\DateTime::format($faker->dateTime()), From b7542133f84baf055c93675fc8b978f622a96171 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 11 Jun 2023 12:36:08 +0300 Subject: [PATCH 09/11] composer.lock --- composer.lock | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/composer.lock b/composer.lock index 05ea6441e..767d445f7 100644 --- a/composer.lock +++ b/composer.lock @@ -336,16 +336,16 @@ }, { "name": "utopia-php/framework", - "version": "0.28.2", + "version": "0.28.4", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "bc0144ff3983afa6724c43f2ce578fdbceec21f9" + "reference": "98c5469efe195aeecc63745dbf8e2f357f8cedac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/bc0144ff3983afa6724c43f2ce578fdbceec21f9", - "reference": "bc0144ff3983afa6724c43f2ce578fdbceec21f9", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/98c5469efe195aeecc63745dbf8e2f357f8cedac", + "reference": "98c5469efe195aeecc63745dbf8e2f357f8cedac", "shasum": "" }, "require": { @@ -375,9 +375,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.28.2" + "source": "https://github.com/utopia-php/framework/tree/0.28.4" }, - "time": "2023-05-30T06:47:57+00:00" + "time": "2023-06-03T14:09:22+00:00" }, { "name": "utopia-php/mongo", @@ -907,16 +907,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.15", + "version": "1.10.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd" + "reference": "52b6416c579663eebdd2f1d97df21971daf3b43f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd", - "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52b6416c579663eebdd2f1d97df21971daf3b43f", + "reference": "52b6416c579663eebdd2f1d97df21971daf3b43f", "shasum": "" }, "require": { @@ -965,7 +965,7 @@ "type": "tidelift" } ], - "time": "2023-05-09T15:28:01+00:00" + "time": "2023-06-07T22:00:43+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1287,16 +1287,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.8", + "version": "9.6.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e" + "reference": "a9aceaf20a682aeacf28d582654a1670d8826778" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/17d621b3aff84d0c8b62539e269e87d8d5baa76e", - "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a9aceaf20a682aeacf28d582654a1670d8826778", + "reference": "a9aceaf20a682aeacf28d582654a1670d8826778", "shasum": "" }, "require": { @@ -1370,7 +1370,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.8" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.9" }, "funding": [ { @@ -1386,7 +1386,7 @@ "type": "tidelift" } ], - "time": "2023-05-11T05:14:45+00:00" + "time": "2023-06-11T06:13:56+00:00" }, { "name": "psr/container", @@ -2495,16 +2495,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", - "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { @@ -2513,7 +2513,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -2542,7 +2542,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.2.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" }, "funding": [ { @@ -2558,7 +2558,7 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:25:55+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "theseer/tokenizer", From 1c8cb818895916cee67fe8f929aac3d0fca58e80 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 11 Jun 2023 12:56:09 +0300 Subject: [PATCH 10/11] fix load --- bin/tasks/load.php | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/bin/tasks/load.php b/bin/tasks/load.php index 20164d4e4..a75f03644 100644 --- a/bin/tasks/load.php +++ b/bin/tasks/load.php @@ -10,6 +10,7 @@ use Utopia\Database\Adapter\MySQL; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Validator\Authorization; use Utopia\Mongo\Client; use Swoole\Database\PDOConfig; use Swoole\Database\PDOPool; @@ -253,20 +254,20 @@ function addArticle($database, Generator $faker): void '$permissions' => [ Permission::read(Role::any()), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::read(Role::user($faker->randomNumber(4))), - Permission::create(Role::user($faker->randomNumber(4))), - Permission::create(Role::user($faker->randomNumber(4))), - Permission::create(Role::user($faker->randomNumber(4))), - Permission::update(Role::user($faker->randomNumber(4))), - Permission::update(Role::user($faker->randomNumber(4))), - Permission::update(Role::user($faker->randomNumber(4))), - Permission::delete(Role::user($faker->randomNumber(4))), - Permission::delete(Role::user($faker->randomNumber(4))), - Permission::delete(Role::user($faker->randomNumber(4))), + Permission::read(Role::user('abc123456789')), // Repeatable user + Permission::read(Role::user($faker->randomNumber(9))), + Permission::read(Role::user($faker->randomNumber(9))), + Permission::read(Role::user($faker->randomNumber(9))), + Permission::read(Role::user($faker->randomNumber(9))), + Permission::create(Role::user($faker->randomNumber(9))), + Permission::create(Role::user($faker->randomNumber(9))), + Permission::create(Role::user($faker->randomNumber(9))), + Permission::update(Role::user($faker->randomNumber(9))), + Permission::update(Role::user($faker->randomNumber(9))), + Permission::update(Role::user($faker->randomNumber(9))), + Permission::delete(Role::user($faker->randomNumber(9))), + Permission::delete(Role::user($faker->randomNumber(9))), + Permission::delete(Role::user($faker->randomNumber(9))), ], 'author' => $faker->name(), 'created' => \Utopia\Database\DateTime::format($faker->dateTime()), From 838e15583d289c6c2917660406251fec3ab0bc20 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 11 Jun 2023 13:12:26 +0300 Subject: [PATCH 11/11] Load --- bin/tasks/load.php | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/tasks/load.php b/bin/tasks/load.php index a75f03644..1a4804290 100644 --- a/bin/tasks/load.php +++ b/bin/tasks/load.php @@ -254,7 +254,6 @@ function addArticle($database, Generator $faker): void '$permissions' => [ Permission::read(Role::any()), - Permission::read(Role::user('abc123456789')), // Repeatable user Permission::read(Role::user($faker->randomNumber(9))), Permission::read(Role::user($faker->randomNumber(9))), Permission::read(Role::user($faker->randomNumber(9))),