Add mode, bring in definition/patch queries
This commit is contained in:
		
							parent
							
								
									ecc13a30cf
								
							
						
					
					
						commit
						98bfceb7c9
					
				| @ -24,6 +24,9 @@ class Configuration | ||||
|     /** @var array|null Options to use for connections (driver-specific) */ | ||||
|     public static ?array $options = null; | ||||
| 
 | ||||
|     /** @var Mode|null The mode in which the library is operating (filled after first connection if not configured) */ | ||||
|     public static ?Mode $mode = null; | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve a new connection to the database | ||||
|      * | ||||
| @ -38,8 +41,15 @@ class Configuration | ||||
|         $db = new PDO(self::$pdoDSN, $_ENV['PDO_DOC_USERNAME'] ?? self::$username, | ||||
|             $_ENV['PDO_DOC_PASSWORD'] ?? self::$password, self::$options); | ||||
| 
 | ||||
|         // TODO: determine driver, set mode for other queries
 | ||||
|         echo $db->getAttribute(PDO::ATTR_DRIVER_NAME); | ||||
|         if (is_null(self::$mode)) { | ||||
|             $driver = $db->getAttribute(PDO::ATTR_DRIVER_NAME); | ||||
|             self::$mode = match ($driver) { | ||||
|                 'pgsql'  => Mode::PgSQL, | ||||
|                 'sqlite' => Mode::SQLite, | ||||
|                 default  => throw new DocumentException( | ||||
|                     "Unsupported driver $driver: this library currently supports PostgreSQL and SQLite") | ||||
|             }; | ||||
|         } | ||||
|         return $db; | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										15
									
								
								src/Mode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Mode.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| <?php declare(strict_types=1); | ||||
| 
 | ||||
| namespace BitBadger\PDODocument; | ||||
| 
 | ||||
| /** | ||||
|  * The mode for queries generated by the library | ||||
|  */ | ||||
| enum Mode | ||||
| { | ||||
|     /** Storing documents in a PostgreSQL database */ | ||||
|     case PgSQL; | ||||
| 
 | ||||
|     /** Storing documents in a SQLite database */ | ||||
|     case SQLite; | ||||
| } | ||||
| @ -3,6 +3,8 @@ | ||||
| namespace BitBadger\PDODocument\Query; | ||||
| 
 | ||||
| use BitBadger\PDODocument\Configuration; | ||||
| use BitBadger\PDODocument\DocumentException; | ||||
| use BitBadger\PDODocument\Mode; | ||||
| 
 | ||||
| /** | ||||
|  * Queries to define tables and indexes | ||||
| @ -13,11 +15,16 @@ class Definition | ||||
|      * SQL statement to create a document table | ||||
|      * | ||||
|      * @param string $name The name of the table (including schema, if applicable) | ||||
|      * @param string $dataType The data type used for the document column | ||||
|      * @return string The CREATE TABLE statement for the document table | ||||
|      * @throws DocumentException If the database mode has not been set | ||||
|      */ | ||||
|     public static function ensureTableFor(string $name, string $dataType): string | ||||
|     public static function ensureTable(string $name): string | ||||
|     { | ||||
|         $dataType = match (Configuration::$mode) { | ||||
|             Mode::PgSQL  => 'JSONB', | ||||
|             Mode::SQLite => 'TEXT', | ||||
|             default      => throw new DocumentException('Database mode not set; cannot make create table statement') | ||||
|         }; | ||||
|         return "CREATE TABLE IF NOT EXISTS $name (data $dataType NOT NULL)"; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										59
									
								
								src/Query/Patch.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/Query/Patch.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| <?php declare(strict_types=1); | ||||
| 
 | ||||
| namespace BitBadger\PDODocument\Query; | ||||
| 
 | ||||
| use BitBadger\PDODocument\Configuration; | ||||
| use BitBadger\PDODocument\DocumentException; | ||||
| use BitBadger\PDODocument\Field; | ||||
| use BitBadger\PDODocument\Mode; | ||||
| use BitBadger\PDODocument\Query; | ||||
| 
 | ||||
| /** | ||||
|  * Queries to perform partial updates on documents | ||||
|  */ | ||||
| class Patch | ||||
| { | ||||
|     /** | ||||
|      * Create an UPDATE statement to patch documents | ||||
|      * | ||||
|      * @param string $tableName The name of the table in which documents should be patched | ||||
|      * @param string $whereClause The body of the WHERE clause to use in the UPDATE statement | ||||
|      * @return string The UPDATE statement to perform the patch | ||||
|      * @throws DocumentException If the database mode has not been set | ||||
|      */ | ||||
|     public static function update(string $tableName, string $whereClause): string | ||||
|     { | ||||
|         $setValue = match (Configuration::$mode) { | ||||
|             Mode::PgSQL  => 'data || @data', | ||||
|             Mode::SQLite => 'json_patch(data, json(@data))', | ||||
|             default      => throw new DocumentException('Database mode not set; cannot make patch statement') | ||||
|         }; | ||||
|         return "UPDATE $tableName SET data = $setValue WHERE $whereClause"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Query to patch (partially update) a document by its ID | ||||
|      * | ||||
|      * @param string $tableName The name of the table in which a document should be patched | ||||
|      * @return string The query to patch a document by its ID | ||||
|      * @throws DocumentException If the database mode has not been set | ||||
|      */ | ||||
|     public static function byId(string $tableName): string | ||||
|     { | ||||
|         return self::update($tableName, Query::whereById()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Query to patch (partially update) a document via a comparison on a JSON field | ||||
|      * | ||||
|      * @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`) | ||||
|      * @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 | ||||
|     { | ||||
|         return self::update($tableName, Query::whereByFields($field, $conjunction)); | ||||
|     } | ||||
| } | ||||
| @ -2,7 +2,11 @@ | ||||
| 
 | ||||
| namespace Test\Unit\Query; | ||||
| 
 | ||||
| use BitBadger\PDODocument\Configuration; | ||||
| use BitBadger\PDODocument\DocumentException; | ||||
| use BitBadger\PDODocument\Mode; | ||||
| use BitBadger\PDODocument\Query\Definition; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| 
 | ||||
| /** | ||||
| @ -10,10 +14,35 @@ use PHPUnit\Framework\TestCase; | ||||
|  */ | ||||
| class DefinitionTest extends TestCase | ||||
| { | ||||
|     public function testEnsureTableForSucceeds(): void | ||||
|     #[TestDox('Ensure table succeeds for PosgtreSQL')]
 | ||||
|     public function testEnsureTableSucceedsForPostgreSQL(): void | ||||
|     { | ||||
|         $this->assertEquals('CREATE TABLE IF NOT EXISTS documents (data JSON NOT NULL)', | ||||
|             Definition::ensureTableFor('documents', 'JSON'), 'CREATE TABLE statement not generated correctly'); | ||||
|         try { | ||||
|             Configuration::$mode = Mode::PgSQL; | ||||
|             $this->assertEquals('CREATE TABLE IF NOT EXISTS documents (data JSONB NOT NULL)', | ||||
|                 Definition::ensureTable('documents'), 'CREATE TABLE statement not generated correctly'); | ||||
|         } finally { | ||||
|             Configuration::$mode = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Ensure table succeeds for SQLite')]
 | ||||
|     public function testEnsureTableSucceedsForSQLite(): void | ||||
|     { | ||||
|         try { | ||||
|             Configuration::$mode = Mode::SQLite; | ||||
|             $this->assertEquals('CREATE TABLE IF NOT EXISTS dox (data TEXT NOT NULL)', Definition::ensureTable('dox'), | ||||
|                 'CREATE TABLE statement not generated correctly'); | ||||
|         } finally { | ||||
|             Configuration::$mode = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function testEnsureTableFailsWhenModeNotSet(): void | ||||
|     { | ||||
|         $this->expectException(DocumentException::class); | ||||
|         Configuration::$mode = null; | ||||
|         Definition::ensureTable('boom'); | ||||
|     } | ||||
| 
 | ||||
|     public function testEnsureIndexOnSucceedsWithoutSchemaSingleAscendingField(): void | ||||
|  | ||||
| @ -4,6 +4,7 @@ namespace Test\Unit\Query; | ||||
| 
 | ||||
| use BitBadger\PDODocument\Field; | ||||
| use BitBadger\PDODocument\Query\Delete; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| 
 | ||||
| /** | ||||
| @ -11,6 +12,7 @@ use PHPUnit\Framework\TestCase; | ||||
|  */ | ||||
| class DeleteTest extends TestCase | ||||
| { | ||||
|     #[TestDox('By ID succeeds')]
 | ||||
|     public function testByIdSucceeds(): void | ||||
|     { | ||||
|         $this->assertEquals("DELETE FROM over_there WHERE data->>'id' = @id", Delete::byId('over_there'), | ||||
|  | ||||
| @ -4,6 +4,7 @@ namespace Test\Unit\Query; | ||||
| 
 | ||||
| use BitBadger\PDODocument\Field; | ||||
| use BitBadger\PDODocument\Query\Exists; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| 
 | ||||
| /** | ||||
| @ -17,6 +18,7 @@ class ExistsTest extends TestCase | ||||
|             'Existence query not generated correctly'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('By ID succeeds')]
 | ||||
|     public function testByIdSucceeds(): void | ||||
|     { | ||||
|         $this->assertEquals("SELECT EXISTS (SELECT 1 FROM dox WHERE data->>'id' = @id)", Exists::byId('dox'), | ||||
|  | ||||
| @ -4,6 +4,7 @@ namespace Test\Unit\Query; | ||||
| 
 | ||||
| use BitBadger\PDODocument\Field; | ||||
| use BitBadger\PDODocument\Query\Find; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| 
 | ||||
| /** | ||||
| @ -11,6 +12,7 @@ use PHPUnit\Framework\TestCase; | ||||
|  */ | ||||
| class FindTest extends TestCase | ||||
| { | ||||
|     #[TestDox('By ID succeeds')]
 | ||||
|     public function testByIdSucceeds(): void | ||||
|     { | ||||
|         $this->assertEquals("SELECT data FROM here WHERE data->>'id' = @id", Find::byId('here'), | ||||
|  | ||||
							
								
								
									
										83
									
								
								tests/unit/Query/PatchTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								tests/unit/Query/PatchTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| <?php declare(strict_types=1); | ||||
| 
 | ||||
| namespace Test\Unit\Query; | ||||
| 
 | ||||
| use BitBadger\PDODocument\Configuration; | ||||
| use BitBadger\PDODocument\DocumentException; | ||||
| use BitBadger\PDODocument\Field; | ||||
| use BitBadger\PDODocument\Mode; | ||||
| use BitBadger\PDODocument\Query\Patch; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| 
 | ||||
| /** | ||||
|  * Unit tests for the Patch class | ||||
|  */ | ||||
| class PatchTest extends TestCase | ||||
| { | ||||
|     #[TestDox('By ID succeeds for PostgreSQL')]
 | ||||
|     public function testByIdSucceedsForPostgreSQL(): void | ||||
|     { | ||||
|         try { | ||||
|             Configuration::$mode = Mode::PgSQL; | ||||
|             $this->assertEquals("UPDATE doc_table SET data = data || @data WHERE data->>'id' = @id", | ||||
|                 Patch::byId('doc_table'), 'Patch UPDATE statement is not correct'); | ||||
|         } finally { | ||||
|             Configuration::$mode = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('By ID succeeds for SQLite')]
 | ||||
|     public function testByIdSucceedsForSQLite(): void | ||||
|     { | ||||
|         try { | ||||
|             Configuration::$mode = Mode::SQLite; | ||||
|             $this->assertEquals("UPDATE my_table SET data = json_patch(data, json(@data)) WHERE data->>'id' = @id", | ||||
|                 Patch::byId('my_table'), 'Patch UPDATE statement is not correct'); | ||||
|         } finally { | ||||
|             Configuration::$mode = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('By ID fails when mode not set')]
 | ||||
|     public function testByIdFailsWhenModeNotSet(): void | ||||
|     { | ||||
|         $this->expectException(DocumentException::class); | ||||
|         Configuration::$mode = null; | ||||
|         Patch::byId('oof'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('By fields succeeds for PostgreSQL')]
 | ||||
|     public function testByFieldsSucceedsForPostgreSQL(): void | ||||
|     { | ||||
|         try { | ||||
|             Configuration::$mode = Mode::PgSQL; | ||||
|             $this->assertEquals("UPDATE that SET data = data || @data WHERE data->>'something' < @some", | ||||
|                 Patch::byFields('that', [Field::LT('something', 17, '@some')]), | ||||
|                 'Patch UPDATE statement is not correct'); | ||||
|         } finally { | ||||
|             Configuration::$mode = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('By fields succeeds for SQLite')]
 | ||||
|     public function testByFieldsSucceedsForSQLite(): void | ||||
|     { | ||||
|         try { | ||||
|             Configuration::$mode = Mode::SQLite; | ||||
|             $this->assertEquals( | ||||
|                 "UPDATE a_table SET data = json_patch(data, json(@data)) WHERE data->>'something' > @it", | ||||
|                 Patch::byFields('a_table', [Field::GT('something', 17, '@it')]), | ||||
|                 'Patch UPDATE statement is not correct'); | ||||
|         } finally { | ||||
|             Configuration::$mode = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function testByFieldsFailsWhenModeNotSet(): void | ||||
|     { | ||||
|         $this->expectException(DocumentException::class); | ||||
|         Configuration::$mode = null; | ||||
|         Patch::byFields('oops', []); | ||||
|     } | ||||
| } | ||||
| @ -4,6 +4,7 @@ namespace Test\Unit; | ||||
| 
 | ||||
| use BitBadger\PDODocument\Field; | ||||
| use BitBadger\PDODocument\Query; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| 
 | ||||
| /** | ||||
| @ -37,11 +38,13 @@ class QueryTest extends TestCase | ||||
|             'WHERE fragment not constructed correctly'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Where by ID succeeds with default parameter')]
 | ||||
|     public function testWhereByIdSucceedsWithDefaultParameter(): void | ||||
|     { | ||||
|         $this->assertEquals("data->>'id' = @id", Query::whereById(), 'WHERE fragment not constructed correctly'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Where by ID succeeds with specific parameter')]
 | ||||
|     public function testWhereByIdSucceedsWithSpecificParameter(): void | ||||
|     { | ||||
|         $this->assertEquals("data->>'id' = @di", Query::whereById('@di'), 'WHERE fragment not constructed correctly'); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user