- Added some SearchRequest attributes ; now it's even simpler to add searchable fields
- Began decoupling of QueryBuilder from native MySQL method ; some works needs to be done on for the Repository object splitting also.
This commit is contained in:
parent
15be1597b8
commit
4571517dc8
|
@ -0,0 +1,7 @@
|
|||
# Search Request
|
||||
|
||||
Ulmus comes with a simple search request processor which allows both flexibility and simplicity.
|
||||
|
||||
## Quick start
|
||||
|
||||
Creating a simple user exemple entity:
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\{ConnectionAdapter, Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder\MysqlQueryBuilder};
|
||||
|
||||
trait DefaultAdapterTrait
|
||||
{
|
||||
public function repositoryClass() : string
|
||||
{
|
||||
return Repository::class;
|
||||
}
|
||||
|
||||
public function queryBuilderClass() : string
|
||||
{
|
||||
return MysqlQueryBuilder::class;
|
||||
}
|
||||
|
||||
public function tableSyntax() : array
|
||||
{
|
||||
return [
|
||||
'ai' => "AUTO_INCREMENT",
|
||||
'pk' => "PRIMARY KEY",
|
||||
'unsigned' => "UNSIGNED",
|
||||
];
|
||||
}
|
||||
|
||||
public function databaseName() : string
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object
|
||||
{
|
||||
return Table::repository(Repository::DEFAULT_ALIAS, $adapter)
|
||||
->select(\Ulmus\Common\Sql::raw('this.*'))
|
||||
->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
|
||||
->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName);
|
||||
}
|
||||
|
||||
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
|
||||
{
|
||||
$type = $field->type;
|
||||
|
||||
$length = $field->length;
|
||||
|
||||
if ( is_a($type, Entity\Field\Date::class, true) ) {
|
||||
$type = "DATE";
|
||||
}
|
||||
elseif ( is_a($type, Entity\Field\Time::class, true) ) {
|
||||
$type = "TIME";
|
||||
}
|
||||
elseif ( is_a($type, \DateTime::class, true) ) {
|
||||
$type = "DATETIME";
|
||||
}
|
||||
|
||||
switch($type) {
|
||||
case "bool":
|
||||
$type = "TINYINT";
|
||||
$length = 1;
|
||||
break;
|
||||
|
||||
case "array":
|
||||
case "string":
|
||||
if ($length && $length <= 255) {
|
||||
$type = "VARCHAR";
|
||||
break;
|
||||
}
|
||||
elseif (! $length || ( $length <= 65535 ) ) {
|
||||
$type = "TEXT";
|
||||
}
|
||||
elseif ( $length <= 16777215 ) {
|
||||
$type = "MEDIUMTEXT";
|
||||
}
|
||||
elseif ($length <= 4294967295) {
|
||||
$type = "LONGTEXT";
|
||||
}
|
||||
else {
|
||||
throw new \Exception("A column with size bigger than 4GB cannot be created.");
|
||||
}
|
||||
|
||||
# Length is unnecessary on TEXT fields
|
||||
unset($length);
|
||||
|
||||
break;
|
||||
|
||||
case "float":
|
||||
$type = "DOUBLE";
|
||||
break;
|
||||
|
||||
default:
|
||||
$type = strtoupper($type);
|
||||
break;
|
||||
}
|
||||
|
||||
return $typeOnly ? $type : $type . ( isset($length) ? "($length" . ( ! empty($precision) ? ",$precision" : "" ) . ")" : "" );
|
||||
}
|
||||
|
||||
public function whitelistAttributes(array &$parameters) : void
|
||||
{
|
||||
$parameters = array_intersect_key($parameters, array_flip(static::ALLOWED_ATTRIBUTES));
|
||||
}
|
||||
|
||||
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
|
||||
{
|
||||
if ($field['previous']) {
|
||||
$position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']['field'], AdapterInterface::IDENTIFIER_FIELD));
|
||||
}
|
||||
else {
|
||||
$position = "FIRST";
|
||||
}
|
||||
|
||||
return implode(" ", [
|
||||
strtoupper($field['action']),
|
||||
$this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
|
||||
$definition->getSqlType(),
|
||||
$definition->getSqlParams(),
|
||||
$position,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -230,6 +230,6 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
|||
|
||||
public function queryBuilderClass() : string
|
||||
{
|
||||
return QueryBuilder\MssqlQueryBuilder::class;
|
||||
return QueryBuilder\SqlQueryBuilder\MssqlQueryBuilder::class;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,11 @@
|
|||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\Entity\InformationSchema\Table;
|
||||
use Ulmus\Migration\MigrateInterface;
|
||||
use Ulmus\QueryBuilder;
|
||||
use Ulmus\Repository;
|
||||
use Ulmus\QueryBuilder\Sql;
|
||||
use Ulmus\Common\PdoObject;
|
||||
|
||||
use Ulmus\Exception\AdapterConfigurationException;
|
||||
use Ulmus\Ulmus;
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
|
||||
class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
|
@ -157,4 +153,10 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
|||
{
|
||||
return "InnoDB";
|
||||
}
|
||||
|
||||
public function queryBuilderClass() : string
|
||||
{
|
||||
return Sql\MysqlQueryBuilder::class;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,10 +5,9 @@ namespace Ulmus\Adapter;
|
|||
use Ulmus\Common\PdoObject;
|
||||
use Ulmus\ConnectionAdapter;
|
||||
|
||||
use Ulmus\Entity\Sqlite\Table;
|
||||
use Ulmus\Exception\AdapterConfigurationException;
|
||||
use Ulmus\Entity;
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder, Ulmus};
|
||||
use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder};
|
||||
|
||||
class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
|
@ -90,9 +89,9 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
|||
|
||||
public function schemaTable(ConnectionAdapter $adapter, string $databaseName, string $tableName) : null|object
|
||||
{
|
||||
return Table::repository(Repository::DEFAULT_ALIAS, $adapter)
|
||||
return Entity\Sqlite\Table::repository(Repository::DEFAULT_ALIAS, $adapter)
|
||||
->select(\Ulmus\Common\Sql::raw('this.*'))
|
||||
->loadOneFromField(Table::field('tableName'), $tableName);
|
||||
->loadOneFromField(Entity\Sqlite\Table::field('tableName'), $tableName);
|
||||
}
|
||||
|
||||
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
|
||||
|
@ -150,7 +149,7 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
|||
|
||||
public function queryBuilderClass() : string
|
||||
{
|
||||
return QueryBuilder\SqliteQueryBuilder::class;
|
||||
return QueryBuilder\Sql\SqliteQueryBuilder::class;
|
||||
}
|
||||
|
||||
public function exportFunctions(PdoObject $pdo) : void
|
||||
|
@ -192,7 +191,7 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
|||
|
||||
public static function registerPragma(PdoObject $pdo, array $pragmaList) : void
|
||||
{
|
||||
$builder = new QueryBuilder\SqliteQueryBuilder();
|
||||
$builder = new QueryBuilder\Sql\SqliteQueryBuilder();
|
||||
|
||||
foreach($pragmaList as $pragma) {
|
||||
list($key, $value) = explode('=', $pragma) + [ null, null ];
|
||||
|
|
|
@ -7,7 +7,7 @@ use Ulmus\{Common\Sql,
|
|||
Entity\InformationSchema\Table,
|
||||
Migration\FieldDefinition,
|
||||
Repository,
|
||||
QueryBuilder,
|
||||
QueryBuilder\SqlQueryBuilder,
|
||||
Ulmus};
|
||||
|
||||
trait SqlAdapterTrait
|
||||
|
@ -19,7 +19,7 @@ trait SqlAdapterTrait
|
|||
|
||||
public function queryBuilderClass() : string
|
||||
{
|
||||
return QueryBuilder::class;
|
||||
return SqlQueryBuilder::class;
|
||||
}
|
||||
|
||||
public function tableSyntax() : array
|
||||
|
|
|
@ -4,19 +4,16 @@ namespace Ulmus;
|
|||
|
||||
use Notes\Attribute\Ignore;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\{Common\EntityResolver,
|
||||
Common\EntityField,
|
||||
Entity\EntityInterface,
|
||||
Query\QueryBuilderInterface,
|
||||
SearchRequest\SearchRequestInterface,
|
||||
SearchRequest\SearchRequestPaginationTrait};
|
||||
use Ulmus\{Common\EntityResolver, Common\EntityField, Entity\EntityInterface, Query\QueryBuilderInterface};
|
||||
use Ulmus\SearchRequest\{Attribute\SearchParameter,
|
||||
SearchMethodEnum,
|
||||
SearchRequestInterface,
|
||||
SearchRequestFromRequestTrait,
|
||||
SearchRequestPaginationTrait};
|
||||
|
||||
trait EntityTrait {
|
||||
use EventTrait;
|
||||
|
||||
#[Ignore]
|
||||
public string $loadedFromAdapter;
|
||||
|
||||
#[Ignore]
|
||||
protected bool $entityStrictFieldsDeclaration = false;
|
||||
|
||||
|
@ -327,6 +324,7 @@ trait EntityTrait {
|
|||
#[Ignore]
|
||||
public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField
|
||||
{
|
||||
|
||||
$default = ( $alias === false ? '' : Repository::DEFAULT_ALIAS ); # bw compatibility, to be deprecated
|
||||
|
||||
return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : $default, Ulmus::resolveEntity(static::class));
|
||||
|
@ -343,47 +341,17 @@ trait EntityTrait {
|
|||
#[Ignore]
|
||||
public static function searchRequest(...$arguments) : SearchRequestInterface
|
||||
{
|
||||
return new class() implements SearchRequestInterface, \JsonSerializable
|
||||
{
|
||||
use SearchRequestPaginationTrait;
|
||||
return new #[SearchRequest\Attribute\SearchRequestParameter(self::class)] class(... $arguments) extends SearchRequest\SearchRequest {
|
||||
# Define searchable properties here, some ex:
|
||||
|
||||
public function fromRequest(ServerRequestInterface $request) : self
|
||||
{
|
||||
$get = new \ArrayObject(array_filter($request->getQueryParams(), function($i) { return $i !== ""; }));
|
||||
# #[SearchParameter(method: SearchMethodEnum::Where)]
|
||||
# public ? string $username = null;
|
||||
|
||||
$this->page = $get->offsetExists('page') ? $get['page'] : 1;
|
||||
$this->limit = 100;
|
||||
# #[SearchParameter(method: SearchMethodEnum::Where, toggle: true)]
|
||||
# public ? string $hidden = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filter(Repository $repository) : Repository
|
||||
{
|
||||
return $repository;
|
||||
}
|
||||
|
||||
public function wheres() : iterable
|
||||
{
|
||||
return array_filter([
|
||||
], fn($i) => ! is_null($i) ) + [ ];
|
||||
}
|
||||
|
||||
public function likes(): iterable
|
||||
{
|
||||
return array_filter([
|
||||
], fn($i) => ! is_null($i) ) + [];
|
||||
}
|
||||
|
||||
public function groups(): iterable
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function orders(): iterable
|
||||
{
|
||||
return array_filter([
|
||||
], fn($e) => ! is_null($e) && $e !== "" );
|
||||
}
|
||||
# #[SearchParameter(method: SearchMethodEnum::Like)]
|
||||
# public ? string $word = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ulmus\Query;
|
||||
|
||||
use Ulmus\QueryBuilder;
|
||||
use Ulmus\MysqlQueryBuilder;
|
||||
|
||||
use Ulmus\Common\EntityField,
|
||||
Ulmus\Common\Sql;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ulmus\Query;
|
||||
|
||||
use Ulmus\QueryBuilder;
|
||||
use Ulmus\MysqlQueryBuilder;
|
||||
use Ulmus\Repository\ConditionTrait;
|
||||
|
||||
class Join extends Fragment
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ulmus\Query;
|
||||
|
||||
use Ulmus\QueryBuilder;
|
||||
use Ulmus\MysqlQueryBuilder;
|
||||
|
||||
class Set extends Fragment {
|
||||
|
||||
|
|
|
@ -10,10 +10,14 @@ class Show extends Fragment {
|
|||
|
||||
const SQL_TOKEN_FROM = "FROM";
|
||||
|
||||
const SQL_TOKEN_IN = "IN";
|
||||
|
||||
const SQL_SHOW_DATABASES = "DATABASES";
|
||||
|
||||
const SQL_SHOW_TABLES = "TABLES";
|
||||
|
||||
const SQL_SHOW_COLUMNS = "COLUMNS";
|
||||
|
||||
public string $show;
|
||||
|
||||
public string $from;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ulmus\Query;
|
||||
|
||||
use Ulmus\QueryBuilder;
|
||||
use Ulmus\MysqlQueryBuilder;
|
||||
|
||||
class Values extends Fragment {
|
||||
|
||||
|
@ -12,9 +12,9 @@ class Values extends Fragment {
|
|||
|
||||
public array $rows;
|
||||
|
||||
public QueryBuilder $queryBuilder;
|
||||
public MysqlQueryBuilder $queryBuilder;
|
||||
|
||||
public function __construct(QueryBuilder $queryBuilder)
|
||||
public function __construct(MysqlQueryBuilder $queryBuilder)
|
||||
{
|
||||
$this->queryBuilder = $queryBuilder;
|
||||
}
|
||||
|
|
|
@ -2,541 +2,7 @@
|
|||
|
||||
namespace Ulmus;
|
||||
|
||||
use Ulmus\Query\QueryBuilderInterface;
|
||||
|
||||
class QueryBuilder implements Query\QueryBuilderInterface
|
||||
class QueryBuilder extends QueryBuilder\MysqlQueryBuilder
|
||||
{
|
||||
public Query\Where $where;
|
||||
|
||||
public Query\Having $having;
|
||||
|
||||
public QueryBuilderInterface $parent;
|
||||
|
||||
/**
|
||||
* Those are the parameters we are going to bind to PDO.
|
||||
*/
|
||||
public array $parameters = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* Those values are to be inserted or updated
|
||||
*/
|
||||
public array $values = [];
|
||||
|
||||
public string $whereConditionOperator = Query\Where::CONDITION_AND;
|
||||
|
||||
public string $havingConditionOperator = Query\Where::CONDITION_AND;
|
||||
|
||||
protected int $parameterIndex = 0;
|
||||
|
||||
protected array $queryStack = [];
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->where ?? false) {
|
||||
#$this->where = clone $this->where;
|
||||
#$this->where->queryBuilder = $this;
|
||||
}
|
||||
|
||||
if ($this->having ?? false) {
|
||||
#$this->having = clone $this->having;
|
||||
#$this->having->queryBuiler = $this;
|
||||
}
|
||||
|
||||
if ($this->parent ?? false) {
|
||||
#$this->parent = clone $this->parent;
|
||||
}
|
||||
}
|
||||
|
||||
public function select(string|\Stringable|array $field, bool $distinct = false) : self
|
||||
{
|
||||
if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) {
|
||||
$select->add($field);
|
||||
}
|
||||
else {
|
||||
$select = new Query\Select();
|
||||
$select->set($field);
|
||||
$this->push($select);
|
||||
}
|
||||
|
||||
$select->distinct = $distinct;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null, bool $replace = false) : self
|
||||
{
|
||||
if ( null === $this->getFragment(Query\Insert::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$insert = new Query\Insert();
|
||||
$this->push($insert);
|
||||
|
||||
$insert->replace = $replace;
|
||||
$insert->fieldlist = $fieldlist;
|
||||
$insert->alias = $alias;
|
||||
$insert->table = $table;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function values(array $dataset) : self
|
||||
{
|
||||
if ( null === ( $values = $this->getFragment(Query\Values::class) ) ) {
|
||||
$values = new Query\Values($this);
|
||||
$this->push($values);
|
||||
}
|
||||
|
||||
$values->add($dataset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function update(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( ! $this->getFragment(Query\Update::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$update = new Query\Update();
|
||||
$this->push($update);
|
||||
|
||||
$update->alias = $alias;
|
||||
$update->table = $table;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set(array $dataset, ? array $escapedFields = null) : self
|
||||
{
|
||||
if ( null === ( $set = $this->getFragment(Query\Set::class) ) ) {
|
||||
$set = new Query\Set($this);
|
||||
$this->push($set);
|
||||
}
|
||||
|
||||
$set->set($dataset, $escapedFields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete() : self
|
||||
{
|
||||
if ( ! $this->getFragment(Query\Delete::class) ) {
|
||||
$this->push(new Query\Delete());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function from(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
if ( null !== ( $from = $this->getFragment(Query\From::class) ) ) {
|
||||
$from->add($alias ? [ $alias => $table ] : $table);
|
||||
}
|
||||
else {
|
||||
$from = new Query\From($this);
|
||||
$this->push($from);
|
||||
|
||||
$from->set($alias ? [ $alias => $table ] : [ $table ]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function open(string $condition = Query\Where::CONDITION_AND) : self
|
||||
{
|
||||
if ( null !== ($this->where ?? null) ) {
|
||||
$this->where->conditionList[] = $new = new Query\Where($this, $condition);
|
||||
$this->where = $new;
|
||||
}
|
||||
else {
|
||||
$this->where = new Query\Where($this, $condition);
|
||||
$this->push($this->where);
|
||||
$this->where->conditionList[] = $new = new Query\Where($this, $condition);
|
||||
$this->where = $new;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function close() : self
|
||||
{
|
||||
if ( null !== ($this->where ?? null) && $this->where->parent ) {
|
||||
|
||||
# if an enclosure was opened, and nothing done, we must remove the unused node
|
||||
if ( empty($this->where->conditionList) && (count($this->where->parent->conditionList) === 1) ) {
|
||||
unset($this->where->parent->conditionList);
|
||||
}
|
||||
|
||||
$this->where = $this->where->parent;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where(string|\Stringable $field, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
|
||||
{
|
||||
# Empty IN case
|
||||
# if ( [] === $value ) {
|
||||
# return $this;
|
||||
# }
|
||||
|
||||
if ( $this->where ?? false ) {
|
||||
$where = $this->where;
|
||||
}
|
||||
elseif ( null === ( $where = $this->getFragment(Query\Where::class) ) ) {
|
||||
$this->where = $where = new Query\Where($this);
|
||||
$this->push($where);
|
||||
}
|
||||
|
||||
$this->whereConditionOperator = $operator;
|
||||
|
||||
$where->add($field, $value, $operator, $condition, $not);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function notWhere(string|\Stringable $field, mixed $value, string $operator = Query\Where::CONDITION_AND) : self
|
||||
{
|
||||
return $this->where($field, $value, $operator, true);
|
||||
}
|
||||
|
||||
public function having(string|\Stringable $field, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
|
||||
{
|
||||
if ( $this->having ?? false ) {
|
||||
$having = $this->having;
|
||||
}
|
||||
elseif ( null === ( $having = $this->getFragment(Query\Having::class) ) ) {
|
||||
$this->having = $having = new Query\Having($this);
|
||||
$this->push($having);
|
||||
}
|
||||
|
||||
$this->havingConditionOperator = $operator;
|
||||
$having->add($field, $value, $operator, $condition, $not);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit(int $value) : self
|
||||
{
|
||||
if ( null === $limit = $this->getFragment(Query\Limit::class) ) {
|
||||
$limit = new Query\Limit();
|
||||
$this->push($limit);
|
||||
}
|
||||
|
||||
$limit->set($value);
|
||||
|
||||
if ($value === 0) {
|
||||
$this->removeFragment(Query\Limit::class);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function offset(int $value) : self
|
||||
{
|
||||
if ( null === $offset = $this->getFragment(Query\Offset::class) ) {
|
||||
$offset = new Query\Offset();
|
||||
$this->push($offset);
|
||||
|
||||
# A limit is required to match an offset
|
||||
if ( null === $limit = $this->getFragment(Query\Limit::class) ) {
|
||||
$this->limit(\PHP_INT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
$offset->set($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy(string|\Stringable $field, ? string $direction = null) : self
|
||||
{
|
||||
if ( null === $orderBy = $this->getFragment(Query\OrderBy::class) ) {
|
||||
$orderBy = new Query\OrderBy();
|
||||
$this->push($orderBy);
|
||||
}
|
||||
|
||||
$orderBy->add($field, $direction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function groupBy(string|object $field, ? string $direction = null) : self
|
||||
{
|
||||
if ( null === $groupBy = $this->getFragment(Query\GroupBy::class) ) {
|
||||
$groupBy = new Query\GroupBy();
|
||||
$this->push($groupBy);
|
||||
}
|
||||
|
||||
$groupBy->add($field, $direction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function join(string $type, /*string | QueryBuilder*/ $table, mixed $field, mixed $value, bool $outer = false, ? string $alias = null) : self
|
||||
{
|
||||
$this->withJoin(...func_get_args());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withJoin(string $type, $table, mixed $field, mixed $value, bool $outer = false, ? string $alias = null) : Query\Join
|
||||
{
|
||||
$join = new Query\Join($this);
|
||||
|
||||
$this->push($join);
|
||||
|
||||
$join->set($type, $table, $field, $value);
|
||||
|
||||
$join->outer = $outer;
|
||||
|
||||
$join->alias = $alias;
|
||||
|
||||
$join->joinOrder = $this->nextJoinOrder();
|
||||
|
||||
return $join;
|
||||
}
|
||||
|
||||
public function showDatabases() : self
|
||||
{
|
||||
$show = new Query\Show();
|
||||
|
||||
$this->push($show);
|
||||
|
||||
$show->set(Query\Show::SQL_SHOW_DATABASES);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showTables(? string $database = null) : self
|
||||
{
|
||||
$show = new Query\Show();
|
||||
|
||||
$this->push($show);
|
||||
|
||||
$show->set(Query\Show::SQL_SHOW_TABLES, $database);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showColumns(string $table) : self
|
||||
{
|
||||
$show = new Query\Show();
|
||||
|
||||
$this->push($show);
|
||||
|
||||
$show->set(Query\Show::SQL_SHOW_COLUMNS, $table);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function truncate(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$truncate = new Query\Truncate($this);
|
||||
|
||||
$this->push($truncate);
|
||||
|
||||
$truncate->set($table);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function create(Adapter\AdapterInterface $adapter, array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( null === $this->getFragment(Query\Create::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$create = new Query\Create($adapter);
|
||||
$this->push($create);
|
||||
|
||||
$create->fieldList = $fieldlist;
|
||||
$create->table = $table;
|
||||
}
|
||||
else {
|
||||
throw new \Exception("A create SQL fragment was already found within the query builder");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function alter(Adapter\AdapterInterface $adapter, array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( null === $this->getFragment(Query\Alter::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$alter = new Query\Alter($adapter);
|
||||
$this->push($alter);
|
||||
|
||||
$alter->fieldList = $fieldlist;
|
||||
$alter->table = $table;
|
||||
}
|
||||
else {
|
||||
throw new \Exception("A create SQL fragment was already found within the query builder");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function engine(string $value) : self
|
||||
{
|
||||
if ( null === $engine = $this->getFragment(Query\Engine::class) ) {
|
||||
$engine = new Query\Engine();
|
||||
$this->push($engine);
|
||||
}
|
||||
|
||||
$engine->engine = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function push(Query\Fragment $queryFragment) : self
|
||||
{
|
||||
$this->queryStack[] = $queryFragment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function pull(Query\Fragment $queryFragment) : self
|
||||
{
|
||||
return array_shift($this->queryStack);
|
||||
}
|
||||
|
||||
public function render(bool $skipToken = false) /* : mixed */
|
||||
{
|
||||
$sql = [];
|
||||
|
||||
usort($this->queryStack, function($q1, $q2) {
|
||||
return (float) $q1->order() <=> (float) $q2->order();
|
||||
});
|
||||
|
||||
foreach($this->queryStack as $fragment) {
|
||||
$sql[] = $fragment->render($skipToken);
|
||||
}
|
||||
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
|
||||
public function reset() : void
|
||||
{
|
||||
$this->parameters = $this->values = $this->queryStack = [];
|
||||
$this->whereConditionOperator = Query\Where::CONDITION_AND;
|
||||
$this->havingConditionOperator = Query\Where::CONDITION_AND;
|
||||
$this->parameterIndex = 0;
|
||||
|
||||
unset($this->where, $this->having);
|
||||
}
|
||||
|
||||
public function getFragment(string $class, int $index = 0) : ? Query\Fragment
|
||||
{
|
||||
foreach($this->queryStack as $item) {
|
||||
if ( is_a($item, $class, true) ) {
|
||||
if ( $index-- === 0 ) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function removeFragment(Query\Fragment|array|\Stringable|string $fragment) : void
|
||||
{
|
||||
is_object($fragment) && $fragment = get_class($fragment);
|
||||
|
||||
foreach($this->queryStack as $key => $item) {
|
||||
if ( get_class($item) === $fragment ) {
|
||||
unset($this->queryStack[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $fragment === Query\Where::class ) {
|
||||
unset($this->where);
|
||||
}
|
||||
elseif ( $fragment === Query\Having::class ) {
|
||||
unset($this->having);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFragments(Query\Fragment|array|\Stringable|string $fragment = null) : array
|
||||
{
|
||||
return $fragment !== null ? array_filter($this->queryStack, fn($e) => is_a($e, $fragment, true) ) : $this->queryStack;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
public function addParameter(mixed $value, string $key = null) : string
|
||||
{
|
||||
if ( $this->parent ?? false ) {
|
||||
return $this->parent->addParameter($value, $key);
|
||||
}
|
||||
|
||||
if ( $key === null ) {
|
||||
$key = ":p" . $this->parameterIndex++;
|
||||
}
|
||||
|
||||
$this->parameters[$key] = $value;
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
public function addValues(array $values) : void
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
protected function nextJoinOrder() : float
|
||||
{
|
||||
$next = 0;
|
||||
|
||||
foreach($this->getFragments(Query\Join::class) as $join) {
|
||||
$next = max($next, $join->joinOrder - Query\Join::ORDER_VALUE);
|
||||
}
|
||||
|
||||
return $next + 1;
|
||||
}
|
||||
}
|
||||
# Backward compatibility defaulting on MySQL/MariaDB query builder
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\QueryBuilder;
|
||||
namespace Ulmus\QueryBuilder\Sql;
|
||||
|
||||
use Ulmus\{Query, QueryBuilder };
|
||||
use Ulmus\{ Query };
|
||||
use Ulmus\QueryBuilder\SqlQueryBuilder;
|
||||
|
||||
class MssqlQueryBuilder extends QueryBuilder implements Query\QueryBuilderInterface
|
||||
# Soon to extends SqlQueryBuilder
|
||||
class MssqlQueryBuilder extends MysqlQueryBuilder implements Query\QueryBuilderInterface
|
||||
{
|
||||
public function limit(int $value) : self
|
||||
{
|
|
@ -0,0 +1,543 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\QueryBuilder\Sql;
|
||||
|
||||
use Ulmus\{Query, Adapter, QueryBuilder\SqlQueryBuilder};
|
||||
use Ulmus\Query\QueryBuilderInterface;
|
||||
|
||||
class MysqlQueryBuilder extends SqlQueryBuilder
|
||||
{
|
||||
public Query\Where $where;
|
||||
|
||||
public Query\Having $having;
|
||||
|
||||
public QueryBuilderInterface $parent;
|
||||
|
||||
/**
|
||||
* Those are the parameters we are going to bind to PDO.
|
||||
*/
|
||||
public array $parameters = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* Those values are to be inserted or updated
|
||||
*/
|
||||
public array $values = [];
|
||||
|
||||
public string $whereConditionOperator = Query\Where::CONDITION_AND;
|
||||
|
||||
public string $havingConditionOperator = Query\Where::CONDITION_AND;
|
||||
|
||||
protected int $parameterIndex = 0;
|
||||
|
||||
protected array $queryStack = [];
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->where ?? false) {
|
||||
#$this->where = clone $this->where;
|
||||
#$this->where->queryBuilder = $this;
|
||||
}
|
||||
|
||||
if ($this->having ?? false) {
|
||||
#$this->having = clone $this->having;
|
||||
#$this->having->queryBuiler = $this;
|
||||
}
|
||||
|
||||
if ($this->parent ?? false) {
|
||||
#$this->parent = clone $this->parent;
|
||||
}
|
||||
}
|
||||
|
||||
public function select(string|\Stringable|array $field, bool $distinct = false) : self
|
||||
{
|
||||
if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) {
|
||||
$select->add($field);
|
||||
}
|
||||
else {
|
||||
$select = new Query\Select();
|
||||
$select->set($field);
|
||||
$this->push($select);
|
||||
}
|
||||
|
||||
$select->distinct = $distinct;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null, bool $replace = false) : self
|
||||
{
|
||||
if ( null === $this->getFragment(Query\Insert::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$insert = new Query\Insert();
|
||||
$this->push($insert);
|
||||
|
||||
$insert->replace = $replace;
|
||||
$insert->fieldlist = $fieldlist;
|
||||
$insert->alias = $alias;
|
||||
$insert->table = $table;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function values(array $dataset) : self
|
||||
{
|
||||
if ( null === ( $values = $this->getFragment(Query\Values::class) ) ) {
|
||||
$values = new Query\Values($this);
|
||||
$this->push($values);
|
||||
}
|
||||
|
||||
$values->add($dataset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function update(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( ! $this->getFragment(Query\Update::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$update = new Query\Update();
|
||||
$this->push($update);
|
||||
|
||||
$update->alias = $alias;
|
||||
$update->table = $table;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set(array $dataset, ? array $escapedFields = null) : self
|
||||
{
|
||||
if ( null === ( $set = $this->getFragment(Query\Set::class) ) ) {
|
||||
$set = new Query\Set($this);
|
||||
$this->push($set);
|
||||
}
|
||||
|
||||
$set->set($dataset, $escapedFields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete() : self
|
||||
{
|
||||
if ( ! $this->getFragment(Query\Delete::class) ) {
|
||||
$this->push(new Query\Delete());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function from(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
if ( null !== ( $from = $this->getFragment(Query\From::class) ) ) {
|
||||
$from->add($alias ? [ $alias => $table ] : $table);
|
||||
}
|
||||
else {
|
||||
$from = new Query\From($this);
|
||||
$this->push($from);
|
||||
|
||||
$from->set($alias ? [ $alias => $table ] : [ $table ]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function open(string $condition = Query\Where::CONDITION_AND) : self
|
||||
{
|
||||
if ( null !== ($this->where ?? null) ) {
|
||||
$this->where->conditionList[] = $new = new Query\Where($this, $condition);
|
||||
$this->where = $new;
|
||||
}
|
||||
else {
|
||||
$this->where = new Query\Where($this, $condition);
|
||||
$this->push($this->where);
|
||||
$this->where->conditionList[] = $new = new Query\Where($this, $condition);
|
||||
$this->where = $new;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function close() : self
|
||||
{
|
||||
if ( null !== ($this->where ?? null) && $this->where->parent ) {
|
||||
|
||||
# if an enclosure was opened, and nothing done, we must remove the unused node
|
||||
if ( empty($this->where->conditionList) && (count($this->where->parent->conditionList) === 1) ) {
|
||||
unset($this->where->parent->conditionList);
|
||||
}
|
||||
|
||||
$this->where = $this->where->parent;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where(string|\Stringable $field, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
|
||||
{
|
||||
# Empty IN case
|
||||
# if ( [] === $value ) {
|
||||
# return $this;
|
||||
# }
|
||||
|
||||
if ( $this->where ?? false ) {
|
||||
$where = $this->where;
|
||||
}
|
||||
elseif ( null === ( $where = $this->getFragment(Query\Where::class) ) ) {
|
||||
$this->where = $where = new Query\Where($this);
|
||||
$this->push($where);
|
||||
}
|
||||
|
||||
$this->whereConditionOperator = $operator;
|
||||
|
||||
$where->add($field, $value, $operator, $condition, $not);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function notWhere(string|\Stringable $field, mixed $value, string $operator = Query\Where::CONDITION_AND) : self
|
||||
{
|
||||
return $this->where($field, $value, $operator, true);
|
||||
}
|
||||
|
||||
public function having(string|\Stringable $field, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
|
||||
{
|
||||
if ( $this->having ?? false ) {
|
||||
$having = $this->having;
|
||||
}
|
||||
elseif ( null === ( $having = $this->getFragment(Query\Having::class) ) ) {
|
||||
$this->having = $having = new Query\Having($this);
|
||||
$this->push($having);
|
||||
}
|
||||
|
||||
$this->havingConditionOperator = $operator;
|
||||
$having->add($field, $value, $operator, $condition, $not);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit(int $value) : self
|
||||
{
|
||||
if ( null === $limit = $this->getFragment(Query\Limit::class) ) {
|
||||
$limit = new Query\Limit();
|
||||
$this->push($limit);
|
||||
}
|
||||
|
||||
$limit->set($value);
|
||||
|
||||
if ($value === 0) {
|
||||
$this->removeFragment(Query\Limit::class);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function offset(int $value) : self
|
||||
{
|
||||
if ( null === $offset = $this->getFragment(Query\Offset::class) ) {
|
||||
$offset = new Query\Offset();
|
||||
$this->push($offset);
|
||||
|
||||
# A limit is required to match an offset
|
||||
if ( null === $limit = $this->getFragment(Query\Limit::class) ) {
|
||||
$this->limit(\PHP_INT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
$offset->set($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy(string|\Stringable $field, ? string $direction = null) : self
|
||||
{
|
||||
if ( null === $orderBy = $this->getFragment(Query\OrderBy::class) ) {
|
||||
$orderBy = new Query\OrderBy();
|
||||
$this->push($orderBy);
|
||||
}
|
||||
|
||||
$orderBy->add($field, $direction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function groupBy(string|object $field, ? string $direction = null) : self
|
||||
{
|
||||
if ( null === $groupBy = $this->getFragment(Query\GroupBy::class) ) {
|
||||
$groupBy = new Query\GroupBy();
|
||||
$this->push($groupBy);
|
||||
}
|
||||
|
||||
$groupBy->add($field, $direction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function join(string $type, /*string | QueryBuilder*/ $table, mixed $field, mixed $value, bool $outer = false, ? string $alias = null) : self
|
||||
{
|
||||
$this->withJoin(...func_get_args());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withJoin(string $type, $table, mixed $field, mixed $value, bool $outer = false, ? string $alias = null) : Query\Join
|
||||
{
|
||||
$join = new Query\Join($this);
|
||||
|
||||
$this->push($join);
|
||||
|
||||
$join->set($type, $table, $field, $value);
|
||||
|
||||
$join->outer = $outer;
|
||||
|
||||
$join->alias = $alias;
|
||||
|
||||
$join->joinOrder = $this->nextJoinOrder();
|
||||
|
||||
return $join;
|
||||
}
|
||||
|
||||
public function showDatabases() : self
|
||||
{
|
||||
$show = new Query\Show();
|
||||
|
||||
$this->push($show);
|
||||
|
||||
$show->set(Query\Show::SQL_SHOW_DATABASES);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showTables(? string $database = null) : self
|
||||
{
|
||||
$show = new Query\Show();
|
||||
|
||||
$this->push($show);
|
||||
|
||||
$show->set(Query\Show::SQL_SHOW_TABLES, $database);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showColumns(string $table) : self
|
||||
{
|
||||
$show = new Query\Show();
|
||||
|
||||
$this->push($show);
|
||||
|
||||
$show->set(Query\Show::SQL_SHOW_COLUMNS, $table);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function truncate(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$truncate = new Query\Truncate($this);
|
||||
|
||||
$this->push($truncate);
|
||||
|
||||
$truncate->set($table);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function create(Adapter\AdapterInterface $adapter, array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( null === $this->getFragment(Query\Create::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$create = new Query\Create($adapter);
|
||||
$this->push($create);
|
||||
|
||||
$create->fieldList = $fieldlist;
|
||||
$create->table = $table;
|
||||
}
|
||||
else {
|
||||
throw new \Exception("A create SQL fragment was already found within the query builder");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function alter(Adapter\AdapterInterface $adapter, array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( null === $this->getFragment(Query\Alter::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$alter = new Query\Alter($adapter);
|
||||
$this->push($alter);
|
||||
|
||||
$alter->fieldList = $fieldlist;
|
||||
$alter->table = $table;
|
||||
}
|
||||
else {
|
||||
throw new \Exception("A create SQL fragment was already found within the query builder");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function engine(string $value) : self
|
||||
{
|
||||
if ( null === $engine = $this->getFragment(Query\Engine::class) ) {
|
||||
$engine = new Query\Engine();
|
||||
$this->push($engine);
|
||||
}
|
||||
|
||||
$engine->engine = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function push(Query\Fragment $queryFragment) : self
|
||||
{
|
||||
$this->queryStack[] = $queryFragment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function pull(Query\Fragment $queryFragment) : self
|
||||
{
|
||||
return array_shift($this->queryStack);
|
||||
}
|
||||
|
||||
public function render(bool $skipToken = false) /* : mixed */
|
||||
{
|
||||
$sql = [];
|
||||
|
||||
usort($this->queryStack, function($q1, $q2) {
|
||||
return (float) $q1->order() <=> (float) $q2->order();
|
||||
});
|
||||
|
||||
foreach($this->queryStack as $fragment) {
|
||||
$sql[] = $fragment->render($skipToken);
|
||||
}
|
||||
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
|
||||
public function reset() : void
|
||||
{
|
||||
$this->parameters = $this->values = $this->queryStack = [];
|
||||
$this->whereConditionOperator = Query\Where::CONDITION_AND;
|
||||
$this->havingConditionOperator = Query\Where::CONDITION_AND;
|
||||
$this->parameterIndex = 0;
|
||||
|
||||
unset($this->where, $this->having);
|
||||
}
|
||||
|
||||
public function getFragment(string $class, int $index = 0) : ? Query\Fragment
|
||||
{
|
||||
foreach($this->queryStack as $item) {
|
||||
if ( is_a($item, $class, true) ) {
|
||||
if ( $index-- === 0 ) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function removeFragment(Query\Fragment|array|\Stringable|string $fragment) : void
|
||||
{
|
||||
is_object($fragment) && $fragment = get_class($fragment);
|
||||
|
||||
foreach($this->queryStack as $key => $item) {
|
||||
if ( get_class($item) === $fragment ) {
|
||||
unset($this->queryStack[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $fragment === Query\Where::class ) {
|
||||
unset($this->where);
|
||||
}
|
||||
elseif ( $fragment === Query\Having::class ) {
|
||||
unset($this->having);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFragments(Query\Fragment|array|\Stringable|string $fragment = null) : array
|
||||
{
|
||||
return $fragment !== null ? array_filter($this->queryStack, fn($e) => is_a($e, $fragment, true) ) : $this->queryStack;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
public function addParameter(mixed $value, string $key = null) : string
|
||||
{
|
||||
if ( $this->parent ?? false ) {
|
||||
return $this->parent->addParameter($value, $key);
|
||||
}
|
||||
|
||||
if ( $key === null ) {
|
||||
$key = ":p" . $this->parameterIndex++;
|
||||
}
|
||||
|
||||
$this->parameters[$key] = $value;
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
public function addValues(array $values) : void
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
protected function nextJoinOrder() : float
|
||||
{
|
||||
$next = 0;
|
||||
|
||||
foreach($this->getFragments(Query\Join::class) as $join) {
|
||||
$next = max($next, $join->joinOrder - Query\Join::ORDER_VALUE);
|
||||
}
|
||||
|
||||
return $next + 1;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\QueryBuilder;
|
||||
|
||||
use Ulmus\QueryBuilder;
|
||||
namespace Ulmus\QueryBuilder\Sql;
|
||||
|
||||
use Ulmus\Query;
|
||||
use Ulmus\QueryBuilder\SqlQueryBuilder;
|
||||
|
||||
class SqliteQueryBuilder extends QueryBuilder implements Query\QueryBuilderInterface
|
||||
# Soon to extends SqlQueryBuilder
|
||||
class SqliteQueryBuilder extends MysqlQueryBuilder implements Query\QueryBuilderInterface
|
||||
{
|
||||
public function pragma(/*object|Stringable*/ $name, $value = null, bool $callable = false) : self
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ class SqliteQueryBuilder extends QueryBuilder implements Query\QueryBuilderInter
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function showColumns(string $table) : self
|
||||
{
|
||||
$this->pragma('table_info', $table, true);
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\QueryBuilder;
|
||||
|
||||
use Ulmus\Query;
|
||||
use Ulmus\Query\Fragment;
|
||||
use Ulmus\Query\QueryBuilderInterface;
|
||||
|
||||
# TODO -> Extract from MysqlQueryBuilder to build an ISO/IEC 9075:2023 compatible layer for a basic SQL QueryBuilder
|
||||
class SqlQueryBuilder implements Query\QueryBuilderInterface
|
||||
{
|
||||
|
||||
public function push(Fragment $queryFragment): Query\QueryBuilderInterface
|
||||
{
|
||||
// TODO: Implement push() method.
|
||||
}
|
||||
|
||||
public function pull(Fragment $queryFragment): Query\QueryBuilderInterface
|
||||
{
|
||||
// TODO: Implement pull() method.
|
||||
}
|
||||
|
||||
public function render(bool $skipToken = false)
|
||||
{
|
||||
// TODO: Implement render() method.
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
// TODO: Implement reset() method.
|
||||
}
|
||||
|
||||
public function getFragment(string $class, int $index = 0): ?Fragment
|
||||
{
|
||||
// TODO: Implement getFragment() method.
|
||||
}
|
||||
|
||||
public function removeFragment(Fragment|\Stringable|array|string $fragment): void
|
||||
{
|
||||
// TODO: Implement removeFragment() method.
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\{Common\EntityResolver, ConnectionAdapter, QueryBuilder, Repository, Query, Ulmus};
|
||||
use Ulmus\{Common\EntityResolver, ConnectionAdapter, MysqlQueryBuilder, Repository, Query, Ulmus};
|
||||
|
||||
class MysqlRepository extends Repository {
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\{ConnectionAdapter, QueryBuilder, Repository, Query, Ulmus, Entity};
|
||||
use Ulmus\{ConnectionAdapter, MysqlQueryBuilder, Repository, Query, Ulmus, Entity};
|
||||
|
||||
class SqliteRepository extends Repository {
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class SearchEqual extends SearchWhere {}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest\Attribute;
|
||||
|
||||
use Ulmus\SearchRequest\SearchMethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class SearchGroupBy extends SearchParameter
|
||||
{
|
||||
public function __construct(
|
||||
public ? string $parameter = null,
|
||||
public ? string $field = null,
|
||||
public bool $toggle = false,
|
||||
public SearchMethodEnum $method = SearchMethodEnum::GroupBy,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest\Attribute;
|
||||
|
||||
use Ulmus\SearchRequest\SearchMethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class SearchLike extends SearchParameter
|
||||
{
|
||||
public function __construct(
|
||||
public ? string $parameter = null,
|
||||
public ? string $field = null,
|
||||
public bool $toggle = false,
|
||||
public SearchMethodEnum $method = SearchMethodEnum::Like,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest\Attribute;
|
||||
|
||||
use Ulmus\SearchRequest\SearchMethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class SearchOrderBy extends SearchParameter
|
||||
{
|
||||
public function __construct(
|
||||
public ? string $parameter = null,
|
||||
public ? string $field = null,
|
||||
public bool $toggle = false,
|
||||
public SearchMethodEnum $method = SearchMethodEnum::OrderByAsc,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest\Attribute;
|
||||
|
||||
use Ulmus\SearchRequest\SearchMethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class SearchParameter
|
||||
{
|
||||
public function __construct(
|
||||
public ? string $parameter = null,
|
||||
public ? string $field = null,
|
||||
public bool $toggle = false,
|
||||
public SearchMethodEnum $method = SearchMethodEnum::Manual,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest\Attribute;
|
||||
|
||||
use Ulmus\SearchRequest\SearchMethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class SearchRequestParameter
|
||||
{
|
||||
public function __construct(
|
||||
public string $class,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest\Attribute;
|
||||
|
||||
use Ulmus\SearchRequest\SearchMethodEnum;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class SearchWhere extends SearchParameter
|
||||
{
|
||||
public function __construct(
|
||||
public ? string $parameter = null,
|
||||
public ? string $field = null,
|
||||
public bool $toggle = false,
|
||||
public SearchMethodEnum $method = SearchMethodEnum::Where,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest;
|
||||
|
||||
enum SearchMethodEnum
|
||||
{
|
||||
case Manual;
|
||||
case Where;
|
||||
case Like;
|
||||
case LikeLeft;
|
||||
case LikeRight;
|
||||
case OrderByAsc;
|
||||
case OrderByDesc;
|
||||
case GroupBy;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\Repository;
|
||||
|
||||
class SearchRequest implements SearchRequestInterface
|
||||
{
|
||||
use SearchRequestPaginationTrait, SearchRequestFromRequestTrait;
|
||||
|
||||
public function filter(Repository $repository) : Repository
|
||||
{
|
||||
return $repository;
|
||||
}
|
||||
|
||||
public function wheres() : iterable
|
||||
{
|
||||
return array_filter($this->wheres + [
|
||||
], fn($i) => ! is_null($i) ) + [ ];
|
||||
}
|
||||
|
||||
public function likes(): iterable
|
||||
{
|
||||
return array_filter($this->likes + [
|
||||
], fn($i) => ! is_null($i) ) + [];
|
||||
}
|
||||
|
||||
public function groups(): iterable
|
||||
{
|
||||
return array_filter($this->groups + [
|
||||
], fn($e) => ! is_null($e) && $e !== "" );
|
||||
}
|
||||
|
||||
public function orders(): iterable
|
||||
{
|
||||
return array_filter($this->orders + [
|
||||
], fn($e) => ! is_null($e) && $e !== "" );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\SearchRequest;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\SearchRequest\Attribute\SearchParameter;
|
||||
use Ulmus\SearchRequest\Attribute\SearchWhere;
|
||||
|
||||
trait SearchRequestFromRequestTrait
|
||||
{
|
||||
protected array $wheres = [];
|
||||
|
||||
protected array $likes = [];
|
||||
|
||||
protected array $groups = [];
|
||||
|
||||
protected array $orders = [];
|
||||
|
||||
public function fromRequest(ServerRequestInterface $request)
|
||||
{
|
||||
$get = new \ArrayObject(array_filter($request->getQueryParams(), function($i) { return $i !== ""; }));
|
||||
|
||||
$this->page = $get->offsetExists('page') ? $get['page'] : 1;
|
||||
|
||||
$classReflection = new \ReflectionClass($this);
|
||||
|
||||
foreach($classReflection->getProperties() as $property) {
|
||||
|
||||
foreach($property->getAttributes() as $attributeReflection) {
|
||||
$attribute = $attributeReflection->newInstance();
|
||||
|
||||
# We can't simply pass this class as first arguments of getAttributes() since it do not check for inheritance
|
||||
if (! $attribute instanceof Attribute\SearchParameter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$propertyName = $property->getName();
|
||||
$fieldName = $attribute->field ?? $property->getName();
|
||||
$queryParamName = $attribute->parameter ?? $property->getName();
|
||||
|
||||
if ($attribute->toggle) {
|
||||
$this->$propertyName = $get->offsetExists('rental');
|
||||
} else {
|
||||
$this->$propertyName = $get->offsetExists($queryParamName) ? $get[$queryParamName] : null;
|
||||
}
|
||||
|
||||
$field = (string) $fieldName;
|
||||
|
||||
switch ($attribute->method) {
|
||||
case SearchMethodEnum::Where:
|
||||
$this->wheres[$field] = $this->$propertyName;
|
||||
break;
|
||||
|
||||
case SearchMethodEnum::Like:
|
||||
$this->likes[$field] = "%{$this->$propertyName}%";
|
||||
break;
|
||||
|
||||
case SearchMethodEnum::LikeLeft:
|
||||
$this->likes[$field] = "%{$this->$propertyName}";
|
||||
break;
|
||||
|
||||
case SearchMethodEnum::LikeRight:
|
||||
$this->likes[$field] = "{$this->$propertyName}%";
|
||||
break;
|
||||
|
||||
case SearchMethodEnum::OrderByAsc:
|
||||
$this->orders[$field] = "ASC";
|
||||
break;
|
||||
|
||||
case SearchMethodEnum::OrderByDesc:
|
||||
$this->orders[$field] = "DESC";
|
||||
break;
|
||||
|
||||
case SearchMethodEnum::GroupBy:
|
||||
$this->groups[$field] = "DESC";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue