diff --git a/core/lib/Drupal/Core/Database/Query/Condition.php b/core/lib/Drupal/Core/Database/Query/Condition.php index ad7397ef8c72e57aa00007d8bfa0aa60c411d22b..400384dca00946a8e4d1ff4210f539f759574237 100644 --- a/core/lib/Drupal/Core/Database/Query/Condition.php +++ b/core/lib/Drupal/Core/Database/Query/Condition.php @@ -39,6 +39,13 @@ class Condition implements ConditionInterface, \Countable { */ protected $queryPlaceholderIdentifier; + /** + * Contains the string version of the Condition. + * + * @var string + */ + protected $stringVersion; + /** * Constructs a Condition object. * @@ -158,86 +165,127 @@ public function compile(Connection $connection, PlaceholderInterface $queryPlace $conjunction = $conditions['#conjunction']; unset($conditions['#conjunction']); foreach ($conditions as $condition) { - if (empty($condition['operator'])) { - // This condition is a literal string, so let it through as is. - $condition_fragments[] = ' (' . $condition['field'] . ') '; + // Process field. + if ($condition['field'] instanceof ConditionInterface) { + // Left hand part is a structured condition or a subquery. Compile, + // put brackets around it (if it is a query), and collect any + // arguments. + $condition['field']->compile($connection, $queryPlaceholder); + $field_fragment = (string) $condition['field']; + if ($condition['field'] instanceof SelectInterface) { + $field_fragment = '(' . $field_fragment . ')'; + } + $arguments += $condition['field']->arguments(); + // If the operator and value were not passed in to the + // @see ConditionInterface::condition() method (and thus have the + // default value as defined over there) it is assumed to be a valid + // condition on its own: ignore the operator and value parts. + $ignore_operator = $condition['operator'] === '=' && $condition['value'] === NULL; + } + elseif (!isset($condition['operator'])) { + // Left hand part is a literal string added with the + // @see ConditionInterface::where() method. Put brackets around + // the snippet and collect the arguments from the value part. + // Also ignore the operator and value parts. + $field_fragment = '(' . $condition['field'] . ')'; $arguments += $condition['value']; + $ignore_operator = TRUE; + } + else { + // Left hand part is a normal field. Add it as is. + $field_fragment = $connection->escapeField($condition['field']); + $ignore_operator = FALSE; + } + + // Process operator. + if ($ignore_operator) { + $operator = array('operator' => '', 'use_value' => FALSE); } else { - // It's a structured condition, so parse it out accordingly. - // Note that $condition['field'] will only be an object for a dependent - // DatabaseCondition object, not for a dependent subquery. - if ($condition['field'] instanceof ConditionInterface) { - // Compile the sub-condition recursively and add it to the list. - $condition['field']->compile($connection, $queryPlaceholder); - $condition_fragments[] = '(' . (string) $condition['field'] . ')'; - $arguments += $condition['field']->arguments(); + // Remove potentially dangerous characters. + // If something passed in an invalid character stop early, so we + // don't rely on a broken SQL statement when we would just replace + // those characters. + if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) { + $this->changed = TRUE; + $this->arguments = []; + // Provide a string which will result into an empty query result. + $this->stringVersion = '( AND 1 = 0 )'; + + // Conceptually throwing an exception caused by user input is bad + // as you result into a WSOD, which depending on your webserver + // configuration can result into the assumption that your site is + // broken. + // On top of that the database API relies on __toString() which + // does not allow to throw exceptions. + trigger_error('Invalid characters in query operator: ' . $condition['operator'], E_USER_ERROR); + return; } - else { - // For simplicity, we treat all operators as the same data structure. - // In the typical degenerate case, this won't get changed. - $operator_defaults = array( - 'prefix' => '', - 'postfix' => '', - 'delimiter' => '', - 'operator' => $condition['operator'], - 'use_value' => TRUE, - ); - // Remove potentially dangerous characters. - // If something passed in an invalid character stop early, so we - // don't rely on a broken SQL statement when we would just replace - // those characters. - if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) { - $this->changed = TRUE; - $this->arguments = []; - // Provide a string which will result into an empty query result. - $this->stringVersion = '( AND 1 = 0 )'; - - // Conceptually throwing an exception caused by user input is bad - // as you result into a WSOD, which depending on your webserver - // configuration can result into the assumption that your site is - // broken. - // On top of that the database API relies on __toString() which - // does not allow to throw exceptions. - trigger_error('Invalid characters in query operator: ' . $condition['operator'], E_USER_ERROR); - return; - } - $operator = $connection->mapConditionOperator($condition['operator']); - if (!isset($operator)) { - $operator = $this->mapConditionOperator($condition['operator']); - } - $operator += $operator_defaults; - - $placeholders = array(); - if ($condition['value'] instanceof SelectInterface) { - $condition['value']->compile($connection, $queryPlaceholder); - $placeholders[] = (string) $condition['value']; - $arguments += $condition['value']->arguments(); - // Subqueries are the actual value of the operator, we don't - // need to add another below. - $operator['use_value'] = FALSE; + + // For simplicity, we convert all operators to a data structure to + // allow to specify a prefix, a delimiter and such. Find the + // associated data structure by first doing a database specific + // lookup, followed by a specification according to the SQL standard. + $operator = $connection->mapConditionOperator($condition['operator']); + if (!isset($operator)) { + $operator = $this->mapConditionOperator($condition['operator']); + } + $operator += array('operator' => $condition['operator']); + } + // Add defaults. + $operator += array( + 'prefix' => '', + 'postfix' => '', + 'delimiter' => '', + 'use_value' => TRUE, + ); + $operator_fragment = $operator['operator']; + + // Process value. + $value_fragment = ''; + if ($operator['use_value']) { + // For simplicity, we first convert to an array, so that we can handle + // the single and multi value cases the same. + if (!is_array($condition['value'])) { + if ($condition['value'] instanceof SelectInterface && ($operator['operator'] === 'IN' || $operator['operator'] === 'NOT IN')) { + // Special case: IN is followed by a single select query instead + // of a set of values: unset prefix and postfix to prevent double + // brackets. + $operator['prefix'] = ''; + $operator['postfix'] = ''; } - // We assume that if there is a delimiter, then the value is an - // array. If not, it is a scalar. For simplicity, we first convert - // up to an array so that we can build the placeholders in the same way. - elseif (!$operator['delimiter'] && !is_array($condition['value'])) { - $condition['value'] = array($condition['value']); + $condition['value'] = array($condition['value']); + } + // Process all individual values. + $value_fragment = array(); + foreach ($condition['value'] as $value) { + if ($value instanceof SelectInterface) { + // Right hand part is a subquery. Compile, put brackets around it + // and collect any arguments. + $value->compile($connection, $queryPlaceholder); + $value_fragment[] = '(' . (string) $value . ')'; + $arguments += $value->arguments(); } - if ($operator['use_value']) { - foreach ($condition['value'] as $value) { - $placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder(); - $arguments[$placeholder] = $value; - $placeholders[] = $placeholder; - } + else { + // Right hand part is a normal value. Replace the value with a + // placeholder and add the value as an argument. + $placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder(); + $value_fragment[] = $placeholder; + $arguments[$placeholder] = $value; } - $condition_fragments[] = ' (' . $connection->escapeField($condition['field']) . ' ' . $operator['operator'] . ' ' . $operator['prefix'] . implode($operator['delimiter'], $placeholders) . $operator['postfix'] . ') '; } + $value_fragment = $operator['prefix'] . implode($operator['delimiter'], $value_fragment) . $operator['postfix']; } + + // Concatenate the left hand part, operator and right hand part. + $condition_fragments[] = trim(implode(' ', array($field_fragment, $operator_fragment, $value_fragment))); } - $this->changed = FALSE; - $this->stringVersion = implode($conjunction, $condition_fragments); + // Concatenate all conditions using the conjunction and brackets around + // the individual conditions to assure the proper evaluation order. + $this->stringVersion = count($condition_fragments) > 1 ? '(' . implode(") $conjunction (", $condition_fragments) . ')' : implode($condition_fragments); $this->arguments = $arguments; + $this->changed = FALSE; } } @@ -289,7 +337,7 @@ function __clone() { * the value data they pass in is not a simple value. This is a simple * overridable lookup function. * - * @param $operator + * @param string $operator * The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive. * * @return array @@ -301,15 +349,17 @@ protected function mapConditionOperator($operator) { static $specials = array( 'BETWEEN' => array('delimiter' => ' AND '), 'NOT BETWEEN' => array('delimiter' => ' AND '), - 'IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'), - 'NOT IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'), - 'EXISTS' => array('prefix' => ' (', 'postfix' => ')'), - 'NOT EXISTS' => array('prefix' => ' (', 'postfix' => ')'), + 'IN' => array('delimiter' => ', ', 'prefix' => '(', 'postfix' => ')'), + 'NOT IN' => array('delimiter' => ', ', 'prefix' => '(', 'postfix' => ')'), 'IS NULL' => array('use_value' => FALSE), 'IS NOT NULL' => array('use_value' => FALSE), // Use backslash for escaping wildcard characters. 'LIKE' => array('postfix' => " ESCAPE '\\\\'"), 'NOT LIKE' => array('postfix' => " ESCAPE '\\\\'"), + // Exists expects an already bracketed subquery as right hand part. Do + // not define additional brackets. + 'EXISTS' => array(), + 'NOT EXISTS' => array(), // These ones are here for performance reasons. '=' => array(), '<' => array(), diff --git a/core/lib/Drupal/Core/Database/Query/ConditionInterface.php b/core/lib/Drupal/Core/Database/Query/ConditionInterface.php index bd0b52bd2cce48b41da719b07c8eb4497b8f1f25..a881cdddbcf9f00aecf809fea9e8c808384016c6 100644 --- a/core/lib/Drupal/Core/Database/Query/ConditionInterface.php +++ b/core/lib/Drupal/Core/Database/Query/ConditionInterface.php @@ -12,13 +12,21 @@ interface ConditionInterface { /** * Helper function: builds the most common conditional clauses. * - * This method can take a variable number of parameters. If called with two - * parameters, they are taken as $field and $value with $operator having a - * value of =. + * This method takes 1 to 3 parameters. + * + * If called with 1 parameter, it should be a ConditionInterface that in + * itself forms a valid where clause. Use e.g. to build clauses with nested + * AND's and OR's. + * + * If called with 2 parameters, they are taken as $field and $value with + * $operator having a value of =. * * Do not use this method to test for NULL values. Instead, use * QueryConditionInterface::isNull() or QueryConditionInterface::isNotNull(). * + * To improve readability, the operators EXISTS and NOT EXISTS have their own + * utility method defined. + * * Drupal considers LIKE case insensitive and the following is often used * to tell the database that case insensitive equivalence is desired: * @code @@ -32,33 +40,43 @@ interface ConditionInterface { * be case sensitive and when a case insensitive collation is used, the = * operator will also be case insensitive. * - * @param $field - * The name of the field to check. If you would like to add a more complex - * condition involving operators or functions, use where(). - * @param $value - * The value to test the field against. In most cases, this is a scalar. - * For more complex options, it is an array. The meaning of each element in - * the array is dependent on the $operator. - * @param $operator - * The comparison operator, such as =, <, or >=. It also accepts more - * complex options such as IN, LIKE, LIKE BINARY, or BETWEEN. Defaults to =. + * @param string|\Drupal\Core\Database\Query\ConditionInterface $field + * The name of the field to check. This can also be QueryConditionInterface + * in itself. Use where(), if you would like to add a more complex condition + * involving operators or functions, or an already compiled condition. + * @param string|array|\Drupal\Core\Database\Query\SelectInterface|null $value + * The value to test the field against. In most cases, and depending on the + * operator, this will be a scalar or an array. As SQL accepts select + * queries on any place where a scalar value or set is expected, $value may + * also be a(n array of) SelectInterface(s). If $operator is a unary + * operator, e.g. EXISTS, $value will be ignored and should be null. + * @param string|null $operator + * The operator to use. Supported for all supported databases are at least: + * - The comparison operators =, <>, <, <=, >, >=. + * - The operators (NOT) BETWEEN, (NOT) IN, (NOT) EXISTS, (NOT) LIKE. + * Other operators (e.g. LIKE, BINARY) may or may not work. Defaults to =. * * @return \Drupal\Core\Database\Query\ConditionInterface * The called object. * * @see \Drupal\Core\Database\Query\ConditionInterface::isNull() * @see \Drupal\Core\Database\Query\ConditionInterface::isNotNull() + * @see \Drupal\Core\Database\Query\ConditionInterface::exists() + * @see \Drupal\Core\Database\Query\ConditionInterface::notExist() + * @see \Drupal\Core\Database\Query\ConditionInterface::where() */ public function condition($field, $value = NULL, $operator = '='); /** * Adds an arbitrary WHERE clause to the query. * - * @param $snippet + * @param string $snippet * A portion of a WHERE clause as a prepared statement. It must use named - * placeholders, not ? placeholders. - * @param $args - * An associative array of arguments. + * placeholders, not ? placeholders. The caller is responsible for providing + * unique placeholders that do not interfere with the placeholders generated + * by this QueryConditionInterface object. + * @param array $args + * An associative array of arguments keyed by the named placeholders. * * @return \Drupal\Core\Database\Query\ConditionInterface * The called object. @@ -68,8 +86,8 @@ public function where($snippet, $args = array()); /** * Sets a condition that the specified field be NULL. * - * @param $field - * The name of the field to check. + * @param string|\Drupal\Core\Database\Query\SelectInterface $field + * The name of the field or a subquery to check. * * @return \Drupal\Core\Database\Query\ConditionInterface * The called object. @@ -79,8 +97,8 @@ public function isNull($field); /** * Sets a condition that the specified field be NOT NULL. * - * @param $field - * The name of the field to check. + * @param string|\Drupal\Core\Database\Query\SelectInterface $field + * The name of the field or a subquery to check. * * @return \Drupal\Core\Database\Query\ConditionInterface * The called object. @@ -110,7 +128,7 @@ public function exists(SelectInterface $select); public function notExists(SelectInterface $select); /** - * Gets a complete list of all conditions in this conditional clause. + * Gets the, possibly nested, list of conditions in this conditional clause. * * This method returns by reference. That allows alter hooks to access the * data structure directly and manipulate it before it gets compiled. @@ -131,6 +149,9 @@ public function notExists(SelectInterface $select); * * There will also be a single array entry of #conjunction, which is the * conjunction that will be applied to the array, such as AND. + * + * @return array + * The, possibly nested, list of all conditions (by reference). */ public function &conditions(); diff --git a/core/modules/views_ui/src/Tests/PreviewTest.php b/core/modules/views_ui/src/Tests/PreviewTest.php index 3b4dd9bf830cdd5b02c2d208a7e831834687d3b2..d2dcd121936649cc36bc755e8dfe64b1ec643ba6 100644 --- a/core/modules/views_ui/src/Tests/PreviewTest.php +++ b/core/modules/views_ui/src/Tests/PreviewTest.php @@ -105,7 +105,7 @@ function testPreviewUI() { $this->assertText(t('Query execute time')); $this->assertText(t('View render time')); $this->assertRaw('<strong>Query</strong>'); - $this->assertText("SELECT views_test_data.name AS views_test_data_name\nFROM \n{views_test_data} views_test_data\nWHERE (( (views_test_data.id = '100' ) ))"); + $this->assertText("SELECT views_test_data.name AS views_test_data_name\nFROM \n{views_test_data} views_test_data\nWHERE (views_test_data.id = '100' )"); // Test that the statistics and query are rendered above the preview. $this->assertTrue(strpos($this->getRawContent(), 'views-query-info') < strpos($this->getRawContent(), 'view-test-preview'), 'Statistics shown above the preview.'); diff --git a/core/tests/Drupal/KernelTests/Core/Database/SelectSubqueryTest.php b/core/tests/Drupal/KernelTests/Core/Database/SelectSubqueryTest.php index 4877ca2f73dc93eacaf6b4628f57a0d78bb0c224..98849bbaaab3cba1f7600b1133dd4517fdf6337e 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/SelectSubqueryTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/SelectSubqueryTest.php @@ -39,7 +39,7 @@ function testFromSubquerySelect() { // WHERE tt.task = 'code' $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 1, 'Returned the correct number of rows.'); + $this->assertCount(1, $people, 'Returned the correct number of rows.'); } } @@ -66,11 +66,11 @@ function testFromSubquerySelectWithLimit() { // INNER JOIN test t ON t.id=tt.pid $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 1, 'Returned the correct number of rows.'); + $this->assertCount(1, $people, 'Returned the correct number of rows.'); } /** - * Tests that we can use a subquery in a WHERE clause. + * Tests that we can use a subquery with an IN operator in a WHERE clause. */ function testConditionSubquerySelect() { // Create a subquery, which is just a normal query object. @@ -89,7 +89,92 @@ function testConditionSubquerySelect() { // FROM test tt2 // WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1) $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 5, 'Returned the correct number of rows.'); + $this->assertCount(5, $people, 'Returned the correct number of rows.'); + } + + /** + * Test that we can use a subquery with a relational operator in a WHERE clause. + */ + function testConditionSubquerySelect2() { + // Create a subquery, which is just a normal query object. + $subquery = db_select('test', 't2'); + $subquery->addExpression('AVG(t2.age)'); + + // Create another query that adds a clause using the subquery. + $select = db_select('test', 't'); + $select->addField('t', 'name'); + $select->condition('t.age', $subquery, '<'); + + // The resulting query should be equivalent to: + // SELECT t.name + // FROM test t + // WHERE t.age < (SELECT AVG(t2.age) FROM test t2) + $people = $select->execute()->fetchCol(); + $this->assertEquals(['John', 'Paul'], $people, 'Returned Paul and John.', 0.0, 10, TRUE); + } + + /** + * Test that we can use 2 subqueries with a relational operator in a WHERE clause. + */ + function testConditionSubquerySelect3() { + // Create subquery 1, which is just a normal query object. + $subquery1 = db_select('test_task', 'tt'); + $subquery1->addExpression('AVG(tt.priority)'); + $subquery1->where('tt.pid = t.id'); + + // Create subquery 2, which is just a normal query object. + $subquery2 = db_select('test_task', 'tt2'); + $subquery2->addExpression('AVG(tt2.priority)'); + + // Create another query that adds a clause using the subqueries. + $select = db_select('test', 't'); + $select->addField('t', 'name'); + $select->condition($subquery1, $subquery2, '>'); + + // The resulting query should be equivalent to: + // SELECT t.name + // FROM test t + // WHERE (SELECT AVG(tt.priority) FROM test_task tt WHERE tt.pid = t.id) > (SELECT AVG(tt2.priority) FROM test_task tt2) + $people = $select->execute()->fetchCol(); + $this->assertEquals(['John'], $people, 'Returned John.', 0.0, 10, TRUE); + } + + /** + * Test that we can use multiple subqueries. + * + * This test uses a subquery at the left hand side and multiple subqueries at + * the right hand side. The test query may not be that logical but that's due + * to the limited amount of data and tables. 'Valid' use cases do exist :) + */ + function testConditionSubquerySelect4() { + // Create subquery 1, which is just a normal query object. + $subquery1 = db_select('test_task', 'tt'); + $subquery1->addExpression('AVG(tt.priority)'); + $subquery1->where('tt.pid = t.id'); + + // Create subquery 2, which is just a normal query object. + $subquery2 = db_select('test_task', 'tt2'); + $subquery2->addExpression('MIN(tt2.priority)'); + $subquery2->where('tt2.pid <> t.id'); + + // Create subquery 3, which is just a normal query object. + $subquery3 = db_select('test_task', 'tt3'); + $subquery3->addExpression('AVG(tt3.priority)'); + $subquery3->where('tt3.pid <> t.id'); + + // Create another query that adds a clause using the subqueries. + $select = db_select('test', 't'); + $select->addField('t', 'name'); + $select->condition($subquery1, [$subquery2, $subquery3], 'BETWEEN'); + + // The resulting query should be equivalent to: + // SELECT t.name AS name + // FROM {test} t + // WHERE (SELECT AVG(tt.priority) AS expression FROM {test_task} tt WHERE (tt.pid = t.id)) + // BETWEEN (SELECT MIN(tt2.priority) AS expression FROM {test_task} tt2 WHERE (tt2.pid <> t.id)) + // AND (SELECT AVG(tt3.priority) AS expression FROM {test_task} tt3 WHERE (tt3.pid <> t.id)); + $people = $select->execute()->fetchCol(); + $this->assertEquals(['George', 'Paul'], $people, 'Returned George and Paul.', 0.0, 10, TRUE); } /** @@ -113,7 +198,7 @@ function testJoinSubquerySelect() { // INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 2, 'Returned the correct number of rows.'); + $this->assertCount(2, $people, 'Returned the correct number of rows.'); } /** @@ -143,7 +228,7 @@ function testExistsSubquerySelect() { // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.'); + $this->assertEquals('George', $record->name, 'Fetched name is correct using EXISTS query.'); } /** @@ -173,7 +258,7 @@ function testNotExistsSubquerySelect() { // Ensure that we got the right number of records. $people = $query->execute()->fetchCol(); - $this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.'); + $this->assertCount(3, $people, 'NOT EXISTS query returned the correct results.'); } } diff --git a/core/tests/Drupal/Tests/Core/Database/ConditionTest.php b/core/tests/Drupal/Tests/Core/Database/ConditionTest.php index fde028b9b5e0aad1a789842211120da9907dbc10..f33e3c331bfbcd2467bac11bc3b9908b7e917f4e 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConditionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConditionTest.php @@ -15,12 +15,27 @@ */ class ConditionTest extends UnitTestCase { + /** + * Provides a list of known operations and the expected output. + * + * @return array + * - Expected result for the string version of the condition. + * - The field name to input in the condition. + */ + public function providerSimpleCondition() { + return [ + ['name = :db_condition_placeholder_0', 'name'], + ['name123 = :db_condition_placeholder_0', 'name-123'], + ]; + } + /** * @covers ::compile + * @dataProvider providerSimpleCondition() */ - public function testSimpleCondition() { + public function testSimpleCondition($expected, $field_name) { $connection = $this->prophesize(Connection::class); - $connection->escapeField('name')->will(function ($args) { + $connection->escapeField($field_name)->will(function ($args) { return preg_replace('/[^A-Za-z0-9_.]+/', '', $args[0]); }); $connection->mapConditionOperator('=')->willReturn(['operator' => '=']); @@ -36,10 +51,10 @@ public function testSimpleCondition() { $query_placeholder = $query_placeholder->reveal(); $condition = new Condition('AND'); - $condition->condition('name', ['value']); + $condition->condition($field_name, ['value']); $condition->compile($connection, $query_placeholder); - $this->assertEquals(' (name = :db_condition_placeholder_0) ', $condition->__toString()); + $this->assertEquals($expected, $condition->__toString()); $this->assertEquals([':db_condition_placeholder_0' => 'value'], $condition->arguments()); } @@ -96,28 +111,28 @@ public function dataProviderTestCompileWithKnownOperators() { // aren't directly supported by core, but instead need manual handling with // prefix/suffix at the moment. $data = []; - $data[] = [' (name = :db_condition_placeholder_0) ', 'name', 'value', '=']; - $data[] = [' (name != :db_condition_placeholder_0) ', 'name', 'value', '!=']; - $data[] = [' (name <> :db_condition_placeholder_0) ', 'name', 'value', '<>']; - $data[] = [' (name >= :db_condition_placeholder_0) ', 'name', 'value', '>=']; - $data[] = [' (name > :db_condition_placeholder_0) ', 'name', 'value', '>']; - $data[] = [' (name <= :db_condition_placeholder_0) ', 'name', 'value', '<=']; - $data[] = [' (name < :db_condition_placeholder_0) ', 'name', 'value', '<']; - // $data[] = [' ( GREATEST (1, 2, 3) ) ', '', [1, 2, 3], 'GREATEST']; - $data[] = [' (name IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2)) ', 'name', ['1', '2', '3'], 'IN']; - $data[] = [' (name NOT IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2)) ', 'name', ['1', '2', '3'], 'NOT IN']; - // $data[] = [' ( INTERVAL (1, 2, 3) ) ', '', [1, 2, 3], 'INTERVAL']; - $data[] = [' (name IS NULL ) ', 'name', NULL, 'IS NULL']; - $data[] = [' (name IS NOT NULL ) ', 'name', NULL, 'IS NOT NULL']; - $data[] = [' (name IS :db_condition_placeholder_0) ', 'name', 'TRUE', 'IS']; - // $data[] = [' ( LEAST (1, 2, 3) ) ', '', [1, 2, 3], 'LEAST']; - $data[] = [" (name LIKE :db_condition_placeholder_0 ESCAPE '\\\\') ", 'name', '%muh%', 'LIKE', [':db_condition_placeholder_0' => '%muh%']]; - $data[] = [" (name NOT LIKE :db_condition_placeholder_0 ESCAPE '\\\\') ", 'name', '%muh%', 'NOT LIKE', [':db_condition_placeholder_0' => '%muh%']]; - $data[] = [" (name BETWEEN :db_condition_placeholder_0 AND :db_condition_placeholder_1) ", 'name', [1, 2], 'BETWEEN', [':db_condition_placeholder_0' => 1, ':db_condition_placeholder_1' => 2]]; - $data[] = [" (name NOT BETWEEN :db_condition_placeholder_0 AND :db_condition_placeholder_1) ", 'name', [1, 2], 'NOT BETWEEN', [':db_condition_placeholder_0' => 1, ':db_condition_placeholder_1' => 2]]; - // $data[] = [' ( STRCMP (name, :db_condition_placeholder_0) ) ', '', ['test-string'], 'STRCMP', [':db_condition_placeholder_0' => 'test-string']]; - // $data[] = [' (EXISTS ) ', '', NULL, 'EXISTS']; - // $data[] = [' (name NOT EXISTS ) ', 'name', NULL, 'NOT EXISTS']; + $data[] = ['name = :db_condition_placeholder_0', 'name', 'value', '=']; + $data[] = ['name != :db_condition_placeholder_0', 'name', 'value', '!=']; + $data[] = ['name <> :db_condition_placeholder_0', 'name', 'value', '<>']; + $data[] = ['name >= :db_condition_placeholder_0', 'name', 'value', '>=']; + $data[] = ['name > :db_condition_placeholder_0', 'name', 'value', '>']; + $data[] = ['name <= :db_condition_placeholder_0', 'name', 'value', '<=']; + $data[] = ['name < :db_condition_placeholder_0', 'name', 'value', '<']; + // $data[] = ['GREATEST (1, 2, 3)', '', [1, 2, 3], 'GREATEST']; + $data[] = ['name IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2)', 'name', ['1', '2', '3'], 'IN']; + $data[] = ['name NOT IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2)', 'name', ['1', '2', '3'], 'NOT IN']; + // $data[] = ['INTERVAL (1, 2, 3)', '', [1, 2, 3], 'INTERVAL']; + $data[] = ['name IS NULL', 'name', NULL, 'IS NULL']; + $data[] = ['name IS NOT NULL', 'name', NULL, 'IS NOT NULL']; + $data[] = ['name IS :db_condition_placeholder_0', 'name', 'TRUE', 'IS']; + // $data[] = ['LEAST (1, 2, 3)', '', [1, 2, 3], 'LEAST']; + $data[] = ["name LIKE :db_condition_placeholder_0 ESCAPE '\\\\'", 'name', '%muh%', 'LIKE', [':db_condition_placeholder_0' => '%muh%']]; + $data[] = ["name NOT LIKE :db_condition_placeholder_0 ESCAPE '\\\\'", 'name', '%muh%', 'NOT LIKE', [':db_condition_placeholder_0' => '%muh%']]; + $data[] = ["name BETWEEN :db_condition_placeholder_0 AND :db_condition_placeholder_1", 'name', [1, 2], 'BETWEEN', [':db_condition_placeholder_0' => 1, ':db_condition_placeholder_1' => 2]]; + $data[] = ["name NOT BETWEEN :db_condition_placeholder_0 AND :db_condition_placeholder_1", 'name', [1, 2], 'NOT BETWEEN', [':db_condition_placeholder_0' => 1, ':db_condition_placeholder_1' => 2]]; + // $data[] = ['STRCMP (name, :db_condition_placeholder_0)', '', ['test-string'], 'STRCMP', [':db_condition_placeholder_0' => 'test-string']]; + // $data[] = ['EXISTS', '', NULL, 'EXISTS']; + // $data[] = ['name NOT EXISTS', 'name', NULL, 'NOT EXISTS']; return $data; }