Triggers + EntityLog
This commit is contained in:
parent
140f428491
commit
43d9a65def
63
core/Configuration/Patch/log.class.php
Normal file
63
core/Configuration/Patch/log.class.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Configuration\Patch;
|
||||
|
||||
use Configuration\DatabaseScript;
|
||||
use Driver\SQL\Column\IntColumn;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\SQL;
|
||||
use Driver\SQL\Type\CurrentColumn;
|
||||
use Driver\SQL\Type\CurrentTable;
|
||||
use Driver\SQL\Type\Trigger;
|
||||
|
||||
class log extends DatabaseScript {
|
||||
|
||||
public static function createQueries(SQL $sql): array {
|
||||
|
||||
$queries = array();
|
||||
|
||||
$queries[] = $sql->createTable("EntityLog")
|
||||
->addInt("entityId")
|
||||
->addString("tableName")
|
||||
->addDateTime("modified", false, $sql->now())
|
||||
->addInt("lifetime", false, 90);
|
||||
|
||||
$insertProcedure = $sql->createProcedure("InsertEntityLog")
|
||||
->param(new CurrentTable())
|
||||
->param(new IntColumn("uid"))
|
||||
->returns(new Trigger())
|
||||
->exec(array(
|
||||
$sql->insert("EntityLog", ["entityId", "tableName"])
|
||||
->addRow(new CurrentColumn("uid"), new CurrentTable())
|
||||
));
|
||||
|
||||
$updateProcedure = $sql->createProcedure("UpdateEntityLog")
|
||||
->param(new CurrentTable())
|
||||
->param(new IntColumn("uid"))
|
||||
->returns(new Trigger())
|
||||
->exec(array(
|
||||
$sql->update("EntityLog")
|
||||
->set("modified", $sql->now())
|
||||
->where(new Compare("entityId",new CurrentColumn("uid")))
|
||||
->where(new Compare("tableName",new CurrentTable()))
|
||||
));
|
||||
|
||||
$queries[] = $insertProcedure;
|
||||
$queries[] = $updateProcedure;
|
||||
|
||||
$tables = ["ContactRequest"];
|
||||
foreach ($tables as $table) {
|
||||
|
||||
$queries[] = $sql->createTrigger("${table}_trg_insert")
|
||||
->after()->insert($table)
|
||||
->exec($insertProcedure);
|
||||
|
||||
$queries[] = $sql->createTrigger("${table}_trg_update")
|
||||
->after()->update($table)
|
||||
->exec($updateProcedure);
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
}
|
@ -588,7 +588,7 @@ namespace Documents\Install {
|
||||
private function createProgessMainview(): string {
|
||||
|
||||
$isDocker = $this->isDocker();
|
||||
$defaultHost = ($isDocker ? "db" : "");
|
||||
$defaultHost = ($isDocker ? "db" : "localhost");
|
||||
$defaultUsername = ($isDocker ? "root" : "");
|
||||
$defaultPassword = ($isDocker ? "webbasedb" : "");
|
||||
$defaultDatabase = ($isDocker ? "webbase" : "");
|
||||
|
@ -16,8 +16,14 @@ use Driver\SQL\Column\JsonColumn;
|
||||
|
||||
use Driver\SQL\Condition\CondRegex;
|
||||
use Driver\SQL\Expression\Add;
|
||||
use Driver\SQL\Query\CreateProcedure;
|
||||
use Driver\SQL\Query\CreateTrigger;
|
||||
use Driver\SQL\Query\Query;
|
||||
use Driver\SQL\Strategy\Strategy;
|
||||
use \Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Driver\SQL\Type\CurrentColumn;
|
||||
use Driver\SQL\Type\CurrentTable;
|
||||
use Driver\SQL\Type\Trigger;
|
||||
|
||||
class MySQL extends SQL {
|
||||
|
||||
@ -67,7 +73,7 @@ class MySQL extends SQL {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getLastError() {
|
||||
public function getLastError(): string {
|
||||
$lastError = parent::getLastError();
|
||||
if (empty($lastError)) {
|
||||
$lastError = mysqli_error($this->connection);
|
||||
@ -175,7 +181,7 @@ class MySQL extends SQL {
|
||||
return ($success && $returnValues) ? $resultRows : $success;
|
||||
}
|
||||
|
||||
protected function getOnDuplicateStrategy(?Strategy $strategy, &$params) {
|
||||
public function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string {
|
||||
if (is_null($strategy)) {
|
||||
return "";
|
||||
} else if ($strategy instanceof UpdateStrategy) {
|
||||
@ -199,7 +205,7 @@ class MySQL extends SQL {
|
||||
} else {
|
||||
$strategyClass = get_class($strategy);
|
||||
$this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet.";
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,40 +213,51 @@ class MySQL extends SQL {
|
||||
$this->lastInsertId = mysqli_insert_id($this->connection);
|
||||
}
|
||||
|
||||
public function getColumnDefinition(Column $column) {
|
||||
$columnName = $this->columnName($column->getName());
|
||||
$defaultValue = $column->getDefaultValue();
|
||||
|
||||
public function getColumnType(Column $column): ?string {
|
||||
if ($column instanceof StringColumn) {
|
||||
$maxSize = $column->getMaxSize();
|
||||
if ($maxSize) {
|
||||
$type = "VARCHAR($maxSize)";
|
||||
return "VARCHAR($maxSize)";
|
||||
} else {
|
||||
$type = "TEXT";
|
||||
return "TEXT";
|
||||
}
|
||||
} else if($column instanceof SerialColumn) {
|
||||
$type = "INTEGER AUTO_INCREMENT";
|
||||
return "INTEGER AUTO_INCREMENT";
|
||||
} else if($column instanceof IntColumn) {
|
||||
$type = "INTEGER";
|
||||
return "INTEGER";
|
||||
} else if($column instanceof DateTimeColumn) {
|
||||
$type = "DATETIME";
|
||||
} else if($column instanceof EnumColumn) {
|
||||
$values = array();
|
||||
foreach($column->getValues() as $value) {
|
||||
$values[] = $this->getValueDefinition($value);
|
||||
}
|
||||
|
||||
$values = implode(",", $values);
|
||||
$type = "ENUM($values)";
|
||||
return "DATETIME";
|
||||
} else if($column instanceof BoolColumn) {
|
||||
$type = "BOOLEAN";
|
||||
return "BOOLEAN";
|
||||
} else if($column instanceof JsonColumn) {
|
||||
$type = "LONGTEXT"; # some maria db setups don't allow JSON here…
|
||||
$defaultValue = NULL; # must be null :(
|
||||
return "LONGTEXT"; # some maria db setups don't allow JSON here…
|
||||
} else {
|
||||
$this->lastError = "Unsupported Column Type: " . get_class($column);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public function getColumnDefinition(Column $column): ?string {
|
||||
$columnName = $this->columnName($column->getName());
|
||||
$defaultValue = $column->getDefaultValue();
|
||||
$type = $this->getColumnType($column);
|
||||
if (!$type) {
|
||||
if ($column instanceof EnumColumn) {
|
||||
$values = array();
|
||||
foreach($column->getValues() as $value) {
|
||||
$values[] = $this->getValueDefinition($value);
|
||||
}
|
||||
|
||||
$values = implode(",", $values);
|
||||
$type = "ENUM($values)";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === "LONGTEXT") {
|
||||
$defaultValue = NULL; # must be null :(
|
||||
}
|
||||
|
||||
$notNull = $column->notNull() ? " NOT NULL" : "";
|
||||
if (!is_null($defaultValue) || !$column->notNull()) {
|
||||
@ -267,16 +284,20 @@ class MySQL extends SQL {
|
||||
}
|
||||
}
|
||||
|
||||
protected function addValue($val, &$params) {
|
||||
public function addValue($val, &$params = NULL) {
|
||||
if ($val instanceof Keyword) {
|
||||
return $val->getValue();
|
||||
} else if ($val instanceof CurrentColumn) {
|
||||
return $val->getName();
|
||||
} else if ($val instanceof Column) {
|
||||
return $this->columnName($val->getName());
|
||||
} else {
|
||||
$params[] = $val;
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
protected function tableName($table) {
|
||||
public function tableName($table): string {
|
||||
if (is_array($table)) {
|
||||
$tables = array();
|
||||
foreach($table as $t) $tables[] = $this->tableName($t);
|
||||
@ -286,7 +307,7 @@ class MySQL extends SQL {
|
||||
}
|
||||
}
|
||||
|
||||
protected function columnName($col) {
|
||||
public function columnName($col): string {
|
||||
if ($col instanceof Keyword) {
|
||||
return $col->getValue();
|
||||
} elseif(is_array($col)) {
|
||||
@ -308,11 +329,72 @@ class MySQL extends SQL {
|
||||
}
|
||||
}
|
||||
|
||||
public function currentTimestamp() {
|
||||
public function currentTimestamp(): Keyword {
|
||||
return new Keyword("NOW()");
|
||||
}
|
||||
|
||||
public function getStatus() {
|
||||
return mysqli_stat($this->connection);
|
||||
}
|
||||
|
||||
public function createTriggerBody(CreateTrigger $trigger): ?string {
|
||||
$values = array();
|
||||
|
||||
foreach ($trigger->getProcedure()->getParameters() as $param) {
|
||||
if ($param instanceof CurrentTable) {
|
||||
$values[] = $this->getUnsafeValue($trigger->getTable());
|
||||
} else {
|
||||
$values[] = $this->columnName("NEW." . $param->getName());
|
||||
}
|
||||
}
|
||||
|
||||
$procName = $trigger->getProcedure()->getName();
|
||||
$procParameters = implode(",", $values);
|
||||
return "CALL $procName($procParameters)";
|
||||
}
|
||||
|
||||
private function getParameterDefinition(Column $parameter, bool $out = false): string {
|
||||
$out = ($out ? "OUT" : "IN");
|
||||
$name = $parameter->getName();
|
||||
$type = $this->getColumnType($parameter);
|
||||
return "$out $name $type";
|
||||
}
|
||||
|
||||
public function getProcedureHead(CreateProcedure $procedure): ?string {
|
||||
$name = $procedure->getName();
|
||||
$returns = $procedure->getReturns();
|
||||
$paramDefs = [];
|
||||
|
||||
foreach ($procedure->getParameters() as $param) {
|
||||
if ($param instanceof Column) {
|
||||
$paramDefs[] = $this->getParameterDefinition($param);
|
||||
} else {
|
||||
$this->setLastError("PROCEDURE parameter type " . gettype($returns) . " is not implemented yet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($returns) {
|
||||
if ($returns instanceof Column) {
|
||||
$paramDefs[] = $this->getParameterDefinition($returns, true);
|
||||
} else if (!($returns instanceof Trigger)) { // mysql does not need to return triggers here
|
||||
$this->setLastError("PROCEDURE RETURN type " . gettype($returns) . " is not implemented yet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$paramDefs = implode(",", $paramDefs);
|
||||
return "CREATE PROCEDURE $name($paramDefs)";
|
||||
}
|
||||
|
||||
protected function buildUnsafe(Query $statement): string {
|
||||
$params = [];
|
||||
$query = $statement->build($params);
|
||||
|
||||
foreach ($params as $value) {
|
||||
$query = preg_replace("?", $this->getUnsafeValue($value), $query, 1);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace Driver\SQL;
|
||||
|
||||
use \Api\Parameter\Parameter;
|
||||
|
||||
use Api\User\Create;
|
||||
use Driver\SQL\Column\Column;
|
||||
use \Driver\SQL\Column\IntColumn;
|
||||
use \Driver\SQL\Column\SerialColumn;
|
||||
@ -15,8 +16,14 @@ use Driver\SQL\Column\JsonColumn;
|
||||
|
||||
use Driver\SQL\Condition\CondRegex;
|
||||
use Driver\SQL\Expression\Add;
|
||||
use Driver\SQL\Query\CreateProcedure;
|
||||
use Driver\SQL\Query\CreateTrigger;
|
||||
use Driver\SQL\Query\Query;
|
||||
use Driver\SQL\Strategy\Strategy;
|
||||
use Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Driver\SQL\Type\CurrentColumn;
|
||||
use Driver\SQL\Type\CurrentTable;
|
||||
use Driver\SQL\Type\Trigger;
|
||||
|
||||
class PostgreSQL extends SQL {
|
||||
|
||||
@ -71,7 +78,7 @@ class PostgreSQL extends SQL {
|
||||
@pg_close($this->connection);
|
||||
}
|
||||
|
||||
public function getLastError() {
|
||||
public function getLastError(): string {
|
||||
$lastError = parent::getLastError();
|
||||
if (empty($lastError)) {
|
||||
$lastError = trim(pg_last_error($this->connection) . " " . pg_last_error($this->connection));
|
||||
@ -134,39 +141,39 @@ class PostgreSQL extends SQL {
|
||||
}
|
||||
}
|
||||
|
||||
protected function getOnDuplicateStrategy(?Strategy $strategy, &$params) {
|
||||
public function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string {
|
||||
if (!is_null($strategy)) {
|
||||
if ($strategy instanceof UpdateStrategy) {
|
||||
$updateValues = array();
|
||||
foreach($strategy->getValues() as $key => $value) {
|
||||
$leftColumn = $this->columnName($key);
|
||||
if ($value instanceof Column) {
|
||||
$columnName = $this->columnName($value->getName());
|
||||
$updateValues[] = "$leftColumn=EXCLUDED.$columnName";
|
||||
} else if ($value instanceof Add) {
|
||||
$columnName = $this->columnName($value->getColumn());
|
||||
$operator = $value->getOperator();
|
||||
$value = $value->getValue();
|
||||
$updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params);
|
||||
} else {
|
||||
$updateValues[] = "$leftColumn=" . $this->addValue($value, $parameters);
|
||||
}
|
||||
}
|
||||
|
||||
$conflictingColumns = $this->columnName($strategy->getConflictingColumns());
|
||||
$updateValues = implode(",", $updateValues);
|
||||
return " ON CONFLICT ($conflictingColumns) DO UPDATE SET $updateValues";
|
||||
} else {
|
||||
$strategyClass = get_class($strategy);
|
||||
$this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet.";
|
||||
return false;
|
||||
$updateValues = array();
|
||||
foreach($strategy->getValues() as $key => $value) {
|
||||
$leftColumn = $this->columnName($key);
|
||||
if ($value instanceof Column) {
|
||||
$columnName = $this->columnName($value->getName());
|
||||
$updateValues[] = "$leftColumn=EXCLUDED.$columnName";
|
||||
} else if ($value instanceof Add) {
|
||||
$columnName = $this->columnName($value->getColumn());
|
||||
$operator = $value->getOperator();
|
||||
$value = $value->getValue();
|
||||
$updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params);
|
||||
} else {
|
||||
$updateValues[] = "$leftColumn=" . $this->addValue($value, $parameters);
|
||||
}
|
||||
}
|
||||
|
||||
$conflictingColumns = $this->columnName($strategy->getConflictingColumns());
|
||||
$updateValues = implode(",", $updateValues);
|
||||
return " ON CONFLICT ($conflictingColumns) DO UPDATE SET $updateValues";
|
||||
} else {
|
||||
$strategyClass = get_class($strategy);
|
||||
$this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet.";
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
protected function getReturning(?string $columns) {
|
||||
public function getReturning(?string $columns): string {
|
||||
return $columns ? (" RETURNING " . $this->columnName($columns)) : "";
|
||||
}
|
||||
|
||||
@ -175,12 +182,7 @@ class PostgreSQL extends SQL {
|
||||
}
|
||||
|
||||
// UGLY but.. what should i do?
|
||||
private function createEnum(EnumColumn $enumColumn) {
|
||||
$typeName = $enumColumn->getName();
|
||||
if(!endsWith($typeName, "_type")) {
|
||||
$typeName = "${typeName}_type";
|
||||
}
|
||||
|
||||
private function createEnum(EnumColumn $enumColumn, string $typeName): string {
|
||||
$values = array();
|
||||
foreach($enumColumn->getValues() as $value) {
|
||||
$values[] = $this->getValueDefinition($value);
|
||||
@ -194,36 +196,50 @@ class PostgreSQL extends SQL {
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;";
|
||||
|
||||
$this->execute($query);
|
||||
return $typeName;
|
||||
return $this->execute($query);
|
||||
}
|
||||
|
||||
protected function getColumnDefinition($column) {
|
||||
$columnName = $this->columnName($column->getName());
|
||||
|
||||
public function getColumnType(Column $column): ?string {
|
||||
if ($column instanceof StringColumn) {
|
||||
$maxSize = $column->getMaxSize();
|
||||
if ($maxSize) {
|
||||
$type = "VARCHAR($maxSize)";
|
||||
return "VARCHAR($maxSize)";
|
||||
} else {
|
||||
$type = "TEXT";
|
||||
return "TEXT";
|
||||
}
|
||||
} else if($column instanceof SerialColumn) {
|
||||
$type = "SERIAL";
|
||||
return "SERIAL";
|
||||
} else if($column instanceof IntColumn) {
|
||||
$type = "INTEGER";
|
||||
return "INTEGER";
|
||||
} else if($column instanceof DateTimeColumn) {
|
||||
$type = "TIMESTAMP";
|
||||
return "TIMESTAMP";
|
||||
} else if($column instanceof EnumColumn) {
|
||||
$type = $this->createEnum($column);
|
||||
$typeName = $column->getName();
|
||||
if(!endsWith($typeName, "_type")) {
|
||||
$typeName = "${typeName}_type";
|
||||
}
|
||||
return $typeName;
|
||||
} else if($column instanceof BoolColumn) {
|
||||
$type = "BOOLEAN";
|
||||
return "BOOLEAN";
|
||||
} else if($column instanceof JsonColumn) {
|
||||
$type = "JSON";
|
||||
return "JSON";
|
||||
} else {
|
||||
$this->lastError = "Unsupported Column Type: " . get_class($column);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public function getColumnDefinition($column): ?string {
|
||||
$columnName = $this->columnName($column->getName());
|
||||
|
||||
$type = $this->getColumnType($column);
|
||||
if (!$type) {
|
||||
return null;
|
||||
} else if ($column instanceof EnumColumn) {
|
||||
if (!$this->createEnum($column, $type)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$notNull = $column->notNull() ? " NOT NULL" : "";
|
||||
$defaultValue = "";
|
||||
@ -249,16 +265,22 @@ class PostgreSQL extends SQL {
|
||||
}
|
||||
}
|
||||
|
||||
protected function addValue($val, &$params) {
|
||||
public function addValue($val, &$params = NULL) {
|
||||
if ($val instanceof Keyword) {
|
||||
return $val->getValue();
|
||||
} else if ($val instanceof CurrentTable) {
|
||||
return "TG_TABLE_NAME";
|
||||
} else if ($val instanceof CurrentColumn) {
|
||||
return "NEW." . $this->columnName($val->getName());
|
||||
} else if ($val instanceof Column) {
|
||||
return $this->columnName($val->getName());
|
||||
} else {
|
||||
$params[] = is_bool($val) ? ($val ? "TRUE" : "FALSE") : $val;
|
||||
return '$' . count($params);
|
||||
}
|
||||
}
|
||||
|
||||
protected function tableName($table) {
|
||||
public function tableName($table): string {
|
||||
if (is_array($table)) {
|
||||
$tables = array();
|
||||
foreach($table as $t) $tables[] = $this->tableName($t);
|
||||
@ -268,7 +290,7 @@ class PostgreSQL extends SQL {
|
||||
}
|
||||
}
|
||||
|
||||
protected function columnName($col) {
|
||||
public function columnName($col): string {
|
||||
if ($col instanceof KeyWord) {
|
||||
return $col->getValue();
|
||||
} elseif(is_array($col)) {
|
||||
@ -291,7 +313,7 @@ class PostgreSQL extends SQL {
|
||||
}
|
||||
|
||||
// Special Keywords and functions
|
||||
public function currentTimestamp() {
|
||||
public function currentTimestamp(): Keyword {
|
||||
return new Keyword("CURRENT_TIMESTAMP");
|
||||
}
|
||||
|
||||
@ -317,4 +339,75 @@ class PostgreSQL extends SQL {
|
||||
return parent::buildCondition($condition, $params);
|
||||
}
|
||||
}
|
||||
|
||||
private function createTriggerProcedure(string $name, array $statements) {
|
||||
$params = [];
|
||||
$query = "CREATE OR REPLACE FUNCTION $name() RETURNS TRIGGER AS \$table\$ BEGIN ";
|
||||
foreach($statements as $stmt) {
|
||||
if ($stmt instanceof Keyword) {
|
||||
$query .= $stmt->getValue() . ";";
|
||||
} else {
|
||||
$query .= $stmt->build($this, $params) . ";";
|
||||
}
|
||||
}
|
||||
$query .= "END;";
|
||||
$query .= "\$table\$ LANGUAGE plpgsql;";
|
||||
|
||||
var_dump($query);
|
||||
var_dump($params);
|
||||
|
||||
return $this->execute($query, $params);
|
||||
}
|
||||
|
||||
public function createTriggerBody(CreateTrigger $trigger): ?string {
|
||||
$procName = $this->tableName($trigger->getProcedure()->getName());
|
||||
return "EXECUTE PROCEDURE $procName()";
|
||||
}
|
||||
|
||||
public function getProcedureHead(CreateProcedure $procedure): ?string {
|
||||
$name = $this->tableName($procedure->getName());
|
||||
$returns = $procedure->getReturns() ?? "";
|
||||
$paramDefs = [];
|
||||
|
||||
if (!($procedure->getReturns() instanceof Trigger)) {
|
||||
foreach ($procedure->getParameters() as $parameter) {
|
||||
$paramDefs[] = $parameter->getName() . " " . $this->getColumnType($parameter);
|
||||
}
|
||||
}
|
||||
|
||||
$paramDefs = implode(",", $paramDefs);
|
||||
if ($returns) {
|
||||
if ($returns instanceof Column) {
|
||||
$returns = " RETURNS " . $this->getColumnType($returns);
|
||||
} else if ($returns instanceof Keyword) {
|
||||
$returns = " RETURNS " . $returns->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return "CREATE OR REPLACE FUNCTION $name($paramDefs)$returns AS $$";
|
||||
}
|
||||
|
||||
public function getProcedureTail(): string {
|
||||
return "$$ LANGUAGE plpgsql;";
|
||||
}
|
||||
|
||||
public function getProcedureBody(CreateProcedure $procedure): string {
|
||||
$statements = parent::getProcedureBody($procedure);
|
||||
if ($procedure->getReturns() instanceof Trigger) {
|
||||
$statements .= "RETURN NEW;";
|
||||
}
|
||||
return $statements;
|
||||
}
|
||||
|
||||
protected function buildUnsafe(Query $statement): string {
|
||||
$params = [];
|
||||
$query = $statement->build($params);
|
||||
|
||||
foreach ($params as $index => $value) {
|
||||
$value = $this->getUnsafeValue($value);
|
||||
$query = preg_replace("\$$index", $value, $query, 1);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ namespace Driver\SQL\Query;
|
||||
|
||||
use Driver\SQL\Column\Column;
|
||||
use Driver\SQL\Constraint\Constraint;
|
||||
use Driver\SQL\Constraint\ForeignKey;
|
||||
use Driver\SQL\Constraint\PrimaryKey;
|
||||
use Driver\SQL\SQL;
|
||||
|
||||
class AlterTable extends Query {
|
||||
@ -52,13 +54,48 @@ class AlterTable extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function execute(): bool {
|
||||
return $this->sql->executeAlter($this);
|
||||
}
|
||||
|
||||
public function getAction(): string { return $this->action; }
|
||||
public function getColumn(): ?Column { return $this->column; }
|
||||
public function getConstraint(): ?Constraint { return $this->constraint; }
|
||||
public function getTable(): string { return $this->table; }
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
$tableName = $this->sql->tableName($this->getTable());
|
||||
$action = $this->getAction();
|
||||
$column = $this->getColumn();
|
||||
$constraint = $this->getConstraint();
|
||||
|
||||
$query = "ALTER TABLE $tableName $action ";
|
||||
|
||||
if ($column) {
|
||||
$query .= "COLUMN ";
|
||||
if ($action === "DROP") {
|
||||
$query .= $this->sql->columnName($column->getName());
|
||||
} else {
|
||||
// ADD or modify
|
||||
$query .= $this->sql->getColumnDefinition($column);
|
||||
}
|
||||
} else if ($constraint) {
|
||||
if ($action === "DROP") {
|
||||
if ($constraint instanceof PrimaryKey) {
|
||||
$query .= "PRIMARY KEY";
|
||||
} else if ($constraint instanceof ForeignKey) {
|
||||
// TODO: how can we pass the constraint name here?
|
||||
$this->sql->setLastError("DROP CONSTRAINT foreign key is not supported yet.");
|
||||
return null;
|
||||
}
|
||||
} else if ($action === "ADD") {
|
||||
$query .= "CONSTRAINT ";
|
||||
$query .= $this->sql->getConstraintDefinition($constraint);
|
||||
} else if ($action === "MODIFY") {
|
||||
$this->sql->setLastError("MODIFY CONSTRAINT foreign key is not supported.");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$this->sql->setLastError("ALTER TABLE requires at least a column or a constraint.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
50
core/Driver/SQL/Query/CreateProcedure.class.php
Normal file
50
core/Driver/SQL/Query/CreateProcedure.class.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Driver\SQL\Query;
|
||||
|
||||
use Driver\SQL\Column\Column;
|
||||
use Driver\SQL\SQL;
|
||||
|
||||
class CreateProcedure extends Query {
|
||||
|
||||
private string $name;
|
||||
private array $parameters;
|
||||
private array $statements;
|
||||
private $returns;
|
||||
|
||||
public function __construct(SQL $sql, string $procName) {
|
||||
parent::__construct($sql);
|
||||
$this->name = $procName;
|
||||
$this->parameters = [];
|
||||
$this->statements = [];
|
||||
$this->returns = NULL;
|
||||
}
|
||||
|
||||
public function param(Column $parameter): CreateProcedure {
|
||||
$this->parameters[] = $parameter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function returns($column): CreateProcedure {
|
||||
$this->returns = $column;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function exec(array $statements): CreateProcedure {
|
||||
$this->statements = $statements;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
$head = $this->sql->getProcedureHead($this);
|
||||
$body = $this->sql->getProcedureBody($this);
|
||||
$tail = $this->sql->getProcedureTail();
|
||||
return "$head BEGIN $body END; $tail";
|
||||
}
|
||||
|
||||
public function getName(): string { return $this->name; }
|
||||
public function getParameters(): array { return $this->parameters; }
|
||||
public function getReturns() { return $this->returns; }
|
||||
public function getStatements(): array { return $this->statements; }
|
||||
}
|
@ -86,12 +86,31 @@ class CreateTable extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute(): bool {
|
||||
return $this->sql->executeCreateTable($this);
|
||||
}
|
||||
|
||||
public function ifNotExists(): bool { return $this->ifNotExists; }
|
||||
public function getTableName(): string { return $this->tableName; }
|
||||
public function getColumns(): array { return $this->columns; }
|
||||
public function getConstraints(): array { return $this->constraints; }
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
$tableName = $this->sql->tableName($this->getTableName());
|
||||
$ifNotExists = $this->ifNotExists() ? " IF NOT EXISTS" : "";
|
||||
|
||||
$entries = array();
|
||||
foreach ($this->getColumns() as $column) {
|
||||
$entries[] = ($tmp = $this->sql->getColumnDefinition($column));
|
||||
if (is_null($tmp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getConstraints() as $constraint) {
|
||||
$entries[] = ($tmp = $this->sql->getConstraintDefinition($constraint));
|
||||
if (is_null($tmp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$entries = implode(",", $entries);
|
||||
return "CREATE TABLE$ifNotExists $tableName ($entries)";
|
||||
}
|
||||
}
|
||||
|
80
core/Driver/SQL/Query/CreateTrigger.class.php
Normal file
80
core/Driver/SQL/Query/CreateTrigger.class.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Driver\SQL\Query;
|
||||
|
||||
use Api\User\Create;
|
||||
use Driver\SQL\SQL;
|
||||
|
||||
class CreateTrigger extends Query {
|
||||
|
||||
private string $name;
|
||||
private string $time;
|
||||
private string $event;
|
||||
private string $tableName;
|
||||
private ?CreateProcedure $procedure;
|
||||
|
||||
public function __construct(SQL $sql, string $triggerName) {
|
||||
parent::__construct($sql);
|
||||
$this->name = $triggerName;
|
||||
$this->time = "AFTER";
|
||||
$this->tableName = "";
|
||||
$this->event = "";
|
||||
$this->procedure = null;
|
||||
}
|
||||
|
||||
public function before(): CreateTrigger {
|
||||
$this->time = "BEFORE";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function after(): CreateTrigger {
|
||||
$this->time = "AFTER";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function update(string $table): CreateTrigger {
|
||||
$this->tableName = $table;
|
||||
$this->event = "UPDATE";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function insert(string $table): CreateTrigger {
|
||||
$this->tableName = $table;
|
||||
$this->event = "INSERT";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete(string $table): CreateTrigger {
|
||||
$this->tableName = $table;
|
||||
$this->event = "DELETE";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function exec(CreateProcedure $procedure): CreateTrigger {
|
||||
$this->procedure = $procedure;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string { return $this->name; }
|
||||
public function getTime(): string { return $this->time; }
|
||||
public function getEvent(): string { return $this->event; }
|
||||
public function getTable(): string { return $this->tableName; }
|
||||
public function getProcedure(): CreateProcedure { return $this->procedure; }
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
$name = $this->sql->tableName($this->getName());
|
||||
$time = $this->getTime();
|
||||
$event = $this->getEvent();
|
||||
$tableName = $this->sql->tableName($this->getTable());
|
||||
|
||||
$params = array();
|
||||
$query = "CREATE TRIGGER $name $time $event ON $tableName FOR EACH ROW ";
|
||||
$triggerBody = $this->sql->createTriggerBody($this);
|
||||
if ($triggerBody === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query .= $triggerBody;
|
||||
return $query;
|
||||
}
|
||||
}
|
@ -21,10 +21,12 @@ class Delete extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute(): bool {
|
||||
return $this->sql->executeDelete($this);
|
||||
}
|
||||
|
||||
public function getTable(): string { return $this->table; }
|
||||
public function getConditions(): array { return $this->conditions; }
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
$table = $this->sql->tableName($this->getTable());
|
||||
$where = $this->sql->getWhereClause($this->getConditions(), $params);
|
||||
return "DELETE FROM $table$where";
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ class Drop extends Query {
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
public function execute(): bool {
|
||||
return $this->sql->executeDrop($this);
|
||||
}
|
||||
|
||||
public function getTable(): string {
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
return "DROP TABLE " . $this->sql->tableName($this->getTable());
|
||||
}
|
||||
}
|
@ -37,8 +37,9 @@ class Insert extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute(): bool {
|
||||
return $this->sql->executeInsert($this);
|
||||
public function execute() {
|
||||
$fetchResult = !empty($this->sql->getReturning($this->returning));
|
||||
return $this->sql->executeQuery($this, $fetchResult);
|
||||
}
|
||||
|
||||
public function getTableName(): string { return $this->tableName; }
|
||||
@ -46,4 +47,42 @@ class Insert extends Query {
|
||||
public function getRows(): array { return $this->rows; }
|
||||
public function onDuplicateKey(): ?Strategy { return $this->onDuplicateKey; }
|
||||
public function getReturning(): ?string { return $this->returning; }
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
$tableName = $this->sql->tableName($this->getTableName());
|
||||
$columns = $this->getColumns();
|
||||
$rows = $this->getRows();
|
||||
|
||||
if (empty($rows)) {
|
||||
$this->sql->setLastError("No rows to insert given.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_null($columns) || empty($columns)) {
|
||||
$columnStr = "";
|
||||
} else {
|
||||
$columnStr = " (" . $this->sql->columnName($columns) . ")";
|
||||
}
|
||||
|
||||
$values = array();
|
||||
foreach ($rows as $row) {
|
||||
$rowPlaceHolder = array();
|
||||
foreach ($row as $val) {
|
||||
$rowPlaceHolder[] = $this->sql->addValue($val, $params);
|
||||
}
|
||||
|
||||
$values[] = "(" . implode(",", $rowPlaceHolder) . ")";
|
||||
}
|
||||
|
||||
$values = implode(",", $values);
|
||||
|
||||
$onDuplicateKey = $this->sql->getOnDuplicateStrategy($this->onDuplicateKey(), $params);
|
||||
if ($onDuplicateKey === FALSE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returningCol = $this->getReturning();
|
||||
$returning = $this->sql->getReturning($returningCol);
|
||||
return "INSERT INTO $tableName$columnStr VALUES $values$onDuplicateKey$returning";
|
||||
}
|
||||
}
|
@ -20,6 +20,9 @@ abstract class Query {
|
||||
}
|
||||
|
||||
// can actually return bool|array (depending on success and query type)
|
||||
public abstract function execute();
|
||||
public function execute() {
|
||||
return $this->sql->executeQuery($this);
|
||||
}
|
||||
|
||||
public abstract function build(array &$params): ?string;
|
||||
}
|
@ -4,6 +4,7 @@ namespace Driver\SQL\Query;
|
||||
|
||||
use Driver\SQL\Condition\CondOr;
|
||||
use Driver\SQL\Join;
|
||||
use Driver\SQL\SQL;
|
||||
|
||||
class Select extends Query {
|
||||
|
||||
@ -81,7 +82,7 @@ class Select extends Query {
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
return $this->sql->executeSelect($this);
|
||||
return $this->sql->executeQuery($this, true);
|
||||
}
|
||||
|
||||
public function getColumns(): array { return $this->columns; }
|
||||
@ -94,4 +95,46 @@ class Select extends Query {
|
||||
public function getOffset(): int { return $this->offset; }
|
||||
public function getGroupBy(): array { return $this->groupColumns; }
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
$columns = $this->sql->columnName($this->getColumns());
|
||||
$tables = $this->getTables();
|
||||
|
||||
if (!$tables) {
|
||||
return "SELECT $columns";
|
||||
}
|
||||
|
||||
$tables = $this->sql->tableName($tables);
|
||||
$where = $this->sql->getWhereClause($this->getConditions(), $params);
|
||||
|
||||
$joinStr = "";
|
||||
$joins = $this->getJoins();
|
||||
if (!empty($joins)) {
|
||||
foreach ($joins as $join) {
|
||||
$type = $join->getType();
|
||||
$joinTable = $this->sql->tableName($join->getTable());
|
||||
$columnA = $this->sql->columnName($join->getColumnA());
|
||||
$columnB = $this->sql->columnName($join->getColumnB());
|
||||
$tableAlias = ($join->getTableAlias() ? " " . $join->getTableAlias() : "");
|
||||
|
||||
$joinStr .= " $type JOIN $joinTable$tableAlias ON $columnA=$columnB";
|
||||
}
|
||||
}
|
||||
|
||||
$groupBy = "";
|
||||
$groupColumns = $this->getGroupBy();
|
||||
if (!empty($groupColumns)) {
|
||||
$groupBy = " GROUP BY " . $this->sql->columnName($groupColumns);
|
||||
}
|
||||
|
||||
$orderBy = "";
|
||||
$orderColumns = $this->getOrderBy();
|
||||
if (!empty($orderColumns)) {
|
||||
$orderBy = " ORDER BY " . $this->sql->columnName($orderColumns);
|
||||
$orderBy .= ($this->isOrderedAscending() ? " ASC" : " DESC");
|
||||
}
|
||||
|
||||
$limit = ($this->getLimit() > 0 ? (" LIMIT " . $this->getLimit()) : "");
|
||||
$offset = ($this->getOffset() > 0 ? (" OFFSET " . $this->getOffset()) : "");
|
||||
return "SELECT $columns FROM $tables$joinStr$where$groupBy$orderBy$limit$offset";
|
||||
}
|
||||
}
|
@ -13,9 +13,9 @@ class Truncate extends Query {
|
||||
$this->tableName = $name;
|
||||
}
|
||||
|
||||
public function execute(): bool {
|
||||
return $this->sql->executeTruncate($this);
|
||||
}
|
||||
|
||||
public function getTable(): string { return $this->tableName; }
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
return "TRUNCATE " . $this->sql->tableName($this->getTable());
|
||||
}
|
||||
}
|
@ -28,11 +28,20 @@ class Update extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
return $this->sql->executeUpdate($this);
|
||||
}
|
||||
|
||||
public function getTable(): string { return $this->table; }
|
||||
public function getConditions(): array { return $this->conditions; }
|
||||
public function getValues(): array { return $this->values; }
|
||||
|
||||
public function build(array &$params, Query $context = NULL): ?string {
|
||||
$table = $this->sql->tableName($this->getTable());
|
||||
|
||||
$valueStr = array();
|
||||
foreach($this->getValues() as $key => $val) {
|
||||
$valueStr[] = $this->sql->columnName($key) . "=" . $this->sql->addValue($val, $params);
|
||||
}
|
||||
$valueStr = implode(",", $valueStr);
|
||||
|
||||
$where = $this->sql->getWhereClause($this->getConditions(), $params);
|
||||
return "UPDATE $table SET $valueStr$where";
|
||||
}
|
||||
}
|
@ -16,7 +16,9 @@ use \Driver\SQL\Constraint\Unique;
|
||||
use \Driver\SQL\Constraint\PrimaryKey;
|
||||
use \Driver\SQL\Constraint\ForeignKey;
|
||||
use Driver\SQL\Query\AlterTable;
|
||||
use Driver\SQL\Query\CreateProcedure;
|
||||
use Driver\SQL\Query\CreateTable;
|
||||
use Driver\SQL\Query\CreateTrigger;
|
||||
use Driver\SQL\Query\Delete;
|
||||
use Driver\SQL\Query\Drop;
|
||||
use Driver\SQL\Query\Insert;
|
||||
@ -44,46 +46,55 @@ abstract class SQL {
|
||||
$this->lastInsertId = 0;
|
||||
}
|
||||
|
||||
public function isConnected() {
|
||||
public function isConnected(): bool {
|
||||
return !is_null($this->connection);
|
||||
}
|
||||
|
||||
public function getLastError() {
|
||||
public function getLastError(): string {
|
||||
return trim($this->lastError);
|
||||
}
|
||||
|
||||
public function createTable($tableName) {
|
||||
public function createTable($tableName): CreateTable {
|
||||
return new CreateTable($this, $tableName);
|
||||
}
|
||||
|
||||
public function insert($tableName, $columns=array()) {
|
||||
public function insert($tableName, $columns=array()): Insert {
|
||||
return new Insert($this, $tableName, $columns);
|
||||
}
|
||||
|
||||
public function select(...$columNames) {
|
||||
public function select(...$columNames): Select {
|
||||
return new Select($this, $columNames);
|
||||
}
|
||||
|
||||
public function truncate($table) {
|
||||
public function truncate($table): Truncate {
|
||||
return new Truncate($this, $table);
|
||||
}
|
||||
|
||||
public function delete($table) {
|
||||
public function delete($table): Delete {
|
||||
return new Delete($this, $table);
|
||||
}
|
||||
|
||||
public function update($table) {
|
||||
public function update($table): Update {
|
||||
return new Update($this, $table);
|
||||
}
|
||||
|
||||
public function drop(string $table) {
|
||||
public function drop(string $table): Drop {
|
||||
return new Drop($this, $table);
|
||||
}
|
||||
|
||||
public function alterTable($tableName) {
|
||||
public function alterTable($tableName): AlterTable {
|
||||
return new AlterTable($this, $tableName);
|
||||
}
|
||||
|
||||
public function createTrigger($triggerName): CreateTrigger {
|
||||
return new CreateTrigger($this, $triggerName);
|
||||
}
|
||||
|
||||
public function createProcedure(string $procName): CreateProcedure {
|
||||
return new CreateProcedure($this, $procName);
|
||||
}
|
||||
|
||||
|
||||
// ####################
|
||||
// ### ABSTRACT METHODS
|
||||
// ####################
|
||||
@ -92,223 +103,37 @@ abstract class SQL {
|
||||
public abstract function checkRequirements();
|
||||
public abstract function getDriverName();
|
||||
|
||||
// Connection Managment
|
||||
// Connection Management
|
||||
public abstract function connect();
|
||||
public abstract function disconnect();
|
||||
|
||||
// Querybuilder
|
||||
protected function buildQuery(Query $query, array &$params) {
|
||||
if ($query instanceof Select) {
|
||||
$select = $query;
|
||||
$columns = $this->columnName($select->getColumns());
|
||||
$tables = $select->getTables();
|
||||
public function executeQuery(Query $query, bool $fetchResult = false) {
|
||||
|
||||
if (!$tables) {
|
||||
return $this->execute("SELECT $columns", $params, true);
|
||||
}
|
||||
$parameters = [];
|
||||
$queryStr = $query->build($parameters);
|
||||
|
||||
$tables = $this->tableName($tables);
|
||||
$where = $this->getWhereClause($select->getConditions(), $params);
|
||||
|
||||
$joinStr = "";
|
||||
$joins = $select->getJoins();
|
||||
if (!empty($joins)) {
|
||||
foreach($joins as $join) {
|
||||
$type = $join->getType();
|
||||
$joinTable = $this->tableName($join->getTable());
|
||||
$columnA = $this->columnName($join->getColumnA());
|
||||
$columnB = $this->columnName($join->getColumnB());
|
||||
$tableAlias = ($join->getTableAlias() ? " " . $join->getTableAlias() : "");
|
||||
|
||||
$joinStr .= " $type JOIN $joinTable$tableAlias ON $columnA=$columnB";
|
||||
}
|
||||
}
|
||||
|
||||
$groupBy = "";
|
||||
$groupColumns = $select->getGroupBy();
|
||||
if (!empty($groupColumns)) {
|
||||
$groupBy = " GROUP BY " . $this->columnName($groupColumns);
|
||||
}
|
||||
|
||||
$orderBy = "";
|
||||
$orderColumns = $select->getOrderBy();
|
||||
if (!empty($orderColumns)) {
|
||||
$orderBy = " ORDER BY " . $this->columnName($orderColumns);
|
||||
$orderBy .= ($select->isOrderedAscending() ? " ASC" : " DESC");
|
||||
}
|
||||
|
||||
$limit = ($select->getLimit() > 0 ? (" LIMIT " . $select->getLimit()) : "");
|
||||
$offset = ($select->getOffset() > 0 ? (" OFFSET " . $select->getOffset()) : "");
|
||||
return "SELECT $columns FROM $tables$joinStr$where$groupBy$orderBy$limit$offset";
|
||||
} else {
|
||||
$this->lastError = "buildQuery() not implemented for type: " . get_class($query);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public function executeCreateTable(CreateTable $createTable) {
|
||||
$tableName = $this->tableName($createTable->getTableName());
|
||||
$ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": "";
|
||||
|
||||
$entries = array();
|
||||
foreach($createTable->getColumns() as $column) {
|
||||
$entries[] = ($tmp = $this->getColumnDefinition($column));
|
||||
if (is_null($tmp)) {
|
||||
return false;
|
||||
}
|
||||
if($query->dump) {
|
||||
var_dump($queryStr);
|
||||
var_dump($parameters);
|
||||
}
|
||||
|
||||
foreach($createTable->getConstraints() as $constraint) {
|
||||
$entries[] = ($tmp = $this->getConstraintDefinition($constraint));
|
||||
if (is_null($tmp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$entries = implode(",", $entries);
|
||||
$query = "CREATE TABLE$ifNotExists $tableName ($entries)";
|
||||
return $this->execute($query);
|
||||
}
|
||||
|
||||
public function executeInsert(Insert $insert) {
|
||||
|
||||
$tableName = $this->tableName($insert->getTableName());
|
||||
$columns = $insert->getColumns();
|
||||
$rows = $insert->getRows();
|
||||
|
||||
if (empty($rows)) {
|
||||
$this->lastError = "No rows to insert given.";
|
||||
if ($queryStr === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_null($columns) || empty($columns)) {
|
||||
$columnStr = "";
|
||||
} else {
|
||||
$columnStr = " (" . $this->columnName($columns) . ")";
|
||||
}
|
||||
|
||||
$parameters = array();
|
||||
$values = array();
|
||||
foreach($rows as $row) {
|
||||
$rowPlaceHolder = array();
|
||||
foreach($row as $val) {
|
||||
$rowPlaceHolder[] = $this->addValue($val, $parameters);
|
||||
}
|
||||
|
||||
$values[] = "(" . implode(",", $rowPlaceHolder) . ")";
|
||||
}
|
||||
|
||||
$values = implode(",", $values);
|
||||
|
||||
$onDuplicateKey = $this->getOnDuplicateStrategy($insert->onDuplicateKey(), $parameters);
|
||||
if ($onDuplicateKey === FALSE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$returningCol = $insert->getReturning();
|
||||
$returning = $this->getReturning($returningCol);
|
||||
|
||||
$query = "INSERT INTO $tableName$columnStr VALUES $values$onDuplicateKey$returning";
|
||||
if($insert->dump) { var_dump($query); var_dump($parameters); }
|
||||
$res = $this->execute($query, $parameters, !empty($returning));
|
||||
$res = $this->execute($queryStr, $parameters, $fetchResult);
|
||||
$success = ($res !== FALSE);
|
||||
|
||||
if($success && $returningCol) {
|
||||
$this->fetchReturning($res, $returningCol);
|
||||
// fetch generated serial ids for Insert statements
|
||||
$generatedColumn = ($query instanceof Insert ? $query->getReturning() : null);
|
||||
if($success && $fetchResult && $generatedColumn) {
|
||||
$this->fetchReturning($res, $generatedColumn);
|
||||
}
|
||||
|
||||
return $success;
|
||||
return $fetchResult ? $res : $success;
|
||||
}
|
||||
|
||||
public function executeSelect(Select $select) {
|
||||
$params = array();
|
||||
$query = $this->buildQuery($select, $params);
|
||||
if($select->dump) { var_dump($query); var_dump($params); }
|
||||
return $this->execute($query, $params, true);
|
||||
}
|
||||
|
||||
public function executeDelete(Delete $delete) {
|
||||
|
||||
$params = array();
|
||||
$table = $this->tableName($delete->getTable());
|
||||
$where = $this->getWhereClause($delete->getConditions(), $params);
|
||||
|
||||
$query = "DELETE FROM $table$where";
|
||||
if($delete->dump) { var_dump($query); }
|
||||
return $this->execute($query, $params);
|
||||
}
|
||||
|
||||
public function executeTruncate(Truncate $truncate) {
|
||||
$query = "TRUNCATE " . $this->tableName($truncate->getTable());
|
||||
if ($truncate->dump) { var_dump($query); }
|
||||
return $this->execute($query);
|
||||
}
|
||||
|
||||
public function executeUpdate(Update $update) {
|
||||
|
||||
$params = array();
|
||||
$table = $this->tableName($update->getTable());
|
||||
|
||||
$valueStr = array();
|
||||
foreach($update->getValues() as $key => $val) {
|
||||
$valueStr[] = $this->columnName($key) . "=" . $this->addValue($val, $params);
|
||||
}
|
||||
$valueStr = implode(",", $valueStr);
|
||||
|
||||
$where = $this->getWhereClause($update->getConditions(), $params);
|
||||
$query = "UPDATE $table SET $valueStr$where";
|
||||
if($update->dump) { var_dump($query); var_dump($params); }
|
||||
return $this->execute($query, $params);
|
||||
}
|
||||
|
||||
public function executeDrop(Drop $drop) {
|
||||
$query = "DROP TABLE " . $this->tableName($drop->getTable());
|
||||
if ($drop->dump) { var_dump($query); }
|
||||
return $this->execute($query);
|
||||
}
|
||||
|
||||
public function executeAlter(AlterTable $alter): bool {
|
||||
$tableName = $this->tableName($alter->getTable());
|
||||
$action = $alter->getAction();
|
||||
$column = $alter->getColumn();
|
||||
$constraint = $alter->getConstraint();
|
||||
|
||||
$query = "ALTER TABLE $tableName $action ";
|
||||
|
||||
if ($column) {
|
||||
$query .= "COLUMN ";
|
||||
if ($action === "DROP") {
|
||||
$query .= $this->columnName($column->getName());
|
||||
} else {
|
||||
// ADD or modify
|
||||
$query .= $this->getColumnDefinition($column);
|
||||
}
|
||||
} else if ($constraint) {
|
||||
if ($action === "DROP") {
|
||||
if ($constraint instanceof PrimaryKey) {
|
||||
$query .= "PRIMARY KEY";
|
||||
} else if ($constraint instanceof ForeignKey) {
|
||||
// TODO: how can we pass the constraint name here?
|
||||
$this->lastError = "DROP CONSTRAINT foreign key is not supported yet.";
|
||||
return false;
|
||||
}
|
||||
} else if ($action === "ADD") {
|
||||
$query .= "CONSTRAINT ";
|
||||
$query .= $this->getConstraintDefinition($constraint);
|
||||
} else if ($action === "MODIFY") {
|
||||
$this->lastError = "MODIFY CONSTRAINT foreign key is not supported.";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->lastError = "ALTER TABLE requires at least a column or a constraint.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($alter->dump) { var_dump($query); }
|
||||
return $this->execute($query);
|
||||
}
|
||||
|
||||
protected function getWhereClause($conditions, &$params) {
|
||||
public function getWhereClause($conditions, &$params): string {
|
||||
if (!$conditions) {
|
||||
return "";
|
||||
} else {
|
||||
@ -316,7 +141,7 @@ abstract class SQL {
|
||||
}
|
||||
}
|
||||
|
||||
public function getConstraintDefinition(Constraint $constraint) {
|
||||
public function getConstraintDefinition(Constraint $constraint): ?string {
|
||||
$columnName = $this->columnName($constraint->getColumnNames());
|
||||
if ($constraint instanceof PrimaryKey) {
|
||||
return "PRIMARY KEY ($columnName)";
|
||||
@ -338,29 +163,52 @@ abstract class SQL {
|
||||
return $code;
|
||||
} else {
|
||||
$this->lastError = "Unsupported constraint type: " . get_class($constraint);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getReturning(?string $columns) {
|
||||
return "";
|
||||
protected abstract function fetchReturning($res, string $returningCol);
|
||||
public abstract function getColumnDefinition(Column $column): ?string;
|
||||
public abstract function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string;
|
||||
public abstract function createTriggerBody(CreateTrigger $trigger): ?string;
|
||||
public abstract function getProcedureHead(CreateProcedure $procedure): ?string;
|
||||
public abstract function getColumnType(Column $column): ?string;
|
||||
public function getProcedureTail(): string { return ""; }
|
||||
public function getReturning(?string $columns): string { return ""; }
|
||||
|
||||
public function getProcedureBody(CreateProcedure $procedure): string {
|
||||
$statements = "";
|
||||
foreach ($procedure->getStatements() as $statement) {
|
||||
$statements .= $this->buildUnsafe($statement) . ";";
|
||||
}
|
||||
return $statements;
|
||||
}
|
||||
|
||||
protected abstract function getColumnDefinition(Column $column);
|
||||
protected abstract function fetchReturning($res, string $returningCol);
|
||||
protected abstract function getOnDuplicateStrategy(?Strategy $strategy, &$params);
|
||||
protected function getUnsafeValue($value): ?string {
|
||||
if (is_string($value) || is_numeric($value) || is_bool($value)) {
|
||||
return "'" . addslashes("$value") . "'"; // unsafe operation here...
|
||||
} else if ($value instanceof Column) {
|
||||
return $this->columnName($value);
|
||||
} else if ($value === null) {
|
||||
return "NULL";
|
||||
} else {
|
||||
$this->lastError = "Cannot create unsafe value of type: " . gettype($value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract function getValueDefinition($val);
|
||||
protected abstract function addValue($val, &$params);
|
||||
public abstract function addValue($val, &$params = NULL);
|
||||
protected abstract function buildUnsafe(Query $statement): string;
|
||||
|
||||
protected abstract function tableName($table);
|
||||
protected abstract function columnName($col);
|
||||
public abstract function tableName($table): string;
|
||||
public abstract function columnName($col): string;
|
||||
|
||||
// Special Keywords and functions
|
||||
public function now() { return $this->currentTimestamp(); }
|
||||
public abstract function currentTimestamp();
|
||||
public function now(): Keyword { return $this->currentTimestamp(); }
|
||||
public abstract function currentTimestamp(): Keyword;
|
||||
|
||||
public function count($col = NULL) {
|
||||
public function count($col = NULL): Keyword {
|
||||
if (is_null($col)) {
|
||||
return new Keyword("COUNT(*) AS count");
|
||||
} else if($col instanceof Keyword) {
|
||||
@ -372,13 +220,13 @@ abstract class SQL {
|
||||
}
|
||||
}
|
||||
|
||||
public function sum($col) {
|
||||
public function sum($col): Keyword {
|
||||
$sumCol = strtolower(str_replace(".","_", $col)) . "_sum";
|
||||
$col = $this->columnName($col);
|
||||
return new Keyword("SUM($col) AS $sumCol");
|
||||
}
|
||||
|
||||
public function distinct($col) {
|
||||
public function distinct($col): Keyword {
|
||||
$col = $this->columnName($col);
|
||||
return new Keyword("DISTINCT($col)");
|
||||
}
|
||||
@ -431,7 +279,7 @@ abstract class SQL {
|
||||
|
||||
$values = implode(",", $values);
|
||||
} else if($expression instanceof Select) {
|
||||
$values = $this->buildQuery($expression, $params);
|
||||
$values = $expression->build($params);
|
||||
} else {
|
||||
$this->lastError = "Unsupported in-expression value: " . get_class($condition);
|
||||
return false;
|
||||
@ -466,7 +314,7 @@ abstract class SQL {
|
||||
$this->lastError = $str;
|
||||
}
|
||||
|
||||
public function getLastInsertId() {
|
||||
public function getLastInsertId(): int {
|
||||
return $this->lastInsertId;
|
||||
}
|
||||
|
||||
|
14
core/Driver/SQL/Type/CurrentColumn.php
Normal file
14
core/Driver/SQL/Type/CurrentColumn.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Driver\SQL\Type;
|
||||
|
||||
|
||||
use Driver\SQL\Column\Column;
|
||||
|
||||
class CurrentColumn extends Column {
|
||||
|
||||
public function __construct(string $string) {
|
||||
parent::__construct($string);
|
||||
}
|
||||
}
|
11
core/Driver/SQL/Type/CurrentTable.class.php
Normal file
11
core/Driver/SQL/Type/CurrentTable.class.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Driver\SQL\Type;
|
||||
|
||||
use Driver\SQL\Column\StringColumn;
|
||||
|
||||
class CurrentTable extends StringColumn {
|
||||
public function __construct() {
|
||||
parent::__construct("CURRENT_TABLE");
|
||||
}
|
||||
}
|
11
core/Driver/SQL/Type/Trigger.class.php
Normal file
11
core/Driver/SQL/Type/Trigger.class.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Driver\SQL\Type;
|
||||
|
||||
use Driver\SQL\Keyword;
|
||||
|
||||
class Trigger extends Keyword {
|
||||
public function __construct() {
|
||||
parent::__construct("TRIGGER");
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
web:
|
||||
container_name: web
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- "80:80"
|
||||
@ -11,6 +12,7 @@ services:
|
||||
- db
|
||||
- php
|
||||
db:
|
||||
container_name: db
|
||||
image: mariadb:latest
|
||||
ports:
|
||||
- '3306:3306'
|
||||
@ -18,6 +20,7 @@ services:
|
||||
- "MYSQL_ROOT_PASSWORD=webbasedb"
|
||||
- "MYSQL_DATABASE=webbase"
|
||||
php:
|
||||
container_name: php
|
||||
volumes:
|
||||
- .:/application:rw
|
||||
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
|
||||
|
Loading…
Reference in New Issue
Block a user