/** * Executes the query that was previously passed to the constructor. * * @param mixed $arg Query arguments to escape and insert at ? placeholders in $query * @param mixed ... Additional arguments **/ function execute() { // Load the function arguments, minus the query itself, which we already extracted $args = func_get_args(); // Split out sub-arrays, etc.. $args = Database::smart_args($args); // Were enough arguments passed in? if (count($args) != $this->num_args_needed) { trigger_error('Database_Query_mysqlicompat called with ' . count($args) . " arguments, but requires {$this->num_args_needed}.", E_USER_ERROR); } // Finish any previous statements $this->finish(); // Replace in the arguments $this->last_query = ''; foreach ($this->query as $part) { $this->last_query .= $part; if (count($args)) { $arg = array_shift($args); $this->last_query .= is_null($arg) ? 'NULL' : "'" . mysqli_real_escape_string($this->dbh, $arg) . "'"; } } // Perform the query // If we don't have a valid connection, fataly error out. if ($this->dbh === false) { $this->db->error(); trigger_error($this->db->error, E_USER_ERROR); } $this->sh = mysqli_query($this->dbh, $this->last_query); // Cache these so the warning count below doesn't interfere if (is_bool($this->sh)) { $this->insert_id = mysqli_insert_id($this->dbh); $this->affected_rows = mysqli_affected_rows($this->dbh); } else { $this->num_rows = mysqli_num_rows($this->sh); } // On each execute, we clear the warnings of the statement handle, so it doesn't // store them up $this->warnings = array(); // Check the warnings and store them if (mysqli_warning_count($this->dbh)) { if ($sh = mysqli_query($this->dbh, 'SHOW WARNINGS')) { while ($row = mysqli_fetch_row($sh)) { $this->warnings[] = array('#' => $row[1], 'MSG' => $row[2]); } mysqli_free_result($sh); // This is used in errors.php to output in the backtrace global $_DEBUG; $_DEBUG['Database Warnings'][] = array('Query' => $this->last_query, 'Warnings' => $this->warnings); } } if ($this->sh === false) { if ($this->db->fatal_errors) { trigger_error('SQL Error: ' . mysqli_error($this->dbh) . ' [#' . mysqli_errno($this->dbh) . ']', E_USER_ERROR); } else { $this->db->error(); } } }
/** * Executes the query that was previously passed to the constructor. * * @param mixed $arg Query arguments to escape and insert at ? placeholders in $query * @param mixed ... Additional arguments **/ public function execute() { // We can't very well perform a query wtihout an active connection. if (!(is_object($this->dbh) && $this->dbh instanceof mysqli)) { $this->db->error('Lost connection to database.'); return; } // Finish any previous statements $this->finish(); // Replace in the arguments. Yes, we're doing it manually. While MySQLi // has prepared statements, the binding method requires that we know the // type of value being passed. We can't guess it with certainty. $this->last_query = $this->replaceholders(Database::smart_args(func_get_args())); if (!$this->last_query) { return; } // Wrap the actual query execution in a bit of benchmarking. $before = microtime(true); $this->sh = mysqli_query($this->dbh, $this->last_query); $after = microtime(true); $this->db->mysql_time += $after - $before; // Non-select statements return a boolean, which means we call affected_rows. if (is_bool($this->sh)) { $this->insert_id = mysqli_insert_id($this->dbh); $this->affected_rows = mysqli_affected_rows($this->dbh); } else { $this->num_rows = mysqli_num_rows($this->sh); } // Can we pull down warnings from the server? $this->warnings = array(); if ($this->db->enable_warning_logging && mysqli_warning_count($this->dbh)) { if ($sh = mysqli_query($this->dbh, 'SHOW WARNINGS')) { while ($row = mysqli_fetch_row($sh)) { $this->warnings[] = array('#' => $row[1], 'MSG' => $row[2]); } mysqli_free_result($sh); // Push it upstream. $GLOBALS['_DEBUG']['Database Warnings'][] = array('Query' => $this->last_query, 'Warnings' => $this->warnings); } } // Did it even work? if ($this->sh === false) { $this->db->error('SQL Error:'); return false; } return true; }
/** * Executes the query that was previously passed to the constructor. * * @param mixed $arg Query arguments to escape and insert at ? placeholders in $query * @param mixed ... Additional arguments **/ public function execute() { // We can't very well perform a query wtihout an active connection. if ($this->dbh === false) { $this->db->error('Lost connection to database.'); return; } // Finish any previous statements $this->finish(); // Replace in the arguments $this->last_query = $this->replaceholders(Database::smart_args(func_get_args())); if (!$this->last_query) { return; } // Wrap the actual query execution in a bit of benchmarking. $before = microtime(true); $this->sh = mysql_query($this->last_query, $this->dbh); $after = microtime(true); $this->db->mysql_time += $after - $before; // Non-select statements return a boolean, which means we call affected_rows. if (is_bool($this->sh)) { $this->insert_id = mysql_insert_id($this->dbh); $this->affected_rows = mysql_affected_rows($this->dbh); } else { $this->num_rows = mysql_num_rows($this->sh); } // Can we pull down warnings from the server? Because the mysql interface // doesn't give us the warning count, we have to check ourselves. $this->warnings = array(); if ($this->db->enable_warning_logging) { if ($sh = mysql_query($this->dbh, 'SHOW WARNINGS')) { while ($row = mysql_fetch_row($sh)) { $this->warnings[] = array('#' => $row[1], 'MSG' => $row[2]); } mysql_free_result($sh); // Push it upstream. $GLOBALS['_DEBUG']['Database Warnings'][] = array('Query' => $this->last_query, 'Warnings' => $this->warnings); } } // No matter what, a false statement handle means something horrid happened. if ($this->sh === false) { $this->db->error('SQL Error:'); return false; } return true; }
/** * Executes the query that was previously passed to the constructor. * * @param mixed $arg Query arguments to escape and insert at ? placeholders in $query * @param mixed ... Additional arguments /**/ function execute() { // Load the function arguments, minus the query itself, which we already extracted $args = func_get_args(); // Split out sub-arrays, etc.. $args = Database::smart_args($args); // Were enough arguments passed in? if (count($args) != $this->num_args_needed) { trigger_error('Database_Query called with ' . count($args) . " arguments, but requires {$this->num_args_needed}.", E_USER_ERROR); } // Finish any previous statements $this->finish(); // Replace in the arguments $this->last_query = ''; foreach ($this->query as $part) { $this->last_query .= $part; if (count($args)) { $arg = array_shift($args); $this->last_query .= is_null($arg) ? 'NULL' : "'" . mysql_real_escape_string($arg) . "'"; } } // Perform the query // If we don't have a valid connection, fataly error out. if ($this->dbh === false) { $this->db->error(); trigger_error($this->db->error, E_USER_ERROR); } $this->sh = mysql_query($this->last_query, $this->dbh); // Cache these if (is_bool($this->sh)) { $this->insert_id = mysql_insert_id($this->dbh); $this->affected_rows = mysql_affected_rows($this->dbh); } else { $this->num_rows = mysql_num_rows($this->sh); } if ($this->sh === false) { if ($this->db->fatal_errors) { trigger_error('SQL Error: ' . mysql_error($this->dbh) . ' [#' . mysql_errno($this->dbh) . ']', E_USER_ERROR); } else { $this->db->error(); } } }
/** * I like how in perl you can pass variables into functions in lists or arrays, * and they all show up to the function as one giant list. This takes an array * containing scalars and arrays of scalars, and returns one cleanarray of all * values. * * @param mixed $args Scalar or nested array to be "flattened" into a single array. * * @return array Single array comprised of all scalars present in $args. **/ static function smart_args($args) { $new_args = array(); // Not an array if (!is_array($args)) { return array($args); } // Loop foreach ($args as $arg) { if (is_array($arg)) { $new_args += Database::smart_args($arg); } else { $new_args[] = $arg; } } // Return return $new_args; }
/** * Executes the query that was previously passed to the constructor. * * @param mixed $arg Query arguments to escape and insert at ? placeholders in $query * @param mixed ... Additional arguments **/ public function execute() { // We can't very well perform a query wtihout an active connection. if ($this->dbh === false || pg_connection_status($this->dbh) !== PGSQL_CONNECTION_OK) { $this->db->error('Lost connection to database.'); return; } // Finish any previous statements $this->finish(); // We need to pre-process the arguments for literals and bytefields. We // can't safely pass them into the naitve placeholder system, so we'll // have to stick them into the query at the right places before then. // Additionally, PG uses the string values 't' and 'f' for boolean true // and false, and will choke over PHP true/false when passed in this way. $args = Database::smart_args(func_get_args()); foreach ($args as $i => $arg) { if (is_bool($arg)) { $args[$i] = $arg === true ? 't' : 'f'; } } list($query, $nargs, $nargn) = $this->reprocess_query($args); if (!$query) { return; } // Prepare the placeholders. PG uses $1, $2 .. $n as their placeholders. // Continuing the example from Database_Query::reprocess_query, we should // have the following $query array: // 'SELECT * FROM table WHERE a = ', // ' AND b < NOW() AND c > ' // which will get turned into the following string: // SELECT * FROM table WHERE a = $1 AND b < NOW() AND c > $2 $this->last_query = ''; $placeholder_count = 1; foreach ($query as $i => $chunk) { $this->last_query .= $chunk; if ($placeholder_count <= $nargn) { $this->last_query .= '$' . $placeholder_count; $placeholder_count++; } } // Wrap the actual query execution in a bit of benchmarking. $before = microtime(true); // We're only using placeholders here, not prepared statements. Why not? // The possibility of missing / pre-replaced placeholders means that the // actual query we get to execute changes each time. $this->sh = false; $this->errno = null; $state = pg_send_query_params($this->dbh, $this->last_query, $nargs); if ($state) { // So, here's some fun. Like PDO, PG has error reporting bits at both // the top level and at the statement level. However, the normal // query method returns false instead of a statement handle, and we // need the statement handle to pull errors back. This means that // we need to use the async query sending method and check every single // time for an error message. We can then nuke the statement handle // so things that try to look for it to detect errors can work. $this->sh = pg_get_result($this->dbh); $sqlstate = pg_result_error_field($this->sh, PGSQL_DIAG_SQLSTATE); if ($sqlstate && $sqlstate != '00000') { $this->errno = $sqlstate; $this->errstr = pg_result_error_field($this->sh, PGSQL_DIAG_MESSAGE_PRIMARY) . ' [detail=' . pg_result_error_field($this->sh, PGSQL_DIAG_MESSAGE_DETAIL) . '] (hint=' . pg_result_error_field($this->sh, PGSQL_DIAG_MESSAGE_HINT) . ') at character ' . pg_result_error_field($this->sh, PGSQL_DIAG_STATEMENT_POSITION); $this->sh = false; } } else { $this->db->error('Could not send query to server.'); return; } $after = microtime(true); $this->db->mysql_time += $after - $before; // The other adapters also fetch the last insert id here, which we can't // effectively do, as it's not exposed by PG. As an alternative, you can // use a RETURNING clause in your query to fetch generated values: // INSERT INTO table(foo, bar) VALUES(?, ?) RETURNING id; if (is_bool($this->sh) || is_null($this->sh)) { $this->affected_rows = 0; $this->num_rows = 0; } elseif (is_resource($this->sh)) { $this->affected_rows = pg_affected_rows($this->sh); $this->num_rows = pg_num_rows($this->sh); } else { assert('false; // statement handler was not a bool or resource, it was a: ' . gettype($this->sh)); } // Upstream code may care about warnings. PG can return multiple notices // for any given query. It's unclear whether or not this will work, as // I haven't been able to actually find any query that does emit multiple // notices. If your code suddenly launches into an infinite loop, now you // know why. Fun times indeed. if ($this->db->enable_warning_logging) { $this->warnings = array(); while (($last_notice = pg_last_notice($this->dbh)) !== false) { $this->warnings[] = $last_notice; } $GLOBALS['_DEBUG']['Database Warnings'][] = array('Query' => $this->last_query, 'Warnings' => $this->warnings); } // Finally, did it even work? if ($this->sh === false) { $this->db->error('SQL Error:'); return false; } return true; }
/** * Executes the query that was previously passed to the constructor. * * @param mixed $arg Query arguments to escape and insert at ? placeholders in $query * @param mixed ... Additional arguments **/ public function execute() { // We can't very well perform a query wtihout an active connection. if (!is_object($this->dbh)) { $this->db->error('Lost connection to database.'); return; } // end if // Finish any previous statements $this->finish(); // We need to pre-process the arguments for literals and bytefields. We // can't safely pass them into the PDO placeholder system, so we'll // have to stick them into the query at the right places before then. $args = Database::smart_args(func_get_args()); list($query, $nargs, $nargn) = $this->reprocess_query($args); if (!$query) { return; } $this->last_query = $this->unprocess_query($query, $nargn); // Wrap the actual query execution in a bit of benchmarking. $before = microtime(true); $this->last_exception = null; try { // We will always create a new statement. The possibility of missing // & pre-replaced placeholders breaks our ability to work with actual // prepared statements. Additionally, PDO tends to break easily when // working on prepared statements that return multiple result sets // combined with multiple concurrent statement handles. See the // comments on the PDOStatement::closeCursor() manual page. // Skip preparing the query if there are no args in the query. if ($nargn == 0) { $this->sh = $this->dbh->query($this->last_query); } else { $this->sh = $this->dbh->prepare($this->last_query); $this->sh->execute($nargs); } } catch (PDOException $e) { $this->last_exception = $e; $this->db->error('SQL Error:'); return false; } // end try $after = microtime(true); $this->db->mysql_time += $after - $before; // The other adapters handle these here. Fun times. // Problem 1: // PDO does not provide a clear way to get the last inserted primary key. // There's PDO::lastInsertId(), but it's poorly supported and sometimes // requires extra arguments be passed. There's undoubtedly a better // way to deal with this than to not deal with it. $this->insert_id = null; // Problem 2: // PDOStatement::rowCount()'s manual page says: // "If the last SQL statement executed by the associated PDOStatement // was a SELECT statement, some databases may return the number of // rows returned by that statement. However, this behaviour is not // guaranteed for all databases and should not be relied on for // portable applications." // So, unlike the other adapters, there's no separate call for getting // the number of returned rows vs the number of affected rows. // We'll just use it here and hope it works and that calling code doesn't // care too much. // Overall, PDO kind of sucks for this kind of thing. $this->affected_rows = $this->sh->rowCount(); $this->num_rows = $this->sh->rowCount(); // PDO has no way to check warnings. We won't try. $this->warnings = array(); // But we do have errors, though this *should* never be called thanks to the // exception catching above. $last_notice = $this->sh->errorCode(); if ($last_notice == '00000' || empty($last_notice)) { $last_notice = null; } if ($last_notice) { $this->db->error('SQL Error:'); return false; } return true; }