DB Ent. fix recursion
This commit is contained in:
parent
424a945fa6
commit
e37b9355b9
@ -343,7 +343,6 @@ namespace Core\API\User {
|
|||||||
$this->result["session"] = $this->context->getSession()->jsonSerialize();
|
$this->result["session"] = $this->context->getSession()->jsonSerialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$res = $sql->select("method", "groups")
|
$res = $sql->select("method", "groups")
|
||||||
->from("ApiPermission")
|
->from("ApiPermission")
|
||||||
|
@ -150,6 +150,7 @@ abstract class SQL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$logLevel = Logger::LOG_LEVEL_ERROR;
|
$logLevel = Logger::LOG_LEVEL_ERROR;
|
||||||
|
// $logLevel = Logger::LOG_LEVEL_DEBUG;
|
||||||
if ($query instanceof Insert && $query->getTableName() === "SystemLog") {
|
if ($query instanceof Insert && $query->getTableName() === "SystemLog") {
|
||||||
$logLevel = Logger::LOG_LEVEL_NONE;
|
$logLevel = Logger::LOG_LEVEL_NONE;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ return [
|
|||||||
"request" => "Anfordern",
|
"request" => "Anfordern",
|
||||||
"cancel" => "Abbrechen",
|
"cancel" => "Abbrechen",
|
||||||
"confirm" => "Bestätigen",
|
"confirm" => "Bestätigen",
|
||||||
|
"add" => "Hinzufügen",
|
||||||
"ok" => "OK",
|
"ok" => "OK",
|
||||||
"language" => "Sprache",
|
"language" => "Sprache",
|
||||||
"loading" => "Laden",
|
"loading" => "Laden",
|
||||||
|
@ -27,6 +27,7 @@ return [
|
|||||||
"request" => "Request",
|
"request" => "Request",
|
||||||
"cancel" => "Cancel",
|
"cancel" => "Cancel",
|
||||||
"confirm" => "Confirm",
|
"confirm" => "Confirm",
|
||||||
|
"add" => "Add",
|
||||||
"close" => "Close",
|
"close" => "Close",
|
||||||
"ok" => "OK",
|
"ok" => "OK",
|
||||||
"remove" => "Remove",
|
"remove" => "Remove",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Core\Objects\DatabaseEntity\Controller;
|
namespace Core\Objects\DatabaseEntity\Controller;
|
||||||
|
|
||||||
use ArrayAccess;
|
use ArrayAccess;
|
||||||
|
use Core\Driver\SQL\Condition\Compare;
|
||||||
use Core\Driver\SQL\Condition\Condition;
|
use Core\Driver\SQL\Condition\Condition;
|
||||||
use Core\Driver\SQL\Expression\Count;
|
use Core\Driver\SQL\Expression\Count;
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
@ -134,25 +135,23 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
|||||||
}, $entities);
|
}, $entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hooks
|
||||||
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 function postUpdate() { }
|
public function postUpdate() { }
|
||||||
public static function getPredefinedValues(): array { return []; }
|
public static function getPredefinedValues(): array { return []; }
|
||||||
public function postDelete() { }
|
public function postDelete() { }
|
||||||
|
|
||||||
public static function fromRow(SQL $sql, array $row): static {
|
public static function newInstance(\ReflectionClass $reflectionClass, array $row) {
|
||||||
$handler = self::getHandler($sql);
|
|
||||||
return $handler->entityFromRow($row);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function newInstance(\ReflectionClass $reflectionClass) {
|
|
||||||
return $reflectionClass->newInstanceWithoutConstructor();
|
return $reflectionClass->newInstanceWithoutConstructor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function find(SQL $sql, int $id, bool $fetchEntities = false, bool $fetchRecursive = false): static|bool|null {
|
public static function find(SQL $sql, int $id, bool $fetchEntities = false, bool $fetchRecursive = false): static|bool|null {
|
||||||
$handler = self::getHandler($sql);
|
$handler = self::getHandler($sql);
|
||||||
if ($fetchEntities) {
|
if ($fetchEntities) {
|
||||||
|
$context = new DatabaseEntityQueryContext();
|
||||||
return DatabaseEntityQuery::fetchOne(self::getHandler($sql))
|
return DatabaseEntityQuery::fetchOne(self::getHandler($sql))
|
||||||
|
->withContext($context)
|
||||||
->whereEq($handler->getTableName() . ".id", $id)
|
->whereEq($handler->getTableName() . ".id", $id)
|
||||||
->fetchEntities($fetchRecursive)
|
->fetchEntities($fetchRecursive)
|
||||||
->execute();
|
->execute();
|
||||||
@ -162,13 +161,8 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function exists(SQL $sql, int $id): bool {
|
public static function exists(SQL $sql, int $id): bool {
|
||||||
$handler = self::getHandler($sql);
|
$count = self::count($sql, new Compare("id", $id));
|
||||||
$res = $sql->select(new Count())
|
return $count !== false && $count !== 0;
|
||||||
->from($handler->getTableName())
|
|
||||||
->whereEq($handler->getTableName() . ".id", $id)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
return $res !== false && $res[0]["count"] !== 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function findBy(DatabaseEntityQuery $dbQuery): static|array|bool|null {
|
public static function findBy(DatabaseEntityQuery $dbQuery): static|array|bool|null {
|
||||||
@ -176,15 +170,22 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function findAll(SQL $sql, ?Condition $condition = null): ?array {
|
public static function findAll(SQL $sql, ?Condition $condition = null): ?array {
|
||||||
$handler = self::getHandler($sql);
|
|
||||||
return $handler->fetchMultiple($condition);
|
$query = self::createBuilder($sql, false);
|
||||||
|
if ($condition) {
|
||||||
|
$query->where($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function createBuilder(SQL $sql, bool $one): DatabaseEntityQuery {
|
public static function createBuilder(SQL $sql, bool $one): DatabaseEntityQuery {
|
||||||
|
$context = new DatabaseEntityQueryContext();
|
||||||
|
|
||||||
if ($one) {
|
if ($one) {
|
||||||
return DatabaseEntityQuery::fetchOne(self::getHandler($sql));
|
return DatabaseEntityQuery::fetchOne(self::getHandler($sql))->withContext($context);
|
||||||
} else {
|
} else {
|
||||||
return DatabaseEntityQuery::fetchAll(self::getHandler($sql));
|
return DatabaseEntityQuery::fetchAll(self::getHandler($sql))->withContext($context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ use Core\Driver\SQL\Column\IntColumn;
|
|||||||
use Core\Driver\SQL\Column\JsonColumn;
|
use Core\Driver\SQL\Column\JsonColumn;
|
||||||
use Core\Driver\SQL\Column\StringColumn;
|
use Core\Driver\SQL\Column\StringColumn;
|
||||||
use Core\Driver\SQL\Condition\CondIn;
|
use Core\Driver\SQL\Condition\CondIn;
|
||||||
use Core\Driver\SQL\Condition\Condition;
|
|
||||||
use Core\Driver\SQL\Column\DoubleColumn;
|
use Core\Driver\SQL\Column\DoubleColumn;
|
||||||
use Core\Driver\SQL\Column\FloatColumn;
|
use Core\Driver\SQL\Column\FloatColumn;
|
||||||
use Core\Driver\SQL\Condition\CondNot;
|
use Core\Driver\SQL\Condition\CondNot;
|
||||||
@ -329,7 +328,9 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
return $rel_row;
|
return $rel_row;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getValueFromRow(array $row, string $propertyName, mixed &$value, int $fetchEntities = DatabaseEntityQuery::FETCH_NONE): bool {
|
private function getValueFromRow(array $row, string $propertyName, mixed &$value,
|
||||||
|
int $fetchEntities = DatabaseEntityQuery::FETCH_NONE,
|
||||||
|
?DatabaseEntityQueryContext $context = null): bool {
|
||||||
$column = $this->columns[$propertyName] ?? null;
|
$column = $this->columns[$propertyName] ?? null;
|
||||||
if (!$column) {
|
if (!$column) {
|
||||||
return false;
|
return false;
|
||||||
@ -355,12 +356,42 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
if ($relId !== null) {
|
if ($relId !== null) {
|
||||||
if ($fetchEntities !== DatabaseEntityQuery::FETCH_NONE) {
|
if ($fetchEntities !== DatabaseEntityQuery::FETCH_NONE) {
|
||||||
if ($this === $relationHandler) {
|
if ($this === $relationHandler) {
|
||||||
$value = DatabaseEntityQuery::fetchOne($this)
|
|
||||||
|
if ($context) {
|
||||||
|
$value = $context->queryCache($this, $relId);
|
||||||
|
if ($value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$subQuery = DatabaseEntityQuery::fetchOne($this)
|
||||||
->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE)
|
->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE)
|
||||||
->whereEq($this->getColumnName("id"), $relId)
|
->whereEq($this->getColumnName("id"), $relId);
|
||||||
->execute();
|
|
||||||
|
if ($context) {
|
||||||
|
$subQuery->withContext($context);
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $subQuery->execute();
|
||||||
|
if ($value !== null && $context !== null) {
|
||||||
|
$context->addCache($this, $value);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$value = $relationHandler->entityFromRow(self::getPrefixedRow($row, $relColumnPrefix), [], true);
|
$prefixedRow = self::getPrefixedRow($row, $relColumnPrefix);
|
||||||
|
|
||||||
|
if ($context) {
|
||||||
|
$value = $context->queryCache($relationHandler, $prefixedRow["id"]);
|
||||||
|
if ($value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$subFetchMode = $fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE ? $fetchEntities : DatabaseEntityQuery::FETCH_NONE;
|
||||||
|
$value = $relationHandler->entityFromRow($prefixedRow, [], $subFetchMode, $context);
|
||||||
|
|
||||||
|
if ($context && $value) {
|
||||||
|
$context->addCache($relationHandler, $value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@ -385,7 +416,9 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityFromRow(array $row, array $additionalColumns = [], int $fetchEntities = DatabaseEntityQuery::FETCH_NONE): ?DatabaseEntity {
|
public function entityFromRow(array $row, array $additionalColumns = [],
|
||||||
|
int $fetchEntities = DatabaseEntityQuery::FETCH_NONE,
|
||||||
|
?DatabaseEntityQueryContext $context = null): ?DatabaseEntity {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
$constructorClass = $this->entityClass;
|
$constructorClass = $this->entityClass;
|
||||||
@ -397,7 +430,7 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$entity = call_user_func($constructorClass->getName() . "::newInstance", $constructorClass);
|
$entity = call_user_func($constructorClass->getName() . "::newInstance", $constructorClass, $row);
|
||||||
if (!($entity instanceof DatabaseEntity)) {
|
if (!($entity instanceof DatabaseEntity)) {
|
||||||
$this->logger->error("Created Object is not of type DatabaseEntity");
|
$this->logger->error("Created Object is not of type DatabaseEntity");
|
||||||
return null;
|
return null;
|
||||||
@ -405,7 +438,7 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
|
|
||||||
foreach ($this->properties as $property) {
|
foreach ($this->properties as $property) {
|
||||||
$propertyName = $property->getName();
|
$propertyName = $property->getName();
|
||||||
if ($this->getValueFromRow($row, $propertyName, $value, $fetchEntities)) {
|
if ($this->getValueFromRow($row, $propertyName, $value, $fetchEntities, $context)) {
|
||||||
$property->setAccessible(true);
|
$property->setAccessible(true);
|
||||||
$property->setValue($entity, $value);
|
$property->setValue($entity, $value);
|
||||||
}
|
}
|
||||||
@ -542,34 +575,17 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
return $success;
|
return $success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fetchNMRelations(array $entities, int $fetchEntities = DatabaseEntityQuery::FETCH_DIRECT) {
|
public function fetchNMRelations(array $entities, int $fetchEntities = DatabaseEntityQuery::FETCH_DIRECT,
|
||||||
|
?DatabaseEntityQueryContext $context = null) {
|
||||||
|
|
||||||
if ($fetchEntities === DatabaseEntityQuery::FETCH_NONE) {
|
if ($fetchEntities === DatabaseEntityQuery::FETCH_NONE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) {
|
|
||||||
foreach ($entities as $entity) {
|
|
||||||
foreach ($this->relations as $propertyName => $relHandler) {
|
|
||||||
$property = $this->properties[$propertyName];
|
|
||||||
if ($property->isInitialized($entity)) {
|
|
||||||
$relEntity = $this->properties[$propertyName]->getValue($entity);
|
|
||||||
if ($relEntity) {
|
|
||||||
$relHandler->fetchNMRelations([$relEntity->getId() => $relEntity], true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($this->nmRelations)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$entityIds = array_keys($entities);
|
$entityIds = array_keys($entities);
|
||||||
if (empty($entityIds)) {
|
|
||||||
return;
|
// N:M
|
||||||
}
|
if (!empty($this->nmRelations) && !empty($entityIds)) {
|
||||||
|
|
||||||
foreach ($this->nmRelations as $nmProperty => $nmRelation) {
|
foreach ($this->nmRelations as $nmProperty => $nmRelation) {
|
||||||
$nmTable = $nmRelation->getTableName();
|
$nmTable = $nmRelation->getTableName();
|
||||||
@ -596,24 +612,28 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
|
|
||||||
$relEntities = [];
|
$relEntities = [];
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$relId = $row["id"];
|
$relEntity = $context?->queryCache($otherHandler, $row["id"]);
|
||||||
if (!isset($relEntities[$relId])) {
|
if (!$relEntity) {
|
||||||
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities);
|
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities, $context);
|
||||||
$relEntities[$relId] = $relEntity;
|
$context?->addCache($otherHandler, $relEntity);
|
||||||
|
$relEntities[$relEntity->getId()] = $relEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||||
$relEntity = $relEntities[$relId];
|
|
||||||
|
|
||||||
$targetArray = $property->getValue($thisEntity);
|
$targetArray = $property->getValue($thisEntity);
|
||||||
$targetArray[$relEntity->getId()] = $relEntity;
|
$targetArray[$relEntity->getId()] = $relEntity;
|
||||||
$property->setValue($thisEntity, $targetArray);
|
$property->setValue($thisEntity, $targetArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) {
|
||||||
|
$otherHandler->fetchNMRelations($relEntities, $fetchEntities, $context);
|
||||||
|
}
|
||||||
} else if ($nmRelation instanceof NMRelationReference) {
|
} else if ($nmRelation instanceof NMRelationReference) {
|
||||||
|
|
||||||
$otherHandler = $nmRelation->getRelHandler();
|
$otherHandler = $nmRelation->getRelHandler();
|
||||||
$thisIdColumn = $otherHandler->getColumnName($nmRelation->getThisProperty(), false);
|
$thisIdColumn = $otherHandler->getColumnName($nmRelation->getThisProperty(), false);
|
||||||
$relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false);
|
$relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false);
|
||||||
|
|
||||||
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||||
|
|
||||||
@ -627,24 +647,47 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
$thisIdProperty = $otherHandler->properties[$nmRelation->getThisProperty()];
|
$thisIdProperty = $otherHandler->properties[$nmRelation->getThisProperty()];
|
||||||
$thisIdProperty->setAccessible(true);
|
$thisIdProperty->setAccessible(true);
|
||||||
|
|
||||||
|
$relEntities = [];
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities);
|
$relEntity = $context?->queryCache($otherHandler, $row["id"]);
|
||||||
|
if (!$relEntity) {
|
||||||
|
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities, $context);
|
||||||
|
$context?->addCache($otherHandler, $relEntity);
|
||||||
|
$relEntities[$relEntity->getId()] = $relEntity;
|
||||||
|
}
|
||||||
|
|
||||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||||
$thisIdProperty->setValue($relEntity, $thisEntity);
|
$thisIdProperty->setValue($relEntity, $thisEntity);
|
||||||
$targetArray = $property->getValue($thisEntity);
|
$targetArray = $property->getValue($thisEntity);
|
||||||
$targetArray[$row[$relIdColumn]] = $relEntity;
|
$targetArray[$row[$relIdColumn]] = $relEntity;
|
||||||
$property->setValue($thisEntity, $targetArray);
|
$property->setValue($thisEntity, $targetArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) {
|
||||||
|
$otherHandler->fetchNMRelations($relEntities, $fetchEntities, $context);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->logger->error("fetchNMRelations for type '" . get_class($nmRelation) . "' is not implemented");
|
$this->logger->error("fetchNMRelations for type '" . get_class($nmRelation) . "' is not implemented");
|
||||||
continue;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO whats that here lol
|
// if fetch mode is recursive, fetch all relations of our fetched relations as well...
|
||||||
if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) {
|
if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) {
|
||||||
foreach ($entities as $entity) {
|
foreach ($entities as $entity) {
|
||||||
$relEntities = $property->getValue($entity);
|
foreach ($this->relations as $propertyName => $relHandler) {
|
||||||
// $otherHandler->fetchNMRelations($relEntities, $fetchEntities);
|
$property = $this->properties[$propertyName];
|
||||||
|
if ($property->isInitialized($entity)) {
|
||||||
|
$relEntity = $property->getValue($entity);
|
||||||
|
if ($relEntity) {
|
||||||
|
$relEntityId = $relEntity->getId(); // $fetchedEntities
|
||||||
|
$relTableName = $relHandler->getTableName();
|
||||||
|
|
||||||
|
if (!isset($fetchedEntities[$relTableName]) || !isset($fetchedEntities[$relTableName][$relEntityId])) {
|
||||||
|
$relHandler->fetchNMRelations([$relEntityId => $relEntity], DatabaseEntityQuery::FETCH_RECURSIVE, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -663,38 +706,11 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
|
|
||||||
if ($res !== false && $res !== null) {
|
if ($res !== false && $res !== null) {
|
||||||
$res = $this->entityFromRow($res);
|
$res = $this->entityFromRow($res);
|
||||||
if ($res instanceof DatabaseEntity) {
|
|
||||||
$this->fetchNMRelations([$res->getId() => $res]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fetchMultiple(?Condition $condition = null): ?array {
|
|
||||||
$query = $this->getSelectQuery();
|
|
||||||
|
|
||||||
if ($condition) {
|
|
||||||
$query->where($condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
$res = $query->execute();
|
|
||||||
if ($res === false) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
$entities = [];
|
|
||||||
foreach ($res as $row) {
|
|
||||||
$entity = $this->entityFromRow($row);
|
|
||||||
if ($entity) {
|
|
||||||
$entities[$entity->getId()] = $entity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->fetchNMRelations($entities);
|
|
||||||
return $entities;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreateQueries(SQL $sql): array {
|
public function getCreateQueries(SQL $sql): array {
|
||||||
|
|
||||||
$queries = [];
|
$queries = [];
|
||||||
|
@ -7,7 +7,6 @@ use Core\Driver\SQL\Column\Column;
|
|||||||
use Core\Driver\SQL\Expression\Alias;
|
use Core\Driver\SQL\Expression\Alias;
|
||||||
use Core\Driver\SQL\Query\Select;
|
use Core\Driver\SQL\Query\Select;
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\External\PHPMailer\Exception;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this class is similar to \Driver\SQL\Query\Select but with reduced functionality
|
* this class is similar to \Driver\SQL\Query\Select but with reduced functionality
|
||||||
@ -26,6 +25,7 @@ class DatabaseEntityQuery extends Select {
|
|||||||
private array $additionalColumns;
|
private array $additionalColumns;
|
||||||
|
|
||||||
private int $fetchSubEntities;
|
private int $fetchSubEntities;
|
||||||
|
private ?DatabaseEntityQueryContext $context;
|
||||||
|
|
||||||
private function __construct(DatabaseEntityHandler $handler, int $resultType) {
|
private function __construct(DatabaseEntityHandler $handler, int $resultType) {
|
||||||
parent::__construct($handler->getSQL(), ...$handler->getColumnNames());
|
parent::__construct($handler->getSQL(), ...$handler->getColumnNames());
|
||||||
@ -33,6 +33,7 @@ class DatabaseEntityQuery extends Select {
|
|||||||
$this->logger = new Logger("DB-EntityQuery", $handler->getSQL());
|
$this->logger = new Logger("DB-EntityQuery", $handler->getSQL());
|
||||||
$this->resultType = $resultType;
|
$this->resultType = $resultType;
|
||||||
$this->logVerbose = false;
|
$this->logVerbose = false;
|
||||||
|
$this->context = null;
|
||||||
$this->additionalColumns = [];
|
$this->additionalColumns = [];
|
||||||
|
|
||||||
$this->from($handler->getTableName());
|
$this->from($handler->getTableName());
|
||||||
@ -53,6 +54,11 @@ class DatabaseEntityQuery extends Select {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withContext(DatabaseEntityQueryContext $context): self {
|
||||||
|
$this->context = $context;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function addCustomValue(mixed $selectValue): DatabaseEntityQuery {
|
public function addCustomValue(mixed $selectValue): DatabaseEntityQuery {
|
||||||
if (is_string($selectValue)) {
|
if (is_string($selectValue)) {
|
||||||
$this->additionalColumns[] = $selectValue;
|
$this->additionalColumns[] = $selectValue;
|
||||||
@ -89,25 +95,24 @@ class DatabaseEntityQuery extends Select {
|
|||||||
// TODO: clean this up
|
// TODO: clean this up
|
||||||
public function fetchEntities(bool $recursive = false): DatabaseEntityQuery {
|
public function fetchEntities(bool $recursive = false): DatabaseEntityQuery {
|
||||||
|
|
||||||
// $this->selectQuery->dump();
|
|
||||||
$this->fetchSubEntities = ($recursive ? self::FETCH_RECURSIVE : self::FETCH_DIRECT);
|
$this->fetchSubEntities = ($recursive ? self::FETCH_RECURSIVE : self::FETCH_DIRECT);
|
||||||
|
$visited = [$this->handler->getTableName()];
|
||||||
$relIndex = 1;
|
|
||||||
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
||||||
if ($this->handler !== $relationHandler || !$recursive) {
|
$this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler,
|
||||||
$this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler, $relIndex, $recursive);
|
$recursive, "", $visited);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchRelation(string $propertyName, string $tableName, DatabaseEntityHandler $src, DatabaseEntityHandler $relationHandler,
|
private function fetchRelation(string $propertyName, string $tableName, DatabaseEntityHandler $src, DatabaseEntityHandler $relationHandler,
|
||||||
int &$relIndex = 1, bool $recursive = false, string $relationColumnPrefix = "") {
|
bool $recursive = false, string $relationColumnPrefix = "", array &$visited = []) {
|
||||||
|
|
||||||
// TODO: fix recursion here...
|
$relIndex = count($visited);
|
||||||
if ($src === $relationHandler && $recursive) {
|
if (in_array($relationHandler->getTableName(), $visited)) {
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
$visited[] = $relationHandler->getTableName();
|
||||||
}
|
}
|
||||||
|
|
||||||
$columns = $src->getColumns();
|
$columns = $src->getColumns();
|
||||||
@ -117,7 +122,6 @@ class DatabaseEntityQuery extends Select {
|
|||||||
$referencedTable = $relationHandler->getTableName();
|
$referencedTable = $relationHandler->getTableName();
|
||||||
$isNullable = !$foreignColumn->notNull();
|
$isNullable = !$foreignColumn->notNull();
|
||||||
$alias = "t$relIndex"; // t1, t2, t3, ...
|
$alias = "t$relIndex"; // t1, t2, t3, ...
|
||||||
$relIndex++;
|
|
||||||
|
|
||||||
if ($isNullable) {
|
if ($isNullable) {
|
||||||
$this->leftJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias);
|
$this->leftJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias);
|
||||||
@ -132,7 +136,8 @@ class DatabaseEntityQuery extends Select {
|
|||||||
if (!isset($recursiveRelations[$relPropertyName]) || $recursive) {
|
if (!isset($recursiveRelations[$relPropertyName]) || $recursive) {
|
||||||
$this->addValue("$alias.$relColumnName as $relationColumnPrefix$relColumnName");
|
$this->addValue("$alias.$relColumnName as $relationColumnPrefix$relColumnName");
|
||||||
if (isset($recursiveRelations[$relPropertyName]) && $recursive) {
|
if (isset($recursiveRelations[$relPropertyName]) && $recursive) {
|
||||||
$this->fetchRelation($relPropertyName, $alias, $relationHandler, $recursiveRelations[$relPropertyName], $relIndex, $recursive, $relationColumnPrefix);
|
$this->fetchRelation($relPropertyName, $alias, $relationHandler, $recursiveRelations[$relPropertyName],
|
||||||
|
$recursive, $relationColumnPrefix, $visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,22 +158,51 @@ class DatabaseEntityQuery extends Select {
|
|||||||
|
|
||||||
if ($this->resultType === SQL::FETCH_ALL) {
|
if ($this->resultType === SQL::FETCH_ALL) {
|
||||||
$entities = [];
|
$entities = [];
|
||||||
|
$entitiesNM = [];
|
||||||
|
|
||||||
foreach ($res as $row) {
|
foreach ($res as $row) {
|
||||||
$entity = $this->handler->entityFromRow($row, $this->additionalColumns, $this->fetchSubEntities);
|
|
||||||
|
$cached = false;
|
||||||
|
$entity = null;
|
||||||
|
|
||||||
|
if ($this->context) {
|
||||||
|
$entity = $this->context->queryCache($this->handler, $row["id"]);
|
||||||
|
$cached = $entity !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$cached) {
|
||||||
|
$entity = $this->handler->entityFromRow($row, $this->additionalColumns, $this->fetchSubEntities, $this->context);
|
||||||
|
$this->context?->addCache($this->handler, $entity);
|
||||||
|
$entitiesNM[$entity->getId()] = $entity;
|
||||||
|
}
|
||||||
|
|
||||||
if ($entity) {
|
if ($entity) {
|
||||||
$entities[$entity->getId()] = $entity;
|
$entities[$entity->getId()] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->fetchSubEntities !== self::FETCH_NONE) {
|
if (!empty($entitiesNM) && $this->fetchSubEntities !== self::FETCH_NONE) {
|
||||||
$this->handler->fetchNMRelations($entities, $this->fetchSubEntities);
|
$this->handler->fetchNMRelations($entitiesNM, $this->fetchSubEntities, $this->context);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $entities;
|
return $entities;
|
||||||
} else if ($this->resultType === SQL::FETCH_ONE) {
|
} else if ($this->resultType === SQL::FETCH_ONE) {
|
||||||
$entity = $this->handler->entityFromRow($res, $this->additionalColumns, $this->fetchSubEntities);
|
|
||||||
|
$cached = false;
|
||||||
|
$entity = null;
|
||||||
|
|
||||||
|
if ($this->context) {
|
||||||
|
$entity = $this->context->queryCache($this->handler, $res["id"]);
|
||||||
|
$cached = $entity !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$cached) {
|
||||||
|
$entity = $this->handler->entityFromRow($res, $this->additionalColumns, $this->fetchSubEntities, $this->context);
|
||||||
if ($entity instanceof DatabaseEntity && $this->fetchSubEntities !== self::FETCH_NONE) {
|
if ($entity instanceof DatabaseEntity && $this->fetchSubEntities !== self::FETCH_NONE) {
|
||||||
$this->handler->fetchNMRelations([$entity->getId() => $entity], $this->fetchSubEntities);
|
$this->handler->fetchNMRelations([$entity->getId() => $entity], $this->fetchSubEntities, $this->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->context?->addCache($this->handler, $entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $entity;
|
return $entity;
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Objects\DatabaseEntity\Controller;
|
||||||
|
|
||||||
|
class DatabaseEntityQueryContext {
|
||||||
|
|
||||||
|
// tableName => [ entityId => entity ]
|
||||||
|
private array $entityCache;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->entityCache = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function queryCache(DatabaseEntityHandler $handler, int $id): ?DatabaseEntity {
|
||||||
|
$tableName = $handler->getTableName();
|
||||||
|
if (isset($this->entityCache[$tableName])) {
|
||||||
|
return $this->entityCache[$tableName][$id] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addCache(DatabaseEntityHandler $handler, DatabaseEntity $entity): void {
|
||||||
|
$tableName = $handler->getTableName();
|
||||||
|
if (!isset($this->entityCache[$tableName])) {
|
||||||
|
$this->entityCache[$tableName] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityCache[$tableName][$entity->getId()] = $entity;
|
||||||
|
}
|
||||||
|
}
|
@ -81,4 +81,15 @@ abstract class TwoFactorToken extends DatabaseEntity {
|
|||||||
|
|
||||||
return $jsonData;
|
return $jsonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function newInstance(\ReflectionClass $reflectionClass, array $row) {
|
||||||
|
$type = $row["type"] ?? null;
|
||||||
|
if ($type === "totp") {
|
||||||
|
return (new \ReflectionClass(TimeBasedTwoFactorToken::class))->newInstanceWithoutConstructor();
|
||||||
|
} else if ($type === "fido") {
|
||||||
|
return (new \ReflectionClass(KeyBasedTwoFactorToken::class))->newInstanceWithoutConstructor();
|
||||||
|
} else {
|
||||||
|
return parent::newInstance($reflectionClass, $row);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ import React, {useCallback, useContext, useEffect, useState} from "react";
|
|||||||
import "./data-table.css";
|
import "./data-table.css";
|
||||||
import {LocaleContext} from "../locale";
|
import {LocaleContext} from "../locale";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {Box, IconButton} from "@mui/material";
|
import {Box, IconButton, TextField} from "@mui/material";
|
||||||
import {formatDate, formatDateTime} from "../util";
|
import {formatDate, formatDateTime} from "../util";
|
||||||
import CachedIcon from "@material-ui/icons/Cached";
|
import CachedIcon from "@material-ui/icons/Cached";
|
||||||
|
|
||||||
@ -72,6 +72,8 @@ export function DataTable(props) {
|
|||||||
for (const [index, column] of columns.entries()) {
|
for (const [index, column] of columns.entries()) {
|
||||||
if (!(column instanceof DataColumn)) {
|
if (!(column instanceof DataColumn)) {
|
||||||
throw new Error("DataTable can only have DataColumn-objects as column definition, got: " + typeof column);
|
throw new Error("DataTable can only have DataColumn-objects as column definition, got: " + typeof column);
|
||||||
|
} else if (column.hidden) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortable && column.sortable) {
|
if (sortable && column.sortable) {
|
||||||
@ -147,6 +149,7 @@ export class DataColumn {
|
|||||||
this.field = field;
|
this.field = field;
|
||||||
this.sortable = !params.hasOwnProperty("sortable") || !!params.sortable;
|
this.sortable = !params.hasOwnProperty("sortable") || !!params.sortable;
|
||||||
this.align = params.align || "left";
|
this.align = params.align || "left";
|
||||||
|
this.hidden = !!params.hidden;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,8 +170,14 @@ export class StringColumn extends DataColumn {
|
|||||||
renderData(L, entry, index) {
|
renderData(L, entry, index) {
|
||||||
let data = super.renderData(L, entry, index);
|
let data = super.renderData(L, entry, index);
|
||||||
|
|
||||||
|
if (this.params.maxLength && data?.length && data.length > this.params.maxLength) {
|
||||||
|
data = data.substring(0, this.params.maxLength) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
if (this.params.style) {
|
if (this.params.style) {
|
||||||
data = <span style={this.params.style}>{data}</span>
|
let style = (typeof this.params.style === 'function'
|
||||||
|
? this.params.style(entry) : this.params.style);
|
||||||
|
data = <span style={style}>{data}</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
@ -241,6 +250,25 @@ export class BoolColumn extends DataColumn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InputColumn extends DataColumn {
|
||||||
|
constructor(label, field, type, onChange, params = {}) {
|
||||||
|
super(label, field, { ...params, sortable: false });
|
||||||
|
this.type = type;
|
||||||
|
this.onChange = onChange;
|
||||||
|
this.props = params.props || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderData(L, entry, index) {
|
||||||
|
let value = super.renderData(L, entry, index);
|
||||||
|
if (this.type === 'text') {
|
||||||
|
return <TextField {...this.props} size={"small"} fullWidth={true}
|
||||||
|
value={value} onChange={(e) => this.onChange(entry, index, e.target.value)} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>[Invalid type: {this.type}]</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ControlsColumn extends DataColumn {
|
export class ControlsColumn extends DataColumn {
|
||||||
constructor(label, buttons = [], params = {}) {
|
constructor(label, buttons = [], params = {}) {
|
||||||
super(label, null, { align: "center", ...params, sortable: false });
|
super(label, null, { align: "center", ...params, sortable: false });
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Input, TextField
|
Input, List, ListItem, TextField
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
export default function Dialog(props) {
|
export default function Dialog(props) {
|
||||||
@ -57,6 +57,18 @@ export default function Dialog(props) {
|
|||||||
onChange={e => setInputData({ ...inputData, [input.name]: e.target.value })}
|
onChange={e => setInputData({ ...inputData, [input.name]: e.target.value })}
|
||||||
/>)
|
/>)
|
||||||
break;
|
break;
|
||||||
|
case 'list':
|
||||||
|
delete inputProps.items;
|
||||||
|
let listItems = input.items.map((item, index) => <ListItem key={"item-" + index}>{item}</ListItem>);
|
||||||
|
inputElements.push(<Box
|
||||||
|
{...inputProps}
|
||||||
|
sx={{marginTop: 1}}
|
||||||
|
key={"input-" + input.name}
|
||||||
|
>
|
||||||
|
<List>
|
||||||
|
{listItems}
|
||||||
|
</List>
|
||||||
|
</Box>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user