Merge branch 'master' of https://git.mcnd.ca/mcndave/ulmus
This commit is contained in:
commit
8507283d27
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -247,6 +247,10 @@ class QueryBuilder implements Query\QueryBuilderInterface
|
|||
|
||||
$limit->set($value);
|
||||
|
||||
if ($value === 0) {
|
||||
$this->removeFragment(Query\Limit::class);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 !
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) ?:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue