- Adapter some code to allow more flexibility for the new LDAP adapter from ulmus-ldap. - Insert's fields are now escaped by default. - A new RelationBuilder was added, a lot of code from the EntityTrait was moved into this. Some code from the Repository class will need to be moved there too.
448 lines
12 KiB
PHP
448 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Ulmus;
|
|
|
|
use Ulmus\Query\QueryBuilderInterface;
|
|
|
|
class QueryBuilder implements Query\QueryBuilderInterface
|
|
{
|
|
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($field) : self
|
|
{
|
|
if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) {
|
|
$select->add($field);
|
|
}
|
|
else {
|
|
$select = new Query\Select();
|
|
$select->set($field);
|
|
$this->push($select);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : 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->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) : self
|
|
{
|
|
|
|
if ( null === ( $set = $this->getFragment(Query\Set::class) ) ) {
|
|
$set = new Query\Set($this);
|
|
$this->push($set);
|
|
}
|
|
|
|
$set->set($dataset);
|
|
|
|
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(/* stringable*/ $field, $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($field, $value, string $operator = Query\Where::CONDITION_AND) : self
|
|
{
|
|
return $this->where($field, $value, $operator, true);
|
|
}
|
|
|
|
public function having($field, $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);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function offset(int $value) : self
|
|
{
|
|
if ( null === $offset = $this->getFragment(Query\Offset::class) ) {
|
|
$offset = new Query\Offset();
|
|
$this->push($offset);
|
|
}
|
|
|
|
$offset->set($value);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function orderBy(string $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 $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, $field, $value, bool $outer = false, ? string $alias = null) : self
|
|
{
|
|
$this->withJoin(...func_get_args());
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function withJoin(string $type, $table, $field, $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;
|
|
|
|
return $join;
|
|
}
|
|
|
|
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(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();
|
|
$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 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 $q1->order <=> $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 ( get_class($item) === $class ) {
|
|
if ( $index-- === 0 ) {
|
|
return $item;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function removeFragment(Query\Fragment $fragment) : void
|
|
{
|
|
foreach($this->queryStack as $key => $item) {
|
|
if ( $item === $fragment ) {
|
|
unset($this->queryStack[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function __toString() : string
|
|
{
|
|
return $this->render();
|
|
}
|
|
|
|
public function addParameter($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;
|
|
}
|
|
}
|