Commit 01e6d0bd authored by Josh Campbell's avatar Josh Campbell

Merge pull request #130 from avbdr/master

Avoid out of memory bug in php 5.2 and 5.3 / 	bug #129: Check correct execution status instead of affected rows,
parents c77fd388 5ef4553e
......@@ -188,11 +188,14 @@ class MysqliDb
*
* @param string $query Contains a user-provided query.
* @param array $bindParams All variables to bind to the SQL statment.
* @param bool $sanitize If query should be filtered before execution
*
* @return array Contains the returned rows from the query.
*/
public function rawQuery($query, $bindParams = null)
public function rawQuery ($query, $bindParams = null, $sanitize = true)
{
$this->_query = $query;
if ($sanitize)
$this->_query = filter_var ($query, FILTER_SANITIZE_STRING,
FILTER_FLAG_NO_ENCODE_QUOTES);
$stmt = $this->_prepareQuery();
......@@ -316,12 +319,13 @@ class MysqliDb
$this->_query = "UPDATE " . self::$_prefix . $tableName ." SET ";
$stmt = $this->_buildQuery(null, $tableData);
$stmt->execute();
$this->_stmtError = $stmt->error;
$stmt = $this->_buildQuery (null, $tableData);
$status = $stmt->execute();
$this->reset();
$this->_stmtError = $stmt->error;
$this->count = $stmt->affected_rows;
return ($stmt->affected_rows > 0);
return $status;
}
/**
......@@ -521,25 +525,38 @@ class MysqliDb
*
* @param string Variable value
*/
protected function _bindParam($value)
{
protected function _bindParam($value) {
$this->_bindParams[0] .= $this->_determineType ($value);
array_push ($this->_bindParams, $value);
}
/**
* Helper function to add variables into bind parameters array in bulk
*
* @param Array Variable with values
*/
protected function _bindParams ($values) {
foreach ($values as $value)
$this->_bindParam ($value);
}
/**
* Helper function to add variables into bind parameters array and will return
* its SQL part of the query according to operator in ' $operator ?' or
* ' $operator ($subquery) ' formats
*
* @param Array Variable with values
*/
protected function _buildPair ($operator, $value) {
if (!is_object($value)) {
$this->_bindParam ($value);
$comparison = ' ' . $operator. ' ? ';
return $comparison;
return ' ' . $operator. ' ? ';
}
$subQuery = $value->getSubQuery();
$comparison = " " . $operator . " (" . $subQuery['query'] . ")";
foreach ($subQuery['params'] as $v)
$this->_bindParam ($v);
$subQuery = $value->getSubQuery ();
$this->_bindParams ($subQuery['params']);
return $comparison;
return " " . $operator . " (" . $subQuery['query'] . ")";
}
/**
......@@ -554,23 +571,97 @@ class MysqliDb
*/
protected function _buildQuery($numRows = null, $tableData = null)
{
$hasTableData = is_array($tableData);
$hasConditional = !empty($this->_where);
$this->_buildJoin();
$this->_buildTableData ($tableData);
$this->_buildWhere();
$this->_buildGroupBy();
$this->_buildOrderBy();
$this->_buildLimit ($numRows);
// Did the user call the "join" method?
if (!empty($this->_join)) {
foreach ($this->_join as $prop => $value) {
$this->_query .= " " . $prop . " on " . $value;
$this->_lastQuery = $this->replacePlaceHolders ($this->_query, $this->_bindParams);
if ($this->isSubQuery)
return;
// Prepare query
$stmt = $this->_prepareQuery();
// Bind parameters to statement if any
if (count ($this->_bindParams) > 1)
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
return $stmt;
}
/**
* This helper method takes care of prepared statements' "bind_result method
* , when the number of variables to pass is unknown.
*
* @param mysqli_stmt $stmt Equal to the prepared statement object.
*
* @return array The results of the SQL fetch.
*/
protected function _dynamicBindResults(mysqli_stmt $stmt)
{
$parameters = array();
$results = array();
$meta = $stmt->result_metadata();
// if $meta is false yet sqlstate is true, there's no sql error but the query is
// most likely an update/insert/delete which doesn't produce any results
if(!$meta && $stmt->sqlstate) {
return array();
}
$row = array();
while ($field = $meta->fetch_field()) {
$row[$field->name] = null;
$parameters[] = & $row[$field->name];
}
// avoid out of memory bug in php 5.2 and 5.3
// https://github.com/joshcam/PHP-MySQLi-Database-Class/pull/119
if (version_compare (phpversion(), '5.4', '<'))
$stmt->store_result();
call_user_func_array(array($stmt, 'bind_result'), $parameters);
while ($stmt->fetch()) {
$x = array();
foreach ($row as $key => $val) {
$x[$key] = $val;
}
$this->count++;
array_push($results, $x);
}
return $results;
}
/**
* Abstraction method that will build an JOIN part of the query
*/
protected function _buildJoin () {
if (empty ($this->_join))
return;
foreach ($this->_join as $prop => $value)
$this->_query .= " " . $prop . " on " . $value;
}
// Determine INSERT or UPDATE query
if ($hasTableData) {
/**
* Abstraction method that will build an INSERT or UPDATE part of the query
*/
protected function _buildTableData ($tableData) {
if (!is_array ($tableData))
return;
$isInsert = strpos ($this->_query, 'INSERT');
$isUpdate = strpos ($this->_query, 'UPDATE');
if ($isInsert !== false) {
//is insert statement
$this->_query .= '(`' . implode(array_keys($tableData), '`, `') . '`)';
$this->_query .= ' VALUES(';
}
......@@ -579,12 +670,20 @@ class MysqliDb
if ($isUpdate !== false)
$this->_query .= "`" . $column . "` = ";
// Subquery value
if (is_object ($value)) {
$this->_query .= $this->_buildPair ("", $value) . ", ";
} else if (!is_array ($value)) {
continue;
}
// Simple value
if (!is_array ($value)) {
$this->_bindParam ($value);
$this->_query .= '?, ';
} else {
continue;
}
// Function value
$key = key ($value);
$val = $value[$key];
switch ($key) {
......@@ -593,10 +692,8 @@ class MysqliDb
break;
case '[F]':
$this->_query .= $val[0] . ", ";
if (!empty ($val[1])) {
foreach ($val[1] as $v)
$this->_bindParam ($v);
}
if (!empty ($val[1]))
$this->_bindParams ($val[1]);
break;
case '[N]':
if ($val == null)
......@@ -608,33 +705,41 @@ class MysqliDb
die ("Wrong operation");
}
}
}
$this->_query = rtrim($this->_query, ', ');
if ($isInsert !== false)
$this->_query .= ')';
}
// Did the user call the "where" method?
if ($hasConditional) {
/**
* Abstraction method that will build the part of the WHERE conditions
*/
protected function _buildWhere () {
if (empty ($this->_where))
return;
//Prepair the where portion of the query
$this->_query .= ' WHERE ';
$i = 0;
// Remove first AND/OR concatenator
$this->_where[0][0] = '';
foreach ($this->_where as $cond) {
list ($concat, $wValue, $wKey) = $cond;
// if its not a first condition insert its concatenator (AND or OR)
if ($i++ != 0)
$this->_query .= " $concat ";
$this->_query .= $wKey;
$this->_query .= " " . $concat ." " . $wKey;
if (is_array ($wValue)) {
// if the value is an array, then this isn't a basic = comparison
$key = key($wValue);
// Empty value (raw where condition in wKey)
if ($wValue === null)
continue;
// Simple = comparison
if (!is_array ($wValue))
$wValue = Array ('=' => $wValue);
$key = key ($wValue);
$val = $wValue[$key];
switch( strtolower($key) ) {
switch (strtolower ($key)) {
case '0':
foreach ($wValue as $v)
$this->_bindParam ($v);
$this->_bindParams ($wValue);
break;
case 'not in':
case 'in':
......@@ -652,103 +757,58 @@ class MysqliDb
case 'not between':
case 'between':
$this->_query .= " $key ? AND ? ";
$this->_bindParam ($val[0]);
$this->_bindParam ($val[1]);
$this->_bindParams ($val);
break;
default:
// We are using a comparison operator with only one parameter after it
$this->_query .= $this->_buildPair ($key, $val);
}
} else if ($wValue === null) {
//
} else {
$this->_query .= $this->_buildPair ("=", $wValue);
}
}
}
// Did the user call the "groupBy" method?
if (!empty($this->_groupBy)) {
/**
* Abstraction method that will build the GROUP BY part of the WHERE statement
*
*/
protected function _buildGroupBy () {
if (empty ($this->_groupBy))
return;
$this->_query .= " GROUP BY ";
foreach ($this->_groupBy as $key => $value) {
// prepares the reset of the SQL query.
foreach ($this->_groupBy as $key => $value)
$this->_query .= $value . ", ";
}
$this->_query = rtrim($this->_query, ', ') . " ";
}
// Did the user call the "orderBy" method?
if (!empty ($this->_orderBy)) {
$this->_query .= " ORDER BY ";
foreach ($this->_orderBy as $prop => $value) {
// prepares the reset of the SQL query.
$this->_query .= $prop . " " . $value . ", ";
}
$this->_query = rtrim ($this->_query, ', ') . " ";
}
// Did the user set a limit
if (isset($numRows)) {
if (is_array ($numRows))
$this->_query .= ' LIMIT ' . (int)$numRows[0] . ', ' . (int)$numRows[1];
else
$this->_query .= ' LIMIT ' . (int)$numRows;
$this->_query = rtrim($this->_query, ', ') . " ";
}
$this->_lastQuery = $this->replacePlaceHolders($this->_query, $this->_bindParams);
if ($this->isSubQuery)
/**
* Abstraction method that will build the LIMIT part of the WHERE statement
*
* @param int $numRows The number of rows total to return.
*/
protected function _buildOrderBy () {
if (empty ($this->_orderBy))
return;
// Prepare query
$stmt = $this->_prepareQuery();
// Bind parameters to statement if any
if (count ($this->_bindParams) > 1)
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
$this->_query .= " ORDER BY ";
foreach ($this->_orderBy as $prop => $value)
$this->_query .= $prop . " " . $value . ", ";
return $stmt;
$this->_query = rtrim ($this->_query, ', ') . " ";
}
/**
* This helper method takes care of prepared statements' "bind_result method
* , when the number of variables to pass is unknown.
*
* @param mysqli_stmt $stmt Equal to the prepared statement object.
* Abstraction method that will build the LIMIT part of the WHERE statement
*
* @return array The results of the SQL fetch.
* @param int $numRows The number of rows total to return.
*/
protected function _dynamicBindResults(mysqli_stmt $stmt)
{
$parameters = array();
$results = array();
$meta = $stmt->result_metadata();
// if $meta is false yet sqlstate is true, there's no sql error but the query is
// most likely an update/insert/delete which doesn't produce any results
if(!$meta && $stmt->sqlstate) {
return array();
}
$row = array();
while ($field = $meta->fetch_field()) {
$row[$field->name] = null;
$parameters[] = & $row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $parameters);
while ($stmt->fetch()) {
$x = array();
foreach ($row as $key => $val) {
$x[$key] = $val;
}
$this->count++;
array_push($results, $x);
}
protected function _buildLimit ($numRows) {
if (!isset ($numRows))
return;
return $results;
if (is_array ($numRows))
$this->_query .= ' LIMIT ' . (int)$numRows[0] . ', ' . (int)$numRows[1];
else
$this->_query .= ' LIMIT ' . (int)$numRows;
}
/**
......
......@@ -68,6 +68,9 @@ $data = Array(
$id = $db->insert ('users', $data);
if ($id)
echo 'user was created. Id=' . $id;
else
echo 'insert failed: ' . $db->getLastError();
```
### Update Query
......@@ -81,7 +84,10 @@ $data = Array (
// active = !active;
);
$db->where ('id', 1);
if($db->update ('users', $data)) echo 'successfully updated';
if ($db->update ('users', $data))
echo $db->count . ' records were updated';
else
echo 'update failed: ' . $db->getLastError();
```
### Select Query
......@@ -121,14 +127,20 @@ if($db->delete('users')) echo 'successfully deleted';
```
### Generic Query Method
By default rawQuery() will filter out special characters so if you getting problems with it
you might try to disable filtering function. In this case make sure that all external variables are passed to the query via bind variables
```php
$users = $db->rawQuery('SELECT * from users');
// filtering enabled
$users = $db->rawQuery('SELECT * from users where customerId=?', Array (10));
// filtering disabled
//$users = $db->rawQuery('SELECT * from users where id >= ?', Array (10), false);
foreach ($users as $user) {
print_r ($user);
}
```
### Raw Query Method
More advanced examples:
```php
$params = Array(1, 'admin');
$users = $db->rawQuery("SELECT id, firstName, lastName FROM users WHERE id = ? AND login = ?", $params);
......@@ -136,8 +148,17 @@ print_r($users); // contains Array of returned rows
// will handle any SQL query
$params = Array(10, 1, 10, 11, 2, 10);
$resutls = $db->rawQuery("(SELECT a FROM t1 WHERE a = ? AND B = ? ORDER BY a LIMIT ?) UNION(SELECT a FROM t2 WHERE a = ? AND B = ? ORDER BY a LIMIT ?)", $params);
print_r($results); // contains Array of returned rows
$q = "(
SELECT a FROM t1
WHERE a = ? AND B = ?
ORDER BY a LIMIT ?
) UNION (
SELECT a FROM t2
WHERE a = ? AND B = ?
ORDER BY a LIMIT ?
)";
$resutls = $db->rawQuery ($q, $params);
print_r ($results); // contains Array of returned rows
```
......
......@@ -125,6 +125,10 @@ if ($db->count != 1) {
$db->where ("active", false);
$db->update("users", Array ("active" => $db->not()));
if ($db->count != 2) {
echo "Invalid update count with not()";
exit;
}
$db->where ("active", true);
$users = $db->get("users");
......@@ -133,12 +137,19 @@ if ($db->count != 3) {
exit;
}
$db->where ("active", true);
$users = $db->get("users", 2);
if ($db->count != 2) {
echo "Invalid total insert count with boolean";
exit;
}
// TODO
//$db->where("createdAt", Array (">" => $db->interval("-1h")));
//$users = $db->get("users");
//print_r ($users);
$db->where("firstname", '%John%', 'LIKE');
$db->where("firstname", Array ('LIKE' => '%John%'));
$users = $db->get("users");
if ($db->count != 1) {
echo "Invalid insert count in LIKE: ".$db->count;
......@@ -160,6 +171,11 @@ $upData = Array (
);
$db->where ("id", 1);
$cnt = $db->update("users", $upData);
if ($db->count != 1) {
echo "Invalid update count with functions";
exit;
}
$db->where ("id", 1);
$r = $db->getOne("users");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment