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; } }