From 4582ca0f9f4e953afd0a2214702c94e03d0ffa9c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 8 Apr 2021 19:08:05 +0200 Subject: [PATCH] sql expression (lots of todo) --- cli.php | 43 +++++++++++++++++++ core/Configuration/Patch/log.class.php | 19 +++++++- core/Driver/SQL/Column/Column.class.php | 4 +- core/Driver/SQL/Condition/Compare.class.php | 8 ++-- core/Driver/SQL/Condition/Condition.class.php | 4 +- .../SQL/Expression/CurrentTimeStamp.class.php | 7 +++ core/Driver/SQL/Expression/DateAdd.class.php | 23 ++++++++++ .../SQL/Expression/Expression.class.php | 9 ++++ core/Driver/SQL/Keyword.class.php | 4 +- core/Driver/SQL/MySQL.class.php | 26 ++++++++--- core/Driver/SQL/PostgreSQL.class.php | 36 +++++++++++++--- core/Driver/SQL/Query/Query.class.php | 5 +-- core/Driver/SQL/SQL.class.php | 30 ++++++++++--- 13 files changed, 187 insertions(+), 31 deletions(-) create mode 100644 core/Driver/SQL/Expression/CurrentTimeStamp.class.php create mode 100644 core/Driver/SQL/Expression/DateAdd.class.php create mode 100644 core/Driver/SQL/Expression/Expression.class.php diff --git a/cli.php b/cli.php index 6439613..6511b88 100644 --- a/cli.php +++ b/cli.php @@ -5,6 +5,10 @@ include_once 'core/constants.php'; use Configuration\Configuration; use Configuration\DatabaseScript; +use Driver\SQL\Column\Column; +use Driver\SQL\Condition\Compare; +use Driver\SQL\Condition\CondIn; +use Driver\SQL\Expression\DateAdd; use Objects\ConnectionData; use Objects\User; @@ -155,6 +159,45 @@ function handleDatabase(array $argv) { proc_close($process); } + } else if ($action === "clean") { + $user = getUser(); + $sql = $user->getSQL(); + + printLine("Deleting user related data older than 90 days..."); + + // 1st: Select all related tables and entities + $tables = []; + $res = $sql->select("entityId", "tableName") + ->from("EntityLog") + ->where(new Compare($sql->now(), new DateAdd(new Column("modified"), new Column("lifetime"), "DAY"), ">=")) + ->execute(); + + $success = ($res !== false); + if (!$success) { + _exit("Error querying data: " . $sql->getLastError()); + } + + foreach ($res as $row) { + $tableName = $row["tableName"]; + $uid = $row["entityId"]; + if (!isset($tables[$tableName])) { + $tables[$tableName] = []; + } + $tables[$tableName][] = $uid; + } + + // 2nd: delete! + foreach ($tables as $table => $uids) { + $success = $sql->delete($table) + ->where(new CondIn("uid", $uids)) + ->execute(); + + if (!$success) { + printLine("Error deleting data: " . $sql->getLastError()); + } + } + + printLine("Done!"); } else { _exit("Usage: cli.php db [options...]"); } diff --git a/core/Configuration/Patch/log.class.php b/core/Configuration/Patch/log.class.php index 12616c9..2bb3918 100644 --- a/core/Configuration/Patch/log.class.php +++ b/core/Configuration/Patch/log.class.php @@ -38,12 +38,23 @@ class log extends DatabaseScript { ->exec(array( $sql->update("EntityLog") ->set("modified", $sql->now()) - ->where(new Compare("entityId",new CurrentColumn("uid"))) - ->where(new Compare("tableName",new CurrentTable())) + ->where(new Compare("entityId", new CurrentColumn("uid"))) + ->where(new Compare("tableName", new CurrentTable())) + )); + + $deleteProcedure = $sql->createProcedure("DeleteEntityLog") + ->param(new CurrentTable()) + ->param(new IntColumn("uid")) + ->returns(new Trigger()) + ->exec(array( + $sql->delete("EntityLog") + ->where(new Compare("entityId", new CurrentColumn("uid"))) + ->where(new Compare("tableName", new CurrentTable())) )); $queries[] = $insertProcedure; $queries[] = $updateProcedure; + $queries[] = $deleteProcedure; $tables = ["ContactRequest"]; foreach ($tables as $table) { @@ -55,6 +66,10 @@ class log extends DatabaseScript { $queries[] = $sql->createTrigger("${table}_trg_update") ->after()->update($table) ->exec($updateProcedure); + + $queries[] = $sql->createTrigger("${table}_trg_delete") + ->after()->delete($table) + ->exec($deleteProcedure); } return $queries; diff --git a/core/Driver/SQL/Column/Column.class.php b/core/Driver/SQL/Column/Column.class.php index dd7e207..56bfede 100644 --- a/core/Driver/SQL/Column/Column.class.php +++ b/core/Driver/SQL/Column/Column.class.php @@ -2,7 +2,9 @@ namespace Driver\SQL\Column; -class Column { +use Driver\SQL\Expression\Expression; + +class Column extends Expression { private string $name; private bool $nullable; diff --git a/core/Driver/SQL/Condition/Compare.class.php b/core/Driver/SQL/Condition/Compare.class.php index c9df65b..9a0229c 100644 --- a/core/Driver/SQL/Condition/Compare.class.php +++ b/core/Driver/SQL/Condition/Compare.class.php @@ -5,16 +5,16 @@ namespace Driver\SQL\Condition; class Compare extends Condition { private string $operator; - private string $column; + private $lhs; private $value; - public function __construct(string $col, $val, string $operator = '=') { + public function __construct($col, $val, string $operator = '=') { $this->operator = $operator; - $this->column = $col; + $this->lhs = $col; $this->value = $val; } - public function getColumn(): string { return $this->column; } + public function getLHS() { return $this->lhs; } public function getValue() { return $this->value; } public function getOperator(): string { return $this->operator; } diff --git a/core/Driver/SQL/Condition/Condition.class.php b/core/Driver/SQL/Condition/Condition.class.php index 7de7a1a..d8266ec 100644 --- a/core/Driver/SQL/Condition/Condition.class.php +++ b/core/Driver/SQL/Condition/Condition.class.php @@ -2,6 +2,8 @@ namespace Driver\SQL\Condition; -abstract class Condition { +use Driver\SQL\Expression\Expression; + +abstract class Condition extends Expression { } \ No newline at end of file diff --git a/core/Driver/SQL/Expression/CurrentTimeStamp.class.php b/core/Driver/SQL/Expression/CurrentTimeStamp.class.php new file mode 100644 index 0000000..0edfd45 --- /dev/null +++ b/core/Driver/SQL/Expression/CurrentTimeStamp.class.php @@ -0,0 +1,7 @@ +lhs = $lhs; + $this->rhs = $rhs; + $this->unit = $unit; + } + + public function getLHS(): Expression { return $this->lhs; } + public function getRHS(): Expression { return $this->rhs; } + public function getUnit(): string { return $this->unit; } + +} \ No newline at end of file diff --git a/core/Driver/SQL/Expression/Expression.class.php b/core/Driver/SQL/Expression/Expression.class.php new file mode 100644 index 0000000..33f6611 --- /dev/null +++ b/core/Driver/SQL/Expression/Expression.class.php @@ -0,0 +1,9 @@ +columnName($value->getName()); $updateValues[] = "$leftColumn=VALUES($columnName)"; } else if($value instanceof Add) { - $columnName = $this->columnName($value->getColumn()); + $columnName = $this->columnName($value->getLHS()); $operator = $value->getOperator(); $value = $value->getValue(); $updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params); @@ -278,6 +281,8 @@ class MySQL extends SQL { return "NULL"; } else if($value instanceof Keyword) { return $value->getValue(); + } else if ($value instanceof CurrentTimeStamp) { + return "CURRENT_TIMESTAMP"; } else { $str = addslashes($value); return "'$str'"; @@ -291,6 +296,8 @@ class MySQL extends SQL { return $val->getName(); } else if ($val instanceof Column) { return $this->columnName($val->getName()); + } else if ($val instanceof Expression) { + return $this->createExpression($val, $params); } else { $params[] = $val; return "?"; @@ -329,10 +336,6 @@ class MySQL extends SQL { } } - public function currentTimestamp(): Keyword { - return new Keyword("NOW()"); - } - public function getStatus() { return mysqli_stat($this->connection); } @@ -397,4 +400,17 @@ class MySQL extends SQL { return $query; } + + protected function createExpression(Expression $exp, array &$params) { + if ($exp instanceof DateAdd) { + $lhs = $this->addValue($exp->getLHS(), $params); + $rhs = $this->addValue($exp->getRHS(), $params); + $unit = $exp->getUnit(); + return "DATE_ADD($lhs, INTERVAL $rhs $unit)"; + } else if ($exp instanceof CurrentTimeStamp) { + return "NOW()"; + } else { + return parent::createExpression($exp, $params); + } + } } diff --git a/core/Driver/SQL/PostgreSQL.class.php b/core/Driver/SQL/PostgreSQL.class.php index 2983833..3e86775 100644 --- a/core/Driver/SQL/PostgreSQL.class.php +++ b/core/Driver/SQL/PostgreSQL.class.php @@ -16,6 +16,9 @@ use Driver\SQL\Column\JsonColumn; use Driver\SQL\Condition\CondRegex; use Driver\SQL\Expression\Add; +use Driver\SQL\Expression\CurrentTimeStamp; +use Driver\SQL\Expression\DateAdd; +use Driver\SQL\Expression\Expression; use Driver\SQL\Query\CreateProcedure; use Driver\SQL\Query\CreateTrigger; use Driver\SQL\Query\Query; @@ -151,7 +154,7 @@ class PostgreSQL extends SQL { $columnName = $this->columnName($value->getName()); $updateValues[] = "$leftColumn=EXCLUDED.$columnName"; } else if ($value instanceof Add) { - $columnName = $this->columnName($value->getColumn()); + $columnName = $this->columnName($value->getLHS()); $operator = $value->getOperator(); $value = $value->getValue(); $updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params); @@ -257,8 +260,10 @@ class PostgreSQL extends SQL { return $value ? "TRUE" : "FALSE"; } else if(is_null($value)) { return "NULL"; - } else if($value instanceof Keyword) { + } else if ($value instanceof Keyword) { return $value->getValue(); + } else if ($value instanceof CurrentTimeStamp) { + return "CURRENT_TIMESTAMP"; } else { $str = str_replace("'", "''", $value); return "'$str'"; @@ -274,6 +279,8 @@ class PostgreSQL extends SQL { return "NEW." . $this->columnName($val->getName()); } else if ($val instanceof Column) { return $this->columnName($val->getName()); + } else if ($val instanceof Expression) { + return $this->createExpression($val, $params); } else { $params[] = is_bool($val) ? ($val ? "TRUE" : "FALSE") : $val; return '$' . count($params); @@ -312,11 +319,6 @@ class PostgreSQL extends SQL { } } - // Special Keywords and functions - public function currentTimestamp(): Keyword { - return new Keyword("CURRENT_TIMESTAMP"); - } - public function getStatus() { $version = pg_version($this->connection)["client"] ?? "??"; $status = pg_connection_status($this->connection); @@ -410,4 +412,24 @@ class PostgreSQL extends SQL { return $query; } + + protected function createExpression(Expression $exp, array &$params) { + if ($exp instanceof DateAdd) { + $lhs = $this->addValue($exp->getLHS(), $params); + $rhs = $this->addValue($exp->getRHS(), $params); + $unit = $exp->getUnit(); + + if ($exp->getRHS() instanceof Column) { + $rhs = "$rhs * INTERVAL '1 $unit'"; + } else { + $rhs = "$rhs $unit"; + } + + return "$lhs + $rhs"; + } else if ($exp instanceof CurrentTimeStamp) { + return "CURRENT_TIMESTAMP"; + } else { + return parent::createExpression($exp, $params); + } + } } \ No newline at end of file diff --git a/core/Driver/SQL/Query/Query.class.php b/core/Driver/SQL/Query/Query.class.php index 9c69ca4..852fabf 100644 --- a/core/Driver/SQL/Query/Query.class.php +++ b/core/Driver/SQL/Query/Query.class.php @@ -2,9 +2,10 @@ namespace Driver\SQL\Query; +use Driver\SQL\Expression\Expression; use Driver\SQL\SQL; -abstract class Query { +abstract class Query extends Expression { protected SQL $sql; public bool $dump; @@ -23,6 +24,4 @@ abstract class Query { public function execute() { return $this->sql->executeQuery($this); } - - public abstract function build(array &$params): ?string; } \ No newline at end of file diff --git a/core/Driver/SQL/SQL.class.php b/core/Driver/SQL/SQL.class.php index da4d816..d0c743d 100644 --- a/core/Driver/SQL/SQL.class.php +++ b/core/Driver/SQL/SQL.class.php @@ -15,6 +15,9 @@ use Driver\SQL\Constraint\Constraint; use \Driver\SQL\Constraint\Unique; use \Driver\SQL\Constraint\PrimaryKey; use \Driver\SQL\Constraint\ForeignKey; +use Driver\SQL\Expression\CurrentTimeStamp; +use Driver\SQL\Expression\DateAdd; +use Driver\SQL\Expression\Expression; use Driver\SQL\Query\AlterTable; use Driver\SQL\Query\CreateProcedure; use Driver\SQL\Query\CreateTable; @@ -205,8 +208,8 @@ abstract class SQL { public abstract function columnName($col): string; // Special Keywords and functions - public function now(): Keyword { return $this->currentTimestamp(); } - public abstract function currentTimestamp(): Keyword; + public function now(): CurrentTimeStamp { return new CurrentTimeStamp(); } + public function currentTimestamp(): CurrentTimeStamp { return new CurrentTimeStamp(); } public function count($col = NULL): Keyword { if (is_null($col)) { @@ -243,19 +246,23 @@ abstract class SQL { } return "(" . implode(" OR ", $conditions) . ")"; } else if ($condition instanceof Compare) { - $column = $this->columnName($condition->getColumn()); + $lhs = $condition->getLHS(); + $lhs = ($lhs instanceof Expression ? + $this->createExpression($lhs, $params) : + $this->columnName($lhs)); + $value = $condition->getValue(); $operator = $condition->getOperator(); if ($value === null) { if ($operator === "=") { - return "$column IS NULL"; + return "$lhs IS NULL"; } else if ($operator === "!=") { - return "$column IS NOT NULL"; + return "$lhs IS NOT NULL"; } } - return $column . $operator . $this->addValue($value, $params); + return $lhs . $operator . $this->addValue($value, $params); } else if ($condition instanceof CondBool) { return $this->columnName($condition->getValue()); } else if (is_array($condition)) { @@ -306,7 +313,16 @@ abstract class SQL { return $this->columnName($condition->getColumn()) . " IS NULL"; } else { $this->lastError = "Unsupported condition type: " . get_class($condition); - return false; + return null; + } + } + + protected function createExpression(Expression $exp, array &$params) { + if ($exp instanceof Column) { + return $this->columnName($exp); + } else { + $this->lastError = "Unsupported expression type: " . get_class($exp); + return null; } }