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 @@
+