database api start
This commit is contained in:
		
							parent
							
								
									a8af7fa700
								
							
						
					
					
						commit
						f14a7a4762
					
				
							
								
								
									
										97
									
								
								Core/API/DatabaseAPI.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										97
									
								
								Core/API/DatabaseAPI.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| 
 | ||||
| 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; | ||||
|   } | ||||
| } | ||||
| @ -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 { | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
|  | ||||
| @ -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"), | ||||
|  | ||||
| @ -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'), | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user