From 610db60a9d95f9bbfbeb18231c9d85c80792aae3 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 21 Oct 2024 14:02:15 +0200 Subject: [PATCH 1/2] Fix before and after filter, make search case insensitive Also, add documentation, and fix facets --- lib/Db/ObjectEntityMapper.php | 5 +- lib/Service/IDatabaseJsonService.php | 43 ++++++++++++++- lib/Service/MySQLJsonService.php | 79 +++++++++++++++++++--------- lib/Service/ObjectService.php | 10 ++-- 4 files changed, 104 insertions(+), 33 deletions(-) diff --git a/lib/Db/ObjectEntityMapper.php b/lib/Db/ObjectEntityMapper.php index 83f5ad2..5090d1a 100644 --- a/lib/Db/ObjectEntityMapper.php +++ b/lib/Db/ObjectEntityMapper.php @@ -193,7 +193,7 @@ public function updateFromArray(int $id, array $object): ObjectEntity return $this->update($obj); } - public function getFacets(array $filters = []) + public function getFacets(array $filters = [], ?string $search = null) { if(key_exists(key: 'register', array: $filters) === true) { $register = $filters['register']; @@ -221,7 +221,8 @@ public function getFacets(array $filters = []) fields: $fields, register: $register, schema: $schema, - filters: $filters + filters: $filters, + search: $search ); } } diff --git a/lib/Service/IDatabaseJsonService.php b/lib/Service/IDatabaseJsonService.php index 7424d18..37ee9f4 100644 --- a/lib/Service/IDatabaseJsonService.php +++ b/lib/Service/IDatabaseJsonService.php @@ -6,6 +6,47 @@ interface IDatabaseJsonService { + /** + * Filters the JSON objects in the objects column based upon given filters. + * + * @param IQueryBuilder $builder The query builder, make sure this matches the database platform used. + * @param array $filters The filters to filter on. + * + * @return IQueryBuilder The updated query builder. + */ public function filterJson(IQueryBuilder $builder, array $filters): IQueryBuilder; - public function getAggregations(IQueryBuilder $builder, array $fields, int $register, int $schema, array $filters = []): array; + + /** + * Searches in the JSON bojects in the objects column for given string. + * + * @param IQueryBuilder $builder The query builder, make sure this matches the database platform used. + * @param string $search The search string to search for. + * + * @return IQueryBuilder The updated query builder. + */ + public function searchJson(IQueryBuilder $builder, string $search): IQueryBuilder; + + /** + * Sorts search results on json fields. + * + * @param IQueryBuilder $builder The query builder, make sure this matches the database platform used. + * @param array $order The fields to order on, and the direction to order with. + * + * @return IQueryBuilder The updated query builder. + */ + public function orderJson(IQueryBuilder $builder, array $order): IQueryBuilder; + + /** + * Generates aggregations (facets) for given fields combined with given filters. + * + * @param IQueryBuilder $builder The query builder, make sure this matches the database platform used. + * @param array $fields The fields to generate aggregations for. + * @param int $register The register id to filter. + * @param int $schema The schema id to filter. + * @param array $filters The filters applied to the request. + * @param string|null $search The search string supplied by the request + * + * @return array The resulting aggregations + */ + public function getAggregations(IQueryBuilder $builder, array $fields, int $register, int $schema, array $filters = [], ?string $search = null): array; } diff --git a/lib/Service/MySQLJsonService.php b/lib/Service/MySQLJsonService.php index 9282b9a..638fab6 100644 --- a/lib/Service/MySQLJsonService.php +++ b/lib/Service/MySQLJsonService.php @@ -7,7 +7,10 @@ class MySQLJsonService implements IDatabaseJsonService { - function orderJson(IQueryBuilder $builder, array $order = []): IQueryBuilder + /** + * @inheritDoc + */ + public function orderJson(IQueryBuilder $builder, array $order = []): IQueryBuilder { foreach($order as $item=>$direction) { @@ -20,17 +23,52 @@ function orderJson(IQueryBuilder $builder, array $order = []): IQueryBuilder return $builder; } - function searchJson(IQueryBuilder $builder, ?string $search = null): IQueryBuilder + /** + * @inheritDoc + */ + public function searchJson(IQueryBuilder $builder, ?string $search = null): IQueryBuilder { - if($search !== null) { + if ($search !== null) { $builder->createNamedParameter(value: "%$search%", placeHolder: ':search'); - $builder->andWhere("JSON_SEARCH(object, 'one', :search) IS NOT NULL"); + $builder->andWhere("JSON_SEARCH(LOWER(object), 'one', LOWER(:search)) IS NOT NULL"); } return $builder; } - function filterJson(IQueryBuilder $builder, array $filters): IQueryBuilder + /** + * @inheritDoc + */ + private function jsonFilterArray(IQueryBuilder $builder, string $filter, array $values): IQueryBuilder + { + foreach ($values as $key=>$value) { + switch ($key) { + case 'after': + $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR, placeHolder: ":value{$filter}after"); + $builder + ->andWhere("json_unquote(json_extract(object, :path$filter)) >= (:value{$filter}after)"); + break; + case 'before': + $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR, placeHolder: ":value${filter}before"); + $builder + ->andWhere("json_unquote(json_extract(object, :path$filter)) <= (:value{$filter}before)"); + break; + default: + $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR_ARRAY, placeHolder: ":value$filter"); + $builder + ->andWhere("json_unquote(json_extract(object, :path$filter)) IN (:value$filter)"); + break; + + } + } + + return $builder; + } + + /** + * @inheritDoc + */ + public function filterJson(IQueryBuilder $builder, array $filters): IQueryBuilder { unset($filters['register'], $filters['schema'], $filters['updated'], $filters['created'], $filters['_queries']); @@ -38,37 +76,27 @@ function filterJson(IQueryBuilder $builder, array $filters): IQueryBuilder $builder->createNamedParameter(value: "$.$filter", placeHolder: ":path$filter"); - if(is_array($value) === true) { - switch(array_keys($value)[0]) { - case 'after': - $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR_ARRAY, placeHolder: ":value$filter"); - $builder - ->andWhere("json_unquote(json_extract(object, :path$filter)) >= (:value$filter)"); - break; - case 'before': - $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR_ARRAY, placeHolder: ":value$filter"); - $builder - ->andWhere("json_unquote(json_extract(object, :path$filter)) <= (:value$filter)"); - break; - default: - $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR_ARRAY, placeHolder: ":value$filter"); - $builder - ->andWhere("json_unquote(json_extract(object, :path$filter)) IN (:value$filter)"); - break; - } + if(is_array($value) === true && array_is_list($value) === false) { + $builder = $this->jsonFilterArray(builder: $builder, filter: $filter, values: $value); continue; + } else if (is_array($value === true)) { + $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR_ARRAY, placeHolder: ":value$filter"); + $builder + ->andWhere("json_unquote(json_extract(object, :path$filter)) IN (:value$filter)"); } $builder->createNamedParameter(value: $value, placeHolder: ":value$filter"); $builder ->andWhere("json_extract(object, :path$filter) = :value$filter OR json_contains(object, json_quote(:value$filter), :path$filter)"); } -// var_dump($builder->getSQL()); return $builder; } - public function getAggregations(IQueryBuilder $builder, array $fields, int $register, int $schema, array $filters = []): array + /** + * @inheritDoc + */ + public function getAggregations(IQueryBuilder $builder, array $fields, int $register, int $schema, array $filters = [], ?string $search = null): array { $facets = []; @@ -87,6 +115,7 @@ public function getAggregations(IQueryBuilder $builder, array $fields, int $regi ->groupBy('_id'); $builder = $this->filterJson($builder, $filters); + $builder = $this->searchJson($builder, $search); $result = $builder->executeQuery(); $facets[$field] = $result->fetchAll(); diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 5665f43..ab85494 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -58,7 +58,7 @@ public function createFromArray(array $object) { public function updateFromArray(string $id, array $object, bool $updatedObject) { $object['id'] = $id; - + return $this->saveObject( register: $this->getRegister(), schema: $this->getSchema(), @@ -117,7 +117,7 @@ public function findMultiple(array $ids): array return $result; } - public function getAggregations(array $filters): array + public function getAggregations(array $filters, ?string $search = null): array { $mapper = $this->getMapper(objectType: 'objectEntity'); @@ -125,7 +125,7 @@ public function getAggregations(array $filters): array $filters['schema'] = $this->getSchema(); if ($mapper instanceof ObjectEntityMapper === true) { - $facets = $this->objectEntityMapper->getFacets($filters); + $facets = $this->objectEntityMapper->getFacets($filters, $search); return $facets; } @@ -230,12 +230,12 @@ public function saveObject(int $register, int $schema, array $object): ObjectEnt // Normal loging //$changed = $objectEntity->getUpdatedFields(); - + // If the object has no uuid, create a new one if (empty($objectEntity->getUuid())) { $objectEntity->setUuid(Uuid::v4()); } - + if($objectEntity->getId()){ $objectEntity = $this->objectEntityMapper->update($objectEntity); $action = 'update'; From 82f1cc93c72559cd3e974bebcb6cda14cb6a8854 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 21 Oct 2024 15:14:56 +0200 Subject: [PATCH 2/2] Fix filtering on multiple possible values --- lib/Service/MySQLJsonService.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Service/MySQLJsonService.php b/lib/Service/MySQLJsonService.php index 638fab6..d16d895 100644 --- a/lib/Service/MySQLJsonService.php +++ b/lib/Service/MySQLJsonService.php @@ -79,10 +79,11 @@ public function filterJson(IQueryBuilder $builder, array $filters): IQueryBuilde if(is_array($value) === true && array_is_list($value) === false) { $builder = $this->jsonFilterArray(builder: $builder, filter: $filter, values: $value); continue; - } else if (is_array($value === true)) { + } else if (is_array($value) === true) { $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR_ARRAY, placeHolder: ":value$filter"); $builder ->andWhere("json_unquote(json_extract(object, :path$filter)) IN (:value$filter)"); + continue; } $builder->createNamedParameter(value: $value, placeHolder: ":value$filter");