From 0db7095527b5d83b4bbb35d67d40a792507528a1 Mon Sep 17 00:00:00 2001 From: "Bruno O. Notario" Date: Fri, 15 Sep 2023 20:47:40 -0300 Subject: [PATCH] MagiQL SQL Builder --- system/database/autoload.php | 9 +- system/database/databases/mysql_legacy.php | 28 +- system/engine/interfaces/databases.php | 6 +- system/magiql/Api/BuilderInterface.php | 77 +++ system/magiql/Api/QueryInterface.php | 35 + .../magiql/Api/Syntax/QueryPartInterface.php | 21 + system/magiql/Api/WhereInterface.php | 26 + system/magiql/Builder.php | 399 +++++++++++ system/magiql/Builder/BuilderException.php | 13 + system/magiql/Builder/MySqlBuilder.php | 89 +++ .../Builder/Syntax/AbstractBaseWriter.php | 94 +++ .../Builder/Syntax/AbstractSetWriter.php | 49 ++ system/magiql/Builder/Syntax/ColumnWriter.php | 160 +++++ system/magiql/Builder/Syntax/DeleteWriter.php | 56 ++ system/magiql/Builder/Syntax/InsertWriter.php | 102 +++ .../magiql/Builder/Syntax/IntersectWriter.php | 28 + system/magiql/Builder/Syntax/MinusWriter.php | 44 ++ .../Builder/Syntax/PlaceholderWriter.php | 146 ++++ system/magiql/Builder/Syntax/SelectWriter.php | 396 +++++++++++ .../magiql/Builder/Syntax/UnionAllWriter.php | 28 + system/magiql/Builder/Syntax/UnionWriter.php | 28 + system/magiql/Builder/Syntax/UpdateWriter.php | 66 ++ system/magiql/Builder/Syntax/WhereWriter.php | 407 +++++++++++ .../magiql/Builder/Syntax/WriterFactory.php | 132 ++++ system/magiql/MagiQL.php | 48 ++ .../magiql/Manipulation/AbstractBaseQuery.php | 284 ++++++++ .../Manipulation/AbstractCreationalQuery.php | 55 ++ .../magiql/Manipulation/AbstractSetQuery.php | 79 +++ system/magiql/Manipulation/ColumnQuery.php | 261 ++++++++ system/magiql/Manipulation/Delete.php | 59 ++ system/magiql/Manipulation/Insert.php | 36 + system/magiql/Manipulation/Intersect.php | 84 +++ system/magiql/Manipulation/JoinQuery.php | 307 +++++++++ system/magiql/Manipulation/Minus.php | 95 +++ system/magiql/Manipulation/QueryException.php | 17 + system/magiql/Manipulation/QueryFactory.php | 107 +++ system/magiql/Manipulation/Select.php | 545 +++++++++++++++ system/magiql/Manipulation/Union.php | 26 + system/magiql/Manipulation/UnionAll.php | 26 + system/magiql/Manipulation/Update.php | 54 ++ system/magiql/Syntax/Column.php | 139 ++++ system/magiql/Syntax/OrderBy.php | 91 +++ system/magiql/Syntax/SyntaxFactory.php | 92 +++ system/magiql/Syntax/Table.php | 137 ++++ system/magiql/Syntax/Where.php | 631 ++++++++++++++++++ system/magiql/autoload.php | 3 + 46 files changed, 5595 insertions(+), 20 deletions(-) create mode 100644 system/magiql/Api/BuilderInterface.php create mode 100644 system/magiql/Api/QueryInterface.php create mode 100644 system/magiql/Api/Syntax/QueryPartInterface.php create mode 100644 system/magiql/Api/WhereInterface.php create mode 100644 system/magiql/Builder.php create mode 100644 system/magiql/Builder/BuilderException.php create mode 100644 system/magiql/Builder/MySqlBuilder.php create mode 100644 system/magiql/Builder/Syntax/AbstractBaseWriter.php create mode 100644 system/magiql/Builder/Syntax/AbstractSetWriter.php create mode 100644 system/magiql/Builder/Syntax/ColumnWriter.php create mode 100644 system/magiql/Builder/Syntax/DeleteWriter.php create mode 100644 system/magiql/Builder/Syntax/InsertWriter.php create mode 100644 system/magiql/Builder/Syntax/IntersectWriter.php create mode 100644 system/magiql/Builder/Syntax/MinusWriter.php create mode 100644 system/magiql/Builder/Syntax/PlaceholderWriter.php create mode 100644 system/magiql/Builder/Syntax/SelectWriter.php create mode 100644 system/magiql/Builder/Syntax/UnionAllWriter.php create mode 100644 system/magiql/Builder/Syntax/UnionWriter.php create mode 100644 system/magiql/Builder/Syntax/UpdateWriter.php create mode 100644 system/magiql/Builder/Syntax/WhereWriter.php create mode 100644 system/magiql/Builder/Syntax/WriterFactory.php create mode 100644 system/magiql/MagiQL.php create mode 100644 system/magiql/Manipulation/AbstractBaseQuery.php create mode 100644 system/magiql/Manipulation/AbstractCreationalQuery.php create mode 100644 system/magiql/Manipulation/AbstractSetQuery.php create mode 100644 system/magiql/Manipulation/ColumnQuery.php create mode 100644 system/magiql/Manipulation/Delete.php create mode 100644 system/magiql/Manipulation/Insert.php create mode 100644 system/magiql/Manipulation/Intersect.php create mode 100644 system/magiql/Manipulation/JoinQuery.php create mode 100644 system/magiql/Manipulation/Minus.php create mode 100644 system/magiql/Manipulation/QueryException.php create mode 100644 system/magiql/Manipulation/QueryFactory.php create mode 100644 system/magiql/Manipulation/Select.php create mode 100644 system/magiql/Manipulation/Union.php create mode 100644 system/magiql/Manipulation/UnionAll.php create mode 100644 system/magiql/Manipulation/Update.php create mode 100644 system/magiql/Syntax/Column.php create mode 100644 system/magiql/Syntax/OrderBy.php create mode 100644 system/magiql/Syntax/SyntaxFactory.php create mode 100644 system/magiql/Syntax/Table.php create mode 100644 system/magiql/Syntax/Where.php create mode 100644 system/magiql/autoload.php diff --git a/system/database/autoload.php b/system/database/autoload.php index 1c9bda2..114a931 100644 --- a/system/database/autoload.php +++ b/system/database/autoload.php @@ -109,12 +109,15 @@ final class Database { /** * Execute the SQL Query * - * @param string $sql + * @param string|null $sql * @param bool $cacheUse - * @return \Phacil\Framework\Databases\Object\ResultInterface|\Phacil\Framework\Database::Cache + * @return \Phacil\Framework\Databases\Object\ResultInterface|\Phacil\Framework\Database::Cache|\Phacil\Framework\MagiQL * @throws PhpfastcacheInvalidArgumentException */ - public function query($sql, $cacheUse = true) { + public function query($sql = null, $cacheUse = true) { + if(!$sql) { + return new \Phacil\Framework\MagiQL($this); + } if(Config::SQL_CACHE() && $cacheUse == true) { diff --git a/system/database/databases/mysql_legacy.php b/system/database/databases/mysql_legacy.php index cf4a979..24d5f4f 100644 --- a/system/database/databases/mysql_legacy.php +++ b/system/database/databases/mysql_legacy.php @@ -18,18 +18,18 @@ final class MySQL_legacy implements \Phacil\Framework\Interfaces\Databases { private $connection; public function __construct($hostname, $username, $password, $database, $port = '3306', $charset = 'utf8') { - if (!$this->connection = mysql_connect($hostname, $username, $password)) { + if (!$this->connection = \mysql_connect($hostname, $username, $password)) { exit('Error: Could not make a database connection using ' . $username . '@' . $hostname); } - if (!mysql_select_db($database, $this->connection)) { + if (!\mysql_select_db($database, $this->connection)) { exit('Error: Could not connect to database ' . $database); } - mysql_query("SET NAMES '".$charset."'", $this->connection); - mysql_query("SET CHARACTER SET ".$charset."", $this->connection); - mysql_query("SET CHARACTER_SET_CONNECTION=".$charset."", $this->connection); - mysql_query("SET SQL_MODE = ''", $this->connection); + \mysql_query("SET NAMES '".$charset."'", $this->connection); + \mysql_query("SET CHARACTER SET ".$charset."", $this->connection); + \mysql_query("SET CHARACTER_SET_CONNECTION=".$charset."", $this->connection); + \mysql_query("SET SQL_MODE = ''", $this->connection); } public function isConnected() { } @@ -41,7 +41,7 @@ final class MySQL_legacy implements \Phacil\Framework\Interfaces\Databases { * @throws \Phacil\Framework\Exception */ public function query($sql) { - $resource = mysql_query($sql, $this->connection); + $resource = \mysql_query($sql, $this->connection); if ($resource) { if (is_resource($resource)) { @@ -49,13 +49,13 @@ final class MySQL_legacy implements \Phacil\Framework\Interfaces\Databases { $data = array(); - while ($result = mysql_fetch_assoc($resource)) { + while ($result = \mysql_fetch_assoc($resource)) { $data[$i] = $result; $i++; } - mysql_free_result($resource); + \mysql_free_result($resource); $query = new \Phacil\Framework\Databases\Object\Result(); $query->row = isset($data[0]) ? $data[0] : array(); @@ -69,23 +69,23 @@ final class MySQL_legacy implements \Phacil\Framework\Interfaces\Databases { return true; } } else { - throw new \Phacil\Framework\Exception('Error: ' . mysql_error($this->connection) . '
Error No: ' . mysql_errno($this->connection) . '
' . $sql); + throw new \Phacil\Framework\Exception('Error: ' . \mysql_error($this->connection) . '
Error No: ' . mysql_errno($this->connection) . '
' . $sql); } } public function escape($value) { - return mysql_real_escape_string($value, $this->connection); + return \mysql_real_escape_string($value, $this->connection); } public function countAffected() { - return mysql_affected_rows($this->connection); + return \mysql_affected_rows($this->connection); } public function getLastId() { - return mysql_insert_id($this->connection); + return \mysql_insert_id($this->connection); } public function __destruct() { - mysql_close($this->connection); + \mysql_close($this->connection); } } diff --git a/system/engine/interfaces/databases.php b/system/engine/interfaces/databases.php index 9c686cb..dc7883f 100644 --- a/system/engine/interfaces/databases.php +++ b/system/engine/interfaces/databases.php @@ -28,11 +28,11 @@ /** * Execute the SQL Query. * - * @param string $sql - * @return \Phacil\Framework\Databases\Object\ResultInterface|true + * @param string|null $sql + * @return \Phacil\Framework\Databases\Object\ResultInterface|\Phacil\Framework\MagiQL|bool * @throws Exception */ - public function query($sql); + public function query($sql = null); /** * Important escape to prevent SQL injection. diff --git a/system/magiql/Api/BuilderInterface.php b/system/magiql/Api/BuilderInterface.php new file mode 100644 index 0000000..2a9c693 --- /dev/null +++ b/system/magiql/Api/BuilderInterface.php @@ -0,0 +1,77 @@ +='; + const OPERATOR_GREATER_THAN = '>'; + const OPERATOR_LESS_THAN_OR_EQUAL = '<='; + const OPERATOR_LESS_THAN = '<'; + const OPERATOR_LIKE = 'LIKE'; + const OPERATOR_NOT_LIKE = 'NOT LIKE'; + const OPERATOR_EQUAL = '='; + const OPERATOR_NOT_EQUAL = '<>'; + const CONJUNCTION_AND = 'AND'; + const CONJUNCTION_AND_NOT = 'AND NOT'; + const CONJUNCTION_OR = 'OR'; + const CONJUNCTION_OR_NOT = 'OR NOT'; + const CONJUNCTION_EXISTS = 'EXISTS'; + const CONJUNCTION_NOT_EXISTS = 'NOT EXISTS'; +} \ No newline at end of file diff --git a/system/magiql/Builder.php b/system/magiql/Builder.php new file mode 100644 index 0000000..011ceba --- /dev/null +++ b/system/magiql/Builder.php @@ -0,0 +1,399 @@ + '\Phacil\Framework\MagiQL\Builder\Syntax\WriterFactory::createSelectWriter', + 'INSERT' => '\Phacil\Framework\MagiQL\Builder\Syntax\WriterFactory::createInsertWriter', + 'UPDATE' => '\Phacil\Framework\MagiQL\Builder\Syntax\WriterFactory::createUpdateWriter', + 'DELETE' => '\Phacil\Framework\MagiQL\Builder\Syntax\WriterFactory::createDeleteWriter', + 'INTERSECT' => '\Phacil\Framework\MagiQL\Builder\Syntax\WriterFactory::createIntersectWriter', + 'MINUS' => '\Phacil\Framework\MagiQL\Builder\Syntax\WriterFactory::createMinusWriter', + 'UNION' => '\Phacil\Framework\MagiQL\Builder\Syntax\WriterFactory::createUnionWriter', + 'UNION ALL' => '\Phacil\Framework\MagiQL\Builder\Syntax\WriterFactory::createUnionAllWriter', + ]; + + /** + * Array that stores instances of query writers. + * + * @var array + */ + protected $queryWriterInstances = [ + 'SELECT' => null, + 'INSERT' => null, + 'UPDATE' => null, + 'DELETE' => null, + 'INTERSECT' => null, + 'MINUS' => null, + 'UNION' => null, + 'UNION ALL' => null, + ]; + + /** + * Creates writers. + */ + public function __construct() + { + $this->placeholderWriter = WriterFactory::createPlaceholderWriter(); + } + + /** + * @param string $table + * @param array $columns + * + * @return \Phacil\Framework\MagiQL\Manipulation\Select + */ + public function select($table = null, array $columns = null) + { + return $this->injectBuilder(QueryFactory::createSelect($table, $columns)); + } + + /** + * @param \Phacil\Framework\MagiQL\Manipulation\AbstractBaseQuery + * + * @return \Phacil\Framework\MagiQL\Manipulation\AbstractBaseQuery + */ + protected function injectBuilder(AbstractBaseQuery $query) + { + return $query->setBuilder($this); + } + + /** + * @param string $table + * @param array $values + * + *@return AbstractBaseQuery + */ + public function insert($table = null, array $values = null) + { + return $this->injectBuilder(QueryFactory::createInsert($table, $values)); + } + + /** + * @param string $table + * @param array $values + * + *@return AbstractBaseQuery + */ + public function update($table = null, array $values = null) + { + return $this->injectBuilder(QueryFactory::createUpdate($table, $values)); + } + + /** + * @param string $table + * + * @return \Phacil\Framework\MagiQL\Manipulation\Delete + */ + public function delete($table = null) + { + return $this->injectBuilder(QueryFactory::createDelete($table)); + } + + /** + * @return \Phacil\Framework\MagiQL\Manipulation\Intersect + */ + public function intersect() + { + return QueryFactory::createIntersect(); + } + + /** + * @return \Phacil\Framework\MagiQL\Manipulation\Union + */ + public function union() + { + return QueryFactory::createUnion(); + } + + /** + * @return \Phacil\Framework\MagiQL\Manipulation\UnionAll + */ + public function unionAll() + { + return QueryFactory::createUnionAll(); + } + + /** + * @param \Phacil\Framework\MagiQL\Manipulation\Select $first + * @param \Phacil\Framework\MagiQL\Manipulation\Select $second + * + * @return \Phacil\Framework\MagiQL\Manipulation\Minus + */ + public function minus(Select $first, Select $second) + { + return QueryFactory::createMinus($first, $second); + } + + /** + * @return array + */ + public function getValues() + { + return $this->placeholderWriter->get(); + } + + /** + * Returns a SQL string in a readable human-friendly format. + * + * @param QueryInterface $query + * + * @return string + */ + public function writeFormatted(QueryInterface $query) + { + if (null === $this->sqlFormatter) { + $this->sqlFormatter = (new \ReflectionClass($this->sqlFormatterClass))->newInstance(); + } + + return $this->sqlFormatter->format($this->write($query)); + } + + /** + * @param QueryInterface $query + * @param bool $resetPlaceholders + * + * @return string + * + * @throws \RuntimeException + */ + public function write(QueryInterface $query, $resetPlaceholders = true) + { + if ($resetPlaceholders) { + $this->placeholderWriter->reset(); + } + + $queryPart = $query->partName(); + + if (false === empty($this->queryWriterArray[$queryPart])) { + $this->createQueryObject($queryPart); + + return $this->queryWriterInstances[$queryPart]->write($query); + } + + throw new \RuntimeException('Query builder part not defined.'); + } + + /** + * @param Select $select + * + * @return string + */ + public function writeJoin(Select $select) + { + if (null === $this->whereWriter) { + $this->whereWriter = WriterFactory::createWhereWriter($this, $this->placeholderWriter); + } + + $sql = ($select->getJoinType()) ? "{$select->getJoinType()} " : ''; + $sql .= 'JOIN '; + $sql .= $this->writeTableWithAlias($select->getTable()); + $sql .= ' ON '; + $sql .= $this->whereWriter->writeWhere($select->getJoinCondition()); + + return $sql; + } + + /** + * @param Table $table + * + * @return string + */ + public function writeTableWithAlias(Table $table) + { + $alias = ($table->getAlias()) ? " AS {$this->writeTableAlias($table->getAlias())}" : ''; + $schema = ($table->getSchema()) ? "{$table->getSchema()}." : ''; + + return $schema.$this->writeTableName($table).$alias; + } + + /** + * @param $alias + * + * @return mixed + */ + public function writeTableAlias($alias) + { + return $alias; + } + + /** + * Returns the table name. + * + * @param Table $table + * + * @return string + */ + public function writeTableName(Table $table) + { + return $table->getName(); + } + + /** + * @param string $alias + * + * @return string + */ + public function writeColumnAlias($alias) + { + return sprintf('"%s"', $alias); + } + + /** + * @param Table $table + * + * @return string + */ + public function writeTable(Table $table) + { + $schema = ($table->getSchema()) ? "{$table->getSchema()}." : ''; + + return $schema.$this->writeTableName($table); + } + + /** + * @param array $values + * + * @return array + */ + public function writeValues(array &$values) + { + \array_walk( + $values, + function (&$value) { + $value = $this->writePlaceholderValue($value); + } + ); + + return $values; + } + + /** + * @param $value + * + * @return string + */ + public function writePlaceholderValue($value) + { + return $this->placeholderWriter->add($value); + } + + /** + * @param $operator + * + * @return string + */ + public function writeConjunction($operator) + { + return ' '.$operator.' '; + } + + /** + * @return string + */ + public function writeIsNull() + { + return ' IS NULL'; + } + + /** + * @return string + */ + public function writeIsNotNull() + { + return ' IS NOT NULL'; + } + + /** + * Returns the column name. + * + * @param Column $column + * + * @return string + */ + public function writeColumnName(Column $column) + { + $name = $column->getName(); + + if ($name === Column::ALL) { + return $this->writeColumnAll(); + } + + return $name; + } + + /** + * @return string + */ + protected function writeColumnAll() + { + return '*'; + } + + /** + * @param string $queryPart + */ + protected function createQueryObject($queryPart) + { + if (null === $this->queryWriterInstances[$queryPart]) { + $this->queryWriterInstances[$queryPart] = \call_user_func_array( + \explode('::', $this->queryWriterArray[$queryPart]), + [$this, $this->placeholderWriter] + ); + } + } +} diff --git a/system/magiql/Builder/BuilderException.php b/system/magiql/Builder/BuilderException.php new file mode 100644 index 0000000..72aec95 --- /dev/null +++ b/system/magiql/Builder/BuilderException.php @@ -0,0 +1,13 @@ +isAll()) { + return '*'; + } + + if (false !== strpos($column->getName(), '(')) { + return parent::writeColumnName($column); + } + + return $this->wrapper(parent::writeColumnName($column)); + } + + /** + * {@inheritdoc} + * + * @param Table $table + * + * @return string + */ + public function writeTableName(Table $table) + { + return $this->wrapper(parent::writeTableName($table)); + } + + /** + * {@inheritdoc} + * + * @param $alias + * + * @return string + */ + public function writeTableAlias($alias) + { + return $this->wrapper(parent::writeTableAlias($alias)); + } + + /** + * {@inheritdoc} + * + * @param $alias + * + * @return string + */ + public function writeColumnAlias($alias) + { + return $this->wrapper($alias); + } + + /** + * @param $string + * @param string $char + * + * @return string + */ + protected function wrapper($string, $char = '`') + { + if (0 === strlen($string)) { + return ''; + } + + return $char.$string.$char; + } +} diff --git a/system/magiql/Builder/Syntax/AbstractBaseWriter.php b/system/magiql/Builder/Syntax/AbstractBaseWriter.php new file mode 100644 index 0000000..d6070ee --- /dev/null +++ b/system/magiql/Builder/Syntax/AbstractBaseWriter.php @@ -0,0 +1,94 @@ +writer = $writer; + $this->placeholderWriter = $placeholder; + + $this->columnWriter = WriterFactory::createColumnWriter($writer, $placeholder); + } + + /** + * @param AbstractBaseQuery $class + * + * @return string + */ + public static function writeQueryComment(AbstractBaseQuery $class) + { + $comment = ''; + if ('' !== $class->getComment()) { + $comment = $class->getComment(); + } + + return $comment; + } + + /** + * @param AbstractBaseQuery $class + * @param \Phacil\Framework\MagiQL\Api\BuilderInterface $writer + * @param PlaceholderWriter $placeholderWriter + * @param array $parts + */ + public static function writeWhereCondition( + AbstractBaseQuery $class, + \Phacil\Framework\MagiQL\Api\BuilderInterface $writer, + PlaceholderWriter $placeholderWriter, + array &$parts + ) { + if (!is_null($class->getWhere())) { + $whereWriter = WriterFactory::createWhereWriter($writer, $placeholderWriter); + $parts[] = "WHERE {$whereWriter->writeWhere($class->getWhere())}"; + } + } + + /** + * @param AbstractBaseQuery $class + * @param PlaceholderWriter $placeholderWriter + * @param array $parts + */ + public static function writeLimitCondition( + AbstractBaseQuery $class, + PlaceholderWriter $placeholderWriter, + array &$parts + ) { + if (!is_null($class->getLimitStart())) { + $start = $placeholderWriter->add($class->getLimitStart()); + $parts[] = "LIMIT {$start}"; + } + } +} diff --git a/system/magiql/Builder/Syntax/AbstractSetWriter.php b/system/magiql/Builder/Syntax/AbstractSetWriter.php new file mode 100644 index 0000000..792fafc --- /dev/null +++ b/system/magiql/Builder/Syntax/AbstractSetWriter.php @@ -0,0 +1,49 @@ +writer = $writer; + } + + /** + * @param QueryPartInterface $setClass + * @param string $setOperation + * @param $glue + * + * @return string + */ + protected function abstractWrite(QueryPartInterface $setClass, $setOperation, $glue) + { + $selects = []; + + foreach ($setClass->$setOperation() as $select) { + $selects[] = $this->writer->write($select, false); + } + + return \implode("\n".$glue."\n", $selects); + } +} diff --git a/system/magiql/Builder/Syntax/ColumnWriter.php b/system/magiql/Builder/Syntax/ColumnWriter.php new file mode 100644 index 0000000..fe7d0a0 --- /dev/null +++ b/system/magiql/Builder/Syntax/ColumnWriter.php @@ -0,0 +1,160 @@ +writer = $writer; + $this->placeholderWriter = $placeholderWriter; + } + + /** + * @param Select $select + * + * @return array + */ + public function writeSelectsAsColumns(Select $select) + { + $selectAsColumns = $select->getColumnSelects(); + + if (!empty($selectAsColumns)) { + $selectWriter = WriterFactory::createSelectWriter($this->writer, $this->placeholderWriter); + $selectAsColumns = $this->selectColumnToQuery($selectAsColumns, $selectWriter); + } + + return $selectAsColumns; + } + + /** + * @param array $selectAsColumns + * @param SelectWriter $selectWriter + * + * @return mixed + */ + protected function selectColumnToQuery(array &$selectAsColumns, SelectWriter $selectWriter) + { + \array_walk( + $selectAsColumns, + function (&$column) use (&$selectWriter) { + $keys = \array_keys($column); + $key = \array_pop($keys); + + $values = \array_values($column); + $value = $values[0]; + + if (\is_numeric($key)) { + /* @var Column $value */ + $key = $this->writer->writeTableName($value->getTable()); + } + $column = $selectWriter->selectToColumn($key, $value); + } + ); + + return $selectAsColumns; + } + + /** + * @param Select $select + * + * @return array + */ + public function writeValueAsColumns(Select $select) + { + $valueAsColumns = $select->getColumnValues(); + $newColumns = []; + + if (!empty($valueAsColumns)) { + foreach ($valueAsColumns as $alias => $value) { + $value = $this->writer->writePlaceholderValue($value); + $newValueColumn = array($alias => $value); + + $newColumns[] = SyntaxFactory::createColumn($newValueColumn, null); + } + } + + return $newColumns; + } + + /** + * @param Select $select + * + * @return array + */ + public function writeFuncAsColumns(Select $select) + { + $funcAsColumns = $select->getColumnFuncs(); + $newColumns = []; + + if (!empty($funcAsColumns)) { + foreach ($funcAsColumns as $alias => $value) { + $funcName = $value['func']; + $funcArgs = (!empty($value['args'])) ? '('.implode(', ', $value['args']).')' : ''; + + $newFuncColumn = array($alias => $funcName.$funcArgs); + $newColumns[] = SyntaxFactory::createColumn($newFuncColumn, null); + } + } + + return $newColumns; + } + + /** + * @param Column $column + * + * @return string + */ + public function writeColumnWithAlias(Column $column) + { + if (($alias = $column->getAlias()) && !$column->isAll()) { + return $this->writeColumn($column).' AS '.$this->writer->writeColumnAlias($alias); + } + + return $this->writeColumn($column); + } + + /** + * @param Column $column + * + * @return string + */ + public function writeColumn(Column $column) + { + $alias = $column->getTable()->getAlias(); + $table = ($alias) ? $this->writer->writeTableAlias($alias) : $this->writer->writeTable($column->getTable()); + + $columnString = (empty($table)) ? '' : "{$table}."; + $columnString .= $this->writer->writeColumnName($column); + + return $columnString; + } +} diff --git a/system/magiql/Builder/Syntax/DeleteWriter.php b/system/magiql/Builder/Syntax/DeleteWriter.php new file mode 100644 index 0000000..c944136 --- /dev/null +++ b/system/magiql/Builder/Syntax/DeleteWriter.php @@ -0,0 +1,56 @@ +writer = $writer; + $this->placeholderWriter = $placeholder; + } + + /** + * @param Delete $delete + * + * @return string + */ + public function write(Delete $delete) + { + $table = $this->writer->writeTable($delete->getTable()); + $parts = array("DELETE FROM {$table}"); + + AbstractBaseWriter::writeWhereCondition($delete, $this->writer, $this->placeholderWriter, $parts); + AbstractBaseWriter::writeLimitCondition($delete, $this->placeholderWriter, $parts); + $comment = AbstractBaseWriter::writeQueryComment($delete); + + return $comment.implode(' ', $parts); + } +} diff --git a/system/magiql/Builder/Syntax/InsertWriter.php b/system/magiql/Builder/Syntax/InsertWriter.php new file mode 100644 index 0000000..b0f5931 --- /dev/null +++ b/system/magiql/Builder/Syntax/InsertWriter.php @@ -0,0 +1,102 @@ +writer = $writer; + $this->columnWriter = WriterFactory::createColumnWriter($this->writer, $placeholder); + } + + /** + * @param Insert $insert + * + * @throws QueryException + * + * @return string + */ + public function write(Insert $insert) + { + $columns = $insert->getColumns(); + + if (empty($columns)) { + throw new QueryException('No columns were defined for the current schema.'); + } + + $columns = $this->writeQueryColumns($columns); + $values = $this->writeQueryValues($insert->getValues()); + $table = $this->writer->writeTable($insert->getTable()); + $comment = AbstractBaseWriter::writeQueryComment($insert); + + return $comment."INSERT INTO {$table} ($columns) VALUES ($values)"; + } + + /** + * @param $columns + * + * @return string + */ + protected function writeQueryColumns($columns) + { + return $this->writeCommaSeparatedValues($columns, $this->columnWriter, 'writeColumn'); + } + + /** + * @param $collection + * @param $writer + * @param string $method + * + * @return string + */ + protected function writeCommaSeparatedValues($collection, $writer, $method) + { + \array_walk( + $collection, + function (&$data) use ($writer, $method) { + $data = $writer->$method($data); + } + ); + + return \implode(', ', $collection); + } + + /** + * @param $values + * + * @return string + */ + protected function writeQueryValues($values) + { + return $this->writeCommaSeparatedValues($values, $this->writer, 'writePlaceholderValue'); + } +} diff --git a/system/magiql/Builder/Syntax/IntersectWriter.php b/system/magiql/Builder/Syntax/IntersectWriter.php new file mode 100644 index 0000000..9e85027 --- /dev/null +++ b/system/magiql/Builder/Syntax/IntersectWriter.php @@ -0,0 +1,28 @@ +abstractWrite($intersect, 'getIntersects', Intersect::INTERSECT); + } +} diff --git a/system/magiql/Builder/Syntax/MinusWriter.php b/system/magiql/Builder/Syntax/MinusWriter.php new file mode 100644 index 0000000..ed676d3 --- /dev/null +++ b/system/magiql/Builder/Syntax/MinusWriter.php @@ -0,0 +1,44 @@ +writer = $writer; + } + + /** + * @param Minus $minus + * + * @return string + */ + public function write(Minus $minus) + { + $first = $this->writer->write($minus->getFirst()); + $second = $this->writer->write($minus->getSecond()); + + return $first."\n".Minus::MINUS."\n".$second; + } +} diff --git a/system/magiql/Builder/Syntax/PlaceholderWriter.php b/system/magiql/Builder/Syntax/PlaceholderWriter.php new file mode 100644 index 0000000..8ac4dd8 --- /dev/null +++ b/system/magiql/Builder/Syntax/PlaceholderWriter.php @@ -0,0 +1,146 @@ +placeholders; + } + + /** + * @return $this + */ + public function reset() + { + $this->counter = 1; + $this->placeholders = []; + + return $this; + } + + /** + * @param $value + * + * @return string + */ + public function add($value) + { + $placeholderKey = ':v'.$this->counter; + $this->placeholders[$placeholderKey] = $this->setValidSqlValue($value); + + ++$this->counter; + + return $placeholderKey; + } + + /** + * @param $value + * + * @return string + */ + protected function setValidSqlValue($value) + { + $value = $this->writeNullSqlString($value); + $value = $this->writeStringAsSqlString($value); + $value = $this->writeBooleanSqlString($value); + + return $value; + } + + /** + * @param $value + * + * @return string + */ + protected function writeNullSqlString($value) + { + if (\is_null($value) || (\is_string($value) && empty($value))) { + $value = $this->writeNull(); + } + + return $value; + } + + /** + * @return string + */ + protected function writeNull() + { + return 'NULL'; + } + + /** + * @param string $value + * + * @return string + */ + protected function writeStringAsSqlString($value) + { + if (\is_string($value)) { + $value = $this->writeString($value); + } + + return $value; + } + + /** + * @param string $value + * + * @return string + */ + protected function writeString($value) + { + return $value; + } + + /** + * @param string $value + * + * @return string + */ + protected function writeBooleanSqlString($value) + { + if (\is_bool($value)) { + $value = $this->writeBoolean($value); + } + + return $value; + } + + /** + * @param bool $value + * + * @return string + */ + protected function writeBoolean($value) + { + $value = \filter_var($value, FILTER_VALIDATE_BOOLEAN); + + return ($value) ? '1' : '0'; + } +} diff --git a/system/magiql/Builder/Syntax/SelectWriter.php b/system/magiql/Builder/Syntax/SelectWriter.php new file mode 100644 index 0000000..ab69ccb --- /dev/null +++ b/system/magiql/Builder/Syntax/SelectWriter.php @@ -0,0 +1,396 @@ +write($select); + + if (!empty($selectAsColumn)) { + $selectAsColumn = '('.$selectAsColumn.')'; + } + + $column = array($alias => $selectAsColumn); + + return SyntaxFactory::createColumn($column, null); + } + + /** + * @param Select $select + * + * @return string + */ + public function write(Select $select) + { + if ($select->isJoinSelect()) { + return $this->writer->writeJoin($select); + } + + return $this->writeSelectQuery($select); + } + + /** + * @param Select $select + * + * @return string + */ + protected function writeSelectQuery(Select $select) + { + $parts = ['SELECT']; + + if ($select->isDistinct()) { + $parts[] = 'DISTINCT'; + } + + $this->writeSelectColumns($select, $parts); + $this->writeSelectFrom($select, $parts); + $this->writeSelectJoins($select, $parts); + $this->writeSelectWhere($select, $parts); + $this->writeSelectGroupBy($select, $parts); + $this->writeSelectHaving($select, $parts); + $this->writeSelectOrderBy($select, $parts); + $this->writeSelectLimit($select, $parts); + + return AbstractBaseWriter::writeQueryComment($select).implode(' ', \array_filter($parts)); + } + + /** + * @param Select $select + * @param string[] $parts + * + * @return $this + */ + public function writeSelectColumns(Select $select, array &$parts) + { + if ($select->isCount() === false) { + $columns = $this->writeColumnAlias( + $select->getAllColumns(), + $this->columnWriter->writeSelectsAsColumns($select), + $this->columnWriter->writeValueAsColumns($select), + $this->columnWriter->writeFuncAsColumns($select) + ); + + $parts = \array_merge($parts, [implode(', ', $columns)]); + + return $this; + } + + $columns = $select->getColumns(); + $column = \array_pop($columns); + $columnList = $column->getName(); + + $parts = \array_merge($parts, [$columnList]); + + return $this; + } + + /** + * @param $tableColumns + * @param $selectAsColumns + * @param $valueAsColumns + * @param $funcAsColumns + * + * @return array + */ + protected function writeColumnAlias($tableColumns, $selectAsColumns, $valueAsColumns, $funcAsColumns) + { + $columns = \array_merge($tableColumns, $selectAsColumns, $valueAsColumns, $funcAsColumns); + + \array_walk( + $columns, + function (&$column) { + $column = $this->columnWriter->writeColumnWithAlias($column); + } + ); + + return $columns; + } + + /** + * @param Select $select + * @param string[] $parts + * + * @return $this + */ + public function writeSelectFrom(Select $select, array &$parts) + { + $parts = \array_merge( + $parts, + ['FROM '.$this->writer->writeTableWithAlias($select->getTable())] + ); + + return $this; + } + + /** + * @param Select $select + * @param array $parts + * + * @return $this + */ + public function writeSelectJoins(Select $select, array &$parts) + { + $parts = \array_merge( + $parts, + [$this->writeSelectAggrupation($select, $this->writer, 'getAllJoins', 'writeJoin', ' ')] + ); + + return $this; + } + + /** + * @param Select $select + * @param $writer + * @param string $getMethod + * @param string $writeMethod + * @param string $glue + * @param string $prepend + * + * @return string + */ + protected function writeSelectAggrupation(Select $select, $writer, $getMethod, $writeMethod, $glue, $prepend = '') + { + $str = ''; + $joins = $select->$getMethod(); + + if (!empty($joins)) { + \array_walk( + $joins, + function (&$join) use ($writer, $writeMethod) { + $join = $writer->$writeMethod($join); + } + ); + + $str = $prepend.implode($glue, $joins); + } + + return $str; + } + + /** + * @param Select $select + * @param array $parts + * + * @return $this + */ + public function writeSelectWhere(Select $select, array &$parts) + { + $str = ''; + $wheres = $this->writeSelectWheres($select->getAllWheres()); + $wheres = \array_filter($wheres); + + if (\count($wheres) > 0) { + $str = 'WHERE '; + $separator = ' '.$this->writer->writeConjunction($select->getWhereOperator()).' '; + + $str .= \implode($separator, $wheres); + } + + $parts = \array_merge($parts, [$str]); + + return $this; + } + + /** + * @param array $wheres + * + * @return array + */ + protected function writeSelectWheres(array $wheres) + { + $whereWriter = WriterFactory::createWhereWriter($this->writer, $this->placeholderWriter); + + \array_walk( + $wheres, + function (&$where) use (&$whereWriter) { + + $where = $whereWriter->writeWhere($where); + } + ); + + return $wheres; + } + + /** + * @param Select $select + * @param array $parts + * + * @return $this + */ + public function writeSelectGroupBy(Select $select, array &$parts) + { + $groupBy = $this->writeSelectAggrupation( + $select, + $this->columnWriter, + 'getGroupBy', + 'writeColumn', + ', ', + 'GROUP BY ' + ); + + $parts = \array_merge($parts, [$groupBy]); + + return $this; + } + + /** + * @param Select $select + * @param array $parts + * + * @return $this + */ + public function writeSelectHaving(Select $select, array &$parts) + { + $str = ''; + $havingArray = $select->getAllHavings(); + + if (\count($havingArray) > 0) { + $placeholder = $this->placeholderWriter; + $writer = $this->writer; + + $str = 'HAVING '; + $separator = ' '.$select->getHavingOperator().' '; + $havingArray = $this->getHavingConditions($havingArray, $select, $writer, $placeholder); + + $str .= \implode($separator, $havingArray); + } + + $parts = \array_merge($parts, [$str]); + + return $this; + } + + /** + * @param array $havingArray + * @param Select $select + * @param BuilderInterface $writer + * @param PlaceholderWriter $placeholder + * + * @return mixed + */ + protected function getHavingConditions( + array &$havingArray, + Select $select, + BuilderInterface $writer, + PlaceholderWriter $placeholder + ) { + \array_walk( + $havingArray, + function (&$having) use ($select, $writer, $placeholder) { + + $whereWriter = WriterFactory::createWhereWriter($writer, $placeholder); + $clauses = $whereWriter->writeWhereClauses($having); + $having = \implode($this->writer->writeConjunction($select->getHavingOperator()), $clauses); + } + ); + + return $havingArray; + } + + /** + * @param Select $select + * @param array $parts + * + * @return $this + */ + protected function writeSelectOrderBy(Select $select, array &$parts) + { + $str = ''; + if (\count($select->getAllOrderBy())) { + $orderByArray = $select->getAllOrderBy(); + \array_walk( + $orderByArray, + function (&$orderBy) { + $orderBy = $this->writeOrderBy($orderBy); + } + ); + + $str = 'ORDER BY '; + $str .= \implode(', ', $orderByArray); + } + + $parts = \array_merge($parts, [$str]); + + return $this; + } + + /** + * @param OrderBy $orderBy + * + * @return string + */ + public function writeOrderBy(OrderBy $orderBy) + { + $column = $this->columnWriter->writeColumn($orderBy->getColumn()); + + return $column.' '.$orderBy->getDirection(); + } + + /** + * @param Select $select + * @param array $parts + * + * @return $this + */ + protected function writeSelectLimit(Select $select, array &$parts) + { + $mask = $this->getStartingLimit($select).$this->getLimitCount($select); + + $limit = ''; + + if ($mask !== '00') { + $start = $this->placeholderWriter->add($select->getLimitStart()); + $count = $this->placeholderWriter->add($select->getLimitCount()); + + $limit = "LIMIT {$start}, {$count}"; + } + + $parts = \array_merge($parts, [$limit]); + + return $this; + } + + /** + * @param Select $select + * + * @return string + */ + protected function getStartingLimit(Select $select) + { + return (null === $select->getLimitStart() || 0 == $select->getLimitStart()) ? '0' : '1'; + } + + /** + * @param Select $select + * + * @return string + */ + protected function getLimitCount(Select $select) + { + return (null === $select->getLimitCount()) ? '0' : '1'; + } +} diff --git a/system/magiql/Builder/Syntax/UnionAllWriter.php b/system/magiql/Builder/Syntax/UnionAllWriter.php new file mode 100644 index 0000000..110bf39 --- /dev/null +++ b/system/magiql/Builder/Syntax/UnionAllWriter.php @@ -0,0 +1,28 @@ +abstractWrite($unionAll, 'getUnions', UnionAll::UNION_ALL); + } +} diff --git a/system/magiql/Builder/Syntax/UnionWriter.php b/system/magiql/Builder/Syntax/UnionWriter.php new file mode 100644 index 0000000..af7cadb --- /dev/null +++ b/system/magiql/Builder/Syntax/UnionWriter.php @@ -0,0 +1,28 @@ +abstractWrite($union, 'getUnions', Union::UNION); + } +} diff --git a/system/magiql/Builder/Syntax/UpdateWriter.php b/system/magiql/Builder/Syntax/UpdateWriter.php new file mode 100644 index 0000000..b7158a4 --- /dev/null +++ b/system/magiql/Builder/Syntax/UpdateWriter.php @@ -0,0 +1,66 @@ +getValues(); + if (empty($values)) { + throw new QueryException('No values to update in Update query.'); + } + + $parts = array( + 'UPDATE '.$this->writer->writeTable($update->getTable()).' SET ', + $this->writeUpdateValues($update), + ); + + AbstractBaseWriter::writeWhereCondition($update, $this->writer, $this->placeholderWriter, $parts); + AbstractBaseWriter::writeLimitCondition($update, $this->placeholderWriter, $parts); + $comment = AbstractBaseWriter::writeQueryComment($update); + + return $comment.implode(' ', $parts); + } + + /** + * @param Update $update + * + * @return string + */ + protected function writeUpdateValues(Update $update) + { + $assigns = []; + foreach ($update->getValues() as $column => $value) { + $newColumn = array($column); + $column = $this->columnWriter->writeColumn(SyntaxFactory::createColumn($newColumn, $update->getTable())); + + $value = $this->writer->writePlaceholderValue($value); + + $assigns[] = "$column = $value"; + } + + return \implode(', ', $assigns); + } +} diff --git a/system/magiql/Builder/Syntax/WhereWriter.php b/system/magiql/Builder/Syntax/WhereWriter.php new file mode 100644 index 0000000..8b8c1a4 --- /dev/null +++ b/system/magiql/Builder/Syntax/WhereWriter.php @@ -0,0 +1,407 @@ + '(MATCH({{columnNames}}) AGAINST({{columnValues}}))', + 'boolean' => '(MATCH({{columnNames}}) AGAINST({{columnValues}} IN BOOLEAN MODE))', + 'query_expansion' => '(MATCH({{columnNames}}) AGAINST({{columnValues}} WITH QUERY EXPANSION))', + ]; + + /** + * @param Where $where + * + * @return string + */ + public function writeWhere(Where $where) + { + $clauses = $this->writeWhereClauses($where); + $clauses = \array_filter($clauses); + + if (empty($clauses)) { + return ''; + } + + return \implode($this->writer->writeConjunction($where->getConjunction()), $clauses); + } + + /** + * @param Where $where + * + * @return array + */ + public function writeWhereClauses(Where $where) + { + $whereArray = []; + + $this->writeWhereMatches($where, $whereArray); + $this->writeWhereIns($where, $whereArray); + $this->writeWhereNotIns($where, $whereArray); + $this->writeWhereBetweens($where, $whereArray); + $this->writeWhereNotBetweens($where, $whereArray); + $this->writeWhereComparisons($where, $whereArray); + $this->writeWhereIsNulls($where, $whereArray); + $this->writeWhereIsNotNulls($where, $whereArray); + $this->writeWhereBooleans($where, $whereArray); + $this->writeExists($where, $whereArray); + $this->writeNotExists($where, $whereArray); + $this->writeSubWheres($where, $whereArray); + + return $whereArray; + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereMatches(Where $where, array &$whereArray) + { + $matches = []; + + foreach ($where->getMatches() as $values) { + $columns = SyntaxFactory::createColumns($values['columns'], $where->getTable()); + $columnNames = $this->getColumnNames($columns); + + $columnValues = array(\implode(' ', $values['values'])); + $columnValues = \implode(', ', $this->writer->writeValues($columnValues)); + + $matches[] = \str_replace( + ['{{columnNames}}', '{{columnValues}}'], + [$columnNames, $columnValues], + $this->matchMode[$values['mode']] + ); + } + + $whereArray = \array_merge($whereArray, $matches); + } + + /** + * @param $columns + * + * @return string + */ + protected function getColumnNames($columns) + { + $columnNames = []; + foreach ($columns as &$column) { + $columnNames[] = $this->columnWriter->writeColumn($column); + } + + return \implode(', ', $columnNames); + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereIns(Where $where, array &$whereArray) + { + $whereArray = \array_merge( + $whereArray, + $this->writeWhereIn($where, 'getIns', 'IN') + ); + } + + /** + * @param Where $where + * @param string $method + * @param string $operation + * + * @return array + */ + protected function writeWhereIn(Where $where, $method, $operation) + { + $collection = []; + + foreach ($where->$method() as $column => $values) { + $newColumn = array($column); + $column = SyntaxFactory::createColumn($newColumn, $where->getTable()); + $column = $this->columnWriter->writeColumn($column); + + $values = $this->writer->writeValues($values); + $values = \implode(', ', $values); + + $collection[] = "({$column} $operation ({$values}))"; + } + + return $collection; + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereNotIns(Where $where, array &$whereArray) + { + $whereArray = \array_merge( + $whereArray, + $this->writeWhereIn($where, 'getNotIns', 'NOT IN') + ); + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereBetweens(Where $where, array &$whereArray) + { + $between = $where->getBetweens(); + \array_walk( + $between, + function (&$between) { + + $between = '(' + .$this->columnWriter->writeColumn($between['subject']) + .' BETWEEN ' + .$this->writer->writePlaceholderValue($between['a']) + .' AND ' + .$this->writer->writePlaceholderValue($between['b']) + .')'; + } + ); + + $whereArray = \array_merge($whereArray, $between); + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereNotBetweens(Where $where, array &$whereArray) + { + $between = $where->getNotBetweens(); + \array_walk( + $between, + function (&$between) { + + $between = '(' + .$this->columnWriter->writeColumn($between['subject']) + .' NOT BETWEEN ' + .$this->writer->writePlaceholderValue($between['a']) + .' AND ' + .$this->writer->writePlaceholderValue($between['b']) + .')'; + } + ); + + $whereArray = \array_merge($whereArray, $between); + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereComparisons(Where $where, array &$whereArray) + { + $comparisons = $where->getComparisons(); + \array_walk( + $comparisons, + function (&$comparison) { + + if (!is_array($comparison)) { + return; + } + + $str = $this->writeWherePartialCondition($comparison['subject']); + $str .= $this->writer->writeConjunction($comparison['conjunction']); + $str .= $this->writeWherePartialCondition($comparison['target']); + + $comparison = "($str)"; + } + ); + + $whereArray = \array_merge($whereArray, $comparisons); + } + + /** + * @param $subject + * + * @return string + */ + protected function writeWherePartialCondition(&$subject) + { + if ($subject instanceof Column) { + $str = $this->columnWriter->writeColumn($subject); + } elseif ($subject instanceof Select) { + $selectWriter = WriterFactory::createSelectWriter($this->writer, $this->placeholderWriter); + $str = '('.$selectWriter->write($subject).')'; + } else { + $str = $this->writer->writePlaceholderValue($subject); + //$str = $subject; + } + + return $str; + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereIsNulls(Where $where, array &$whereArray) + { + $whereArray = \array_merge( + $whereArray, + $this->writeWhereIsNullable($where, 'getNull', 'writeIsNull') + ); + } + + /** + * @param Where $where + * @param string $getMethod + * @param string $writeMethod + * + * @return array + */ + protected function writeWhereIsNullable(Where $where, $getMethod, $writeMethod) + { + $collection = $where->$getMethod(); + + \array_walk( + $collection, + function (&$collection) use ($writeMethod) { + $collection = + '('.$this->columnWriter->writeColumn($collection['subject']) + .$this->writer->$writeMethod().')'; + } + ); + + return $collection; + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereIsNotNulls(Where $where, array &$whereArray) + { + $whereArray = \array_merge( + $whereArray, + $this->writeWhereIsNullable($where, 'getNotNull', 'writeIsNotNull') + ); + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeWhereBooleans(Where $where, array &$whereArray) + { + $booleans = $where->getBooleans(); + $placeholderWriter = $this->placeholderWriter; + + \array_walk( + $booleans, + function (&$boolean) use (&$placeholderWriter) { + $column = $this->columnWriter->writeColumn($boolean['subject']); + $value = $this->placeholderWriter->add($boolean['value']); + + $boolean = '(ISNULL('.$column.', 0) = '.$value.')'; + } + ); + + $whereArray = \array_merge($whereArray, $booleans); + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeExists(Where $where, array &$whereArray) + { + $whereArray = \array_merge( + $whereArray, + $this->writeExistence($where, 'getExists', 'EXISTS') + ); + } + + /** + * @param Where $where + * @param string $method + * @param string $operation + * + * @return array + */ + protected function writeExistence(Where $where, $method, $operation) + { + $exists = []; + + foreach ($where->$method() as $select) { + $exists[] = "$operation (".$this->writer->write($select, false).')'; + } + + return $exists; + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeNotExists(Where $where, array &$whereArray) + { + $whereArray = \array_merge( + $whereArray, + $this->writeExistence($where, 'getNotExists', 'NOT EXISTS') + ); + } + + /** + * @param Where $where + * @param array $whereArray + * + * @return array + */ + protected function writeSubWheres(Where $where, array &$whereArray) + { + $subWheres = $where->getSubWheres(); + + \array_walk( + $subWheres, + function (&$subWhere) { + $subWhere = "({$this->writeWhere($subWhere)})"; + } + ); + + $whereArray = \array_merge($whereArray, $subWheres); + } +} diff --git a/system/magiql/Builder/Syntax/WriterFactory.php b/system/magiql/Builder/Syntax/WriterFactory.php new file mode 100644 index 0000000..99cf6dc --- /dev/null +++ b/system/magiql/Builder/Syntax/WriterFactory.php @@ -0,0 +1,132 @@ +db = $db; + + $this->queryObj = new Builder(); + parent::__construct(); + } + + public function __call($name, $arguments = array()){ + + return call_user_func_array([$this->queryObj, $name], $arguments); + } + +} \ No newline at end of file diff --git a/system/magiql/Manipulation/AbstractBaseQuery.php b/system/magiql/Manipulation/AbstractBaseQuery.php new file mode 100644 index 0000000..661d1b8 --- /dev/null +++ b/system/magiql/Manipulation/AbstractBaseQuery.php @@ -0,0 +1,284 @@ +where)) { + $this->where = QueryFactory::createWhere($this); + } + + return $this->where; + } + + /** + * Stores the builder that created this query. + * + * @param BuilderInterface $builder + * + * @return $this + */ + final public function setBuilder(BuilderInterface $builder) + { + $this->builder = $builder; + + return $this; + } + + /** + * @return BuilderInterface + * + * @throws \RuntimeException when builder has not been injected + */ + final public function getBuilder() + { + if (!$this->builder) { + throw new \RuntimeException('Query builder has not been injected with setBuilder'); + } + + return $this->builder; + } + + /** + * Converts this query into an SQL string by using the injected builder. + * + * @return string + */ + public function __toString() + { + try { + return $this->getSql(); + } catch (\Exception $e) { + return \sprintf('[%s] %s', \get_class($e), $e->getMessage()); + } + } + + /** + * Converts this query into an SQL string by using the injected builder. + * Optionally can return the SQL with formatted structure. + * + * @param bool $formatted + * + * @return string + */ + public function getSql($formatted = false) + { + if ($formatted) { + return $this->getBuilder()->writeFormatted($this); + } + + return $this->getBuilder()->write($this); + } + + /** + * @return string + */ + abstract public function partName(); + + /** + * @return Where + */ + public function getWhere() + { + return $this->where; + } + + /** + * @param Where $where + * + * @return $this + */ + public function setWhere(Where $where) + { + $this->where = $where; + + return $this; + } + + /** + * @return Table + */ + public function getTable() + { + $newTable = array($this->table); + + return \is_null($this->table) ? null : SyntaxFactory::createTable($newTable); + } + + /** + * @param string $table + * + * @return $this + */ + public function setTable($table) + { + $this->table = (string) $table; + + return $this; + } + + /** + * + * @param string $table + * @return $this + */ + public function from($table){ + return $this->setTable($table); + } + + /** + * @param string $whereOperator + * + * @return Where + */ + public function where($whereOperator = 'AND') + { + if (!isset($this->where)) { + $this->where = $this->filter(); + } + + $this->where->conjunction($whereOperator); + + return $this->where; + } + + /** + * @return string + */ + public function getWhereOperator() + { + if (!isset($this->where)) { + $this->where = $this->filter(); + } + + return $this->where->getConjunction(); + } + + /** + * @param string $column + * @param string $direction + * @param null $table + * + * @return $this + */ + public function orderBy($column, $direction = OrderBy::ASC, $table = null) + { + $newColumn = array($column); + $column = SyntaxFactory::createColumn($newColumn, \is_null($table) ? $this->getTable() : $table); + $this->orderBy[] = new OrderBy($column, $direction); + + return $this; + } + + /** + * @return int + */ + public function getLimitCount() + { + return $this->limitCount; + } + + /** + * @return int + */ + public function getLimitStart() + { + return $this->limitStart; + } + + /** + * @param string $comment + * + * @return $this + */ + public function setComment($comment) + { + // Make each line of the comment prefixed with "--", + // and remove any trailing whitespace. + $comment = '-- '.str_replace("\n", "\n-- ", \rtrim($comment)); + + // Trim off any trailing "-- ", to ensure that the comment is valid. + $this->comment = \rtrim($comment, '- '); + + if ($this->comment) { + $this->comment .= "\n"; + } + + return $this; + } + + /** + * @return string + */ + public function getComment() + { + return $this->comment; + } +} diff --git a/system/magiql/Manipulation/AbstractCreationalQuery.php b/system/magiql/Manipulation/AbstractCreationalQuery.php new file mode 100644 index 0000000..58ead50 --- /dev/null +++ b/system/magiql/Manipulation/AbstractCreationalQuery.php @@ -0,0 +1,55 @@ +setTable($table); + } + + if (!empty($values)) { + $this->setValues($values); + } + } + + /** + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * @param array $values + * + * @return $this + */ + public function setValues(array $values) + { + $this->values = $values; + + return $this; + } +} diff --git a/system/magiql/Manipulation/AbstractSetQuery.php b/system/magiql/Manipulation/AbstractSetQuery.php new file mode 100644 index 0000000..868d0b4 --- /dev/null +++ b/system/magiql/Manipulation/AbstractSetQuery.php @@ -0,0 +1,79 @@ +union[] = $select; + + return $this; + } + + /** + * @return array + */ + public function getUnions() + { + return $this->union; + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Table + */ + public function getTable() + { + throw new QueryException( + \sprintf('%s does not support tables', $this->partName()) + ); + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Where + */ + public function getWhere() + { + throw new QueryException( + \sprintf('%s does not support WHERE.', $this->partName()) + ); + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Where + */ + public function where() + { + throw new QueryException( + \sprintf('%s does not support the WHERE statement.', $this->partName()) + ); + } +} diff --git a/system/magiql/Manipulation/ColumnQuery.php b/system/magiql/Manipulation/ColumnQuery.php new file mode 100644 index 0000000..c6f52d9 --- /dev/null +++ b/system/magiql/Manipulation/ColumnQuery.php @@ -0,0 +1,261 @@ +select = $select; + $this->joinQuery = $joinQuery; + + if (!isset($columns)) { + $columns = array(Column::ALL); + } + + if (\count($columns)) { + $this->setColumns($columns); + } + } + + /** + * @param $start + * @param int $count + * + * @return Select + */ + public function limit($start, $count = 0) + { + return $this->select->limit($start, $count); + } + + /** + * @param string $whereOperator + * + * @return \Phacil\Framework\MagiQL\Syntax\Where + */ + public function where($whereOperator = 'AND') + { + return $this->select->where($whereOperator); + } + + /** + * @param string $column + * @param string $direction + * @param null $table + * + * @return Select + */ + public function orderBy($column, $direction = OrderBy::ASC, $table = null) + { + return $this->select->orderBy($column, $direction, $table); + } + + /** + * @param string[] $columns + * + * @return Select + */ + public function groupBy(array $columns) + { + return $this->select->groupBy($columns); + } + + /** + * Allows setting a Select query as a column value. + * + * @param array $column + * + * @return $this + */ + public function setSelectAsColumn(array $column) + { + $this->columnSelects[] = $column; + + return $this; + } + + /** + * @return array + */ + public function getColumnSelects() + { + return $this->columnSelects; + } + + /** + * Allows setting a value to the select statement. + * + * @param string $value + * @param string $alias + * + * @return $this + */ + public function setValueAsColumn($value, $alias) + { + $this->columnValues[$alias] = $value; + + return $this; + } + + /** + * @return array + */ + public function getColumnValues() + { + return $this->columnValues; + } + + /** + * Allows calculation on columns using predefined SQL functions. + * + * @param string $funcName + * @param string[] $arguments + * @param string $alias + * + * @return $this + */ + public function setFunctionAsColumn($funcName, array $arguments, $alias) + { + $this->columnFuncs[$alias] = ['func' => $funcName, 'args' => $arguments]; + + return $this; + } + + /** + * @return array + */ + public function getColumnFuncs() + { + return $this->columnFuncs; + } + + /** + * @param string $columnName + * @param string $alias + * + * @return $this + */ + public function count($columnName = '*', $alias = '') + { + $table = $this->select->getTable(); + + $count = 'COUNT('; + $count .= ($columnName !== '*') ? "$table.{$columnName}" : '*'; + $count .= ')'; + + if (isset($alias) && \strlen($alias) > 0) { + $count .= ' AS "'.$alias.'"'; + } + + $this->columns = array($count); + $this->isCount = true; + + return $this; + } + + /** + * @return bool + */ + public function isCount() + { + return $this->isCount; + } + + /** + * @return array + */ + public function getAllColumns() + { + $columns = $this->getColumns(); + + foreach ($this->joinQuery->getJoins() as $join) { + $joinCols = $join->getAllColumns(); + $columns = \array_merge($columns, $joinCols); + } + + return $columns; + } + + /** + * @return \Phacil\Framework\MagiQL\Syntax\Column + * + * @throws QueryException + */ + public function getColumns() + { + if (\is_null($this->select->getTable())) { + throw new QueryException('No table specified for the Select instance'); + } + + return SyntaxFactory::createColumns($this->columns, $this->select->getTable()); + } + + /** + * Sets the column names used to write the SELECT statement. + * If key is set, key is the column's alias. Value is always the column names. + * + * @param array $columns + * + * @return $this + */ + public function setColumns(array $columns) + { + $this->columns = $columns; + + return $this; + } +} diff --git a/system/magiql/Manipulation/Delete.php b/system/magiql/Manipulation/Delete.php new file mode 100644 index 0000000..01dc1d5 --- /dev/null +++ b/system/magiql/Manipulation/Delete.php @@ -0,0 +1,59 @@ +setTable($table); + } + } + + /** + * @return string + */ + public function partName() + { + return 'DELETE'; + } + + /** + * @return int + */ + public function getLimitStart() + { + return $this->limitStart; + } + + /** + * @param int $start + * + * @return $this + */ + public function limit($start) + { + $this->limitStart = $start; + + return $this; + } +} diff --git a/system/magiql/Manipulation/Insert.php b/system/magiql/Manipulation/Insert.php new file mode 100644 index 0000000..3e3fdf3 --- /dev/null +++ b/system/magiql/Manipulation/Insert.php @@ -0,0 +1,36 @@ +values); + + return SyntaxFactory::createColumns($columns, $this->getTable()); + } +} diff --git a/system/magiql/Manipulation/Intersect.php b/system/magiql/Manipulation/Intersect.php new file mode 100644 index 0000000..ef2a0c3 --- /dev/null +++ b/system/magiql/Manipulation/Intersect.php @@ -0,0 +1,84 @@ +intersect[] = $select; + + return $this; + } + + /** + * @return array + */ + public function getIntersects() + { + return $this->intersect; + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Table + */ + public function getTable() + { + throw new QueryException('INTERSECT does not support tables'); + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Where + */ + public function getWhere() + { + throw new QueryException('INTERSECT does not support WHERE.'); + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Where + */ + public function where() + { + throw new QueryException('INTERSECT does not support the WHERE statement.'); + } +} diff --git a/system/magiql/Manipulation/JoinQuery.php b/system/magiql/Manipulation/JoinQuery.php new file mode 100644 index 0000000..aae91d0 --- /dev/null +++ b/system/magiql/Manipulation/JoinQuery.php @@ -0,0 +1,307 @@ +select = $select; + } + + /** + * @param string $table + * + * @return $this + */ + public function setTable($table) + { + $this->select->setTable($table); + + return $this; + } + + /** + * @param string $table + * @param mixed $selfColumn + * @param mixed $refColumn + * @param string[] $columns + * + * @return Select + */ + public function leftJoin($table, $selfColumn = null, $refColumn = null, $columns = []) + { + return $this->join($table, $selfColumn, $refColumn, $columns, self::JOIN_LEFT); + } + + /** + * @param string $table + * @param mixed $selfColumn + * @param mixed $refColumn + * @param string[] $columns + * @param string $joinType + * + * @return Select + */ + public function join( + $table, + $selfColumn = null, + $refColumn = null, + $columns = [], + $joinType = null + ) { + if (!isset($this->joins[$table])) { + $select = QueryFactory::createSelect($table); + $select->setColumns($columns); + $select->setJoinType($joinType); + $select->setParentQuery($this->select); + $this->addJoin($select, $selfColumn, $refColumn); + } + + return $this->joins[$table]; + } + + /** + * @param Select $select + * @param mixed $selfColumn + * @param mixed $refColumn + * + * @return Select + */ + public function addJoin(Select $select, $selfColumn, $refColumn) + { + $select->isJoin(true); + $table = $select->getTable()->getName(); + + if (!isset($this->joins[$table])) { + if (!$selfColumn instanceof Column) { + $newColumn = array($selfColumn); + $selfColumn = SyntaxFactory::createColumn( + $newColumn, + $this->select->getTable() + ); + } + + $select->joinCondition()->equals($refColumn, $selfColumn); + $this->joins[$table] = $select; + } + + return $this->joins[$table]; + } + + /** + * Transforms Select in a joint. + * + * @param bool $isJoin + * + * @return $this + */ + public function setJoin($isJoin = true) + { + $this->isJoin = $isJoin; + + return $this; + } + + /** + * @param string $table + * @param mixed $selfColumn + * @param mixed $refColumn + * @param string[] $columns + * + * @internal param null $selectClass + * + * @return Select + */ + public function rightJoin($table, $selfColumn = null, $refColumn = null, $columns = []) + { + return $this->join($table, $selfColumn, $refColumn, $columns, self::JOIN_RIGHT); + } + + /** + * @param string $table + * @param mixed $selfColumn + * @param mixed $refColumn + * @param string[] $columns + * + * @return Select + */ + public function crossJoin($table, $selfColumn = null, $refColumn = null, $columns = []) + { + return $this->join($table, $selfColumn, $refColumn, $columns, self::JOIN_CROSS); + } + + /** + * @param string $table + * @param mixed $selfColumn + * @param mixed $refColumn + * @param string[] $columns + * + * @return Select + */ + public function innerJoin($table, $selfColumn = null, $refColumn = null, $columns = []) + { + return $this->join($table, $selfColumn, $refColumn, $columns, self::JOIN_INNER); + } + + /** + * Alias to joinCondition. + * + * @return Where + */ + public function on() + { + return $this->joinCondition(); + } + + /** + * WHERE constrains used for the ON clause of a (LEFT/RIGHT/INNER/CROSS) JOIN. + * + * @return Where + */ + public function joinCondition() + { + if (!isset($this->joinCondition)) { + $this->joinCondition = QueryFactory::createWhere($this->select); + } + + return $this->joinCondition; + } + + /** + * @return bool + */ + public function isJoinSelect() + { + return $this->isJoin; + } + + /** + * @return bool + */ + public function isJoin() + { + return $this->isJoin; + } + + /** + * @return \Phacil\Framework\MagiQL\Syntax\Where + */ + public function getJoinCondition() + { + return $this->joinCondition; + } + + /** + * @param \Phacil\Framework\MagiQL\Syntax\Where $joinCondition + * + * @return $this + */ + public function setJoinCondition($joinCondition) + { + $this->joinCondition = $joinCondition; + + return $this; + } + + /** + * @return string + */ + public function getJoinType() + { + return $this->joinType; + } + + /** + * @param string $joinType + * + * @return $this + */ + public function setJoinType($joinType) + { + $this->joinType = $joinType; + + return $this; + } + + /** + * @return array + */ + public function getJoins() + { + return $this->joins; + } + + /** + * @param array $joins + * + * @return $this + */ + public function setJoins($joins) + { + $this->joins = $joins; + + return $this; + } + + /** + * @return array + */ + public function getAllJoins() + { + $joins = $this->joins; + + foreach ($this->joins as $join) { + $joins = \array_merge($joins, $join->getAllJoins()); + } + + return $joins; + } +} diff --git a/system/magiql/Manipulation/Minus.php b/system/magiql/Manipulation/Minus.php new file mode 100644 index 0000000..f28803c --- /dev/null +++ b/system/magiql/Manipulation/Minus.php @@ -0,0 +1,95 @@ +first = $first; + $this->second = $second; + } + + /** + * @return \Phacil\Framework\MagiQL\Manipulation\Select + */ + public function getFirst() + { + return $this->first; + } + + /** + * @return \Phacil\Framework\MagiQL\Manipulation\Select + */ + public function getSecond() + { + return $this->second; + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Table + */ + public function getTable() + { + throw new QueryException('MINUS does not support tables'); + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Where + */ + public function getWhere() + { + throw new QueryException('MINUS does not support WHERE.'); + } + + /** + * @throws QueryException + * + * @return \Phacil\Framework\MagiQL\Syntax\Where + */ + public function where() + { + throw new QueryException('MINUS does not support the WHERE statement.'); + } +} diff --git a/system/magiql/Manipulation/QueryException.php b/system/magiql/Manipulation/QueryException.php new file mode 100644 index 0000000..99f91d1 --- /dev/null +++ b/system/magiql/Manipulation/QueryException.php @@ -0,0 +1,17 @@ +setTable($table); + } + + $this->joinQuery = new JoinQuery($this); + $this->columnQuery = new ColumnQuery($this, $this->joinQuery, $columns); + } + + /** + * This __clone method will create an exact clone but without the object references due to the fact these + * are lost in the process of serialization and un-serialization. + * + * @return Select + */ + public function __clone() + { + return \unserialize(\serialize($this)); + } + + /** + * @return string + */ + public function partName() + { + return 'SELECT'; + } + + /** + * @param string $table + * @param string $selfColumn + * @param string $refColumn + * @param string[] $columns + * + * @return Select + */ + public function leftJoin($table, $selfColumn = null, $refColumn = null, $columns = []) + { + return $this->joinQuery->leftJoin($table, $selfColumn, $refColumn, $columns); + } + + /** + * @param string $table + * @param string $selfColumn + * @param string $refColumn + * @param string[] $columns + * @param string $joinType + * + * @return Select + */ + public function join( + $table, + $selfColumn = null, + $refColumn = null, + $columns = [], + $joinType = null + ) { + return $this->joinQuery->join($table, $selfColumn, $refColumn, $columns, $joinType); + } + + /** + * WHERE constrains used for the ON clause of a (LEFT/RIGHT/INNER/CROSS) JOIN. + * + * @return Where + */ + public function joinCondition() + { + return $this->joinQuery->joinCondition(); + } + + /** + * @param Select $select + * @param string $selfColumn + * @param string $refColumn + * + * @return Select + */ + public function addJoin(Select $select, $selfColumn, $refColumn) + { + return $this->joinQuery->addJoin($select, $selfColumn, $refColumn); + } + + /** + * Transforms Select in a joint. + * + * @param bool $isJoin + * + * @return JoinQuery + */ + public function isJoin($isJoin = true) + { + return $this->joinQuery->setJoin($isJoin); + } + + /** + * @param string $table + * @param string $selfColumn + * @param string $refColumn + * @param string[] $columns + * + * @internal param null $selectClass + * + * @return Select + */ + public function rightJoin($table, $selfColumn = null, $refColumn = null, $columns = []) + { + return $this->joinQuery->rightJoin($table, $selfColumn, $refColumn, $columns); + } + + /** + * @param string $table + * @param string $selfColumn + * @param string $refColumn + * @param string[] $columns + * + * @return Select + */ + public function crossJoin($table, $selfColumn = null, $refColumn = null, $columns = []) + { + return $this->joinQuery->crossJoin($table, $selfColumn, $refColumn, $columns); + } + + /** + * @param string $table + * @param string $selfColumn + * @param string $refColumn + * @param string[] $columns + * + * @return Select + */ + public function innerJoin($table, $selfColumn = null, $refColumn = null, $columns = []) + { + return $this->joinQuery->innerJoin($table, $selfColumn, $refColumn, $columns); + } + + /** + * Alias to joinCondition. + * + * @return Where + */ + public function on() + { + return $this->joinQuery->joinCondition(); + } + + /** + * @return bool + */ + public function isJoinSelect() + { + return $this->joinQuery->isJoin(); + } + + /** + * @return array + */ + public function getAllColumns() + { + return $this->columnQuery->getAllColumns(); + } + + /** + * @return \Phacil\Framework\MagiQL\Syntax\Column[] + * + * @throws QueryException + */ + public function getColumns() + { + return $this->columnQuery->getColumns(); + } + + /** + * Sets the column names used to write the SELECT statement. + * If key is set, key is the column's alias. Value is always the column names. + * + * @param string[] $columns + * + * @return ColumnQuery + */ + public function setColumns(array $columns) + { + return $this->columnQuery->setColumns($columns); + } + + /** + * Allows setting a Select query as a column value. + * + * @param array $column + * + * @return ColumnQuery + */ + public function setSelectAsColumn(array $column) + { + return $this->columnQuery->setSelectAsColumn($column); + } + + /** + * @return array + */ + public function getColumnSelects() + { + return $this->columnQuery->getColumnSelects(); + } + + /** + * Allows setting a value to the select statement. + * + * @param string $value + * @param string $alias + * + * @return ColumnQuery + */ + public function setValueAsColumn($value, $alias) + { + return $this->columnQuery->setValueAsColumn($value, $alias); + } + + /** + * @return array + */ + public function getColumnValues() + { + return $this->columnQuery->getColumnValues(); + } + + /** + * Allows calculation on columns using predefined SQL functions. + * + * @param string $funcName + * @param string[] $arguments + * @param string $alias + * + * @return ColumnQuery + */ + public function setFunctionAsColumn($funcName, array $arguments, $alias) + { + return $this->columnQuery->setFunctionAsColumn($funcName, $arguments, $alias); + } + + /** + * @return array + */ + public function getColumnFuncs() + { + return $this->columnQuery->getColumnFuncs(); + } + + /** + * Returns all the Where conditions to the BuilderInterface class in order to write the SQL WHERE statement. + * + * @return array + */ + public function getAllWheres() + { + return $this->getAllOperation($this->where, 'getAllWheres'); + } + + /** + * @param null|Where $data + * @param string $operation + * + * @return array + */ + protected function getAllOperation($data, $operation) + { + $collection = []; + + if (!is_null($data)) { + $collection[] = $data; + } + + foreach ($this->joinQuery->getJoins() as $join) { + $collection = \array_merge($collection, $join->$operation()); + } + + return $collection; + } + + /** + * @return array + */ + public function getAllHavings() + { + return $this->getAllOperation($this->having, 'getAllHavings'); + } + + /** + * @param string $columnName + * @param string $alias + * + * @return ColumnQuery + */ + public function count($columnName = '*', $alias = '') + { + return $this->columnQuery->count($columnName, $alias); + } + + /** + * @return bool + */ + public function isCount() + { + return $this->columnQuery->isCount(); + } + + /** + * @param int $start + * @param $count + * + * @return $this + */ + public function limit($start, $count = 0) + { + $this->limitStart = $start; + $this->limitCount = $count; + + return $this; + } + + /** + * @return array + */ + public function getAllJoins() + { + return $this->joinQuery->getAllJoins(); + } + + /** + * @return array + */ + public function getGroupBy() + { + return SyntaxFactory::createColumns($this->groupBy, $this->getTable()); + } + + /** + * @param string[] $columns + * + * @return $this + */ + public function groupBy(array $columns) + { + $this->groupBy = $columns; + + return $this; + } + + /** + * @return Where + */ + public function getJoinCondition() + { + return $this->joinQuery->getJoinCondition(); + } + + /** + * @return string + */ + public function getJoinType() + { + return $this->joinQuery->getJoinType(); + } + + /** + * @param string|null $joinType + * + * @return $this + */ + public function setJoinType($joinType) + { + $this->joinQuery->setJoinType($joinType); + + return $this; + } + + /** + * @param $havingOperator + * + * @throws QueryException + * + * @return Where + */ + public function having($havingOperator = 'AND') + { + if (!isset($this->having)) { + $this->having = QueryFactory::createWhere($this); + } + + if (!in_array($havingOperator, array(Where::CONJUNCTION_AND, Where::CONJUNCTION_OR))) { + throw new QueryException( + "Invalid conjunction specified, must be one of AND or OR, but '".$havingOperator."' was found." + ); + } + + $this->havingOperator = $havingOperator; + + return $this->having; + } + + /** + * @return string + */ + public function getHavingOperator() + { + return $this->havingOperator; + } + + /** + * @return $this + */ + public function distinct() + { + $this->isDistinct = true; + + return $this; + } + + /** + * @return bool + */ + public function isDistinct() + { + return $this->isDistinct; + } + + /** + * @return array + */ + public function getAllOrderBy() + { + return $this->orderBy; + } + + /** + * @return ParentQuery + */ + public function getParentQuery() + { + return $this->parentQuery; + } + + /** + * @param Select $parentQuery + * + * @return $this + */ + public function setParentQuery(Select $parentQuery) + { + $this->parentQuery = $parentQuery; + + return $this; + } + + /** + * @param string $column + * @param string $direction + * @param null $table + * + * @return $this + */ + public function orderBy($column, $direction = OrderBy::ASC, $table = null) + { + $current = parent::orderBy($column, $direction, $table); + if ($this->getParentQuery() != null) { + $this->getParentQuery()->orderBy($column, $direction, \is_null($table) ? $this->getTable() : $table); + } + return $current; + } +} diff --git a/system/magiql/Manipulation/Union.php b/system/magiql/Manipulation/Union.php new file mode 100644 index 0000000..56dea39 --- /dev/null +++ b/system/magiql/Manipulation/Union.php @@ -0,0 +1,26 @@ +limitStart; + } + + /** + * @param int $start + * + * @return $this + */ + public function limit($start) + { + $this->limitStart = $start; + + return $this; + } +} diff --git a/system/magiql/Syntax/Column.php b/system/magiql/Syntax/Column.php new file mode 100644 index 0000000..8c0a2e4 --- /dev/null +++ b/system/magiql/Syntax/Column.php @@ -0,0 +1,139 @@ +setName($name); + $this->setTable($table); + $this->setAlias($alias); + } + + /** + * @return string + */ + public function partName() + { + return 'COLUMN'; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = (string) $name; + + return $this; + } + + /** + * @return Table + */ + public function getTable() + { + return $this->table; + } + + /** + * @param string $table + * + * @return $this + */ + public function setTable($table) + { + $newTable = array($table); + $this->table = SyntaxFactory::createTable($newTable); + + return $this; + } + + /** + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * @param null|string $alias + * + * @return $this + * + * @throws QueryException + */ + public function setAlias($alias) + { + if (0 == \strlen($alias)) { + $this->alias = null; + + return $this; + } + + if ($this->isAll()) { + throw new QueryException("Can't use alias because column name is ALL (*)"); + } + + $this->alias = (string) $alias; + + return $this; + } + + /** + * Check whether column name is '*' or not. + * + * @return bool + */ + public function isAll() + { + return $this->getName() == self::ALL; + } +} diff --git a/system/magiql/Syntax/OrderBy.php b/system/magiql/Syntax/OrderBy.php new file mode 100644 index 0000000..899d6c2 --- /dev/null +++ b/system/magiql/Syntax/OrderBy.php @@ -0,0 +1,91 @@ +setColumn($column); + $this->setDirection($direction); + } + + /** + * @return Column + */ + public function getColumn() + { + return $this->column; + } + + /** + * @param Column $column + * + * @return $this + */ + public function setColumn($column) + { + $this->column = $column; + + return $this; + } + + /** + * @return string + */ + public function getDirection() + { + return $this->direction; + } + + /** + * @param string $direction + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setDirection($direction) + { + if (!in_array($direction, array(self::ASC, self::DESC))) { + throw new \InvalidArgumentException( + "Specified direction '$direction' is not allowed. Only ASC or DESC are allowed." + ); + } + $this->direction = $direction; + + return $this; + } +} diff --git a/system/magiql/Syntax/SyntaxFactory.php b/system/magiql/Syntax/SyntaxFactory.php new file mode 100644 index 0000000..be012a2 --- /dev/null +++ b/system/magiql/Syntax/SyntaxFactory.php @@ -0,0 +1,92 @@ + $column) { + if (!is_object($column)) { + $newColumn = array($column); + $column = self::createColumn($newColumn, $table); + if (!is_numeric($index)) { + $column->setAlias($index); + } + + $createdColumns[] = $column; + } else if ($column instanceof Column) { + $createdColumns[] = $column; + } + } + + return \array_filter($createdColumns); + } + + /** + * Creates a Column object. + * + * @param array $argument + * @param null|Table $table + * + * @return Column + */ + public static function createColumn(array &$argument, $table = null) + { + $columnName = \array_values($argument); + $columnName = $columnName[0]; + + $columnAlias = \array_keys($argument); + $columnAlias = $columnAlias[0]; + + if (\is_numeric($columnAlias) || \strpos($columnName, '*') !== false) { + $columnAlias = null; + } + + return new Column($columnName, (string) $table, $columnAlias); + } + + /** + * Creates a Table object. + * + * @param string[] $table + * + * @return Table + */ + public static function createTable($table) + { + $tableName = $table; + if (\is_array($table)) { + $tableName = \current($table); + $tableAlias = \key($table); + } + + $newTable = new Table($tableName); + + if (isset($tableAlias) && !is_numeric($tableAlias)) { + $newTable->setAlias($tableAlias); + } + + return $newTable; + } +} diff --git a/system/magiql/Syntax/Table.php b/system/magiql/Syntax/Table.php new file mode 100644 index 0000000..ffb0eff --- /dev/null +++ b/system/magiql/Syntax/Table.php @@ -0,0 +1,137 @@ +name = $name; + + if (!is_null($schema)) { + $this->schema = $schema; + } + } + + /** + * @return string + */ + public function __toString() + { + return (string) $this->name; + } + + /** + * @param bool $view + * + * @return $this + */ + public function setView($view) + { + $this->view = $view; + + return $this; + } + + /** + * @return bool + */ + public function isView() + { + return $this->view; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * @return string + */ + public function getCompleteName() + { + $alias = ($this->alias) ? " AS {$this->alias}" : ''; + $schema = ($this->schema) ? "{$this->schema}." : ''; + + return $schema.$this->name.$alias; + } + + /** + * @param string $alias + * + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + + return $this; + } + + /** + * @return string + */ + public function getSchema() + { + return $this->schema; + } + + /** + * @param string + * @param string $schema + * + * @return $this + */ + public function setSchema($schema) + { + $this->schema = $schema; + + return $this; + } +} diff --git a/system/magiql/Syntax/Where.php b/system/magiql/Syntax/Where.php new file mode 100644 index 0000000..acb6d18 --- /dev/null +++ b/system/magiql/Syntax/Where.php @@ -0,0 +1,631 @@ +query = $query; + } + + /** + * Deep copy for nested references. + * + * @return mixed + */ + public function __clone() + { + return \unserialize(\serialize($this)); + } + + /** + * @return bool + */ + public function isEmpty() + { + $empty = \array_merge( + $this->comparisons, + $this->booleans, + $this->betweens, + $this->isNotNull, + $this->isNull, + $this->ins, + $this->notIns, + $this->subWheres, + $this->exists + ); + + return 0 == \count($empty); + } + + /** + * @return string + */ + public function getConjunction() + { + return $this->conjunction; + } + + /** + * @param string $operator + * + * @return $this + * + * @throws QueryException + */ + public function conjunction($operator) + { + if (false === \in_array( + $operator, + [WhereInterface::CONJUNCTION_AND, WhereInterface::CONJUNCTION_OR, WhereInterface::CONJUNCTION_OR_NOT, WhereInterface::CONJUNCTION_AND_NOT] + ) + ) { + throw new QueryException( + "Invalid conjunction specified, must be one of AND or OR, but '".$operator."' was found." + ); + } + $this->conjunction = $operator; + + return $this; + } + + /** + * @return array + */ + public function getSubWheres() + { + return $this->subWheres; + } + + /** + * @param $operator + * + * @return Where + */ + public function subWhere($operator = 'OR') + { + /** @var Where $filter */ + $filter = QueryFactory::createWhere($this->query); + $filter->conjunction($operator); + $filter->setTable($this->getTable()); + + $this->subWheres[] = $filter; + + return $filter; + } + + /** + * @return Table + */ + public function getTable() + { + return $this->query->getTable(); + } + + /** + * Used for subWhere query building. + * + * @param Table $table string + * + * @return $this + */ + public function setTable($table) + { + $this->table = $table; + + return $this; + } + + /** + * equals alias. + * + * @param $column + * @param int $value + * + * @return static + */ + public function eq($column, $value) + { + return $this->equals($column, $value); + } + + /** + * @param $column + * @param $value + * + * @return static + */ + public function equals($column, $value) + { + return $this->compare($column, $value, WhereInterface::OPERATOR_EQUAL); + } + + /** + * @param $column + * @param $value + * @param string $operator + * + * @return $this + */ + protected function compare($column, $value, $operator) + { + $column = $this->prepareColumn($column); + + $this->comparisons[] = [ + 'subject' => $column, + 'conjunction' => $operator, + 'target' => $value, + ]; + + return $this; + } + + /** + * @param $column + * + * @return Column|Select + */ + protected function prepareColumn($column) + { + //This condition handles the "Select as a a column" special case. + //or when compare column is customized. + if ($column instanceof Select || $column instanceof Column) { + return $column; + } + + $newColumn = [$column]; + + return SyntaxFactory::createColumn($newColumn, $this->getTable()); + } + + /** + * @param string $column + * @param int $value + * + * @return static + */ + public function notEquals($column, $value) + { + return $this->compare($column, $value, WhereInterface::OPERATOR_NOT_EQUAL); + } + + /** + * @param string $column + * @param int $value + * + * @return static + */ + public function greaterThan($column, $value) + { + return $this->compare($column, $value, WhereInterface::OPERATOR_GREATER_THAN); + } + + /** + * @param string $column + * @param int $value + * + * @return static + */ + public function greaterThanOrEqual($column, $value) + { + return $this->compare($column, $value, WhereInterface::OPERATOR_GREATER_THAN_OR_EQUAL); + } + + /** + * @param string $column + * @param int $value + * + * @return static + */ + public function lessThan($column, $value) + { + return $this->compare($column, $value, WhereInterface::OPERATOR_LESS_THAN); + } + + /** + * @param string $column + * @param int $value + * + * @return static + */ + public function lessThanOrEqual($column, $value) + { + return $this->compare($column, $value, WhereInterface::OPERATOR_LESS_THAN_OR_EQUAL); + } + + /** + * @param string $column + * @param $value + * + * @return static + */ + public function like($column, $value) + { + return $this->compare($column, $value, WhereInterface::OPERATOR_LIKE); + } + + /** + * @param string $column + * @param int $value + * + * @return static + */ + public function notLike($column, $value) + { + return $this->compare($column, $value, WhereInterface::OPERATOR_NOT_LIKE); + } + + /** + * @param string[] $columns + * @param mixed[] $values + * + * @return static + */ + public function match(array $columns, array $values) + { + return $this->genericMatch($columns, $values, 'natural'); + } + + /** + * @param string[] $columns + * @param mixed[] $values + * @param string $mode + * + * @return $this + */ + protected function genericMatch(array &$columns, array &$values, $mode) + { + $this->match[] = [ + 'columns' => $columns, + 'values' => $values, + 'mode' => $mode, + ]; + + return $this; + } + + /** + * @param string $literal + * + * @return $this + */ + public function asLiteral($literal) + { + $this->comparisons[] = $literal; + + return $this; + } + + /** + * @param string[] $columns + * @param mixed[] $values + * + * @return $this + */ + public function matchBoolean(array $columns, array $values) + { + return $this->genericMatch($columns, $values, 'boolean'); + } + + /** + * @param string[] $columns + * @param mixed[] $values + * + * @return $this + */ + public function matchWithQueryExpansion(array $columns, array $values) + { + return $this->genericMatch($columns, $values, 'query_expansion'); + } + + /** + * @param string $column + * @param int[] $values + * + * @return $this + */ + public function in($column, array $values) + { + $this->ins[$column] = $values; + + return $this; + } + + /** + * @param string $column + * @param int[] $values + * + * @return $this + */ + public function notIn($column, array $values) + { + $this->notIns[$column] = $values; + + return $this; + } + + /** + * @param string $column + * @param int $a + * @param int $b + * + * @return $this + */ + public function between($column, $a, $b) + { + $column = $this->prepareColumn($column); + $this->betweens[] = ['subject' => $column, 'a' => $a, 'b' => $b]; + + return $this; + } + + /** + * @param string $column + * @param int $a + * @param int $b + * + * @return $this + */ + public function notBetween($column, $a, $b) + { + $column = $this->prepareColumn($column); + $this->notBetweens[] = ['subject' => $column, 'a' => $a, 'b' => $b]; + + return $this; + } + + /** + * @param string $column + * + * @return static + */ + public function isNull($column) + { + $column = $this->prepareColumn($column); + $this->isNull[] = ['subject' => $column]; + + return $this; + } + + /** + * @param string $column + * + * @return $this + */ + public function isNotNull($column) + { + $column = $this->prepareColumn($column); + $this->isNotNull[] = ['subject' => $column]; + + return $this; + } + + /** + * @param string $column + * @param int $value + * + * @return $this + */ + public function addBitClause($column, $value) + { + $column = $this->prepareColumn($column); + $this->booleans[] = ['subject' => $column, 'value' => $value]; + + return $this; + } + + /** + * @param Select $select + * + * @return $this + */ + public function exists(Select $select) + { + $this->exists[] = $select; + + return $this; + } + + /** + * @return array + */ + public function getExists() + { + return $this->exists; + } + + /** + * @param Select $select + * + * @return $this + */ + public function notExists(Select $select) + { + $this->notExists[] = $select; + + return $this; + } + + /** + * @return array + */ + public function getNotExists() + { + return $this->notExists; + } + + /** + * @return array + */ + public function getMatches() + { + return $this->match; + } + + /** + * @return array + */ + public function getIns() + { + return $this->ins; + } + + /** + * @return array + */ + public function getNotIns() + { + return $this->notIns; + } + + /** + * @return array + */ + public function getBetweens() + { + return $this->betweens; + } + + /** + * @return array + */ + public function getNotBetweens() + { + return $this->notBetweens; + } + + /** + * @return array + */ + public function getBooleans() + { + return $this->booleans; + } + + /** + * @return array + */ + public function getComparisons() + { + return $this->comparisons; + } + + /** + * @return array + */ + public function getNotNull() + { + return $this->isNotNull; + } + + /** + * @return array + */ + public function getNull() + { + return $this->isNull; + } + + /** + * @return QueryInterface + */ + public function end() + { + return $this->query; + } +} diff --git a/system/magiql/autoload.php b/system/magiql/autoload.php new file mode 100644 index 0000000..04dbbff --- /dev/null +++ b/system/magiql/autoload.php @@ -0,0 +1,3 @@ +