2020-04-02 00:02:51 +02:00
|
|
|
<?php
|
|
|
|
|
2022-11-18 18:06:46 +01:00
|
|
|
namespace Core\Driver\SQL;
|
|
|
|
|
|
|
|
use Core\Driver\Logger\Logger;
|
|
|
|
use Core\Driver\SQL\Column\Column;
|
|
|
|
use Core\Driver\SQL\Condition\Condition;
|
|
|
|
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\CurrentTimeStamp;
|
|
|
|
use Core\Driver\SQL\Query\AlterTable;
|
2022-11-27 15:58:44 +01:00
|
|
|
use Core\Driver\SQL\Query\Commit;
|
2022-11-18 18:06:46 +01:00
|
|
|
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;
|
2022-11-27 15:58:44 +01:00
|
|
|
use Core\Driver\SQL\Query\RollBack;
|
2022-11-18 18:06:46 +01:00
|
|
|
use Core\Driver\SQL\Query\Select;
|
2022-11-27 15:58:44 +01:00
|
|
|
use Core\Driver\SQL\Query\StartTransaction;
|
2022-11-18 18:06:46 +01:00
|
|
|
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;
|
2020-04-03 14:46:29 +02:00
|
|
|
|
2020-04-02 00:02:51 +02:00
|
|
|
abstract class SQL {
|
|
|
|
|
2022-06-08 18:37:08 +02:00
|
|
|
const FETCH_NONE = 0;
|
|
|
|
const FETCH_ONE = 1;
|
|
|
|
const FETCH_ALL = 2;
|
|
|
|
const FETCH_ITERATIVE = 3;
|
|
|
|
|
2022-05-31 16:14:49 +02:00
|
|
|
protected Logger $logger;
|
2020-04-03 17:39:58 +02:00
|
|
|
protected string $lastError;
|
2020-04-02 00:02:51 +02:00
|
|
|
protected $connection;
|
2020-04-03 17:39:58 +02:00
|
|
|
protected ConnectionData $connectionData;
|
|
|
|
protected int $lastInsertId;
|
2020-04-02 00:02:51 +02:00
|
|
|
|
2022-06-20 19:52:31 +02:00
|
|
|
protected bool $logQueries;
|
|
|
|
|
2020-04-02 01:48:46 +02:00
|
|
|
public function __construct($connectionData) {
|
2020-04-02 00:02:51 +02:00
|
|
|
$this->connection = NULL;
|
2020-04-03 14:46:29 +02:00
|
|
|
$this->lastError = 'Unknown Error';
|
2020-04-02 00:02:51 +02:00
|
|
|
$this->connectionData = $connectionData;
|
|
|
|
$this->lastInsertId = 0;
|
2022-05-31 16:14:49 +02:00
|
|
|
$this->logger = new Logger(getClassName($this), $this);
|
2022-06-20 19:52:31 +02:00
|
|
|
$this->logQueries = false;
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function isConnected(): bool {
|
2022-06-20 19:52:31 +02:00
|
|
|
return !is_null($this->connection) && !is_bool($this->connection);
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function getLastError(): string {
|
2020-04-02 00:02:51 +02:00
|
|
|
return trim($this->lastError);
|
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function createTable($tableName): CreateTable {
|
2020-06-26 23:32:45 +02:00
|
|
|
return new CreateTable($this, $tableName);
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function insert($tableName, $columns=array()): Insert {
|
2020-06-26 23:32:45 +02:00
|
|
|
return new Insert($this, $tableName, $columns);
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function select(...$columNames): Select {
|
2020-06-26 23:32:45 +02:00
|
|
|
return new Select($this, $columNames);
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function truncate($table): Truncate {
|
2020-06-26 23:32:45 +02:00
|
|
|
return new Truncate($this, $table);
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function delete($table): Delete {
|
2020-06-26 23:32:45 +02:00
|
|
|
return new Delete($this, $table);
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function update($table): Update {
|
2020-06-26 23:32:45 +02:00
|
|
|
return new Update($this, $table);
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function drop(string $table): Drop {
|
2021-01-07 14:59:36 +01:00
|
|
|
return new Drop($this, $table);
|
|
|
|
}
|
|
|
|
|
2022-11-27 15:58:44 +01:00
|
|
|
public function startTransaction(): bool {
|
|
|
|
return (new StartTransaction($this))->execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function commit(): bool {
|
|
|
|
return (new Commit($this))->execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function rollback(): bool {
|
|
|
|
return (new RollBack($this))->execute();
|
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function alterTable($tableName): AlterTable {
|
2021-04-03 16:23:30 +02:00
|
|
|
return new AlterTable($this, $tableName);
|
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function createTrigger($triggerName): CreateTrigger {
|
|
|
|
return new CreateTrigger($this, $triggerName);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function createProcedure(string $procName): CreateProcedure {
|
|
|
|
return new CreateProcedure($this, $procName);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-02 01:48:46 +02:00
|
|
|
// ####################
|
|
|
|
// ### ABSTRACT METHODS
|
|
|
|
// ####################
|
|
|
|
|
|
|
|
// Misc
|
|
|
|
public abstract function checkRequirements();
|
|
|
|
public abstract function getDriverName();
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
// Connection Management
|
2020-04-02 01:48:46 +02:00
|
|
|
public abstract function connect();
|
|
|
|
public abstract function disconnect();
|
|
|
|
|
2023-01-09 20:27:01 +01:00
|
|
|
// Schema
|
|
|
|
public abstract function tableExists(string $tableName): bool;
|
|
|
|
|
2022-06-14 10:30:35 +02:00
|
|
|
/**
|
|
|
|
* @param Query $query
|
|
|
|
* @param int $fetchType
|
|
|
|
* @return mixed
|
|
|
|
*/
|
2022-06-08 18:37:08 +02:00
|
|
|
public function executeQuery(Query $query, int $fetchType = self::FETCH_NONE) {
|
2020-04-03 17:39:58 +02:00
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
$parameters = [];
|
|
|
|
$queryStr = $query->build($parameters);
|
2020-04-03 17:39:58 +02:00
|
|
|
|
2022-06-20 19:52:31 +02:00
|
|
|
if ($query->dump) {
|
2021-04-08 18:29:47 +02:00
|
|
|
var_dump($queryStr);
|
|
|
|
var_dump($parameters);
|
2020-04-03 17:39:58 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
if ($queryStr === null) {
|
2020-04-03 17:39:58 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-01-18 14:37:34 +01:00
|
|
|
$logLevel = Logger::LOG_LEVEL_ERROR;
|
2023-01-22 12:32:18 +01:00
|
|
|
// $logLevel = Logger::LOG_LEVEL_DEBUG;
|
2023-01-16 21:47:23 +01:00
|
|
|
if ($query instanceof Insert && $query->getTableName() === "SystemLog") {
|
|
|
|
$logLevel = Logger::LOG_LEVEL_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
$res = $this->execute($queryStr, $parameters, $fetchType, $logLevel);
|
2020-04-03 17:39:58 +02:00
|
|
|
$success = ($res !== FALSE);
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
// fetch generated serial ids for Insert statements
|
|
|
|
$generatedColumn = ($query instanceof Insert ? $query->getReturning() : null);
|
2022-06-08 18:37:08 +02:00
|
|
|
if ($success && $generatedColumn) {
|
2021-04-08 18:29:47 +02:00
|
|
|
$this->fetchReturning($res, $generatedColumn);
|
2020-04-03 14:46:29 +02:00
|
|
|
}
|
|
|
|
|
2022-06-20 19:52:31 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-06-08 18:37:08 +02:00
|
|
|
return $fetchType === self::FETCH_NONE ? $success : $res;
|
2020-04-03 14:46:29 +02:00
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function getWhereClause($conditions, &$params): string {
|
2020-04-03 14:46:29 +02:00
|
|
|
if (!$conditions) {
|
|
|
|
return "";
|
|
|
|
} else {
|
|
|
|
return " WHERE " . $this->buildCondition($conditions, $params);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function getConstraintDefinition(Constraint $constraint): ?string {
|
2020-04-03 17:39:58 +02:00
|
|
|
$columnName = $this->columnName($constraint->getColumnNames());
|
2022-11-18 18:06:46 +01:00
|
|
|
|
2020-04-03 14:46:29 +02:00
|
|
|
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 {
|
2022-05-31 16:14:49 +02:00
|
|
|
$this->lastError = $this->logger->error("Unsupported constraint type: " . get_class($constraint));
|
2021-04-08 18:29:47 +02:00
|
|
|
return null;
|
2020-04-03 14:46:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
protected abstract function fetchReturning($res, string $returningCol);
|
|
|
|
public abstract function getColumnDefinition(Column $column): ?string;
|
|
|
|
public abstract function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string;
|
2022-02-20 16:53:26 +01:00
|
|
|
public abstract function createTriggerBody(CreateTrigger $trigger, array $params = []): ?string;
|
2021-04-08 18:29:47 +02:00
|
|
|
public abstract function getProcedureHead(CreateProcedure $procedure): ?string;
|
|
|
|
public abstract function getColumnType(Column $column): ?string;
|
2023-01-05 22:47:17 +01:00
|
|
|
public abstract function getStatus();
|
2021-04-08 18:29:47 +02:00
|
|
|
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;
|
2020-04-03 17:39:58 +02:00
|
|
|
}
|
|
|
|
|
2023-01-05 22:47:17 +01:00
|
|
|
public function getLogger(): Logger {
|
|
|
|
return $this->logger;
|
|
|
|
}
|
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
protected function getUnsafeValue($value): ?string {
|
2021-04-10 01:33:40 +02:00
|
|
|
if (is_string($value)) {
|
2021-04-08 18:29:47 +02:00
|
|
|
return "'" . addslashes("$value") . "'"; // unsafe operation here...
|
2021-04-10 01:33:40 +02:00
|
|
|
} else if (is_numeric($value) || is_bool($value)) {
|
|
|
|
return $value;
|
2021-04-08 18:29:47 +02:00
|
|
|
} else if ($value instanceof Column) {
|
|
|
|
return $this->columnName($value);
|
|
|
|
} else if ($value === null) {
|
|
|
|
return "NULL";
|
|
|
|
} else {
|
2022-05-31 16:14:49 +02:00
|
|
|
$this->lastError = $this->logger->error("Cannot create unsafe value of type: " . gettype($value));
|
2021-04-08 18:29:47 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2020-04-03 17:39:58 +02:00
|
|
|
|
2020-04-02 00:02:51 +02:00
|
|
|
protected abstract function getValueDefinition($val);
|
2021-04-10 01:33:40 +02:00
|
|
|
public abstract function addValue($val, &$params = NULL, bool $unsafe = false);
|
2021-04-08 18:29:47 +02:00
|
|
|
protected abstract function buildUnsafe(Query $statement): string;
|
2020-04-02 00:02:51 +02:00
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public abstract function tableName($table): string;
|
|
|
|
public abstract function columnName($col): string;
|
2020-04-02 15:08:14 +02:00
|
|
|
|
2020-04-02 01:48:46 +02:00
|
|
|
// Special Keywords and functions
|
2021-04-08 19:08:05 +02:00
|
|
|
public function now(): CurrentTimeStamp { return new CurrentTimeStamp(); }
|
|
|
|
public function currentTimestamp(): CurrentTimeStamp { return new CurrentTimeStamp(); }
|
2020-04-02 21:19:06 +02:00
|
|
|
|
2020-04-02 01:48:46 +02:00
|
|
|
// Statements
|
2022-06-14 10:30:35 +02:00
|
|
|
/**
|
|
|
|
* @return mixed
|
|
|
|
*/
|
2023-01-16 21:47:23 +01:00
|
|
|
protected abstract function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE, int $logLevel = Logger::LOG_LEVEL_ERROR);
|
2020-04-02 00:02:51 +02:00
|
|
|
|
2023-01-05 22:47:17 +01:00
|
|
|
public function buildCondition(Condition|array $condition, &$params): string {
|
2020-04-04 01:15:59 +02:00
|
|
|
|
2023-01-05 22:47:17 +01:00
|
|
|
if (is_array($condition)) {
|
2020-04-04 01:15:59 +02:00
|
|
|
if (count($condition) === 1) {
|
2020-04-02 01:48:46 +02:00
|
|
|
return $this->buildCondition($condition[0], $params);
|
|
|
|
} else {
|
|
|
|
$conditions = array();
|
2020-06-19 14:12:07 +02:00
|
|
|
foreach ($condition as $cond) {
|
2020-04-02 01:48:46 +02:00
|
|
|
$conditions[] = $this->buildCondition($cond, $params);
|
|
|
|
}
|
|
|
|
return implode(" AND ", $conditions);
|
|
|
|
}
|
2020-04-03 17:39:58 +02:00
|
|
|
} else {
|
2023-01-05 22:47:17 +01:00
|
|
|
return $this->addValue($condition, $params);
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-29 16:37:42 +01:00
|
|
|
public function setLastError($str): void {
|
2020-04-02 01:48:46 +02:00
|
|
|
$this->lastError = $str;
|
|
|
|
}
|
2020-04-02 00:02:51 +02:00
|
|
|
|
2021-04-08 18:29:47 +02:00
|
|
|
public function getLastInsertId(): int {
|
2020-04-02 00:02:51 +02:00
|
|
|
return $this->lastInsertId;
|
|
|
|
}
|
|
|
|
|
2024-03-29 16:37:42 +01:00
|
|
|
public function close(): void {
|
2020-04-02 01:48:46 +02:00
|
|
|
$this->disconnect();
|
|
|
|
$this->connection = NULL;
|
2020-04-02 00:02:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 17:39:58 +02:00
|
|
|
public static function createConnection(ConnectionData $connectionData) {
|
2020-04-02 00:02:51 +02:00
|
|
|
$type = $connectionData->getProperty("type");
|
|
|
|
if ($type === "mysql") {
|
|
|
|
$sql = new MySQL($connectionData);
|
2020-04-02 01:48:46 +02:00
|
|
|
} else if ($type === "postgres") {
|
|
|
|
$sql = new PostgreSQL($connectionData);
|
2020-04-02 00:02:51 +02:00
|
|
|
} else {
|
2022-05-31 16:14:49 +02:00
|
|
|
Logger::instance()->error("Unknown database type: $type");
|
2020-04-02 00:02:51 +02:00
|
|
|
return "Unknown database type";
|
|
|
|
}
|
|
|
|
|
2020-04-02 01:48:46 +02:00
|
|
|
if ($sql->checkRequirements()) {
|
|
|
|
$sql->connect();
|
|
|
|
}
|
|
|
|
|
2020-04-02 00:02:51 +02:00
|
|
|
return $sql;
|
|
|
|
}
|
2020-06-19 13:13:13 +02:00
|
|
|
|
2020-06-25 16:54:58 +02:00
|
|
|
public function parseBool($val) : bool {
|
2020-06-25 21:53:33 +02:00
|
|
|
return in_array($val, array(true, 1, '1', 't', 'true', 'TRUE'), true);
|
2020-06-25 16:54:58 +02:00
|
|
|
}
|
2020-04-03 17:39:58 +02:00
|
|
|
}
|