Update types for limit clauses, misc improvements
This commit is contained in:
parent
2620df1589
commit
c67fabd1f7
@ -34,14 +34,14 @@
|
||||
"require-dev": {
|
||||
"consolidation/robo": "^2.0.0",
|
||||
"monolog/monolog": "^2.0.1",
|
||||
"phploc/phploc": "^6.0",
|
||||
"phploc/phploc": "^5.0",
|
||||
"phpmd/phpmd": "^2.8",
|
||||
"phpstan/phpstan": "^0.12.2",
|
||||
"phpunit/phpunit": "^9.1",
|
||||
"sebastian/phpcpd": "^5",
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"sebastian/phpcpd": "^4.1",
|
||||
"simpletest/simpletest": "^1.1",
|
||||
"squizlabs/php_codesniffer": "^3.0.0",
|
||||
"theseer/phpdox": "*"
|
||||
"theseer/phpdox": "^0.12.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -16,6 +16,7 @@
|
||||
namespace Query;
|
||||
|
||||
use DomainException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Connection manager class to manage connections for the
|
||||
@ -33,7 +34,7 @@ final class ConnectionManager {
|
||||
* Class instance variable
|
||||
* @var ConnectionManager|null
|
||||
*/
|
||||
private static $instance;
|
||||
private static ?ConnectionManager $instance = NULL;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent multiple instances
|
||||
@ -119,7 +120,7 @@ final class ConnectionManager {
|
||||
/**
|
||||
* Parse the passed parameters and return a connection
|
||||
*
|
||||
* @param object|array $params
|
||||
* @param array|object $params
|
||||
* @throws Exception\BadDBDriverException
|
||||
* @return QueryBuilderInterface
|
||||
*/
|
||||
@ -161,13 +162,13 @@ final class ConnectionManager {
|
||||
/**
|
||||
* Parses params into a dsn and option array
|
||||
*
|
||||
* @param object | array $params
|
||||
* @param array|object $rawParams
|
||||
* @return array
|
||||
* @throws Exception\BadDBDriverException
|
||||
*/
|
||||
public function parseParams($params): array
|
||||
public function parseParams($rawParams): array
|
||||
{
|
||||
$params = (object) $params;
|
||||
$params = (object) $rawParams;
|
||||
$params->type = strtolower($params->type);
|
||||
$dbType = ($params->type === 'postgresql') ? 'pgsql' : $params->type;
|
||||
$dbType = ucfirst($dbType);
|
||||
@ -205,10 +206,10 @@ final class ConnectionManager {
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $dbType
|
||||
* @param array|object $params
|
||||
* @param stdClass $params
|
||||
* @return string
|
||||
*/
|
||||
private function createDsn(string $dbType, $params): string
|
||||
private function createDsn(string $dbType, stdClass $params): string
|
||||
{
|
||||
$pairs = [];
|
||||
|
||||
|
@ -20,6 +20,7 @@ use function dbFilter;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Base Database class
|
||||
@ -52,7 +53,7 @@ abstract class AbstractDriver
|
||||
* Reference to sql class
|
||||
* @var SQLInterface
|
||||
*/
|
||||
protected SQLInterface $sql;
|
||||
protected SQLInterface $driverSQL;
|
||||
|
||||
/**
|
||||
* Reference to util class
|
||||
@ -110,7 +111,7 @@ abstract class AbstractDriver
|
||||
$sqlClass = __NAMESPACE__ . "\\{$driver}\\SQL";
|
||||
$utilClass = __NAMESPACE__ . "\\{$driver}\\Util";
|
||||
|
||||
$this->sql = new $sqlClass();
|
||||
$this->driverSQL = new $sqlClass();
|
||||
$this->util = new $utilClass($this);
|
||||
}
|
||||
|
||||
@ -168,7 +169,7 @@ abstract class AbstractDriver
|
||||
*/
|
||||
public function getSql(): SQLInterface
|
||||
{
|
||||
return $this->sql;
|
||||
return $this->driverSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -494,7 +495,7 @@ abstract class AbstractDriver
|
||||
public function driverQuery($query, $filteredIndex=TRUE): ?array
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
@ -687,7 +688,7 @@ abstract class AbstractDriver
|
||||
// and is not already quoted before quoting
|
||||
// that value, otherwise, return the original value
|
||||
return (
|
||||
\is_string($str)
|
||||
is_string($str)
|
||||
&& strpos($str, $this->escapeCharOpen) !== 0
|
||||
&& strrpos($str, $this->escapeCharClose) !== 0
|
||||
)
|
||||
|
@ -25,10 +25,10 @@ abstract class AbstractSQL implements SQLInterface {
|
||||
*
|
||||
* @param string $sql
|
||||
* @param int $limit
|
||||
* @param int|bool $offset
|
||||
* @param int $offset
|
||||
* @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}";
|
||||
|
||||
|
@ -17,6 +17,7 @@ namespace Query\Drivers\Mysql;
|
||||
|
||||
use PDO;
|
||||
use Query\Drivers\AbstractDriver;
|
||||
use function defined;
|
||||
|
||||
/**
|
||||
* MySQL specific class
|
||||
@ -49,7 +50,7 @@ class Driver extends AbstractDriver {
|
||||
public function __construct(string $dsn, string $username=NULL, string $password=NULL, array $options=[])
|
||||
{
|
||||
// Set the charset to UTF-8
|
||||
if (\defined('\\PDO::MYSQL_ATTR_INIT_COMMAND'))
|
||||
if (defined('\\PDO::MYSQL_ATTR_INIT_COMMAND'))
|
||||
{
|
||||
$options = array_merge($options, [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF-8 COLLATE 'UTF-8'",
|
||||
|
@ -30,7 +30,7 @@ class SQL extends AbstractSQL {
|
||||
* @param int|boolean $offset
|
||||
* @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))
|
||||
{
|
||||
@ -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
|
||||
* @return string
|
||||
@ -186,7 +186,7 @@ SQL;
|
||||
public function fkList(string $table): string
|
||||
{
|
||||
return <<<SQL
|
||||
SELECT DISTINCT
|
||||
SELECT DISTINCT
|
||||
`kcu`.`COLUMN_NAME` as `child_column`,
|
||||
`kcu`.`REFERENCED_TABLE_NAME` as `parent_table`,
|
||||
`kcu`.`REFERENCED_COLUMN_NAME` as `parent_column`,
|
||||
|
@ -33,7 +33,8 @@ class Util extends AbstractUtil {
|
||||
$string = [];
|
||||
|
||||
// Get databases
|
||||
$dbs = $this->getDriver()->getDbs();
|
||||
$driver = $this->getDriver();
|
||||
$dbs = $driver->getDbs();
|
||||
|
||||
foreach($dbs as &$d)
|
||||
{
|
||||
@ -46,11 +47,11 @@ class Util extends AbstractUtil {
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// 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)
|
||||
{
|
||||
$array = $this->getDriver()->driverQuery("SHOW CREATE TABLE `{$d}`.`{$table}`", FALSE);
|
||||
$array = $driver->driverQuery("SHOW CREATE TABLE `{$d}`.`{$table}`", FALSE);
|
||||
$row = current($array);
|
||||
|
||||
if ( ! isset($row['Create Table']))
|
||||
@ -72,9 +73,10 @@ class Util extends AbstractUtil {
|
||||
* @param array $exclude
|
||||
* @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
|
||||
if( ! empty($exclude))
|
||||
@ -88,7 +90,7 @@ class Util extends AbstractUtil {
|
||||
foreach($tables as $t)
|
||||
{
|
||||
$sql = "SELECT * FROM `{$t}`";
|
||||
$res = $this->getDriver()->query($sql);
|
||||
$res = $driver->query($sql);
|
||||
$rows = $res->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Skip empty tables
|
||||
@ -110,7 +112,7 @@ class Util extends AbstractUtil {
|
||||
// Workaround for Quercus
|
||||
foreach($row as &$r)
|
||||
{
|
||||
$r = $this->getDriver()->quote($r);
|
||||
$r = $driver->quote($r);
|
||||
}
|
||||
unset($r);
|
||||
$row = array_map('trim', $row);
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
namespace Query\Drivers\Pgsql;
|
||||
|
||||
use PDO;
|
||||
use Query\Drivers\AbstractUtil;
|
||||
|
||||
/**
|
||||
@ -56,7 +57,7 @@ class Util extends AbstractUtil {
|
||||
{
|
||||
$sql = 'SELECT * FROM "'.trim($t).'"';
|
||||
$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
|
||||
if (count($objRes) < 1)
|
||||
|
@ -25,10 +25,10 @@ interface SQLInterface {
|
||||
*
|
||||
* @param string $sql
|
||||
* @param int $limit
|
||||
* @param int|bool $offset
|
||||
* @param int|null $offset
|
||||
* @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
|
||||
|
@ -68,7 +68,7 @@ class Driver extends AbstractDriver {
|
||||
*/
|
||||
public function getTables(): array
|
||||
{
|
||||
$sql = $this->sql->tableList();
|
||||
$sql = $this->getSql()->tableList();
|
||||
$res = $this->query($sql);
|
||||
return dbFilter($res->fetchAll(PDO::FETCH_ASSOC), 'name');
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class Util extends AbstractUtil {
|
||||
|
||||
unset($res);
|
||||
|
||||
// If the row is empty, continue;
|
||||
// If the row is empty, continue
|
||||
if (empty($objRes))
|
||||
{
|
||||
continue;
|
||||
|
28
src/MapType.php
Normal file
28
src/MapType.php
Normal 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
618
src/QueryBuilderBase.php
Normal 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;
|
||||
}
|
||||
}
|
@ -107,14 +107,6 @@ interface QueryBuilderInterface {
|
||||
*/
|
||||
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
|
||||
*
|
||||
@ -308,10 +300,10 @@ interface QueryBuilderInterface {
|
||||
* Set a limit on the current sql statement
|
||||
*
|
||||
* @param int $limit
|
||||
* @param int|bool $offset
|
||||
* @param int|null $offset
|
||||
* @return self
|
||||
*/
|
||||
public function limit(int $limit, $offset=FALSE): self;
|
||||
public function limit(int $limit, ?int $offset=NULL): self;
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// ! Query Grouping Methods
|
||||
@ -364,22 +356,22 @@ interface QueryBuilderInterface {
|
||||
* execute current compiled query
|
||||
*
|
||||
* @param string $table
|
||||
* @param int|bool $limit
|
||||
* @param int|bool $offset
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @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
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $where
|
||||
* @param int|bool $limit
|
||||
* @param int|bool $offset
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @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
|
||||
@ -413,9 +405,9 @@ interface QueryBuilderInterface {
|
||||
*
|
||||
* @param string $table
|
||||
* @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
|
||||
|
@ -21,6 +21,8 @@ namespace Query;
|
||||
class QueryType {
|
||||
public const SELECT = 'select';
|
||||
public const INSERT = 'insert';
|
||||
public const INSERT_BATCH = 'insert_batch';
|
||||
public const UPDATE = 'update';
|
||||
public const UPDATE_BATCH = 'update_batch';
|
||||
public const DELETE = 'delete';
|
||||
}
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
namespace Query;
|
||||
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Query builder state
|
||||
*
|
||||
@ -28,14 +30,20 @@ namespace Query;
|
||||
* @method getGroupArray(): array
|
||||
* @method getValues(): array
|
||||
* @method getWhereValues(): array
|
||||
* @method getLimit(): int
|
||||
* @method getLimit(): int|null
|
||||
* @method getOffset()
|
||||
* @method getQueryMap(): array
|
||||
* @method getHavingMap(): array
|
||||
*
|
||||
* @method setSelectString(string): self
|
||||
* @method setFromString(string): self
|
||||
* @method setSetString(string): self
|
||||
* @method setSelectString(string $selectString): self
|
||||
* @method setFromString(string $fromString): 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 {
|
||||
// --------------------------------------------------------------------------
|
||||
@ -112,15 +120,15 @@ class State {
|
||||
|
||||
/**
|
||||
* Value for limit string
|
||||
* @var integer
|
||||
* @var int
|
||||
*/
|
||||
protected int $limit;
|
||||
protected ?int $limit = NULL;
|
||||
|
||||
/**
|
||||
* Value for offset in limit string
|
||||
* @var string|false
|
||||
* @var int
|
||||
*/
|
||||
protected $offset = FALSE;
|
||||
protected ?int $offset = NULL;
|
||||
|
||||
/**
|
||||
* Query component order mapping
|
||||
@ -177,26 +185,6 @@ class State {
|
||||
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
|
||||
* @return State
|
||||
@ -207,16 +195,6 @@ class State {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $setArrayKeys
|
||||
* @return State
|
||||
*/
|
||||
public function setSetArrayKeys(array $setArrayKeys): self
|
||||
{
|
||||
$this->setArrayKeys = $setArrayKeys;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $orderArray
|
||||
@ -228,16 +206,6 @@ class State {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $groupArray
|
||||
* @return State
|
||||
*/
|
||||
public function setGroupArray(array $groupArray): self
|
||||
{
|
||||
$this->groupArray = $groupArray;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $groupArray
|
||||
* @return State
|
||||
@ -264,7 +232,7 @@ class State {
|
||||
*/
|
||||
public function appendWhereValues($val): self
|
||||
{
|
||||
if (\is_array($val))
|
||||
if (is_array($val))
|
||||
{
|
||||
foreach($val as $v)
|
||||
{
|
||||
@ -278,26 +246,6 @@ class State {
|
||||
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
|
||||
*
|
||||
|
@ -15,10 +15,8 @@
|
||||
*/
|
||||
namespace {
|
||||
|
||||
use Query\{
|
||||
ConnectionManager,
|
||||
QueryBuilderInterface
|
||||
};
|
||||
use Query\ConnectionManager;
|
||||
use Query\QueryBuilderInterface;
|
||||
|
||||
/**
|
||||
* Global functions that don't really fit anywhere else
|
||||
@ -139,6 +137,5 @@ namespace {
|
||||
// Otherwise, return a new connection
|
||||
return $manager->connect($paramsObject);
|
||||
}
|
||||
|
||||
}
|
||||
// End of common.php
|
||||
|
Loading…
Reference in New Issue
Block a user