This commit is contained in:
Dave Mc Nicoll 2022-06-28 12:45:25 +00:00
commit 8507283d27
12 changed files with 144 additions and 90 deletions

View File

@ -92,11 +92,11 @@ 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";
}
@ -104,7 +104,7 @@ class MsSQL implements AdapterInterface {
$parts[] = "Encrypt=0";
}
if ( $this->failoverPartner ?? false ) {
if ( $this->failoverPartner ?? false ) {
$parts[] = "Failover_Partner={$this->failoverPartner}";
}
@ -133,7 +133,7 @@ class MsSQL implements AdapterInterface {
}
if ( $this->trustServerCertificate ?? false ) {
$parts[] = "TrustServerCertificate=1";
$parts[] = "TrustServerCertificate=yes";
}
if ( $this->WSID ?? false ) {
@ -238,4 +238,8 @@ class MsSQL implements AdapterInterface {
return Repository\MssqlRepository::class;
}
public function queryBuilderClass() : string
{
return QueryBuilder\MssqlQueryBuilder::class;
}
}

View File

@ -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;
}
}

View File

@ -3,7 +3,17 @@
namespace Ulmus\Annotation\Property;
class Virtual extends Field {
public Closure $closure;
public bool $readonly = true;
public \Closure $closure;
public string $method;
public function __construct(? \Closure $closure = null)
{
if ( $closure !== null ) {
$this->closure = $closure;
}
}
}

View File

@ -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) {

View File

@ -9,7 +9,7 @@ use Ulmus\Repository,
use Ulmus\Annotation\Classes\{ Method, Table, Collation, };
use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Relation, OrderBy, Where, OrWhere, Join, Virtual, On, WithJoin, };
use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Blob, Text, Mediumtext, Longtext, Tinyblob, Mediumblob, Longblob };
use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Text, Mediumtext, Longtext, };
use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore };
trait EntityTrait {
@ -30,6 +30,10 @@ trait EntityTrait {
*/
public array $entityLoadedDataset = [];
public function __construct() {
$this->resetVirtualProperties();
}
/**entityLoadedDataset
* @Ignore
*/
@ -108,7 +112,7 @@ trait EntityTrait {
foreach($this->resolveEntity()->properties as $prop => $property) {
if ( ! $property['builtin'] ) {
foreach($property['tags'] as $tag) {
if ( in_array(strtolower($tag['tag']), [ 'relation', 'join' ] ) ) {
if ( in_array(strtolower($tag['tag']), [ 'relation', 'join', 'virtual' ] ) ) {
unset($this->$prop);
}
}
@ -200,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));
}
@ -311,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));
}
/**

View File

@ -247,6 +247,10 @@ class QueryBuilder implements Query\QueryBuilderInterface
$limit->set($value);
if ($value === 0) {
$this->removeFragment(Query\Limit::class);
}
return $this;
}

View File

@ -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;
}

View File

@ -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();
@ -337,7 +341,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)));
}
}
@ -351,7 +355,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));
}
}
@ -381,7 +385,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;
}
@ -395,7 +399,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;
}
@ -419,13 +423,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);
@ -436,7 +440,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);
@ -564,7 +568,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']}");
}
}
@ -627,7 +633,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 !

View File

@ -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);

View File

@ -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)");
}
}

View File

@ -45,11 +45,29 @@ class RelationBuilder
return $dataset;
}
return $this->resolveRelation($name);
return $this->resolveRelation($name) ?: $this->resolveVirtual($name);
}
elseif ( $relation = $this->resolver->searchFieldAnnotation($name, new Relation() ) ) {
return $this->instanciateEmptyObject($name, $relation);
}
return false;
}
protected function resolveRelation(string $name) /* : object|EntityCollection */
protected function resolveVirtual(string $name) /* : bool|object|EntityCollection */
{
if (null !== ($virtual = $this->resolver->searchFieldAnnotation($name, new Annotation\Property\Virtual()))) {
if ($virtual->closure ?? false) {
return call_user_func_array($virtual->closure, [ $this->entity ]);
}
return call_user_func_array([ $this->entity, $virtual->method ], [ $this->entity ]);
}
return false;
}
protected function resolveRelation(string $name) /* : bool|object|EntityCollection */
{
if ( null !== ( $relation = $this->resolver->searchFieldAnnotation($name, new Relation() ) ) ) {
$this->orders = $this->resolver->searchFieldAnnotationList($name, new OrderBy() );
@ -139,6 +157,25 @@ class RelationBuilder
return new $class();
}
protected function instanciateEmptyObject(string $name, Relation $relation) : object
{
switch( true ) {
case $relation->isOneToOne():
return $this->instanciateEmptyEntity($name, $relation);
case $relation->isOneToMany():
return ($relation->entity ?? $this->resolver->properties[$name]['type'])::entityCollection();
case $relation->isManyToMany():
extract($this->relationAnnotations($name, $relation));
return $relation->bridgeField ?? false ? $relation->bridge::entityCollection() : $relationRelation->entity::entityCollection();
}
return new $class();
}
protected function fetchFromDataset($name, ? array $data = null) /* object|bool */
{
$annotation = $this->resolver->searchFieldAnnotation($name, new Annotation\Property\Join) ?:

View File

@ -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();
}
}