| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- <?php
- /**
- * pQuery plugin for executing MySQL queries.
- *
- * @package pQuery
- */
- /**
- * @todo Documentation
- * @property $query The query that is being evaluated.
- */
- class pQuerySql extends pQuery {
- static $accepts = array('string' => 'parse_query', 'resource');
-
- /**
- * The pattern to use for specifying variables in a query.
- */
- const VARIABLE_PATTERN = '/\[\s*%s\s*\]/';
-
- /**
- * The default row fetching type, one of 'assoc', 'object' or 'array'.
- */
- const DEFAULT_FETCH_TYPE = 'assoc';
-
- /**
- * The MySQL link identifier.
- *
- * @var resource
- */
- static $link;
-
- /**
- * @see pQuery::$variable_alias
- * @var string|array
- */
- static $variable_alias = 'query';
-
- /**
- * Database login data, should be an associative array containing
- * values for 'host', 'username', 'password' and 'dbname'
- *
- * @var array
- */
- static $login_data = array();
-
- /**
- * The result of the current query.
- *
- * @var resource|bool
- */
- var $result;
-
- /**
- * Indicates whether the query has been executed yet.
- *
- * @var bool
- */
- var $executed;
-
- /**
- * Parse the given query string.
- */
- function parse_query() {
- $args = $this->arguments;
-
- if( !count($args) )
- return;
-
- // Parse arguments as variables. Arrays and
- // Replace variable indices by names equal to their indices
- $variables = array();
-
- foreach( $args as $i => $argument ) {
- if( is_array($argument) )
- $variables = array_merge($variables, $argument);
- else
- $variables[$i] = $argument;
- }
-
- // Replace variables by their escaped values
- $this->set($variables);
- }
-
- /**
- * Replace a set of variables with their (optionally escaped)
- * values in the current query.
- *
- * @param array $variables The variables to replace.
- * @param bool $escape Whether to escape the variable values.
- * @returns pQuerySql The current query object.
- */
- function replace_variables($variables, $escape) {
- $patterns = array_map('pQuerySql::variable_pattern', array_keys($variables));
- $escape && $variables = array_map('pQuerySql::escape', $variables);
- $this->variable = preg_replace($patterns, $variables, $this->variable);
- $this->executed = false;
-
- return $this;
- }
-
- /**
- * Replace a set of variables with their escaped values in the current query.
- *
- * @param array $variables The variables to replace.
- * @returns pQuerySql The current query object.
- */
- function set($variables) {
- return $this->replace_variables($variables, true);
- }
-
- /**
- * Replace a set of variables with their non-escaped values in the current query.
- *
- * @param array $variables The variables to replace.
- * @returns pQuerySql The current query object.
- */
- function set_unescaped($variables) {
- return $this->replace_variables($variables, false);
- }
-
- /**
- * Transform a variable name to a regex to be used as a replacement
- * pattern in a query.
- *
- * @param string $name The variable name to transform.
- * @returns string The variable's replacement pattern.
- */
- static function variable_pattern($name) {
- return sprintf(self::VARIABLE_PATTERN, $name);
- }
-
- /**
- * Execute the current query.
- *
- * @returns pQuerySql The current query object.
- */
- function execute() {
- self::assert_connection();
-
- $result = mysql_query($this->query, self::$link);
-
- if( !$result )
- return self::mysql_error($this->query);
-
- $this->result = $result;
- $this->executed = true;
-
- return $this;
- }
-
- /**
- * Find the number of resulting rows of the current query.
- *
- * @returns int The number of result rows.
- * @uses mysql_num_rows
- */
- function num_rows() {
- $this->assert_execution();
-
- return is_resource($this->result) ? mysql_num_rows($this->result) : 0;
- }
-
- /**
- * Find the number of rows affected by the current query.
- *
- * @returns int The number of affected rows.
- * @uses mysql_affected_rows
- */
- function affected_rows() {
- $this->assert_execution();
-
- return mysql_affected_rows(self::$link);
- }
-
- /**
- * Fetch a row from the current result.
- *
- * @param string $type The format of the result row.
- * @returns mixed The fetched row in the requested format.
- */
- function fetch($type=self::DEFAULT_FETCH_TYPE) {
- $this->assert_execution();
-
- if( !is_resource($this->result) )
- return self::error('Query result is not a resource.');
-
- $func = 'mysql_fetch_'.$type;
-
- if( !function_exists($func) )
- return self::error('Fetch type "%s" is not supported.', $type);
-
- return $func($this->result);
- }
-
- /**
- * Fetch all rows from the current result.
- *
- * @param string $type The format of the result rows.
- * @returns array The result set.
- */
- function fetch_all($type=self::DEFAULT_FETCH_TYPE) {
- $results = array();
-
- while( ($row = $this->fetch($type)) !== false )
- $results[] = $row;
-
- return $results;
-
- return $func($this->result);
- }
-
- /**
- * Assert that the current query has been executed.
- */
- function assert_execution() {
- $this->executed || $this->execute();
- }
-
- /**
- * Set database server login data.
- *
- * @param string $host The database server to connect with.
- * @param string $username The username to login with on the database server.
- * @param string $password The password to login with on the database server.
- * @param string $dbname The name of the database to select after connecting to the server.
- */
- static function set_login_data($host, $username, $password, $dbname) {
- // Close any existing connection
- self::disconnect();
-
- self::$login_data = array_merge(self::$login_data,
- compact('host', 'username', 'password', 'dbname'));
- }
-
- /**
- * Assert that the database server config has been set.
- */
- static function assert_login_data_exist() {
- if( !isset(self::$login_data['host']) )
- return self::error('No MySQL database server host is specified.');
-
- if( !isset(self::$login_data['username']) )
- return self::error('No username is specified for the MySQL server.');
-
- if( !isset(self::$login_data['password']) )
- return self::error('No password is specified for the MySQL server.');
-
- if( !isset(self::$login_data['host']) )
- return self::error('No MySQL database name is specified.');
- }
-
- /**
- * Assert that the MySQL connection is opened.
- *
- * @uses mysql_connect,mysql_select_db
- */
- static function assert_connection() {
- // Return if the connection has already been opened
- if( self::$link )
- return;
-
- self::assert_login_data_exist();
-
- // Connect to the database
- $c = self::$login_data;
- $link = @mysql_connect($c['host'], $c['username'], $c['password']);
-
- if( $link === false )
- return self::mysql_error();
-
- self::$link = $link;
-
- // Select the correct database
- if( !@mysql_select_db($c['dbname'], $link) )
- return self::mysql_error();
- }
-
- /**
- * Close the current connection, if any.
- *
- * @uses mysql_close
- */
- static function disconnect() {
- // Return if the connection has already been closed
- if( !self::$link )
- return;
-
- mysql_close(self::$link);
- self::$link = null;
- }
-
- /**
- * Echo the latest MySQL error.
- * If a query is specified and debug mode is on, add the query to the error message.
- *
- * @param string $query The query that was executed, if any.
- * @codeCoverageIgnore
- */
- static function mysql_error($query='') {
- $error = sprintf('MySQL error %d: %s.', mysql_errno(), mysql_error());
- PQUERY_DEBUG && $error .= "\nQuery: ".$query;
-
- self::error($error);
- }
-
- /**
- * Escape a string for safe use in a query.
- *
- * @param string $value The string to escape.
- * @uses mysql_real_escape_string
- */
- static function escape($value) {
- self::assert_connection();
-
- return mysql_real_escape_string($value, self::$link);
- }
-
- /**
- * Select all records from the given table that match the constraints.
- *
- * @param string $table The table to select from.
- * @param array $constraints Column names pointing to their values.
- * @param bool $escape Whether to escape the constraint values. Defaults to TRUE.
- * @returns pQuerySql The created query instance.
- */
- static function select($table, $columns, $constraints=array(), $escape=true) {
- return _sql("SELECT [columns] FROM `[table]` WHERE [constraints];")
- ->set_unescaped(array(
- 'columns' => self::parse_columns($columns),
- 'table' => $table,
- 'constraints' => self::parse_constraints($constraints, $escape)
- ));
- }
-
- /**
- * Apply the given changes to all records in the given table that
- * match the constraints.
- *
- * @param string $table The table to update in.
- * @param array $changes Column names pointing to their new values.
- * @param array $constraints Column names pointing to their values.
- * @param bool $escape Whether to escape the changed values and the
- * constraint values. Defaults to TRUE.
- * @returns pQuerySql The created query instance.
- */
- static function update($table, $changes, $constraints=array(), $escape=true) {
- // Parse changes
- $escaped_changes = array();
-
- foreach( $changes as $column => $value ) {
- $column = self::escape_column($column);
- $value = self::escape_value($value);
- $escaped_changes[] = "$column = $value";
- }
-
- return _sql("UPDATE `[table]` SET [changes] WHERE [constraints];")
- ->set_unescaped(array(
- 'table' => $table,
- 'changes' => implode(", ", $escaped_changes),
- 'constraints' => self::parse_constraints($constraints, $escape)
- ));
- }
-
- /**
- * Insert a record in the given table.
- *
- * @param string $table The table to insert into.
- * @param array $values The values to insert, pointed to by their column names.
- * @param bool $escape Whether to escape the values. Defaults to TRUE.
- * @returns pQuerySql The created query instance.
- */
- static function insert_row($table, $values, $escape=true) {
- $columns = array_keys($values);
- $escape && $values = array_map('pQuerySql::escape', $values);
-
- return _sql("INSERT INTO `[table]`([columns]) VALUES([values]);")
- ->set_unescaped(array(
- 'table' => $table,
- 'columns' => "`".implode("`, `", $columns)."`",
- 'values' => "'".implode("', '", $values)."'"
- ));
- }
-
- /**
- * Delete all records from the given table that match the constraints.
- *
- * @param string $table The table to delete from.
- * @param array $constraints Column names pointing to their values.
- * @param bool $escape Whether to escape the constraint values. Defaults to TRUE.
- * @returns pQuerySql The created query instance.
- */
- static function delete($table, $constraints, $escape=true) {
- return _sql("DELETE FROM `[table]` WHERE [constraints];")
- ->set_unescaped(array(
- 'table' => $table,
- 'constraints' => self::parse_constraints($constraints, $escape)
- ));
- }
-
- /**
- * Parse a list of column names.
- *
- * @param string|array $columns One of:
- * - '*': Returns itself.
- * - string: Treated as a column name or aggregate function, and
- * escaped as such with backticks.
- * - array:
- * @returns string The parsed columns.
- */
- static function parse_columns($columns) {
- if( $columns == '*' )
- return '*';
-
- if( is_string($columns) )
- return self::escape_column($columns);
-
- if( !is_array($columns) )
- return self::error('Unknown columns type.');
-
- $escaped_columns = array();
-
- foreach( $columns as $key => $value ) {
- if( is_numeric($key) ) {
- // Simple column name
- $escaped_columns[] = self::escape_column($value);
- } else {
- // MySQL 'AS' construction
- $escaped_columns[] = self::escape_column($key)." AS `".$value."`";
- }
- }
-
- return implode(", ", $escaped_columns);
- }
-
- /**
- * Escape a column name to be safely used (and in a tidy manner) in a column list.
- *
- * @param string $column The column name to escape.
- * @returns string The escaped column.
- */
- static function escape_column($column) {
- if( preg_match('/^`.*?`$/', $column) ) {
- // `column` -> `column`
- return $column;
- } elseif( preg_match('/^(\w+)\.(\w+)$/', $column, $m) ) {
- // table.column -> `table`.`column`
- list($table, $column) = array_slice($m, 1);
- return "`$table`.`$column`";
- } elseif( preg_match('/^(\w+)\(([^)]+)\)$/', $column, $m) ) {
- // function(name) -> FUNCTION(`name`)
- // function(`name`) -> FUNCTION(`name`)
- list($aggregate_function, $column) = array_slice($m, 1);
- return strtoupper($aggregate_function)."(".self::escape_column($column).")";
- }
-
- // column -> `column`
- return "`$column`";
- }
-
- /**
- * Escape a value so that it can be saved safely.
- *
- * @param string $value The value to escape.
- * @returns string The escaped value.
- */
- static function escape_value($value) {
- if( preg_match("/^'[^']*'$/", $value) ) {
- // 'value' -> 'value'
- return $value;
- }
-
- // value -> 'value'
- return "'$value'";
- }
-
- /**
- * Parse a list of constraints.
- *
- * @param mixed $constraints One of:
- * - A variable that evaluates as "empty", which will yield the string '1'.
- * - A string, which will be returned unchanged.
- * - A list of column names pointing to their values. A value may be
- * a list, wich will yield a query with the MySQL 'IN' selector.
- * @param bool $escape Whether to escape the values.
- * @returns string The parsed constraints.
- */
- static function parse_constraints($constraints, $escape) {
- if( empty($constraints) )
- return "1";
-
- if( is_string($constraints) )
- return $constraints;
-
- if( !is_array($constraints) )
- return self::error('Unknown constraints type.');
-
- $conditions = array();
-
- foreach( $constraints as $column => $value ) {
- $condition = "`$column` ";
-
- if( is_array($value) ) {
- $escape && $value = array_map('pQuerySql::escape', $value);
- $value = array_map('pQuerySql::escape_value', $value);
- $condition .= "IN (".implode(", ", $value).")";
- } else {
- $escape && $value = self::escape($value);
- $condition .= "= ".self::escape_value($value);
- }
-
- $conditions[] = $condition;
- }
-
- return implode(" AND ", $conditions);
- }
- }
- /**
- * Shortcut constructor for {@link pQuerySql}.
- *
- * @param string $query A MySQL query to evaluate.
- * @returns pQuerySql A new SQL query instance.
- */
- function _sql($query /* [ , $arg1, ... ] */) {
- $args = func_get_args();
- $query = array_shift($args);
- array_unshift($args, 'sql', $query);
-
- return call_user_func_array('pQuery::create', $args);
- }
- pQuery::extend('pQuerySql', 'sql');
- ?>
|