database api start

This commit is contained in:
Roman 2023-01-09 20:27:01 +01:00
parent a8af7fa700
commit f14a7a4762
9 changed files with 174 additions and 31 deletions

@ -0,0 +1,97 @@
<?php
namespace Core\API {
abstract class DatabaseAPI extends Request {
}
}
namespace Core\API\Database {
use Core\API\DatabaseAPI;
use Core\API\Parameter\StringType;
use Core\Objects\Context;
class Status extends DatabaseAPI {
public function __construct(Context $context, bool $externalCall = false) {
parent::__construct($context, $externalCall, []);
}
protected function _execute(): bool {
$sql = $this->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;
}
}
}

@ -2,18 +2,21 @@
namespace Core\Driver\SQL\Condition; namespace Core\Driver\SQL\Condition;
use Core\Driver\SQL\MySQL;
use Core\Driver\SQL\SQL; use Core\Driver\SQL\SQL;
class Compare extends Condition { class Compare extends Condition {
private string $operator; private string $operator;
private string $column; 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->operator = $operator;
$this->column = $col; $this->column = $col;
$this->value = $val; $this->value = $val;
$this->binary = $binary;
} }
public function getColumn(): string { return $this->column; } 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;
} }
} }

@ -4,6 +4,9 @@ namespace Core\Driver\SQL;
use Core\API\Parameter\Parameter; 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 DateTime;
use Core\Driver\SQL\Column\Column; use Core\Driver\SQL\Column\Column;
use Core\Driver\SQL\Column\IntColumn; use Core\Driver\SQL\Column\IntColumn;
@ -453,6 +456,18 @@ class MySQL extends SQL {
return $query; 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 { class RowIteratorMySQL extends RowIterator {

@ -14,8 +14,11 @@ use Core\Driver\SQL\Column\DateTimeColumn;
use Core\Driver\SQL\Column\BoolColumn; use Core\Driver\SQL\Column\BoolColumn;
use Core\Driver\SQL\Column\JsonColumn; 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\Condition\CondRegex;
use Core\Driver\SQL\Expression\Add; use Core\Driver\SQL\Expression\Add;
use Core\Driver\SQL\Expression\Count;
use Core\Driver\SQL\Expression\CurrentTimeStamp; use Core\Driver\SQL\Expression\CurrentTimeStamp;
use Core\Driver\SQL\Expression\Expression; use Core\Driver\SQL\Expression\Expression;
use Core\Driver\SQL\Query\CreateProcedure; use Core\Driver\SQL\Query\CreateProcedure;
@ -444,6 +447,17 @@ class PostgreSQL extends SQL {
return $query; 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 { class RowIteratorPostgreSQL extends RowIterator {

@ -127,6 +127,9 @@ abstract class SQL {
public abstract function connect(); public abstract function connect();
public abstract function disconnect(); public abstract function disconnect();
// Schema
public abstract function tableExists(string $tableName): bool;
/** /**
* @param Query $query * @param Query $query
* @param int $fetchType * @param int $fetchType

@ -112,7 +112,7 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
public function preInsert(array &$row) { } public function preInsert(array &$row) { }
public function postFetch(SQL $sql, 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 { public static function fromRow(SQL $sql, array $row): static {
$handler = self::getHandler($sql); $handler = self::getHandler($sql);

@ -146,32 +146,38 @@ class DatabaseEntityHandler implements Persistable {
} else if ($propertyTypeName === 'DateTime') { } else if ($propertyTypeName === 'DateTime') {
$this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable, $defaultValue); $this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable, $defaultValue);
} else if ($propertyTypeName === "array") { } else if ($propertyTypeName === "array") {
$multiple = self::getAttribute($property, Multiple::class); $json = self::getAttribute($property, Json::class);
if (!$multiple) { if ($json) {
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName. " . $this->columns[$propertyName] = new JsonColumn($columnName, $nullable, $defaultValue);
"Is the 'Multiple' attribute missing?"); } else {
}
try { $multiple = self::getAttribute($property, Multiple::class);
$refClass = $multiple->getClassName(); if (!$multiple) {
$requestedClass = new \ReflectionClass($refClass); $this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName. " .
if ($requestedClass->isSubclassOf(DatabaseEntity::class)) { "Is the 'Multiple' attribute missing?");
$nmTableName = NMRelation::buildTableName($this->getTableName(), $requestedClass->getShortName()); }
$nmRelation = $this->nmRelations[$nmTableName] ?? null;
if (!$nmRelation) { try {
$otherHandler = DatabaseEntity::getHandler($this->sql, $requestedClass); $refClass = $multiple->getClassName();
$otherNM = $otherHandler->getNMRelations(); $requestedClass = new \ReflectionClass($refClass);
$nmRelation = $otherNM[$nmTableName] ?? (new NMRelation($this, $otherHandler)); if ($requestedClass->isSubclassOf(DatabaseEntity::class)) {
$this->nmRelations[$nmTableName] = $nmRelation; $nmTableName = NMRelation::buildTableName($this->getTableName(), $requestedClass->getShortName());
} $nmRelation = $this->nmRelations[$nmTableName] ?? null;
if (!$nmRelation) {
$this->nmRelations[$nmTableName]->addProperty($this, $property); $otherHandler = DatabaseEntity::getHandler($this->sql, $requestedClass);
} else { $otherNM = $otherHandler->getNMRelations();
$this->raiseError("Cannot persist class '$className': Property '$propertyName' of type multiple can " . $nmRelation = $otherNM[$nmTableName] ?? (new NMRelation($this, $otherHandler));
"only reference DatabaseEntity types, but got: $refClass"); $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") { } else if ($propertyTypeName !== "mixed") {
try { try {
@ -630,7 +636,7 @@ class DatabaseEntityHandler implements Persistable {
// pre defined values // pre defined values
$getPredefinedValues = $this->entityClass->getMethod("getPredefinedValues"); $getPredefinedValues = $this->entityClass->getMethod("getPredefinedValues");
$getPredefinedValues->setAccessible(true); $getPredefinedValues->setAccessible(true);
$predefinedValues = $getPredefinedValues->invoke(null, $sql); $predefinedValues = $getPredefinedValues->invoke(null);
if ($predefinedValues) { if ($predefinedValues) {
$queries[] = $this->getInsertQuery($predefinedValues); $queries[] = $this->getInsertQuery($predefinedValues);
} }

@ -37,7 +37,7 @@ class Group extends DatabaseEntity {
return User::toJsonArray($users, ["id", "name", "fullName", "profilePicture"]); return User::toJsonArray($users, ["id", "name", "fullName", "profilePicture"]);
} }
public static function getPredefinedValues(SQL $sql): array { public static function getPredefinedValues(): array {
return [ return [
new Group(Group::ADMIN, Group::GROUPS[Group::ADMIN], "#dc3545"), new Group(Group::ADMIN, Group::GROUPS[Group::ADMIN], "#dc3545"),
new Group(Group::MODERATOR, Group::GROUPS[Group::MODERATOR], "#28a745"), new Group(Group::MODERATOR, Group::GROUPS[Group::MODERATOR], "#28a745"),

@ -141,7 +141,7 @@ namespace Core\Objects\DatabaseEntity {
return array_key_exists($module, $this->entries); return array_key_exists($module, $this->entries);
} }
public static function getPredefinedValues(SQL $sql): array { public static function getPredefinedValues(): array {
return [ return [
new Language(Language::AMERICAN_ENGLISH, "en_US", 'American English'), new Language(Language::AMERICAN_ENGLISH, "en_US", 'American English'),
new Language(Language::GERMAN_STANDARD, "de_DE", 'Deutsch Standard'), new Language(Language::GERMAN_STANDARD, "de_DE", 'Deutsch Standard'),