diff --git a/Conditionable.php b/Conditionable.php index 47f9bba..5153648 100644 --- a/Conditionable.php +++ b/Conditionable.php @@ -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; } diff --git a/Database.php b/Database.php index 8f4b4e6..3e13d45 100644 --- a/Database.php +++ b/Database.php @@ -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; diff --git a/Deletion.php b/Deletion.php index e7e77a3..15ffa22 100644 --- a/Deletion.php +++ b/Deletion.php @@ -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); diff --git a/Insertion.php b/Insertion.php index 483f3ba..1765b16 100644 --- a/Insertion.php +++ b/Insertion.php @@ -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; } diff --git a/Migration.php b/Migration.php index c06ec5a..15a20ed 100644 --- a/Migration.php +++ b/Migration.php @@ -1,6 +1,8 @@ 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)) diff --git a/Query.php b/Query.php index 9ccc699..8d72c9d 100644 --- a/Query.php +++ b/Query.php @@ -1,6 +1,7 @@ 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 { diff --git a/Table.php b/Table.php index c0fb7de..220c41e 100644 --- a/Table.php +++ b/Table.php @@ -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; } } diff --git a/Update.php b/Update.php index cbd9cba..d6444c7 100644 --- a/Update.php +++ b/Update.php @@ -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(); diff --git a/beans/OrderBy.php b/beans/OrderBy.php new file mode 100644 index 0000000..1a2b702 --- /dev/null +++ b/beans/OrderBy.php @@ -0,0 +1,9 @@ + */ - public array $columns = []; + public array $cols = []; /** * @var array */ - 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."); + } }