Initial SQLite development (#1)

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2024-06-08 23:58:45 +00:00
parent e91acee70f
commit f784f3e52c
66 changed files with 5509 additions and 2 deletions

35
src/Query/Count.php Normal file
View File

@@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Field, Query};
/**
* Queries for counting documents
*/
class Count
{
/**
* Query to count all documents in a table
*
* @param string $tableName The name of the table in which documents should be counted
* @return string The query to count all documents in a table
*/
public static function all(string $tableName): string
{
return "SELECT COUNT(*) FROM $tableName";
}
/**
* Query to count matching documents using a text comparison on JSON fields
*
* @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`)
* @return string The query to count documents using a field comparison
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): string
{
return self::all($tableName) . ' WHERE ' . Query::whereByFields($fields, $conjunction);
}
}

71
src/Query/Definition.php Normal file
View File

@@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Configuration, DocumentException, Mode};
/**
* Queries to define tables and indexes
*/
class Definition
{
/**
* SQL statement to create a document table
*
* @param string $name The name of the table (including schema, if applicable)
* @return string The CREATE TABLE statement for the document table
* @throws DocumentException If the database mode has not been set
*/
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)";
}
/**
* Split a schema and table name
*
* @param string $tableName The name of the table, possibly including the schema
* @return array|string[] An array with the schema at index 0 and the table name at index 1
*/
private static function splitSchemaAndTable(string $tableName): array
{
$parts = explode('.', $tableName);
return sizeof($parts) == 1 ? ["", $tableName] : [$parts[0], $parts[1]];
}
/**
* SQL statement to create an index on one or more fields in a JSON document
*
* @param string $tableName The name of the table which should be indexed
* @param string $indexName The name of the index to create
* @param array $fields An array of fields to be indexed; may contain direction (ex. 'salary DESC')
* @return string The CREATE INDEX statement to ensure the index exists
*/
public static function ensureIndexOn(string $tableName, string $indexName, array $fields): string
{
[, $tbl] = self::splitSchemaAndTable($tableName);
$jsonFields = implode(', ', array_map(function (string $field) {
$parts = explode(' ', $field);
$fieldName = sizeof($parts) == 1 ? $field : $parts[0];
$direction = sizeof($parts) < 2 ? "" : " $parts[1]";
return "(data->>'$fieldName')$direction";
}, $fields));
return "CREATE INDEX IF NOT EXISTS idx_{$tbl}_$indexName ON $tableName ($jsonFields)";
}
/**
* SQL statement to create a key index for a document table
*
* @param string $tableName The name of the table whose key should be ensured
* @return string The CREATE INDEX statement to ensure the key index exists
*/
public static function ensureKey(string $tableName): string
{
return str_replace('INDEX', 'UNIQUE INDEX', self::ensureIndexOn($tableName, 'key', [Configuration::$idField]));
}
}

35
src/Query/Delete.php Normal file
View File

@@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Field, Query};
/**
* Queries to delete documents
*/
class Delete
{
/**
* Query to delete a document by its ID
*
* @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
*/
public static function byId(string $tableName): string
{
return "DELETE FROM $tableName WHERE " . Query::whereById();
}
/**
* Query to delete documents using a comparison on JSON fields
*
* @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`)
* @return string The DELETE statement to delete documents via field comparison
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): string
{
return "DELETE FROM $tableName WHERE " . Query::whereByFields($fields, $conjunction);
}
}

47
src/Query/Exists.php Normal file
View File

@@ -0,0 +1,47 @@
<?php declare(strict_types=1);
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Field, Query};
/**
* Queries to determine document existence
*/
class Exists
{
/**
* The shell of a query for an existence check
*
* @param string $tableName The name of the table in which existence should be checked
* @param string $whereClause The conditions for which existence should be checked
* @return string The query to determine existence of documents
*/
public static function query(string $tableName, string $whereClause): string
{
return "SELECT EXISTS (SELECT 1 FROM $tableName WHERE $whereClause)";
}
/**
* Query to determine if a document exists for the given ID
*
* @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
*/
public static function byId(string $tableName): string
{
return self::query($tableName, Query::whereById());
}
/**
* Query to determine if documents exist using a comparison on JSON fields
*
* @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`)
* @return string The query to determine document existence by field comparison
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): string
{
return self::query($tableName, Query::whereByFields($fields, $conjunction));
}
}

35
src/Query/Find.php Normal file
View File

@@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Field, Query};
/**
* Queries for retrieving documents
*/
class Find
{
/**
* Query to retrieve a document by its ID
*
* @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
*/
public static function byId(string $tableName): string
{
return Query::selectFromTable($tableName) . ' WHERE ' . Query::whereById();
}
/**
* Query to retrieve documents using a comparison on JSON fields
*
* @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`)
* @return string The SELECT statement to retrieve documents by field comparison
*/
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): string
{
return Query::selectFromTable($tableName) . ' WHERE ' . Query::whereByFields($fields, $conjunction);
}
}

55
src/Query/Patch.php Normal file
View File

@@ -0,0 +1,55 @@
<?php declare(strict_types=1);
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Configuration, DocumentException, Field, Mode, 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));
}
}

View File

@@ -0,0 +1,66 @@
<?php declare(strict_types=1);
namespace BitBadger\PDODocument\Query;
use BitBadger\PDODocument\{Configuration, DocumentException, Field, Mode, Query};
/**
* Queries to remove fields from documents
*
* _NOTE: When using these queries to build custom functions, be aware that different databases use significantly
* different syntax. The `$parameters` passed to these functions should be run through `Parameters::fieldNames`
* function to generate them appropriately for the database currently being targeted._
*/
class RemoveFields
{
/**
* Create an UPDATE statement to remove fields from a JSON document
*
* @param string $tableName The name of the table in which documents should be manipulated
* @param array $parameters The parameter list for the query
* @param string $whereClause The body of the WHERE clause for the update
* @return string The UPDATE statement to remove fields from a JSON document
* @throws DocumentException If the database mode has not been set
*/
public static function update(string $tableName, array $parameters, string $whereClause): string
{
switch (Configuration::$mode) {
case Mode::PgSQL:
return "UPDATE $tableName SET data = data - " . array_keys($parameters)[0] . " WHERE $whereClause";
case Mode::SQLite:
$paramNames = implode(', ', array_keys($parameters));
return "UPDATE $tableName SET data = json_remove(data, $paramNames) WHERE $whereClause";
default:
throw new DocumentException('Database mode not set; cannot generate field removal query');
}
}
/**
* Query to remove fields from a document by the document's ID
*
* @param string $tableName The name of the table in which the document should be manipulated
* @param array $parameters The parameter list for the query
* @return string The UPDATE statement to remove fields from a document by its ID
* @throws DocumentException If the database mode has not been set
*/
public static function byId(string $tableName, array $parameters): string
{
return self::update($tableName, $parameters, Query::whereById());
}
/**
* Query to remove fields from documents via a comparison on JSON fields within the document
*
* @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`)
* @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
{
return self::update($tableName, $parameters, Query::whereByFields($fields, $conjunction));
}
}