Commit a7ffcdd2 authored by François Agneray's avatar François Agneray
Browse files

Ajout de plusieurs operateurs + codage #8

parent 1dbae35a
......@@ -12,69 +12,27 @@ namespace App\Action\Search;
use Psr\Log\LoggerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use App\Action\AbstractAction;
use App\Utils\DBALConnectionFactory;
use App\Entity\Dataset;
use App\Entity\Attribute;
use App\Search\SearchException;
use App\Search\MetaToolbox;
use App\Search\Operator\OperatorFactory;
use App\utils\AnisQueryBuilderFactory;
abstract class AbstractSearchAction extends AbstractAction
{
protected $em;
protected $dcf;
protected $aqbf;
public function __construct(LoggerInterface $logger, EntityManagerInterface $em, DBALConnectionFactory $dcf)
public function __construct(
LoggerInterface $logger,
EntityManagerInterface $em,
DBALConnectionFactory $dcf,
AnisQueryBuilderFactory $aqbf
)
{
parent::__construct($logger);
$this->em = $em;
$this->dcf = $dcf;
}
protected function addWhere(QueryBuilder $queryBuilder, Dataset $dataset, array $queryParams): void
{
if (!array_key_exists('c', $queryParams)) {
throw SearchException::paramDoesNotExist('c');
}
$args = array();
$criteria = explode(';', $queryParams['c']);
foreach ($criteria as $criterion) {
$args[] = $this->getExpression($criterion, $dataset, $queryBuilder->expr());
}
$queryBuilder->where(new CompositeExpression(CompositeExpression::TYPE_AND, $args));
}
protected function getExpression(string $criterion, Dataset $dataset, ExpressionBuilder $expr)
{
$params = $this->getCriterionParams($criterion);
$attribute = $this->getCriterionAttribute($params[0], $dataset);
$column = $attribute->getTableName() . '.' . $attribute->getName();
$values = explode('|', $params[2]);
$operator = OperatorFactory::create($params[1], $expr, $column, $values);
return $operator->getExpression();
}
protected function getCriterionParams(string $criterion): array
{
$params = explode(':', $criterion);
$numberOfParams = count($params);
if ($numberOfParams != 3) {
throw SearchException::numberOfCriterionParams($numberOfParams, $criterion);
}
return $params;
}
protected function getCriterionAttribute(int $id, Dataset $dataset): Attribute
{
if (!MetaToolbox::checkOneAttributeId($dataset, $id)) {
throw SearchException::attributeNotFound($id, $dataset->getLabel());
}
return MetaToolbox::getAttributeById($dataset, $id);
$this->aqbf = $aqbf;
}
}
......@@ -12,9 +12,6 @@ namespace App\Action\Search;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Doctrine\DBAL\Query\QueryBuilder;
use App\Entity\Dataset;
final class SearchDataAction extends AbstractSearchAction
{
......@@ -36,71 +33,44 @@ final class SearchDataAction extends AbstractSearchAction
)->withStatus(404);
}
$queryParams = $request->getQueryParams();
if (!array_key_exists('c', $queryParams)) {
return $this->dispatchHttpError(
$response,
'Invalid request',
'HTTP 400: Param c is required for this request'
)->withStatus(400);
}
$queryParams = $request->getQueryParams();
if (!array_key_exists('a', $queryParams)) {
return $this->dispatchHttpError(
$response,
'Invalid request',
'HTTP 400: Param a is required for this request'
)->withStatus(400);
}
$database = $dataset->getProject()->getDatabase();
$connection = $this->dcf->create($database);
$queryBuilder = $connection->createQueryBuilder();
$this->addSelect($queryBuilder, $dataset, $request->getQueryParams());
$queryBuilder->from($dataset->getTableRef());
$this->addWhere($queryBuilder, $dataset, $request->getQueryParams());
$this->addOrder($queryBuilder, $request->getQueryParams());
$this->addLimit($queryBuilder, $request->getQueryParams());
$stmt = $queryBuilder->execute();
$result = $stmt->fetchAll();
return $response->withJson($result);
}
private function addSelect(QueryBuilder $queryBuilder, Dataset $dataset, array $queryParams): void
{
if (array_key_exists('a', $queryParams)) {
$listIds = explode(';', $queryParams['a']);
$attributes = $dataset->getAttributes();
$foundedAttributes = array();
foreach ($attributes as $attribute) {
$id = $attribute->getId();
if (in_array($id, $listIds)) {
$foundedAttributes[$id] = $attribute;
}
}
$columns = array();
foreach ($listIds as $id) {
$attribute = $foundedAttributes[$id];
$columns[] = $attribute->getTableName() . '.' . $attribute->getName() . ' as ' . $attribute->getLabel();
}
$queryBuilder->select($columns);
}
}
$anisQueryBuilder = $this->aqbf->create($queryBuilder, $dataset);
$anisQueryBuilder
->select(explode(';', $queryParams['a']))
->where(explode(';', $queryParams['c']));
private function addOrder(QueryBuilder $queryBuilder, array $queryParams): void
{
if (array_key_exists('o', $queryParams)) {
$orders = explode(';', $queryParams['o']);
foreach($orders as $order) {
$o = explode(':', $order);
$attribute = $this->em->find('App\Entity\Attribute', $o[0]);
if ($o[1] === 'a') {
$aord = 'ASC';
} else if ($o[1] === 'd') {
$aord = 'DESC';
}
$queryBuilder->orderBy($attribute->getTableName() . '.' . $attribute->getName(), $aord);
}
$anisQueryBuilder->order(explode(';', $queryParams['o']));
}
}
private function addLimit(QueryBuilder $queryBuilder, array $queryParams): void
{
if (array_key_exists('p', $queryParams)) {
$p = explode(':', $queryParams['p']);
$limit = $p[0];
$offset = ($p[1] - 1) * $limit;
$queryBuilder
->setFirstResult($offset)
->setMaxResults($limit);
$anisQueryBuilder->limit($queryParams['p']);
}
$this->logger->info('SQL: ' . $anisQueryBuilder->getSQL());
$result = $anisQueryBuilder->fetchAll();
return $response->withJson($result);
}
}
......@@ -13,6 +13,8 @@ namespace App\Action\Search;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use App\Search\AnisQueryBuilder;
final class SearchMetaAction extends AbstractSearchAction
{
public function __invoke(Request $request, Response $response, array $args): Response
......@@ -24,7 +26,6 @@ final class SearchMetaAction extends AbstractSearchAction
}
$dataset = $this->em->find('App\Entity\Dataset', $args['dname']);
if (is_null($dataset)) {
return $this->dispatchHttpError(
$response,
......@@ -33,16 +34,27 @@ final class SearchMetaAction extends AbstractSearchAction
)->withStatus(404);
}
$queryParams = $request->getQueryParams();
if (!array_key_exists('c', $queryParams)) {
return $this->dispatchHttpError(
$response,
'Invalid request',
'HTTP 400: Param c is required for this request'
)->withStatus(400);
}
$database = $dataset->getProject()->getDatabase();
$connection = $this->dcf->create($database);
$queryBuilder = $connection->createQueryBuilder();
$queryBuilder->select('COUNT(*) as nb');
$queryBuilder->from($dataset->getTableRef());
$this->addWhere($queryBuilder, $dataset, $request->getQueryParams());
$anisQueryBuilder = $this->aqbf->create($queryBuilder, $dataset);
$anisQueryBuilder
->count()
->where(explode(';', $queryParams['c']));
$stmt = $queryBuilder->execute();
$result = $stmt->fetchAll();
$this->logger->info('SQL: ' . $anisQueryBuilder->getSQL());
$result = $anisQueryBuilder->fetchAll();
$meta = array();
$meta['dataset-selected'] = $dataset->getLabel();
......
......@@ -11,34 +11,89 @@
namespace App\Search;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use App\Entity\Dataset;
use App\Entity\Attribute;
class QueryBuilderAdapter
class AnisQueryBuilder
{
private $queryBuilder;
private $dataset;
private $operatorFactory;
public function __construct(QueryBuilder $queryBuilder, Dataset $dataset)
public function __construct(QueryBuilder $queryBuilder, Dataset $dataset, string $operatorFactory)
{
$this->queryBuilder = $queryBuilder->from($dataset->getTableRef());
$this->queryBuilder = $queryBuilder->from($dataset->getTableRef());;
$this->dataset = $dataset;
$this->operatorFactory = $operatorFactory;
}
public function setOperatorFactory(string $operatorFactory)
{
$this->$operatorFactory = $operatorFactory;
}
public function count()
{
$queryBuilder->select('COUNT(*) as nb');
$this->queryBuilder->select('COUNT(*) as nb');
return $this;
}
public function select(array $listOfIds)
{
if (!MetaToolbox::checkListOfAttributesIds($this->dataset, $listOfIds)) {
throw SearchException::listOfIdsIsNotCorrect();
}
$columns = array();
foreach ($listOfIds as $id) {
$attribute = $this->getAttribute((int) $id);
$columns[] = $attribute->getTableName() . '.' . $attribute->getName() . ' as ' . $attribute->getLabel();
}
$this->queryBuilder->select($columns);
return $this;
}
public function where(array $criteria)
{
$expressions = array();
foreach ($criteria as $criterion) {
$params = $this->getCriterionParams($criterion);
$attribute = $this->getCriterionAttribute($params[0], $dataset);
$attribute = $this->getAttribute((int) $params[0]);
$column = $attribute->getTableName() . '.' . $attribute->getName();
$values = explode('|', $params[2]);
$operator = OperatorFactory::create($params[1], $expr, $column, $values);
$operator = $this->operatorFactory::create($params[1], $this->queryBuilder->expr(), $column, $values);
$expressions[] = $operator->getExpression();
}
$this->queryBuilder->where(new CompositeExpression(CompositeExpression::TYPE_AND, $expressions));
return $this;
}
public function order(array $orders)
{
foreach($orders as $order) {
$o = explode(':', $order);
$attribute = $this->getAttribute((int) $o[0]);
if ($o[1] === 'a') {
$aord = 'ASC';
} else if ($o[1] === 'd') {
$aord = 'DESC';
}
$this->queryBuilder->orderBy($attribute->getTableName() . '.' . $attribute->getName(), $aord);
}
return $this;
}
public function limit(string $param)
{
$p = explode(':', $param);
$limit = $p[0];
$offset = ($p[1] - 1) * $limit;
$this->queryBuilder
->setFirstResult($offset)
->setMaxResults($limit);
return $this;
}
public function getSQL()
......@@ -48,8 +103,8 @@ class QueryBuilderAdapter
public function fetchAll()
{
$stmt = $queryBuilder->execute();
$result = $stmt->fetchAll();
$stmt = $this->queryBuilder->execute();
return $stmt->fetchAll();
}
private function getCriterionParams(string $criterion): array
......@@ -62,11 +117,11 @@ class QueryBuilderAdapter
return $params;
}
private function getCriterionAttribute(int $id, Dataset $dataset): Attribute
private function getAttribute(int $id): Attribute
{
if (!MetaToolbox::checkOneAttributeId($dataset, $id)) {
throw SearchException::attributeNotFound($id, $dataset->getLabel());
if (!MetaToolbox::checkOneAttributeId($this->dataset, $id)) {
throw SearchException::attributeNotFound($id, $this->dataset->getLabel());
}
return MetaToolbox::getAttributeById($dataset, $id);
return MetaToolbox::getAttributeById($this->dataset, $id);
}
}
\ No newline at end of file
......@@ -39,10 +39,13 @@ abstract class MetaToolbox
public static function checkListOfAttributesIds(Dataset $dataset, array $listOfIds): bool
{
$ids = array();
$attributes = $dataset->getAttributes();
foreach ($attributes as $attribute) {
$id = $attribute->getId();
if (!in_array($id, $listOfIds)) {
$ids[] = $attribute->getId();
}
foreach ($listOfIds as $id) {
if (!in_array($id, $ids)) {
return false;
}
}
......
......@@ -20,9 +20,9 @@ class Equal extends Operator
{
parent::__construct($expr, $column);
if (is_numeric($value)) {
$this->value = (float) $value;
} if(is_string($value)) {
$this->value = $value;
} else {
$this->value = $this->expr->literal($value);
}
}
......
<?php declare(strict_types=1);
/*
* This file is part of ANIS SERVER API.
*
* (c) François Agneray <francois.agneray@lam.fr>
* (c) Chrystel Moreau <chrystel.moreau@lam.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Search\Operator;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
class In extends Operator
{
private $value;
public function __construct(ExpressionBuilder $expr, string $column, string $value)
{
parent::__construct($expr, $column);
$this->value = $this->expr->literal($value);
}
public function getExpression()
{
return $this->expr->like($this->column, $this->value);
}
}
\ No newline at end of file
<?php declare(strict_types=1);
/*
* This file is part of ANIS SERVER API.
*
* (c) François Agneray <francois.agneray@lam.fr>
* (c) Chrystel Moreau <chrystel.moreau@lam.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Search\Operator;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
class Like extends Operator
{
private $value;
public function __construct(ExpressionBuilder $expr, string $column, string $value)
{
parent::__construct($expr, $column);
$this->value = $this->expr->literal($value);
}
public function getExpression()
{
return $this->expr->like($this->column, $this->value);
}
}
\ No newline at end of file
......@@ -20,9 +20,9 @@ class NotEqual extends Operator
{
parent::__construct($expr, $column);
if (is_numeric($value)) {
$this->value = (float) $value;
$this->value = $value;
} if(is_string($value)) {
$this->value = (string) $value;
$this->value = $this->expr->literal($value);
}
}
......
<?php declare(strict_types=1);
/*
* This file is part of ANIS SERVER API.
*
* (c) François Agneray <francois.agneray@lam.fr>
* (c) Chrystel Moreau <chrystel.moreau@lam.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Search\Operator;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
class NotLike extends Operator
{
private $value;
public function __construct(ExpressionBuilder $expr, string $column, string $value)
{
parent::__construct($expr, $column);
$this->value = $this->expr->literal($value);
}
public function getExpression()
{
return $this->expr->notLike($this->column, $this->value);
}
}
\ No newline at end of file
......@@ -39,7 +39,13 @@ class OperatorFactory
return new LessThanEqual($expr, $column, (float) $parameters[0]);
case 'lk':
return Like($expr, $column, $parameters);
return new Like($expr, $column, $parameters[0]);
case 'nlk':
return new NotLike($expr, $column, $parameters[0]);
case 'nl':
return new OperatorNull($expr, $column);
default:
throw OperatorException::unknownOperator($type);
......
<?php
<?php declare(strict_types=1);
/*
* This file is part of ANIS SERVER API.
*
......@@ -8,15 +8,19 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Output\Data\Strategy;
namespace App\Search\Operator;
use App\Output\Data\AbstractDataOutput;
use App\SqlBuilder\ISqlQuery;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
class VoOutput extends AbstractDataOutput
class OperatorNull extends Operator
{
public function getResponse(ISqlQuery $select)
public function __construct(ExpressionBuilder $expr, string $column)
{
parent::__construct($expr, $column);
}
public function getExpression()
{
return $this->expr->isNull($this->column);
}
}
}
\ No newline at end of file
......@@ -34,4 +34,9 @@ class SearchException extends Exception
{
return new self("Attribute with the id " . $id . " is not found for the dataset " . $datasetLabel);
}
public static function listOfIdsIsNotCorrect()
{
return new self("List of ids is not correct");
}
}
\ No newline at end of file
......@@ -8,20 +8,24 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Search\SearchType;
namespace App\Utils;
use App\Search\AbstractSearchType;
use App\SqlBuilder\Column;
use App\SqlBuilder\BetweenCriterion;
use Doctrine\DBAL\Query\QueryBuilder;
class Between extends AbstractSearchType
use App\Entity\Dataset;
use App\Search\AnisQueryBuilder;
class AnisQueryBuilderFactory
{
public function createCriterion($attribute, $parameters)
private $operatorFactory;
public function __construct($operatorFactory)
{
$this->operatorFactory = $operatorFactory;
}
public function create(QueryBuilder $queryBuilder, Dataset $dataset)
{
$values = explode(':', $parameters);
// $value0 = $this->castPhpType($values[0], $attribute->getPhpType());
// $value1 = $this->castPhpType($values[1], $attribute->getPhpType());
$column = new Column($attribute->getName(), $attribute->getTableName());
return new BetweenCriterion($column, $values[0], $values[1]);
return new AnisQueryBuilder($queryBuilder, $dataset, $this->operatorFactory);
}
}
}
\ No newline at end of file
<?php
/*
* This file is part of ANIS SERVER API.
*
* (c) François Agneray <francois.agneray@lam.fr>
* (c) Chrystel Moreau <chrystel.moreau@lam.fr>
*