From fb4985160a90072198c7b3d00be191762222278f Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Wed, 21 May 2025 18:46:34 +0000 Subject: [PATCH] - WIP on Migration of MsSQL --- src/Adapter/DefaultAdapterTrait.php | 121 ------------------------ src/Adapter/MsSQL.php | 12 ++- src/Adapter/MySQL.php | 22 +++++ src/Attribute/Property/Filter.php | 3 +- src/Attribute/Property/Join.php | 17 ++++ src/Attribute/Property/Relation.php | 18 ++++ src/Entity/DatasetHandler.php | 13 ++- src/Entity/InformationSchema/Column.php | 15 ++- src/Entity/Mssql/Column.php | 73 ++++++++++++++ src/Entity/Mssql/Table.php | 17 ++++ src/EntityTrait.php | 5 +- src/Migration/SqlMigrationTrait.php | 10 -- src/Repository.php | 61 +++++++----- src/Repository/MssqlRepository.php | 22 ++++- src/Repository/MysqlRepository.php | 10 +- src/Repository/RelationBuilder.php | 7 +- src/Repository/SqliteRepository.php | 1 - 17 files changed, 252 insertions(+), 175 deletions(-) delete mode 100644 src/Adapter/DefaultAdapterTrait.php create mode 100644 src/Entity/Mssql/Column.php create mode 100644 src/Entity/Mssql/Table.php diff --git a/src/Adapter/DefaultAdapterTrait.php b/src/Adapter/DefaultAdapterTrait.php deleted file mode 100644 index 68b1cde..0000000 --- a/src/Adapter/DefaultAdapterTrait.php +++ /dev/null @@ -1,121 +0,0 @@ - "AUTO_INCREMENT", - 'pk' => "PRIMARY KEY", - 'unsigned' => "UNSIGNED", - ]; - } - - public function databaseName() : string - { - return $this->database; - } - - public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object - { - return Table::repository(Repository::DEFAULT_ALIAS, $adapter) - ->select(\Ulmus\Common\Sql::raw('this.*')) - ->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName) - ->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName); - } - - public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string - { - $type = $field->type; - - $length = $field->length; - - if ( is_a($type, Entity\Field\Date::class, true) ) { - $type = "DATE"; - } - elseif ( is_a($type, Entity\Field\Time::class, true) ) { - $type = "TIME"; - } - elseif ( is_a($type, \DateTime::class, true) ) { - $type = "DATETIME"; - } - - switch($type) { - case "bool": - $type = "TINYINT"; - $length = 1; - break; - - case "array": - case "string": - if ($length && $length <= 255) { - $type = "VARCHAR"; - break; - } - elseif (! $length || ( $length <= 65535 ) ) { - $type = "TEXT"; - } - elseif ( $length <= 16777215 ) { - $type = "MEDIUMTEXT"; - } - elseif ($length <= 4294967295) { - $type = "LONGTEXT"; - } - else { - throw new \Exception("A column with size bigger than 4GB cannot be created."); - } - - # Length is unnecessary on TEXT fields - unset($length); - - break; - - case "float": - $type = "DOUBLE"; - break; - - default: - $type = strtoupper($type); - break; - } - - return $typeOnly ? $type : $type . ( isset($length) ? "($length" . ( ! empty($precision) ? ",$precision" : "" ) . ")" : "" ); - } - - public function whitelistAttributes(array &$parameters) : void - { - $parameters = array_intersect_key($parameters, array_flip(static::ALLOWED_ATTRIBUTES)); - } - - public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable - { - if ($field['previous']) { - $position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']['field'], AdapterInterface::IDENTIFIER_FIELD)); - } - else { - $position = "FIRST"; - } - - return implode(" ", [ - strtoupper($field['action']), - $this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD), - $definition->getSqlType(), - $definition->getSqlParams(), - $position, - ]); - } -} diff --git a/src/Adapter/MsSQL.php b/src/Adapter/MsSQL.php index fc4b505..1fba020 100644 --- a/src/Adapter/MsSQL.php +++ b/src/Adapter/MsSQL.php @@ -9,7 +9,8 @@ use Ulmus\Exception\AdapterConfigurationException; use Ulmus\Ulmus; use Ulmus\Migration\FieldDefinition; -use Ulmus\{Entity\InformationSchema\Table, +use Ulmus\{ConnectionAdapter, + Entity\InformationSchema\Table, Migration\MigrateInterface, Migration\SqlMigrationTrait, Repository, @@ -246,4 +247,13 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { { return QueryBuilder\Sql\MssqlQueryBuilder::class; } + + public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object + { + return \Ulmus\Entity\Mssql\Table::repository(Repository::DEFAULT_ALIAS, $adapter) + ->select(\Ulmus\Common\Sql::raw('this.*')) + ->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName) + ->or($this->escapeIdentifier('table_catalog', AdapterInterface::IDENTIFIER_FIELD), $databaseName) + ->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName); + } } diff --git a/src/Adapter/MySQL.php b/src/Adapter/MySQL.php index cabd2c9..62e7add 100644 --- a/src/Adapter/MySQL.php +++ b/src/Adapter/MySQL.php @@ -181,4 +181,26 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { ->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName) ->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName); } + + + public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable + { + if ($field['action'] === 'add') { + if (! empty($field['previous'])) { + $position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']->field, AdapterInterface::IDENTIFIER_FIELD)); + } + else { + $position = "FIRST"; + } + } + + return implode(" ", array_filter([ + strtoupper($field['action']), + $this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD), + $definition->getSqlType(), + $definition->getSqlParams(), + $position ?? null, + ])); + } + } diff --git a/src/Attribute/Property/Filter.php b/src/Attribute/Property/Filter.php index 816b6f8..5e91725 100644 --- a/src/Attribute/Property/Filter.php +++ b/src/Attribute/Property/Filter.php @@ -6,6 +6,5 @@ namespace Ulmus\Attribute\Property; class Filter { public function __construct( public string $method = "" - ) - {} + ) {} } \ No newline at end of file diff --git a/src/Attribute/Property/Join.php b/src/Attribute/Property/Join.php index 6d825fd..817cc92 100644 --- a/src/Attribute/Property/Join.php +++ b/src/Attribute/Property/Join.php @@ -3,6 +3,7 @@ namespace Ulmus\Attribute\Property; use Ulmus\Attribute\Attribute; +use Ulmus\Repository; #[\Attribute] class Join implements ResettablePropertyInterface { @@ -16,4 +17,20 @@ class Join implements ResettablePropertyInterface { $this->key = Attribute::handleArrayField($this->key); $this->foreignKey = Attribute::handleArrayField($this->foreignKey); } + + ## AWAITING THE Closures in constant expression from PHP 8.5 ! + public function foreignKey(? Repository $repository = null) : mixed + { + if (is_string($this->foreignKey)) { + if (str_starts_with($this->foreignKey, 'fn(') && str_contains($this->foreignKey, '=>')) { + $closure = eval("return {$this->foreignKey};"); + + return $repository ? $closure($repository) : $closure; + } + + return $this->entity::field($this->foreignKey, $this->alias); + } + + return $this->foreignKey; + } } diff --git a/src/Attribute/Property/Relation.php b/src/Attribute/Property/Relation.php index 9aca8cc..1820e1a 100644 --- a/src/Attribute/Property/Relation.php +++ b/src/Attribute/Property/Relation.php @@ -3,6 +3,7 @@ namespace Ulmus\Attribute\Property; use Ulmus\Attribute\Attribute; +use Ulmus\Repository; #[\Attribute(\Attribute::TARGET_PROPERTY)] class Relation implements ResettablePropertyInterface { @@ -21,6 +22,7 @@ class Relation implements ResettablePropertyInterface { public null|string $entity = null, public null|string $join = null, public null|string $function = null, + public null|string $alias = null, ) { $this->key = Attribute::handleArrayField($this->key); $this->foreignKey = Attribute::handleArrayField($this->foreignKey); @@ -83,4 +85,20 @@ class Relation implements ResettablePropertyInterface { { return (bool) $this->bridge; } + + ## AWAITING THE Closures in constant expression from PHP 8.5 ! + public function foreignKey(? Repository $repository = null) : mixed + { + if (is_string($this->foreignKey)) { + if (str_starts_with($this->foreignKey, 'fn(') && str_contains($this->foreignKey, '=>')) { + $closure = eval("return {$this->foreignKey};"); + + return $repository ? $closure($repository) : $closure; + } + + return $this->entity::field($this->foreignKey, $this->alias); + } + + return $this->foreignKey; + } } diff --git a/src/Entity/DatasetHandler.php b/src/Entity/DatasetHandler.php index 1cde76a..1a3b32b 100644 --- a/src/Entity/DatasetHandler.php +++ b/src/Entity/DatasetHandler.php @@ -111,15 +111,17 @@ class DatasetHandler return $unmatched; } - public function pullRelation(object $entity) : Generator + public function pullRelation(object $entity, bool $onlyPreviouslyLoaded = true) : Generator { - foreach($this->entityResolver->reflectedClass->getProperties(true) as $name => $field){ + foreach($this->entityResolver->reflectedClass->getProperties(true) as $name => $field) { $relation = $this->entityResolver->searchFieldAnnotation($name, [ Relation::class ] ); if ($relation) { $ignore = $this->entityResolver->searchFieldAnnotation($name, [ Relation\Ignore::class ] ); - if ($ignore && $ignore->ignoreExport) { + $load = $onlyPreviouslyLoaded ? (new \ReflectionProperty($entity::class, $name))->isInitialized($entity) : true; + + if ( ! $load || ( $ignore && $ignore->ignoreExport ) ) { if ( $relation->isOneToOne() ) { # @TODO TO INCLUDED INTO getTypes() RETURNED CLASS WHEN DONE ! yield $name => ( new \ReflectionClass($field->getTypes()[0]) )->newInstanceWithoutConstructor(); @@ -132,9 +134,10 @@ class DatasetHandler continue; } - # @TODO Must fix recursive bug.. this last check is way too basic to work if ( $entity->__isset($name) && ($relation->entity ?? $relation->bridge) !== static::class ) { - if ( null !== $value = $entity->__isset($name) ?? null ) { + $value = $entity->$name ?? null; + + if ( null !== $value ) { if ( is_iterable($value) ) { $list = []; diff --git a/src/Entity/InformationSchema/Column.php b/src/Entity/InformationSchema/Column.php index fc427b4..914f676 100644 --- a/src/Entity/InformationSchema/Column.php +++ b/src/Entity/InformationSchema/Column.php @@ -69,16 +69,19 @@ class Column { $nullable = $this->nullable === 'YES'; + $definitionValue = $this->getDefinitionValue($definition); + if ($nullable !== $definition->allowsNull()) { return false; } - if ( isset($definition->value) && $this->canHaveDefaultValue() ) { - if ( $definition->value !== $this->defaultValue()) { + if ( isset($definitionValue) && $this->canHaveDefaultValue() ) { + if ( $definitionValue !== $this->defaultValue() ) { + # dump($definition->value, $this->defaultValue(), $definition->name); return false; } } - elseif (! isset($definition->value)) { + elseif (! isset($definitionValue)) { if ( ! $this->defaultValueIsNull() ) { return false; } @@ -87,6 +90,12 @@ class Column return true; } + protected function getDefinitionValue(ReflectedProperty $definition) : mixed + { + # Attribute's value first, then defined in class value + return $definition->getAttribute(Field::class)->object->default ?? $definition->value ?? null; + } + protected function defaultValueIsNull() : bool { return $this->defaultValue() === null; diff --git a/src/Entity/Mssql/Column.php b/src/Entity/Mssql/Column.php new file mode 100644 index 0000000..b51723d --- /dev/null +++ b/src/Entity/Mssql/Column.php @@ -0,0 +1,73 @@ +dataType), [ + # 'BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB', 'JSON', 'TEXT', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'GEOMETRY' + ]); + } + + protected function defaultValue() : mixed + { + if ($this->default === null) { + return null; + } + + # Removing first pairs of brackets + $default = $this->isBetween($this->default, '(', ')') ? substr($this->default, 1, -1) : $this->default; + + if ($default === "NULL") { + return null; + } + + # Checking if another pairs of brackets surrounds the value + if ($this->isBetween($default, '(', ')')) { + $default = substr($default, 1, -1); + + if (is_numeric($default)) { + return (int) $default; + } + } + elseif ($this->isBetween($default, "'", "'")) { + $default = new class($default) implements \Stringable { + public function __construct( + private readonly string $default + ) {} + + public function __toString(): string + { + return substr($this->default, 1, -1);; + } + }; + } + else { + $default = new class($default) implements \Stringable { + public function __construct( + private readonly string $default + ) {} + + public function __toString(): string + { + return $this->default; + } + }; + } + + return (string) $default; + } + + private function isBetween(string $str, string $first, string $last) : bool + { + return str_starts_with($str, $first) && str_ends_with($str, $last); + } + +} \ No newline at end of file diff --git a/src/Entity/Mssql/Table.php b/src/Entity/Mssql/Table.php new file mode 100644 index 0000000..2014f71 --- /dev/null +++ b/src/Entity/Mssql/Table.php @@ -0,0 +1,17 @@ +escapeIdentifier($field['previous']->field, AdapterInterface::IDENTIFIER_FIELD)); - } - else { - $position = "FIRST"; - } - } - return implode(" ", array_filter([ strtoupper($field['action']), $this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD), $definition->getSqlType(), $definition->getSqlParams(), - $position ?? null, ])); } diff --git a/src/Repository.php b/src/Repository.php index 937f424..6b7acaf 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -438,19 +438,19 @@ class Repository implements RepositoryInterface } if ( $attribute ) { - $alias = $attribute->alias ?? $item; + $attribute->alias ??= $item; - $entity = $attribute->entity ?? $this->entityResolver->getPropertyEntityType($item); + $attribute->entity ??= $this->entityResolver->getPropertyEntityType($item); - foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { - if ( null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ]) ) { - $escAlias = $this->escapeIdentifier($alias); + foreach($attribute->entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { + if ( null === $attribute->entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ]) ) { + $escAlias = $this->escapeIdentifier($attribute->alias); $fieldName = $this->escapeIdentifier($key); - $name = $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Field::class ])->name ?? $field->name; + $name = $attribute->entity::resolveEntity()->searchFieldAnnotation($field->name, [ Field::class ])->name ?? $field->name; if ($canSelect) { - $this->select("$escAlias.$fieldName as $alias\${$name}"); + $this->select("$escAlias.$fieldName as {$attribute->alias}\${$name}"); } } } @@ -459,15 +459,15 @@ class Repository implements RepositoryInterface if ( ! in_array(WithOptionEnum::SkipWhere, $options)) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ] ) as $condition) { - if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) { - $this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); + if ( is_object($condition->field) && ( $condition->field->entityClass !== $attribute->entity ) ) { + $this->where(is_object($condition->field) ? $condition->field : $attribute->entity::field($condition->field), $condition->getValue(), $condition->operator); } } } if ( ! in_array(WithOptionEnum::SkipHaving, $options)) { foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ]) as $condition) { - $this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); + $this->having(is_object($condition->field) ? $condition->field : $attribute->entity::field($condition->field), $condition->getValue(), $condition->operator); } } @@ -480,9 +480,9 @@ class Repository implements RepositoryInterface $this->close(); $key = is_string($attribute->key) ? $this->entityClass::field($attribute->key) : $attribute->key; - $foreignKey = $this->evalClosure($attribute->foreignKey, $entity, $alias); + $foreignKey = $this->evalClosure($attribute->foreignKey, $attribute->entity, $attribute->alias); - $this->join($isRelation ? "LEFT" : $attribute->type, $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) { + $this->join($isRelation ? "LEFT" : $attribute->type, $attribute->entity::resolveEntity()->tableName(), $key, $attribute->foreignKey($this), $attribute->alias, function($join) use ($item, $attribute, $options) { if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) { if ( ! is_object($condition->field) ) { @@ -493,10 +493,10 @@ class Repository implements RepositoryInterface } # Adding directly - if ( $field->entityClass === $entity ) { - $field->alias = $alias; + if ( $field->entityClass === $attribute->entity ) { + $field->alias = $attribute->alias; - $join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->getValue(), $condition->operator); + $join->where(is_object($field) ? $field : $attribute->entity::field($field, $attribute->alias), $condition->getValue(), $condition->operator); } } } @@ -536,7 +536,7 @@ class Repository implements RepositoryInterface # Apply FILTER annotation to this too ! foreach(array_filter((array) $fields) as $item) { if ( $relation = $this->entityResolver->searchFieldAnnotation($item, [ Relation::class ]) ) { - $alias = $relation->alias ?? $item; + $relation->alias ??= $item; if ( $relation->isManyToMany() ) { $entity = $relation->bridge; @@ -545,13 +545,14 @@ class Repository implements RepositoryInterface extract(Repository\RelationBuilder::relationAnnotations($item, $relation)); - $repository->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $relation->bridgeField), $relationRelation->entity::field($relationRelation->foreignKey, $alias), $relation->bridgeField) + $repository->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $relation->bridgeField), $relationRelation->entity::field($relationRelation->foreignKey, $relation->alias), $relation->bridgeField) ->where( $entity::field($bridgeRelation->key, $relation->bridgeField), $entity::field($bridgeRelation->foreignKey, $this->alias)) - ->selectJsonEntity($relationRelation->entity, $alias)->open(); + ->selectJsonEntity($relationRelation->entity, $relation->alias)->open(); } else { - $entity = $relation->entity ?? $this->entityResolver->getPropertyEntityType($name); - $repository = $entity::repository()->selectJsonEntity($entity, $alias)->open(); + $relation->entity ??= $this->entityResolver->getPropertyEntityType($name); + $entity = $relation->entity; + $repository = $relation->entity::repository()->selectJsonEntity($entity, $relation->alias)->open(); } # $relation->isManyToMany() and $repository->selectJsonEntity($relation->bridge, $relation->bridgeField, true); @@ -560,16 +561,21 @@ class Repository implements RepositoryInterface $repository->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); } + foreach($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ] ) as $condition) { $repository->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); } + foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Filter::class ]) as $filter) { + call_user_func_array([$this->entityClass, $filter->method], [$this, $item, true]); + } + $repository->close(); $key = is_string($relation->key) ? $this->entityClass::field($relation->key) : $relation->key; if (! $relation->isManyToMany() ) { - $foreignKey = is_string($relation->foreignKey) ? $entity::field($relation->foreignKey, $alias) : $relation->foreignKey; + $foreignKey = is_string($relation->foreignKey) ? $entity::field($relation->foreignKey, $relation->alias) : $relation->foreignKey; $repository->where( $foreignKey, $key); } @@ -590,6 +596,7 @@ class Repository implements RepositoryInterface if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, [ Relation::class ] ))) { $order = $this->entityResolver->searchFieldAnnotationList($name, [ OrderBy::class ]); $where = $this->entityResolver->searchFieldAnnotationList($name, [ Where::class ]); + $filters = $this->entityResolver->searchFieldAnnotationList($name, [ Filter::class ]); $baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->getPropertyEntityType($name); $baseEntityResolver = $baseEntity::resolveEntity(); @@ -610,6 +617,10 @@ class Repository implements RepositoryInterface $repository->where($condition->field, $condition->getValue(/* why repository sent here ??? $this */), $condition->operator, $condition->condition); } + foreach ($filters as $filter) { + call_user_func_array([ $this->entityClass, $filter->method ], [ $repository, $name, true ]); + } + foreach ($order as $item) { $repository->orderBy($item->field, $item->order); } @@ -628,7 +639,8 @@ class Repository implements RepositoryInterface $repository->where($key, $values); - $results = $repository->loadAll(); + $loadMethod = $relation->isOneToOne() ? 'loadAll' : $relation->function(); + $results = $repository->{$loadMethod}(); if ($relation->isOneToOne()) { foreach ($collection as $item) { @@ -638,7 +650,8 @@ class Repository implements RepositoryInterface elseif ($relation->isOneToMany()) { foreach ($collection as $item) { $item->$name = $baseEntity::entityCollection(); - $item->$name->mergeWith($results->searchAll($item->$entityProperty, $property)); + $search = $results->searchAll($item->$entityProperty, $property); + $item->$name->mergeWith($search); } } } @@ -646,7 +659,7 @@ class Repository implements RepositoryInterface } ## AWAITING THE Closures in constant expression from PHP 8.5 ! - protected function evalClosure(\Stringable|string $content, string $entityClass, mixed $alias = self::DEFAULT_ALIAS) : mixed + protected function evalClosure(mixed $content, string $entityClass, mixed $alias = self::DEFAULT_ALIAS) : mixed { if (is_string($content)) { if ( str_starts_with($content, 'fn(') ) { diff --git a/src/Repository/MssqlRepository.php b/src/Repository/MssqlRepository.php index 2e3267c..49aceb1 100644 --- a/src/Repository/MssqlRepository.php +++ b/src/Repository/MssqlRepository.php @@ -2,19 +2,35 @@ namespace Ulmus\Repository; -use Ulmus\{Repository, Query, Ulmus, Common}; +use Ulmus\{EntityCollection, Repository, Query, Entity, Ulmus, Common}; class MssqlRepository extends Repository { protected function finalizeQuery() : void { - if ( $this->queryBuilder->getFragment(Query\MsSQL\Offset::class) ) { - if (null === $order = $this->queryBuilder->getFragment(Query\OrderBy::class)) { + $delete = $this->queryBuilder->getFragment(Query\Delete::class); + $offset = $this->queryBuilder->getFragment(Query\MsSQL\Offset::class); + + if ($offset) { + if ( $delete ) { + $delete->top = $offset->limit; + + $this->queryBuilder->removeFragment(Query\MsSQL\Offset::class); + } + elseif (null === $this->queryBuilder->getFragment(Query\OrderBy::class)) { $this->orderBy("(SELECT 0)"); } } } + public function listColumns(? string $table = null) : EntityCollection + { + $table ??= $this->entityResolver->tableName(); + $this->showColumnsSqlQuery($table); + + return $this->collectionFromQuery(Entity\Mssql\Column::class)->iterate(fn($e) => $e->tableName = $table); + } + protected function serverRequestCountRepository() : Repository { return new static($this->entityClass, $this->alias, $this->adapter); diff --git a/src/Repository/MysqlRepository.php b/src/Repository/MysqlRepository.php index 0d5f31f..65cc74f 100644 --- a/src/Repository/MysqlRepository.php +++ b/src/Repository/MysqlRepository.php @@ -2,7 +2,7 @@ namespace Ulmus\Repository; -use Ulmus\{ Repository, Query, Ulmus}; +use Ulmus\{EntityCollection, Repository, Query, Entity, Ulmus}; class MysqlRepository extends Repository { use JsonConditionTrait; @@ -14,6 +14,14 @@ class MysqlRepository extends Repository { return $this; } + public function listColumns(? string $table = null) : EntityCollection + { + $table ??= $this->entityResolver->tableName(); + $this->showColumnsSqlQuery($table); + + return $this->collectionFromQuery(Entity\Mysql\Column::class)->iterate(fn($e) => $e->tableName = $table); + } + public function createSqlQuery() : self { if ( null === $this->queryBuilder->getFragment(Query\Engine::class) ) { diff --git a/src/Repository/RelationBuilder.php b/src/Repository/RelationBuilder.php index 3d368a3..cb69b3f 100644 --- a/src/Repository/RelationBuilder.php +++ b/src/Repository/RelationBuilder.php @@ -262,9 +262,9 @@ class RelationBuilder public function oneToMany(string $name, Relation $relation) : Repository { - $baseEntity = $relation->entity ?? $this->resolver->reflectedClass->getProperties()[$name]->getTypes()[0]->type; + $relation->entity ??= $this->resolver->reflectedClass->getProperties()[$name]->getTypes()[0]->type; - $this->repository = $baseEntity::repository(); + $this->repository = $relation->entity::repository(); $this->applyWhere(); @@ -281,7 +281,8 @@ class RelationBuilder } if (isset($value)) { - $this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey), $value ); + #$this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $relation->entity::field($relation->foreignKey), $value ); + $this->repository->where( $relation->foreignKey($this->repository), $value ); } else { $this->repository->where(Sql::raw('TRUE'), Sql::raw('FALSE')); diff --git a/src/Repository/SqliteRepository.php b/src/Repository/SqliteRepository.php index 361c608..0bb466c 100644 --- a/src/Repository/SqliteRepository.php +++ b/src/Repository/SqliteRepository.php @@ -27,7 +27,6 @@ class SqliteRepository extends Repository { return $this->collectionFromQuery(Entity\Sqlite\Column::class)->iterate(fn($e) => $e->tableName = $table); } - protected function serverRequestCountRepository() : Repository { return new static($this->entityClass, $this->alias, $this->adapter);