diff --git a/src/Custom.php b/src/Custom.php index e4d2300..8644cda 100644 --- a/src/Custom.php +++ b/src/Custom.php @@ -47,7 +47,9 @@ class Custom if ($stmt->execute()) return $stmt; } catch (PDOException $ex) { $keyword = explode(' ', $query, 2)[0]; - throw new DocumentException("Error executing $keyword statement: " . $stmt->errorCode(), previous: $ex); + throw new DocumentException( + sprintf("Error executing %s statement: [%s] %s", $keyword, $stmt->errorCode(), $stmt->errorInfo()[2]), + previous: $ex); } $keyword = explode(' ', $query, 2)[0]; throw new DocumentException("Error executing $keyword statement: " . $stmt->errorCode()); diff --git a/src/DocumentList.php b/src/DocumentList.php index 44ec060..5c4c011 100644 --- a/src/DocumentList.php +++ b/src/DocumentList.php @@ -15,13 +15,23 @@ use PDOStatement; */ class DocumentList { + /** @var TDoc|null $_first The first item from the results */ + private mixed $_first = null; + /** * Constructor * * @param PDOStatement|null $result The result of the query * @param Mapper $mapper The mapper to deserialize JSON */ - private function __construct(private ?PDOStatement &$result, private readonly Mapper $mapper) { } + private function __construct(private ?PDOStatement &$result, private readonly Mapper $mapper) + { + if ($row = $this->result->fetch(PDO::FETCH_ASSOC)) { + $this->_first = $this->mapper->map($row); + } else { + $this->result = null; + } + } /** * Construct a new document list @@ -45,9 +55,29 @@ class DocumentList */ public function items(): Generator { - if ($this->result) { - while ($row = $this->result->fetch(PDO::FETCH_ASSOC)) yield $this->mapper->map($row); + if (!$this->result) return; + yield $this->_first; + while ($row = $this->result->fetch(PDO::FETCH_ASSOC)) { + yield $this->mapper->map($row); } $this->result = null; } + + /** + * Does this list have items remaining? + * + * @return bool True if there are items still to be retrieved from the list, false if not + */ + public function hasItems(): bool + { + return !is_null($this->result); + } + + /** + * Ensure the statement is destroyed if the generator is not exhausted + */ + public function __destruct() + { + if (!is_null($this->result)) $this->result = null; + } } diff --git a/src/Field.php b/src/Field.php index 38c7f10..04c4f68 100644 --- a/src/Field.php +++ b/src/Field.php @@ -48,6 +48,7 @@ class Field * Get the WHERE clause fragment for this parameter * * @return string The WHERE clause fragment for this parameter + * @throws DocumentException If the database mode has not been set */ public function toWhere(): string { @@ -57,7 +58,18 @@ class Field default => " $this->paramName" }; $prefix = $this->qualifier == '' ? '' : "$this->qualifier."; - return "{$prefix}data->>'$this->fieldName' " . $this->op->toString() . $criteria; + $fieldPath = match (Configuration::$mode) { + Mode::SQLite => "{$prefix}data->>'" + . (str_contains($this->fieldName, '.') + ? implode("'->>'", explode('.', $this->fieldName)) + : $this->fieldName) + . "'", + Mode::PgSQL => $this->op == Op::BT && is_numeric($this->value[0]) + ? "({$prefix}data->>'$this->fieldName')::numeric" + : "{$prefix}data->>'$this->fieldName'", + default => throw new DocumentException('Database mode not set; cannot make field WHERE clause') + }; + return $fieldPath . ' ' . $this->op->toString() . $criteria; } /** diff --git a/src/Parameters.php b/src/Parameters.php index 0d32fab..2e13830 100644 --- a/src/Parameters.php +++ b/src/Parameters.php @@ -72,7 +72,7 @@ class Parameters case Mode::SQLite: $it = []; $idx = 0; - foreach ($fieldNames as $field) $it[$paramName . $idx++] = $field; + foreach ($fieldNames as $field) $it[$paramName . $idx++] = "$.$field"; return $it; default: throw new DocumentException('Database mode not set; cannot generate field name parameters'); diff --git a/tests/integration/sqlite/CountTest.php b/tests/integration/sqlite/CountTest.php index 30e3c0d..259bf28 100644 --- a/tests/integration/sqlite/CountTest.php +++ b/tests/integration/sqlite/CountTest.php @@ -3,8 +3,13 @@ namespace Test\Integration\SQLite; use BitBadger\PDODocument\{Count, Field}; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; +/** + * SQLite integration tests for the Count class + */ +#[TestDox('Count (SQLite integration)')] class CountTest extends TestCase { /** @var string Database name for throwaway database */ diff --git a/tests/integration/sqlite/CustomTest.php b/tests/integration/sqlite/CustomTest.php index 31c1d74..bf83bdd 100644 --- a/tests/integration/sqlite/CustomTest.php +++ b/tests/integration/sqlite/CustomTest.php @@ -4,12 +4,14 @@ namespace Test\Integration\SQLite; use BitBadger\PDODocument\{Count, Custom, DocumentException, Query}; use BitBadger\PDODocument\Mapper\{CountMapper, DocumentMapper}; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; use Test\Integration\TestDocument; /** * SQLite Integration tests for the Custom class */ +#[TestDox('Custom (SQLite integration)')] class CustomTest extends TestCase { /** @var string Database name for throwaway database */ @@ -61,9 +63,7 @@ class CustomTest extends TestCase $list = Custom::list(Query::selectFromTable(ThrowawayDb::TABLE) . " WHERE data->>'num_value' > :value", [':value' => 100], new DocumentMapper(TestDocument::class)); $this->assertNotNull($list, 'The document list should not be null'); - $count = 0; - foreach ($list->items() as $ignored) $count++; - $this->assertEquals(0, $count, 'There should have been no documents in the list'); + $this->assertFalse($list->hasItems(), 'There should have been no documents in the list'); } public function testArraySucceedsWhenDataIsFound() diff --git a/tests/integration/sqlite/DefinitionTest.php b/tests/integration/sqlite/DefinitionTest.php index 44f248e..59b192f 100644 --- a/tests/integration/sqlite/DefinitionTest.php +++ b/tests/integration/sqlite/DefinitionTest.php @@ -4,11 +4,13 @@ namespace Test\Integration\SQLite; use BitBadger\PDODocument\{Custom, Definition, DocumentException}; use BitBadger\PDODocument\Mapper\ExistsMapper; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; /** * SQLite integration tests for the Definition class */ +#[TestDox('Definition (SQLite integration)')] class DefinitionTest extends TestCase { /** @var string Database name for throwaway database */ diff --git a/tests/integration/sqlite/DeleteTest.php b/tests/integration/sqlite/DeleteTest.php new file mode 100644 index 0000000..58a664a --- /dev/null +++ b/tests/integration/sqlite/DeleteTest.php @@ -0,0 +1,59 @@ +dbName = ThrowawayDb::create(); + } + + protected function tearDown(): void + { + ThrowawayDb::destroy($this->dbName); + parent::tearDown(); + } + + #[TestDox('By ID succeeds when a document is deleted')] + public function testByIdSucceedsWhenADocumentIsDeleted(): void + { + $this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents to start'); + Delete::byId(ThrowawayDb::TABLE, 'four'); + $this->assertEquals(4, Count::all(ThrowawayDb::TABLE), 'There should have been 4 documents remaining'); + } + + #[TestDox('By ID succeeds when a document is not deleted')] + public function testByIdSucceedsWhenADocumentIsNotDeleted(): void + { + $this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents to start'); + Delete::byId(ThrowawayDb::TABLE, 'negative four'); + $this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents remaining'); + } + + public function testByFieldsSucceedsWhenDocumentsAreDeleted(): void + { + $this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents to start'); + Delete::byFields(ThrowawayDb::TABLE, [Field::NE('value', 'purple')]); + $this->assertEquals(2, Count::all(ThrowawayDb::TABLE), 'There should have been 2 documents remaining'); + } + + public function testByFieldsSucceedsWhenDocumentsAreNotDeleted(): void + { + $this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents to start'); + Delete::byFields(ThrowawayDb::TABLE, [Field::EQ('value', 'crimson')]); + $this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents remaining'); + } +} diff --git a/tests/integration/sqlite/DocumentListTest.php b/tests/integration/sqlite/DocumentListTest.php new file mode 100644 index 0000000..08ff618 --- /dev/null +++ b/tests/integration/sqlite/DocumentListTest.php @@ -0,0 +1,73 @@ +dbName = ThrowawayDb::create(); + } + + protected function tearDown(): void + { + ThrowawayDb::destroy($this->dbName); + parent::tearDown(); + } + + public function testCreateSucceeds(): void + { + $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], + new DocumentMapper(TestDocument::class)); + $this->assertNotNull($list, 'There should have been a document list created'); + $list = null; + } + + public function testItems(): void + { + $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], + new DocumentMapper(TestDocument::class)); + $this->assertNotNull($list, 'There should have been a document list created'); + $count = 0; + foreach ($list->items() as $item) { + $this->assertContains($item->id, ['one', 'two', 'three', 'four', 'five'], + 'An unexpected document ID was returned'); + $count++; + } + $this->assertEquals(5, $count, 'There should have been 5 documents returned'); + } + + public function testHasItemsSucceedsWithEmptyResults(): void + { + $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE) . " WHERE data->>'num_value' < 0", [], + new DocumentMapper(TestDocument::class)); + $this->assertNotNull($list, 'There should have been a document list created'); + $this->assertFalse($list->hasItems(), 'There should be no items in the list'); + } + + public function testHasItemsSucceedsWithNonEmptyResults(): void + { + $list = DocumentList::create(Query::selectFromTable(ThrowawayDb::TABLE), [], + new DocumentMapper(TestDocument::class)); + $this->assertNotNull($list, 'There should have been a document list created'); + $this->assertTrue($list->hasItems(), 'There should be items in the list'); + foreach ($list->items() as $ignored) { + $this->assertTrue($list->hasItems(), 'There should be items remaining in the list'); + } + $this->assertFalse($list->hasItems(), 'There should be no remaining items in the list'); + } +} diff --git a/tests/integration/sqlite/DocumentTest.php b/tests/integration/sqlite/DocumentTest.php index ca5309b..2616633 100644 --- a/tests/integration/sqlite/DocumentTest.php +++ b/tests/integration/sqlite/DocumentTest.php @@ -3,12 +3,14 @@ namespace Test\Integration\SQLite; use BitBadger\PDODocument\{Document, DocumentException, Find}; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; use Test\Integration\{SubDocument, TestDocument}; /** * SQLite integration tests for the Document class */ +#[TestDox('Document (SQLite integration)')] class DocumentTest extends TestCase { /** @var string Database name for throwaway database */ diff --git a/tests/integration/sqlite/ExistsTest.php b/tests/integration/sqlite/ExistsTest.php new file mode 100644 index 0000000..0454b10 --- /dev/null +++ b/tests/integration/sqlite/ExistsTest.php @@ -0,0 +1,54 @@ +dbName = ThrowawayDb::create(); + } + + protected function tearDown(): void + { + ThrowawayDb::destroy($this->dbName); + parent::tearDown(); + } + + #[TestDox('By ID succeeds when a document exists')] + public function testByIdSucceedsWhenADocumentExists(): void + { + $this->assertTrue(Exists::byId(ThrowawayDb::TABLE, 'three'), 'There should have been an existing document'); + } + + #[TestDox('By ID succeeds when a document does not exist')] + public function testByIdSucceedsWhenADocumentDoesNotExist(): void + { + $this->assertFalse(Exists::byId(ThrowawayDb::TABLE, 'seven'), + 'There should not have been an existing document'); + } + + public function testByFieldsSucceedsWhenDocumentsExist(): void + { + $this->assertTrue(Exists::byFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 10)]), + 'There should have been existing documents'); + } + + public function testByFieldsSucceedsWhenNoMatchingDocumentsExist(): void + { + $this->assertFalse(Exists::byFields(ThrowawayDb::TABLE, [Field::LT('nothing', 'none')]), + 'There should not have been any existing documents'); + } +} diff --git a/tests/integration/sqlite/FindTest.php b/tests/integration/sqlite/FindTest.php new file mode 100644 index 0000000..7552a51 --- /dev/null +++ b/tests/integration/sqlite/FindTest.php @@ -0,0 +1,100 @@ +dbName = ThrowawayDb::create(); + } + + protected function tearDown(): void + { + ThrowawayDb::destroy($this->dbName); + parent::tearDown(); + } + + public function testAllSucceedsWhenThereIsData(): void + { + $docs = Find::all(ThrowawayDb::TABLE, TestDocument::class); + $this->assertNotNull($docs, 'There should have been a document list returned'); + $count = 0; + foreach ($docs->items() as $ignored) $count++; + $this->assertEquals(5, $count, 'There should have been 5 documents in the list'); + } + + public function testAllSucceedsWhenThereIsNoData(): void + { + Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); + $docs = Find::all(ThrowawayDb::TABLE, TestDocument::class); + $this->assertNotNull($docs, 'There should have been a document list returned'); + $this->assertFalse($docs->hasItems(), 'There should have been no documents in the list'); + } + + #[TestDox('By ID succeeds when a document is found')] + public function testByIdSucceedsWhenADocumentIsFound(): void + { + $doc = Find::byId(ThrowawayDb::TABLE, 'two', TestDocument::class); + $this->assertNotFalse($doc, 'There should have been a document returned'); + $this->assertEquals('two', $doc->id, 'An incorrect document was returned'); + } + + #[TestDox('By ID succeeds when a document is not found')] + public function testByIdSucceedsWhenADocumentIsNotFound(): void + { + $doc = Find::byId(ThrowawayDb::TABLE, 'seventy-five', TestDocument::class); + $this->assertFalse($doc, 'There should not have been a document returned'); + } + + public function testByFieldsSucceedsWhenDocumentsAreFound(): void + { + $docs = Find::byFields(ThrowawayDb::TABLE, [Field::GT('num_value', 15)], TestDocument::class); + $this->assertNotNull($docs, 'There should have been a document list returned'); + $count = 0; + foreach ($docs->items() as $ignored) $count++; + $this->assertEquals(2, $count, 'There should have been 2 documents in the list'); + } + + public function testByFieldsSucceedsWhenNoDocumentsAreFound(): void + { + $docs = Find::byFields(ThrowawayDb::TABLE, [Field::GT('num_value', 100)], TestDocument::class); + $this->assertNotNull($docs, 'There should have been a document list returned'); + $count = 0; + foreach ($docs->items() as $ignored) $count++; + $this->assertFalse($docs->hasItems(), 'There should have been no documents in the list'); + } + + public function testFirstByFieldsSucceedsWhenADocumentIsFound(): void + { + $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'another')], TestDocument::class); + $this->assertNotFalse($doc, 'There should have been a document returned'); + $this->assertEquals('two', $doc->id, 'The incorrect document was returned'); + } + + public function testFirstByFieldsSucceedsWhenMultipleDocumentsAreFound(): void + { + $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('sub.foo', 'green')], TestDocument::class); + $this->assertNotFalse($doc, 'There should have been a document returned'); + $this->assertContains($doc->id, ['two', 'four'], 'An incorrect document was returned'); + } + + public function testFirstByFieldsSucceedsWhenADocumentIsNotFound(): void + { + $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'absent')], TestDocument::class); + $this->assertFalse($doc, 'There should not have been a document returned'); + } +} diff --git a/tests/integration/sqlite/PatchTest.php b/tests/integration/sqlite/PatchTest.php index b891501..ce64f24 100644 --- a/tests/integration/sqlite/PatchTest.php +++ b/tests/integration/sqlite/PatchTest.php @@ -10,6 +10,7 @@ use Test\Integration\TestDocument; /** * SQLite integration tests for the Patch class */ +#[TestDox('Patch (SQLite integration)')] class PatchTest extends TestCase { /** @var string Database name for throwaway database */ diff --git a/tests/integration/sqlite/RemoveFieldsTest.php b/tests/integration/sqlite/RemoveFieldsTest.php new file mode 100644 index 0000000..e1b3cf1 --- /dev/null +++ b/tests/integration/sqlite/RemoveFieldsTest.php @@ -0,0 +1,74 @@ +dbName = ThrowawayDb::create(); + } + + protected function tearDown(): void + { + ThrowawayDb::destroy($this->dbName); + parent::tearDown(); + } + + #[TestDox('By ID succeeds when fields are removed')] + public function testByIdSucceedsWhenFieldsAreRemoved(): void + { + RemoveFields::byId(ThrowawayDb::TABLE, 'two', ['sub', 'value']); + $doc = Find::byId(ThrowawayDb::TABLE, 'two', TestDocument::class); + $this->assertNotFalse($doc, 'There should have been a document returned'); + $this->assertEquals('', $doc->value, 'Value should have been blank (its default value)'); + $this->assertNull($doc->sub, 'Sub-document should have been null'); + } + + #[TestDox('By ID succeeds when a field is not removed')] + public function testByIdSucceedsWhenAFieldIsNotRemoved(): void + { + RemoveFields::byId(ThrowawayDb::TABLE, 'one', ['a_field_that_does_not_exist']); + $this->assertTrue(true, 'The above not throwing an exception is the test'); + } + + #[TestDox('By ID succeeds when no document is matched')] + public function testByIdSucceedsWhenNoDocumentIsMatched(): void + { + RemoveFields::byId(ThrowawayDb::TABLE, 'fifty', ['sub']); + $this->assertTrue(true, 'The above not throwing an exception is the test'); + } + + public function testByFieldsSucceedsWhenAFieldIsRemoved(): void + { + RemoveFields::byFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 17)], ['sub']); + $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 17)], TestDocument::class); + $this->assertNotFalse($doc, 'There should have been a document returned'); + $this->assertNull($doc->sub, 'Sub-document should have been null'); + } + + public function testByFieldsSucceedsWhenAFieldIsNotRemoved(): void + { + RemoveFields::byFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 17)], ['nada']); + $this->assertTrue(true, 'The above not throwing an exception is the test'); + } + + public function testByFieldsSucceedsWhenNoDocumentIsMatched(): void + { + RemoveFields::byFields(ThrowawayDb::TABLE, [Field::NE('missing', 'nope')], ['value']); + $this->assertTrue(true, 'The above not throwing an exception is the test'); + } +} diff --git a/tests/unit/FieldTest.php b/tests/unit/FieldTest.php index f2f76e3..421d131 100644 --- a/tests/unit/FieldTest.php +++ b/tests/unit/FieldTest.php @@ -2,7 +2,7 @@ namespace Test\Unit; -use BitBadger\PDODocument\{Field, Op}; +use BitBadger\PDODocument\{Configuration, Field, Mode, Op}; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -36,46 +36,236 @@ class FieldTest extends TestCase 'Field parameter not returned correctly'); } - #[TestDox('To where succeeds for EX without qualifier')] - public function testToWhereSucceedsForEXWithoutQualifier(): void + #[TestDox('To where succeeds for EX without qualifier for PostgreSQL')] + public function testToWhereSucceedsForEXWithoutQualifierForPostgreSQL(): void { - $this->assertEquals("data->>'that_field' IS NOT NULL", Field::EX('that_field')->toWhere(), - 'WHERE fragment not generated correctly'); + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals("data->>'that_field' IS NOT NULL", Field::EX('that_field')->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } } - #[TestDox('To where succeeds for NEX without qualifier')] - public function testToWhereSucceedsForNEXWithoutQualifier(): void + #[TestDox('To where succeeds for EX without qualifier for SQLite')] + public function testToWhereSucceedsForEXWithoutQualifierForSQLite(): void { - $this->assertEquals("data->>'a_field' IS NULL", Field::NEX('a_field')->toWhere(), - 'WHERE fragment not generated correctly'); + Configuration::$mode = Mode::SQLite; + try { + $this->assertEquals("data->>'that_field' IS NOT NULL", Field::EX('that_field')->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } } - #[TestDox('To where succeeds for BT without qualifier')] - public function testToWhereSucceedsForBTWithoutQualifier(): void + #[TestDox('To where succeeds for NEX without qualifier for PostgreSQL')] + public function testToWhereSucceedsForNEXWithoutQualifierForPostgreSQL(): void { - $this->assertEquals("data->>'age' BETWEEN @agemin AND @agemax", Field::BT('age', 13, 17, '@age')->toWhere(), - 'WHERE fragment not generated correctly'); + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals("data->>'a_field' IS NULL", Field::NEX('a_field')->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } } - public function testToWhereSucceedsForOthersWithoutQualifier(): void + #[TestDox('To where succeeds for NEX without qualifier for SQLite')] + public function testToWhereSucceedsForNEXWithoutQualifierForSQLite(): void { - $this->assertEquals("data->>'some_field' = @value", Field::EQ('some_field', '', '@value')->toWhere(), - 'WHERE fragment not generated correctly'); + Configuration::$mode = Mode::SQLite; + try { + $this->assertEquals("data->>'a_field' IS NULL", Field::NEX('a_field')->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } } - public function testToWhereSucceedsWithQualifierNoParameter(): void + #[TestDox('To where succeeds for BT without qualifier for SQLite')] + public function testToWhereSucceedsForBTWithoutQualifierForSQLite(): void { - $field = Field::EX('no_field'); - $field->qualifier = 'test'; - $this->assertEquals("test.data->>'no_field' IS NOT NULL", $field->toWhere(), - 'WHERE fragment not generated correctly'); + Configuration::$mode = Mode::SQLite; + try { + $this->assertEquals("data->>'age' BETWEEN @agemin AND @agemax", Field::BT('age', 13, 17, '@age')->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } } - public function testToWhereSucceedsWithQualifierAndParameter(): void + #[TestDox('To where succeeds for BT without qualifier for PostgreSQL with numeric range')] + public function testToWhereSucceedsForBTWithoutQualifierForPostgreSQLWithNumericRange(): void { - $field = Field::LE('le_field', 18, '@it'); - $field->qualifier = 'q'; - $this->assertEquals("q.data->>'le_field' <= @it", $field->toWhere(), 'WHERE fragment not generated correctly'); + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals("(data->>'age')::numeric BETWEEN @agemin AND @agemax", + Field::BT('age', 13, 17, '@age')->toWhere(), 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds for BT without qualifier for PostgreSQL with non-numeric range')] + public function testToWhereSucceedsForBTWithoutQualifierForPostgreSQLWithNonNumericRange(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals("data->>'city' BETWEEN :citymin AND :citymax", + Field::BT('city', 'Atlanta', 'Chicago', ':city')->toWhere(), 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds for BT with qualifier for SQLite')] + public function testToWhereSucceedsForBTWithQualifierForSQLite(): void + { + Configuration::$mode = Mode::SQLite; + try { + $field = Field::BT('age', 13, 17, '@age'); + $field->qualifier = 'me'; + $this->assertEquals("me.data->>'age' BETWEEN @agemin AND @agemax", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds for BT with qualifier for PostgreSQL with numeric range')] + public function testToWhereSucceedsForBTWithQualifierForPostgreSQLWithNumericRange(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $field = Field::BT('age', 13, 17, '@age'); + $field->qualifier = 'me'; + $this->assertEquals("(me.data->>'age')::numeric BETWEEN @agemin AND @agemax", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds for BT with qualifier for PostgreSQL with non-numeric range')] + public function testToWhereSucceedsForBTWithQualifierForPostgreSQLWithNonNumericRange(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $field = Field::BT('city', 'Atlanta', 'Chicago', ':city'); + $field->qualifier = 'me'; + $this->assertEquals("me.data->>'city' BETWEEN :citymin AND :citymax", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds for others without qualifier for PostgreSQL')] + public function testToWhereSucceedsForOthersWithoutQualifierForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $this->assertEquals("data->>'some_field' = @value", Field::EQ('some_field', '', '@value')->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds for others without qualifier for SQLite')] + public function testToWhereSucceedsForOthersWithoutQualifierForSQLite(): void + { + Configuration::$mode = Mode::SQLite; + try { + $this->assertEquals("data->>'some_field' = @value", Field::EQ('some_field', '', '@value')->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds with qualifier no parameter for PostgreSQL')] + public function testToWhereSucceedsWithQualifierNoParameterForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $field = Field::EX('no_field'); + $field->qualifier = 'test'; + $this->assertEquals("test.data->>'no_field' IS NOT NULL", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds with qualifier no parameter for SQLite')] + public function testToWhereSucceedsWithQualifierNoParameterForSQLite(): void + { + Configuration::$mode = Mode::SQLite; + try { + $field = Field::EX('no_field'); + $field->qualifier = 'test'; + $this->assertEquals("test.data->>'no_field' IS NOT NULL", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds with qualifier and parameter for PostgreSQL')] + public function testToWhereSucceedsWithQualifierAndParameterForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $field = Field::LE('le_field', 18, '@it'); + $field->qualifier = 'q'; + $this->assertEquals("q.data->>'le_field' <= @it", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds with qualifier and parameter for SQLite')] + public function testToWhereSucceedsWithQualifierAndParameterForSQLite(): void + { + Configuration::$mode = Mode::SQLite; + try { + $field = Field::LE('le_field', 18, '@it'); + $field->qualifier = 'q'; + $this->assertEquals("q.data->>'le_field' <= @it", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds with sub-document for PostgreSQL')] + public function testToWhereSucceedsWithSubDocumentForPostgreSQL(): void + { + Configuration::$mode = Mode::PgSQL; + try { + $field = Field::EQ('sub.foo.bar', 22, '@it'); + $this->assertEquals("data->>'sub.foo.bar' = @it", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } + } + + #[TestDox('To where succeeds with sub-document for SQLite')] + public function testToWhereSucceedsWithSubDocumentForSQLite(): void + { + Configuration::$mode = Mode::SQLite; + try { + $field = Field::EQ('sub.foo.bar', 22, '@it'); + $this->assertEquals("data->>'sub'->>'foo'->>'bar' = @it", $field->toWhere(), + 'WHERE fragment not generated correctly'); + } finally { + Configuration::$mode = null; + } } #[TestDox('EQ succeeds without parameter')] diff --git a/tests/unit/ParametersTest.php b/tests/unit/ParametersTest.php index be9d236..a633124 100644 --- a/tests/unit/ParametersTest.php +++ b/tests/unit/ParametersTest.php @@ -63,7 +63,7 @@ class ParametersTest extends TestCase { try { Configuration::$mode = Mode::SQLite; - $this->assertEquals([':it0' => 'test', ':it1' => 'unit', ':it2' => 'wow'], + $this->assertEquals([':it0' => '$.test', ':it1' => '$.unit', ':it2' => '$.wow'], Parameters::fieldNames(':it', ['test', 'unit', 'wow']), 'Field name parameters not correct'); } finally { Configuration::$mode = null; diff --git a/tests/unit/Query/CountTest.php b/tests/unit/Query/CountTest.php index 0a70527..69eb6a7 100644 --- a/tests/unit/Query/CountTest.php +++ b/tests/unit/Query/CountTest.php @@ -2,7 +2,7 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\Field; +use BitBadger\PDODocument\{Configuration, Field, Mode}; use BitBadger\PDODocument\Query\Count; use PHPUnit\Framework\TestCase; @@ -20,7 +20,12 @@ class CountTest extends TestCase public function testByFieldsSucceeds() { - $this->assertEquals("SELECT COUNT(*) FROM somewhere WHERE data->>'errors' > :errors", - Count::byFields('somewhere', [Field::GT('errors', 10, ':errors')])); + 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; + } } } diff --git a/tests/unit/Query/DeleteTest.php b/tests/unit/Query/DeleteTest.php index edf21bc..1ac3c7b 100644 --- a/tests/unit/Query/DeleteTest.php +++ b/tests/unit/Query/DeleteTest.php @@ -2,7 +2,7 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\Field; +use BitBadger\PDODocument\{Configuration, Field, Mode}; use BitBadger\PDODocument\Query\Delete; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -12,6 +12,16 @@ use PHPUnit\Framework\TestCase; */ class DeleteTest extends TestCase { + protected function setUp(): void + { + Configuration::$mode = Mode::SQLite; + } + + protected function tearDown(): void + { + Configuration::$mode = null; + } + #[TestDox('By ID succeeds')] public function testByIdSucceeds(): void { diff --git a/tests/unit/Query/ExistsTest.php b/tests/unit/Query/ExistsTest.php index 32fbae2..416c9dd 100644 --- a/tests/unit/Query/ExistsTest.php +++ b/tests/unit/Query/ExistsTest.php @@ -2,7 +2,7 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\Field; +use BitBadger\PDODocument\{Configuration, Field, Mode}; use BitBadger\PDODocument\Query\Exists; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -12,6 +12,16 @@ use PHPUnit\Framework\TestCase; */ class ExistsTest extends TestCase { + protected function setUp(): void + { + Configuration::$mode = Mode::SQLite; + } + + protected function tearDown(): void + { + Configuration::$mode = null; + } + public function testQuerySucceeds(): void { $this->assertEquals('SELECT EXISTS (SELECT 1 FROM abc WHERE def)', Exists::query('abc', 'def'), diff --git a/tests/unit/Query/FindTest.php b/tests/unit/Query/FindTest.php index 65bc571..5b2fb9a 100644 --- a/tests/unit/Query/FindTest.php +++ b/tests/unit/Query/FindTest.php @@ -2,7 +2,7 @@ namespace Test\Unit\Query; -use BitBadger\PDODocument\Field; +use BitBadger\PDODocument\{Configuration, Field, Mode}; use BitBadger\PDODocument\Query\Find; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -12,6 +12,16 @@ use PHPUnit\Framework\TestCase; */ class FindTest extends TestCase { + protected function setUp(): void + { + Configuration::$mode = Mode::SQLite; + } + + protected function tearDown(): void + { + Configuration::$mode = null; + } + #[TestDox('By ID succeeds')] public function testByIdSucceeds(): void { diff --git a/tests/unit/QueryTest.php b/tests/unit/QueryTest.php index 6e00cab..b3ac80b 100644 --- a/tests/unit/QueryTest.php +++ b/tests/unit/QueryTest.php @@ -2,7 +2,7 @@ namespace Test\Unit; -use BitBadger\PDODocument\{Field, Query}; +use BitBadger\PDODocument\{Configuration, Field, Mode, Query}; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; @@ -11,6 +11,16 @@ use PHPUnit\Framework\TestCase; */ class QueryTest extends TestCase { + protected function setUp(): void + { + Configuration::$mode = Mode::SQLite; + } + + protected function tearDown(): void + { + Configuration::$mode = null; + } + public function testSelectFromTableSucceeds(): void { $this->assertEquals('SELECT data FROM testing', Query::selectFromTable('testing'),