- Added a MssqlRepository

- Adapter some code to allow more flexibility for the new LDAP adapter from ulmus-ldap.
- Insert's fields are now escaped by default.
- A new RelationBuilder was added, a lot of code from the EntityTrait was moved into this. Some code from the Repository class will need to be moved there too.
This commit is contained in:
mcndave committed 2021-03-01 16:26:06 +00:00
1 parent 8f9332fbad
commit 16c6eb57fb
32 files changed
+641 -243

No files matched your search

+126 -17
View File
@@ -2,7 +2,7 @@
namespace Ulmus;
use Ulmus\Annotation\Property\{ Where, Having, Relation, Join };
use Ulmus\Annotation\Property\{ Where, Having, Relation, Join, WithJoin, Relation\Ignore as RelationIgnore };
use Ulmus\Common\EntityResolver;
class Repository
@@ -13,7 +13,7 @@ class Repository
public ? ConnectionAdapter $adapter;
protected QueryBuilder $queryBuilder;
protected Query\QueryBuilderInterface $queryBuilder;
protected EntityResolver $entityResolver;
@@ -271,6 +271,39 @@ class Repository
}
}
public function selectEntity(string $entity, string $alias, string $prependField = "") : self
{
$prependField and ( $prependField .= "$" );
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']}");
}
}
return $this;
}
public function selectJsonEntity(string $entity, string $alias, bool $skipFrom = false) : self
{
$fieldlist = [];
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);
}
}
$this->select(
Common\Sql::function('JSON_ARRAYAGG', Common\Sql::function('JSON_OBJECT', ... $fieldlist))
);
return $skipFrom ? $this : $this->from(
$entity::resolveEntity()->tableName(), $alias, $entity::resolveEntity()->schemaName()
);
}
public function select(/*array|Stringable*/ $fields) : self
{
$this->queryBuilder->select($fields);
@@ -444,8 +477,8 @@ class Repository
$this->select("{$this->alias}.*");
}
# Apply FILTER annotation to this too !
foreach((array) $fields as $item) {
# @TODO Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) {
$annotation = $this->entityResolver->searchFieldAnnotation($item, new Join) ?:
$this->entityResolver->searchFieldAnnotation($item, new Relation);
@@ -459,7 +492,9 @@ class Repository
$entity = $annotation->entity ?? $this->entityResolver->properties[$item]['type'];
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
$this->select("$alias.$key as {$alias}\${$field['name']}");
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore) ) {
$this->select("$alias.$key as {$alias}\${$field['name']}");
}
}
$this->open();
@@ -482,9 +517,14 @@ class Repository
$this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias) {
foreach($this->entityResolver->searchFieldAnnotationList($item, new Where() ) as $condition) {
$field = clone $condition->field;
if ( ! is_object($condition->field) ) {
$field = $this->entityClass::field($condition->field);
}
else {
$field = clone $condition->field;
}
if ( is_object($condition->field) && ( $condition->field->entityClass === $entity ) ) {
if ( $condition->field->entityClass === $entity ) {
$field->alias = $alias;
$join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->value, $condition->operator);
@@ -500,13 +540,77 @@ class Repository
return $this;
}
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self
public function withSubquery(/*string|array*/ $fields) : self
{
$searchRequest->count = $searchRequest->filter( clone $this )
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
->likes($searchRequest->likes(), Query\Where::CONDITION_OR)
->groups($searchRequest->groups())
->count();
# We skip subqueries when counting results since it should not affect the row count.
if ( $this instanceof Repository\ServerRequestCountRepository ) {
return $this;
}
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
$this->select("{$this->alias}.*");
}
# Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) {
if ( $relation = $this->entityResolver->searchFieldAnnotation($item, new Relation) ) {
$alias = $relation->alias ?? $item;
if ( $relation->isManyToMany() ) {
$entity = $relation->bridge;
$repository = $relation->bridge::repository();
$repository->queryBuilder->parent = $this->queryBuilder;
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)
->where( $entity::field($bridgeRelation->key, $relation->bridgeField), $entity::field($bridgeRelation->foreignKey, $this->alias))
->selectJsonEntity($relationRelation->entity, $alias)->open();
}
else {
$entity = $relation->entity ?? $this->entityResolver->properties[$item]['type'];
$repository = $entity::repository()->selectJsonEntity($entity, $alias)->open();
}
# $relation->isManyToMany() and $repository->selectJsonEntity($relation->bridge, $relation->bridgeField, true);
foreach($this->entityResolver->searchFieldAnnotationList($item, new Where() ) as $condition) {
$repository->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
}
foreach($this->entityResolver->searchFieldAnnotationList($item, new Having() ) as $condition) {
$repository->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
}
$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;
$repository->where( $foreignKey, $key);
}
$this->select("(" . $r = Common\Sql::raw($repository->queryBuilder->render() . ") as $item\$collection"));
}
else {
throw new \Exception("You referenced field `$item` which do not exist or do not contain a valid @Join annotation.");
}
}
return $this;
}
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self
{
if ($count) {
$searchRequest->count = $searchRequest->filter(new Repository\ServerRequestCountRepository($this->entityClass, $this->alias, $this->adapter))
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
->likes($searchRequest->likes(), Query\Where::CONDITION_OR)
->groups($searchRequest->groups())
->count();
}
return $searchRequest->filter($this)
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
@@ -551,18 +655,18 @@ class Repository
return Ulmus::runQuery($this->queryBuilder, $this->adapter);
}
public function resetQuery() : self
{
$this->queryBuilder->reset();
return $this;
}
protected function insertSqlQuery(array $dataset) : self
{
if ( null === $this->queryBuilder->getFragment(Query\Insert::class) ) {
$this->insert(array_keys($dataset), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
$this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
}
$this->values($dataset);
@@ -651,6 +755,11 @@ class Repository
return new $this->entityClass();
}
public function escapeField(string $identifier) : string
{
return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_FIELD);
}
public function escapeTable(string $identifier) : string
{
return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_TABLE);