Commit 5fdf5f27 authored by Josh Campbell's avatar Josh Campbell

Merge pull request #114 from avbdr/master

Subquery / nicer where() api
parents 0c024935 ce7d9e8b
......@@ -24,7 +24,7 @@ class MysqliDb
*
* @var string
*/
protected $_prefix;
protected static $_prefix;
/**
* MySQLi instance
*
......@@ -93,6 +93,12 @@ class MysqliDb
protected $db;
protected $port;
/**
* Is Subquery object
*
*/
protected $isSubQuery = false;
/**
* @param string $host
* @param string $username
......@@ -100,7 +106,7 @@ class MysqliDb
* @param string $db
* @param int $port
*/
public function __construct($host, $username, $password, $db, $port = NULL)
public function __construct($host = NULL, $username = NULL, $password = NULL, $db = NULL, $port = NULL)
{
$this->host = $host;
$this->username = $username;
......@@ -110,7 +116,13 @@ class MysqliDb
$this->port = ini_get ('mysqli.default_port');
else
$this->port = $port;
if ($host == null && $username == null && $db == null) {
$this->isSubQuery = true;
return;
}
// for subqueries we do not need database connection and redefine root instance
$this->connect();
$this->setPrefix();
self::$_instance = $this;
......@@ -122,6 +134,9 @@ class MysqliDb
*/
public function connect()
{
if ($this->isSubQuery)
return;
$this->_mysqli = new mysqli ($this->host, $this->username, $this->password, $this->db, $this->port)
or die('There was a problem connecting to the database');
......@@ -164,7 +179,8 @@ class MysqliDb
*/
public function setPrefix($prefix = '')
{
$this->_prefix = $prefix;
self::$_prefix = $prefix;
return $this;
}
/**
......@@ -177,7 +193,7 @@ class MysqliDb
*/
public function rawQuery($query, $bindParams = null)
{
$this->_query = filter_var ($query, FILTER_SANITIZE_MAGIC_QUOTES,
$this->_query = filter_var ($query, FILTER_SANITIZE_STRING,
FILTER_FLAG_NO_ENCODE_QUOTES);
$stmt = $this->_prepareQuery();
......@@ -231,8 +247,12 @@ class MysqliDb
$columns = '*';
$column = is_array($columns) ? implode(', ', $columns) : $columns;
$this->_query = "SELECT $column FROM $this->_prefix$tableName";
$this->_query = "SELECT $column FROM " .self::$_prefix . $tableName;
$stmt = $this->_buildQuery($numRows);
if ($this->isSubQuery)
return $this;
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
......@@ -250,6 +270,10 @@ class MysqliDb
public function getOne($tableName, $columns = '*')
{
$res = $this->get ($tableName, 1, $columns);
if (is_object($res))
return $res;
if (isset($res[0]))
return $res[0];
......@@ -265,7 +289,10 @@ class MysqliDb
*/
public function insert($tableName, $insertData)
{
$this->_query = "INSERT into $this->_prefix$tableName";
if ($this->isSubQuery)
return;
$this->_query = "INSERT into " .self::$_prefix . $tableName;
$stmt = $this->_buildQuery(null, $insertData);
$stmt->execute();
$this->_stmtError = $stmt->error;
......@@ -284,7 +311,10 @@ class MysqliDb
*/
public function update($tableName, $tableData)
{
$this->_query = "UPDATE $this->_prefix$tableName SET ";
if ($this->isSubQuery)
return;
$this->_query = "UPDATE " . self::$_prefix . $tableName ." SET ";
$stmt = $this->_buildQuery(null, $tableData);
$stmt->execute();
......@@ -304,7 +334,10 @@ class MysqliDb
*/
public function delete($tableName, $numRows = null)
{
$this->_query = "DELETE FROM $this->_prefix$tableName";
if ($this->isSubQuery)
return;
$this->_query = "DELETE FROM " . self::$_prefix . $tableName;
$stmt = $this->_buildQuery($numRows);
$stmt->execute();
......@@ -324,9 +357,12 @@ class MysqliDb
*
* @return MysqliDb
*/
public function where($whereProp, $whereValue = null)
public function where($whereProp, $whereValue = null, $operator = null)
{
$this->_where[$whereProp] = Array ("AND", $whereValue);
if ($operator)
$whereValue = Array ($operator => $whereValue);
$this->_where[] = Array ("AND", $whereValue, $whereProp);
return $this;
}
......@@ -340,9 +376,12 @@ class MysqliDb
*
* @return MysqliDb
*/
public function orWhere($whereProp, $whereValue = null)
public function orWhere($whereProp, $whereValue = null, $operator = null)
{
$this->_where[$whereProp] = Array ("OR", $whereValue);
if ($operator)
$whereValue = Array ($operator => $whereValue);
$this->_where[] = Array ("OR", $whereValue, $whereProp);
return $this;
}
/**
......@@ -365,7 +404,7 @@ class MysqliDb
if ($joinType && !in_array ($joinType, $allowedTypes))
die ('Wrong JOIN type: '.$joinType);
$this->_join[$joinType . " JOIN " . $this->_prefix.$joinTable] = $joinCondition;
$this->_join[$joinType . " JOIN " . self::$_prefix . $joinTable] = $joinCondition;
return $this;
}
......@@ -488,6 +527,21 @@ class MysqliDb
array_push ($this->_bindParams, $value);
}
protected function _buildPair ($operator, $value) {
if (!is_object($value)) {
$this->_bindParam ($value);
$comparison = ' ' . $operator. ' ? ';
return $comparison;
}
$subQuery = $value->getSubQuery();
$comparison = " " . $operator . " (" . $subQuery['query'] . ")";
foreach ($subQuery['params'] as $v)
$this->_bindParam ($v);
return $comparison;
}
/**
* Abstraction method that will compile the WHERE statement,
* any passed update data, and the desired rows.
......@@ -525,7 +579,9 @@ class MysqliDb
if ($isUpdate !== false)
$this->_query .= "`" . $column . "` = ";
if (!is_array ($value)) {
if (is_object ($value)) {
$this->_query .= $this->_buildPair ("", $value) . ", ";
} else if (!is_array ($value)) {
$this->_bindParam ($value);
$this->_query .= '?, ';
} else {
......@@ -562,50 +618,50 @@ class MysqliDb
if ($hasConditional) {
//Prepair the where portion of the query
$this->_query .= ' WHERE ';
foreach ($this->_where as $column => $value) {
//value[0] -- AND/OR, value[1] -- condition array
$i = 0;
foreach ($this->_where as list($concat, $wValue, $wKey)) {
// if its not a first condition insert its concatenator (AND or OR)
if (array_search ($column, array_keys ($this->_where)) != 0)
$this->_query .= ' ' . $value[0]. ' ';
if ($i++ != 0)
$this->_query .= " $concat ";
$this->_query .= $wKey;
if (is_array ($value[1])) {
//value[0] -- AND/OR, value[1] -- condition array
if (is_array ($wValue)) {
// if the value is an array, then this isn't a basic = comparison
$key = key($value[1]);
$val = $value[1][$key];
$key = key($wValue);
$val = $wValue[$key];
switch( strtolower($key) ) {
case '0':
$comparison = '';
foreach ($value[1] as $v)
foreach ($wValue as $v)
$this->_bindParam ($v);
break;
case 'not in':
case 'in':
$comparison = ' ' . $key . ' (';
foreach($val as $v){
$comparison .= ' ?,';
$this->_bindParam ($v);
if (is_object ($val)) {
$comparison .= $this->_buildPair ("", $val);
} else {
foreach ($val as $v) {
$comparison .= ' ?,';
$this->_bindParam ($v);
}
}
$comparison = rtrim($comparison, ',').' ) ';
$this->_query .= rtrim($comparison, ',').' ) ';
break;
case 'not between':
case 'between':
$comparison = ' ' . $key . ' ? AND ? ';
$this->_query .= " $key ? AND ? ";
$this->_bindParam ($val[0]);
$this->_bindParam ($val[1]);
break;
default:
// We are using a comparison operator with only one parameter after it
$comparison = ' '.$key.' ? ';
$this->_bindParam ($val);
$this->_query .= $this->_buildPair ($key, $val);
}
} else if ($value[1] === null) {
$comparison = '';
} else if ($wValue === null) {
//
} else {
$comparison = ' = ? ';
$this->_bindParam ($value[1]);
$this->_query .= $this->_buildPair ("=", $wValue);
}
$this->_query .= $column.$comparison;
}
}
......@@ -637,6 +693,11 @@ class MysqliDb
$this->_query .= ' LIMIT ' . (int)$numRows;
}
$this->_lastQuery = $this->replacePlaceHolders($this->_query, $this->_bindParams);
if ($this->isSubQuery)
return;
// Prepare query
$stmt = $this->_prepareQuery();
......@@ -644,7 +705,6 @@ class MysqliDb
if (count ($this->_bindParams) > 1)
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
$this->_lastQuery = $this->replacePlaceHolders($this->_query, $this->_bindParams);
return $stmt;
}
......@@ -708,7 +768,10 @@ class MysqliDb
*/
public function __destruct()
{
$this->_mysqli->close();
if (!$this->isSubQuery)
return;
if ($this->_mysqli)
$this->_mysqli->close();
}
/**
......@@ -741,7 +804,10 @@ class MysqliDb
$newStr = "";
while ($pos = strpos ($str, "?")) {
$newStr .= substr ($str, 0, $pos) . $vals[$i++];
$val = $vals[$i++];
if (is_object ($val))
$val = '[object]';
$newStr .= substr ($str, 0, $pos) . $val;
$str = substr ($str, $pos + 1);
}
return $newStr;
......@@ -765,6 +831,24 @@ class MysqliDb
return $this->_stmtError . " " . $this->_mysqli->error;
}
/**
* Mostly internal method to get query and its params out of subquery object
* after get() and getAll()
*
* @return array
*/
public function getSubQuery () {
if (!$this->isSubQuery)
return null;
array_shift ($this->_bindParams);
$val = Array ('query' => $this->_query,
'params' => $this->_bindParams
);
$this->reset();
return $val;
}
/* Helper functions */
/**
* Method returns generated interval function as a string
......@@ -841,4 +925,69 @@ class MysqliDb
return Array ("[F]" => Array($expr, $bindParams));
}
/**
* Method creates new mysqlidb object for a subquery generation
*/
public static function subQuery()
{
return new MysqliDb();
}
/**
* Method returns a copy of a mysqlidb subquery object
*
* @param object new mysqlidb object
*/
public function copy ()
{
return clone $this;
}
/**
* Begin a transaction
*
* @uses mysqli->autocommit(false)
* @uses register_shutdown_function(array($this, "_transaction_shutdown_check"))
*/
public function startTransaction () {
$this->_mysqli->autocommit (false);
$this->_transaction_in_progress = true;
register_shutdown_function (array ($this, "_transaction_status_check"));
}
/**
* Transaction commit
*
* @uses mysqli->commit();
* @uses mysqli->autocommit(true);
*/
public function commit () {
$this->_mysqli->commit ();
$this->_transaction_in_progress = false;
$this->_mysqli->autocommit (true);
}
/**
* Transaction rollback function
*
* @uses mysqli->rollback();
* @uses mysqli->autocommit(true);
*/
public function rollback () {
$this->_mysqli->rollback ();
$this->_transaction_in_progress = false;
$this->_mysqli->autocommit (true);
}
/**
* Shutdown handler to rollback uncommited operations in order to keep
* atomic operations sane.
*
* @uses mysqli->rollback();
*/
public function _transaction_status_check () {
if (!$this->_transaction_in_progress)
return;
$this->rollback ();
}
} // END class
......@@ -10,7 +10,7 @@ After that, create a new instance of the class.
$db = new Mysqlidb('host', 'username', 'password', 'databaseName');
```
It's also possible to set a table prefix:
Its also possible to set a table prefix:
```php
$db->setPrefix('tablePrefix');
```
......@@ -133,38 +133,42 @@ $results = $db->get('users');
```
```php
$db->where('id', Array('>=' => 50));
$db->where('id', 50, ">=");
// or $db->where('id', Array('>=' => 50));
$results = $db->get('users');
// Gives: SELECT * FROM users WHERE id >= 50;
```
BETWEEN:
BETWEEN / NOT BETWEEN:
```php
$db->where('id', Array('between' => Array(4, 20) ) );
//$db->where('id', Array('not between' => Array(4, 20) ) );
$db->where('id', Array(4, 20), 'between');
// or $db->where('id', Array('between' => Array(4, 20) ) );
$results = $db->get('users');
// Gives: SELECT * FROM users WHERE id BETWEEN 4 AND 20
```
IN:
IN / NOT IN:
```php
$db->where('id', Array( 'in' => Array(1, 5, 27, -1, 'd') ) );
//$db->where('id', Array( 'not in' => Array(1, 5, 27, -1, 'd') ) );
$db->where('id', Array(1, 5, 27, -1, 'd'), 'IN');
// or $db->where('id', Array( 'in' => Array(1, 5, 27, -1, 'd') ) );
$results = $db->get('users');
// Gives: SELECT * FROM users WHERE id IN (1, 5, 27, -1, 'd');
```
OR CASE
```php
$db->where('firstName','John');
$db->orWhere('firstName','Peter');
$results = $db->get('users');
$db->where ('firstName', 'John');
$db->orWhere ('firstName', 'Peter');
$results = $db->get ('users');
// Gives: SELECT * FROM users WHERE firstName='John' OR firstName='peter'
```
NULL comparison:
```php
$db->where ("lastName", Array("<=>" => NULL));
$db->where ("lastName", NULL, '<=>');
$results = $db->get("users");
// Gives: SELECT * FROM users where lastName <=> NULL
```
......@@ -202,8 +206,8 @@ $results = $db->get('users');
### Grouping method
```php
$db->groupBy("name");
$results = $db->get('users');
$db->groupBy ("name");
$results = $db->get ('users');
// Gives: SELECT * FROM users GROUP BY name;
```
......@@ -216,6 +220,46 @@ $products = $db->get ("products p", null, "u.name, p.productName");
print_r ($products);
```
### Properties sharing
Its is also possible to copy properties
```php
$db->where ("agentId", 10);
$customers = $db->copy ();
$res = $customers->get ("customers");
// SELECT * FROM customers where agentId = 10
$db->orWhere ("agentId", 20);
$res = $db->get ("users");
// SELECT * FROM users where agentId = 10 or agentId = 20
```
### Subqueries
Subquery in selects:
```php
$ids = $db->subQuery ();
$ids->where ("qty", 2, ">");
$ids->get ("products", null, "userId");
$db->where ("id", $ids, 'in');
$res = $db->get ("users");
// Gives SELECT * FROM users WHERE id IN (SELECT userId FROM products WHERE qty > 2)
```
Subquery in inserts:
```php
$userIdQ = $db->subQuery ();
$userIdQ->where ("id", 6);
$userIdQ->getOne ("users", "name"),
$data = Array (
"productName" => "test product",
"userId" => $userIdQ,
"lastUpdated" => $db->now()
);
$id = $db->insert ("products", $data);
// Gives INSERT INTO PRODUCTS (productName, userId, lastUpdated) values ("test product", (SELECT name FROM users WHERE id = 6), NOW());
```
### Helper commands
Reconnect in case mysql connection died
```php
......@@ -227,3 +271,25 @@ Obtain an initialized instance of the class from another class
```php
$db = MysqliDb::getInstance();
```
Get last executed SQL query.
Please note that function returns SQL query only for debugging purposes as its execution most likely will fail due missing quotes around char variables.
```php
$db->get('users');
echo "Last executed query was ". $db->getLastQuery();
```
### Transaction helpers
Please keep in mind that transactions are working on innoDB tables.
Rollback transaction if insert fails:
```php
$db->startTransaction();
...
if (!$db->insert ('myTable', $insertData)) {
//Error while saving, cancel new record
$db->rollback();
} else {
//OK
$db->commit();
}
```
......@@ -94,7 +94,7 @@ function createTable ($name, $data) {
}
foreach ($tables as $name => $fields) {
$db->rawQuery("DROP TABLE $name");
$db->rawQuery("DROP TABLE ".$prefix.$name);
createTable ($prefix.$name, $fields);
}
......@@ -138,7 +138,7 @@ if ($db->count != 3) {
//$users = $db->get("users");
//print_r ($users);
$db->where("firstname", Array("LIKE" => '%John%'));
$db->where("firstname", '%John%', 'LIKE');
$users = $db->get("users");
if ($db->count != 1) {
echo "Invalid insert count in LIKE: ".$db->count;
......@@ -172,14 +172,14 @@ if ($r['password'] != '546f98b24edfdc3b9bbe0d241bd8b29783f71b32') {
exit;
}
$db->where ("id", Array('in' => Array('1','2','3')));
$db->where ("id", Array('1','2','3'), 'IN');
$db->get("users");
if ($db->count != 3) {
echo "Invalid users count on where() with in ";
exit;
}
$db->where ("id", Array('between' => Array('2','3')));
$db->where ("id", Array('2','3'), 'between');
$db->get("users");
if ($db->count != 2) {
echo "Invalid users count on where() with between";
......@@ -194,7 +194,7 @@ if ($db->count != 2) {
exit;
}
$db->where ("lastName", Array("<=>" => NULL));
$db->where ("lastName", NULL, '<=>');
$r = $db->get("users");
if ($db->count != 1) {
echo "Invalid users count on null where()";
......@@ -217,6 +217,25 @@ if ($db->count != 2) {
exit;
}
$db->where("id = 1 or id = 2");
$res = $db->get ("users");
if ($db->count != 2) {
echo "Invalid users count on select with multiple params";
exit;
}
$usersQ = $db->subQuery();
$usersQ->where ("login", "user2");
$usersQ->getOne ("users", "id");
$db2 = $db->copy();
$db2->where ("userId", $usersQ);
$res = $db2->getOne ("products", "count(id) as cnt");
if ($res['cnt'] != 2) {
echo "Invalid select result with subquery";
exit;
}
//TODO: insert test
$db->delete("users");
$db->get("users");
if ($db->count != 0) {
......
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