From f14a7a47628fe851290092d431487ae405035a4c Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 9 Jan 2023 20:27:01 +0100 Subject: [PATCH] database api start --- Core/API/DatabaseAPI.class.php | 97 +++++++++++++++++++ Core/Driver/SQL/Condition/Compare.class.php | 14 ++- Core/Driver/SQL/MySQL.class.php | 15 +++ Core/Driver/SQL/PostgreSQL.class.php | 14 +++ Core/Driver/SQL/SQL.class.php | 3 + .../Controller/DatabaseEntity.class.php | 2 +- .../Controller/DatabaseEntityHandler.php | 56 ++++++----- Core/Objects/DatabaseEntity/Group.class.php | 2 +- .../Objects/DatabaseEntity/Language.class.php | 2 +- 9 files changed, 174 insertions(+), 31 deletions(-) create mode 100644 Core/API/DatabaseAPI.class.php diff --git a/Core/API/DatabaseAPI.class.php b/Core/API/DatabaseAPI.class.php new file mode 100644 index 0000000..f3886f2 --- /dev/null +++ b/Core/API/DatabaseAPI.class.php @@ -0,0 +1,97 @@ +context->getSQL(); + $status = $sql->getStatus(); + + $this->result["status"] = $status; + + return true; + } + } + + class Migrate extends DatabaseAPI { + public function __construct(Context $context, bool $externalCall = false) { + parent::__construct($context, $externalCall, [ + "className" => new StringType("className", 256) + ]); + } + + protected function _execute(): bool { + $className = $this->getParam("className"); + if (!preg_match("/[a-zA-Z0-9]+/", $className)) { + return $this->createError("Invalid class name"); + } + + $class = null; + foreach (["Site", "Core"] as $baseDir) { + $classPath = "\\$baseDir\\Objects\\DatabaseEntity\\$className"; + if (isClass($classPath)) { + $class = new \ReflectionClass($classPath); + break; + } + } + + if ($class === null) { + return $this->createError("Class not found"); + } + + $sql = $this->context->getSQL(); + $handler = call_user_func("$classPath::getHandler", $sql, null, true); + $persistables = array_merge([ + $handler->getTableName() => $handler + ], $handler->getNMRelations()); + + foreach ($persistables as $tableName => $persistable) { + // first check if table exists + if (!$sql->tableExists($tableName)) { + $sql->startTransaction(); + $success = true; + try { + foreach ($persistable->getCreateQueries($sql) as $query) { + if (!$query->execute()) { + $this->lastError = "Error migrating table: " . $sql->getLastError(); + $success = false; + break; + } + } + } catch (\Exception $ex) { + $success = false; + $this->lastError = "Error migrating table: " . $ex->getMessage(); + } + + if (!$success) { + $sql->rollback(); + return false; + } else { + $sql->commit(); + } + } else { + // TODO: Alter table ... + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/Core/Driver/SQL/Condition/Compare.class.php b/Core/Driver/SQL/Condition/Compare.class.php index 7db63ab..e8356dd 100644 --- a/Core/Driver/SQL/Condition/Compare.class.php +++ b/Core/Driver/SQL/Condition/Compare.class.php @@ -2,18 +2,21 @@ namespace Core\Driver\SQL\Condition; +use Core\Driver\SQL\MySQL; use Core\Driver\SQL\SQL; class Compare extends Condition { private string $operator; private string $column; - private $value; + private mixed $value; + private bool $binary; - public function __construct(string $col, $val, string $operator = '=') { + public function __construct(string $col, $val, string $operator = '=', bool $binary = false) { $this->operator = $operator; $this->column = $col; $this->value = $val; + $this->binary = $binary; } public function getColumn(): string { return $this->column; } @@ -30,6 +33,11 @@ class Compare extends Condition { } } - return $sql->columnName($this->column) . $this->operator . $sql->addValue($this->value, $params); + $condition = $sql->columnName($this->column) . $this->operator . $sql->addValue($this->value, $params); + if ($this->binary && $sql instanceof MySQL) { + $condition = "BINARY $condition"; + } + + return $condition; } } \ No newline at end of file diff --git a/Core/Driver/SQL/MySQL.class.php b/Core/Driver/SQL/MySQL.class.php index 8503cf9..560f10a 100644 --- a/Core/Driver/SQL/MySQL.class.php +++ b/Core/Driver/SQL/MySQL.class.php @@ -4,6 +4,9 @@ namespace Core\Driver\SQL; use Core\API\Parameter\Parameter; +use Core\Driver\SQL\Condition\Compare; +use Core\Driver\SQL\Condition\CondLike; +use Core\Driver\SQL\Expression\Count; use DateTime; use Core\Driver\SQL\Column\Column; use Core\Driver\SQL\Column\IntColumn; @@ -453,6 +456,18 @@ class MySQL extends SQL { return $query; } + + public function tableExists(string $tableName): bool { + $tableSchema = $this->connectionData->getProperty("database"); + $res = $this->select(new Count()) + ->from("information_schema.TABLES") + ->where(new Compare("TABLE_NAME", $tableName, "=", true)) + ->where(new Compare("TABLE_SCHEMA", $tableSchema, "=", true)) + ->where(new CondLike(new Column("TABLE_TYPE"), "BASE TABLE")) + ->execute(); + + return $res && $res[0]["count"] > 0; + } } class RowIteratorMySQL extends RowIterator { diff --git a/Core/Driver/SQL/PostgreSQL.class.php b/Core/Driver/SQL/PostgreSQL.class.php index 6d15ac5..18b0f81 100644 --- a/Core/Driver/SQL/PostgreSQL.class.php +++ b/Core/Driver/SQL/PostgreSQL.class.php @@ -14,8 +14,11 @@ use Core\Driver\SQL\Column\DateTimeColumn; use Core\Driver\SQL\Column\BoolColumn; use Core\Driver\SQL\Column\JsonColumn; +use Core\Driver\SQL\Condition\Compare; +use Core\Driver\SQL\Condition\CondLike; use Core\Driver\SQL\Condition\CondRegex; use Core\Driver\SQL\Expression\Add; +use Core\Driver\SQL\Expression\Count; use Core\Driver\SQL\Expression\CurrentTimeStamp; use Core\Driver\SQL\Expression\Expression; use Core\Driver\SQL\Query\CreateProcedure; @@ -444,6 +447,17 @@ class PostgreSQL extends SQL { return $query; } + + public function tableExists(string $tableName): bool { + $tableSchema = $this->connectionData->getProperty("database"); + $res = $this->select(new Count()) + ->from("pg_tables") + ->whereEq("tablename", $tableName) + ->whereEq("schemaname", $tableSchema) + ->execute(); + + return $res && $res[0]["count"] > 0; + } } class RowIteratorPostgreSQL extends RowIterator { diff --git a/Core/Driver/SQL/SQL.class.php b/Core/Driver/SQL/SQL.class.php index aa27838..d571f7b 100644 --- a/Core/Driver/SQL/SQL.class.php +++ b/Core/Driver/SQL/SQL.class.php @@ -127,6 +127,9 @@ abstract class SQL { public abstract function connect(); public abstract function disconnect(); + // Schema + public abstract function tableExists(string $tableName): bool; + /** * @param Query $query * @param int $fetchType diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php index 34556ce..4f9d6a9 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php @@ -112,7 +112,7 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable { public function preInsert(array &$row) { } public function postFetch(SQL $sql, array $row) { } - public static function getPredefinedValues(SQL $sql): array { return []; } + public static function getPredefinedValues(): array { return []; } public static function fromRow(SQL $sql, array $row): static { $handler = self::getHandler($sql); diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php index b422c10..22b70bb 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php @@ -146,32 +146,38 @@ class DatabaseEntityHandler implements Persistable { } else if ($propertyTypeName === 'DateTime') { $this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable, $defaultValue); } else if ($propertyTypeName === "array") { - $multiple = self::getAttribute($property, Multiple::class); - if (!$multiple) { - $this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName. " . - "Is the 'Multiple' attribute missing?"); - } + $json = self::getAttribute($property, Json::class); + if ($json) { + $this->columns[$propertyName] = new JsonColumn($columnName, $nullable, $defaultValue); + } else { - try { - $refClass = $multiple->getClassName(); - $requestedClass = new \ReflectionClass($refClass); - if ($requestedClass->isSubclassOf(DatabaseEntity::class)) { - $nmTableName = NMRelation::buildTableName($this->getTableName(), $requestedClass->getShortName()); - $nmRelation = $this->nmRelations[$nmTableName] ?? null; - if (!$nmRelation) { - $otherHandler = DatabaseEntity::getHandler($this->sql, $requestedClass); - $otherNM = $otherHandler->getNMRelations(); - $nmRelation = $otherNM[$nmTableName] ?? (new NMRelation($this, $otherHandler)); - $this->nmRelations[$nmTableName] = $nmRelation; - } - - $this->nmRelations[$nmTableName]->addProperty($this, $property); - } else { - $this->raiseError("Cannot persist class '$className': Property '$propertyName' of type multiple can " . - "only reference DatabaseEntity types, but got: $refClass"); + $multiple = self::getAttribute($property, Multiple::class); + if (!$multiple) { + $this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName. " . + "Is the 'Multiple' attribute missing?"); + } + + try { + $refClass = $multiple->getClassName(); + $requestedClass = new \ReflectionClass($refClass); + if ($requestedClass->isSubclassOf(DatabaseEntity::class)) { + $nmTableName = NMRelation::buildTableName($this->getTableName(), $requestedClass->getShortName()); + $nmRelation = $this->nmRelations[$nmTableName] ?? null; + if (!$nmRelation) { + $otherHandler = DatabaseEntity::getHandler($this->sql, $requestedClass); + $otherNM = $otherHandler->getNMRelations(); + $nmRelation = $otherNM[$nmTableName] ?? (new NMRelation($this, $otherHandler)); + $this->nmRelations[$nmTableName] = $nmRelation; + } + + $this->nmRelations[$nmTableName]->addProperty($this, $property); + } else { + $this->raiseError("Cannot persist class '$className': Property '$propertyName' of type multiple can " . + "only reference DatabaseEntity types, but got: $refClass"); + } + } catch (\Exception $ex) { + $this->raiseError("Cannot persist class '$className' property '$propertyTypeName': " . $ex->getMessage()); } - } catch (\Exception $ex) { - $this->raiseError("Cannot persist class '$className' property '$propertyTypeName': " . $ex->getMessage()); } } else if ($propertyTypeName !== "mixed") { try { @@ -630,7 +636,7 @@ class DatabaseEntityHandler implements Persistable { // pre defined values $getPredefinedValues = $this->entityClass->getMethod("getPredefinedValues"); $getPredefinedValues->setAccessible(true); - $predefinedValues = $getPredefinedValues->invoke(null, $sql); + $predefinedValues = $getPredefinedValues->invoke(null); if ($predefinedValues) { $queries[] = $this->getInsertQuery($predefinedValues); } diff --git a/Core/Objects/DatabaseEntity/Group.class.php b/Core/Objects/DatabaseEntity/Group.class.php index f12a81f..c4203f9 100644 --- a/Core/Objects/DatabaseEntity/Group.class.php +++ b/Core/Objects/DatabaseEntity/Group.class.php @@ -37,7 +37,7 @@ class Group extends DatabaseEntity { return User::toJsonArray($users, ["id", "name", "fullName", "profilePicture"]); } - public static function getPredefinedValues(SQL $sql): array { + public static function getPredefinedValues(): array { return [ new Group(Group::ADMIN, Group::GROUPS[Group::ADMIN], "#dc3545"), new Group(Group::MODERATOR, Group::GROUPS[Group::MODERATOR], "#28a745"), diff --git a/Core/Objects/DatabaseEntity/Language.class.php b/Core/Objects/DatabaseEntity/Language.class.php index b3ded3a..e1ff008 100644 --- a/Core/Objects/DatabaseEntity/Language.class.php +++ b/Core/Objects/DatabaseEntity/Language.class.php @@ -141,7 +141,7 @@ namespace Core\Objects\DatabaseEntity { return array_key_exists($module, $this->entries); } - public static function getPredefinedValues(SQL $sql): array { + public static function getPredefinedValues(): array { return [ new Language(Language::AMERICAN_ENGLISH, "en_US", 'American English'), new Language(Language::GERMAN_STANDARD, "de_DE", 'Deutsch Standard'),