diff --git a/src/Definition.php b/src/Definition.php index be87c3a..60ab363 100644 --- a/src/Definition.php +++ b/src/Definition.php @@ -31,4 +31,16 @@ class Definition { Custom::nonQuery(Query\Definition::ensureIndexOn($tableName, $indexName, $fields), []); } + + /** + * Create a full-document index on a table (PostgreSQL only) + * + * @param string $tableName The name of the table on which the document index should be created + * @param DocumentIndex $indexType The type of document index to create + * @throws DocumentException If the database mode is not PostgreSQL or if an error occurs creating the index + */ + public static function ensureDocumentIndex(string $tableName, DocumentIndex $indexType): void + { + Custom::nonQuery(Query\Definition::ensureDocumentIndexOn($tableName, $indexType), []); + } } diff --git a/src/DocumentIndex.php b/src/DocumentIndex.php new file mode 100644 index 0000000..0728657 --- /dev/null +++ b/src/DocumentIndex.php @@ -0,0 +1,15 @@ +, @?, @@ operators) */ + case Optimized; +} diff --git a/src/Query.php b/src/Query.php index 1bcbc08..51ed2e4 100644 --- a/src/Query.php +++ b/src/Query.php @@ -48,6 +48,36 @@ class Query return self::whereByFields([Field::EQ(Configuration::$idField, $docId ?? '', $paramName)]); } + /** + * Create a WHERE clause fragment to implement a JSON containment query (PostgreSQL only) + * + * @param string $paramName The name of the parameter (optional; defaults to `:criteria`) + * @return string The WHERE clause fragment for a JSON containment query + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function whereDataContains(string $paramName = ':criteria'): string + { + if (Configuration::$mode <> Mode::PgSQL) { + throw new DocumentException('JSON containment is only supported on PostgreSQL'); + } + return "data @> $paramName"; + } + + /** + * Create a WHERE clause fragment to implement a JSON Path match query (PostgreSQL only) + * + * @param string $paramName The name of the parameter (optional; defaults to `:path`) + * @return string The WHERE clause fragment for a JSON Path match query + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function whereJsonPathMatches(string $paramName = ':path'): string + { + if (Configuration::$mode <> Mode::PgSQL) { + throw new DocumentException('JSON Path matching is only supported on PostgreSQL'); + } + return "data @? $paramName::jsonpath"; + } + /** * Create an `INSERT` statement for a document * diff --git a/src/Query/Count.php b/src/Query/Count.php index 8727e96..7efed68 100644 --- a/src/Query/Count.php +++ b/src/Query/Count.php @@ -33,4 +33,28 @@ class Count { return self::all($tableName) . ' WHERE ' . Query::whereByFields($fields, $match); } + + /** + * Query to count matching documents using a JSON containment query (PostgreSQL only) + * + * @param string $tableName The name of the table in which documents should be counted + * @return string The query to count documents using a JSON containment query + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byContains(string $tableName): string + { + return self::all($tableName) . ' WHERE ' . Query::whereDataContains(); + } + + /** + * Query to count matching documents using a JSON Path match (PostgreSQL only) + * + * @param string $tableName The name of the table in which documents should be counted + * @return string The query to count documents using a JSON Path match + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byJsonPath(string $tableName): string + { + return self::all($tableName) . ' WHERE ' . Query::whereJsonPathMatches(); + } } diff --git a/src/Query/Definition.php b/src/Query/Definition.php index 6e584f5..6c94f01 100644 --- a/src/Query/Definition.php +++ b/src/Query/Definition.php @@ -2,7 +2,7 @@ namespace BitBadger\PDODocument\Query; -use BitBadger\PDODocument\{Configuration, DocumentException, Mode}; +use BitBadger\PDODocument\{Configuration, DocumentException, DocumentIndex, Mode}; /** * Queries to define tables and indexes @@ -68,4 +68,25 @@ class Definition { return str_replace('INDEX', 'UNIQUE INDEX', self::ensureIndexOn($tableName, 'key', [Configuration::$idField])); } + + /** + * Create a document-wide index on a table (PostgreSQL only) + * + * @param string $tableName The name of the table on which the document index should be created + * @param DocumentIndex $indexType The type of index to be created + * @return string The SQL statement to create an index on JSON documents in the specified table + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function ensureDocumentIndexOn(string $tableName, DocumentIndex $indexType): string + { + if (Configuration::$mode <> Mode::PgSQL) { + throw new DocumentException('Document indexes are only supported on PostgreSQL'); + } + [, $tbl] = self::splitSchemaAndTable($tableName); + $extraOps = match ($indexType) { + DocumentIndex::Full => '', + DocumentIndex::Optimized => ' jsonb_path_ops' + }; + return "CREATE INDEX IF NOT EXISTS idx_{$tbl}_document ON $tableName USING GIN (data$extraOps)"; + } } diff --git a/src/Query/Delete.php b/src/Query/Delete.php index 576378e..f043bd7 100644 --- a/src/Query/Delete.php +++ b/src/Query/Delete.php @@ -35,4 +35,28 @@ class Delete { return "DELETE FROM $tableName WHERE " . Query::whereByFields($fields, $match); } + + /** + * Query to delete documents using a JSON containment query (PostgreSQL only) + * + * @param string $tableName The name of the table from which documents should be deleted + * @return string The DELETE statement to delete documents via a JSON containment query + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byContains(string $tableName): string + { + return "DELETE FROM $tableName WHERE " . Query::whereDataContains(); + } + + /** + * Query to delete documents using a JSON Path match query (PostgreSQL only) + * + * @param string $tableName The name of the table from which documents should be deleted + * @return string The DELETE statement to delete documents via a JSON Path match + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byJsonPath(string $tableName): string + { + return "DELETE FROM $tableName WHERE " . Query::whereJsonPathMatches(); + } } diff --git a/src/Query/Exists.php b/src/Query/Exists.php index f2d07ee..f6eb305 100644 --- a/src/Query/Exists.php +++ b/src/Query/Exists.php @@ -47,4 +47,28 @@ class Exists { return self::query($tableName, Query::whereByFields($fields, $match)); } + + /** + * Query to determine if documents exist using a JSON containment query (PostgreSQL only) + * + * @param string $tableName The name of the table in which document existence should be checked + * @return string The query to determine document existence by a JSON containment query + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byContains(string $tableName): string + { + return self::query($tableName, Query::whereDataContains()); + } + + /** + * Query to determine if documents exist using a JSON Path match query (PostgreSQL only) + * + * @param string $tableName The name of the table in which document existence should be checked + * @return string The query to determine document existence by a JSON Path match + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byJsonPath(string $tableName): string + { + return self::query($tableName, Query::whereJsonPathMatches()); + } } diff --git a/src/Query/Find.php b/src/Query/Find.php index 68e004b..c649208 100644 --- a/src/Query/Find.php +++ b/src/Query/Find.php @@ -35,4 +35,28 @@ class Find { return Query::selectFromTable($tableName) . ' WHERE ' . Query::whereByFields($fields, $match); } + + /** + * Query to retrieve documents using a JSON containment query (PostgreSQL only) + * + * @param string $tableName The name of the table from which documents should be retrieved + * @return string The SELECT statement to retrieve documents by a JSON containment query + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byContains(string $tableName): string + { + return Query::selectFromTable($tableName) . ' WHERE ' . Query::whereDataContains(); + } + + /** + * Query to retrieve documents using a JSON Path match query (PostgreSQL only) + * + * @param string $tableName The name of the table from which documents should be retrieved + * @return string The SELECT statement to retrieve documents by a JSON Path match + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byJsonPath(string $tableName): string + { + return Query::selectFromTable($tableName) . ' WHERE ' . Query::whereJsonPathMatches(); + } } diff --git a/src/Query/Patch.php b/src/Query/Patch.php index 2cfd059..d320735 100644 --- a/src/Query/Patch.php +++ b/src/Query/Patch.php @@ -53,4 +53,28 @@ class Patch { return self::update($tableName, Query::whereByFields($field, $match)); } + + /** + * Query to patch (partially update) a document via a JSON containment query (PostgreSQL only) + * + * @param string $tableName The name of the table in which documents should be patched + * @return string The query to patch documents via a JSON containment query + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byContains(string $tableName): string + { + return self::update($tableName, Query::whereDataContains()); + } + + /** + * Query to patch (partially update) a document via a JSON Path match query (PostgreSQL only) + * + * @param string $tableName The name of the table in which documents should be patched + * @return string The query to patch documents via a JSON Path match + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byJsonPath(string $tableName): string + { + return self::update($tableName, Query::whereJsonPathMatches()); + } } diff --git a/src/Query/RemoveFields.php b/src/Query/RemoveFields.php index 766395c..a35c641 100644 --- a/src/Query/RemoveFields.php +++ b/src/Query/RemoveFields.php @@ -65,4 +65,30 @@ class RemoveFields { return self::update($tableName, $parameters, Query::whereByFields($fields, $match)); } + + /** + * Query to remove fields from documents via a JSON containment query (PostgreSQL only) + * + * @param string $tableName The name of the table in which documents should be manipulated + * @param array $parameters The parameter list for the query + * @return string The UPDATE statement to remove fields from documents via a JSON containment query + * @throws DocumentException If the database mode is not PostgreSQL + */ + public static function byContains(string $tableName, array $parameters): string + { + return self::update($tableName, $parameters, Query::whereDataContains()); + } + + /** + * Query to remove fields from documents via a JSON Path match query (PostgreSQL only) + * + * @param string $tableName The name of the table in which documents should be manipulated + * @param array $parameters The parameter list for the query + * @return string The UPDATE statement to remove fields from documents via a JSON Path match + * @throws DocumentException + */ + public static function byJsonPath(string $tableName, array $parameters): string + { + return self::update($tableName, $parameters, Query::whereJsonPathMatches()); + } } diff --git a/tests/integration/postgresql/DefinitionTest.php b/tests/integration/postgresql/DefinitionTest.php index ff3d5c8..e770704 100644 --- a/tests/integration/postgresql/DefinitionTest.php +++ b/tests/integration/postgresql/DefinitionTest.php @@ -2,7 +2,7 @@ namespace Test\Integration\PostgreSQL; -use BitBadger\PDODocument\{Custom, Definition, DocumentException}; +use BitBadger\PDODocument\{Custom, Definition, DocumentException, DocumentIndex}; use BitBadger\PDODocument\Mapper\ExistsMapper; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -41,7 +41,7 @@ class DefinitionTest extends TestCase [':name' => $name], new ExistsMapper()); } - public function testEnsureTableSucceeds() + public function testEnsureTableSucceeds(): void { $this->assertFalse($this->itExists('ensured'), 'The table should not exist already'); $this->assertFalse($this->itExists('idx_ensured_key'), 'The key index should not exist already'); @@ -50,11 +50,29 @@ class DefinitionTest extends TestCase $this->assertTrue($this->itExists('idx_ensured_key'), 'The key index should now exist'); } - public function testEnsureFieldIndexSucceeds() + public function testEnsureFieldIndexSucceeds(): void { $this->assertFalse($this->itExists('idx_ensured_test'), 'The index should not exist already'); Definition::ensureTable('ensured'); Definition::ensureFieldIndex('ensured', 'test', ['name', 'age']); $this->assertTrue($this->itExists('idx_ensured_test'), 'The index should now exist'); } + + public function testEnsureDocumentIndexSucceedsForFull(): void + { + $docIdx = 'idx_' . ThrowawayDb::TABLE . '_document'; + Definition::ensureTable(ThrowawayDb::TABLE); + $this->assertFalse($this->itExists($docIdx), 'The document index should not exist'); + Definition::ensureDocumentIndex(ThrowawayDb::TABLE, DocumentIndex::Full); + $this->assertTrue($this->itExists($docIdx), 'The document index should now exist'); + } + + public function testEnsureDocumentIndexSucceedsForOptimized(): void + { + $docIdx = 'idx_' . ThrowawayDb::TABLE . '_document'; + Definition::ensureTable(ThrowawayDb::TABLE); + $this->assertFalse($this->itExists($docIdx), 'The document index should not exist'); + Definition::ensureDocumentIndex(ThrowawayDb::TABLE, DocumentIndex::Optimized); + $this->assertTrue($this->itExists($docIdx), 'The document index should now exist'); + } } diff --git a/tests/integration/sqlite/DefinitionTest.php b/tests/integration/sqlite/DefinitionTest.php index 59b192f..f89304a 100644 --- a/tests/integration/sqlite/DefinitionTest.php +++ b/tests/integration/sqlite/DefinitionTest.php @@ -2,7 +2,7 @@ namespace Test\Integration\SQLite; -use BitBadger\PDODocument\{Custom, Definition, DocumentException}; +use BitBadger\PDODocument\{Custom, Definition, DocumentException, DocumentIndex}; use BitBadger\PDODocument\Mapper\ExistsMapper; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -41,7 +41,7 @@ class DefinitionTest extends TestCase [':name' => $name], new ExistsMapper()); } - public function testEnsureTableSucceeds() + public function testEnsureTableSucceeds(): void { $this->assertFalse($this->itExists('ensured'), 'The table should not exist already'); $this->assertFalse($this->itExists('idx_ensured_key'), 'The key index should not exist already'); @@ -50,11 +50,17 @@ class DefinitionTest extends TestCase $this->assertTrue($this->itExists('idx_ensured_key'), 'The key index should now exist'); } - public function testEnsureFieldIndexSucceeds() + public function testEnsureFieldIndexSucceeds(): void { $this->assertFalse($this->itExists('idx_ensured_test'), 'The index should not exist already'); Definition::ensureTable('ensured'); Definition::ensureFieldIndex('ensured', 'test', ['name', 'age']); $this->assertTrue($this->itExists('idx_ensured_test'), 'The index should now exist'); } + + public function testEnsureDocumentIndexFails(): void + { + $this->expectException(DocumentException::class); + Definition::ensureDocumentIndex('nope', DocumentIndex::Full); + } } diff --git a/tests/integration/sqlite/ThrowawayDb.php b/tests/integration/sqlite/ThrowawayDb.php index 6b4f56a..ff2b1b2 100644 --- a/tests/integration/sqlite/ThrowawayDb.php +++ b/tests/integration/sqlite/ThrowawayDb.php @@ -48,6 +48,6 @@ class ThrowawayDb public static function destroy(string $fileName): void { Configuration::resetPDO(); - unlink("./$fileName"); + if (file_exists("./$fileName")) unlink("./$fileName"); } } diff --git a/tests/unit/ConfigurationTest.php b/tests/unit/ConfigurationTest.php index 50dd909..6eb80f7 100644 --- a/tests/unit/ConfigurationTest.php +++ b/tests/unit/ConfigurationTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Configuration class */ +#[TestDox('Configuration (Unit tests)')] class ConfigurationTest extends TestCase { #[TestDox('ID field default succeeds')] diff --git a/tests/unit/DocumentExceptionTest.php b/tests/unit/DocumentExceptionTest.php index 7103575..c068473 100644 --- a/tests/unit/DocumentExceptionTest.php +++ b/tests/unit/DocumentExceptionTest.php @@ -4,11 +4,13 @@ namespace Test\Unit; use BitBadger\PDODocument\DocumentException; use Exception; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; /** * Unit tests for the DocumentException class */ +#[TestDox('Document Exception (Unit tests)')] class DocumentExceptionTest extends TestCase { public function testConstructorSucceedsWithCodeAndPriorException() diff --git a/tests/unit/FieldMatchTest.php b/tests/unit/FieldMatchTest.php index 9fd8942..f7d1ea4 100644 --- a/tests/unit/FieldMatchTest.php +++ b/tests/unit/FieldMatchTest.php @@ -3,11 +3,13 @@ namespace Test\Unit; use BitBadger\PDODocument\FieldMatch; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; /** * Unit tests for the FieldMatch enum */ +#[TestDox('Field Match (Unit tests)')] class FieldMatchTest extends TestCase { public function testToStringSucceedsForAll(): void diff --git a/tests/unit/FieldTest.php b/tests/unit/FieldTest.php index d2639c4..a98bb12 100644 --- a/tests/unit/FieldTest.php +++ b/tests/unit/FieldTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Field class */ +#[TestDox('Field (Unit tests)')] class FieldTest extends TestCase { #[TestDox('Append parameter succeeds for EX')] diff --git a/tests/unit/Mapper/ArrayMapperTest.php b/tests/unit/Mapper/ArrayMapperTest.php index 51a8603..b16de99 100644 --- a/tests/unit/Mapper/ArrayMapperTest.php +++ b/tests/unit/Mapper/ArrayMapperTest.php @@ -3,11 +3,13 @@ namespace Test\Unit\Mapper; use BitBadger\PDODocument\Mapper\ArrayMapper; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; /** * Unit tests for the ArrayMapper class */ +#[TestDox('Array Mapper (Unit tests)')] class ArrayMapperTest extends TestCase { public function testMapSucceeds(): void diff --git a/tests/unit/Mapper/CountMapperTest.php b/tests/unit/Mapper/CountMapperTest.php index 130763a..cdc04f2 100644 --- a/tests/unit/Mapper/CountMapperTest.php +++ b/tests/unit/Mapper/CountMapperTest.php @@ -3,11 +3,13 @@ namespace Test\Unit\Mapper; use BitBadger\PDODocument\Mapper\CountMapper; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; /** * Unit tests for the CountMapper class */ +#[TestDox('Count Mapper (Unit tests)')] class CountMapperTest extends TestCase { public function testMapSucceeds(): void diff --git a/tests/unit/Mapper/DocumentMapperTest.php b/tests/unit/Mapper/DocumentMapperTest.php index 5213312..691dedf 100644 --- a/tests/unit/Mapper/DocumentMapperTest.php +++ b/tests/unit/Mapper/DocumentMapperTest.php @@ -22,6 +22,7 @@ class TestDocument /** * Unit tests for the DocumentMapper class */ +#[TestDox('Document Mapper (Unit tests)')] class DocumentMapperTest extends TestCase { public function testConstructorSucceedsWithDefaultField(): void diff --git a/tests/unit/Mapper/ExistsMapperTest.php b/tests/unit/Mapper/ExistsMapperTest.php index 1dbaf02..7e4eac8 100644 --- a/tests/unit/Mapper/ExistsMapperTest.php +++ b/tests/unit/Mapper/ExistsMapperTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the ExistsMapper class */ +#[TestDox('Exists Mapper (Unit tests)')] class ExistsMapperTest extends TestCase { #[TestDox('Map succeeds for PostgreSQL')] diff --git a/tests/unit/Mapper/StringMapperTest.php b/tests/unit/Mapper/StringMapperTest.php index 57c1c0a..9bda0ca 100644 --- a/tests/unit/Mapper/StringMapperTest.php +++ b/tests/unit/Mapper/StringMapperTest.php @@ -3,8 +3,13 @@ namespace Test\Unit\Mapper; use BitBadger\PDODocument\Mapper\StringMapper; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; +/** + * Unit tests for the StringMapper class + */ +#[TestDox('String Mapper (Unit tests)')] class StringMapperTest extends TestCase { public function testMapSucceedsWhenFieldIsPresentAndString() diff --git a/tests/unit/OpTest.php b/tests/unit/OpTest.php index 3cb3106..cac2247 100644 --- a/tests/unit/OpTest.php +++ b/tests/unit/OpTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Op enumeration */ +#[TestDox('Op (Unit tests)')] class OpTest extends TestCase { #[TestDox('To string succeeds for EQ')] diff --git a/tests/unit/ParametersTest.php b/tests/unit/ParametersTest.php index 3d1595d..9ef2002 100644 --- a/tests/unit/ParametersTest.php +++ b/tests/unit/ParametersTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Parameters class */ +#[TestDox('Parameters (Unit tests)')] class ParametersTest extends TestCase { #[TestDox('ID succeeds with string')] diff --git a/tests/unit/Query/CountTest.php b/tests/unit/Query/CountTest.php index 69eb6a7..58d9ac0 100644 --- a/tests/unit/Query/CountTest.php +++ b/tests/unit/Query/CountTest.php @@ -2,30 +2,64 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\{Configuration, Field, Mode}; +use BitBadger\PDODocument\{Configuration, DocumentException, Field, Mode}; use BitBadger\PDODocument\Query\Count; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; /** * Unit tests for the Count class */ +#[TestDox('Count Queries (Unit tests)')] class CountTest extends TestCase { + public function tearDown(): void + { + Configuration::$mode = null; + parent::tearDown(); + } - public function testAllSucceeds() + public function testAllSucceeds(): void { $this->assertEquals('SELECT COUNT(*) FROM a_table', Count::all('a_table'), 'SELECT statement not generated correctly'); } - public function testByFieldsSucceeds() + public function testByFieldsSucceeds(): void { Configuration::$mode = Mode::SQLite; - try { - $this->assertEquals("SELECT COUNT(*) FROM somewhere WHERE data->>'errors' > :errors", - Count::byFields('somewhere', [Field::GT('errors', 10, ':errors')])); - } finally { - Configuration::$mode = null; - } + $this->assertEquals("SELECT COUNT(*) FROM somewhere WHERE data->>'errors' > :errors", + Count::byFields('somewhere', [Field::GT('errors', 10, ':errors')]), + 'SELECT statement not generated correctly'); + } + + #[TestDox('By contains succeeds for PostgreSQL')] + public function testByContainsSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals("SELECT COUNT(*) FROM the_table WHERE data @> :criteria", Count::byContains('the_table'), + 'SELECT statement not generated correctly'); + } + + #[TestDox('By contains fails for non PostgreSQL')] + public function testByContainsFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Count::byContains(''); + } + + #[TestDox('By JSON Path succeeds for PostgreSQL')] + public function testByJsonPathSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals("SELECT COUNT(*) FROM a_table WHERE data @? :path::jsonpath", Count::byJsonPath('a_table'), + 'SELECT statement not generated correctly'); + } + + #[TestDox('By JSON Path fails for non PostgreSQL')] + public function testByJsonPathFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Count::byJsonPath(''); } } diff --git a/tests/unit/Query/DefinitionTest.php b/tests/unit/Query/DefinitionTest.php index c579db3..3b5db8f 100644 --- a/tests/unit/Query/DefinitionTest.php +++ b/tests/unit/Query/DefinitionTest.php @@ -2,7 +2,7 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\{Configuration, DocumentException, Mode}; +use BitBadger\PDODocument\{Configuration, DocumentException, DocumentIndex, Mode}; use BitBadger\PDODocument\Query\Definition; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -10,36 +10,34 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Definition class */ +#[TestDox('Definition Queries (Unit tests)')] class DefinitionTest extends TestCase { + protected function tearDown(): void + { + Configuration::$mode = null; + parent::tearDown(); + } + #[TestDox('Ensure table succeeds for PosgtreSQL')] public function testEnsureTableSucceedsForPostgreSQL(): void { - try { - Configuration::$mode = Mode::PgSQL; - $this->assertEquals('CREATE TABLE IF NOT EXISTS documents (data JSONB NOT NULL)', - Definition::ensureTable('documents'), 'CREATE TABLE statement not generated correctly'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('CREATE TABLE IF NOT EXISTS documents (data JSONB NOT NULL)', + Definition::ensureTable('documents'), 'CREATE TABLE statement not generated correctly'); } #[TestDox('Ensure table succeeds for SQLite')] public function testEnsureTableSucceedsForSQLite(): void { - try { - Configuration::$mode = Mode::SQLite; - $this->assertEquals('CREATE TABLE IF NOT EXISTS dox (data TEXT NOT NULL)', Definition::ensureTable('dox'), - 'CREATE TABLE statement not generated correctly'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::SQLite; + $this->assertEquals('CREATE TABLE IF NOT EXISTS dox (data TEXT NOT NULL)', Definition::ensureTable('dox'), + 'CREATE TABLE statement not generated correctly'); } public function testEnsureTableFailsWhenModeNotSet(): void { $this->expectException(DocumentException::class); - Configuration::$mode = null; Definition::ensureTable('boom'); } @@ -57,9 +55,30 @@ class DefinitionTest extends TestCase 'CREATE INDEX statement not generated correctly'); } - public function testEnsureKey(): void + public function testEnsureKeySucceeds(): void { $this->assertEquals("CREATE UNIQUE INDEX IF NOT EXISTS idx_tbl_key ON tbl ((data->>'id'))", Definition::ensureKey('tbl'), 'CREATE INDEX statement for document key not generated correctly'); } + + public function testEnsureDocumentIndexOnSucceedsForSchemaAndFull(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals("CREATE INDEX IF NOT EXISTS idx_tbl_document ON my.tbl USING GIN (data)", + Definition::ensureDocumentIndexOn('my.tbl', DocumentIndex::Full)); + } + + public function testEnsureDocumentIndexOnSucceedsForNoSchemaAndOptimized(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals("CREATE INDEX IF NOT EXISTS idx_it_document ON it USING GIN (data jsonb_path_ops)", + Definition::ensureDocumentIndexOn('it', DocumentIndex::Optimized)); + } + + #[TestDox('Ensure document index on fails for non PostgreSQL')] + public function testEnsureDocumentIndexOnFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Definition::ensureDocumentIndexOn('', DocumentIndex::Full); + } } diff --git a/tests/unit/Query/DeleteTest.php b/tests/unit/Query/DeleteTest.php index 1ac3c7b..9efe38b 100644 --- a/tests/unit/Query/DeleteTest.php +++ b/tests/unit/Query/DeleteTest.php @@ -2,7 +2,7 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\{Configuration, Field, Mode}; +use BitBadger\PDODocument\{Configuration, DocumentException, Field, Mode}; use BitBadger\PDODocument\Query\Delete; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -10,13 +10,9 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Delete class */ +#[TestDox('Delete Queries (Unit tests)')] class DeleteTest extends TestCase { - protected function setUp(): void - { - Configuration::$mode = Mode::SQLite; - } - protected function tearDown(): void { Configuration::$mode = null; @@ -25,14 +21,46 @@ class DeleteTest extends TestCase #[TestDox('By ID succeeds')] public function testByIdSucceeds(): void { + Configuration::$mode = Mode::SQLite; $this->assertEquals("DELETE FROM over_there WHERE data->>'id' = :id", Delete::byId('over_there'), 'DELETE statement not constructed correctly'); } public function testByFieldsSucceeds(): void { + Configuration::$mode = Mode::SQLite; $this->assertEquals("DELETE FROM my_table WHERE data->>'value' < :max AND data->>'value' >= :min", Delete::byFields('my_table', [Field::LT('value', 99, ':max'), Field::GE('value', 18, ':min')]), 'DELETE statement not constructed correctly'); } + + #[TestDox('By contains succeeds for PostgreSQL')] + public function testByContainsSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('DELETE FROM somewhere WHERE data @> :criteria', Delete::byContains('somewhere'), + 'DELETE statement not constructed correctly'); + } + + #[TestDox('By contains fails for non PostgreSQL')] + public function testByContainsFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Delete::byContains(''); + } + + #[TestDox('By JSON Path succeeds for PostgreSQL')] + public function testByJsonPathSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('DELETE FROM here WHERE data @? :path::jsonpath', Delete::byJsonPath('here'), + 'DELETE statement not constructed correctly'); + } + + #[TestDox('By JSON Path fails for non PostgreSQL')] + public function testByJsonPathFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Delete::byJsonPath(''); + } } diff --git a/tests/unit/Query/ExistsTest.php b/tests/unit/Query/ExistsTest.php index 416c9dd..ba77418 100644 --- a/tests/unit/Query/ExistsTest.php +++ b/tests/unit/Query/ExistsTest.php @@ -2,7 +2,7 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\{Configuration, Field, Mode}; +use BitBadger\PDODocument\{Configuration, DocumentException, Field, Mode}; use BitBadger\PDODocument\Query\Exists; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -10,13 +10,9 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Exists class */ +#[TestDox('Exists Queries (Unit tests)')] class ExistsTest extends TestCase { - protected function setUp(): void - { - Configuration::$mode = Mode::SQLite; - } - protected function tearDown(): void { Configuration::$mode = null; @@ -24,6 +20,7 @@ class ExistsTest extends TestCase public function testQuerySucceeds(): void { + Configuration::$mode = Mode::SQLite; $this->assertEquals('SELECT EXISTS (SELECT 1 FROM abc WHERE def)', Exists::query('abc', 'def'), 'Existence query not generated correctly'); } @@ -31,14 +28,46 @@ class ExistsTest extends TestCase #[TestDox('By ID succeeds')] public function testByIdSucceeds(): void { + Configuration::$mode = Mode::SQLite; $this->assertEquals("SELECT EXISTS (SELECT 1 FROM dox WHERE data->>'id' = :id)", Exists::byId('dox'), 'Existence query not generated correctly'); } public function testByFieldsSucceeds(): void { + Configuration::$mode = Mode::SQLite; $this->assertEquals("SELECT EXISTS (SELECT 1 FROM box WHERE data->>'status' <> :status)", Exists::byFields('box', [Field::NE('status', 'occupied', ':status')]), 'Existence query not generated correctly'); } + + #[TestDox('By contains succeeds for PostgreSQL')] + public function testByContainsSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('SELECT EXISTS (SELECT 1 FROM pocket WHERE data @> :criteria)', + Exists::byContains('pocket'), 'Existence query not generated correctly'); + } + + #[TestDox('By contains fails for non PostgreSQL')] + public function testByContainsFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Exists::byContains(''); + } + + #[TestDox('By JSON Path succeeds for PostgreSQL')] + public function testByJsonPathSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('SELECT EXISTS (SELECT 1 FROM lint WHERE data @? :path::jsonpath)', + Exists::byJsonPath('lint'), 'Existence query not generated correctly'); + } + + #[TestDox('By JSON Path fails for non PostgreSQL')] + public function testByJsonPathFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Exists::byJsonPath(''); + } } diff --git a/tests/unit/Query/FindTest.php b/tests/unit/Query/FindTest.php index 10e7d73..2a98d82 100644 --- a/tests/unit/Query/FindTest.php +++ b/tests/unit/Query/FindTest.php @@ -2,7 +2,7 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\{Configuration, Field, FieldMatch, Mode}; +use BitBadger\PDODocument\{Configuration, DocumentException, Field, FieldMatch, Mode}; use BitBadger\PDODocument\Query\Find; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -10,13 +10,9 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Find class */ +#[TestDox('Find Queries (Unit tests)')] class FindTest extends TestCase { - protected function setUp(): void - { - Configuration::$mode = Mode::SQLite; - } - protected function tearDown(): void { Configuration::$mode = null; @@ -25,15 +21,47 @@ class FindTest extends TestCase #[TestDox('By ID succeeds')] public function testByIdSucceeds(): void { + Configuration::$mode = Mode::SQLite; $this->assertEquals("SELECT data FROM here WHERE data->>'id' = :id", Find::byId('here'), 'SELECT query not generated correctly'); } public function testByFieldsSucceeds(): void { + Configuration::$mode = Mode::SQLite; $this->assertEquals("SELECT data FROM there WHERE data->>'active' = :act OR data->>'locked' = :lock", Find::byFields('there', [Field::EQ('active', true, ':act'), Field::EQ('locked', true, ':lock')], FieldMatch::Any), 'SELECT query not generated correctly'); } + + #[TestDox('By contains succeeds for PostgreSQL')] + public function testByContainsSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('SELECT data FROM disc WHERE data @> :criteria', Find::byContains('disc'), + 'SELECT query not generated correctly'); + } + + #[TestDox('By contains fails for non PostgreSQL')] + public function testByContainsFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Find::byContains(''); + } + + #[TestDox('By JSON Path succeeds for PostgreSQL')] + public function testByJsonPathSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('SELECT data FROM light WHERE data @? :path::jsonpath', Find::byJsonPath('light'), + 'SELECT query not generated correctly'); + } + + #[TestDox('By JSON Path fails for non PostgreSQL')] + public function testByJsonPathFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Find::byJsonPath(''); + } } diff --git a/tests/unit/Query/PatchTest.php b/tests/unit/Query/PatchTest.php index f4b1608..bf5e05e 100644 --- a/tests/unit/Query/PatchTest.php +++ b/tests/unit/Query/PatchTest.php @@ -10,71 +10,87 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Patch class */ +#[TestDox('Patch Queries (Unit tests)')] class PatchTest extends TestCase { + protected function tearDown(): void + { + Configuration::$mode = null; + parent::tearDown(); + } #[TestDox('By ID succeeds for PostgreSQL')] public function testByIdSucceedsForPostgreSQL(): void { - try { - Configuration::$mode = Mode::PgSQL; - $this->assertEquals("UPDATE doc_table SET data = data || :data WHERE data->>'id' = :id", - Patch::byId('doc_table'), 'Patch UPDATE statement is not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::PgSQL; + $this->assertEquals("UPDATE doc_table SET data = data || :data WHERE data->>'id' = :id", + Patch::byId('doc_table'), 'Patch UPDATE statement is not correct'); } #[TestDox('By ID succeeds for SQLite')] public function testByIdSucceedsForSQLite(): void { - try { - Configuration::$mode = Mode::SQLite; - $this->assertEquals("UPDATE my_table SET data = json_patch(data, json(:data)) WHERE data->>'id' = :id", - Patch::byId('my_table'), 'Patch UPDATE statement is not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::SQLite; + $this->assertEquals("UPDATE my_table SET data = json_patch(data, json(:data)) WHERE data->>'id' = :id", + Patch::byId('my_table'), 'Patch UPDATE statement is not correct'); } #[TestDox('By ID fails when mode not set')] public function testByIdFailsWhenModeNotSet(): void { $this->expectException(DocumentException::class); - Configuration::$mode = null; Patch::byId('oof'); } #[TestDox('By fields succeeds for PostgreSQL')] public function testByFieldsSucceedsForPostgreSQL(): void { - try { - Configuration::$mode = Mode::PgSQL; - $this->assertEquals("UPDATE that SET data = data || :data WHERE (data->>'something')::numeric < :some", - Patch::byFields('that', [Field::LT('something', 17, ':some')]), - 'Patch UPDATE statement is not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::PgSQL; + $this->assertEquals("UPDATE that SET data = data || :data WHERE (data->>'something')::numeric < :some", + Patch::byFields('that', [Field::LT('something', 17, ':some')]), 'Patch UPDATE statement is not correct'); } #[TestDox('By fields succeeds for SQLite')] public function testByFieldsSucceedsForSQLite(): void { - try { - Configuration::$mode = Mode::SQLite; - $this->assertEquals( - "UPDATE a_table SET data = json_patch(data, json(:data)) WHERE data->>'something' > :it", - Patch::byFields('a_table', [Field::GT('something', 17, ':it')]), - 'Patch UPDATE statement is not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::SQLite; + $this->assertEquals( + "UPDATE a_table SET data = json_patch(data, json(:data)) WHERE data->>'something' > :it", + Patch::byFields('a_table', [Field::GT('something', 17, ':it')]), 'Patch UPDATE statement is not correct'); } public function testByFieldsFailsWhenModeNotSet(): void { $this->expectException(DocumentException::class); - Configuration::$mode = null; Patch::byFields('oops', []); } + + #[TestDox('By contains succeeds for PostgreSQL')] + public function testByContainsSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('UPDATE this SET data = data || :data WHERE data @> :criteria', Patch::byContains('this'), + 'Patch UPDATE statement is not correct'); + } + + #[TestDox('By contains fails for non PostgreSQL')] + public function testByContainsFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Patch::byContains(''); + } + + #[TestDox('By JSON Path succeeds for PostgreSQL')] + public function testByJsonPathSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('UPDATE that SET data = data || :data WHERE data @? :path::jsonpath', + Patch::byJsonPath('that'), 'Patch UPDATE statement is not correct'); + } + + #[TestDox('By JSON Path fails for non PostgreSQL')] + public function testByJsonPathFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + Patch::byJsonPath(''); + } } diff --git a/tests/unit/Query/RemoveFieldsTest.php b/tests/unit/Query/RemoveFieldsTest.php index 1933845..7d5dfac 100644 --- a/tests/unit/Query/RemoveFieldsTest.php +++ b/tests/unit/Query/RemoveFieldsTest.php @@ -10,108 +10,117 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the RemoveFields class */ +#[TestDox('Remove Fields Queries (Unit tests)')] class RemoveFieldsTest extends TestCase { + protected function tearDown(): void + { + Configuration::$mode = null; + } + #[TestDox('Update succeeds for PostgreSQL')] public function testUpdateSucceedsForPostgreSQL(): void { - try { - Configuration::$mode = Mode::PgSQL; - $this->assertEquals('UPDATE taco SET data = data - :names::text[] WHERE it = true', - RemoveFields::update('taco', [':names' => "{one,two}"], 'it = true'), - 'UPDATE statement not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('UPDATE taco SET data = data - :names::text[] WHERE it = true', + RemoveFields::update('taco', [':names' => "{one,two}"], 'it = true'), 'UPDATE statement not correct'); } #[TestDox('Update succeeds for SQLite')] public function testUpdateSucceedsForSQLite(): void { - try { - Configuration::$mode = Mode::SQLite; - $this->assertEquals('UPDATE burrito SET data = json_remove(data, :name0, :name1, :name2) WHERE a = b', - RemoveFields::update('burrito', Parameters::fieldNames(':name', ['one', 'two', 'ten']), 'a = b'), - 'UPDATE statement not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::SQLite; + $this->assertEquals('UPDATE burrito SET data = json_remove(data, :name0, :name1, :name2) WHERE a = b', + RemoveFields::update('burrito', Parameters::fieldNames(':name', ['one', 'two', 'ten']), 'a = b'), + 'UPDATE statement not correct'); } public function testUpdateFailsWhenModeNotSet(): void { $this->expectException(DocumentException::class); - Configuration::$mode = null; RemoveFields::update('wow', [], ''); } #[TestDox('By ID succeeds for PostgreSQL')] public function testByIdSucceedsForPostgreSQL() { - try { - Configuration::$mode = Mode::PgSQL; - $this->assertEquals("UPDATE churro SET data = data - :bite::text[] WHERE data->>'id' = :id", - RemoveFields::byId('churro', Parameters::fieldNames(':bite', ['byte'])), - 'UPDATE statement not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::PgSQL; + $this->assertEquals("UPDATE churro SET data = data - :bite::text[] WHERE data->>'id' = :id", + RemoveFields::byId('churro', Parameters::fieldNames(':bite', ['byte'])), 'UPDATE statement not correct'); } #[TestDox('By ID succeeds for SQLite')] public function testByIdSucceedsForSQLite() { - try { - Configuration::$mode = Mode::SQLite; - $this->assertEquals("UPDATE quesadilla SET data = json_remove(data, :bite0) WHERE data->>'id' = :id", - RemoveFields::byId('quesadilla', Parameters::fieldNames(':bite', ['byte'])), - 'UPDATE statement not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::SQLite; + $this->assertEquals("UPDATE quesadilla SET data = json_remove(data, :bite0) WHERE data->>'id' = :id", + RemoveFields::byId('quesadilla', Parameters::fieldNames(':bite', ['byte'])), + 'UPDATE statement not correct'); } #[TestDox('By ID fails when mode not set')] public function testByIdFailsWhenModeNotSet(): void { $this->expectException(DocumentException::class); - Configuration::$mode = null; RemoveFields::byId('oof', []); } #[TestDox('By fields succeeds for PostgreSQL')] public function testByFieldsSucceedsForPostgreSQL() { - try { - Configuration::$mode = Mode::PgSQL; - $this->assertEquals("UPDATE enchilada SET data = data - :sauce::text[] WHERE data->>'cheese' = :queso", - RemoveFields::byFields('enchilada', [Field::EQ('cheese', 'jack', ':queso')], - Parameters::fieldNames(':sauce', ['white'])), - 'UPDATE statement not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::PgSQL; + $this->assertEquals("UPDATE enchilada SET data = data - :sauce::text[] WHERE data->>'cheese' = :queso", + RemoveFields::byFields('enchilada', [Field::EQ('cheese', 'jack', ':queso')], + Parameters::fieldNames(':sauce', ['white'])), + 'UPDATE statement not correct'); } #[TestDox('By fields succeeds for SQLite')] public function testByFieldsSucceedsForSQLite() { - try { - Configuration::$mode = Mode::SQLite; - $this->assertEquals( - "UPDATE chimichanga SET data = json_remove(data, :filling0) WHERE data->>'side' = :rice", - RemoveFields::byFields('chimichanga', [Field::EQ('side', 'beans', ':rice')], - Parameters::fieldNames(':filling', ['beef'])), - 'UPDATE statement not correct'); - } finally { - Configuration::$mode = null; - } + Configuration::$mode = Mode::SQLite; + $this->assertEquals( + "UPDATE chimichanga SET data = json_remove(data, :filling0) WHERE data->>'side' = :rice", + RemoveFields::byFields('chimichanga', [Field::EQ('side', 'beans', ':rice')], + Parameters::fieldNames(':filling', ['beef'])), + 'UPDATE statement not correct'); } public function testByFieldsFailsWhenModeNotSet(): void { $this->expectException(DocumentException::class); - Configuration::$mode = null; RemoveFields::byFields('boing', [], []); } + + #[TestDox('By contains succeeds for PostgreSQL')] + public function testByContainsSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('UPDATE food SET data = data - :drink::text[] WHERE data @> :criteria', + RemoveFields::byContains('food', Parameters::fieldNames(':drink', ['a', 'b'])), + 'UPDATE statement not correct'); + } + + #[TestDox('By contains fails for non PostgreSQL')] + public function testByContainsFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + RemoveFields::byContains('', []); + } + + #[TestDox('By JSON Path succeeds for PostgreSQL')] + public function testByJsonPathSucceedsForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + $this->assertEquals('UPDATE dessert SET data = data - :cake::text[] WHERE data @? :path::jsonpath', + RemoveFields::byJsonPath('dessert', Parameters::fieldNames(':cake', ['b', 'c'])), + 'UPDATE statement not correct'); + } + + #[TestDox('By JSON Path fails for non PostgreSQL')] + public function testByJsonPathFailsForNonPostgreSQL(): void + { + $this->expectException(DocumentException::class); + RemoveFields::byJsonPath('', []); + } } diff --git a/tests/unit/QueryTest.php b/tests/unit/QueryTest.php index a3418fe..3c0c945 100644 --- a/tests/unit/QueryTest.php +++ b/tests/unit/QueryTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; /** * Unit tests for the Query class */ +#[TestDox('Query (Unit tests)')] class QueryTest extends TestCase { protected function setUp(): void @@ -60,6 +61,68 @@ class QueryTest extends TestCase $this->assertEquals("data->>'id' = :di", Query::whereById(':di'), 'WHERE fragment not constructed correctly'); } + public function testWhereDataContainsSucceedsWithDefaultParameter(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals('data @> :criteria', Query::whereDataContains(), + 'WHERE fragment not constructed correctly'); + } finally { + Configuration::$mode = null; + } + } + + public function testWhereDataContainsSucceedsWithSpecifiedParameter(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals('data @> :it', Query::whereDataContains(':it'), + 'WHERE fragment not constructed correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('Where data contains fails if not PostgreSQL')] + public function testWhereDataContainsFailsIfNotPostgreSQL(): void + { + Configuration::$mode = null; + $this->expectException(DocumentException::class); + Query::whereDataContains(); + } + + #[TestDox('Where JSON Path matches succeeds with default parameter')] + public function testWhereJsonPathMatchesSucceedsWithDefaultParameter(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals('data @? :path::jsonpath', Query::whereJsonPathMatches(), + 'WHERE fragment not constructed correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('Where JSON Path matches succeeds with specified parameter')] + public function testWhereJsonPathMatchesSucceedsWithSpecifiedParameter(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals('data @? :road::jsonpath', Query::whereJsonPathMatches(':road'), + 'WHERE fragment not constructed correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('Where JSON Path matches fails if not PostgreSQL')] + public function testWhereJsonPathMatchesFailsIfNotPostgreSQL(): void + { + Configuration::$mode = null; + $this->expectException(DocumentException::class); + Query::whereJsonPathMatches(); + } + #[TestDox('Insert succeeds with no auto-ID for PostgreSQL')] public function testInsertSucceedsWithNoAutoIdForPostgreSQL(): void {