diff options
author | Tristan Darricau <tristan.darricau@sensiolabs.com> | 2015-10-14 16:18:46 +0200 |
---|---|---|
committer | Tristan Darricau <tristan.darricau@sensiolabs.com> | 2015-10-14 16:18:46 +0200 |
commit | cd114a68e956e448df18ffbad855a12a6f597861 (patch) | |
tree | 3118bd5045b970829b9b84eb17d94dbf672870d2 | |
parent | 4682b5abdd342bd4ac7dd01afc36cf30c496ac24 (diff) | |
parent | fe132f19e8d5c821fff936d208fbb981aa6ac92d (diff) | |
download | forums-cd114a68e956e448df18ffbad855a12a6f597861.tar forums-cd114a68e956e448df18ffbad855a12a6f597861.tar.gz forums-cd114a68e956e448df18ffbad855a12a6f597861.tar.bz2 forums-cd114a68e956e448df18ffbad855a12a6f597861.tar.xz forums-cd114a68e956e448df18ffbad855a12a6f597861.zip |
Merge pull request #3441 from brunoais/feature/sql-bool-builder
[feature/sql-bool-builder] Extending DBAL query builder for boolean generation
* brunoais/feature/sql-bool-builder:
[feature/sql-bool-builder] Improved tests output to show the SQL error.
[feature/sql-bool-builder] Adding the IS operator to predicted operators
[feature/sql-bool-builder] test_single_not_like
[feature/sql-bool-builder] test_single_like
[feature/sql-bool-builder] test_single_not_in
[feature/sql-bool-builder] test_single_in
[feature/sql-bool-builder] test_and_of_or_of_and
[feature/sql-bool-builder] test_triple_and_with_in
[feature/sql-bool-builder] test_double_and_with_not_of_and
[feature/sql-bool-builder] test_triple_and_with_is_null
[feature/sql-bool-builder] Prepare testing class
[feature/sql-bool-builder] Added LIKE and NOT_LIKE to the comparations
[feature/sql-bool-builder] Explain better the code in the first
[feature/sql-bool-builder] Also use parenthesis for the NOT operator
[feature/sql-bool-builder] AS keyword must be lowercase;
[feature/sql-bool-builder] Added code to use this feature for the WHERE clause
[feature/sql-bool-builder] Removed non-necessary spaces
[feature/sql-bool-builder] First working version
-rw-r--r-- | phpBB/phpbb/db/driver/driver.php | 137 | ||||
-rw-r--r-- | tests/dbal/boolean_processor_test.php | 306 | ||||
-rw-r--r-- | tests/dbal/fixtures/boolean_processor.xml | 84 |
3 files changed, 526 insertions, 1 deletions
diff --git a/phpBB/phpbb/db/driver/driver.php b/phpBB/phpbb/db/driver/driver.php index 2925765e94..e497e6dda1 100644 --- a/phpBB/phpbb/db/driver/driver.php +++ b/phpBB/phpbb/db/driver/driver.php @@ -774,7 +774,18 @@ abstract class driver implements driver_interface if (!empty($array['WHERE'])) { - $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); + $sql .= ' WHERE '; + + if (is_array($array['WHERE'])) + { + $sql_where = $this->_process_boolean_tree_first($array['WHERE']); + } + else + { + $sql_where = $array['WHERE']; + } + + $sql .= $this->_sql_custom_build('WHERE', $sql_where); } if (!empty($array['GROUP_BY'])) @@ -793,6 +804,130 @@ abstract class driver implements driver_interface return $sql; } + + protected function _process_boolean_tree_first($operations_ary) + { + // In cases where an array exists but there is no head condition, + // it should be because there's only 1 WHERE clause. This seems the best way to deal with it. + if ($operations_ary[0] !== 'AND' && + $operations_ary[0] !== 'OR') + { + $operations_ary = array('AND', $operations_ary); + } + return $this->_process_boolean_tree($operations_ary) . "\n"; + } + + protected function _process_boolean_tree($operations_ary) + { + $operation = array_shift($operations_ary); + + foreach ($operations_ary as &$condition) + { + switch ($condition[0]) + { + case 'AND': + case 'OR': + + $condition = ' ( ' . $this->_process_boolean_tree($condition) . ') '; + + break; + case 'NOT': + + $condition = ' NOT (' . $this->_process_boolean_tree($condition) . ') '; + + break; + + default: + + switch (sizeof($condition)) + { + case 3: + + // Typical 3 element clause with {left hand} {operator} {right hand} + switch ($condition[1]) + { + case 'IN': + case 'NOT_IN': + + // As this is used with an IN, assume it is a set of elements for sql_in_set() + $condition = $this->sql_in_set($condition[0], $condition[2], $condition[1] === 'NOT_IN', true); + + break; + + case 'LIKE': + + $condition = $condition[0] . ' ' . $this->sql_like_expression($condition[2]) . ' '; + + break; + + case 'NOT_LIKE': + + $condition = $condition[0] . ' ' . $this->sql_not_like_expression($condition[2]) . ' '; + + break; + + case 'IS_NOT': + + $condition[1] = 'IS NOT'; + + // no break + case 'IS': + + // If the value is NULL, the string of it is the empty string ('') which is not the intended result. + // this should solve that + if ($condition[2] === null) + { + $condition[2] = 'NULL'; + } + + $condition = implode(' ', $condition); + + break; + + default: + + $condition = implode(' ', $condition); + + break; + } + + break; + + case 5: + + // Subquery with {left hand} {operator} {compare kind} {SELECT Kind } {Sub Query} + + $condition = $condition[0] . ' ' . $condition[1] . ' ' . $condition[2] . ' ( '; + $condition .= $this->sql_build_query($condition[3], $condition[4]); + $condition .= ' )'; + + break; + + default: + // This is an unpredicted clause setup. Just join all elements. + $condition = implode(' ', $condition); + + break; + } + + break; + } + + } + + if($operation === 'NOT') + { + $operations_ary = implode("", $operations_ary); + } + else + { + $operations_ary = implode(" \n $operation ", $operations_ary); + } + + return $operations_ary; + } + + /** * {@inheritDoc} */ diff --git a/tests/dbal/boolean_processor_test.php b/tests/dbal/boolean_processor_test.php new file mode 100644 index 0000000000..8662485ac8 --- /dev/null +++ b/tests/dbal/boolean_processor_test.php @@ -0,0 +1,306 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php'; +require_once dirname(__FILE__) . '/../../phpBB/includes/utf/utf_tools.php'; + +class phpbb_boolean_processor_test extends phpbb_database_test_case +{ + public function getDataSet() + { + return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/boolean_processor.xml'); + } + + public function test_single_not_like() + { + $db = $this->new_dbal(); + + $db->sql_return_on_error(true); + + $sql_ary = array( + 'SELECT' => 'u.user_id', + 'FROM' => array( + 'phpbb_users' => 'u', + ), + 'WHERE' => array('u.username_clean', 'NOT_LIKE', 'gr' . $db->get_any_char()), + 'ORDER_BY' => 'u.user_id', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $db->sql_return_on_error(false); + + $this->assertEquals(array( + array('user_id' => '1'), + array('user_id' => '2'), + array('user_id' => '3'), + array('user_id' => '6'), + ), $db->sql_fetchrowset($result), + ($result === false) ? + "SQL ERROR:<br>" . var_export($sql, true) . "<br>" . $db->sql_error() : + var_export($sql, true) . ' ' . var_export($result, true) + ); + } + + public function test_single_like() + { + $db = $this->new_dbal(); + + $db->sql_return_on_error(true); + + $sql_ary = array( + 'SELECT' => 'u.user_id', + 'FROM' => array( + 'phpbb_users' => 'u', + ), + 'WHERE' => array('u.username_clean', 'LIKE', 'gr' . $db->get_any_char()), + 'ORDER_BY' => 'u.user_id', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $db->sql_return_on_error(false); + + $this->assertEquals(array( + array('user_id' => '4'), + array('user_id' => '5'), + ), $db->sql_fetchrowset($result), + ($result === false) ? + "SQL ERROR:<br>" . var_export($sql, true) . "<br>" . $db->sql_error() : + var_export($sql, true) . ' ' . var_export($result, true) + ); + } + + public function test_single_not_in() + { + $db = $this->new_dbal(); + + $db->sql_return_on_error(true); + + $sql_ary = array( + 'SELECT' => 'u.user_id', + 'FROM' => array( + 'phpbb_users' => 'u', + ), + 'WHERE' => array('u.user_id', 'NOT_IN', array(3,4,5)), + 'ORDER_BY' => 'u.user_id', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $db->sql_return_on_error(false); + + $this->assertEquals(array( + array('user_id' => '1'), + array('user_id' => '2'), + array('user_id' => '6'), + ), $db->sql_fetchrowset($result), + ($result === false) ? + "SQL ERROR:<br>" . var_export($sql, true) . "<br>" . $db->sql_error() : + var_export($sql, true) . ' ' . var_export($result, true) + ); + } + + public function test_single_in() + { + $db = $this->new_dbal(); + + $db->sql_return_on_error(true); + + $sql_ary = array( + 'SELECT' => 'u.user_id', + 'FROM' => array( + 'phpbb_users' => 'u', + ), + 'WHERE' => array('u.user_id', 'IN', array(3,4,5)), + 'ORDER_BY' => 'u.user_id', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $db->sql_return_on_error(false); + + $this->assertEquals(array( + array('user_id' => '3'), + array('user_id' => '4'), + array('user_id' => '5'), + ), $db->sql_fetchrowset($result), + ($result === false) ? + "SQL ERROR:<br>" . var_export($sql, true) . "<br>" . $db->sql_error() : + var_export($sql, true) . ' ' . var_export($result, true) + ); + } + + public function test_and_of_or_of_and() + { + $db = $this->new_dbal(); + + $db->sql_return_on_error(true); + + $sql_ary = array( + 'SELECT' => 'u.user_id', + 'FROM' => array( + 'phpbb_users' => 'u', + 'phpbb_user_group' => 'ug', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array( + 'phpbb_banlist' => 'b', + ), + 'ON' => 'u.user_id = b.ban_userid', + ), + ), + 'WHERE' => array('AND', + array('OR', + array('AND', + array('ug.user_id', 'IN', array(1, 2, 3, 4)), + array('ug.group_id', '=', 2), + ), + array('AND', + array('ug.group_id', '=', 1), + array('b.ban_id', 'IS_NOT', NULL), + ), + ), + array('u.user_id', '=', 'ug.user_id'), + ), + 'ORDER_BY' => 'u.user_id', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $db->sql_return_on_error(false); + + $this->assertEquals(array( + array('user_id' => '2'), + array('user_id' => '4'), + ), $db->sql_fetchrowset($result), + ($result === false) ? + "SQL ERROR:<br>" . var_export($sql, true) . "<br>" . $db->sql_error() : + var_export($sql, true) . ' ' . var_export($result, true) + ); + } + + public function test_triple_and_with_in() + { + $db = $this->new_dbal(); + + $db->sql_return_on_error(true); + + $sql_ary = array( + 'SELECT' => 'u.user_id', + 'FROM' => array( + 'phpbb_users' => 'u', + 'phpbb_user_group' => 'ug', + ), + 'WHERE' => array('AND', + array('ug.user_id', 'IN', array(1, 2, 3, 4)), + array('ug.group_id', '=', 1), + array('u.user_id', '=', 'ug.user_id'), + ), + 'ORDER_BY' => 'u.user_id', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $db->sql_return_on_error(false); + + $this->assertEquals(array( + array('user_id' => '1'), + array('user_id' => '2'), + array('user_id' => '3'), + ), $db->sql_fetchrowset($result), + ($result === false) ? + "SQL ERROR:<br>" . var_export($sql, true) . "<br>" . $db->sql_error() : + var_export($sql, true) . ' ' . var_export($result, true) + ); + + } + + public function test_double_and_with_not_of_or() + { + $db = $this->new_dbal(); + + $db->sql_return_on_error(true); + + $sql_ary = array( + 'SELECT' => 'u.user_id', + 'FROM' => array( + 'phpbb_users' => 'u', + 'phpbb_user_group' => 'ug', + ), + 'WHERE' => array('AND', + array('NOT', + array('OR', + array('ug.group_id', '=', 1), + array('ug.group_id', '=', 2), + ), + ), + array('u.user_id', '=', 'ug.user_id'), + ), + 'ORDER_BY' => 'u.user_id', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $db->sql_return_on_error(false); + + $this->assertEquals(array(), $db->sql_fetchrowset($result), + ($result === false) ? + "SQL ERROR:<br>" . var_export($sql, true) . "<br>" . $db->sql_error() : + var_export($sql, true) . ' ' . var_export($result, true) + ); + } + + public function test_triple_and_with_is_null() + { + $db = $this->new_dbal(); + + $db->sql_return_on_error(true); + + $sql_ary = array( + 'SELECT' => 'u.username', + 'FROM' => array( + 'phpbb_users' => 'u', + 'phpbb_user_group' => 'ug', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array( + 'phpbb_banlist' => 'b', + ), + 'ON' => 'u.user_id = b.ban_userid', + ), + ), + 'WHERE' => array('AND', + array('ug.group_id', '=', 1), + array('u.user_id', '=', 'ug.user_id'), + array('b.ban_id', 'IS', NULL), + ), + 'ORDER_BY' => 'u.username', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $db->sql_return_on_error(false); + + $this->assertEquals(array( + array('username' => 'helper'), + array('username' => 'mass email'), + ), $db->sql_fetchrowset($result), + ($result === false) ? + "SQL ERROR:<br>" . var_export($sql, true) . "<br>" . $db->sql_error() : + var_export($sql, true) . ' ' . var_export($result, true) + ); + } +} diff --git a/tests/dbal/fixtures/boolean_processor.xml b/tests/dbal/fixtures/boolean_processor.xml new file mode 100644 index 0000000000..c5da677116 --- /dev/null +++ b/tests/dbal/fixtures/boolean_processor.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<dataset> + <table name="phpbb_banlist"> + <column>ban_id</column> + <column>ban_userid</column> + <row> + <value>1</value> + <value>2</value> + </row> + </table> + <table name="phpbb_users"> + <column>user_id</column> + <column>username</column> + <column>username_clean</column> + <column>user_permissions</column> + <column>user_sig</column> + <row> + <value>1</value> + <value>mass email</value> + <value>mass email</value> + <value></value> + <value></value> + </row> + <row> + <value>2</value> + <value>banned</value> + <value>banned</value> + <value></value> + <value></value> + </row> + <row> + <value>3</value> + <value>helper</value> + <value>helper</value> + <value></value> + <value></value> + </row> + <row> + <value>4</value> + <value>GroupBPal</value> + <value>groupbpal</value> + <value></value> + <value></value> + </row> + <row> + <value>5</value> + <value>GroupBPal2</value> + <value>groupBPal2</value> + <value></value> + <value></value> + </row> + <row> + <value>6</value> + <value>not in group</value> + <value>not in group</value> + <value></value> + <value></value> + </row> + </table> + <table name="phpbb_user_group"> + <column>user_id</column> + <column>group_id</column> + <row> + <value>1</value> + <value>1</value> + </row> + <row> + <value>2</value> + <value>1</value> + </row> + <row> + <value>3</value> + <value>1</value> + </row> + <row> + <value>4</value> + <value>2</value> + </row> + <row> + <value>5</value> + <value>2</value> + </row> + </table> +</dataset> |