From 81be910014785d017da9ab9154553102d6c0ed49 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Thu, 24 Apr 2014 17:07:50 -0400 Subject: [PATCH] Split Query Builder class --- core/abstract/abstract_driver.php | 4 +- core/abstract/abstract_query_builder.php | 607 +++++++++++++++++++++++ core/query_builder.php | 578 +-------------------- drivers/firebird/firebird_driver.php | 4 - 4 files changed, 612 insertions(+), 581 deletions(-) create mode 100644 core/abstract/abstract_query_builder.php diff --git a/core/abstract/abstract_driver.php b/core/abstract/abstract_driver.php index c2dbbcd..2cd0787 100644 --- a/core/abstract/abstract_driver.php +++ b/core/abstract/abstract_driver.php @@ -145,7 +145,7 @@ abstract class Abstract_Driver extends \PDO implements Driver_Interface { /** * Get the SQL class for the current driver * - * @return SQL_Interface + * @return SQL\SQL_Interface */ public function get_sql() { @@ -157,7 +157,7 @@ abstract class Abstract_Driver extends \PDO implements Driver_Interface { /** * Get the Util class for the current driver * - * @return Abstract_Util + * @return Util\Abstract_Util */ public function get_util() { diff --git a/core/abstract/abstract_query_builder.php b/core/abstract/abstract_query_builder.php new file mode 100644 index 0000000..4173a79 --- /dev/null +++ b/core/abstract/abstract_query_builder.php @@ -0,0 +1,607 @@ + 'where', + * 'conjunction' => ' AND ', + * 'string' => 'k=?' + * ) + * + * @var array + */ + protected $query_map = array(); + + /** + * Map for having clause + * @var array + */ + protected $having_map; + + /** + * Convenience property for connection management + * @var string + */ + public $conn_name = ""; + + /** + * List of queries executed + * @var array + */ + public $queries; + + /** + * Whether to do only an explain on the query + * @var bool + */ + protected $explain; + + // -------------------------------------------------------------------------- + // Methods + // -------------------------------------------------------------------------- + + /** + * Calls a function further down the inheritence chain + * + * @param string $name + * @param array $params + * @return mixed + * @throws \BadMethodCallException + */ + public function __call($name, $params) + { + // Allow camel-case method calls + $snake_name = \from_camel_case($name); + + foreach(array($this, $this->db) as $object) + { + foreach(array($name, $snake_name) as $method_name) + { + if (method_exists($object, $method_name)) + { + return call_user_func_array(array($object, $method_name), $params); + } + } + + } + + throw new \BadMethodCallException("Method does not exist"); + } + + // -------------------------------------------------------------------------- + + /** + * Method to simplify select_ methods + * + * @param string $field + * @param string|bool $as + * @return string + */ + protected function _select($field, $as = FALSE) + { + // Escape the identifiers + $field = $this->db->quote_ident($field); + + $as = ($as !== FALSE) + ? $this->db->quote_ident($as) + : $field; + + return "({$field}) AS {$as} "; + } + + // -------------------------------------------------------------------------- + + /** + * Helper function for returning sql strings + * + * @param string $type + * @param string $table + * @param bool $reset + * @return string + */ + protected function _get_compile($type, $table, $reset) + { + $sql = $this->_compile($type, $table); + + // Reset the query builder for the next query + if ($reset) + { + $this->reset_query(); + } + + return $sql; + } + + // -------------------------------------------------------------------------- + + /** + * Simplify 'like' methods + * + * @param string $field + * @param mixed $val + * @param string $pos + * @param string $like + * @param string $conj + * @return Query_Builder + */ + protected function _like($field, $val, $pos, $like='LIKE', $conj='AND') + { + $field = $this->db->quote_ident($field); + + // Add the like string into the order map + $l = $field. " {$like} ?"; + + if ($pos == 'before') + { + $val = "%{$val}"; + } + elseif ($pos == 'after') + { + $val = "{$val}%"; + } + else + { + $val = "%{$val}%"; + } + + $conj = (empty($this->query_map)) ? ' WHERE ' : " {$conj} "; + $this->_append_map($conj, $l, 'like'); + + // Add to the values array + $this->where_values[] = $val; + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Simplify building having clauses + * + * @param mixed $key + * @param mixed $val + * @param string $conj + * @return Query_Builder + */ + protected function _having($key, $val=array(), $conj='AND') + { + $where = $this->_where($key, $val); + + // Create key/value placeholders + foreach($where as $f => $val) + { + // Split each key by spaces, in case there + // is an operator such as >, <, !=, etc. + $f_array = explode(' ', trim($f)); + + $item = $this->db->quote_ident($f_array[0]); + + // Simple key value, or an operator + $item .= (count($f_array) === 1) ? '=?' : " {$f_array[1]} ?"; + + // Put in the having map + $this->having_map[] = array( + 'conjunction' => ( ! empty($this->having_map)) ? " {$conj} " : ' HAVING ', + 'string' => $item + ); + } + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Do all the repeditive stuff for where/having type methods + * + * @param mixed $key + * @param mixed $val + * @return array + */ + protected function _where($key, $val=array()) + { + $where = array(); + + // Key and value passed? Add them to the where array + if (is_scalar($key) && is_scalar($val)) + { + $where[$key] = $val; + $this->where_values[] = $val; + } + // Array or object, loop through and add to the where array + elseif ( ! is_scalar($key)) + { + foreach($key as $k => $v) + { + $where[$k] = $v; + $this->where_values[] = $v; + } + } + + return $where; + } + + // -------------------------------------------------------------------------- + + /** + * Simplify generating where string + * + * @param mixed $key + * @param mixed $val + * @param string $conj + * @return Query_Builder + */ + protected function _where_string($key, $val=array(), $conj='AND') + { + $where = $this->_where($key, $val); + + // Create key/value placeholders + foreach($where as $f => $val) + { + // Split each key by spaces, in case there + // is an operator such as >, <, !=, etc. + $f_array = explode(' ', trim($f)); + + $item = $this->db->quote_ident($f_array[0]); + + // Simple key value, or an operator + $item .= (count($f_array) === 1) ? '=?' : " {$f_array[1]} ?"; + + // Get the type of the first item in the query map + $first_item = end($this->query_map); + + // Determine the correct conjunction + if (empty($this->query_map)) + { + $conj = "\nWHERE "; + } + elseif ($first_item['type'] === 'group_start') + { + $conj = ''; + } + else + { + $conj = " {$conj} "; + } + + $this->_append_map($conj, $item, '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 Query_Builder + */ + protected function _where_in($key, $val=array(), $in='IN', $conj='AND') + { + $key = $this->db->quote_ident($key); + $params = array_fill(0, count($val), '?'); + + foreach($val as $v) + { + $this->where_values[] = $v; + } + + $conjunction = ( ! empty($this->query_map)) ? " {$conj} " : ' WHERE '; + $str = $key . " {$in} (".implode(',', $params).') '; + + $this->_append_map($conjunction, $str, 'where_in'); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Executes the compiled query + * + * @param string $type + * @param string $table + * @param string $sql + * @param array|null $vals + * @return \PDOStatement + */ + protected function _run($type, $table, $sql=NULL, $vals=NULL) + { + if (is_null($sql)) + { + $sql = $this->_compile($type, $table); + } + + if (is_null($vals)) + { + $vals = array_merge($this->values, (array) $this->where_values); + } + + $start_time = microtime(TRUE); + + $res = (empty($vals)) + ? $this->db->query($sql) + : $this->db->prepare_execute($sql, $vals); + + $end_time = microtime(TRUE); + $total_time = number_format($end_time - $start_time, 5); + + // Add this query to the list of executed queries + $this->_append_query($vals, $sql, $total_time); + + // Reset class state for next query + $this->reset_query(); + + return $res; + } + + // -------------------------------------------------------------------------- + + /** + * Add an additional set of mapping pairs to a internal map + * + * @param string $conjunction + * @param string $string + * @param string $type + * @return void + */ + protected function _append_map($conjunction = '', $string = '', $type = '') + { + array_push($this->query_map, array( + 'type' => $type, + 'conjunction' => $conjunction, + 'string' => $string + )); + } + + // -------------------------------------------------------------------------- + + /** + * Convert the prepared statement into readable sql + * + * @param array $vals + * @param string $sql + * @param string $total_time + * @return void + */ + protected function _append_query($vals, $sql, $total_time) + { + $evals = (is_array($vals)) ? $vals : array(); + $esql = str_replace('?', "%s", $sql); + + // Quote string values + foreach($evals as &$v) + { + $v = ( ! is_numeric($v)) ? htmlentities($this->db->quote($v), ENT_NOQUOTES, 'utf-8', FALSE) : $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[] = array( + 'time' => $total_time, + 'sql' => call_user_func_array('sprintf', $evals), + ); + + $this->queries['total_time'] += $total_time; + + // Set the last query to get rowcounts properly + $this->db->last_query = $sql; + } + + // -------------------------------------------------------------------------- + + /** + * Sub-method for generating sql strings + * + * @param string $type + * @param string $table + * @return string + */ + protected function _compile_type($type='', $table='') + { + if ($type === 'insert') + { + $param_count = count($this->set_array_keys); + $params = array_fill(0, $param_count, '?'); + $sql = "INSERT INTO {$table} (" + . implode(',', $this->set_array_keys) + . ")\nVALUES (".implode(',', $params).')'; + } + elseif ($type === 'update') + { + $sql = "UPDATE {$table}\nSET {$this->set_string}"; + } + elseif ($type === 'delete') + { + $sql = "DELETE FROM {$table}"; + } + else // GET queries + { + $sql = "SELECT * \nFROM {$this->from_string}"; + + // Set the select string + if ( ! empty($this->select_string)) + { + // Replace the star with the selected fields + $sql = str_replace('*', $this->select_string, $sql); + } + } + + return $sql; + } + + // -------------------------------------------------------------------------- + + /** + * String together the sql statements for sending to the db + * + * @param string $type + * @param string $table + * @return $string + */ + protected function _compile($type='', $table='') + { + // Get the base clause for the query + $sql = $this->_compile_type($type, $this->db->quote_table($table)); + + $clauses = array( + 'query_map', + 'group_string', + 'order_string', + 'having_map', + ); + + // Set each type of subclause + foreach($clauses as $clause) + { + $param = $this->$clause; + if (is_array($param)) + { + foreach($param as $q) + { + $sql .= $q['conjunction'] . $q['string']; + } + } + else + { + $sql .= $param; + } + } + + // Set the limit via the class variables + if (is_numeric($this->limit)) + { + $sql = $this->sql->limit($sql, $this->limit, $this->offset); + } + + // See if the query plan, rather than the + // query data should be returned + if ($this->explain === TRUE) + { + $sql = $this->sql->explain($sql); + } + + return $sql; + } +} + +// End of abstract_query_builder.php \ No newline at end of file diff --git a/core/query_builder.php b/core/query_builder.php index bb99016..e514970 100644 --- a/core/query_builder.php +++ b/core/query_builder.php @@ -14,9 +14,10 @@ // -------------------------------------------------------------------------- namespace Query; - use \Query\Driver\Driver_Interface; +// -------------------------------------------------------------------------- + /** * Convenience class for creating sql queries - also the class that * instantiates the specific db driver @@ -24,130 +25,7 @@ use \Query\Driver\Driver_Interface; * @package Query * @subpackage Query_Builder */ -class Query_Builder implements Query_Builder_Interface { - - // -------------------------------------------------------------------------- - // ! SQL Clause Strings - // -------------------------------------------------------------------------- - - /** - * Compiled 'select' clause - * @var string - */ - protected $select_string = ''; - - /** - * Compiled 'from' clause - * @var string - */ - protected $from_string; - - /** - * Compiled arguments for insert / update - * @var string - */ - protected $set_string; - - /** - * Order by clause - * @var string - */ - protected $order_string; - - /** - * Group by clause - * @var string - */ - protected $group_string; - - // -------------------------------------------------------------------------- - // ! SQL Clause Arrays - // -------------------------------------------------------------------------- - - /** - * Keys for insert/update statement - * @var array - */ - protected $set_array_keys = array(); - - /** - * Key/val pairs for order by clause - * @var array - */ - protected $order_array = array(); - - /** - * Key/val pairs for group by clause - * @var array - */ - protected $group_array = array(); - - // -------------------------------------------------------------------------- - // ! Other Class vars - // -------------------------------------------------------------------------- - - /** - * Values to apply to prepared statements - * @var array - */ - protected $values = array(); - - /** - * Values to apply to where clauses in prepared statements - * @var array - */ - protected $where_values = array(); - - /** - * Value for limit string - * @var string - */ - protected $limit; - - /** - * Value for offset in limit string - * @var int - */ - protected $offset; - - /** - * Query component order mapping - * for complex select queries - * - * Format: - * array( - * 'type' => 'where', - * 'conjunction' => ' AND ', - * 'string' => 'k=?' - * ) - * - * @var array - */ - protected $query_map = array(); - - /** - * Map for having clause - * @var array - */ - protected $having_map; - - /** - * Convenience property for connection management - * @var string - */ - public $conn_name = ""; - - /** - * List of queries executed - * @var array - */ - public $queries; - - /** - * Whether to do only an explain on the query - * @var bool - */ - protected $explain; +class Query_Builder extends Abstract_Query_Builder { /** * The current database driver @@ -211,25 +89,6 @@ class Query_Builder implements Query_Builder_Interface { // ! Select Queries // -------------------------------------------------------------------------- - /** - * Method to simplify select_ methods - * - * @param string $field - * @param string|bool $as - * @return string - */ - protected function _select($field, $as = FALSE) - { - // Escape the identifiers - $field = $this->db->quote_ident($field); - - $as = ($as !== FALSE) - ? $this->db->quote_ident($as) - : $field; - - return "({$field}) AS {$as} "; - } - /** * Specifies rows to select in a query * @@ -392,47 +251,6 @@ class Query_Builder implements Query_Builder_Interface { // ! 'Like' methods // -------------------------------------------------------------------------- - /** - * Simplify 'like' methods - * - * @param string $field - * @param mixed $val - * @param string $pos - * @param string $like - * @param string $conj - * @return Query_Builder - */ - protected function _like($field, $val, $pos, $like='LIKE', $conj='AND') - { - $field = $this->db->quote_ident($field); - - // Add the like string into the order map - $l = $field. " {$like} ?"; - - if ($pos == 'before') - { - $val = "%{$val}"; - } - elseif ($pos == 'after') - { - $val = "{$val}%"; - } - else - { - $val = "%{$val}%"; - } - - $conj = (empty($this->query_map)) ? ' WHERE ' : " {$conj} "; - $this->_append_map($conj, $l, 'like'); - - // Add to the values array - $this->where_values[] = $val; - - return $this; - } - - // -------------------------------------------------------------------------- - /** * Creates a Like clause in the sql statement * @@ -495,42 +313,6 @@ class Query_Builder implements Query_Builder_Interface { // ! Having methods // -------------------------------------------------------------------------- - /** - * Simplify building having clauses - * - * @param mixed $key - * @param mixed $val - * @param string $conj - * @return Query_Builder - */ - protected function _having($key, $val=array(), $conj='AND') - { - $where = $this->_where($key, $val); - - // Create key/value placeholders - foreach($where as $f => $val) - { - // Split each key by spaces, in case there - // is an operator such as >, <, !=, etc. - $f_array = explode(' ', trim($f)); - - $item = $this->db->quote_ident($f_array[0]); - - // Simple key value, or an operator - $item .= (count($f_array) === 1) ? '=?' : " {$f_array[1]} ?"; - - // Put in the having map - $this->having_map[] = array( - 'conjunction' => ( ! empty($this->having_map)) ? " {$conj} " : ' HAVING ', - 'string' => $item - ); - } - - return $this; - } - - // -------------------------------------------------------------------------- - /** * Generates a 'Having' clause * @@ -561,116 +343,6 @@ class Query_Builder implements Query_Builder_Interface { // ! 'Where' methods // -------------------------------------------------------------------------- - /** - * Do all the repedative stuff for where/having type methods - * - * @param mixed $key - * @param mixed $val - * @return array - */ - protected function _where($key, $val=array()) - { - $where = array(); - - // Key and value passed? Add them to the where array - if (is_scalar($key) && is_scalar($val)) - { - $where[$key] = $val; - $this->where_values[] = $val; - } - // Array or object, loop through and add to the where array - elseif ( ! is_scalar($key)) - { - foreach($key as $k => $v) - { - $where[$k] = $v; - $this->where_values[] = $v; - } - } - - return $where; - } - - // -------------------------------------------------------------------------- - - /** - * Simplify generating where string - * - * @param mixed $key - * @param mixed $val - * @param string $conj - * @return Query_Builder - */ - protected function _where_string($key, $val=array(), $conj='AND') - { - $where = $this->_where($key, $val); - - // Create key/value placeholders - foreach($where as $f => $val) - { - // Split each key by spaces, in case there - // is an operator such as >, <, !=, etc. - $f_array = explode(' ', trim($f)); - - $item = $this->db->quote_ident($f_array[0]); - - // Simple key value, or an operator - $item .= (count($f_array) === 1) ? '=?' : " {$f_array[1]} ?"; - - // Get the type of the first item in the query map - $first_item = end($this->query_map); - - // Determine the correct conjunction - if (empty($this->query_map)) - { - $conj = "\nWHERE "; - } - elseif ($first_item['type'] === 'group_start') - { - $conj = ''; - } - else - { - $conj = " {$conj} "; - } - - $this->_append_map($conj, $item, '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 Query_Builder - */ - protected function _where_in($key, $val=array(), $in='IN', $conj='AND') - { - $key = $this->db->quote_ident($key); - $params = array_fill(0, count($val), '?'); - - foreach($val as $v) - { - $this->where_values[] = $v; - } - - $conjunction = ( ! empty($this->query_map)) ? " {$conj} " : ' WHERE '; - $str = $key . " {$in} (".implode(',', $params).') '; - - $this->_append_map($conjunction, $str, 'where_in'); - - return $this; - } - - // -------------------------------------------------------------------------- - /** * Specify condition(s) in the where clause of a query * Note: this function works with key / value, or a @@ -1137,28 +809,7 @@ class Query_Builder implements Query_Builder_Interface { // ! SQL Returning Methods // -------------------------------------------------------------------------- - /** - * Helper function for returning sql strings - * - * @param string $type - * @param string $table - * @param bool $reset - * @return string - */ - protected function _get_compile($type, $table, $reset) - { - $sql = $this->_compile($type, $table); - // Reset the query builder for the next query - if ($reset) - { - $this->reset_query(); - } - - return $sql; - } - - // -------------------------------------------------------------------------- /** * Returns the generated 'select' sql query @@ -1261,228 +912,5 @@ class Query_Builder implements Query_Builder_Interface { $this->$var = array(); } } - - // -------------------------------------------------------------------------- - - /** - * Executes the compiled query - * - * @param string $type - * @param string $table - * @param string $sql - * @param array|null $vals - * @return \PDOStatement - */ - protected function _run($type, $table, $sql=NULL, $vals=NULL) - { - if (is_null($sql)) - { - $sql = $this->_compile($type, $table); - } - - if (is_null($vals)) - { - $vals = array_merge($this->values, (array) $this->where_values); - } - - $start_time = microtime(TRUE); - - $res = (empty($vals)) - ? $this->db->query($sql) - : $this->db->prepare_execute($sql, $vals); - - $end_time = microtime(TRUE); - $total_time = number_format($end_time - $start_time, 5); - - // Add this query to the list of executed queries - $this->_append_query($vals, $sql, $total_time); - - // Reset class state for next query - $this->reset_query(); - - return $res; - } - - // -------------------------------------------------------------------------- - - /** - * Calls a function further down the inheritence chain - * - * @param string $name - * @param array $params - * @return mixed - * @throws \BadMethodCallException - */ - public function __call($name, $params) - { - // Allow camel-case method calls - $snake_name = \from_camel_case($name); - - foreach(array($this, $this->db) as $object) - { - foreach(array($name, $snake_name) as $method_name) - { - if (method_exists($object, $method_name)) - { - return call_user_func_array(array($object, $method_name), $params); - } - } - - } - - throw new \BadMethodCallException("Method does not exist"); - } - - // -------------------------------------------------------------------------- - - /** - * Convert the prepared statement into readable sql - * - * @param array $vals - * @param string $sql - * @param string $total_time - * @return void - */ - protected function _append_query($vals, $sql, $total_time) - { - $evals = (is_array($vals)) ? $vals : array(); - $esql = str_replace('?', "%s", $sql); - - // Quote string values - foreach($evals as &$v) - { - $v = ( ! is_numeric($v)) ? htmlentities($this->db->quote($v), ENT_NOQUOTES, 'utf-8', FALSE) : $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[] = array( - 'time' => $total_time, - 'sql' => call_user_func_array('sprintf', $evals), - ); - - $this->queries['total_time'] += $total_time; - - // Set the last query to get rowcounts properly - $this->db->last_query = $sql; - } - - // -------------------------------------------------------------------------- - - /** - * Add an additional set of mapping pairs to a internal map - * - * @param string $conjunction - * @param string $string - * @param string $type - * @return void - */ - protected function _append_map($conjunction = '', $string = '', $type = '') - { - array_push($this->query_map, array( - 'type' => $type, - 'conjunction' => $conjunction, - 'string' => $string - )); - } - - // -------------------------------------------------------------------------- - - /** - * Sub-method for generating sql strings - * - * @param string $type - * @param string $table - * @return string - */ - protected function _compile_type($type='', $table='') - { - if ($type === 'insert') - { - $param_count = count($this->set_array_keys); - $params = array_fill(0, $param_count, '?'); - $sql = "INSERT INTO {$table} (" - . implode(',', $this->set_array_keys) - . ")\nVALUES (".implode(',', $params).')'; - } - elseif ($type === 'update') - { - $sql = "UPDATE {$table}\nSET {$this->set_string}"; - } - elseif ($type === 'delete') - { - $sql = "DELETE FROM {$table}"; - } - else // GET queries - { - $sql = "SELECT * \nFROM {$this->from_string}"; - - // Set the select string - if ( ! empty($this->select_string)) - { - // Replace the star with the selected fields - $sql = str_replace('*', $this->select_string, $sql); - } - } - - return $sql; - } - - // -------------------------------------------------------------------------- - - /** - * String together the sql statements for sending to the db - * - * @param string $type - * @param string $table - * @return $string - */ - protected function _compile($type='', $table='') - { - // Get the base clause for the query - $sql = $this->_compile_type($type, $this->db->quote_table($table)); - - $clauses = array( - 'query_map', - 'group_string', - 'order_string', - 'having_map', - ); - - // Set each type of subclause - foreach($clauses as $clause) - { - $param = $this->$clause; - if (is_array($param)) - { - foreach($param as $q) - { - $sql .= $q['conjunction'] . $q['string']; - } - } - else - { - $sql .= $param; - } - } - - // Set the limit via the class variables - if (is_numeric($this->limit)) - { - $sql = $this->sql->limit($sql, $this->limit, $this->offset); - } - - // See if the query plan, rather than the - // query data should be returned - if ($this->explain === TRUE) - { - $sql = $this->sql->explain($sql); - } - - return $sql; - } } // End of query_builder.php \ No newline at end of file diff --git a/drivers/firebird/firebird_driver.php b/drivers/firebird/firebird_driver.php index 86c3aaf..875f693 100644 --- a/drivers/firebird/firebird_driver.php +++ b/drivers/firebird/firebird_driver.php @@ -15,10 +15,6 @@ namespace Query\Driver; -use Query\Table\Table_Builder; - -// -------------------------------------------------------------------------- - /** * Firebird Database class *