Namespace and ClassPath rewrites
This commit is contained in:
126
Core/Driver/Logger/Logger.class.php
Normal file
126
Core/Driver/Logger/Logger.class.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\Logger;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class Logger {
|
||||
|
||||
public const LOG_FILE_DATE_FORMAT = "Y-m-d_H-i-s_v";
|
||||
public const LOG_LEVELS = [
|
||||
0 => "debug",
|
||||
1 => "info",
|
||||
2 => "warning",
|
||||
3 => "error",
|
||||
4 => "severe"
|
||||
];
|
||||
|
||||
public static Logger $INSTANCE;
|
||||
|
||||
private ?SQL $sql;
|
||||
private string $module;
|
||||
|
||||
// unit tests
|
||||
private bool $unitTestMode;
|
||||
private ?string $lastMessage;
|
||||
private ?string $lastLevel;
|
||||
|
||||
public function __construct(string $module = "Unknown", ?SQL $sql = null) {
|
||||
$this->module = $module;
|
||||
$this->sql = $sql;
|
||||
$this->unitTestMode = false;
|
||||
$this->lastMessage = null;
|
||||
$this->lastLevel = null;
|
||||
}
|
||||
|
||||
protected function getStackTrace(int $pop = 2): string {
|
||||
$debugTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
if ($pop > 0) {
|
||||
array_splice($debugTrace, 0, $pop);
|
||||
}
|
||||
return implode("\n", array_map(function ($trace) {
|
||||
if (isset($trace["file"])) {
|
||||
return $trace["file"] . "#" . $trace["line"] . ": " . $trace["function"] . "()";
|
||||
} else {
|
||||
return $trace["function"] . "()";
|
||||
}
|
||||
}, $debugTrace));
|
||||
}
|
||||
|
||||
public function log(string $message, string $severity, bool $appendStackTrace = true) {
|
||||
|
||||
if ($appendStackTrace) {
|
||||
$message .= "\n" . $this->getStackTrace();
|
||||
}
|
||||
|
||||
$this->lastMessage = $message;
|
||||
$this->lastLevel = $severity;
|
||||
if ($this->unitTestMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->sql !== null && $this->sql->isConnected()) {
|
||||
$success = $this->sql->insert("SystemLog", ["module", "message", "severity"])
|
||||
->addRow($this->module, $message, $severity)
|
||||
->execute();
|
||||
if ($success !== false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// database logging failed, try to log to file
|
||||
$module = preg_replace("/[^a-zA-Z0-9-]/", "-", $this->module);
|
||||
$date = (\DateTime::createFromFormat('U.u', microtime(true)))->format(self::LOG_FILE_DATE_FORMAT);
|
||||
$logFile = implode("_", [$module, $severity, $date]) . ".log";
|
||||
$logPath = implode(DIRECTORY_SEPARATOR, [WEBROOT, "core", "Logs", $logFile]);
|
||||
@file_put_contents($logPath, $message);
|
||||
}
|
||||
|
||||
public function error(string $message): string {
|
||||
$this->log($message, "error");
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function severe(string $message): string {
|
||||
$this->log($message, "severe");
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function warning(string $message): string {
|
||||
$this->log($message, "warning", false);
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function info(string $message): string {
|
||||
$this->log($message, "info", false);
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function debug(string $message, bool $appendStackTrace = false): string {
|
||||
$this->log($message, "debug", $appendStackTrace);
|
||||
return $message;
|
||||
}
|
||||
|
||||
public static function instance(): Logger {
|
||||
if (self::$INSTANCE === null) {
|
||||
self::$INSTANCE = new Logger("Global");
|
||||
}
|
||||
|
||||
return self::$INSTANCE;
|
||||
}
|
||||
|
||||
public function getLastMessage(): ?string {
|
||||
return $this->lastMessage;
|
||||
}
|
||||
|
||||
public function getLastLevel(): ?string {
|
||||
return $this->lastLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling this method will prevent the logger from persisting log messages (writing to database/file),
|
||||
*/
|
||||
public function unitTestMode() {
|
||||
$this->unitTestMode = true;
|
||||
}
|
||||
}
|
||||
11
Core/Driver/SQL/Column/BigIntColumn.class.php
Normal file
11
Core/Driver/SQL/Column/BigIntColumn.class.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class BigIntColumn extends IntColumn {
|
||||
|
||||
public function __construct(string $name, bool $nullable, $defaultValue, bool $unsigned) {
|
||||
parent::__construct($name, $nullable, $defaultValue, $unsigned);
|
||||
$this->type = "BIGINT";
|
||||
}
|
||||
}
|
||||
11
Core/Driver/SQL/Column/BoolColumn.class.php
Normal file
11
Core/Driver/SQL/Column/BoolColumn.class.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class BoolColumn extends Column {
|
||||
|
||||
public function __construct(string $name, bool $defaultValue = false) {
|
||||
parent::__construct($name, false, $defaultValue);
|
||||
}
|
||||
|
||||
}
|
||||
23
Core/Driver/SQL/Column/Column.class.php
Normal file
23
Core/Driver/SQL/Column/Column.class.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
use Core\Driver\SQL\Expression\Expression;
|
||||
|
||||
class Column extends Expression {
|
||||
|
||||
private string $name;
|
||||
private bool $nullable;
|
||||
private $defaultValue;
|
||||
|
||||
public function __construct(string $name, bool $nullable = false, $defaultValue = NULL) {
|
||||
$this->name = $name;
|
||||
$this->nullable = $nullable;
|
||||
$this->defaultValue = $defaultValue;
|
||||
}
|
||||
|
||||
public function getName(): string { return $this->name; }
|
||||
public function notNull(): bool { return !$this->nullable; }
|
||||
public function getDefaultValue() { return $this->defaultValue; }
|
||||
|
||||
}
|
||||
10
Core/Driver/SQL/Column/DateTimeColumn.class.php
Normal file
10
Core/Driver/SQL/Column/DateTimeColumn.class.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class DateTimeColumn extends Column {
|
||||
|
||||
public function __construct(string $name, bool $nullable = false, $defaultValue = NULL) {
|
||||
parent::__construct($name, $nullable, $defaultValue);
|
||||
}
|
||||
}
|
||||
10
Core/Driver/SQL/Column/DoubleColumn.class.php
Normal file
10
Core/Driver/SQL/Column/DoubleColumn.class.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class DoubleColumn extends NumericColumn {
|
||||
public function __construct(string $name, bool $nullable, $defaultValue = NULL, ?int $totalDigits = null, ?int $decimalDigits = null) {
|
||||
parent::__construct($name, $nullable, $defaultValue, $totalDigits, $decimalDigits);
|
||||
$this->type = "DOUBLE";
|
||||
}
|
||||
}
|
||||
19
Core/Driver/SQL/Column/EnumColumn.class.php
Normal file
19
Core/Driver/SQL/Column/EnumColumn.class.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class EnumColumn extends Column {
|
||||
|
||||
private array $values;
|
||||
|
||||
public function __construct(string $name, array $values, bool $nullable = false, $defaultValue = NULL) {
|
||||
parent::__construct($name, $nullable, $defaultValue);
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
public function addValue(string $value) {
|
||||
$this->values[] = $value;
|
||||
}
|
||||
|
||||
public function getValues(): array { return $this->values; }
|
||||
}
|
||||
10
Core/Driver/SQL/Column/FloatColumn.class.php
Normal file
10
Core/Driver/SQL/Column/FloatColumn.class.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class FloatColumn extends NumericColumn {
|
||||
public function __construct(string $name, bool $nullable, $defaultValue = NULL, ?int $totalDigits = null, ?int $decimalDigits = null) {
|
||||
parent::__construct($name, $nullable, $defaultValue, $totalDigits, $decimalDigits);
|
||||
$this->type = "FLOAT";
|
||||
}
|
||||
}
|
||||
23
Core/Driver/SQL/Column/IntColumn.class.php
Normal file
23
Core/Driver/SQL/Column/IntColumn.class.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class IntColumn extends Column {
|
||||
|
||||
protected string $type;
|
||||
private bool $unsigned;
|
||||
|
||||
public function __construct(string $name, bool $nullable = false, $defaultValue = NULL, bool $unsigned = false) {
|
||||
parent::__construct($name, $nullable, $defaultValue);
|
||||
$this->type = "INTEGER";
|
||||
$this->unsigned = $unsigned;
|
||||
}
|
||||
|
||||
public function isUnsigned(): bool {
|
||||
return $this->unsigned;
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
11
Core/Driver/SQL/Column/JsonColumn.class.php
Normal file
11
Core/Driver/SQL/Column/JsonColumn.class.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class JsonColumn extends Column {
|
||||
|
||||
public function __construct(string $name, bool $nullable = false, $defaultValue = null) {
|
||||
parent::__construct($name, $nullable, $defaultValue);
|
||||
}
|
||||
|
||||
}
|
||||
31
Core/Driver/SQL/Column/NumericColumn.class.php
Normal file
31
Core/Driver/SQL/Column/NumericColumn.class.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
|
||||
class NumericColumn extends Column {
|
||||
|
||||
protected string $type;
|
||||
private ?int $totalDigits;
|
||||
private ?int $decimalDigits;
|
||||
|
||||
public function __construct(string $name, bool $nullable, $defaultValue = NULL, ?int $totalDigits = null, ?int $decimalDigits = null) {
|
||||
parent::__construct($name, $nullable, $defaultValue);
|
||||
$this->totalDigits = $totalDigits;
|
||||
$this->decimalDigits = $decimalDigits;
|
||||
$this->type = "NUMERIC";
|
||||
}
|
||||
|
||||
public function getDecimalDigits(): ?int {
|
||||
return $this->decimalDigits;
|
||||
}
|
||||
|
||||
public function getTotalDigits(): ?int {
|
||||
return $this->totalDigits;
|
||||
}
|
||||
|
||||
public function getTypeName(): string {
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
11
Core/Driver/SQL/Column/SerialColumn.class.php
Normal file
11
Core/Driver/SQL/Column/SerialColumn.class.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class SerialColumn extends Column {
|
||||
|
||||
public function __construct(string $name, $defaultValue = NULL) {
|
||||
parent::__construct($name, false, $defaultValue); # not nullable
|
||||
}
|
||||
|
||||
}
|
||||
15
Core/Driver/SQL/Column/StringColumn.class.php
Normal file
15
Core/Driver/SQL/Column/StringColumn.class.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Column;
|
||||
|
||||
class StringColumn extends Column {
|
||||
|
||||
private ?int $maxSize;
|
||||
|
||||
public function __construct(string $name, ?int $maxSize = null, bool $nullable = false, $defaultValue = null) {
|
||||
parent::__construct($name, $nullable, $defaultValue);
|
||||
$this->maxSize = $maxSize;
|
||||
}
|
||||
|
||||
public function getMaxSize(): ?int { return $this->maxSize; }
|
||||
}
|
||||
21
Core/Driver/SQL/Condition/Compare.class.php
Normal file
21
Core/Driver/SQL/Condition/Compare.class.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class Compare extends Condition {
|
||||
|
||||
private string $operator;
|
||||
private string $column;
|
||||
private $value;
|
||||
|
||||
public function __construct(string $col, $val, string $operator = '=') {
|
||||
$this->operator = $operator;
|
||||
$this->column = $col;
|
||||
$this->value = $val;
|
||||
}
|
||||
|
||||
public function getColumn(): string { return $this->column; }
|
||||
public function getValue() { return $this->value; }
|
||||
public function getOperator(): string { return $this->operator; }
|
||||
|
||||
}
|
||||
14
Core/Driver/SQL/Condition/CondAnd.class.php
Normal file
14
Core/Driver/SQL/Condition/CondAnd.class.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class CondAnd extends Condition {
|
||||
|
||||
private array $conditions;
|
||||
|
||||
public function __construct(...$conditions) {
|
||||
$this->conditions = $conditions;
|
||||
}
|
||||
|
||||
public function getConditions(): array { return $this->conditions; }
|
||||
}
|
||||
15
Core/Driver/SQL/Condition/CondBool.class.php
Normal file
15
Core/Driver/SQL/Condition/CondBool.class.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class CondBool extends Condition {
|
||||
|
||||
private $value;
|
||||
|
||||
public function __construct($val) {
|
||||
$this->value = $val;
|
||||
}
|
||||
|
||||
public function getValue() { return $this->value; }
|
||||
|
||||
}
|
||||
17
Core/Driver/SQL/Condition/CondIn.class.php
Normal file
17
Core/Driver/SQL/Condition/CondIn.class.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class CondIn extends Condition {
|
||||
|
||||
private $needle;
|
||||
private $haystack;
|
||||
|
||||
public function __construct($needle, $haystack) {
|
||||
$this->needle = $needle;
|
||||
$this->haystack = $haystack;
|
||||
}
|
||||
|
||||
public function getNeedle() { return $this->needle; }
|
||||
public function getHaystack() { return $this->haystack; }
|
||||
}
|
||||
20
Core/Driver/SQL/Condition/CondKeyword.class.php
Normal file
20
Core/Driver/SQL/Condition/CondKeyword.class.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
abstract class CondKeyword extends Condition {
|
||||
|
||||
private $leftExpression;
|
||||
private $rightExpression;
|
||||
private string $keyword;
|
||||
|
||||
public function __construct(string $keyword, $leftExpression, $rightExpression) {
|
||||
$this->leftExpression = $leftExpression;
|
||||
$this->rightExpression = $rightExpression;
|
||||
$this->keyword = $keyword;
|
||||
}
|
||||
|
||||
public function getLeftExp() { return $this->leftExpression; }
|
||||
public function getRightExp() { return $this->rightExpression; }
|
||||
public function getKeyword(): string { return $this->keyword; }
|
||||
}
|
||||
10
Core/Driver/SQL/Condition/CondLike.class.php
Normal file
10
Core/Driver/SQL/Condition/CondLike.class.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class CondLike extends CondKeyword {
|
||||
|
||||
public function __construct($leftExpression, $rightExpression) {
|
||||
parent::__construct("LIKE", $leftExpression, $rightExpression);
|
||||
}
|
||||
}
|
||||
16
Core/Driver/SQL/Condition/CondNot.class.php
Normal file
16
Core/Driver/SQL/Condition/CondNot.class.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class CondNot extends Condition {
|
||||
|
||||
private $expression; // string or condition
|
||||
|
||||
public function __construct($expression) {
|
||||
$this->expression = $expression;
|
||||
}
|
||||
|
||||
public function getExpression() {
|
||||
return $this->expression;
|
||||
}
|
||||
}
|
||||
14
Core/Driver/SQL/Condition/CondNull.class.php
Normal file
14
Core/Driver/SQL/Condition/CondNull.class.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class CondNull extends Condition {
|
||||
|
||||
private string $column;
|
||||
|
||||
public function __construct(string $col) {
|
||||
$this->column = $col;
|
||||
}
|
||||
|
||||
public function getColumn(): string { return $this->column; }
|
||||
}
|
||||
14
Core/Driver/SQL/Condition/CondOr.class.php
Normal file
14
Core/Driver/SQL/Condition/CondOr.class.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class CondOr extends Condition {
|
||||
|
||||
private array $conditions;
|
||||
|
||||
public function __construct(...$conditions) {
|
||||
$this->conditions = (!empty($conditions) && is_array($conditions[0])) ? $conditions[0] : $conditions;
|
||||
}
|
||||
|
||||
public function getConditions(): array { return $this->conditions; }
|
||||
}
|
||||
11
Core/Driver/SQL/Condition/CondRegex.class.php
Normal file
11
Core/Driver/SQL/Condition/CondRegex.class.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
class CondRegex extends CondKeyword {
|
||||
|
||||
public function __construct($leftExpression, $rightExpression) {
|
||||
parent::__construct("REGEXP", $leftExpression, $rightExpression);
|
||||
}
|
||||
|
||||
}
|
||||
9
Core/Driver/SQL/Condition/Condition.class.php
Normal file
9
Core/Driver/SQL/Condition/Condition.class.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
use Core\Driver\SQL\Expression\Expression;
|
||||
|
||||
abstract class Condition extends Expression {
|
||||
|
||||
}
|
||||
22
Core/Driver/SQL/Condition/Exists.class.php
Normal file
22
Core/Driver/SQL/Condition/Exists.class.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Core\Driver\SQL\Condition;
|
||||
|
||||
|
||||
use Core\Driver\SQL\Query\Select;
|
||||
|
||||
class Exists extends Condition
|
||||
{
|
||||
private Select $subQuery;
|
||||
|
||||
public function __construct(Select $subQuery)
|
||||
{
|
||||
$this->subQuery = $subQuery;
|
||||
}
|
||||
|
||||
public function getSubQuery(): Select
|
||||
{
|
||||
return $this->subQuery;
|
||||
}
|
||||
}
|
||||
18
Core/Driver/SQL/Constraint/Constraint.class.php
Normal file
18
Core/Driver/SQL/Constraint/Constraint.class.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Constraint;
|
||||
|
||||
abstract class Constraint {
|
||||
|
||||
private array $columnNames;
|
||||
private ?string $name;
|
||||
|
||||
public function __construct($columnNames, ?string $constraintName = NULL) {
|
||||
$this->columnNames = (!is_array($columnNames) ? array($columnNames) : $columnNames);
|
||||
$this->name = $constraintName;
|
||||
}
|
||||
|
||||
public function getColumnNames(): array { return $this->columnNames; }
|
||||
public function getName(): ?string { return $this->name; }
|
||||
public function setName(string $name) { $this->name = $name; }
|
||||
}
|
||||
23
Core/Driver/SQL/Constraint/ForeignKey.class.php
Normal file
23
Core/Driver/SQL/Constraint/ForeignKey.class.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Constraint;
|
||||
|
||||
use Core\Driver\SQL\Strategy\Strategy;
|
||||
|
||||
class ForeignKey extends Constraint {
|
||||
|
||||
private string $referencedTable;
|
||||
private string $referencedColumn;
|
||||
private ?Strategy $strategy;
|
||||
|
||||
public function __construct(string $columnName, string $refTable, string $refColumn, ?Strategy $strategy = NULL) {
|
||||
parent::__construct($columnName);
|
||||
$this->referencedTable = $refTable;
|
||||
$this->referencedColumn = $refColumn;
|
||||
$this->strategy = $strategy;
|
||||
}
|
||||
|
||||
public function getReferencedTable(): string { return $this->referencedTable; }
|
||||
public function getReferencedColumn(): string { return $this->referencedColumn; }
|
||||
public function onDelete(): ?Strategy { return $this->strategy; }
|
||||
}
|
||||
11
Core/Driver/SQL/Constraint/PrimaryKey.class.php
Normal file
11
Core/Driver/SQL/Constraint/PrimaryKey.class.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Constraint;
|
||||
|
||||
class PrimaryKey extends Constraint {
|
||||
|
||||
public function __construct(...$names) {
|
||||
parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names);
|
||||
}
|
||||
|
||||
}
|
||||
11
Core/Driver/SQL/Constraint/Unique.class.php
Normal file
11
Core/Driver/SQL/Constraint/Unique.class.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Constraint;
|
||||
|
||||
class Unique extends Constraint {
|
||||
|
||||
public function __construct(...$names) {
|
||||
parent::__construct((!empty($names) && is_array($names[0])) ? $names[0] : $names);
|
||||
}
|
||||
|
||||
}
|
||||
14
Core/Driver/SQL/Expression/Add.class.php
Normal file
14
Core/Driver/SQL/Expression/Add.class.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Expression;
|
||||
|
||||
use Core\Driver\SQL\Condition\Compare;
|
||||
|
||||
# TODO: change confusing class inheritance here
|
||||
class Add extends Compare {
|
||||
|
||||
public function __construct(string $col, $val) {
|
||||
parent::__construct($col, $val, "+");
|
||||
}
|
||||
|
||||
}
|
||||
23
Core/Driver/SQL/Expression/CaseWhen.class.php
Normal file
23
Core/Driver/SQL/Expression/CaseWhen.class.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Expression;
|
||||
|
||||
use Core\Driver\SQL\Condition\Condition;
|
||||
|
||||
class CaseWhen extends Expression {
|
||||
|
||||
private Condition $condition;
|
||||
private $trueCase;
|
||||
private $falseCase;
|
||||
|
||||
public function __construct(Condition $condition, $trueCase, $falseCase) {
|
||||
$this->condition = $condition;
|
||||
$this->trueCase = $trueCase;
|
||||
$this->falseCase = $falseCase;
|
||||
}
|
||||
|
||||
public function getCondition(): Condition { return $this->condition; }
|
||||
public function getTrueCase() { return $this->trueCase; }
|
||||
public function getFalseCase() { return $this->falseCase; }
|
||||
|
||||
}
|
||||
7
Core/Driver/SQL/Expression/CurrentTimeStamp.class.php
Normal file
7
Core/Driver/SQL/Expression/CurrentTimeStamp.class.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Expression;
|
||||
|
||||
class CurrentTimeStamp extends Expression {
|
||||
|
||||
}
|
||||
23
Core/Driver/SQL/Expression/DateAdd.class.php
Normal file
23
Core/Driver/SQL/Expression/DateAdd.class.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Expression;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class DateAdd extends Expression {
|
||||
|
||||
private Expression $lhs;
|
||||
private Expression $rhs;
|
||||
private string $unit;
|
||||
|
||||
public function __construct(Expression $lhs, Expression $rhs, string $unit) {
|
||||
$this->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; }
|
||||
|
||||
}
|
||||
23
Core/Driver/SQL/Expression/DateSub.class.php
Normal file
23
Core/Driver/SQL/Expression/DateSub.class.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Expression;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class DateSub extends Expression {
|
||||
|
||||
private Expression $lhs;
|
||||
private Expression $rhs;
|
||||
private string $unit;
|
||||
|
||||
public function __construct(Expression $lhs, Expression $rhs, string $unit) {
|
||||
$this->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; }
|
||||
|
||||
}
|
||||
9
Core/Driver/SQL/Expression/Expression.class.php
Normal file
9
Core/Driver/SQL/Expression/Expression.class.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Expression;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
abstract class Expression {
|
||||
|
||||
}
|
||||
18
Core/Driver/SQL/Expression/JsonArrayAgg.class.php
Normal file
18
Core/Driver/SQL/Expression/JsonArrayAgg.class.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Expression;
|
||||
|
||||
class JsonArrayAgg extends Expression {
|
||||
|
||||
private $value;
|
||||
private string $alias;
|
||||
|
||||
public function __construct($value, string $alias) {
|
||||
$this->value = $value;
|
||||
$this->alias = $alias;
|
||||
}
|
||||
|
||||
public function getValue() { return $this->value; }
|
||||
public function getAlias(): string { return $this->alias; }
|
||||
|
||||
}
|
||||
18
Core/Driver/SQL/Expression/Sum.class.php
Normal file
18
Core/Driver/SQL/Expression/Sum.class.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Expression;
|
||||
|
||||
class Sum extends Expression {
|
||||
|
||||
private $value;
|
||||
private string $alias;
|
||||
|
||||
public function __construct($value, string $alias) {
|
||||
$this->value = $value;
|
||||
$this->alias = $alias;
|
||||
}
|
||||
|
||||
public function getValue() { return $this->value; }
|
||||
public function getAlias(): string { return $this->alias; }
|
||||
|
||||
}
|
||||
34
Core/Driver/SQL/Join.class.php
Normal file
34
Core/Driver/SQL/Join.class.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL;
|
||||
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Condition\Compare;
|
||||
|
||||
class Join {
|
||||
|
||||
private string $type;
|
||||
private string $table;
|
||||
private string $columnA;
|
||||
private string $columnB;
|
||||
private ?string $tableAlias;
|
||||
private array $conditions;
|
||||
|
||||
public function __construct(string $type, string $table, string $columnA, string $columnB, ?string $tableAlias = null, array $conditions = []) {
|
||||
$this->type = $type;
|
||||
$this->table = $table;
|
||||
$this->columnA = $columnA;
|
||||
$this->columnB = $columnB;
|
||||
$this->tableAlias = $tableAlias;
|
||||
$this->conditions = $conditions;
|
||||
array_unshift($this->conditions , new Compare($columnA, new Column($columnB), "="));
|
||||
}
|
||||
|
||||
public function getType(): string { return $this->type; }
|
||||
public function getTable(): string { return $this->table; }
|
||||
public function getColumnA(): string { return $this->columnA; }
|
||||
public function getColumnB(): string { return $this->columnB; }
|
||||
public function getTableAlias(): ?string { return $this->tableAlias; }
|
||||
public function getConditions(): array { return $this->conditions; }
|
||||
|
||||
}
|
||||
17
Core/Driver/SQL/Keyword.class.php
Normal file
17
Core/Driver/SQL/Keyword.class.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL;
|
||||
|
||||
use Core\Driver\SQL\Expression\Expression;
|
||||
|
||||
class Keyword extends Expression {
|
||||
|
||||
private string $value;
|
||||
|
||||
public function __construct(string $value) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValue(): string { return $this->value; }
|
||||
|
||||
}
|
||||
520
Core/Driver/SQL/MySQL.class.php
Normal file
520
Core/Driver/SQL/MySQL.class.php
Normal file
@@ -0,0 +1,520 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL;
|
||||
|
||||
use Core\API\Parameter\Parameter;
|
||||
|
||||
use DateTime;
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Column\IntColumn;
|
||||
use Core\Driver\SQL\Column\NumericColumn;
|
||||
use Core\Driver\SQL\Column\SerialColumn;
|
||||
use Core\Driver\SQL\Column\StringColumn;
|
||||
use Core\Driver\SQL\Column\EnumColumn;
|
||||
use Core\Driver\SQL\Column\DateTimeColumn;
|
||||
use Core\Driver\SQL\Column\BoolColumn;
|
||||
use Core\Driver\SQL\Column\JsonColumn;
|
||||
|
||||
use Core\Driver\SQL\Expression\Add;
|
||||
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Core\Driver\SQL\Expression\DateAdd;
|
||||
use Core\Driver\SQL\Expression\DateSub;
|
||||
use Core\Driver\SQL\Expression\Expression;
|
||||
use Core\Driver\SQL\Expression\JsonArrayAgg;
|
||||
use Core\Driver\SQL\Query\CreateProcedure;
|
||||
use Core\Driver\SQL\Query\CreateTrigger;
|
||||
use Core\Driver\SQL\Query\Query;
|
||||
use Core\Driver\SQL\Strategy\Strategy;
|
||||
use Core\Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Core\Driver\SQL\Type\CurrentColumn;
|
||||
use Core\Driver\SQL\Type\CurrentTable;
|
||||
use Core\Driver\SQL\Type\Trigger;
|
||||
|
||||
class MySQL extends SQL {
|
||||
|
||||
public function __construct($connectionData) {
|
||||
parent::__construct($connectionData);
|
||||
}
|
||||
|
||||
public function checkRequirements() {
|
||||
return function_exists('mysqli_connect');
|
||||
}
|
||||
|
||||
public function getDriverName() {
|
||||
return 'mysqli';
|
||||
}
|
||||
|
||||
// Connection Management
|
||||
public function connect() {
|
||||
|
||||
if (!is_null($this->connection)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@$this->connection = mysqli_connect(
|
||||
$this->connectionData->getHost(),
|
||||
$this->connectionData->getLogin(),
|
||||
$this->connectionData->getPassword(),
|
||||
$this->connectionData->getProperty('database'),
|
||||
$this->connectionData->getPort()
|
||||
);
|
||||
|
||||
if (mysqli_connect_errno()) {
|
||||
$this->lastError = $this->logger->severe("Failed to connect to MySQL: " . mysqli_connect_error());
|
||||
$this->connection = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
mysqli_set_charset($this->connection, $this->connectionData->getProperty('encoding', 'UTF8'));
|
||||
return true;
|
||||
}
|
||||
|
||||
public function disconnect() {
|
||||
if (is_null($this->connection)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mysqli_close($this->connection);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getLastError(): string {
|
||||
$lastError = parent::getLastError();
|
||||
if (empty($lastError)) {
|
||||
$lastError = mysqli_error($this->connection);
|
||||
}
|
||||
|
||||
return $lastError;
|
||||
}
|
||||
|
||||
private function getPreparedParams($values): array {
|
||||
$sqlParams = array('');
|
||||
foreach ($values as $value) {
|
||||
$paramType = Parameter::parseType($value);
|
||||
switch ($paramType) {
|
||||
case Parameter::TYPE_BOOLEAN:
|
||||
$value = $value ? 1 : 0;
|
||||
$sqlParams[0] .= 'i';
|
||||
break;
|
||||
case Parameter::TYPE_INT:
|
||||
$sqlParams[0] .= 'i';
|
||||
break;
|
||||
case Parameter::TYPE_FLOAT:
|
||||
$sqlParams[0] .= 'd';
|
||||
break;
|
||||
case Parameter::TYPE_DATE:
|
||||
if ($value instanceof DateTime) {
|
||||
$value = $value->format('Y-m-d');
|
||||
}
|
||||
$sqlParams[0] .= 's';
|
||||
break;
|
||||
case Parameter::TYPE_TIME:
|
||||
if ($value instanceof DateTime) {
|
||||
$value = $value->format('H:i:s');
|
||||
}
|
||||
$sqlParams[0] .= 's';
|
||||
break;
|
||||
case Parameter::TYPE_DATE_TIME:
|
||||
if ($value instanceof DateTime) {
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
}
|
||||
$sqlParams[0] .= 's';
|
||||
break;
|
||||
case Parameter::TYPE_ARRAY:
|
||||
$value = json_encode($value);
|
||||
$sqlParams[0] .= 's';
|
||||
break;
|
||||
case Parameter::TYPE_EMAIL:
|
||||
default:
|
||||
$sqlParams[0] .= 's';
|
||||
}
|
||||
|
||||
$sqlParams[] = $value;
|
||||
}
|
||||
|
||||
return $sqlParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE) {
|
||||
|
||||
$result = null;
|
||||
$this->lastError = "";
|
||||
$stmt = null;
|
||||
$res = null;
|
||||
$success = false;
|
||||
|
||||
try {
|
||||
if (empty($values)) {
|
||||
$res = mysqli_query($this->connection, $query);
|
||||
$success = ($res !== FALSE);
|
||||
if ($success) {
|
||||
switch ($fetchType) {
|
||||
case self::FETCH_NONE:
|
||||
$result = true;
|
||||
break;
|
||||
case self::FETCH_ONE:
|
||||
$result = $res->fetch_assoc();
|
||||
break;
|
||||
case self::FETCH_ALL:
|
||||
$result = $res->fetch_all(MYSQLI_ASSOC);
|
||||
break;
|
||||
case self::FETCH_ITERATIVE:
|
||||
$result = new RowIteratorMySQL($res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if ($stmt = $this->connection->prepare($query)) {
|
||||
|
||||
$sqlParams = $this->getPreparedParams($values);
|
||||
if ($stmt->bind_param(...$sqlParams)) {
|
||||
if ($stmt->execute()) {
|
||||
if ($fetchType === self::FETCH_NONE) {
|
||||
$result = true;
|
||||
$success = true;
|
||||
} else {
|
||||
$res = $stmt->get_result();
|
||||
if ($res) {
|
||||
switch ($fetchType) {
|
||||
case self::FETCH_ONE:
|
||||
$result = $res->fetch_assoc();
|
||||
break;
|
||||
case self::FETCH_ALL:
|
||||
$result = $res->fetch_all(MYSQLI_ASSOC);
|
||||
break;
|
||||
case self::FETCH_ITERATIVE:
|
||||
$result = new RowIteratorMySQL($res);
|
||||
break;
|
||||
}
|
||||
$success = true;
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("PreparedStatement::get_result failed: $stmt->error ($stmt->errno)");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("PreparedStatement::execute failed: $stmt->error ($stmt->errno)");
|
||||
}
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("PreparedStatement::prepare failed: $stmt->error ($stmt->errno)");
|
||||
}
|
||||
}
|
||||
} catch (\mysqli_sql_exception $exception) {
|
||||
$this->lastError = $this->logger->error("MySQL::execute failed: $stmt->error ($stmt->errno)");
|
||||
} finally {
|
||||
|
||||
if ($res !== null && !is_bool($res) && $fetchType !== self::FETCH_ITERATIVE) {
|
||||
$res->close();
|
||||
}
|
||||
|
||||
if ($stmt !== null && !is_bool($stmt)) {
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $success ? $result : false;
|
||||
}
|
||||
|
||||
public function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string {
|
||||
if (is_null($strategy)) {
|
||||
return "";
|
||||
} else 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=VALUES($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, $params);
|
||||
}
|
||||
}
|
||||
|
||||
return " ON DUPLICATE KEY UPDATE " . implode(",", $updateValues);
|
||||
} else {
|
||||
$strategyClass = get_class($strategy);
|
||||
$this->lastError = $this->logger->error("ON DUPLICATE Strategy $strategyClass is not supported yet.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function fetchReturning($res, string $returningCol) {
|
||||
$this->lastInsertId = mysqli_insert_id($this->connection);
|
||||
}
|
||||
|
||||
public function getColumnType(Column $column): ?string {
|
||||
if ($column instanceof StringColumn) {
|
||||
$maxSize = $column->getMaxSize();
|
||||
if ($maxSize) {
|
||||
return "VARCHAR($maxSize)";
|
||||
} else {
|
||||
return "TEXT";
|
||||
}
|
||||
} else if ($column instanceof SerialColumn) {
|
||||
return "INTEGER AUTO_INCREMENT";
|
||||
} else if ($column instanceof IntColumn) {
|
||||
$unsigned = $column->isUnsigned() ? " UNSIGNED" : "";
|
||||
return $column->getType() . $unsigned;
|
||||
} else if ($column instanceof DateTimeColumn) {
|
||||
return "DATETIME";
|
||||
} else if ($column instanceof BoolColumn) {
|
||||
return "BOOLEAN";
|
||||
} else if ($column instanceof JsonColumn) {
|
||||
return "LONGTEXT"; # some maria db setups don't allow JSON here…
|
||||
} else if ($column instanceof NumericColumn) {
|
||||
$digitsTotal = $column->getTotalDigits();
|
||||
$digitsDecimal = $column->getDecimalDigits();
|
||||
$type = $column->getTypeName();
|
||||
if ($digitsTotal !== null) {
|
||||
if ($digitsDecimal !== null) {
|
||||
return "$type($digitsTotal,$digitsDecimal)";
|
||||
} else {
|
||||
return "$type($digitsTotal)";
|
||||
}
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public function getColumnDefinition(Column $column): ?string {
|
||||
$columnName = $this->columnName($column->getName());
|
||||
$defaultValue = $column->getDefaultValue();
|
||||
if ($column instanceof EnumColumn) { // check this, shouldn't it be in getColumnType?
|
||||
$values = array();
|
||||
foreach ($column->getValues() as $value) {
|
||||
$values[] = $this->getValueDefinition($value);
|
||||
}
|
||||
|
||||
$values = implode(",", $values);
|
||||
$type = "ENUM($values)";
|
||||
} else {
|
||||
$type = $this->getColumnType($column);
|
||||
if (!$type) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === "LONGTEXT") {
|
||||
$defaultValue = NULL; # must be null :(
|
||||
}
|
||||
|
||||
$notNull = $column->notNull() ? " NOT NULL" : "";
|
||||
if (!is_null($defaultValue) || !$column->notNull()) {
|
||||
$defaultValue = " DEFAULT " . $this->getValueDefinition($defaultValue);
|
||||
} else {
|
||||
$defaultValue = "";
|
||||
}
|
||||
|
||||
return "$columnName $type$notNull$defaultValue";
|
||||
}
|
||||
|
||||
public function getValueDefinition($value) {
|
||||
if (is_numeric($value)) {
|
||||
return $value;
|
||||
} else if (is_bool($value)) {
|
||||
return $value ? "TRUE" : "FALSE";
|
||||
} else if (is_null($value)) {
|
||||
return "NULL";
|
||||
} else if ($value instanceof Keyword) {
|
||||
return $value->getValue();
|
||||
} else if ($value instanceof CurrentTimeStamp) {
|
||||
return "CURRENT_TIMESTAMP";
|
||||
} else {
|
||||
$str = addslashes($value);
|
||||
return "'$str'";
|
||||
}
|
||||
}
|
||||
|
||||
public function addValue($val, &$params = NULL, bool $unsafe = false) {
|
||||
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 if ($val instanceof Expression) {
|
||||
return $this->createExpression($val, $params);
|
||||
} else {
|
||||
if ($unsafe) {
|
||||
return $this->getUnsafeValue($val);
|
||||
} else {
|
||||
$params[] = $val;
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function tableName($table): string {
|
||||
if (is_array($table)) {
|
||||
$tables = array();
|
||||
foreach ($table as $t) $tables[] = $this->tableName($t);
|
||||
return implode(",", $tables);
|
||||
} else {
|
||||
$parts = explode(" ", $table);
|
||||
if (count($parts) === 2) {
|
||||
list ($name, $alias) = $parts;
|
||||
return "`$name` $alias";
|
||||
} else {
|
||||
return "`$table`";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function columnName($col): string {
|
||||
if ($col instanceof Keyword) {
|
||||
return $col->getValue();
|
||||
} elseif (is_array($col)) {
|
||||
$columns = array();
|
||||
foreach ($col as $c) $columns[] = $this->columnName($c);
|
||||
return implode(",", $columns);
|
||||
} else {
|
||||
if (($index = strrpos($col, ".")) !== FALSE) {
|
||||
$tableName = $this->tableName(substr($col, 0, $index));
|
||||
$columnName = $this->columnName(substr($col, $index + 1));
|
||||
return "$tableName.$columnName";
|
||||
} else if (($index = stripos($col, " as ")) !== FALSE) {
|
||||
$columnName = $this->columnName(trim(substr($col, 0, $index)));
|
||||
$alias = trim(substr($col, $index + 4));
|
||||
return "$columnName as $alias";
|
||||
} else {
|
||||
return "`$col`";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getStatus() {
|
||||
return mysqli_stat($this->connection);
|
||||
}
|
||||
|
||||
public function createTriggerBody(CreateTrigger $trigger, array $parameters = []): ?string {
|
||||
$values = array();
|
||||
|
||||
foreach ($parameters as $paramValue) {
|
||||
if ($paramValue instanceof CurrentTable) {
|
||||
$values[] = $this->getUnsafeValue($trigger->getTable());
|
||||
} elseif ($paramValue instanceof CurrentColumn) {
|
||||
$prefix = ($trigger->getEvent() !== "DELETE" ? "NEW." : "OLD.");
|
||||
$values[] = $this->columnName($prefix . $paramValue->getName());
|
||||
} else {
|
||||
$values[] = $paramValue;
|
||||
}
|
||||
}
|
||||
|
||||
$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->lastError = $this->logger->error("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->lastError = $this->logger->error("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;
|
||||
}
|
||||
|
||||
protected function createExpression(Expression $exp, array &$params): ?string {
|
||||
if ($exp instanceof DateAdd || $exp instanceof DateSub) {
|
||||
$lhs = $this->addValue($exp->getLHS(), $params);
|
||||
$rhs = $this->addValue($exp->getRHS(), $params);
|
||||
$unit = $exp->getUnit();
|
||||
$dateFunction = ($exp instanceof DateAdd ? "DATE_ADD" : "DATE_SUB");
|
||||
return "$dateFunction($lhs, INTERVAL $rhs $unit)";
|
||||
} else if ($exp instanceof CurrentTimeStamp) {
|
||||
return "NOW()";
|
||||
} else if ($exp instanceof JsonArrayAgg) {
|
||||
$value = $this->addValue($exp->getValue(), $params);
|
||||
$alias = $this->columnName($exp->getAlias());
|
||||
return "JSON_ARRAYAGG($value) as $alias";
|
||||
} else {
|
||||
return parent::createExpression($exp, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RowIteratorMySQL extends RowIterator {
|
||||
|
||||
public function __construct($resultSet, bool $useCache = false) {
|
||||
parent::__construct($resultSet, $useCache);
|
||||
}
|
||||
|
||||
protected function getNumRows(): int {
|
||||
return $this->resultSet->num_rows;
|
||||
}
|
||||
|
||||
protected function fetchRow(int $index): array {
|
||||
// check if we already fetched that row
|
||||
if (!$this->useCache || $index >= count($this->fetchedRows)) {
|
||||
// if not, fetch it from the result set
|
||||
$row = $this->resultSet->fetch_assoc();
|
||||
if ($this->useCache) {
|
||||
$this->fetchedRows[] = $row;
|
||||
}
|
||||
|
||||
// close result set, after everything's fetched
|
||||
if ($index >= $this->numRows - 1) {
|
||||
$this->resultSet->close();
|
||||
}
|
||||
} else {
|
||||
$row = $this->fetchedRows[$index];
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
public function rewind() {
|
||||
if ($this->useCache) {
|
||||
$this->rowIndex = 0;
|
||||
} else if ($this->rowIndex !== 0) {
|
||||
throw new \Exception("RowIterator::rewind() not supported, when caching is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
497
Core/Driver/SQL/PostgreSQL.class.php
Normal file
497
Core/Driver/SQL/PostgreSQL.class.php
Normal file
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL;
|
||||
|
||||
use Core\API\Parameter\Parameter;
|
||||
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Column\IntColumn;
|
||||
use Core\Driver\SQL\Column\NumericColumn;
|
||||
use Core\Driver\SQL\Column\SerialColumn;
|
||||
use Core\Driver\SQL\Column\StringColumn;
|
||||
use Core\Driver\SQL\Column\EnumColumn;
|
||||
use Core\Driver\SQL\Column\DateTimeColumn;
|
||||
use Core\Driver\SQL\Column\BoolColumn;
|
||||
use Core\Driver\SQL\Column\JsonColumn;
|
||||
|
||||
use Core\Driver\SQL\Condition\CondRegex;
|
||||
use Core\Driver\SQL\Expression\Add;
|
||||
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Core\Driver\SQL\Expression\DateAdd;
|
||||
use Core\Driver\SQL\Expression\DateSub;
|
||||
use Core\Driver\SQL\Expression\Expression;
|
||||
use Core\Driver\SQL\Expression\JsonArrayAgg;
|
||||
use Core\Driver\SQL\Query\CreateProcedure;
|
||||
use Core\Driver\SQL\Query\CreateTrigger;
|
||||
use Core\Driver\SQL\Query\Insert;
|
||||
use Core\Driver\SQL\Query\Query;
|
||||
use Core\Driver\SQL\Strategy\Strategy;
|
||||
use Core\Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Core\Driver\SQL\Type\CurrentColumn;
|
||||
use Core\Driver\SQL\Type\CurrentTable;
|
||||
use Core\Driver\SQL\Type\Trigger;
|
||||
|
||||
class PostgreSQL extends SQL {
|
||||
|
||||
public function __construct($connectionData) {
|
||||
parent::__construct($connectionData);
|
||||
}
|
||||
|
||||
public function checkRequirements() {
|
||||
return function_exists('pg_connect');
|
||||
}
|
||||
|
||||
public function getDriverName() {
|
||||
return 'pgsql';
|
||||
}
|
||||
|
||||
// Connection Management
|
||||
public function connect() {
|
||||
if (!is_null($this->connection)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$config = array(
|
||||
"host" => $this->connectionData->getHost(),
|
||||
"port" => $this->connectionData->getPort(),
|
||||
"dbname" => $this->connectionData->getProperty('database', 'public'),
|
||||
"user" => $this->connectionData->getLogin(),
|
||||
"password" => $this->connectionData->getPassword()
|
||||
);
|
||||
|
||||
$connectionString = array();
|
||||
foreach ($config as $key => $val) {
|
||||
if (!empty($val)) {
|
||||
$connectionString[] = "$key=$val";
|
||||
}
|
||||
}
|
||||
|
||||
$this->connection = @pg_connect(implode(" ", $connectionString), PGSQL_CONNECT_FORCE_NEW);
|
||||
if (!$this->connection) {
|
||||
$this->lastError = $this->logger->severe("Failed to connect to Database");
|
||||
$this->connection = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
pg_set_client_encoding($this->connection, $this->connectionData->getProperty('encoding', 'UTF-8'));
|
||||
return true;
|
||||
}
|
||||
|
||||
public function disconnect() {
|
||||
if (is_null($this->connection))
|
||||
return;
|
||||
|
||||
@pg_close($this->connection);
|
||||
}
|
||||
|
||||
public function getLastError(): string {
|
||||
$lastError = parent::getLastError();
|
||||
if (empty($lastError)) {
|
||||
$lastError = trim(pg_last_error($this->connection) . " " . pg_last_error($this->connection));
|
||||
}
|
||||
|
||||
return $lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE) {
|
||||
|
||||
$this->lastError = "";
|
||||
$stmt_name = uniqid();
|
||||
$pgParams = array();
|
||||
|
||||
if (!is_null($values)) {
|
||||
foreach ($values as $value) {
|
||||
$paramType = Parameter::parseType($value);
|
||||
switch ($paramType) {
|
||||
case Parameter::TYPE_DATE:
|
||||
$value = $value->format("Y-m-d");
|
||||
break;
|
||||
case Parameter::TYPE_TIME:
|
||||
$value = $value->format("H:i:s");
|
||||
break;
|
||||
case Parameter::TYPE_DATE_TIME:
|
||||
$value = $value->format("Y-m-d H:i:s");
|
||||
break;
|
||||
case Parameter::TYPE_ARRAY:
|
||||
$value = json_encode($value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$pgParams[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = @pg_prepare($this->connection, $stmt_name, $query);
|
||||
if ($stmt === FALSE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = @pg_execute($this->connection, $stmt_name, $pgParams);
|
||||
if ($result === FALSE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($fetchType) {
|
||||
case self::FETCH_NONE:
|
||||
return true;
|
||||
case self::FETCH_ONE:
|
||||
return pg_fetch_assoc($result);
|
||||
case self::FETCH_ALL:
|
||||
$rows = pg_fetch_all($result);
|
||||
if ($rows === FALSE) {
|
||||
if (empty(trim($this->getLastError()))) {
|
||||
$rows = array();
|
||||
}
|
||||
}
|
||||
return $rows;
|
||||
case self::FETCH_ITERATIVE:
|
||||
return new RowIteratorPostgreSQL($result);
|
||||
}
|
||||
}
|
||||
|
||||
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 = $this->logger->error("ON DUPLICATE Strategy $strategyClass is not supported yet.");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public function getReturning(?string $columns): string {
|
||||
return $columns ? (" RETURNING " . $this->columnName($columns)) : "";
|
||||
}
|
||||
|
||||
public function executeQuery(Query $query, int $fetchType = self::FETCH_NONE) {
|
||||
|
||||
if ($query instanceof Insert && !empty($query->getReturning())) {
|
||||
$fetchType = self::FETCH_ONE;
|
||||
}
|
||||
|
||||
return parent::executeQuery($query, $fetchType);
|
||||
}
|
||||
|
||||
protected function fetchReturning($res, string $returningCol) {
|
||||
$this->lastInsertId = $res[0][$returningCol];
|
||||
}
|
||||
|
||||
// UGLY but.. what should i do?
|
||||
private function createEnum(EnumColumn $enumColumn, string $typeName): string {
|
||||
$values = array();
|
||||
foreach ($enumColumn->getValues() as $value) {
|
||||
$values[] = $this->getValueDefinition($value);
|
||||
}
|
||||
|
||||
$values = implode(",", $values);
|
||||
$query =
|
||||
"DO $$ BEGIN
|
||||
CREATE TYPE \"$typeName\" AS ENUM ($values);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;";
|
||||
|
||||
return $this->execute($query);
|
||||
}
|
||||
|
||||
public function getColumnType(Column $column): ?string {
|
||||
if ($column instanceof StringColumn) {
|
||||
$maxSize = $column->getMaxSize();
|
||||
if ($maxSize) {
|
||||
return "VARCHAR($maxSize)";
|
||||
} else {
|
||||
return "TEXT";
|
||||
}
|
||||
} else if ($column instanceof SerialColumn) {
|
||||
return "SERIAL";
|
||||
} else if ($column instanceof IntColumn) {
|
||||
return $column->getType();
|
||||
} else if ($column instanceof DateTimeColumn) {
|
||||
return "TIMESTAMP";
|
||||
} else if ($column instanceof EnumColumn) {
|
||||
$typeName = $column->getName();
|
||||
if (!endsWith($typeName, "_type")) {
|
||||
$typeName = "${typeName}_type";
|
||||
}
|
||||
return $typeName;
|
||||
} else if ($column instanceof BoolColumn) {
|
||||
return "BOOLEAN";
|
||||
} else if ($column instanceof JsonColumn) {
|
||||
return "JSON";
|
||||
} else if ($column instanceof NumericColumn) {
|
||||
$digitsDecimal = $column->getDecimalDigits();
|
||||
$type = $column->getTypeName();
|
||||
if ($digitsDecimal !== null) {
|
||||
if ($type === "double") {
|
||||
$type = "float"; // postgres doesn't know about double :/
|
||||
}
|
||||
return "$type($digitsDecimal)";
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("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 = "";
|
||||
if (!is_null($column->getDefaultValue()) || !$column->notNull()) {
|
||||
$defaultValue = " DEFAULT " . $this->getValueDefinition($column->getDefaultValue());
|
||||
}
|
||||
|
||||
return "$columnName $type$notNull$defaultValue";
|
||||
}
|
||||
|
||||
protected function getValueDefinition($value) {
|
||||
if (is_numeric($value)) {
|
||||
return $value;
|
||||
} else if (is_bool($value)) {
|
||||
return $value ? "TRUE" : "FALSE";
|
||||
} else if (is_null($value)) {
|
||||
return "NULL";
|
||||
} else if ($value instanceof Keyword) {
|
||||
return $value->getValue();
|
||||
} else if ($value instanceof CurrentTimeStamp) {
|
||||
return "CURRENT_TIMESTAMP";
|
||||
} else {
|
||||
$str = str_replace("'", "''", $value);
|
||||
return "'$str'";
|
||||
}
|
||||
}
|
||||
|
||||
public function addValue($val, &$params = NULL, bool $unsafe = false) {
|
||||
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 if ($val instanceof Expression) {
|
||||
return $this->createExpression($val, $params);
|
||||
} else {
|
||||
if ($unsafe) {
|
||||
return $this->getUnsafeValue($val);
|
||||
} else {
|
||||
$params[] = is_bool($val) ? ($val ? "TRUE" : "FALSE") : $val;
|
||||
return '$' . count($params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function tableName($table): string {
|
||||
if (is_array($table)) {
|
||||
$tables = array();
|
||||
foreach ($table as $t) $tables[] = $this->tableName($t);
|
||||
return implode(",", $tables);
|
||||
} else {
|
||||
$parts = explode(" ", $table);
|
||||
if (count($parts) === 2) {
|
||||
list ($name, $alias) = $parts;
|
||||
return "\"$name\" $alias";
|
||||
} else {
|
||||
return "\"$table\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function columnName($col): string {
|
||||
if ($col instanceof KeyWord) {
|
||||
return $col->getValue();
|
||||
} elseif (is_array($col)) {
|
||||
$columns = array_map(function ($c) {
|
||||
return $this->columnName($c);
|
||||
}, $col);
|
||||
return implode(",", $columns);
|
||||
} else {
|
||||
if (($index = strrpos($col, ".")) !== FALSE) {
|
||||
$tableName = $this->tableName(substr($col, 0, $index));
|
||||
$columnName = $this->columnName(substr($col, $index + 1));
|
||||
return "$tableName.$columnName";
|
||||
} else if (($index = stripos($col, " as ")) !== FALSE) {
|
||||
$columnName = $this->columnName(trim(substr($col, 0, $index)));
|
||||
$alias = $this->columnName(trim(substr($col, $index + 4)));
|
||||
return "$columnName as $alias";
|
||||
} else {
|
||||
return "\"$col\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getStatus() {
|
||||
$version = pg_version($this->connection)["client"] ?? "??";
|
||||
$status = pg_connection_status($this->connection);
|
||||
static $statusTexts = array(
|
||||
PGSQL_CONNECTION_OK => "PGSQL_CONNECTION_OK",
|
||||
PGSQL_CONNECTION_BAD => "PGSQL_CONNECTION_BAD",
|
||||
);
|
||||
|
||||
return ($statusTexts[$status] ?? "Unknown") . " (v$version)";
|
||||
}
|
||||
|
||||
public function buildCondition($condition, &$params) {
|
||||
if ($condition instanceof CondRegex) {
|
||||
$left = $condition->getLeftExp();
|
||||
$right = $condition->getRightExp();
|
||||
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
|
||||
$right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params);
|
||||
return $left . " ~ " . $right;
|
||||
} else {
|
||||
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;";
|
||||
|
||||
return $this->execute($query, $params);
|
||||
}
|
||||
|
||||
public function createTriggerBody(CreateTrigger $trigger, array $params = []): ?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;
|
||||
}
|
||||
|
||||
protected function createExpression(Expression $exp, array &$params): ?string {
|
||||
if ($exp instanceof DateAdd || $exp instanceof DateSub) {
|
||||
$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";
|
||||
}
|
||||
|
||||
$operator = ($exp instanceof DateAdd ? "+" : "-");
|
||||
return "$lhs $operator $rhs";
|
||||
} else if ($exp instanceof CurrentTimeStamp) {
|
||||
return "CURRENT_TIMESTAMP";
|
||||
} else if ($exp instanceof JsonArrayAgg) {
|
||||
$value = $this->addValue($exp->getValue(), $params);
|
||||
$alias = $this->columnName($exp->getAlias());
|
||||
return "JSON_AGG($value) as $alias";
|
||||
} else {
|
||||
return parent::createExpression($exp, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RowIteratorPostgreSQL extends RowIterator {
|
||||
|
||||
public function __construct($resultSet, bool $useCache = false) {
|
||||
parent::__construct($resultSet, false); // caching not needed
|
||||
}
|
||||
|
||||
protected function getNumRows(): int {
|
||||
return pg_num_rows($this->resultSet);
|
||||
}
|
||||
|
||||
public function rewind() {
|
||||
$this->rowIndex = 0;
|
||||
}
|
||||
|
||||
protected function fetchRow(int $index): array {
|
||||
return pg_fetch_assoc($this->resultSet, $index);
|
||||
}
|
||||
}
|
||||
133
Core/Driver/SQL/Query/AlterTable.class.php
Normal file
133
Core/Driver/SQL/Query/AlterTable.class.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Column\EnumColumn;
|
||||
use Core\Driver\SQL\Constraint\Constraint;
|
||||
use Core\Driver\SQL\Constraint\ForeignKey;
|
||||
use Core\Driver\SQL\Constraint\PrimaryKey;
|
||||
use Core\Driver\SQL\PostgreSQL;
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class AlterTable extends Query {
|
||||
|
||||
private string $table;
|
||||
private string $action;
|
||||
private $data;
|
||||
|
||||
private ?Column $column;
|
||||
private ?Constraint $constraint;
|
||||
|
||||
public function __construct(SQL $sql, string $table) {
|
||||
parent::__construct($sql);
|
||||
$this->table = $table;
|
||||
$this->column = null;
|
||||
$this->constraint = null;
|
||||
}
|
||||
|
||||
public function add($what): AlterTable {
|
||||
if ($what instanceof Column) {
|
||||
$this->column = $what;
|
||||
} else if ($what instanceof Constraint) {
|
||||
$this->constraint = $what;
|
||||
} else {
|
||||
$this->column = new Column($what);
|
||||
}
|
||||
|
||||
$this->action = "ADD";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function modify(Column $column): AlterTable {
|
||||
$this->column = $column;
|
||||
$this->action = "MODIFY";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function drop($what): AlterTable {
|
||||
if ($what instanceof Column) {
|
||||
$this->column = $what;
|
||||
} else if ($what instanceof Constraint) {
|
||||
$this->constraint = $what;
|
||||
} else {
|
||||
$this->column = new Column($what);
|
||||
}
|
||||
$this->action = "DROP";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function resetAutoIncrement(): AlterTable {
|
||||
$this->action = "RESET_AUTO_INCREMENT";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addToEnum(EnumColumn $column, string $newValue): AlterTable {
|
||||
$this->action = "MODIFY";
|
||||
$this->column = $column;
|
||||
$this->data = $newValue;
|
||||
return $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): ?string {
|
||||
$tableName = $this->sql->tableName($this->getTable());
|
||||
$action = $this->getAction();
|
||||
$column = $this->getColumn();
|
||||
$constraint = $this->getConstraint();
|
||||
|
||||
if ($action === "RESET_AUTO_INCREMENT") {
|
||||
return "ALTER TABLE $tableName AUTO_INCREMENT=1";
|
||||
}
|
||||
|
||||
$query = "ALTER TABLE $tableName $action ";
|
||||
|
||||
if ($column) {
|
||||
$query .= "COLUMN ";
|
||||
if ($action === "DROP") {
|
||||
$query .= $this->sql->columnName($column->getName());
|
||||
} else {
|
||||
// ADD or modify
|
||||
if ($column instanceof EnumColumn) {
|
||||
if ($this->sql instanceof PostgreSQL) {
|
||||
$typeName = $this->sql->getColumnType($column);
|
||||
$value = $this->sql->addValue($this->data, $params);
|
||||
return "ALTER TYPE $typeName ADD VALUE $value";
|
||||
}
|
||||
$column->addValue($this->data);
|
||||
}
|
||||
|
||||
$query .= $this->sql->getColumnDefinition($column);
|
||||
}
|
||||
} else if ($constraint) {
|
||||
if ($action === "DROP") {
|
||||
if ($constraint instanceof PrimaryKey) {
|
||||
$query .= "PRIMARY KEY";
|
||||
} else {
|
||||
$constraintName = $constraint->getName();
|
||||
if ($constraintName) {
|
||||
$query .= "CONSTRAINT " . $this->sql->columnName($constraintName);
|
||||
} else {
|
||||
$this->sql->setLastError("Cannot DROP CONSTRAINT without a constraint name.");
|
||||
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;
|
||||
}
|
||||
}
|
||||
15
Core/Driver/SQL/Query/Commit.class.php
Normal file
15
Core/Driver/SQL/Query/Commit.class.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class Commit extends Query {
|
||||
public function __construct(SQL $sql) {
|
||||
parent::__construct($sql);
|
||||
}
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
return "COMMIT";
|
||||
}
|
||||
}
|
||||
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 Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\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): ?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; }
|
||||
}
|
||||
156
Core/Driver/SQL/Query/CreateTable.class.php
Normal file
156
Core/Driver/SQL/Query/CreateTable.class.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\Column\BigIntColumn;
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Column\DoubleColumn;
|
||||
use Core\Driver\SQL\Column\FloatColumn;
|
||||
use Core\Driver\SQL\Column\NumericColumn;
|
||||
use Core\Driver\SQL\Column\SerialColumn;
|
||||
use Core\Driver\SQL\Column\StringColumn;
|
||||
use Core\Driver\SQL\Column\IntColumn;
|
||||
use Core\Driver\SQL\Column\DateTimeColumn;
|
||||
use Core\Driver\SQL\Column\EnumColumn;
|
||||
use Core\Driver\SQL\Column\BoolColumn;
|
||||
use Core\Driver\SQL\Column\JsonColumn;
|
||||
|
||||
use Core\Driver\SQL\Constraint\Constraint;
|
||||
use Core\Driver\SQL\Constraint\PrimaryKey;
|
||||
use Core\Driver\SQL\Constraint\Unique;
|
||||
use Core\Driver\SQL\Constraint\ForeignKey;
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Driver\SQL\Strategy\Strategy;
|
||||
|
||||
class CreateTable extends Query {
|
||||
|
||||
private string $tableName;
|
||||
private array $columns;
|
||||
private array $constraints;
|
||||
private bool $ifNotExists;
|
||||
|
||||
public function __construct(SQL $sql, string $name) {
|
||||
parent::__construct($sql);
|
||||
$this->tableName = $name;
|
||||
$this->columns = array();
|
||||
$this->constraints = array();
|
||||
$this->ifNotExists = false;
|
||||
}
|
||||
|
||||
public function addColumn(Column $column): CreateTable {
|
||||
$this->columns[$column->getName()] = $column;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addConstraint(Constraint $constraint): CreateTable {
|
||||
$this->constraints[] = $constraint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addSerial(string $name): CreateTable {
|
||||
$this->columns[$name] = new SerialColumn($name);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addString(string $name, ?int $maxSize = NULL, bool $nullable = false, $defaultValue = NULL): CreateTable {
|
||||
$this->columns[$name] = new StringColumn($name, $maxSize, $nullable, $defaultValue);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDateTime(string $name, bool $nullable = false, $defaultValue = NULL): CreateTable {
|
||||
$this->columns[$name] = new DateTimeColumn($name, $nullable, $defaultValue);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addInt(string $name, bool $nullable = false, $defaultValue = NULL, bool $unsigned = false): CreateTable {
|
||||
$this->columns[$name] = new IntColumn($name, $nullable, $defaultValue, $unsigned);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addBigInt(string $name, bool $nullable = false, $defaultValue = NULL, bool $unsigned = false): CreateTable {
|
||||
$this->columns[$name] = new BigIntColumn($name, $nullable, $defaultValue, $unsigned);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addBool(string $name, $defaultValue = false): CreateTable {
|
||||
$this->columns[$name] = new BoolColumn($name, $defaultValue);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addJson(string $name, bool $nullable = false, $defaultValue = NULL): CreateTable {
|
||||
$this->columns[$name] = new JsonColumn($name, $nullable, $defaultValue);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addEnum(string $name, array $values, bool $nullable = false, $defaultValue = NULL): CreateTable {
|
||||
$this->columns[$name] = new EnumColumn($name, $values, $nullable, $defaultValue);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addNumeric(string $name, bool $nullable = false, $defaultValue = NULL, ?int $digitsTotal = 10, ?int $digitsDecimal = 0): CreateTable {
|
||||
$this->columns[$name] = new NumericColumn($name, $nullable, $defaultValue, $digitsTotal, $digitsDecimal);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addFloat(string $name, bool $nullable = false, $defaultValue = NULL, ?int $digitsTotal = null, ?int $digitsDecimal = null): CreateTable {
|
||||
$this->columns[$name] = new FloatColumn($name, $nullable, $defaultValue, $digitsTotal, $digitsDecimal);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDouble(string $name, bool $nullable = false, $defaultValue = NULL, ?int $digitsTotal = null, ?int $digitsDecimal = null): CreateTable {
|
||||
$this->columns[$name] = new DoubleColumn($name, $nullable, $defaultValue, $digitsTotal, $digitsDecimal);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function primaryKey(...$names): CreateTable {
|
||||
$pk = new PrimaryKey($names);
|
||||
$pk->setName(strtolower("pk_{$this->tableName}"));
|
||||
$this->constraints[] = $pk;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function unique(...$names): CreateTable {
|
||||
$this->constraints[] = new Unique($names);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function foreignKey(string $column, string $refTable, string $refColumn, ?Strategy $strategy = NULL): CreateTable {
|
||||
$fk = new ForeignKey($column, $refTable, $refColumn, $strategy);
|
||||
$fk->setName(strtolower("fk_{$this->tableName}_${refTable}_${refColumn}"));
|
||||
$this->constraints[] = $fk;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function onlyIfNotExists(): CreateTable {
|
||||
$this->ifNotExists = true;
|
||||
return $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)";
|
||||
}
|
||||
}
|
||||
83
Core/Driver/SQL/Query/CreateTrigger.class.php
Normal file
83
Core/Driver/SQL/Query/CreateTrigger.class.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\API\User\Create;
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class CreateTrigger extends Query {
|
||||
|
||||
private string $name;
|
||||
private string $time;
|
||||
private string $event;
|
||||
private string $tableName;
|
||||
private array $parameters;
|
||||
private ?CreateProcedure $procedure;
|
||||
|
||||
public function __construct(SQL $sql, string $triggerName) {
|
||||
parent::__construct($sql);
|
||||
$this->name = $triggerName;
|
||||
$this->time = "AFTER";
|
||||
$this->tableName = "";
|
||||
$this->event = "";
|
||||
$this->parameters = [];
|
||||
$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, array $parameters = []): CreateTrigger {
|
||||
$this->procedure = $procedure;
|
||||
$this->parameters = $parameters;
|
||||
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): ?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, $this->parameters);
|
||||
if ($triggerBody === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query .= $triggerBody;
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
32
Core/Driver/SQL/Query/Delete.class.php
Normal file
32
Core/Driver/SQL/Query/Delete.class.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\Condition\CondOr;
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class Delete extends Query {
|
||||
|
||||
private string $table;
|
||||
private array $conditions;
|
||||
|
||||
public function __construct(SQL $sql, string $table) {
|
||||
parent::__construct($sql);
|
||||
$this->table = $table;
|
||||
$this->conditions = array();
|
||||
}
|
||||
|
||||
public function where(...$conditions): Delete {
|
||||
$this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTable(): string { return $this->table; }
|
||||
public function getConditions(): array { return $this->conditions; }
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
$table = $this->sql->tableName($this->getTable());
|
||||
$where = $this->sql->getWhereClause($this->getConditions(), $params);
|
||||
return "DELETE FROM $table$where";
|
||||
}
|
||||
}
|
||||
29
Core/Driver/SQL/Query/Drop.php
Normal file
29
Core/Driver/SQL/Query/Drop.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class Drop extends Query {
|
||||
|
||||
private string $table;
|
||||
|
||||
/**
|
||||
* Drop constructor.
|
||||
* @param SQL $sql
|
||||
* @param string $table
|
||||
*/
|
||||
public function __construct(SQL $sql, string $table) {
|
||||
parent::__construct($sql);
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
public function getTable(): string {
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
return "DROP TABLE " . $this->sql->tableName($this->getTable());
|
||||
}
|
||||
}
|
||||
83
Core/Driver/SQL/Query/Insert.class.php
Normal file
83
Core/Driver/SQL/Query/Insert.class.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Driver\SQL\Strategy\Strategy;
|
||||
|
||||
class Insert extends Query {
|
||||
|
||||
private string $tableName;
|
||||
private array $columns;
|
||||
private array $rows;
|
||||
private ?Strategy $onDuplicateKey;
|
||||
private ?string $returning;
|
||||
|
||||
public function __construct(SQL $sql, string $name, array $columns = array()) {
|
||||
parent::__construct($sql);
|
||||
$this->tableName = $name;
|
||||
$this->columns = $columns;
|
||||
$this->rows = array();
|
||||
$this->onDuplicateKey = NULL;
|
||||
$this->returning = NULL;
|
||||
}
|
||||
|
||||
public function addRow(...$values): Insert {
|
||||
$this->rows[] = $values;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function onDuplicateKeyStrategy(Strategy $strategy): Insert {
|
||||
$this->onDuplicateKey = $strategy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function returning(string $column): Insert {
|
||||
$this->returning = $column;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTableName(): string { return $this->tableName; }
|
||||
public function getColumns(): array { return $this->columns; }
|
||||
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): ?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";
|
||||
}
|
||||
}
|
||||
29
Core/Driver/SQL/Query/Query.class.php
Normal file
29
Core/Driver/SQL/Query/Query.class.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\Expression\Expression;
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
abstract class Query extends Expression {
|
||||
|
||||
protected SQL $sql;
|
||||
public bool $dump;
|
||||
|
||||
public function __construct(SQL $sql) {
|
||||
$this->sql = $sql;
|
||||
$this->dump = false;
|
||||
}
|
||||
|
||||
public function dump(): Query {
|
||||
$this->dump = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// can actually return bool|array (depending on success and query type)
|
||||
public function execute() {
|
||||
return $this->sql->executeQuery($this);
|
||||
}
|
||||
|
||||
public abstract function build(array &$params): ?string;
|
||||
}
|
||||
15
Core/Driver/SQL/Query/RollBack.class.php
Normal file
15
Core/Driver/SQL/Query/RollBack.class.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class RollBack extends Query {
|
||||
public function __construct(SQL $sql) {
|
||||
parent::__construct($sql);
|
||||
}
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
return "ROLLBACK";
|
||||
}
|
||||
}
|
||||
214
Core/Driver/SQL/Query/Select.class.php
Normal file
214
Core/Driver/SQL/Query/Select.class.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\Condition\CondOr;
|
||||
use Core\Driver\SQL\Expression\JsonArrayAgg;
|
||||
use Core\Driver\SQL\Join;
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class Select extends Query {
|
||||
|
||||
private array $selectValues;
|
||||
private array $tables;
|
||||
private array $conditions;
|
||||
private array $joins;
|
||||
private array $orderColumns;
|
||||
private array $groupColumns;
|
||||
private array $havings;
|
||||
private bool $sortAscending;
|
||||
private int $limit;
|
||||
private int $offset;
|
||||
private bool $forUpdate;
|
||||
private int $fetchType;
|
||||
|
||||
public function __construct($sql, ...$selectValues) {
|
||||
parent::__construct($sql);
|
||||
$this->selectValues = (!empty($selectValues) && is_array($selectValues[0])) ? $selectValues[0] : $selectValues;
|
||||
$this->tables = array();
|
||||
$this->conditions = array();
|
||||
$this->havings = array();
|
||||
$this->joins = array();
|
||||
$this->orderColumns = array();
|
||||
$this->groupColumns = array();
|
||||
$this->limit = 0;
|
||||
$this->offset = 0;
|
||||
$this->sortAscending = true;
|
||||
$this->forUpdate = false;
|
||||
$this->fetchType = SQL::FETCH_ALL;
|
||||
}
|
||||
|
||||
public function from(...$tables): Select {
|
||||
$this->tables = array_merge($this->tables, $tables);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addValue($value): Select {
|
||||
$this->selectValues[] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where(...$conditions): Select {
|
||||
$this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function having(...$conditions): Select {
|
||||
$this->havings[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function innerJoin(string $table, string $columnA, string $columnB, ?string $tableAlias = null, array $conditions = []): Select {
|
||||
$this->joins[] = new Join("INNER", $table, $columnA, $columnB, $tableAlias, $conditions);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function leftJoin(string $table, string $columnA, string $columnB, ?string $tableAlias = null, array $conditions = []): Select {
|
||||
$this->joins[] = new Join("LEFT", $table, $columnA, $columnB, $tableAlias, $conditions);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addJoin(Join $join): Select {
|
||||
$this->joins[] = $join;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function groupBy(...$columns): Select {
|
||||
$this->groupColumns = $columns;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy(...$columns): Select {
|
||||
$this->orderColumns = $columns;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function ascending(): Select {
|
||||
$this->sortAscending = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function descending(): Select {
|
||||
$this->sortAscending = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit(int $limit): Select {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function offset(int $offset): Select {
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function lockForUpdate(): Select {
|
||||
$this->forUpdate = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function iterator(): Select {
|
||||
$this->fetchType = SQL::FETCH_ITERATIVE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function first(): Select {
|
||||
$this->fetchType = SQL::FETCH_ONE;
|
||||
$this->limit = 1;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute() {
|
||||
return $this->sql->executeQuery($this, $this->fetchType);
|
||||
}
|
||||
|
||||
public function getSelectValues(): array { return $this->selectValues; }
|
||||
public function getTables(): array { return $this->tables; }
|
||||
public function getConditions(): array { return $this->conditions; }
|
||||
public function getJoins(): array { return $this->joins; }
|
||||
public function isOrderedAscending(): bool { return $this->sortAscending; }
|
||||
public function getOrderBy(): array { return $this->orderColumns; }
|
||||
public function getLimit(): int { return $this->limit; }
|
||||
public function getOffset(): int { return $this->offset; }
|
||||
public function getGroupBy(): array { return $this->groupColumns; }
|
||||
public function getHavings(): array { return $this->havings; }
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
|
||||
$selectValues = [];
|
||||
foreach ($this->selectValues as $value) {
|
||||
if (is_string($value)) {
|
||||
$selectValues[] = $this->sql->columnName($value);
|
||||
} else if ($value instanceof Select) {
|
||||
$subSelect = $value->build($params);
|
||||
if (count($value->getSelectValues()) !== 1) {
|
||||
$selectValues[] = "($subSelect)";
|
||||
} else {
|
||||
$columnAlias = null;
|
||||
$subSelectColumn = $value->getSelectValues()[0];
|
||||
if (is_string($subSelectColumn) && ($index = stripos($subSelectColumn, " as ")) !== FALSE) {
|
||||
$columnAlias = substr($subSelectColumn, $index + 4);
|
||||
} else if ($subSelectColumn instanceof JsonArrayAgg) {
|
||||
$columnAlias = $subSelectColumn->getAlias();
|
||||
}
|
||||
|
||||
if ($columnAlias) {
|
||||
$selectValues[] = "($subSelect) as $columnAlias";
|
||||
} else {
|
||||
$selectValues[] = "($subSelect)";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$selectValues[] = $this->sql->addValue($value, $params);
|
||||
}
|
||||
}
|
||||
|
||||
$tables = $this->getTables();
|
||||
$selectValues = implode(",", $selectValues);
|
||||
|
||||
if (!$tables) {
|
||||
return "SELECT $selectValues";
|
||||
}
|
||||
|
||||
$tables = $this->sql->tableName($tables);
|
||||
$where = $this->sql->getWhereClause($this->getConditions(), $params);
|
||||
$havingClause = "";
|
||||
if (count($this->havings) > 0) {
|
||||
$havingClause = " HAVING " . $this->sql->buildCondition($this->getHavings(), $params);
|
||||
}
|
||||
|
||||
$joinStr = "";
|
||||
$joins = $this->getJoins();
|
||||
if (!empty($joins)) {
|
||||
foreach ($joins as $join) {
|
||||
$type = $join->getType();
|
||||
$joinTable = $this->sql->tableName($join->getTable());
|
||||
$tableAlias = ($join->getTableAlias() ? " " . $join->getTableAlias() : "");
|
||||
$condition = $this->sql->buildCondition($join->getConditions(), $params);
|
||||
$joinStr .= " $type JOIN $joinTable$tableAlias ON ($condition)";
|
||||
}
|
||||
}
|
||||
|
||||
$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()) : "");
|
||||
$forUpdate = ($this->forUpdate ? " FOR UPDATE" : "");
|
||||
return "SELECT $selectValues FROM $tables$joinStr$where$groupBy$havingClause$orderBy$limit$offset$forUpdate";
|
||||
}
|
||||
}
|
||||
15
Core/Driver/SQL/Query/StartTransaction.class.php
Normal file
15
Core/Driver/SQL/Query/StartTransaction.class.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class StartTransaction extends Query {
|
||||
public function __construct(SQL $sql) {
|
||||
parent::__construct($sql);
|
||||
}
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
return "START TRANSACTION";
|
||||
}
|
||||
}
|
||||
21
Core/Driver/SQL/Query/Truncate.class.php
Normal file
21
Core/Driver/SQL/Query/Truncate.class.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class Truncate extends Query {
|
||||
|
||||
private string $tableName;
|
||||
|
||||
public function __construct(SQL $sql, string $name) {
|
||||
parent::__construct($sql);
|
||||
$this->tableName = $name;
|
||||
}
|
||||
|
||||
public function getTable(): string { return $this->tableName; }
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
return "TRUNCATE " . $this->sql->tableName($this->getTable());
|
||||
}
|
||||
}
|
||||
47
Core/Driver/SQL/Query/Update.class.php
Normal file
47
Core/Driver/SQL/Query/Update.class.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Query;
|
||||
|
||||
use Core\Driver\SQL\Condition\CondOr;
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class Update extends Query {
|
||||
|
||||
private array $values;
|
||||
private string $table;
|
||||
private array $conditions;
|
||||
|
||||
public function __construct(SQL $sql, string $table) {
|
||||
parent::__construct($sql);
|
||||
$this->values = array();
|
||||
$this->table = $table;
|
||||
$this->conditions = array();
|
||||
}
|
||||
|
||||
public function where(...$conditions): Update {
|
||||
$this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set(string $key, $val): Update {
|
||||
$this->values[$key] = $val;
|
||||
return $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): ?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";
|
||||
}
|
||||
}
|
||||
39
Core/Driver/SQL/RowIterator.class.php
Normal file
39
Core/Driver/SQL/RowIterator.class.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL;
|
||||
|
||||
abstract class RowIterator implements \Iterator {
|
||||
|
||||
protected $resultSet;
|
||||
protected int $rowIndex;
|
||||
protected array $fetchedRows;
|
||||
protected int $numRows;
|
||||
protected bool $useCache;
|
||||
|
||||
public function __construct($resultSet, bool $useCache = false) {
|
||||
$this->resultSet = $resultSet;
|
||||
$this->fetchedRows = [];
|
||||
$this->rowIndex = 0;
|
||||
$this->numRows = $this->getNumRows();
|
||||
$this->useCache = $useCache;
|
||||
}
|
||||
|
||||
protected abstract function getNumRows(): int;
|
||||
protected abstract function fetchRow(int $index): array;
|
||||
|
||||
public function current() {
|
||||
return $this->fetchRow($this->rowIndex);
|
||||
}
|
||||
|
||||
public function next() {
|
||||
$this->rowIndex++;
|
||||
}
|
||||
|
||||
public function key() {
|
||||
return $this->rowIndex;
|
||||
}
|
||||
|
||||
public function valid(): bool {
|
||||
return $this->rowIndex < $this->numRows;
|
||||
}
|
||||
}
|
||||
434
Core/Driver/SQL/SQL.class.php
Normal file
434
Core/Driver/SQL/SQL.class.php
Normal file
@@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL;
|
||||
|
||||
use Core\Driver\Logger\Logger;
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Condition\Compare;
|
||||
use Core\Driver\SQL\Condition\CondAnd;
|
||||
use Core\Driver\SQL\Condition\CondBool;
|
||||
use Core\Driver\SQL\Condition\CondIn;
|
||||
use Core\Driver\SQL\Condition\Condition;
|
||||
use Core\Driver\SQL\Condition\CondKeyword;
|
||||
use Core\Driver\SQL\Condition\CondNot;
|
||||
use Core\Driver\Sql\Condition\CondNull;
|
||||
use Core\Driver\SQL\Condition\CondOr;
|
||||
use Core\Driver\SQL\Condition\Exists;
|
||||
use Core\Driver\SQL\Constraint\Constraint;
|
||||
use Core\Driver\SQL\Constraint\Unique;
|
||||
use Core\Driver\SQL\Constraint\PrimaryKey;
|
||||
use Core\Driver\SQL\Constraint\ForeignKey;
|
||||
use Core\Driver\SQL\Expression\CaseWhen;
|
||||
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Core\Driver\SQL\Expression\Expression;
|
||||
use Core\Driver\SQL\Expression\Sum;
|
||||
use Core\Driver\SQL\Query\AlterTable;
|
||||
use Core\Driver\SQL\Query\CreateProcedure;
|
||||
use Core\Driver\SQL\Query\CreateTable;
|
||||
use Core\Driver\SQL\Query\CreateTrigger;
|
||||
use Core\Driver\SQL\Query\Delete;
|
||||
use Core\Driver\SQL\Query\Drop;
|
||||
use Core\Driver\SQL\Query\Insert;
|
||||
use Core\Driver\SQL\Query\Query;
|
||||
use Core\Driver\SQL\Query\Select;
|
||||
use Core\Driver\SQL\Query\Truncate;
|
||||
use Core\Driver\SQL\Query\Update;
|
||||
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
||||
use Core\Driver\SQL\Strategy\SetDefaultStrategy;
|
||||
use Core\Driver\SQL\Strategy\SetNullStrategy;
|
||||
use Core\Driver\SQL\Strategy\Strategy;
|
||||
use Core\Objects\ConnectionData;
|
||||
|
||||
abstract class SQL {
|
||||
|
||||
const FETCH_NONE = 0;
|
||||
const FETCH_ONE = 1;
|
||||
const FETCH_ALL = 2;
|
||||
const FETCH_ITERATIVE = 3;
|
||||
|
||||
protected Logger $logger;
|
||||
protected string $lastError;
|
||||
protected $connection;
|
||||
protected ConnectionData $connectionData;
|
||||
protected int $lastInsertId;
|
||||
|
||||
protected bool $logQueries;
|
||||
|
||||
public function __construct($connectionData) {
|
||||
$this->connection = NULL;
|
||||
$this->lastError = 'Unknown Error';
|
||||
$this->connectionData = $connectionData;
|
||||
$this->lastInsertId = 0;
|
||||
$this->logger = new Logger(getClassName($this), $this);
|
||||
$this->logQueries = false;
|
||||
}
|
||||
|
||||
public function isConnected(): bool {
|
||||
return !is_null($this->connection) && !is_bool($this->connection);
|
||||
}
|
||||
|
||||
public function getLastError(): string {
|
||||
return trim($this->lastError);
|
||||
}
|
||||
|
||||
public function createTable($tableName): CreateTable {
|
||||
return new CreateTable($this, $tableName);
|
||||
}
|
||||
|
||||
public function insert($tableName, $columns=array()): Insert {
|
||||
return new Insert($this, $tableName, $columns);
|
||||
}
|
||||
|
||||
public function select(...$columNames): Select {
|
||||
return new Select($this, $columNames);
|
||||
}
|
||||
|
||||
public function truncate($table): Truncate {
|
||||
return new Truncate($this, $table);
|
||||
}
|
||||
|
||||
public function delete($table): Delete {
|
||||
return new Delete($this, $table);
|
||||
}
|
||||
|
||||
public function update($table): Update {
|
||||
return new Update($this, $table);
|
||||
}
|
||||
|
||||
public function drop(string $table): Drop {
|
||||
return new Drop($this, $table);
|
||||
}
|
||||
|
||||
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
|
||||
// ####################
|
||||
|
||||
// Misc
|
||||
public abstract function checkRequirements();
|
||||
public abstract function getDriverName();
|
||||
|
||||
// Connection Management
|
||||
public abstract function connect();
|
||||
public abstract function disconnect();
|
||||
|
||||
/**
|
||||
* @param Query $query
|
||||
* @param int $fetchType
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeQuery(Query $query, int $fetchType = self::FETCH_NONE) {
|
||||
|
||||
$parameters = [];
|
||||
$queryStr = $query->build($parameters);
|
||||
|
||||
if ($query->dump) {
|
||||
var_dump($queryStr);
|
||||
var_dump($parameters);
|
||||
}
|
||||
|
||||
if ($queryStr === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$res = $this->execute($queryStr, $parameters, $fetchType);
|
||||
$success = ($res !== FALSE);
|
||||
|
||||
// fetch generated serial ids for Insert statements
|
||||
$generatedColumn = ($query instanceof Insert ? $query->getReturning() : null);
|
||||
if ($success && $generatedColumn) {
|
||||
$this->fetchReturning($res, $generatedColumn);
|
||||
}
|
||||
|
||||
if ($this->logQueries && (!($query instanceof Insert) || $query->getTableName() !== "SystemLog")) {
|
||||
|
||||
if ($success === false || $fetchType == self::FETCH_NONE) {
|
||||
$result = var_export($success, true);
|
||||
} else if ($fetchType === self::FETCH_ALL) {
|
||||
$result = count($res) . " rows";
|
||||
} else if ($fetchType === self::FETCH_ONE) {
|
||||
$result = ($res === null ? "(empty)" : "1 row");
|
||||
} else if ($fetchType === self::FETCH_ITERATIVE) {
|
||||
$result = $res->getNumRows() . " rows (iterative)";
|
||||
} else {
|
||||
$result = "Unknown";
|
||||
}
|
||||
|
||||
$message = sprintf("Query: %s, Parameters: %s, Result: %s",
|
||||
var_export($queryStr, true), var_export($parameters, true), $result
|
||||
);
|
||||
|
||||
if ($success === false) {
|
||||
$message .= "Error: " . var_export($this->lastError, true);
|
||||
}
|
||||
|
||||
$this->logger->debug($message);
|
||||
}
|
||||
|
||||
return $fetchType === self::FETCH_NONE ? $success : $res;
|
||||
}
|
||||
|
||||
public function getWhereClause($conditions, &$params): string {
|
||||
if (!$conditions) {
|
||||
return "";
|
||||
} else {
|
||||
return " WHERE " . $this->buildCondition($conditions, $params);
|
||||
}
|
||||
}
|
||||
|
||||
public function getConstraintDefinition(Constraint $constraint): ?string {
|
||||
$columnName = $this->columnName($constraint->getColumnNames());
|
||||
|
||||
if ($constraint instanceof PrimaryKey) {
|
||||
return "PRIMARY KEY ($columnName)";
|
||||
} else if ($constraint instanceof Unique) {
|
||||
return "UNIQUE ($columnName)";
|
||||
} else if ($constraint instanceof ForeignKey) {
|
||||
$refTable = $this->tableName($constraint->getReferencedTable());
|
||||
$refColumn = $this->columnName($constraint->getReferencedColumn());
|
||||
$strategy = $constraint->onDelete();
|
||||
$code = "FOREIGN KEY ($columnName) REFERENCES $refTable ($refColumn)";
|
||||
if ($strategy instanceof SetDefaultStrategy) {
|
||||
$code .= " ON DELETE SET DEFAULT";
|
||||
} else if($strategy instanceof SetNullStrategy) {
|
||||
$code .= " ON DELETE SET NULL";
|
||||
} else if($strategy instanceof CascadeStrategy) {
|
||||
$code .= " ON DELETE CASCADE";
|
||||
}
|
||||
|
||||
return $code;
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("Unsupported constraint type: " . get_class($constraint));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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, array $params = []): ?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 function getUnsafeValue($value): ?string {
|
||||
if (is_string($value)) {
|
||||
return "'" . addslashes("$value") . "'"; // unsafe operation here...
|
||||
} else if (is_numeric($value) || is_bool($value)) {
|
||||
return $value;
|
||||
} else if ($value instanceof Column) {
|
||||
return $this->columnName($value);
|
||||
} else if ($value === null) {
|
||||
return "NULL";
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("Cannot create unsafe value of type: " . gettype($value));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract function getValueDefinition($val);
|
||||
public abstract function addValue($val, &$params = NULL, bool $unsafe = false);
|
||||
protected abstract function buildUnsafe(Query $statement): string;
|
||||
|
||||
public abstract function tableName($table): string;
|
||||
public abstract function columnName($col): string;
|
||||
|
||||
// Special Keywords and functions
|
||||
public function now(): CurrentTimeStamp { return new CurrentTimeStamp(); }
|
||||
public function currentTimestamp(): CurrentTimeStamp { return new CurrentTimeStamp(); }
|
||||
|
||||
public function count($col = NULL): Keyword {
|
||||
if (is_null($col)) {
|
||||
return new Keyword("COUNT(*) AS count");
|
||||
} else if($col instanceof Keyword) {
|
||||
return new Keyword("COUNT(" . $col->getValue() . ") AS count");
|
||||
} else {
|
||||
$countCol = strtolower(str_replace(".","_", $col)) . "_count";
|
||||
$col = $this->columnName($col);
|
||||
return new Keyword("COUNT($col) AS $countCol");
|
||||
}
|
||||
}
|
||||
|
||||
public function distinct($col): Keyword {
|
||||
$col = $this->columnName($col);
|
||||
return new Keyword("DISTINCT($col)");
|
||||
}
|
||||
|
||||
// Statements
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected abstract function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE);
|
||||
|
||||
public function buildCondition($condition, &$params) {
|
||||
|
||||
if ($condition instanceof CondOr) {
|
||||
$conditions = array();
|
||||
foreach($condition->getConditions() as $cond) {
|
||||
$conditions[] = $this->buildCondition($cond, $params);
|
||||
}
|
||||
return "(" . implode(" OR ", $conditions) . ")";
|
||||
} else if ($condition instanceof CondAnd) {
|
||||
$conditions = array();
|
||||
foreach($condition->getConditions() as $cond) {
|
||||
$conditions[] = $this->buildCondition($cond, $params);
|
||||
}
|
||||
return "(" . implode(" AND ", $conditions) . ")";
|
||||
} else if ($condition instanceof Compare) {
|
||||
$column = $this->columnName($condition->getColumn());
|
||||
$value = $condition->getValue();
|
||||
$operator = $condition->getOperator();
|
||||
|
||||
if ($value === null) {
|
||||
if ($operator === "=") {
|
||||
return "$column IS NULL";
|
||||
} else if ($operator === "!=") {
|
||||
return "$column IS NOT NULL";
|
||||
}
|
||||
}
|
||||
|
||||
return $column . $operator . $this->addValue($value, $params);
|
||||
} else if ($condition instanceof CondBool) {
|
||||
return $this->columnName($condition->getValue());
|
||||
} else if (is_array($condition)) {
|
||||
if (count($condition) === 1) {
|
||||
return $this->buildCondition($condition[0], $params);
|
||||
} else {
|
||||
$conditions = array();
|
||||
foreach ($condition as $cond) {
|
||||
$conditions[] = $this->buildCondition($cond, $params);
|
||||
}
|
||||
return implode(" AND ", $conditions);
|
||||
}
|
||||
} else if($condition instanceof CondIn) {
|
||||
|
||||
$needle = $condition->getNeedle();
|
||||
$haystack = $condition->getHaystack();
|
||||
if (is_array($haystack)) {
|
||||
$values = array();
|
||||
foreach ($haystack as $value) {
|
||||
$values[] = $this->addValue($value, $params);
|
||||
}
|
||||
|
||||
$values = implode(",", $values);
|
||||
} else if($haystack instanceof Select) {
|
||||
$values = $haystack->build($params);
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("Unsupported in-expression value: " . get_class($condition));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($needle instanceof Column) {
|
||||
$lhs = $this->createExpression($needle, $params);
|
||||
} else {
|
||||
$lhs = $this->addValue($needle, $params);
|
||||
}
|
||||
|
||||
return "$lhs IN ($values)";
|
||||
} else if($condition instanceof CondKeyword) {
|
||||
$left = $condition->getLeftExp();
|
||||
$right = $condition->getRightExp();
|
||||
$keyword = $condition->getKeyword();
|
||||
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
|
||||
$right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params);
|
||||
return "$left $keyword $right ";
|
||||
} else if($condition instanceof CondNot) {
|
||||
$expression = $condition->getExpression();
|
||||
if ($expression instanceof Condition) {
|
||||
$expression = $this->buildCondition($expression, $params);
|
||||
} else {
|
||||
$expression = $this->columnName($expression);
|
||||
}
|
||||
|
||||
return "NOT $expression";
|
||||
} else if ($condition instanceof CondNull) {
|
||||
return $this->columnName($condition->getColumn()) . " IS NULL";
|
||||
} else if ($condition instanceof Exists) {
|
||||
return "EXISTS(" .$condition->getSubQuery()->build($params) . ")";
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("Unsupported condition type: " . gettype($condition));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function createExpression(Expression $exp, array &$params): ?string {
|
||||
if ($exp instanceof Column) {
|
||||
return $this->columnName($exp->getName());
|
||||
} else if ($exp instanceof Query) {
|
||||
return "(" . $exp->build($params) . ")";
|
||||
} else if ($exp instanceof CaseWhen) {
|
||||
$condition = $this->buildCondition($exp->getCondition(), $params);
|
||||
|
||||
// psql requires constant values here
|
||||
$trueCase = $this->addValue($exp->getTrueCase(), $params, true);
|
||||
$falseCase = $this->addValue($exp->getFalseCase(), $params, true);
|
||||
|
||||
return "CASE WHEN $condition THEN $trueCase ELSE $falseCase END";
|
||||
} else if ($exp instanceof Sum) {
|
||||
$value = $this->addValue($exp->getValue(), $params);
|
||||
$alias = $this->columnName($exp->getAlias());
|
||||
return "SUM($value) AS $alias";
|
||||
} else {
|
||||
$this->lastError = $this->logger->error("Unsupported expression type: " . get_class($exp));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function setLastError($str) {
|
||||
$this->lastError = $str;
|
||||
}
|
||||
|
||||
public function getLastInsertId(): int {
|
||||
return $this->lastInsertId;
|
||||
}
|
||||
|
||||
public function close() {
|
||||
$this->disconnect();
|
||||
$this->connection = NULL;
|
||||
}
|
||||
|
||||
public static function createConnection(ConnectionData $connectionData) {
|
||||
$type = $connectionData->getProperty("type");
|
||||
if ($type === "mysql") {
|
||||
$sql = new MySQL($connectionData);
|
||||
} else if ($type === "postgres") {
|
||||
$sql = new PostgreSQL($connectionData);
|
||||
} else {
|
||||
Logger::instance()->error("Unknown database type: $type");
|
||||
return "Unknown database type";
|
||||
}
|
||||
|
||||
if ($sql->checkRequirements()) {
|
||||
$sql->connect();
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
public abstract function getStatus();
|
||||
|
||||
public function parseBool($val) : bool {
|
||||
return in_array($val, array(true, 1, '1', 't', 'true', 'TRUE'), true);
|
||||
}
|
||||
}
|
||||
10
Core/Driver/SQL/Strategy/CascadeStrategy.class.php
Normal file
10
Core/Driver/SQL/Strategy/CascadeStrategy.class.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Strategy;
|
||||
|
||||
class CascadeStrategy extends Strategy {
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
}
|
||||
10
Core/Driver/SQL/Strategy/SetDefaultStrategy.class.php
Normal file
10
Core/Driver/SQL/Strategy/SetDefaultStrategy.class.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Strategy;
|
||||
|
||||
class SetDefaultStrategy extends Strategy {
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
}
|
||||
10
Core/Driver/SQL/Strategy/SetNullStrategy.class.php
Normal file
10
Core/Driver/SQL/Strategy/SetNullStrategy.class.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Strategy;
|
||||
|
||||
class SetNullStrategy extends Strategy {
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
}
|
||||
7
Core/Driver/SQL/Strategy/Strategy.class.php
Normal file
7
Core/Driver/SQL/Strategy/Strategy.class.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Strategy;
|
||||
|
||||
abstract class Strategy {
|
||||
|
||||
}
|
||||
22
Core/Driver/SQL/Strategy/UpdateStrategy.class.php
Normal file
22
Core/Driver/SQL/Strategy/UpdateStrategy.class.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Driver\SQL\Strategy;
|
||||
|
||||
class UpdateStrategy extends Strategy {
|
||||
|
||||
private array $values;
|
||||
private array $conflictingColumns;
|
||||
|
||||
public function __construct(array $conflictingColumns, array $values) {
|
||||
$this->conflictingColumns = $conflictingColumns;
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
public function getConflictingColumns(): array {
|
||||
return $this->conflictingColumns;
|
||||
}
|
||||
|
||||
public function getValues(): array {
|
||||
return $this->values;
|
||||
}
|
||||
}
|
||||
14
Core/Driver/SQL/Type/CurrentColumn.php
Normal file
14
Core/Driver/SQL/Type/CurrentColumn.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Core\Driver\SQL\Type;
|
||||
|
||||
|
||||
use Core\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 Core\Driver\SQL\Type;
|
||||
|
||||
use Core\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 Core\Driver\SQL\Type;
|
||||
|
||||
use Core\Driver\SQL\Keyword;
|
||||
|
||||
class Trigger extends Keyword {
|
||||
public function __construct() {
|
||||
parent::__construct("TRIGGER");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user