Add field match enum

This commit is contained in:
Daniel J. Summers 2024-06-10 11:00:08 -04:00
parent 1f1f06679f
commit 9729c50c00
19 changed files with 120 additions and 57 deletions

View File

@ -17,7 +17,7 @@
"rss": "https://git.bitbadger.solutions/bit-badger/pdo-document.rss"
},
"require": {
"php": ">=8.3",
"php": ">=8.1",
"netresearch/jsonmapper": "^4",
"ext-pdo": "*"
},

4
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ca79f450e8e715ad61ba3581734c0fe7",
"content-hash": "f4563891566be8872ae85552261303bd",
"packages": [
{
"name": "netresearch/jsonmapper",
@ -1697,7 +1697,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.3",
"php": ">=8.1",
"ext-pdo": "*"
},
"platform-dev": [],

View File

@ -26,14 +26,14 @@ class Count
*
* @param string $tableName The name of the table in which documents should be counted
* @param array|Field[] $fields The field comparison to match
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return int The count of documents matching the field comparison
* @throws DocumentException If one is encountered
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): int
public static function byFields(string $tableName, array $fields, ?FieldMatch $match = null): int
{
$namedFields = Parameters::nameFields($fields);
return Custom::scalar(Query\Count::byFields($tableName, $namedFields, $conjunction),
return Custom::scalar(Query\Count::byFields($tableName, $namedFields, $match),
Parameters::addFields($namedFields, []), new CountMapper());
}
}

View File

@ -24,13 +24,13 @@ class Delete
*
* @param string $tableName The table from which documents should be deleted
* @param array|Field[] $fields The field comparison to match
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): void
public static function byFields(string $tableName, array $fields, ?FieldMatch $match = null): void
{
$namedFields = Parameters::nameFields($fields);
Custom::nonQuery(Query\Delete::byFields($tableName, $namedFields, $conjunction),
Custom::nonQuery(Query\Delete::byFields($tableName, $namedFields, $match),
Parameters::addFields($namedFields, []));
}
}

View File

@ -27,14 +27,14 @@ class Exists
*
* @param string $tableName The name of the table in which document existence should be determined
* @param Field[] $fields The field comparison to match
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return bool True if any documents match the field comparison, false if not
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): bool
public static function byFields(string $tableName, array $fields, ?FieldMatch $match = null): bool
{
$namedFields = Parameters::nameFields($fields);
return Custom::scalar(Query\Exists::byFields($tableName, $namedFields, $conjunction),
return Custom::scalar(Query\Exists::byFields($tableName, $namedFields, $match),
Parameters::addFields($namedFields, []), new ExistsMapper());
}
}

28
src/FieldMatch.php Normal file
View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace BitBadger\PDODocument;
/**
* How multiple fields should be matched
*/
enum FieldMatch
{
/** Match all provided fields (`AND`) */
case All;
/** Match any provided fields (`OR`) */
case Any;
/**
* Get the SQL keyword for this enumeration value
*
* @return string The SQL keyword for this enumeration value
*/
public function toString(): string
{
return match ($this) {
FieldMatch::All => 'AND',
FieldMatch::Any => 'OR'
};
}
}

View File

@ -45,15 +45,15 @@ class Find
* @param string $tableName The table from which documents should be retrieved
* @param array|Field[] $fields The field comparison to match
* @param class-string<TDoc> $className The name of the class to be retrieved
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return DocumentList<TDoc> A list of documents matching the given field comparison
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, string $className,
string $conjunction = 'AND'): DocumentList
?FieldMatch $match = null): DocumentList
{
$namedFields = Parameters::nameFields($fields);
return Custom::list(Query\Find::byFields($tableName, $namedFields, $conjunction),
return Custom::list(Query\Find::byFields($tableName, $namedFields, $match),
Parameters::addFields($namedFields, []), new DocumentMapper($className));
}
@ -64,15 +64,15 @@ class Find
* @param string $tableName The table from which the document should be retrieved
* @param array|Field[] $fields The field comparison to match
* @param class-string<TDoc> $className The name of the class to be retrieved
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return false|TDoc The first document if any matches are found, false otherwise
* @throws DocumentException If any is encountered
*/
public static function firstByFields(string $tableName, array $fields, string $className,
string $conjunction = 'AND'): mixed
?FieldMatch $match = null): mixed
{
$namedFields = Parameters::nameFields($fields);
return Custom::single(Query\Find::byFields($tableName, $namedFields, $conjunction),
return Custom::single(Query\Find::byFields($tableName, $namedFields, $match),
Parameters::addFields($namedFields, []), new DocumentMapper($className));
}
}

View File

@ -27,14 +27,14 @@ class Patch
* @param string $tableName The table in which documents should be patched
* @param array|Field[] $fields The field comparison to match
* @param array|object $patch The object with which the documents should be patched (will be JSON-encoded)
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, array|object $patch,
string $conjunction = 'AND'): void
?FieldMatch $match = null): void
{
$namedFields = Parameters::nameFields($fields);
Custom::nonQuery(Query\Patch::byFields($tableName, $namedFields, $conjunction),
Custom::nonQuery(Query\Patch::byFields($tableName, $namedFields, $match),
Parameters::addFields($namedFields, Parameters::json(':data', $patch)));
}
}

View File

@ -22,12 +22,14 @@ class Query
* Create a WHERE clause fragment to implement a comparison on fields in a JSON document
*
* @param Field[] $fields The field comparison to generate
* @param string $conjunction How to join multiple conditions (optional; defaults to AND)
* @param FieldMatch|null $match How to join multiple conditions (optional; defaults to All)
* @return string The WHERE clause fragment matching the given fields and parameter
* @throws DocumentException If the database mode has not been set
*/
public static function whereByFields(array $fields, string $conjunction = 'AND'): string
public static function whereByFields(array $fields, ?FieldMatch $match = null): string
{
return implode(" $conjunction ", array_map(fn($it) => $it->toWhere(), $fields));
return implode(' ' . ($match ?? FieldMatch::All)->toString() . ' ',
array_map(fn($it) => $it->toWhere(), $fields));
}
/**
@ -35,6 +37,7 @@ class Query
*
* @param string $paramName The parameter name where the value of the ID will be provided (optional; default @id)
* @return string The WHERE clause fragment to match by ID
* @throws DocumentException If the database mode has not been set
*/
public static function whereById(string $paramName = ':id'): string
{
@ -69,6 +72,7 @@ class Query
*
* @param string $tableName The name of the table in which the document should be updated
* @return string The UPDATE query for the document
* @throws DocumentException If the database mode has not been set
*/
public static function update(string $tableName): string
{

View File

@ -2,7 +2,7 @@
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Field, Query};
use BitBadger\PDODocument\{DocumentException, Field, FieldMatch, Query};
/**
* Queries for counting documents
@ -25,11 +25,12 @@ class Count
*
* @param string $tableName The name of the table in which documents should be counted
* @param Field[] $fields The field comparison to match
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to join multiple conditions (optional; defaults to All)
* @return string The query to count documents using a field comparison
* @throws DocumentException If the database mode has not been set
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): string
public static function byFields(string $tableName, array $fields, ?FieldMatch $match = null): string
{
return self::all($tableName) . ' WHERE ' . Query::whereByFields($fields, $conjunction);
return self::all($tableName) . ' WHERE ' . Query::whereByFields($fields, $match);
}
}

View File

@ -2,7 +2,7 @@
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Field, Query};
use BitBadger\PDODocument\{DocumentException, Field, FieldMatch, Query};
/**
* Queries to delete documents
@ -14,6 +14,7 @@ class Delete
*
* @param string $tableName The name of the table from which a document should be deleted
* @return string The DELETE statement to delete a document by its ID
* @throws DocumentException If the database mode has not been set
*/
public static function byId(string $tableName): string
{
@ -25,11 +26,12 @@ class Delete
*
* @param string $tableName The name of the table from which documents should be deleted
* @param Field[] $fields The field comparison to match
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return string The DELETE statement to delete documents via field comparison
* @throws DocumentException If the database mode has not been set
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): string
public static function byFields(string $tableName, array $fields, ?FieldMatch $match = null): string
{
return "DELETE FROM $tableName WHERE " . Query::whereByFields($fields, $conjunction);
return "DELETE FROM $tableName WHERE " . Query::whereByFields($fields, $match);
}
}

View File

@ -2,7 +2,7 @@
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Field, Query};
use BitBadger\PDODocument\{DocumentException, Field, FieldMatch, Query};
/**
* Queries to determine document existence
@ -26,6 +26,7 @@ class Exists
*
* @param string $tableName The name of the table in which document existence should be checked
* @return string The query to determine document existence by ID
* @throws DocumentException If the database mode has not been set
*/
public static function byId(string $tableName): string
{
@ -37,11 +38,12 @@ class Exists
*
* @param string $tableName The name of the table in which document existence should be checked
* @param Field[] $fields The field comparison to match
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return string The query to determine document existence by field comparison
* @throws DocumentException If the database mode has not been set
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): string
public static function byFields(string $tableName, array $fields, ?FieldMatch $match = null): string
{
return self::query($tableName, Query::whereByFields($fields, $conjunction));
return self::query($tableName, Query::whereByFields($fields, $match));
}
}

View File

@ -2,7 +2,7 @@
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Field, Query};
use BitBadger\PDODocument\{DocumentException, Field, FieldMatch, Query};
/**
* Queries for retrieving documents
@ -14,6 +14,7 @@ class Find
*
* @param string $tableName The name of the table from which a document should be retrieved
* @return string The SELECT statement to retrieve a document by its ID
* @throws DocumentException If the database mode has not been set
*/
public static function byId(string $tableName): string
{
@ -25,11 +26,12 @@ class Find
*
* @param string $tableName The name of the table from which documents should be retrieved
* @param Field[] $fields The field comparison to match
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return string The SELECT statement to retrieve documents by field comparison
* @throws DocumentException If the database mode has not been set
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): string
public static function byFields(string $tableName, array $fields, ?FieldMatch $match = null): string
{
return Query::selectFromTable($tableName) . ' WHERE ' . Query::whereByFields($fields, $conjunction);
return Query::selectFromTable($tableName) . ' WHERE ' . Query::whereByFields($fields, $match);
}
}

View File

@ -2,7 +2,7 @@
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Configuration, DocumentException, Field, Mode, Query};
use BitBadger\PDODocument\{Configuration, DocumentException, Field, FieldMatch, Mode, Query};
/**
* Queries to perform partial updates on documents
@ -44,12 +44,12 @@ class Patch
*
* @param string $tableName The name of the table in which documents should be patched
* @param array|Field[] $field The field comparison to match
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return string The query to patch documents via field comparison
* @throws DocumentException If the database mode has not been set
*/
public static function byFields(string $tableName, array $field, string $conjunction = 'AND'): string
public static function byFields(string $tableName, array $field, ?FieldMatch $match = null): string
{
return self::update($tableName, Query::whereByFields($field, $conjunction));
return self::update($tableName, Query::whereByFields($field, $match));
}
}

View File

@ -2,7 +2,7 @@
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Configuration, DocumentException, Field, Mode, Query};
use BitBadger\PDODocument\{Configuration, DocumentException, Field, FieldMatch, Mode, Query};
/**
* Queries to remove fields from documents
@ -54,13 +54,13 @@ class RemoveFields
* @param string $tableName The name of the table in which documents should be manipulated
* @param array|Field[] $fields The field comparison to match
* @param array $parameters The parameter list for the query
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @return string The UPDATE statement to remove fields from documents via field comparison
* @throws DocumentException If the database mode has not been set
*/
public static function byFields(string $tableName, array $fields, array $parameters,
string $conjunction = 'AND'): string
?FieldMatch $match = null): string
{
return self::update($tableName, $parameters, Query::whereByFields($fields, $conjunction));
return self::update($tableName, $parameters, Query::whereByFields($fields, $match));
}
}

View File

@ -28,15 +28,15 @@ class RemoveFields
* @param string $tableName The table in which documents should have fields removed
* @param array|Field[] $fields The field comparison to match
* @param array|string[] $fieldNames The names of the fields to be removed
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @param FieldMatch|null $match How to handle multiple conditions (optional; defaults to All)
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, array $fieldNames,
string $conjunction = 'AND'): void
?FieldMatch $match = null): void
{
$nameParams = Parameters::fieldNames(':name', $fieldNames);
$namedFields = Parameters::nameFields($fields);
Custom::nonQuery(Query\RemoveFields::byFields($tableName, $namedFields, $nameParams, $conjunction),
Custom::nonQuery(Query\RemoveFields::byFields($tableName, $namedFields, $nameParams, $match),
Parameters::addFields($namedFields, $nameParams));
}
}

View File

@ -0,0 +1,22 @@
<?php declare(strict_types=1);
namespace Test\Unit;
use BitBadger\PDODocument\FieldMatch;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for the FieldMatch enum
*/
class FieldMatchTest extends TestCase
{
public function testToStringSucceedsForAll(): void
{
$this->assertEquals('AND', FieldMatch::All->toString(), 'All should have returned AND');
}
public function testToStringSucceedsForAny(): void
{
$this->assertEquals('OR', FieldMatch::Any->toString(), 'Any should have returned OR');
}
}

View File

@ -2,7 +2,7 @@
namespace Test\Unit\Query;
use BitBadger\PDODocument\{Configuration, Field, Mode};
use BitBadger\PDODocument\{Configuration, Field, FieldMatch, Mode};
use BitBadger\PDODocument\Query\Find;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
@ -32,7 +32,8 @@ class FindTest extends TestCase
public function testByFieldsSucceeds(): void
{
$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')], 'OR'),
Find::byFields('there', [Field::EQ('active', true, ':act'), Field::EQ('locked', true, ':lock')],
FieldMatch::Any),
'SELECT query not generated correctly');
}
}

View File

@ -2,7 +2,7 @@
namespace Test\Unit;
use BitBadger\PDODocument\{Configuration, Field, Mode, Query};
use BitBadger\PDODocument\{Configuration, Field, FieldMatch, Mode, Query};
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
@ -33,17 +33,18 @@ class QueryTest extends TestCase
Query::whereByFields([Field::LE('test_field', '', ':it')]), 'WHERE fragment not constructed correctly');
}
public function testWhereByFieldsSucceedsForMultipleFields(): void
public function testWhereByFieldsSucceedsForMultipleFieldsAll(): void
{
$this->assertEquals("data->>'test_field' <= :it AND data->>'other_field' = :other",
Query::whereByFields([Field::LE('test_field', '', ':it'), Field::EQ('other_field', '', ':other')]),
'WHERE fragment not constructed correctly');
}
public function testWhereByFieldsSucceedsForMultipleFieldsWithOr(): void
public function testWhereByFieldsSucceedsForMultipleFieldsAny(): void
{
$this->assertEquals("data->>'test_field' <= :it OR data->>'other_field' = :other",
Query::whereByFields([Field::LE('test_field', '', ':it'), Field::EQ('other_field', '', ':other')], 'OR'),
Query::whereByFields([Field::LE('test_field', '', ':it'), Field::EQ('other_field', '', ':other')],
FieldMatch::Any),
'WHERE fragment not constructed correctly');
}