From e1990e7cc07db825e072e1267db0588c3f678a49 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 21 Jun 2022 15:38:06 +0000 Subject: [PATCH] - Merge with Mssql repository bugfix --- src/Adapter/MsSQL.php | 15 +++-- src/Adapter/SQLite.php | 10 ++++ src/EntityCollection.php | 5 ++ src/EntityTrait.php | 6 +- src/QueryBuilder.php | 4 ++ src/QueryBuilder/MssqlQueryBuilder.php | 10 ++-- src/Repository.php | 38 +++++++----- src/Repository/EscapeTrait.php | 5 ++ src/Repository/MssqlRepository.php | 59 +------------------ src/Repository/RelationBuilder.php | 6 +- .../SearchRequestPaginationTrait.php | 24 +++++++- 11 files changed, 95 insertions(+), 87 deletions(-) diff --git a/src/Adapter/MsSQL.php b/src/Adapter/MsSQL.php index 879606e..fa4df77 100644 --- a/src/Adapter/MsSQL.php +++ b/src/Adapter/MsSQL.php @@ -92,16 +92,19 @@ class MsSQL implements AdapterInterface { $parts[] = "Server={$this->server}" . ( isset($this->port) ? ",{$this->port}" : "" ); $parts[] = "Database={$this->database}"; $parts[] = "ConnectionPooling={$this->connectionPooling}"; - + if ( $this->app ?? false ) { $parts[] = "APP={$this->app}"; } - + if ( $this->encrypt ?? false ) { $parts[] = "Encrypt=1"; } + else { + $parts[] = "Encrypt=0"; + } - if ( $this->failoverPartner ?? false ) { + if ( $this->failoverPartner ?? false ) { $parts[] = "Failover_Partner={$this->failoverPartner}"; } @@ -130,7 +133,7 @@ class MsSQL implements AdapterInterface { } if ( $this->trustServerCertificate ?? false ) { - $parts[] = "TrustServerCertificate=1"; + $parts[] = "TrustServerCertificate=yes"; } if ( $this->WSID ?? false ) { @@ -235,4 +238,8 @@ class MsSQL implements AdapterInterface { return Repository\MssqlRepository::class; } + public function queryBuilderClass() : string + { + return QueryBuilder\MssqlQueryBuilder::class; + } } diff --git a/src/Adapter/SQLite.php b/src/Adapter/SQLite.php index dd89b0d..7026e14 100644 --- a/src/Adapter/SQLite.php +++ b/src/Adapter/SQLite.php @@ -137,4 +137,14 @@ class SQLite implements AdapterInterface { 'unsigned' => "", ]; } + + public function repositoryClass() : string + { + return Repository\SqliteRepository::class; + } + + public function queryBuilderClass() : string + { + return QueryBuilder\SqliteQueryBuilder::class; + } } diff --git a/src/EntityCollection.php b/src/EntityCollection.php index 75b9e3f..44fc72d 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -44,6 +44,11 @@ class EntityCollection extends \ArrayObject { } } + public function filtersArray(Callable $callback) : array + { + return $this->filtersCollection($callback, true, false)->toArray(); + } + public function filtersOne(Callable $callback) : ? object { foreach($this->filters($callback, true) as $item) { diff --git a/src/EntityTrait.php b/src/EntityTrait.php index d851fb6..7e3edb8 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -204,6 +204,10 @@ trait EntityTrait { */ public function isLoaded() : bool { + if (empty($this->entityLoadedDataset)) { + return false; + } + if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) { throw new Exception\EntityPrimaryKeyUnknown(sprintf("Entity %s has no field containing attributes 'primary_key'", static::class)); } @@ -315,7 +319,7 @@ trait EntityTrait { */ public static function field($name, ? string $alias = null) : EntityField { - return new EntityField(static::class, $name, $alias ?: Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class)); + return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class)); } /** diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 10263db..0ec401e 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -247,6 +247,10 @@ class QueryBuilder implements Query\QueryBuilderInterface $limit->set($value); + if ($value === 0) { + $this->removeFragment(Query\Limit::class); + } + return $this; } diff --git a/src/QueryBuilder/MssqlQueryBuilder.php b/src/QueryBuilder/MssqlQueryBuilder.php index e139517..bd8e4f7 100644 --- a/src/QueryBuilder/MssqlQueryBuilder.php +++ b/src/QueryBuilder/MssqlQueryBuilder.php @@ -2,11 +2,9 @@ namespace Ulmus\QueryBuilder; -use Ulmus\QueryBuilder; +use Ulmus\{Query, QueryBuilder }; -use Ulmus\Query; - -class MssqlQueryBuilder extends QueryBuilder implements Ulmus\Query\QueryBuilderInterface +class MssqlQueryBuilder extends QueryBuilder implements Query\QueryBuilderInterface { public function limit(int $value) : self { @@ -17,6 +15,10 @@ class MssqlQueryBuilder extends QueryBuilder implements Ulmus\Query\QueryBuilder $offset->limit = $value; + if ($value === 0) { + $this->removeFragment(Query\MsSQL\Offset::class); + } + return $this; } diff --git a/src/Repository.php b/src/Repository.php index 795ce36..28d201d 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -68,7 +68,7 @@ class Repository public function count() : int { - $this->removeQueryFragment(Query\Select::class); + $this->removeQueryFragment([ Query\Select::class, Query\OrderBy::class, ]); if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) { $this->select( "DISTINCT COUNT(*) OVER ()" ); @@ -149,16 +149,20 @@ class Repository $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery(); - if ( ( 0 !== $statement->lastInsertId ) && - ( null !== $primaryKeyDefinition )) { - + if ( null !== $primaryKeyDefinition ) { $pkField = key($primaryKeyDefinition); - $dataset[$pkField] = $statement->lastInsertId; + + if ($statement->lastInsertId ) { + $dataset[$pkField] = $statement->lastInsertId; + } + elseif ($replace) { + $pkValue = $dataset[$pkField]; + } } $entity->entityFillFromDataset($dataset, true); - return (bool) $statement->lastInsertId; + return (bool) ($pkValue ?? $statement->lastInsertId); } else { if ( $primaryKeyDefinition === null ) { @@ -270,7 +274,7 @@ class Repository { $schema = $schema ?: $this->entityResolver->schemaName(); - $this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $alias ?: $this->alias, $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null); + $this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $this->escapeIdentifier($alias ?: $this->alias), $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null); $this->finalizeQuery(); @@ -336,7 +340,7 @@ class Repository foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { if (null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore)) { - $this->select("$alias.$key as {$prependField}{$field['name']}"); + $this->select(sprintf("%s.$key as {$prependField}{$field['name']}", $this->escapeIdentifier($alias))); } } @@ -350,7 +354,7 @@ class Repository foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { if (null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore)) { $fieldlist[] = $key; - $fieldlist[] = $entity::field($field['name'], $alias); + $fieldlist[] = $entity::field($field['name'], $this->escapeIdentifier($alias)); } } @@ -380,7 +384,7 @@ class Repository public function insert(array $fieldlist, string $table, string $alias, ? string $schema, bool $replace = false) : self { - $this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $alias, $this->escapedDatabase(), $schema, $replace); + $this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $this->escapeIdentifier($alias), $this->escapedDatabase(), $schema, $replace); return $this; } @@ -394,7 +398,7 @@ class Repository public function update(string $table, string $alias, ? string $schema) : self { - $this->queryBuilder->update($this->escapeTable($table), $alias, $this->escapedDatabase(), $schema); + $this->queryBuilder->update($this->escapeTable($table), $this->escapeIdentifier($alias), $this->escapedDatabase(), $schema); return $this; } @@ -418,13 +422,13 @@ class Repository public function from(string $table, ? string $alias, ? string $schema) : self { - $this->queryBuilder->from($this->escapeTable($table), $alias, $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null); + $this->queryBuilder->from($this->escapeTable($table), $this->escapeIdentifier($alias), $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null); return $this; } public function join(string $type, $table, $field, $value, ? string $alias = null, ? callable $callback = null) : self { - $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $alias); + $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $this->escapeIdentifier($alias)); if ( $callback ) { $callback($join); @@ -435,7 +439,7 @@ class Repository public function outerJoin(string $type, $table, $field, $value, ? string $alias = null, ? callable $callback = null) : self { - $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $alias); + $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $this->escapeIdentifier($alias)); if ( $callback ) { $callback($join); @@ -563,7 +567,9 @@ class Repository foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { if ( null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore) ) { - $this->select("$alias.$key as {$alias}\${$field['name']}"); + $escAlias = $this->escapeIdentifier($alias); + + $this->select("$escAlias.$key as $alias\${$field['name']}"); } } @@ -626,7 +632,7 @@ class Repository } if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) { - $this->select("{$this->alias}.*"); + $this->select($this->escapeIdentifier($this->alias) . ".*"); } # Apply FILTER annotation to this too ! diff --git a/src/Repository/EscapeTrait.php b/src/Repository/EscapeTrait.php index eb69ab7..6269f89 100644 --- a/src/Repository/EscapeTrait.php +++ b/src/Repository/EscapeTrait.php @@ -7,6 +7,11 @@ use Ulmus\Annotation\Property\Field; trait EscapeTrait { + public function escapeIdentifier(string $identifier) : string + { + return $this->escapeField($identifier); + } + public function escapeField(string $identifier) : string { return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_FIELD); diff --git a/src/Repository/MssqlRepository.php b/src/Repository/MssqlRepository.php index ed388e7..629b46e 100644 --- a/src/Repository/MssqlRepository.php +++ b/src/Repository/MssqlRepository.php @@ -5,66 +5,11 @@ namespace Ulmus\Repository; use Ulmus\{Repository, Query, Ulmus, Common}; class MssqlRepository extends Repository { -/* - protected function finalizeQuery() : void - { - if ( null !== $offset = $this->queryBuilder->getFragment(Query\Offset::class) ) { - if ( null === $limit = $this->queryBuilder->getFragment(Query\Limit::class) ) { - throw new \Exception("Your offset query fragment is missing a LIMIT value."); - } - - # an order by is mandatory for mssql offset/limit - if ( null === $order = $this->queryBuilder->getFragment(Query\OrderBy::class) ) { - $this->orderBy("(SELECT 0)"); - } - - $mssqlOffset = new \Ulmus\Query\MsSQL\Offset(); - - $mssqlOffset->set($offset->offset, $limit->limit); - - $this->queryBuilder->removeFragment($offset); - $this->queryBuilder->removeFragment($limit); - - $this->queryBuilder->push($mssqlOffset); - } - elseif ( null !== $limit = $this->queryBuilder->getFragment(Query\Limit::class) ) { - - if ( null !== $select = $this->queryBuilder->getFragment(Query\Select::class) ) { - $select->top = $limit->limit; - } - elseif ( null !== $delete = $this->queryBuilder->getFragment(Query\Delete::class) ) { - $delete->top = $limit->limit; - } - - $this->queryBuilder->removeFragment($limit); - } - } -*/ protected function finalizeQuery() : void { - if ( null !== $offset = $this->queryBuilder->getFragment(Query\Offset::class) ) { - # an order by is mandatory for mssql offset/limit - if ( null === $order = $this->queryBuilder->getFragment(Query\OrderBy::class) ) { - $this->orderBy("(SELECT 0)"); - } - - if ( empty ($offset->offset ) ) { - if ( null !== $select = $this->queryBuilder->getFragment(Query\Select::class) ) { - $select->top = $offset->limit; - } - elseif ( null !== $delete = $this->queryBuilder->getFragment(Query\Delete::class) ) { - $delete->top = $offset->limit; - } - $this->queryBuilder->removeFragment($offset); - } - elseif ( empty($offset->limit) ) { - throw new \Exception("Your offset query fragment is missing a LIMIT value."); - } - } - - if ( null !== $limit = $this->queryBuilder->getFragment(Query\Limit::class) ) { - $this->queryBuilder->removeFragment($limit); + if ( null === $order = $this->queryBuilder->getFragment(Query\OrderBy::class) ) { + $this->orderBy("(SELECT 0)"); } } diff --git a/src/Repository/RelationBuilder.php b/src/Repository/RelationBuilder.php index d95f8e7..5c28a79 100644 --- a/src/Repository/RelationBuilder.php +++ b/src/Repository/RelationBuilder.php @@ -47,9 +47,11 @@ class RelationBuilder return $this->resolveRelation($name) ?: $this->resolveVirtual($name); } - else { - return $this->instanciateEmptyObject($name, $this->resolver->searchFieldAnnotation($name, new Relation() )); + elseif ( $relation = $this->resolver->searchFieldAnnotation($name, new Relation() ) ) { + return $this->instanciateEmptyObject($name, $relation); } + + return false; } protected function resolveVirtual(string $name) /* : bool|object|EntityCollection */ diff --git a/src/SearchRequest/SearchRequestPaginationTrait.php b/src/SearchRequest/SearchRequestPaginationTrait.php index b07b6e7..356703d 100644 --- a/src/SearchRequest/SearchRequestPaginationTrait.php +++ b/src/SearchRequest/SearchRequestPaginationTrait.php @@ -15,7 +15,17 @@ trait SearchRequestPaginationTrait { public bool $skipCount = false; public ? array $columns = null; - + + public function count(): int + { + return $this->count; + } + + public function page(): int + { + return $this->page; + } + public function limit(): int { return $this->limit; @@ -23,7 +33,7 @@ trait SearchRequestPaginationTrait { public function offset(): int { - return (int) ( abs( ( $this->page - 1 ) * $this->limit() ) ); + return (int) ( abs( ( $this->page() - 1 ) * $this->limit() ) ); } public function pagination(int $page, int $itemCount) : void @@ -34,17 +44,25 @@ trait SearchRequestPaginationTrait { public function pageCount() : int { - return ceil($this->count / $this->limit()); + return ceil($this->count() / $this->limit()); } public function hasPagination() : int { return $this->pageCount() > 1; } + public function skipCount(bool $value) : self { $this->skipCount = $value; return $this; } + + public function resultShown() : int + { + $total = $this->page() * $this->limit(); + + return $total <= $this->count() ? $total : $this->count(); + } }