Update types for limit clauses, misc improvements

This commit is contained in:
Timothy Warren 2020-04-17 14:57:56 -04:00
parent 2620df1589
commit c67fabd1f7
18 changed files with 803 additions and 813 deletions

View File

@ -34,14 +34,14 @@
"require-dev": { "require-dev": {
"consolidation/robo": "^2.0.0", "consolidation/robo": "^2.0.0",
"monolog/monolog": "^2.0.1", "monolog/monolog": "^2.0.1",
"phploc/phploc": "^6.0", "phploc/phploc": "^5.0",
"phpmd/phpmd": "^2.8", "phpmd/phpmd": "^2.8",
"phpstan/phpstan": "^0.12.2", "phpstan/phpstan": "^0.12.2",
"phpunit/phpunit": "^9.1", "phpunit/phpunit": "^8.5",
"sebastian/phpcpd": "^5", "sebastian/phpcpd": "^4.1",
"simpletest/simpletest": "^1.1", "simpletest/simpletest": "^1.1",
"squizlabs/php_codesniffer": "^3.0.0", "squizlabs/php_codesniffer": "^3.0.0",
"theseer/phpdox": "*" "theseer/phpdox": "^0.12.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -16,6 +16,7 @@
namespace Query; namespace Query;
use DomainException; use DomainException;
use stdClass;
/** /**
* Connection manager class to manage connections for the * Connection manager class to manage connections for the
@ -33,7 +34,7 @@ final class ConnectionManager {
* Class instance variable * Class instance variable
* @var ConnectionManager|null * @var ConnectionManager|null
*/ */
private static $instance; private static ?ConnectionManager $instance = NULL;
/** /**
* Private constructor to prevent multiple instances * Private constructor to prevent multiple instances
@ -119,7 +120,7 @@ final class ConnectionManager {
/** /**
* Parse the passed parameters and return a connection * Parse the passed parameters and return a connection
* *
* @param object|array $params * @param array|object $params
* @throws Exception\BadDBDriverException * @throws Exception\BadDBDriverException
* @return QueryBuilderInterface * @return QueryBuilderInterface
*/ */
@ -161,13 +162,13 @@ final class ConnectionManager {
/** /**
* Parses params into a dsn and option array * Parses params into a dsn and option array
* *
* @param object | array $params * @param array|object $rawParams
* @return array * @return array
* @throws Exception\BadDBDriverException * @throws Exception\BadDBDriverException
*/ */
public function parseParams($params): array public function parseParams($rawParams): array
{ {
$params = (object) $params; $params = (object) $rawParams;
$params->type = strtolower($params->type); $params->type = strtolower($params->type);
$dbType = ($params->type === 'postgresql') ? 'pgsql' : $params->type; $dbType = ($params->type === 'postgresql') ? 'pgsql' : $params->type;
$dbType = ucfirst($dbType); $dbType = ucfirst($dbType);
@ -205,10 +206,10 @@ final class ConnectionManager {
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $dbType * @param string $dbType
* @param array|object $params * @param stdClass $params
* @return string * @return string
*/ */
private function createDsn(string $dbType, $params): string private function createDsn(string $dbType, stdClass $params): string
{ {
$pairs = []; $pairs = [];

View File

@ -20,6 +20,7 @@ use function dbFilter;
use InvalidArgumentException; use InvalidArgumentException;
use PDO; use PDO;
use PDOStatement; use PDOStatement;
use function is_string;
/** /**
* Base Database class * Base Database class
@ -52,7 +53,7 @@ abstract class AbstractDriver
* Reference to sql class * Reference to sql class
* @var SQLInterface * @var SQLInterface
*/ */
protected SQLInterface $sql; protected SQLInterface $driverSQL;
/** /**
* Reference to util class * Reference to util class
@ -110,7 +111,7 @@ abstract class AbstractDriver
$sqlClass = __NAMESPACE__ . "\\{$driver}\\SQL"; $sqlClass = __NAMESPACE__ . "\\{$driver}\\SQL";
$utilClass = __NAMESPACE__ . "\\{$driver}\\Util"; $utilClass = __NAMESPACE__ . "\\{$driver}\\Util";
$this->sql = new $sqlClass(); $this->driverSQL = new $sqlClass();
$this->util = new $utilClass($this); $this->util = new $utilClass($this);
} }
@ -168,7 +169,7 @@ abstract class AbstractDriver
*/ */
public function getSql(): SQLInterface public function getSql(): SQLInterface
{ {
return $this->sql; return $this->driverSQL;
} }
/** /**
@ -494,7 +495,7 @@ abstract class AbstractDriver
public function driverQuery($query, $filteredIndex=TRUE): ?array public function driverQuery($query, $filteredIndex=TRUE): ?array
{ {
// Call the appropriate method, if it exists // Call the appropriate method, if it exists
if (is_string($query) && method_exists($this->sql, $query)) if (is_string($query) && method_exists($this->driverSQL, $query))
{ {
$query = $this->getSql()->$query(); $query = $this->getSql()->$query();
} }
@ -687,7 +688,7 @@ abstract class AbstractDriver
// and is not already quoted before quoting // and is not already quoted before quoting
// that value, otherwise, return the original value // that value, otherwise, return the original value
return ( return (
\is_string($str) is_string($str)
&& strpos($str, $this->escapeCharOpen) !== 0 && strpos($str, $this->escapeCharOpen) !== 0
&& strrpos($str, $this->escapeCharClose) !== 0 && strrpos($str, $this->escapeCharClose) !== 0
) )

View File

@ -25,10 +25,10 @@ abstract class AbstractSQL implements SQLInterface {
* *
* @param string $sql * @param string $sql
* @param int $limit * @param int $limit
* @param int|bool $offset * @param int $offset
* @return string * @return string
*/ */
public function limit(string $sql, int $limit, $offset=FALSE): string public function limit(string $sql, int $limit, ?int $offset=NULL): string
{ {
$sql .= "\nLIMIT {$limit}"; $sql .= "\nLIMIT {$limit}";

View File

@ -17,6 +17,7 @@ namespace Query\Drivers\Mysql;
use PDO; use PDO;
use Query\Drivers\AbstractDriver; use Query\Drivers\AbstractDriver;
use function defined;
/** /**
* MySQL specific class * MySQL specific class
@ -49,7 +50,7 @@ class Driver extends AbstractDriver {
public function __construct(string $dsn, string $username=NULL, string $password=NULL, array $options=[]) public function __construct(string $dsn, string $username=NULL, string $password=NULL, array $options=[])
{ {
// Set the charset to UTF-8 // Set the charset to UTF-8
if (\defined('\\PDO::MYSQL_ATTR_INIT_COMMAND')) if (defined('\\PDO::MYSQL_ATTR_INIT_COMMAND'))
{ {
$options = array_merge($options, [ $options = array_merge($options, [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF-8 COLLATE 'UTF-8'", PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF-8 COLLATE 'UTF-8'",

View File

@ -30,7 +30,7 @@ class SQL extends AbstractSQL {
* @param int|boolean $offset * @param int|boolean $offset
* @return string * @return string
*/ */
public function limit(string $sql, int $limit, $offset=FALSE): string public function limit(string $sql, int $limit, ?int $offset=NULL): string
{ {
if ( ! is_numeric($offset)) if ( ! is_numeric($offset))
{ {
@ -166,7 +166,7 @@ SQL;
} }
/** /**
* SQL to show infromation about columns in a table * SQL to show information about columns in a table
* *
* @param string $table * @param string $table
* @return string * @return string
@ -186,7 +186,7 @@ SQL;
public function fkList(string $table): string public function fkList(string $table): string
{ {
return <<<SQL return <<<SQL
SELECT DISTINCT SELECT DISTINCT
`kcu`.`COLUMN_NAME` as `child_column`, `kcu`.`COLUMN_NAME` as `child_column`,
`kcu`.`REFERENCED_TABLE_NAME` as `parent_table`, `kcu`.`REFERENCED_TABLE_NAME` as `parent_table`,
`kcu`.`REFERENCED_COLUMN_NAME` as `parent_column`, `kcu`.`REFERENCED_COLUMN_NAME` as `parent_column`,

View File

@ -33,7 +33,8 @@ class Util extends AbstractUtil {
$string = []; $string = [];
// Get databases // Get databases
$dbs = $this->getDriver()->getDbs(); $driver = $this->getDriver();
$dbs = $driver->getDbs();
foreach($dbs as &$d) foreach($dbs as &$d)
{ {
@ -46,11 +47,11 @@ class Util extends AbstractUtil {
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
// Get the list of tables // Get the list of tables
$tables = $this->getDriver()->driverQuery("SHOW TABLES FROM `{$d}`", TRUE); $tables = $driver->driverQuery("SHOW TABLES FROM `{$d}`", TRUE);
foreach($tables as $table) foreach($tables as $table)
{ {
$array = $this->getDriver()->driverQuery("SHOW CREATE TABLE `{$d}`.`{$table}`", FALSE); $array = $driver->driverQuery("SHOW CREATE TABLE `{$d}`.`{$table}`", FALSE);
$row = current($array); $row = current($array);
if ( ! isset($row['Create Table'])) if ( ! isset($row['Create Table']))
@ -72,9 +73,10 @@ class Util extends AbstractUtil {
* @param array $exclude * @param array $exclude
* @return string * @return string
*/ */
public function backupData($exclude=[]): string public function backupData(array $exclude=[]): string
{ {
$tables = $this->getDriver()->getTables(); $driver = $this->getDriver();
$tables = $driver->getTables();
// Filter out the tables you don't want // Filter out the tables you don't want
if( ! empty($exclude)) if( ! empty($exclude))
@ -88,7 +90,7 @@ class Util extends AbstractUtil {
foreach($tables as $t) foreach($tables as $t)
{ {
$sql = "SELECT * FROM `{$t}`"; $sql = "SELECT * FROM `{$t}`";
$res = $this->getDriver()->query($sql); $res = $driver->query($sql);
$rows = $res->fetchAll(PDO::FETCH_ASSOC); $rows = $res->fetchAll(PDO::FETCH_ASSOC);
// Skip empty tables // Skip empty tables
@ -110,7 +112,7 @@ class Util extends AbstractUtil {
// Workaround for Quercus // Workaround for Quercus
foreach($row as &$r) foreach($row as &$r)
{ {
$r = $this->getDriver()->quote($r); $r = $driver->quote($r);
} }
unset($r); unset($r);
$row = array_map('trim', $row); $row = array_map('trim', $row);

View File

@ -15,6 +15,7 @@
*/ */
namespace Query\Drivers\Pgsql; namespace Query\Drivers\Pgsql;
use PDO;
use Query\Drivers\AbstractUtil; use Query\Drivers\AbstractUtil;
/** /**
@ -56,7 +57,7 @@ class Util extends AbstractUtil {
{ {
$sql = 'SELECT * FROM "'.trim($t).'"'; $sql = 'SELECT * FROM "'.trim($t).'"';
$res = $this->getDriver()->query($sql); $res = $this->getDriver()->query($sql);
$objRes = $res->fetchAll(\PDO::FETCH_ASSOC); $objRes = $res->fetchAll(PDO::FETCH_ASSOC);
// Don't add to the file if the table is empty // Don't add to the file if the table is empty
if (count($objRes) < 1) if (count($objRes) < 1)

View File

@ -25,10 +25,10 @@ interface SQLInterface {
* *
* @param string $sql * @param string $sql
* @param int $limit * @param int $limit
* @param int|bool $offset * @param int|null $offset
* @return string * @return string
*/ */
public function limit(string $sql, int $limit, $offset=FALSE): string; public function limit(string $sql, int $limit, ?int $offset=NULL): string;
/** /**
* Modify the query to get the query plan * Modify the query to get the query plan

View File

@ -68,7 +68,7 @@ class Driver extends AbstractDriver {
*/ */
public function getTables(): array public function getTables(): array
{ {
$sql = $this->sql->tableList(); $sql = $this->getSql()->tableList();
$res = $this->query($sql); $res = $this->query($sql);
return dbFilter($res->fetchAll(PDO::FETCH_ASSOC), 'name'); return dbFilter($res->fetchAll(PDO::FETCH_ASSOC), 'name');
} }

View File

@ -57,7 +57,7 @@ class Util extends AbstractUtil {
unset($res); unset($res);
// If the row is empty, continue; // If the row is empty, continue
if (empty($objRes)) if (empty($objRes))
{ {
continue; continue;

28
src/MapType.php Normal file
View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
/**
* Query
*
* SQL Query Builder / Database Abstraction Layer
*
* PHP version 7.4
*
* @package Query
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2012 - 2020 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://git.timshomepage.net/aviat/Query
* @version 3.0.0
*/
namespace Query;
/**
* 'Enum' of query map types
*/
class MapType {
public const GROUP_END = 'group_end';
public const GROUP_START = 'group_start';
public const JOIN = 'join';
public const LIKE = 'like';
public const WHERE = 'where';
public const WHERE_IN = 'where_in';
}

File diff suppressed because it is too large Load Diff

618
src/QueryBuilderBase.php Normal file
View File

@ -0,0 +1,618 @@
<?php declare(strict_types=1);
/**
* Query
*
* SQL Query Builder / Database Abstraction Layer
*
* PHP version 7.4
*
* @package Query
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2012 - 2020 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://git.timshomepage.net/aviat/Query
* @version 3.0.0
*/
namespace Query;
use function regexInArray;
use BadMethodCallException;
use PDO;
use PDOStatement;
use Query\Drivers\DriverInterface;
/**
* @method affectedRows(): int
* @method beginTransaction(): bool
* @method commit(): bool
* @method errorCode(): string
* @method errorInfo(): array
* @method exec(string $statement): int
* @method getAttribute(int $attribute)
* @method getColumns(string $table): array | null
* @method getDbs(): array | null
* @method getFks(string $table): array | null
* @method getFunctions(): array | null
* @method getIndexes(string $table): array | null
* @method getLastQuery(): string
* @method getProcedures(): array | null
* @method getSchemas(): array | null
* @method getSequences(): array | null
* @method getSystemTables(): array | null
* @method getTables(): array
* @method getTriggers(): array | null
* @method getTypes(): array | null
* @method getUtil(): \Query\Drivers\AbstractUtil
* @method getVersion(): string
* @method getViews(): array | null
* @method inTransaction(): bool
* @method lastInsertId(string $name = NULL): string
* @method numRows(): int | null
* @method prepare(string $statement, array $driver_options = []): PDOStatement
* @method prepareExecute(string $sql, array $params): PDOStatement
* @method prepareQuery(string $sql, array $data): PDOStatement
* @method query(string $statement): PDOStatement
* @method quote(string $string, int $parameter_type = PDO::PARAM_STR): string
* @method rollback(): bool
* @method setAttribute(int $attribute, $value): bool
* @method setTablePrefix(string $prefix): void
* @method truncate(string $table): PDOStatement
*/
class QueryBuilderBase {
/**
* Convenience property for connection management
* @var string
*/
public string $connName = '';
/**
* List of queries executed
* @var array
*/
public array $queries = [
'total_time' => 0
];
/**
* Whether to do only an explain on the query
* @var bool
*/
protected bool $explain = FALSE;
/**
* Whether to return data from a modification query
* @var bool
*/
protected bool $returning = FALSE;
/**
* The current database driver
* @var DriverInterface
*/
protected ?DriverInterface $driver;
/**
* Query parser class instance
* @var QueryParser
*/
protected QueryParser $parser;
/**
* Query Builder state
* @var State
*/
protected State $state;
// --------------------------------------------------------------------------
// ! Methods
// --------------------------------------------------------------------------
/**
* Constructor
*
* @param DriverInterface $driver
* @param QueryParser $parser
*/
public function __construct(DriverInterface $driver, QueryParser $parser)
{
// Inject driver and parser
$this->driver = $driver;
$this->parser = $parser;
// Create new State object
$this->state = new State();
}
/**
* Destructor
* @codeCoverageIgnore
*/
public function __destruct()
{
$this->driver = NULL;
}
/**
* Calls a function further down the inheritance chain.
* 'Implements' methods on the driver object
*
* @param string $name
* @param array $params
* @return mixed
* @throws BadMethodCallException
*/
public function __call(string $name, array $params)
{
if (method_exists($this->driver, $name))
{
return $this->driver->$name(...$params);
}
throw new BadMethodCallException('Method does not exist');
}
/**
* Clear out the class variables, so the next query can be run
*
* @return void
*/
public function resetQuery(): void
{
$this->state = new State();
$this->explain = FALSE;
$this->returning = FALSE;
}
/**
* Method to simplify select_ methods
*
* @param string $field
* @param string|bool $as
* @return string
*/
protected function _select(string $field, $as = FALSE): string
{
// Escape the identifiers
$field = $this->driver->quoteIdent($field);
if ( ! \is_string($as))
{
// @codeCoverageIgnoreStart
return $field;
// @codeCoverageIgnoreEnd
}
$as = $this->driver->quoteIdent($as);
return "({$field}) AS {$as} ";
}
/**
* Helper function for returning sql strings
*
* @param string $type
* @param string $table
* @param bool $reset
* @return string
*/
protected function _getCompile(string $type, string $table, bool $reset): string
{
$sql = $this->_compile($type, $table);
// Reset the query builder for the next query
if ($reset)
{
$this->resetQuery();
}
return $sql;
}
/**
* Simplify 'like' methods
*
* @param string $field
* @param mixed $val
* @param string $pos
* @param string $like
* @param string $conj
* @return self
*/
protected function _like(string $field, $val, string $pos, string $like = 'LIKE', string $conj = 'AND'): self
{
$field = $this->driver->quoteIdent($field);
// Add the like string into the order map
$like = $field . " {$like} ?";
if ($pos === 'before')
{
$val = "%{$val}";
} elseif ($pos === 'after')
{
$val = "{$val}%";
} else
{
$val = "%{$val}%";
}
$conj = empty($this->state->getQueryMap()) ? ' WHERE ' : " {$conj} ";
$this->state->appendMap($conj, $like, MapType::LIKE);
// Add to the values array
$this->state->appendWhereValues($val);
return $this;
}
/**
* Simplify building having clauses
*
* @param mixed $key
* @param mixed $values
* @param string $conj
* @return self
*/
protected function _having($key, $values = [], string $conj = 'AND'): self
{
$where = $this->_where($key, $values);
// Create key/value placeholders
foreach ($where as $f => $val)
{
// Split each key by spaces, in case there
// is an operator such as >, <, !=, etc.
$fArray = explode(' ', trim($f));
$item = $this->driver->quoteIdent($fArray[0]);
// Simple key value, or an operator
$item .= (count($fArray) === 1) ? '=?' : " {$fArray[1]} ?";
// Put in the having map
$this->state->appendHavingMap([
'conjunction' => empty($this->state->getHavingMap())
? ' HAVING '
: " {$conj} ",
'string' => $item
]);
}
return $this;
}
/**
* Do all the redundant stuff for where/having type methods
*
* @param mixed $key
* @param mixed $val
* @return array
*/
protected function _where($key, $val = []): array
{
$where = [];
$pairs = [];
if (is_scalar($key))
{
$pairs[$key] = $val;
} else
{
$pairs = $key;
}
foreach ($pairs as $k => $v)
{
$where[$k] = $v;
$this->state->appendWhereValues($v);
}
return $where;
}
/**
* Simplify generating where string
*
* @param mixed $key
* @param mixed $values
* @param string $defaultConj
* @return self
*/
protected function _whereString($key, $values = [], string $defaultConj = 'AND'): self
{
// Create key/value placeholders
foreach ($this->_where($key, $values) as $f => $val)
{
$queryMap = $this->state->getQueryMap();
// Split each key by spaces, in case there
// is an operator such as >, <, !=, etc.
$fArray = explode(' ', trim($f));
$item = $this->driver->quoteIdent($fArray[0]);
// Simple key value, or an operator
$item .= (count($fArray) === 1) ? '=?' : " {$fArray[1]} ?";
$lastItem = end($queryMap);
// Determine the correct conjunction
$conjunctionList = array_column($queryMap, 'conjunction');
if (empty($queryMap) || ( ! regexInArray($conjunctionList, "/^ ?\n?WHERE/i")))
{
$conj = "\nWHERE ";
} elseif ($lastItem['type'] === 'group_start')
{
$conj = '';
} else
{
$conj = " {$defaultConj} ";
}
$this->state->appendMap($conj, $item, MapType::WHERE);
}
return $this;
}
/**
* Simplify where_in methods
*
* @param mixed $key
* @param mixed $val
* @param string $in - The (not) in fragment
* @param string $conj - The where in conjunction
* @return self
*/
protected function _whereIn($key, $val = [], string $in = 'IN', string $conj = 'AND'): self
{
$key = $this->driver->quoteIdent($key);
$params = array_fill(0, count($val), '?');
$this->state->appendWhereValues($val);
$conjunction = empty($this->state->getQueryMap()) ? ' WHERE ' : " {$conj} ";
$str = $key . " {$in} (" . implode(',', $params) . ') ';
$this->state->appendMap($conjunction, $str, MapType::WHERE_IN);
return $this;
}
/**
* Executes the compiled query
*
* @param string $type
* @param string $table
* @param string $sql
* @param array|null $vals
* @param boolean $reset
* @return PDOStatement
*/
protected function _run(string $type, string $table, string $sql = NULL, array $vals = NULL, bool $reset = TRUE): PDOStatement
{
if ($sql === NULL)
{
$sql = $this->_compile($type, $table);
}
if ($vals === NULL)
{
$vals = array_merge($this->state->getValues(), $this->state->getWhereValues());
}
$startTime = microtime(TRUE);
$res = empty($vals)
? $this->driver->query($sql)
: $this->driver->prepareExecute($sql, $vals);
$endTime = microtime(TRUE);
$totalTime = number_format($endTime - $startTime, 5);
// Add this query to the list of executed queries
$this->_appendQuery($vals, $sql, (int)$totalTime);
// Reset class state for next query
if ($reset)
{
$this->resetQuery();
}
return $res;
}
/**
* Convert the prepared statement into readable sql
*
* @param array $values
* @param string $sql
* @param int $totalTime
* @return void
*/
protected function _appendQuery(array $values, string $sql, int $totalTime): void
{
$evals = is_array($values) ? $values : [];
$esql = str_replace('?', '%s', $sql);
// Quote string values
foreach ($evals as &$v)
{
$v = ( ! is_numeric($v))
? htmlentities($this->driver->quote($v), ENT_NOQUOTES, 'utf-8')
: $v;
}
unset($v);
// Add the query onto the array of values to pass
// as arguments to sprintf
array_unshift($evals, $esql);
// Add the interpreted query to the list of executed queries
$this->queries[] = [
'time' => $totalTime,
'sql' => sprintf(...$evals)
];
$this->queries['total_time'] += $totalTime;
// Set the last query to get rowcounts properly
$this->driver->setLastQuery($sql);
}
/**
* Sub-method for generating sql strings
*
* @codeCoverageIgnore
* @param string $type
* @param string $table
* @return string
*/
protected function _compileType(string $type = QueryType::SELECT, string $table = ''): string
{
$setArrayKeys = $this->state->getSetArrayKeys();
switch ($type)
{
case QueryType::INSERT:
$paramCount = count($setArrayKeys);
$params = array_fill(0, $paramCount, '?');
$sql = "INSERT INTO {$table} ("
. implode(',', $setArrayKeys)
. ")\nVALUES (" . implode(',', $params) . ')';
break;
case QueryType::UPDATE:
$setString = $this->state->getSetString();
$sql = "UPDATE {$table}\nSET {$setString}";
break;
case QueryType::DELETE:
$sql = "DELETE FROM {$table}";
break;
case QueryType::SELECT:
default:
$fromString = $this->state->getFromString();
$selectString = $this->state->getSelectString();
$sql = "SELECT * \nFROM {$fromString}";
// Set the select string
if ( ! empty($selectString))
{
// Replace the star with the selected fields
$sql = str_replace('*', $selectString, $sql);
}
break;
}
return $sql;
}
/**
* String together the sql statements for sending to the db
*
* @param string $type
* @param string $table
* @return string
*/
protected function _compile(string $type = '', string $table = ''): string
{
// Get the base clause for the query
$sql = $this->_compileType($type, $this->driver->quoteTable($table));
$clauses = [
'queryMap',
'groupString',
'orderString',
'havingMap',
];
// Set each type of subclause
foreach ($clauses as $clause)
{
$func = 'get' . ucFirst($clause);
$param = $this->state->$func();
if (is_array($param))
{
foreach ($param as $q)
{
$sql .= $q['conjunction'] . $q['string'];
}
} else
{
$sql .= $param;
}
}
// Set the limit via the class variables
$limit = $this->state->getLimit();
if (is_numeric($limit))
{
$sql = $this->driver->getSql()->limit($sql, $limit, $this->state->getOffset());
}
// Set the returning clause, if applicable
$sql = $this->_compileReturning($sql, $type);
// See if the query plan, rather than the
// query data should be returned
if ($this->explain === TRUE)
{
$sql = $this->driver->getSql()->explain($sql);
}
return $sql;
}
/**
* Generate returning clause of query
*
* @param string $sql
* @param string $type
* @return string
*/
protected function _compileReturning(string $sql, string $type): string
{
if ($this->returning === FALSE)
{
return $sql;
}
$rawSelect = $this->state->getSelectString();
$selectString = ($rawSelect === '') ? '*' : $rawSelect;
$returningSQL = $this->driver->returning($sql, $selectString);
if ($returningSQL === $sql)
{
// If the driver doesn't support the returning clause, it returns the original query.
// Fake the same result with a transaction and a select query
if ( ! $this->inTransaction())
{
$this->beginTransaction();
}
// Generate the appropriate select query for the returning clause fallback
switch ($type)
{
case QueryType::INSERT:
// @TODO figure out a good response for insert query
break;
case QueryType::UPDATE:
// @TODO figure out a good response for update query
break;
case QueryType::INSERT_BATCH:
case QueryType::UPDATE_BATCH:
// @TODO figure out a good response for batch queries
break;
default:
// On Delete queries, what would we return?
break;
}
}
return $returningSQL;
}
}

View File

@ -107,14 +107,6 @@ interface QueryBuilderInterface {
*/ */
public function selectSum(string $field, $as=FALSE): self; public function selectSum(string $field, $as=FALSE): self;
/**
* Add a 'returning' clause to an insert,update, or delete query
*
* @param string $fields
* @return self
*/
public function returning(string $fields = '*'): self;
/** /**
* Adds the 'distinct' keyword to a query * Adds the 'distinct' keyword to a query
* *
@ -308,10 +300,10 @@ interface QueryBuilderInterface {
* Set a limit on the current sql statement * Set a limit on the current sql statement
* *
* @param int $limit * @param int $limit
* @param int|bool $offset * @param int|null $offset
* @return self * @return self
*/ */
public function limit(int $limit, $offset=FALSE): self; public function limit(int $limit, ?int $offset=NULL): self;
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// ! Query Grouping Methods // ! Query Grouping Methods
@ -364,22 +356,22 @@ interface QueryBuilderInterface {
* execute current compiled query * execute current compiled query
* *
* @param string $table * @param string $table
* @param int|bool $limit * @param int|null $limit
* @param int|bool $offset * @param int|null $offset
* @return PDOStatement * @return PDOStatement
*/ */
public function get(string $table='', $limit=FALSE, $offset=FALSE): PDOStatement; public function get(string $table='', ?int $limit=NULL, ?int $offset=NULL): PDOStatement;
/** /**
* Convenience method for get() with a where clause * Convenience method for get() with a where clause
* *
* @param string $table * @param string $table
* @param array $where * @param array $where
* @param int|bool $limit * @param int|null $limit
* @param int|bool $offset * @param int|null $offset
* @return PDOStatement * @return PDOStatement
*/ */
public function getWhere(string $table, $where=[], $limit=FALSE, $offset=FALSE): PDOStatement; public function getWhere(string $table, $where=[], ?int $limit=NULL, ?int $offset=NULL): PDOStatement;
/** /**
* Retrieve the number of rows in the selected table * Retrieve the number of rows in the selected table
@ -413,9 +405,9 @@ interface QueryBuilderInterface {
* *
* @param string $table * @param string $table
* @param array $data * @param array $data
* @return PDOStatement * @return PDOStatement | null
*/ */
public function insertBatch(string $table, $data=[]): PDOStatement; public function insertBatch(string $table, $data=[]): ?PDOStatement;
/** /**
* Creates an update clause, and executes it * Creates an update clause, and executes it

View File

@ -21,6 +21,8 @@ namespace Query;
class QueryType { class QueryType {
public const SELECT = 'select'; public const SELECT = 'select';
public const INSERT = 'insert'; public const INSERT = 'insert';
public const INSERT_BATCH = 'insert_batch';
public const UPDATE = 'update'; public const UPDATE = 'update';
public const UPDATE_BATCH = 'update_batch';
public const DELETE = 'delete'; public const DELETE = 'delete';
} }

View File

@ -15,6 +15,8 @@
*/ */
namespace Query; namespace Query;
use function is_array;
/** /**
* Query builder state * Query builder state
* *
@ -28,14 +30,20 @@ namespace Query;
* @method getGroupArray(): array * @method getGroupArray(): array
* @method getValues(): array * @method getValues(): array
* @method getWhereValues(): array * @method getWhereValues(): array
* @method getLimit(): int * @method getLimit(): int|null
* @method getOffset() * @method getOffset()
* @method getQueryMap(): array * @method getQueryMap(): array
* @method getHavingMap(): array * @method getHavingMap(): array
* *
* @method setSelectString(string): self * @method setSelectString(string $selectString): self
* @method setFromString(string): self * @method setFromString(string $fromString): self
* @method setSetString(string): self * @method setSetString(string $setString): self
* @method setOrderString(string $orderString): self
* @method setGroupString(string $groupString): self
* @method setSetArrayKeys(array $arrayKeys): self
* @method setGroupArray(array $array): self
* @method setLimit(int $limit): self
* @method setOffset(?int $offset): self
*/ */
class State { class State {
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
@ -112,15 +120,15 @@ class State {
/** /**
* Value for limit string * Value for limit string
* @var integer * @var int
*/ */
protected int $limit; protected ?int $limit = NULL;
/** /**
* Value for offset in limit string * Value for offset in limit string
* @var string|false * @var int
*/ */
protected $offset = FALSE; protected ?int $offset = NULL;
/** /**
* Query component order mapping * Query component order mapping
@ -177,26 +185,6 @@ class State {
return $this; return $this;
} }
/**
* @param string $orderString
* @return State
*/
public function setOrderString(string $orderString): self
{
$this->orderString = $orderString;
return $this;
}
/**
* @param string $groupString
* @return State
*/
public function setGroupString(string $groupString): self
{
$this->groupString = $groupString;
return $this;
}
/** /**
* @param array $setArrayKeys * @param array $setArrayKeys
* @return State * @return State
@ -207,16 +195,6 @@ class State {
return $this; return $this;
} }
/**
* @param array $setArrayKeys
* @return State
*/
public function setSetArrayKeys(array $setArrayKeys): self
{
$this->setArrayKeys = $setArrayKeys;
return $this;
}
/** /**
* @param string $key * @param string $key
* @param mixed $orderArray * @param mixed $orderArray
@ -228,16 +206,6 @@ class State {
return $this; return $this;
} }
/**
* @param array $groupArray
* @return State
*/
public function setGroupArray(array $groupArray): self
{
$this->groupArray = $groupArray;
return $this;
}
/** /**
* @param string $groupArray * @param string $groupArray
* @return State * @return State
@ -264,7 +232,7 @@ class State {
*/ */
public function appendWhereValues($val): self public function appendWhereValues($val): self
{ {
if (\is_array($val)) if (is_array($val))
{ {
foreach($val as $v) foreach($val as $v)
{ {
@ -278,26 +246,6 @@ class State {
return $this; return $this;
} }
/**
* @param int $limit
* @return State
*/
public function setLimit(int $limit): self
{
$this->limit = $limit;
return $this;
}
/**
* @param string|false $offset
* @return State
*/
public function setOffset($offset): self
{
$this->offset = $offset;
return $this;
}
/** /**
* Add an additional set of mapping pairs to a internal map * Add an additional set of mapping pairs to a internal map
* *

View File

@ -15,10 +15,8 @@
*/ */
namespace { namespace {
use Query\{ use Query\ConnectionManager;
ConnectionManager, use Query\QueryBuilderInterface;
QueryBuilderInterface
};
/** /**
* Global functions that don't really fit anywhere else * Global functions that don't really fit anywhere else
@ -139,6 +137,5 @@ namespace {
// Otherwise, return a new connection // Otherwise, return a new connection
return $manager->connect($paramsObject); return $manager->connect($paramsObject);
} }
} }
// End of common.php // End of common.php