Add byContains and byJsonPath throughout
- Change to JSON Path function to work around PDO syntax quirk
This commit is contained in:
parent
9ecabbe39f
commit
7de39a41fc
@ -36,4 +36,31 @@ class Count
|
||||
return Custom::scalar(Query\Count::byFields($tableName, $namedFields, $match),
|
||||
Parameters::addFields($namedFields, []), new CountMapper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Count matching documents using a JSON containment query (`@>`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The name of the table in which documents should be counted
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @return int The number of documents matching the JSON containment query
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byContains(string $tableName, array|object $criteria): int
|
||||
{
|
||||
return Custom::scalar(Query\Count::byContains($tableName), Parameters::json(':criteria', $criteria),
|
||||
new CountMapper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Count matching documents using a JSON Path match query (`@?`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The name of the table in which documents should be counted
|
||||
* @param string $path The JSON Path match string
|
||||
* @return int The number of documents matching the given JSON Path criteria
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byJsonPath(string $tableName, string $path): int
|
||||
{
|
||||
return Custom::scalar(Query\Count::byJsonPath($tableName), [':path' => $path], new CountMapper());
|
||||
}
|
||||
}
|
||||
|
@ -33,4 +33,28 @@ class Delete
|
||||
Custom::nonQuery(Query\Delete::byFields($tableName, $namedFields, $match),
|
||||
Parameters::addFields($namedFields, []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete documents matching a JSON containment query (`@>`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The table from which documents should be deleted
|
||||
* @param array|object $criteria The JSON containment query values
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byContains(string $tableName, array|object $criteria): void
|
||||
{
|
||||
Custom::nonQuery(Query\Delete::byContains($tableName), Parameters::json(':criteria', $criteria));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete documents matching a JSON Path match query (`@?`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The table from which documents should be deleted
|
||||
* @param string $path The JSON Path match string
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byJsonPath(string $tableName, string $path): void
|
||||
{
|
||||
Custom::nonQuery(Query\Delete::byJsonPath($tableName), [':path' => $path]);
|
||||
}
|
||||
}
|
||||
|
@ -37,4 +37,31 @@ class Exists
|
||||
return Custom::scalar(Query\Exists::byFields($tableName, $namedFields, $match),
|
||||
Parameters::addFields($namedFields, []), new ExistsMapper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if documents exist by a JSON containment query (`@>`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The name of the table in which document existence should be determined
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @return bool True if any documents match the JSON containment query, false if not
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byContains(string $tableName, array|object $criteria): bool
|
||||
{
|
||||
return Custom::scalar(Query\Exists::byContains($tableName), Parameters::json(':criteria', $criteria),
|
||||
new ExistsMapper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if documents exist by a JSON Path match query (`@?`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The name of the table in which document existence should be determined
|
||||
* @param string $path The JSON Path match string
|
||||
* @return bool True if any documents match the JSON Path string, false if not
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byJsonPath(string $tableName, string $path): bool
|
||||
{
|
||||
return Custom::scalar(Query\Exists::byJsonPath($tableName), [':path' => $path], new ExistsMapper());
|
||||
}
|
||||
}
|
||||
|
62
src/Find.php
62
src/Find.php
@ -58,6 +58,37 @@ class Find
|
||||
Parameters::addFields($namedFields, []), new DocumentMapper($className));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents via a JSON containment query (`@>`; PostgreSQL only)
|
||||
*
|
||||
* @template TDoc The type of document to be retrieved
|
||||
* @param string $tableName The name of the table from which documents should be retrieved
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @param class-string<TDoc> $className The name of the class to be retrieved
|
||||
* @return DocumentList<TDoc> A list of documents matching the JSON containment query
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byContains(string $tableName, array|object $criteria, string $className): DocumentList
|
||||
{
|
||||
return Custom::list(Query\Find::byContains($tableName), Parameters::json(':criteria', $criteria),
|
||||
new DocumentMapper($className));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents via a JSON Path match query (`@?`; PostgreSQL only)
|
||||
*
|
||||
* @template TDoc The type of document to be retrieved
|
||||
* @param string $tableName The name of the table from which documents should be retrieved
|
||||
* @param string $path The JSON Path match string
|
||||
* @param class-string<TDoc> $className The name of the class to be retrieved
|
||||
* @return DocumentList<TDoc> A list of documents matching the JSON Path
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byJsonPath(string $tableName, string $path, string $className): DocumentList
|
||||
{
|
||||
return Custom::list(Query\Find::byJsonPath($tableName), [':path' => $path], new DocumentMapper($className));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||
*
|
||||
@ -76,4 +107,35 @@ class Find
|
||||
return Custom::single(Query\Find::byFields($tableName, $namedFields, $match),
|
||||
Parameters::addFields($namedFields, []), new DocumentMapper($className));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents via a JSON containment query (`@>`), returning only the first result (PostgreSQL only)
|
||||
*
|
||||
* @template TDoc The type of document to be retrieved
|
||||
* @param string $tableName The name of the table from which documents should be retrieved
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @param class-string<TDoc> $className The name of the class to be retrieved
|
||||
* @return false|TDoc The first document matching the JSON containment query if any is found, false otherwise
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function firstByContains(string $tableName, array|object $criteria, string $className): mixed
|
||||
{
|
||||
return Custom::single(Query\Find::byContains($tableName), Parameters::json(':criteria', $criteria),
|
||||
new DocumentMapper($className));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents via a JSON Path match query (`@?`), returning only the first result (PostgreSQL only)
|
||||
*
|
||||
* @template TDoc The type of document to be retrieved
|
||||
* @param string $tableName The name of the table from which documents should be retrieved
|
||||
* @param string $path The JSON Path match string
|
||||
* @param class-string<TDoc> $className The name of the class to be retrieved
|
||||
* @return false|TDoc The first document matching the JSON Path if any is found, false otherwise
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function firstByJsonPath(string $tableName, string $path, string $className): mixed
|
||||
{
|
||||
return Custom::single(Query\Find::byJsonPath($tableName), [':path' => $path], new DocumentMapper($className));
|
||||
}
|
||||
}
|
||||
|
@ -37,4 +37,32 @@ class Patch
|
||||
Custom::nonQuery(Query\Patch::byFields($tableName, $namedFields, $match),
|
||||
Parameters::addFields($namedFields, Parameters::json(':data', $patch)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch documents using a JSON containment query (`@>`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The table in which documents should be patched
|
||||
* @param array|object $criteria The JSON containment query values to match
|
||||
* @param array|object $patch The object with which the documents should be patched (will be JSON-encoded)
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byContains(string $tableName, array|object $criteria, array|object $patch): void
|
||||
{
|
||||
Custom::nonQuery(Query\Patch::byContains($tableName),
|
||||
array_merge(Parameters::json(':criteria', $criteria), Parameters::json(':data', $patch)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch documents using a JSON Path match query (`@?`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The table in which documents should be patched
|
||||
* @param string $path The JSON Path match string
|
||||
* @param array|object $patch The object with which the documents should be patched (will be JSON-encoded)
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byJsonPath(string $tableName, string $path, array|object $patch): void
|
||||
{
|
||||
Custom::nonQuery(Query\Patch::byJsonPath($tableName),
|
||||
array_merge([':path' => $path], Parameters::json(':data', $patch)));
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ class Query
|
||||
if (Configuration::$mode <> Mode::PgSQL) {
|
||||
throw new DocumentException('JSON Path matching is only supported on PostgreSQL');
|
||||
}
|
||||
return "data @? $paramName::jsonpath";
|
||||
return "jsonb_path_exists(data, $paramName::jsonpath)";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,4 +39,34 @@ class RemoveFields
|
||||
Custom::nonQuery(Query\RemoveFields::byFields($tableName, $namedFields, $nameParams, $match),
|
||||
Parameters::addFields($namedFields, $nameParams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove fields from documents via a JSON containment query (`@>`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The table in which documents should have fields removed
|
||||
* @param array|object $criteria The JSON containment query values
|
||||
* @param array|string[] $fieldNames The names of the fields to be removed
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byContains(string $tableName, array|object $criteria, array $fieldNames): void
|
||||
{
|
||||
$nameParams = Parameters::fieldNames(':name', $fieldNames);
|
||||
Custom::nonQuery(Query\RemoveFields::byContains($tableName, $nameParams),
|
||||
array_merge(Parameters::json(':criteria', $criteria), $nameParams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove fields from documents via a JSON Path match query (`@?`; PostgreSQL only)
|
||||
*
|
||||
* @param string $tableName The table in which documents should have fields removed
|
||||
* @param string $path The JSON Path match string
|
||||
* @param array|string[] $fieldNames The names of the fields to be removed
|
||||
* @throws DocumentException If the database mode is not PostgreSQL, or if an error occurs
|
||||
*/
|
||||
public static function byJsonPath(string $tableName, string $path, array $fieldNames): void
|
||||
{
|
||||
$nameParams = Parameters::fieldNames(':name', $fieldNames);
|
||||
Custom::nonQuery(Query\RemoveFields::byJsonPath($tableName, $nameParams),
|
||||
array_merge([':path' => $path], $nameParams));
|
||||
}
|
||||
}
|
||||
|
@ -44,4 +44,30 @@ class CountTest extends TestCase
|
||||
$count = Count::byFields(ThrowawayDb::TABLE, [Field::BT('value', 'aardvark', 'apple')]);
|
||||
$this->assertEquals(1, $count, 'There should have been 1 matching document');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenDocumentsMatch(): void
|
||||
{
|
||||
$this->assertEquals(2, Count::byContains(ThrowawayDb::TABLE, ['value' => 'purple']),
|
||||
'There should have been 2 matching documents');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenNoDocumentsMatch(): void
|
||||
{
|
||||
$this->assertEquals(0, Count::byContains(ThrowawayDb::TABLE, ['value' => 'magenta']),
|
||||
'There should have been no matching documents');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when documents match')]
|
||||
public function testByJsonPathSucceedsWhenDocumentsMatch(): void
|
||||
{
|
||||
$this->assertEquals(2, Count::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ < 5)'),
|
||||
'There should have been 2 matching documents');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when no documents match')]
|
||||
public function testByJsonPathSucceedsWhenNoDocumentsMatch(): void
|
||||
{
|
||||
$this->assertEquals(0, Count::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ > 100)'),
|
||||
'There should have been no matching documents');
|
||||
}
|
||||
}
|
||||
|
@ -56,4 +56,34 @@ class DeleteTest extends TestCase
|
||||
Delete::byFields(ThrowawayDb::TABLE, [Field::EQ('value', 'crimson')]);
|
||||
$this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents remaining');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenDocumentsAreDeleted(): void
|
||||
{
|
||||
$this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents to start');
|
||||
Delete::byContains(ThrowawayDb::TABLE, ['value' => 'purple']);
|
||||
$this->assertEquals(3, Count::all(ThrowawayDb::TABLE), 'There should have been 3 documents remaining');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenDocumentsAreNotDeleted(): void
|
||||
{
|
||||
$this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents to start');
|
||||
Delete::byContains(ThrowawayDb::TABLE, ['target' => 'acquired']);
|
||||
$this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents remaining');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when documents are deleted')]
|
||||
public function testByJsonPathSucceedsWhenDocumentsAreDeleted(): void
|
||||
{
|
||||
$this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents to start');
|
||||
Delete::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ <> 0)');
|
||||
$this->assertEquals(1, Count::all(ThrowawayDb::TABLE), 'There should have been 1 document remaining');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when documents are not deleted')]
|
||||
public function testByJsonPathSucceedsWhenDocumentsAreNotDeleted(): void
|
||||
{
|
||||
$this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents to start');
|
||||
Delete::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ < 0)');
|
||||
$this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents remaining');
|
||||
}
|
||||
}
|
||||
|
@ -51,4 +51,30 @@ class ExistsTest extends TestCase
|
||||
$this->assertFalse(Exists::byFields(ThrowawayDb::TABLE, [Field::LT('nothing', 'none')]),
|
||||
'There should not have been any existing documents');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenDocumentsExist(): void
|
||||
{
|
||||
$this->assertTrue(Exists::byContains(ThrowawayDb::TABLE, ['value' => 'purple']),
|
||||
'There should have been existing documents');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenNoMatchingDocumentsExist(): void
|
||||
{
|
||||
$this->assertFalse(Exists::byContains(ThrowawayDb::TABLE, ['value' => 'violet']),
|
||||
'There should not have been existing documents');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when documents exist')]
|
||||
public function testByJsonPathSucceedsWhenDocumentsExist(): void
|
||||
{
|
||||
$this->assertTrue(Exists::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ == 10)'),
|
||||
'There should have been existing documents');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when no matching documents exist')]
|
||||
public function testByJsonPathSucceedsWhenNoMatchingDocumentsExist(): void
|
||||
{
|
||||
$this->assertFalse(Exists::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ == 10.1)'),
|
||||
'There should have been existing documents');
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,40 @@ class FindTest extends TestCase
|
||||
$this->assertFalse($docs->hasItems(), 'There should have been no documents in the list');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenDocumentsAreFound(): void
|
||||
{
|
||||
$docs = Find::byContains(ThrowawayDb::TABLE, ['value' => 'purple'], 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 testByContainsSucceedsWhenNoDocumentsAreFound(): void
|
||||
{
|
||||
$docs = Find::byContains(ThrowawayDb::TABLE, ['value' => 'indigo'], TestDocument::class);
|
||||
$this->assertNotNull($docs, 'There should have been a document list returned');
|
||||
$this->assertFalse($docs->hasItems(), 'The document list should be empty');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when documents are found')]
|
||||
public function testByJsonPathSucceedsWhenDocumentsAreFound(): void
|
||||
{
|
||||
$docs = Find::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ > 10)', 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');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when no documents are found')]
|
||||
public function testByJsonPathSucceedsWhenNoDocumentsAreFound(): void
|
||||
{
|
||||
$docs = Find::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ > 100)', TestDocument::class);
|
||||
$this->assertNotNull($docs, 'There should have been a document list returned');
|
||||
$this->assertFalse($docs->hasItems(), 'The document list should be empty');
|
||||
}
|
||||
|
||||
public function testFirstByFieldsSucceedsWhenADocumentIsFound(): void
|
||||
{
|
||||
$doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'another')], TestDocument::class);
|
||||
@ -105,4 +139,46 @@ class FindTest extends TestCase
|
||||
$doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'absent')], TestDocument::class);
|
||||
$this->assertFalse($doc, 'There should not have been a document returned');
|
||||
}
|
||||
|
||||
public function testFirstByContainsSucceedsWhenADocumentIsFound(): void
|
||||
{
|
||||
$doc = Find::firstByContains(ThrowawayDb::TABLE, ['value' => 'FIRST!'], TestDocument::class);
|
||||
$this->assertNotFalse($doc, 'There should have been a document returned');
|
||||
$this->assertEquals('one', $doc->id, 'The incorrect document was returned');
|
||||
}
|
||||
|
||||
public function testFirstByContainsSucceedsWhenMultipleDocumentsAreFound(): void
|
||||
{
|
||||
$doc = Find::firstByContains(ThrowawayDb::TABLE, ['value' => 'purple'], TestDocument::class);
|
||||
$this->assertNotFalse($doc, 'There should have been a document returned');
|
||||
$this->assertContains($doc->id, ['four', 'five'], 'An incorrect document was returned');
|
||||
}
|
||||
|
||||
public function testFirstByContainsSucceedsWhenADocumentIsNotFound(): void
|
||||
{
|
||||
$doc = Find::firstByContains(ThrowawayDb::TABLE, ['value' => 'indigo'], TestDocument::class);
|
||||
$this->assertFalse($doc, 'There should not have been a document returned');
|
||||
}
|
||||
|
||||
#[TestDox('First by JSON Path succeeds when a document is found')]
|
||||
public function testFirstByJsonPathSucceedsWhenADocumentIsFound(): void
|
||||
{
|
||||
$doc = Find::firstByJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ == 10)', TestDocument::class);
|
||||
$this->assertEquals('two', $doc->id, 'The incorrect document was returned');
|
||||
}
|
||||
|
||||
#[TestDox('First by JSON Path succeeds when multiple documents are found')]
|
||||
public function testFirstByJsonPathSucceedsWhenMultipleDocumentsAreFound(): void
|
||||
{
|
||||
$doc = Find::firstByJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ > 10)', TestDocument::class);
|
||||
$this->assertNotFalse($doc, 'There should have been a document returned');
|
||||
$this->assertContains($doc->id, ['four', 'five'], 'An incorrect document was returned');
|
||||
}
|
||||
|
||||
#[TestDox('First by JSON Path succeeds when a document is not found')]
|
||||
public function testFirstByJsonPathSucceedsWhenADocumentIsNotFound(): void
|
||||
{
|
||||
$doc = Find::firstByJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ > 100)', TestDocument::class);
|
||||
$this->assertFalse($doc, 'There should not have been a document returned');
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Test\Integration\PostgreSQL;
|
||||
|
||||
use BitBadger\PDODocument\{Count, Field, Find, Patch};
|
||||
use BitBadger\PDODocument\{Count, Exists, Field, Find, Patch};
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Test\Integration\TestDocument;
|
||||
@ -40,7 +40,9 @@ class PatchTest extends TestCase
|
||||
#[TestDox('By ID succeeds when no document is updated')]
|
||||
public function testByIdSucceedsWhenNoDocumentIsUpdated(): void
|
||||
{
|
||||
Patch::byId(ThrowawayDb::TABLE, 'forty-seven', ['foo' => 'green']);
|
||||
$id = 'forty-seven';
|
||||
$this->assertFalse(Exists::byId(ThrowawayDb::TABLE, $id), 'The document should not exist');
|
||||
Patch::byId(ThrowawayDb::TABLE, $id, ['foo' => 'green']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
@ -53,7 +55,50 @@ class PatchTest extends TestCase
|
||||
|
||||
public function testByFieldsSucceedsWhenNoDocumentIsUpdated(): void
|
||||
{
|
||||
Patch::byFields(ThrowawayDb::TABLE, [Field::EQ('value', 'burgundy')], ['foo' => 'green']);
|
||||
$fields = [Field::EQ('value', 'burgundy')];
|
||||
$this->assertEquals(0, Count::byFields(ThrowawayDb::TABLE, $fields), 'There should be no matching documents');
|
||||
Patch::byFields(ThrowawayDb::TABLE, $fields, ['foo' => 'green']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenDocumentsAreUpdated(): void
|
||||
{
|
||||
Patch::byContains(ThrowawayDb::TABLE, ['value' => 'another'], ['num_value' => 12]);
|
||||
$doc = Find::firstByContains(ThrowawayDb::TABLE, ['value' => 'another'], TestDocument::class);
|
||||
$this->assertNotFalse($doc, 'There should have been a document returned');
|
||||
$this->assertEquals('two', $doc->id, 'An incorrect document was returned');
|
||||
$this->assertEquals(12, $doc->num_value, 'The document was not patched');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenNoDocumentsAreUpdated(): void
|
||||
{
|
||||
$criteria = ['value' => 'updated'];
|
||||
$this->assertEquals(0, Count::byContains(ThrowawayDb::TABLE, $criteria),
|
||||
'There should be no matching documents');
|
||||
Patch::byContains(ThrowawayDb::TABLE, $criteria, ['sub.foo' => 'green']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when documents are updated')]
|
||||
public function testByJsonPathSucceedsWhenDocumentsAreUpdated(): void
|
||||
{
|
||||
Patch::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ > 10)', ['value' => 'blue']);
|
||||
$docs = Find::byJsonPath(ThrowawayDb::TABLE, '$.num_value ? (@ > 10)', TestDocument::class);
|
||||
$this->assertNotNull($docs, 'There should have been a document list returned');
|
||||
$this->assertTrue($docs->hasItems(), 'The document list should not be empty');
|
||||
foreach ($docs->items() as $item) {
|
||||
$this->assertContains($item->id, ['four', 'five'], 'An incorrect document was returned');
|
||||
$this->assertEquals('blue', $item->value, 'The document was not patched');
|
||||
}
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when documents are not updated')]
|
||||
public function testByJsonPathSucceedsWhenDocumentsAreNotUpdated(): void
|
||||
{
|
||||
$path = '$.num_value ? (@ > 100)';
|
||||
$this->assertEquals(0, Count::byJsonPath(ThrowawayDb::TABLE, $path),
|
||||
'There should be no documents matching this path');
|
||||
Patch::byJsonPath(ThrowawayDb::TABLE, $path, ['value' => 'blue']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
}
|
||||
|
@ -71,4 +71,57 @@ class RemoveFieldsTest extends TestCase
|
||||
RemoveFields::byFields(ThrowawayDb::TABLE, [Field::NE('missing', 'nope')], ['value']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenAFieldIsRemoved(): void
|
||||
{
|
||||
$criteria = ['sub' => ['foo' => 'green']];
|
||||
RemoveFields::byContains(ThrowawayDb::TABLE, $criteria, ['value']);
|
||||
$docs = Find::byContains(ThrowawayDb::TABLE, $criteria, TestDocument::class);
|
||||
$this->assertNotNull($docs, 'There should have been a document list returned');
|
||||
$this->assertTrue($docs->hasItems(), 'The document list should not have been empty');
|
||||
foreach ($docs->items() as $item) {
|
||||
$this->assertContains($item->id, ['two', 'four'], 'An incorrect document was returned');
|
||||
$this->assertEquals('', $item->value, 'The value field was not removed');
|
||||
}
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenAFieldIsNotRemoved(): void
|
||||
{
|
||||
RemoveFields::byContains(ThrowawayDb::TABLE, ['sub' => ['foo' => 'green']], ['invalid_field']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
public function testByContainsSucceedsWhenNoDocumentIsMatched(): void
|
||||
{
|
||||
RemoveFields::byContains(ThrowawayDb::TABLE, ['value' => 'substantial'], ['num_value']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when a field is removed')]
|
||||
public function testByJsonPathSucceedsWhenAFieldIsRemoved(): void
|
||||
{
|
||||
$path = '$.value ? (@ == "purple")';
|
||||
RemoveFields::byJsonPath(ThrowawayDb::TABLE, $path, ['sub']);
|
||||
$docs = Find::byJsonPath(ThrowawayDb::TABLE, $path, TestDocument::class);
|
||||
$this->assertNotNull($docs, 'There should have been a document list returned');
|
||||
$this->assertTrue($docs->hasItems(), 'The document list should not have been empty');
|
||||
foreach ($docs->items() as $item) {
|
||||
$this->assertContains($item->id, ['four', 'five'], 'An incorrect document was returned');
|
||||
$this->assertNull($item->sub, 'The sub field was not removed');
|
||||
}
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when a field is not removed')]
|
||||
public function testByJsonPathSucceedsWhenAFieldIsNotRemoved(): void
|
||||
{
|
||||
RemoveFields::byJsonPath(ThrowawayDb::TABLE, '$.value ? (@ == "purple")', ['submarine']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path succeeds when no document is matched')]
|
||||
public function testByJsonPathSucceedsWhenNoDocumentIsMatched(): void
|
||||
{
|
||||
RemoveFields::byJsonPath(ThrowawayDb::TABLE, '$.value ? (@ == "mauve")', ['value']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Test\Integration\SQLite;
|
||||
|
||||
use BitBadger\PDODocument\{Count, Field};
|
||||
use BitBadger\PDODocument\{Count, DocumentException, Field};
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@ -44,4 +44,17 @@ class CountTest extends TestCase
|
||||
$count = Count::byFields(ThrowawayDb::TABLE, [Field::BT('value', 'aardvark', 'apple')]);
|
||||
$this->assertEquals(1, $count, 'There should have been 1 matching document');
|
||||
}
|
||||
|
||||
public function testByContainsFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Count::byContains('', []);
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails')]
|
||||
public function testByJsonPathFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Count::byJsonPath('', '');
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Test\Integration\SQLite;
|
||||
|
||||
use BitBadger\PDODocument\{Count, Delete, Field};
|
||||
use BitBadger\PDODocument\{Count, Delete, DocumentException, Field};
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@ -56,4 +56,17 @@ class DeleteTest extends TestCase
|
||||
Delete::byFields(ThrowawayDb::TABLE, [Field::EQ('value', 'crimson')]);
|
||||
$this->assertEquals(5, Count::all(ThrowawayDb::TABLE), 'There should have been 5 documents remaining');
|
||||
}
|
||||
|
||||
public function testByContainsFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Delete::byContains('', []);
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails')]
|
||||
public function testByJsonPathFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Delete::byJsonPath('', '');
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Test\Integration\SQLite;
|
||||
|
||||
use BitBadger\PDODocument\{Exists, Field};
|
||||
use BitBadger\PDODocument\{DocumentException, Exists, Field};
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@ -51,4 +51,18 @@ class ExistsTest extends TestCase
|
||||
$this->assertFalse(Exists::byFields(ThrowawayDb::TABLE, [Field::LT('nothing', 'none')]),
|
||||
'There should not have been any existing documents');
|
||||
}
|
||||
|
||||
public function testByContainsFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Exists::byContains('', []);
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails')]
|
||||
public function testByJsonPathFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Exists::byJsonPath('', '');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Test\Integration\SQLite;
|
||||
|
||||
use BitBadger\PDODocument\{Custom, Document, Field, Find};
|
||||
use BitBadger\PDODocument\{Custom, Document, DocumentException, Field, Find};
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Test\Integration\TestDocument;
|
||||
@ -85,6 +85,19 @@ class FindTest extends TestCase
|
||||
$this->assertFalse($docs->hasItems(), 'There should have been no documents in the list');
|
||||
}
|
||||
|
||||
public function testByContainsFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Find::byContains('', [], TestDocument::class);
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails')]
|
||||
public function testByJsonPathFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Find::byJsonPath('', '', TestDocument::class);
|
||||
}
|
||||
|
||||
public function testFirstByFieldsSucceedsWhenADocumentIsFound(): void
|
||||
{
|
||||
$doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'another')], TestDocument::class);
|
||||
@ -104,4 +117,17 @@ class FindTest extends TestCase
|
||||
$doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'absent')], TestDocument::class);
|
||||
$this->assertFalse($doc, 'There should not have been a document returned');
|
||||
}
|
||||
|
||||
public function testFirstByContainsFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Find::firstByContains('', [], TestDocument::class);
|
||||
}
|
||||
|
||||
#[TestDox('First by JSON Path fails')]
|
||||
public function testFirstByJsonPathFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Find::firstByJsonPath('', '', TestDocument::class);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Test\Integration\SQLite;
|
||||
|
||||
use BitBadger\PDODocument\{Count, Field, Find, Patch};
|
||||
use BitBadger\PDODocument\{Count, DocumentException, Field, Find, Patch};
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Test\Integration\TestDocument;
|
||||
@ -56,4 +56,17 @@ class PatchTest extends TestCase
|
||||
Patch::byFields(ThrowawayDb::TABLE, [Field::EQ('value', 'burgundy')], ['foo' => 'green']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
public function testByContainsFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Patch::byContains('', [], []);
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails')]
|
||||
public function testByJsonPathFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
Patch::byJsonPath('', '', []);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Test\Integration\SQLite;
|
||||
|
||||
use BitBadger\PDODocument\{Field, Find, RemoveFields};
|
||||
use BitBadger\PDODocument\{DocumentException, Field, Find, RemoveFields};
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Test\Integration\TestDocument;
|
||||
@ -71,4 +71,17 @@ class RemoveFieldsTest extends TestCase
|
||||
RemoveFields::byFields(ThrowawayDb::TABLE, [Field::NE('missing', 'nope')], ['value']);
|
||||
$this->assertTrue(true, 'The above not throwing an exception is the test');
|
||||
}
|
||||
|
||||
public function testByContainsFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
RemoveFields::byContains('', [], []);
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails')]
|
||||
public function testByJsonPathFails(): void
|
||||
{
|
||||
$this->expectException(DocumentException::class);
|
||||
RemoveFields::byJsonPath('', '', []);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class CountTest extends TestCase
|
||||
public function testByContainsSucceedsForPostgreSQL(): void
|
||||
{
|
||||
Configuration::$mode = Mode::PgSQL;
|
||||
$this->assertEquals("SELECT COUNT(*) FROM the_table WHERE data @> :criteria", Count::byContains('the_table'),
|
||||
$this->assertEquals('SELECT COUNT(*) FROM the_table WHERE data @> :criteria', Count::byContains('the_table'),
|
||||
'SELECT statement not generated correctly');
|
||||
}
|
||||
|
||||
@ -52,8 +52,8 @@ class CountTest extends TestCase
|
||||
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');
|
||||
$this->assertEquals('SELECT COUNT(*) FROM a_table WHERE jsonb_path_exists(data, :path::jsonpath)',
|
||||
Count::byJsonPath('a_table'), 'SELECT statement not generated correctly');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails for non PostgreSQL')]
|
||||
|
@ -53,8 +53,8 @@ class DeleteTest extends TestCase
|
||||
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');
|
||||
$this->assertEquals('DELETE FROM here WHERE jsonb_path_exists(data, :path::jsonpath)',
|
||||
Delete::byJsonPath('here'), 'DELETE statement not constructed correctly');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails for non PostgreSQL')]
|
||||
|
@ -60,7 +60,7 @@ class ExistsTest extends TestCase
|
||||
public function testByJsonPathSucceedsForPostgreSQL(): void
|
||||
{
|
||||
Configuration::$mode = Mode::PgSQL;
|
||||
$this->assertEquals('SELECT EXISTS (SELECT 1 FROM lint WHERE data @? :path::jsonpath)',
|
||||
$this->assertEquals('SELECT EXISTS (SELECT 1 FROM lint WHERE jsonb_path_exists(data, :path::jsonpath))',
|
||||
Exists::byJsonPath('lint'), 'Existence query not generated correctly');
|
||||
}
|
||||
|
||||
|
@ -54,8 +54,8 @@ class FindTest extends TestCase
|
||||
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');
|
||||
$this->assertEquals('SELECT data FROM light WHERE jsonb_path_exists(data, :path::jsonpath)',
|
||||
Find::byJsonPath('light'), 'SELECT query not generated correctly');
|
||||
}
|
||||
|
||||
#[TestDox('By JSON Path fails for non PostgreSQL')]
|
||||
|
@ -83,7 +83,7 @@ class PatchTest extends TestCase
|
||||
public function testByJsonPathSucceedsForPostgreSQL(): void
|
||||
{
|
||||
Configuration::$mode = Mode::PgSQL;
|
||||
$this->assertEquals('UPDATE that SET data = data || :data WHERE data @? :path::jsonpath',
|
||||
$this->assertEquals('UPDATE that SET data = data || :data WHERE jsonb_path_exists(data, :path::jsonpath)',
|
||||
Patch::byJsonPath('that'), 'Patch UPDATE statement is not correct');
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,8 @@ class RemoveFieldsTest extends TestCase
|
||||
public function testByJsonPathSucceedsForPostgreSQL(): void
|
||||
{
|
||||
Configuration::$mode = Mode::PgSQL;
|
||||
$this->assertEquals('UPDATE dessert SET data = data - :cake::text[] WHERE data @? :path::jsonpath',
|
||||
$this->assertEquals(
|
||||
'UPDATE dessert SET data = data - :cake::text[] WHERE jsonb_path_exists(data, :path::jsonpath)',
|
||||
RemoveFields::byJsonPath('dessert', Parameters::fieldNames(':cake', ['b', 'c'])),
|
||||
'UPDATE statement not correct');
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ class QueryTest extends TestCase
|
||||
{
|
||||
Configuration::$mode = Mode::PgSQL;
|
||||
try {
|
||||
$this->assertEquals('data @? :path::jsonpath', Query::whereJsonPathMatches(),
|
||||
$this->assertEquals('jsonb_path_exists(data, :path::jsonpath)', Query::whereJsonPathMatches(),
|
||||
'WHERE fragment not constructed correctly');
|
||||
} finally {
|
||||
Configuration::$mode = null;
|
||||
@ -108,7 +108,7 @@ class QueryTest extends TestCase
|
||||
{
|
||||
Configuration::$mode = Mode::PgSQL;
|
||||
try {
|
||||
$this->assertEquals('data @? :road::jsonpath', Query::whereJsonPathMatches(':road'),
|
||||
$this->assertEquals('jsonb_path_exists(data, :road::jsonpath)', Query::whereJsonPathMatches(':road'),
|
||||
'WHERE fragment not constructed correctly');
|
||||
} finally {
|
||||
Configuration::$mode = null;
|
||||
|
Loading…
Reference in New Issue
Block a user