Browse Source

Triggers + EntityLog

Roman 3 years ago
parent
commit
43d9a65def

+ 63 - 0
core/Configuration/Patch/log.class.php

@@ -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;
+  }
+
+}

+ 1 - 1
core/Documents/Install.class.php

@@ -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" : "");

+ 109 - 27
core/Driver/SQL/MySQL.class.php

@@ -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;
+  }
 }

+ 142 - 49
core/Driver/SQL/PostgreSQL.class.php

@@ -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;
+  }
 }

+ 42 - 5
core/Driver/SQL/Query/AlterTable.class.php

@@ -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 - 0
core/Driver/SQL/Query/CreateProcedure.class.php

@@ -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; }
+}

+ 23 - 4
core/Driver/SQL/Query/CreateTable.class.php

@@ -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 - 0
core/Driver/SQL/Query/CreateTrigger.class.php

@@ -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;
+  }
+}

+ 6 - 4
core/Driver/SQL/Query/Delete.class.php

@@ -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";
+  }
 }

+ 4 - 4
core/Driver/SQL/Query/Drop.php

@@ -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());
+  }
 }

+ 41 - 2
core/Driver/SQL/Query/Insert.class.php

@@ -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";
+  }
 }

+ 4 - 1
core/Driver/SQL/Query/Query.class.php

@@ -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;
 }

+ 44 - 1
core/Driver/SQL/Query/Select.class.php

@@ -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";
+  }
 }

+ 4 - 4
core/Driver/SQL/Query/Truncate.class.php

@@ -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());
+  }
 }

+ 13 - 4
core/Driver/SQL/Query/Update.class.php

@@ -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";
+  }
 }

+ 76 - 228
core/Driver/SQL/SQL.class.php

@@ -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();
-
-      if (!$tables) {
-        return $this->execute("SELECT $columns", $params, true);
-      }
-
-      $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;
-      }
-    }
-
-    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.";
-      return false;
-    }
-
-    if (is_null($columns) || empty($columns)) {
-      $columnStr = "";
-    } else {
-      $columnStr = " (" . $this->columnName($columns) . ")";
-    }
+  public function executeQuery(Query $query, bool $fetchResult = false) {
 
-    $parameters = array();
-    $values = array();
-    foreach($rows as $row) {
-      $rowPlaceHolder = array();
-      foreach($row as $val) {
-        $rowPlaceHolder[] = $this->addValue($val, $parameters);
-      }
+    $parameters = [];
+    $queryStr = $query->build($parameters);
 
-      $values[] = "(" . implode(",", $rowPlaceHolder) . ")";
+    if($query->dump) {
+      var_dump($queryStr);
+      var_dump($parameters);
     }
 
-    $values = implode(",", $values);
-
-    $onDuplicateKey = $this->getOnDuplicateStrategy($insert->onDuplicateKey(), $parameters);
-    if ($onDuplicateKey === FALSE) {
+    if ($queryStr === null) {
       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);
-    }
-
-    return $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);
+    // fetch generated serial ids for Insert statements
+    $generatedColumn = ($query instanceof Insert ? $query->getReturning() : null);
+    if($success && $fetchResult && $generatedColumn) {
+      $this->fetchReturning($res, $generatedColumn);
     }
-    $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);
+    return $fetchResult ? $res : $success;
   }
 
-  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 - 0
core/Driver/SQL/Type/CurrentColumn.php

@@ -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 - 0
core/Driver/SQL/Type/CurrentTable.class.php

@@ -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 - 0
core/Driver/SQL/Type/Trigger.class.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Driver\SQL\Type;
+
+use Driver\SQL\Keyword;
+
+class Trigger extends Keyword {
+  public function __construct() {
+    parent::__construct("TRIGGER");
+  }
+}

+ 3 - 0
docker-compose.yml

@@ -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