- 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
									
								
								docs/50-search-request.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								docs/50-search-request.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										121
									
								
								src/Adapter/DefaultAdapterTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/Adapter/DefaultAdapterTrait.php
									
									
									
									
									
										Normal file
									
								
							| @ -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 |     public function queryBuilderClass() : string | ||||||
|     { |     { | ||||||
|         return QueryBuilder\MssqlQueryBuilder::class; |         return QueryBuilder\SqlQueryBuilder\MssqlQueryBuilder::class; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,15 +2,11 @@ | |||||||
| 
 | 
 | ||||||
| namespace Ulmus\Adapter; | namespace Ulmus\Adapter; | ||||||
| 
 | 
 | ||||||
| use Ulmus\Entity\InformationSchema\Table; |  | ||||||
| use Ulmus\Migration\MigrateInterface; | use Ulmus\Migration\MigrateInterface; | ||||||
| use Ulmus\QueryBuilder; | use Ulmus\QueryBuilder\Sql; | ||||||
| use Ulmus\Repository; |  | ||||||
| use Ulmus\Common\PdoObject; | use Ulmus\Common\PdoObject; | ||||||
| 
 | 
 | ||||||
| use Ulmus\Exception\AdapterConfigurationException; | use Ulmus\Exception\AdapterConfigurationException; | ||||||
| use Ulmus\Ulmus; |  | ||||||
| use Ulmus\Migration\FieldDefinition; |  | ||||||
| 
 | 
 | ||||||
| class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { | class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { | ||||||
|     use SqlAdapterTrait; |     use SqlAdapterTrait; | ||||||
| @ -157,4 +153,10 @@ class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { | |||||||
|     { |     { | ||||||
|         return "InnoDB"; |         return "InnoDB"; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function queryBuilderClass() : string | ||||||
|  |     { | ||||||
|  |         return Sql\MysqlQueryBuilder::class; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,10 +5,9 @@ namespace Ulmus\Adapter; | |||||||
| use Ulmus\Common\PdoObject; | use Ulmus\Common\PdoObject; | ||||||
| use Ulmus\ConnectionAdapter; | use Ulmus\ConnectionAdapter; | ||||||
| 
 | 
 | ||||||
| use Ulmus\Entity\Sqlite\Table; | use Ulmus\Entity; | ||||||
| use Ulmus\Exception\AdapterConfigurationException; |  | ||||||
| use Ulmus\Migration\FieldDefinition; | use Ulmus\Migration\FieldDefinition; | ||||||
| use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder, Ulmus}; | use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder}; | ||||||
| 
 | 
 | ||||||
| class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface { | class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface { | ||||||
|     use SqlAdapterTrait; |     use SqlAdapterTrait; | ||||||
| @ -90,9 +89,9 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface | |||||||
| 
 | 
 | ||||||
|     public function schemaTable(ConnectionAdapter $adapter, string $databaseName, string $tableName) : null|object |     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.*')) |             ->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 |     public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string | ||||||
| @ -150,7 +149,7 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface | |||||||
| 
 | 
 | ||||||
|     public function queryBuilderClass() : string |     public function queryBuilderClass() : string | ||||||
|     { |     { | ||||||
|         return QueryBuilder\SqliteQueryBuilder::class; |         return QueryBuilder\Sql\SqliteQueryBuilder::class; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function exportFunctions(PdoObject $pdo) : void |     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 |     public static function registerPragma(PdoObject $pdo, array $pragmaList) : void | ||||||
|     { |     { | ||||||
|         $builder = new QueryBuilder\SqliteQueryBuilder(); |         $builder = new QueryBuilder\Sql\SqliteQueryBuilder(); | ||||||
| 
 | 
 | ||||||
|         foreach($pragmaList as $pragma) { |         foreach($pragmaList as $pragma) { | ||||||
|             list($key, $value) = explode('=', $pragma) + [ null, null ]; |             list($key, $value) = explode('=', $pragma) + [ null, null ]; | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ use Ulmus\{Common\Sql, | |||||||
|     Entity\InformationSchema\Table, |     Entity\InformationSchema\Table, | ||||||
|     Migration\FieldDefinition, |     Migration\FieldDefinition, | ||||||
|     Repository, |     Repository, | ||||||
|     QueryBuilder, |     QueryBuilder\SqlQueryBuilder, | ||||||
|     Ulmus}; |     Ulmus}; | ||||||
| 
 | 
 | ||||||
| trait SqlAdapterTrait | trait SqlAdapterTrait | ||||||
| @ -19,7 +19,7 @@ trait SqlAdapterTrait | |||||||
| 
 | 
 | ||||||
|     public function queryBuilderClass() : string |     public function queryBuilderClass() : string | ||||||
|     { |     { | ||||||
|         return QueryBuilder::class; |         return SqlQueryBuilder::class; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function tableSyntax() : array |     public function tableSyntax() : array | ||||||
|  | |||||||
| @ -4,19 +4,16 @@ namespace Ulmus; | |||||||
| 
 | 
 | ||||||
| use Notes\Attribute\Ignore; | use Notes\Attribute\Ignore; | ||||||
| use Psr\Http\Message\ServerRequestInterface; | use Psr\Http\Message\ServerRequestInterface; | ||||||
| use Ulmus\{Common\EntityResolver, | use Ulmus\{Common\EntityResolver, Common\EntityField, Entity\EntityInterface, Query\QueryBuilderInterface}; | ||||||
|     Common\EntityField, | use Ulmus\SearchRequest\{Attribute\SearchParameter, | ||||||
|     Entity\EntityInterface, |     SearchMethodEnum, | ||||||
|     Query\QueryBuilderInterface, |     SearchRequestInterface, | ||||||
|     SearchRequest\SearchRequestInterface, |     SearchRequestFromRequestTrait, | ||||||
|     SearchRequest\SearchRequestPaginationTrait}; |     SearchRequestPaginationTrait}; | ||||||
| 
 | 
 | ||||||
| trait EntityTrait { | trait EntityTrait { | ||||||
|     use EventTrait; |     use EventTrait; | ||||||
| 
 | 
 | ||||||
|     #[Ignore]
 |  | ||||||
|     public string $loadedFromAdapter; |  | ||||||
| 
 |  | ||||||
|     #[Ignore]
 |     #[Ignore]
 | ||||||
|     protected bool $entityStrictFieldsDeclaration = false; |     protected bool $entityStrictFieldsDeclaration = false; | ||||||
| 
 | 
 | ||||||
| @ -327,6 +324,7 @@ trait EntityTrait { | |||||||
|     #[Ignore]
 |     #[Ignore]
 | ||||||
|     public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField |     public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField | ||||||
|     { |     { | ||||||
|  | 
 | ||||||
|         $default = ( $alias === false ? '' : Repository::DEFAULT_ALIAS ); # bw compatibility, to be deprecated
 |         $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)); |         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]
 |     #[Ignore]
 | ||||||
|     public static function searchRequest(...$arguments) : SearchRequestInterface |     public static function searchRequest(...$arguments) : SearchRequestInterface | ||||||
|     { |     { | ||||||
|         return new class() implements SearchRequestInterface, \JsonSerializable |         return new #[SearchRequest\Attribute\SearchRequestParameter(self::class)] class(... $arguments) extends SearchRequest\SearchRequest {
 | ||||||
|         { |             # Define searchable properties here, some ex:
 | ||||||
|             use SearchRequestPaginationTrait; |  | ||||||
| 
 | 
 | ||||||
|             public function fromRequest(ServerRequestInterface $request) : self |             #  #[SearchParameter(method: SearchMethodEnum::Where)]
 | ||||||
|             { |             #  public ? string $username = null;
 | ||||||
|                 $get = new \ArrayObject(array_filter($request->getQueryParams(), function($i) { return $i !== ""; })); |  | ||||||
| 
 | 
 | ||||||
|                 $this->page = $get->offsetExists('page') ? $get['page'] : 1; |             #  #[SearchParameter(method: SearchMethodEnum::Where, toggle: true)]
 | ||||||
|                 $this->limit = 100; |             #  public ? string $hidden = null;
 | ||||||
| 
 | 
 | ||||||
|                 return $this; |             #  #[SearchParameter(method: SearchMethodEnum::Like)]
 | ||||||
|             } |             #  public ? string $word = null;
 | ||||||
| 
 |  | ||||||
|             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 !== "" ); |  | ||||||
|             } |  | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace Ulmus\Query; | namespace Ulmus\Query; | ||||||
| 
 | 
 | ||||||
| use Ulmus\QueryBuilder; | use Ulmus\MysqlQueryBuilder; | ||||||
| 
 | 
 | ||||||
| use Ulmus\Common\EntityField, | use Ulmus\Common\EntityField, | ||||||
|     Ulmus\Common\Sql; |     Ulmus\Common\Sql; | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace Ulmus\Query; | namespace Ulmus\Query; | ||||||
| 
 | 
 | ||||||
| use Ulmus\QueryBuilder; | use Ulmus\MysqlQueryBuilder; | ||||||
| use Ulmus\Repository\ConditionTrait; | use Ulmus\Repository\ConditionTrait; | ||||||
| 
 | 
 | ||||||
| class Join extends Fragment | class Join extends Fragment | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace Ulmus\Query; | namespace Ulmus\Query; | ||||||
| 
 | 
 | ||||||
| use Ulmus\QueryBuilder; | use Ulmus\MysqlQueryBuilder; | ||||||
| 
 | 
 | ||||||
| class Set extends Fragment { | class Set extends Fragment { | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,10 +10,14 @@ class Show extends Fragment { | |||||||
| 
 | 
 | ||||||
|     const SQL_TOKEN_FROM = "FROM"; |     const SQL_TOKEN_FROM = "FROM"; | ||||||
| 
 | 
 | ||||||
|  |     const SQL_TOKEN_IN = "IN"; | ||||||
|  | 
 | ||||||
|     const SQL_SHOW_DATABASES = "DATABASES"; |     const SQL_SHOW_DATABASES = "DATABASES"; | ||||||
| 
 | 
 | ||||||
|     const SQL_SHOW_TABLES = "TABLES"; |     const SQL_SHOW_TABLES = "TABLES"; | ||||||
| 
 | 
 | ||||||
|  |     const SQL_SHOW_COLUMNS = "COLUMNS"; | ||||||
|  | 
 | ||||||
|     public string $show; |     public string $show; | ||||||
| 
 | 
 | ||||||
|     public string $from; |     public string $from; | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace Ulmus\Query; | namespace Ulmus\Query; | ||||||
| 
 | 
 | ||||||
| use Ulmus\QueryBuilder; | use Ulmus\MysqlQueryBuilder; | ||||||
| 
 | 
 | ||||||
| class Values extends Fragment { | class Values extends Fragment { | ||||||
| 
 | 
 | ||||||
| @ -12,9 +12,9 @@ class Values extends Fragment { | |||||||
|      |      | ||||||
|     public array $rows; |     public array $rows; | ||||||
|      |      | ||||||
|     public QueryBuilder $queryBuilder; |     public MysqlQueryBuilder $queryBuilder; | ||||||
| 
 | 
 | ||||||
|     public function __construct(QueryBuilder $queryBuilder) |     public function __construct(MysqlQueryBuilder $queryBuilder) | ||||||
|     { |     { | ||||||
|         $this->queryBuilder = $queryBuilder; |         $this->queryBuilder = $queryBuilder; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,541 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace Ulmus; | namespace Ulmus; | ||||||
| 
 | 
 | ||||||
| use Ulmus\Query\QueryBuilderInterface; | class QueryBuilder extends QueryBuilder\MysqlQueryBuilder | ||||||
| 
 |  | ||||||
| class QueryBuilder implements Query\QueryBuilderInterface |  | ||||||
| { | { | ||||||
|     public Query\Where $where; |     # Backward compatibility defaulting on MySQL/MariaDB query builder
 | ||||||
| 
 | } | ||||||
|     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,10 +1,12 @@ | |||||||
| <?php | <?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 |     public function limit(int $value) : self | ||||||
|     { |     { | ||||||
							
								
								
									
										543
									
								
								src/QueryBuilder/Sql/MysqlQueryBuilder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										543
									
								
								src/QueryBuilder/Sql/MysqlQueryBuilder.php
									
									
									
									
									
										Normal file
									
								
							| @ -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 | <?php | ||||||
| 
 | 
 | ||||||
| namespace Ulmus\QueryBuilder; | namespace Ulmus\QueryBuilder\Sql; | ||||||
| 
 |  | ||||||
| use Ulmus\QueryBuilder; |  | ||||||
| 
 | 
 | ||||||
| use Ulmus\Query; | 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 |     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; |         return $this; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     public function showColumns(string $table) : self |     public function showColumns(string $table) : self | ||||||
|     { |     { | ||||||
|         $this->pragma('table_info', $table, true); |         $this->pragma('table_info', $table, true); | ||||||
							
								
								
									
										42
									
								
								src/QueryBuilder/SqlQueryBuilder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/QueryBuilder/SqlQueryBuilder.php
									
									
									
									
									
										Normal file
									
								
							| @ -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; | 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 { | class MysqlRepository extends Repository { | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace Ulmus\Repository; | namespace Ulmus\Repository; | ||||||
| 
 | 
 | ||||||
| use Ulmus\{ConnectionAdapter, QueryBuilder, Repository, Query, Ulmus, Entity}; | use Ulmus\{ConnectionAdapter, MysqlQueryBuilder, Repository, Query, Ulmus, Entity}; | ||||||
| 
 | 
 | ||||||
| class SqliteRepository extends Repository { | class SqliteRepository extends Repository { | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								src/SearchRequest/Attribute/SearchEqual.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/SearchRequest/Attribute/SearchEqual.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace Ulmus\SearchRequest\Attribute; | ||||||
|  | 
 | ||||||
|  | #[\Attribute(\Attribute::TARGET_PROPERTY)]
 | ||||||
|  | class SearchEqual extends SearchWhere {} | ||||||
							
								
								
									
										16
									
								
								src/SearchRequest/Attribute/SearchGroupBy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/SearchRequest/Attribute/SearchGroupBy.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  |     ) {} | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/SearchRequest/Attribute/SearchLike.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/SearchRequest/Attribute/SearchLike.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  |     ) {} | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/SearchRequest/Attribute/SearchOrderBy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/SearchRequest/Attribute/SearchOrderBy.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  |     ) {} | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/SearchRequest/Attribute/SearchParameter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/SearchRequest/Attribute/SearchParameter.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  |     ) {} | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								src/SearchRequest/Attribute/SearchRequestParameter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/SearchRequest/Attribute/SearchRequestParameter.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  |     ) {} | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/SearchRequest/Attribute/SearchWhere.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/SearchRequest/Attribute/SearchWhere.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  |     ) {} | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								src/SearchRequest/SearchMethodEnum.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/SearchRequest/SearchMethodEnum.php
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								src/SearchRequest/SearchRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/SearchRequest/SearchRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -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 !== "" ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										83
									
								
								src/SearchRequest/SearchRequestFromRequestTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/SearchRequest/SearchRequestFromRequestTrait.php
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user