proceed further with beans

This commit is contained in:
Jan-Niclas Loosen 2025-02-24 10:54:51 +01:00
parent 024e6e73cb
commit 239b81bf0e
10 changed files with 156 additions and 197 deletions

View File

@ -22,18 +22,19 @@ trait Conditionable
}
protected function addCondition(string $col, string|Operator $operator, mixed $val, string $prefix): self {
$this->schema->requireColumn($col);
$this->schema->reqCol($col);
$info = $this->schema->getCol($col);
// Convert the operator string to a ConditionOperator enum if needed.
if (is_string($operator))
$operator = Operator::fromString($operator);
$columnType = $this->schema->columnType($col);
$castedValue = $columnType->dbCast($val);
$type = $info->type;
$castedVal = $type->dbCast($val);
if (!empty($this->conditions))
$this->conditions[] = $prefix;
$this->conditions[] = "$col " . $operator->toString() . " $castedValue";
$this->conditions[] = "$col " . $operator->toString() . " $castedVal";
return $this;
}

View File

@ -5,31 +5,19 @@ use DatabaseHelper\beans\Schema;
class Database
{
/**
* Creates a new TableBlueprint instance for the specified table name.
* @param string $tableName The name of the table to create.
* @return Table instance supporting method chaining
*/
public static function makeTable(string $tableName): Table {
return new Table($tableName);
}
public static function makeMigration(Schema $table): Migration {
// TODO: Implement makeMigration() method.
return new Migration($table);
}
/**
* Creates a new QueryBuilder instance for the specified table.
* @param Schema $table The table to query on.
* @return Query instance supporting method chaining.
*/
public static function makeQuery(Schema $table): Query {
return new Query($table);
}
public static function makeDeletion(Schema $table): Deletion {
// TODO: Implement makeDeletion() method.
return new Deletion($table);
}
public static function makeInsertion(Schema $table): Insertion {
@ -40,11 +28,6 @@ class Database
return new Update($table);
}
/**
* Adds the WordPress database prefix to table names if not already present.
* @param string ...$tableName Array of table names to process
* @return void
*/
public static function standardizeTableNames(string &...$tableName): void {
global $wpdb;
$dbPrefix = $wpdb->prefix;

View File

@ -9,19 +9,14 @@ class Deletion
{
use Conditionable;
protected Schema $table;
protected Schema $schema;
public function __construct(Schema $table) {
$this->table = $table;
$this->schema = $table;
}
/**
* Generates the SQL statement.
* @return string SQL query.
* @throws InvalidArgumentException
*/
public function toSql(): string {
$table = $this->table->name;
$table = $this->schema->name;
$whereClause = $this->combineConditions();
if (!$this->isConditioned())
@ -30,11 +25,6 @@ class Deletion
return "DELETE FROM $table WHERE $whereClause";
}
/**
* Executes the DELETE query.
* @return int Number of affected rows.
* @throws Exception
*/
public function delete(): int {
global $wpdb;
$query = $this->toSql();
@ -45,14 +35,9 @@ class Deletion
return intval($result);
}
/**
* Clears all entries from the table.
* @return int Number of affected rows.
* @throws Exception
*/
public function truncate(): int {
global $wpdb;
$table = $this->table->name;
$table = $this->schema->name;
$query = esc_sql("TRUNCATE TABLE $table");
$result = $wpdb->query($query);

View File

@ -6,75 +6,52 @@ use InvalidArgumentException;
class Insertion
{
private Schema $table;
private array $currentRow = [];
private array $batchRows = [];
private Schema $schema;
private array $currInsert = [];
private array $batchInserts = [];
public function __construct(Schema $table) {
$this->table = $table;
$this->schema = $table;
}
/**
* Adds a single column-value pair to the row.
* @param string $col Column name.
* @param mixed $val Value to insert.
* @return Insertion Current instance for method chaining.
* @throws InvalidArgumentException
*/
public function data(string $col, mixed $val): Insertion {
if (!isset($this->table->columns[$col]))
throw new InvalidArgumentException("Column '$col' does not exist.");
$columnType = $this->table->columns[$col]['colType'];
$this->currentRow[$col] = $columnType->dbCast($val);
$this->schema->reqCol($col);
$info = $this->schema->getCol($col);
$type = $info->type;
$this->currInsert[$col] = $type->dbCast($val);
return $this;
}
/**
* Adds multiple column-value pairs to the row.
* @param array $data Associative array of column-value pairs.
* @return Insertion Current instance for method chaining.
* @throws InvalidArgumentException
*/
public function batchData(array $data): Insertion {
foreach ($data as $key => $value)
$this->data($key, $value);
return $this;
}
/**
* Finalizes the current row and stacks it for insertion.
* @return Insertion Current instance for method chaining.
*/
public function stack(): Insertion {
if (!empty($this->currentRow))
if (!empty($this->currInsert))
$this->stackForInsertion();
return $this;
}
private function stackForInsertion(): void {
$this->batchRows[] = $this->currentRow;
$this->currentRow = [];
$this->batchInserts[] = $this->currInsert;
$this->currInsert = [];
}
/**
* Executes the insertion of all batched rows into the database.
* @throws InvalidArgumentException
*/
public function insert(): bool {
global $wpdb;
// Convert single to batch queries.
if (!empty($this->currentRow))
if (!empty($this->currInsert))
$this->stackForInsertion();
if (empty($this->batchRows))
if (empty($this->batchInserts))
throw new InvalidArgumentException("No data set for insertion.");
foreach ($this->batchRows as $row) {
foreach ($this->batchInserts as $row) {
if (!empty($row)) {
$result = $wpdb->insert($this->table->name, $row);
$result = $wpdb->insert($this->schema->name, $row);
if ($result === false)
return false;
}

View File

@ -1,6 +1,8 @@
<?php
namespace DatabaseHelper;
use DatabaseHelper\beans\Column;
use DatabaseHelper\beans\Reference;
use DatabaseHelper\beans\Schema;
use DatabaseHelper\enums\Propagation;
use DatabaseHelper\enums\Type;
@ -9,81 +11,76 @@ use InvalidArgumentException;
class Migration
{
protected Schema $schema;
protected array $columnsToAdd = [];
protected array $columnsToModify = [];
protected array $columnsToDrop = [];
protected array $foreignKeysToAdd = [];
protected array $foreignKeysToDrop = [];
protected array $newCols = [];
protected array $editCols = [];
protected array $dropCols = [];
protected array $addRefs = [];
protected array $dropRefs = [];
public function __construct(Schema $table) {
$this->schema = $table->copy();
$this->schema = $table;
}
public function column(string $name, Type $type, mixed $default = null, bool $isNullable = false, bool $isUnique = false): Migration {
if ($this->schema->existsColumn($name))
throw new InvalidArgumentException("Column '$name' already exists.");
$table = $this->schema->name;
if ($this->schema->existsCol($name))
throw new InvalidArgumentException("Column '$name' already exists in $table.");
$this->schema->columns[$name] = [
'name' => $name,
'type' => $type,
'default' => $default,
'isNullable' => $isNullable,
'isUnique' => $isUnique
];
$col = new Column;
$col->name = $name;
$col->type = $type;
$col->default = $default;
$col->isUnique = $isUnique;
$col->isNullable = $isNullable;
$this->columnsToAdd[] = $name;
$this->schema->cols[$name] = $col;
$this->newCols[] = $name;
return $this;
}
public function modify(string $name, Type $type, mixed $default = null, bool $isNullable = false, bool $isUnique = false): Migration {
$this->schema->requireColumn($name);
if (isset($this->schema->references[$name]))
throw new InvalidArgumentException('Referencing columns cannot be modified.');
$this->schema->reqCol($name);
$col = $this->schema->getCol($name);
$this->schema->columns[$name] = [
'name' => $name,
'type' => $type,
'default' => $default,
'isNullable' => $isNullable,
'isUnique' => $isUnique
];
$col->name = $name;
$col->type = $type;
$col->default = $default;
$col->isNullable = $isNullable;
$col->isUnique = $isUnique;#
$this->columnsToModify[] = $name;
$this->editCols[$name] = $col;
return $this;
}
public function drop(string $name): Migration {
$this->schema->requireColumn($name);
unset($this->schema->columns[$name]);
$this->columnsToDrop[] = $name;
unset($this->schema->cols[$name]);
$this->dropCols[] = $name;
return $this;
}
public function reference(Schema $foreignTable, Propagation $onDelete = Propagation::CASCADE, Propagation $onUpdate = Propagation::CASCADE): Migration {
$name = $foreignTable->primaryKey();
$this->schema->requireColumn($name);
if($this->schema->existsRef($foreignTable))
throw new InvalidArgumentException("Foreign table '$foreignTable->name' is already referenced in '{$this->schema->name}'.");
$name = $foreignTable->id->name;
$this->schema->references[$name] = [
'name' => $name,
'table' => $foreignTable->name,
'onDelete' => $onDelete,
'onUpdate' => $onUpdate
];
$this->schema->columns[$name] = $foreignTable->columns[$name];
$ref = new Reference;
$ref->name = $name;
$ref->onUpdate = $onUpdate;
$ref->onDelete = $onDelete;
$this->schema->refs[$name] = $ref;
$this->addRefs[] = $name;
$this->foreignKeysToAdd[] = $name;
return $this;
}
public function dereference(Schema $foreignTable): Migration {
$name = $foreignTable->primaryKey();
if ($this->schema->existsReference($foreignTable))
throw new InvalidArgumentException('Foreign table is not referenced.');
$this->schema->reqRef($foreignTable);
$name = $foreignTable->id->name;
unset($this->schema->references[$name]);
$this->drop($name);
unset($this->schema->refs[$name]);
$this->dropRefs[] = $name;
$this->foreignKeysToDrop[] = $name;
return $this;
}
@ -94,8 +91,8 @@ class Migration
$clauses = [];
// Process new columns.
foreach ($this->columnsToAdd as $columnName) {
$col = $this->schema->columns[$columnName];
foreach ($this->newCols as $columnName) {
$col = $this->schema->cols[$columnName];
$clause = "ADD COLUMN `{$col['name']}` {$col['type']->toString()}";
$clause .= !$col['isNullable'] ? " NOT NULL" : "";
@ -110,8 +107,8 @@ class Migration
}
// Process modified columns.
foreach ($this->columnsToModify as $columnName) {
$col = $this->schema->columns[$columnName];
foreach ($this->editCols as $columnName) {
$col = $this->schema->cols[$columnName];
$clause = "MODIFY COLUMN `{$col['name']}` {$col['type']->toString()}";
$clause .= !$col['isNullable'] ? " NOT NULL" : "";
@ -127,12 +124,12 @@ class Migration
// Process dropped columns.
foreach ($this->columnsToDrop as $columnName)
foreach ($this->dropCols as $columnName)
$clauses[] = "DROP COLUMN `$columnName`";
// Process foreign keys to add.
foreach ($this->foreignKeysToAdd as $key) {
$foreignKey = $this->schema->references[$key];
foreach ($this->addRefs as $key) {
$foreignKey = $this->schema->refs[$key];
$clause = " ADD CONSTRAINT `fk_{$foreignKey['name']}`";
$clause .= " FOREIGN KEY (`{$foreignKey['name']}`)";
@ -144,7 +141,7 @@ class Migration
}
// Process foreign keys to drop.
foreach ($this->foreignKeysToDrop as $fkName)
foreach ($this->dropRefs as $fkName)
$clauses[] = "DROP FOREIGN KEY `fk_{$fkName}`";
if (empty($clauses))

View File

@ -1,6 +1,7 @@
<?php
namespace DatabaseHelper;
use DatabaseHelper\beans\OrderBy;
use DatabaseHelper\beans\Schema;
use DatabaseHelper\enums\Aggregation;
use DatabaseHelper\enums\Join;
@ -12,10 +13,10 @@ class Query
use Conditionable;
protected Schema $schema;
protected array $columns = ['*'];
protected array $aggregations = [];
protected array $cols = ['*'];
protected array $aggregates = [];
protected array $joins = [];
public array $orderBy;
public OrderBy $orderBy;
public function __construct(Schema $table) {
$this->schema = $table;
@ -23,27 +24,27 @@ class Query
public function select(string ...$cols): Query {
if (!empty($cols))
$this->columns = $cols;
$this->cols = $cols;
foreach ($cols as $col)
$this->schema->requireColumn($col);
if (!isset($this->schema->cols[$col]))
throw new InvalidArgumentException("Column '$col' does not exist");
return $this;
}
public function orderBy(string $col, Order $order): Query {
$this->schema->requireColumn($col);
$this->orderBy = [
'name' => $col,
'order' => $order
];
$this->schema->reqCol($col);
$this->orderBy = new OrderBy;
$this->orderBy->col = $col;
$this->orderBy->order = $order;
return $this;
}
public function join(Join $join, Schema $other): Query {
$foreignKey = null;
if($this->schema->existsReference($other))
$foreignKey = $this->schema->references[$other->name];
$foreignKey = $this->schema->refs[$other->name];
if ($other->existsReference($this->schema))
$foreignKey = $other->references[$this->schema->name];
$foreignKey = $other->refs[$this->schema->name];
if (is_null($foreignKey))
throw new InvalidArgumentException('Joins can only applied to referencing columns.');
@ -68,9 +69,9 @@ class Query
public function toSql(): string {
// Merge any aggregations with the standard columns.
$selectColumns = $this->columns;
$selectColumns = $this->cols;
if ($this->hasAggregations())
$selectColumns = array_merge($selectColumns, $this->aggregations);
$selectColumns = array_merge($selectColumns, $this->aggregates);
// Build the SELECT clause.
$columns = implode(", ", $selectColumns);
@ -100,12 +101,12 @@ class Query
public function aggregate(string $col, string $alias, Aggregation $func): Query {
if ($col != '*')
$this->schema->requireColumn($col);
$this->aggregations[] = strtoupper($func->toString()) . "($col) AS $alias";
$this->aggregates[] = strtoupper($func->toString()) . "($col) AS $alias";
return $this;
}
public function hasAggregations(): bool {
return !empty($this->aggregations);
return !empty($this->aggregates);
}
public function query(): mixed {

View File

@ -10,15 +10,14 @@ use DatabaseHelper\enums\Charset;
use DatabaseHelper\enums\Collation;
use DatabaseHelper\enums\Type;
use DatabaseHelper\enums\Engine;
use InvalidArgumentException;
class Table
{
protected Schema $table;
protected Schema $schema;
public function __construct(string $tableName) {
Database::standardizeTableNames($tableName);
$this->table = new Schema($tableName);
$this->schema = new Schema($tableName);
}
public function column(string $name, Type $type, mixed $default = null, bool $isNullable = false, bool $isUnique = false): Table {
@ -29,19 +28,19 @@ class Table
$col->isUnique = $isUnique;
$col->isNullable = $isNullable;
$this->table->columns[$name] = $col;
$this->schema->cols[$name] = $col;
return $this;
}
protected function id(): Table {
$id = new Primary;
$id->name = $this->table->name . '_id';
$this->table->primary = $id;
$id->name = $this->schema->name . '_id';
$this->schema->id = $id;
return $this;
}
public function reference(Schema $foreignTable, Propagation $onDelete = Propagation::CASCADE, Propagation $onUpdate = Propagation::CASCADE): Table {
$name = $foreignTable->primary->name;
$name = $foreignTable->id->name;
$ref = new Reference;
$ref->name = $name;
@ -49,31 +48,31 @@ class Table
$ref->onDelete = $onDelete;
$ref->onUpdate = $onUpdate;
$this->table->references[$name] = $ref;
$this->schema->refs[$name] = $ref;
return $this;
}
public function engine(Engine $engine): Table {
$this->table->engine = $engine;
$this->schema->engine = $engine;
return $this;
}
public function charset(Charset $charset): Table {
$this->table->charset = $charset;
$this->schema->charset = $charset;
return $this;
}
public function collation(Collation $collation): Table {
$this->table->collation = $collation;
$this->schema->collation = $collation;
return $this;
}
public function toSql(): string {
$primaryKey = $this->table->primary->name;
$clause = "CREATE TABLE `{$this->table->name}` (\n";
$primaryKey = $this->schema->id->name;
$clause = "CREATE TABLE `{$this->schema->name}` (\n";
$clause .= " PRIMARY KEY (`$primaryKey`),\n";
foreach ($this->table->columns as $col) {
foreach ($this->schema->cols as $col) {
if ($col->name !== $primaryKey) {
$clause .= " `$col->name` {$col->type->toString()}";
$clause .= !$col->isNullable ? ' NOT NULL' : '';
@ -89,7 +88,7 @@ class Table
}
// Add foreign keys
foreach ($this->table->references as $ref) {
foreach ($this->schema->refs as $ref) {
$clause .= " FOREIGN KEY (`{$ref->name}`)";
$clause .= " REFERENCES `{$ref->otherTable->name}` (`{$ref->name}`)";
$clause .= " ON DELETE {$ref->onDelete->toString()}";
@ -98,9 +97,9 @@ class Table
// Close the SQL string and add constraints
$clause = rtrim($clause, ",\n") . "\n) ";
$clause .= "ENGINE={$this->table->engine->toString()} ";
$clause .= "CHARSET={$this->table->charset->toString()} ";
$clause .= "COLLATE={$this->table->collation->toString()};";
$clause .= "ENGINE={$this->schema->engine->toString()} ";
$clause .= "CHARSET={$this->schema->charset->toString()} ";
$clause .= "COLLATE={$this->schema->collation->toString()};";
return $clause;
}
@ -108,8 +107,8 @@ class Table
global $wpdb;
$this->id();
Database::standardizeTableNames($this->table->name);
$tableName = $this->table->name;
Database::standardizeTableNames($this->schema->name);
$tableName = $this->schema->name;
$foundTables = $wpdb->get_var("SHOW TABLES LIKE '$tableName'");
if (is_null($foundTables)) {
@ -117,6 +116,6 @@ class Table
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
return $this->table;
return $this->schema;
}
}

View File

@ -10,39 +10,26 @@ class Update
{
use Conditionable;
protected Schema $table;
protected Schema $schema;
protected array $values = [];
public function __construct(Schema $table) {
$this->table = $table;
$this->schema = $table;
}
/**
* Sets a new value for a column.
* @param string $col Column name.
* @param mixed $val Value to set.
* @return self Current instance for method chaining.
* @throws InvalidArgumentException
*/
public function set(string $col, mixed $val): self {
$this->table->requireColumn($col);
$columnType = $this->table->columnType($col);
$castedValue = $columnType->dbCast($val);
$info = $this->schema->cols[$col];
$type = $info->type;
$castedValue = $type->dbCast($val);
$this->values[$col] = $castedValue;
return $this;
}
/**
* Generates the SQL statement.
* @return string SQL query.
* @throws InvalidArgumentException
*/
public function toSql(): string {
if (empty($this->values))
throw new InvalidArgumentException("No values have been set for the Update.");
$table = $this->table->name;
$table = $this->schema->name;
$setParts = [];
foreach ($this->values as $col => $value)
$setParts[] = "$col = $value";
@ -57,10 +44,6 @@ class Update
return $update;
}
/**
* Executes the SQL UPDATE query.
* @return bool Returns true on success.
*/
public function update(): bool {
global $wpdb;
$query = $this->toSql();

9
beans/OrderBy.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace DatabaseHelper\beans;
use DatabaseHelper\enums\Order;
class OrderBy {
public string $col;
public Order $order;
}

View File

@ -10,19 +10,43 @@ use http\Exception\InvalidArgumentException;
class Schema
{
public string $name = '';
public Primary $primary;
public Primary $id;
/**
* @var array<string, Column>
*/
public array $columns = [];
public array $cols = [];
/**
* @var array<string, Reference>
*/
public array $references = [];
public array $refs = [];
public Engine $engine = Engine::INNODB;
public Charset $charset = Charset::UTF8;
public Collation $collation = Collation::UTF8_GENERAL_CI;
public function existsCol(string $name): bool {
return isset($this->cols[$name]);
}
public function reqCol(string $name): void {
if (!$this->existsCol($name))
throw new InvalidArgumentException("Column $name does not exist in schema $this->name.");
}
public function getCol(string $name): Column {
return $this->cols[$name];
}
public function existsRef(Schema $other): bool {
$otherId = $other->id;
// TODO
return true;
}
public function reqRef(Schema $other): void {
if (!$this->existsRef($other))
throw new InvalidArgumentException("A reference to $other->name is not defined in $this->name.");
}
}