diff --git a/.buildpath b/.buildpath
index c7f9553..e97ddaf 100644
--- a/.buildpath
+++ b/.buildpath
@@ -6,6 +6,11 @@
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73fdafa..70e8e9d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to
[Semantic Versioning](http://semver.org/).
+## [1.1.0] - 2017-11-28
+* Add support for expressions with simple logical operators and without parenthesis. The or operator is writen with `-`,
+ the and operator is written with `+` ;
+* Update minor composer dependency versions.
+
## [1.0.0] - 2017-08-01
* Initial release.
diff --git a/README.md b/README.md
index f9bad4f..8b4e151 100644
--- a/README.md
+++ b/README.md
@@ -85,12 +85,18 @@ The expression language provides the following operators.
The `!` operator is special, it can be used directly before a value string or in combination with the `=` or `in`
operators.
-For exemple `!5` or `!=5` to express "no equals to 5" or `!in('Paris','London')` ro express "no equals to Paris or
+For exemple `!5` or `!=5` to express "not equals to 5" or `!in('Paris','London')` to express "not equals to Paris or
London".
-### And or operators
+### AND and OR operators
-The `+` and `-` operator allow to create AND and OR SQL requests.
+The `+` and `-` operator allow to create AND and OR SQL requests.
+
+Here are sample expressions with logical operators.
+
+* `property=>5.4+<12` is translated to `property >= ? AND property < ?` with 2 parameters `[5.4,12]` ;
+* `property=~'*ball*'-~'*tennis*'` is translated to `property like ? OR property like ?` with 2 parameters
+ `['%ball%','%tennis%'].
### Like operator
diff --git a/composer.lock b/composer.lock
index fbc566e..5d9fd85 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4700,19 +4700,20 @@
},
{
"name": "zetacomponents/base",
- "version": "1.9",
+ "version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/zetacomponents/Base.git",
- "reference": "f20df24e8de3e48b6b69b2503f917e457281e687"
+ "reference": "489e20235989ddc97fdd793af31ac803972454f1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zetacomponents/Base/zipball/f20df24e8de3e48b6b69b2503f917e457281e687",
- "reference": "f20df24e8de3e48b6b69b2503f917e457281e687",
+ "url": "https://api.github.com/repos/zetacomponents/Base/zipball/489e20235989ddc97fdd793af31ac803972454f1",
+ "reference": "489e20235989ddc97fdd793af31ac803972454f1",
"shasum": ""
},
"require-dev": {
+ "phpunit/phpunit": "~5.7",
"zetacomponents/unit-test": "*"
},
"type": "library",
@@ -4759,7 +4760,7 @@
],
"description": "The Base package provides the basic infrastructure that all packages rely on. Therefore every component relies on this package.",
"homepage": "https://github.com/zetacomponents",
- "time": "2014-09-19T03:28:34+00:00"
+ "time": "2017-11-28T11:30:00+00:00"
},
{
"name": "zetacomponents/document",
diff --git a/src/main/php/Gomoob/Filter/Sql/SqlFilterConverter.php b/src/main/php/Gomoob/Filter/Sql/SqlFilterConverter.php
index 8038bbd..9dc9728 100644
--- a/src/main/php/Gomoob/Filter/Sql/SqlFilterConverter.php
+++ b/src/main/php/Gomoob/Filter/Sql/SqlFilterConverter.php
@@ -530,19 +530,31 @@ private function transformComplexFilter(
// Tokenize the filter
$tokens = $tokenizer->tokenize($value);
- if (count($tokens) === 3 && ($tokens[1]->getTokenCode() === LogicOperatorToken::AND ||
- $tokens[1]->getTokenCode() === LogicOperatorToken::OR)) {
- $resultFirstPart = $this->transformSimpleFilter($key, $tokens[0]->getSequence(), $context);
- $resultSecondPart = $this->transformSimpleFilter($key, $tokens[2]->getSequence(), $context);
+ // If a logical expression is expressed
+ if (count($tokens) === 3 &&
+ ($tokens[1]->getTokenCode() === LogicOperatorToken::AND ||
+ $tokens[1]->getTokenCode() === LogicOperatorToken::OR)) {
+ // Transform the first part of the logical expression
+ $sqlFilter1 = $this->transformSimpleFilter($key, $tokens[0]->getSequence(), $context);
+
+ // Transform the second part of the logical expression
+ $sqlFilter2 = $this->transformSimpleFilter($key, $tokens[2]->getSequence(), $context);
+
+ // Creates the resulting SQL logical expression
+ $result[0] = $sqlFilter1->getExpression();
if ($tokens[1]->getTokenCode() === LogicOperatorToken::AND) {
- $result[0] = "$resultFirstPart[0] AND $resultSecondPart[0]";
+ $result[0] .= ' AND ';
} elseif ($tokens[1]->getTokenCode() === LogicOperatorToken::OR) {
- $result[0] = "($resultFirstPart[0] OR $resultSecondPart[0])";
+ $result[0] .= ' OR ';
}
- $result[1] = array_merge($resultFirstPart[1], $resultSecondPart[1]);
+
+ $result[0] .= $sqlFilter2->getExpression();
+
+ // Creates the SQL parameters array
+ $result[1] = array_merge($sqlFilter1->getParams(), $sqlFilter2->getParams());
} else {
- $result = $this->transformSimpleFilter($key, $value, $context);
+ return $this->transformSimpleFilter($key, $value, $context);
}
} catch (TokenizerException $tex) {
// If an exception is encountered at tokenization then we consider the value to be a simple string
@@ -568,17 +580,22 @@ private function transformSimpleFilter(
) /* : array */ {
$result = ['', []];
- // Creates a tokenizer to tokenize the filter value
- $tokenizer = new FilterTokenizer();
+ try {
+ // Creates a tokenizer to tokenize the filter value
+ $tokenizer = new FilterTokenizer();
- // Tokenize the filter
- $tokens = $tokenizer->tokenize($value);
+ // Tokenize the filter
+ $tokens = $tokenizer->tokenize($value);
- // Now parse the tokens
- if (!empty($tokens)) {
- $result = $this->parseFromFirstToken($key, $value, $tokens, false);
+ // Now parse the tokens
+ if (!empty($tokens)) {
+ $result = $this->parseFromFirstToken($key, $value, $tokens, false);
+ }
+ } catch (TokenizerException $tex) {
+ // If an exception is encountered at tokenization then we consider the value to be a simple string
+ $result = [$key . ' = ?', [$value]];
}
- return $result;
+ return new SqlFilter($result[0], $result[1]);
}
}
diff --git a/src/main/php/Gomoob/Filter/Tokenizer/FilterTokenizer.php b/src/main/php/Gomoob/Filter/Tokenizer/FilterTokenizer.php
index 53dbb76..aae161e 100644
--- a/src/main/php/Gomoob/Filter/Tokenizer/FilterTokenizer.php
+++ b/src/main/php/Gomoob/Filter/Tokenizer/FilterTokenizer.php
@@ -60,7 +60,7 @@ public function __construct()
$this->addTokenInfo('(<)', FilterToken::LESS_THAN);
$this->addTokenInfo('(~)', FilterToken::LIKE);
- // No operator
+ // Not operator
$this->addTokenInfo('(!)', FilterToken::NOT);
// Function operators
diff --git a/src/main/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizer.php b/src/main/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizer.php
index f3ef7fd..500495b 100644
--- a/src/main/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizer.php
+++ b/src/main/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizer.php
@@ -34,7 +34,6 @@
*/
class LogicOperatorTokenizer extends AbstractTokenizer
{
-
/**
* Creates a new instance of the logic operator tokenizer.
*
@@ -42,7 +41,6 @@ class LogicOperatorTokenizer extends AbstractTokenizer
*/
public function __construct()
{
-
// This allows to clean our matched tokens a little
$this->trim = true;
@@ -52,9 +50,16 @@ public function __construct()
$this->addTokenInfo('(\+)', LogicOperatorToken::AND);
$this->addTokenInfo('(-)', LogicOperatorToken::OR);
- // Values
+ // "Raw" values
$this->addTokenInfo('([0-9.]+)', LogicOperatorToken::NUMBER);
$this->addTokenInfo('(\'[^\']+\')', LogicOperatorToken::STRING);
+
+ // Values prefixed with Simple operators
+ $this->addTokenInfo('(~\'[^\']+\')', LogicOperatorToken::STRING);
+
+ // Values prefixed with Not operator
+ $this->addTokenInfo('(!\'[^\']+\')', LogicOperatorToken::STRING);
+
$this->addTokenInfo('([^\'\+-]+)', LogicOperatorToken::STRING);
}
}
diff --git a/src/test/php/Gomoob/Filter/Sql/SqlFilterConverterTest.php b/src/test/php/Gomoob/Filter/Sql/SqlFilterConverterTest.php
index e440266..4e13024 100644
--- a/src/test/php/Gomoob/Filter/Sql/SqlFilterConverterTest.php
+++ b/src/test/php/Gomoob/Filter/Sql/SqlFilterConverterTest.php
@@ -79,23 +79,59 @@ public function testTransform()
$this->assertCount(1, $sqlFilter->getParams());
$this->assertSame('Sample string', $sqlFilter->getParams()[0]);
- // Test with a complex filter and only one property
- // Sample complex filters with only one property would be
- // "<10+>2" : Lower than 10 and greater than 2
- // "'Handball'-'Football'" : Equals to 'Hand ball' or 'Foot ball'
- // "'*ball*'+'*tennis*'" : Like 'ball' and like 'tennis'
+ // Test with a key which has a bad type
+ try {
+ $this->filterConverter->transform(0.26, '>10');
+ $this->fail('Must have thrown a ConverterException !');
+ } catch (ConverterException $cex) {
+ $this->assertSame('Invalid filter key type !', $cex->getMessage());
+ }
+ }
+
+
+ /**
+ * Test method for {@link SqlFilterConverter#transform(Object, String)}.
+ *
+ * @group SqlFilterConverterTest.testTransformAnd
+ */
+ public function testTransformAnd()
+ {
+ // Test with integers
$sqlFilter = $this->filterConverter->transform('property', '<10+>2');
$this->assertSame('property < ? AND property > ?', $sqlFilter->getExpression());
$this->assertCount(2, $sqlFilter->getParams());
$this->assertSame(10, $sqlFilter->getParams()[0]);
$this->assertSame(2, $sqlFilter->getParams()[1]);
- $sqlFilter = $this->filterConverter->transform('property', '>10-<2');
- $this->assertSame('(property > ? OR property < ?)', $sqlFilter->getExpression());
+ // Test with floats
+ $sqlFilter = $this->filterConverter->transform('property', '<5.3+>3.4');
+ $this->assertSame('property < ? AND property > ?', $sqlFilter->getExpression());
$this->assertCount(2, $sqlFilter->getParams());
- $this->assertSame(10, $sqlFilter->getParams()[0]);
- $this->assertSame(2, $sqlFilter->getParams()[1]);
+ $this->assertSame(5.3, $sqlFilter->getParams()[0]);
+ $this->assertSame(3.4, $sqlFilter->getParams()[1]);
+
+ // Test with strings
+ $sqlFilter = $this->filterConverter->transform('property', "Handball+Football");
+ $this->assertSame('property = ? AND property = ?', $sqlFilter->getExpression());
+ $this->assertCount(2, $sqlFilter->getParams());
+ $this->assertSame('Handball', $sqlFilter->getParams()[0]);
+ $this->assertSame('Football', $sqlFilter->getParams()[1]);
+ // Test with strings and the like operator
+ $sqlFilter = $this->filterConverter->transform('property', "~'*ball*'+~'*tennis*'");
+ $this->assertSame('property like ? AND property like ?', $sqlFilter->getExpression());
+ $this->assertCount(2, $sqlFilter->getParams());
+ $this->assertSame('%ball%', $sqlFilter->getParams()[0]);
+ $this->assertSame('%tennis%', $sqlFilter->getParams()[1]);
+ }
+
+ /**
+ * Test method for {@link SqlFilterConverter#transform(Object, String)}.
+ *
+ * @group SqlFilterConverterTest.testTransformComplex
+ */
+ public function testTransformComplex()
+ {
// Test with a complex filter with multiple properties (currently not supported and will fail)
try {
$this->filterConverter->transform(0, 'price:<90-validity:>=3');
@@ -103,14 +139,6 @@ public function testTransform()
} catch (ConverterException $cex) {
$this->assertSame('Complex filters are currently not implemented !', $cex->getMessage());
}
-
- // Test with a key which has a bad type
- try {
- $this->filterConverter->transform(0.26, '>10');
- $this->fail('Must have thrown a ConverterException !');
- } catch (ConverterException $cex) {
- $this->assertSame('Invalid filter key type !', $cex->getMessage());
- }
}
/**
@@ -497,4 +525,40 @@ public function testTransformNotLike()
);
}
}
+
+ /**
+ * Test method for {@link SqlFilterConverter#transform(Object, String)}.
+ *
+ * @group SqlFilterConverterTest.testTransformOr
+ */
+ public function testTransformOr()
+ {
+ // Test with integers
+ $sqlFilter = $this->filterConverter->transform('property', '<10->2');
+ $this->assertSame('property < ? OR property > ?', $sqlFilter->getExpression());
+ $this->assertCount(2, $sqlFilter->getParams());
+ $this->assertSame(10, $sqlFilter->getParams()[0]);
+ $this->assertSame(2, $sqlFilter->getParams()[1]);
+
+ // Test with floats
+ $sqlFilter = $this->filterConverter->transform('property', '<5.3->3.4');
+ $this->assertSame('property < ? OR property > ?', $sqlFilter->getExpression());
+ $this->assertCount(2, $sqlFilter->getParams());
+ $this->assertSame(5.3, $sqlFilter->getParams()[0]);
+ $this->assertSame(3.4, $sqlFilter->getParams()[1]);
+
+ // Test with strings
+ $sqlFilter = $this->filterConverter->transform('property', "Handball-Football");
+ $this->assertSame('property = ? OR property = ?', $sqlFilter->getExpression());
+ $this->assertCount(2, $sqlFilter->getParams());
+ $this->assertSame('Handball', $sqlFilter->getParams()[0]);
+ $this->assertSame('Football', $sqlFilter->getParams()[1]);
+
+ // Test with strings and the like operator
+ $sqlFilter = $this->filterConverter->transform('property', "~'*ball*'-~'*tennis*'");
+ $this->assertSame('property like ? OR property like ?', $sqlFilter->getExpression());
+ $this->assertCount(2, $sqlFilter->getParams());
+ $this->assertSame('%ball%', $sqlFilter->getParams()[0]);
+ $this->assertSame('%tennis%', $sqlFilter->getParams()[1]);
+ }
}
diff --git a/src/test/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizerTest.php b/src/test/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizerTest.php
index 78eda2e..09f6063 100644
--- a/src/test/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizerTest.php
+++ b/src/test/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizerTest.php
@@ -37,7 +37,6 @@
*/
class LogicOperatorTokenizerTest extends TestCase
{
-
/**
* An instance of the logic operator tokenizer to test.
*
@@ -56,12 +55,11 @@ public function setUp()
/**
* Test with a complex '+' and '-' operators.
*
- * @group LogicOperatorTokenizerTest.testTokenizeComplexAndOr
+ * @group LogicOperatorTokenizerTest.testTokenize
*/
- public function testTokenizeComplexAndOr()
+ public function testTokenize()
{
-
- // Test with a simple integer '+'
+ // Test with 2 integers and '+'
$tokens = $this->tokenizer->tokenize('>=10+<50');
$this->assertCount(3, $tokens);
@@ -69,7 +67,7 @@ public function testTokenizeComplexAndOr()
$this->assertSame('+', $tokens[1]->getSequence());
$this->assertSame('<50', $tokens[2]->getSequence());
- // Test with a simple float '-'
+ // Test with 2 floats and '-'
$tokens = $this->tokenizer->tokenize('>=10.1-<50.2');
$this->assertCount(3, $tokens);
@@ -77,6 +75,30 @@ public function testTokenizeComplexAndOr()
$this->assertSame('-', $tokens[1]->getSequence());
$this->assertSame('<50.2', $tokens[2]->getSequence());
+ // Test with 2 not quoted strings and '+'
+ $tokens = $this->tokenizer->tokenize("Hand+Ball");
+
+ $this->assertCount(3, $tokens);
+ $this->assertSame('Hand', $tokens[0]->getSequence());
+ $this->assertSame('+', $tokens[1]->getSequence());
+ $this->assertSame('Ball', $tokens[2]->getSequence());
+
+ // Test with 2 quoted strings and '+'
+ $tokens = $this->tokenizer->tokenize("'Hand'+'Ball'");
+
+ $this->assertCount(3, $tokens);
+ $this->assertSame("'Hand'", $tokens[0]->getSequence());
+ $this->assertSame('+', $tokens[1]->getSequence());
+ $this->assertSame("'Ball'", $tokens[2]->getSequence());
+
+ // Test 2 strings prefixed with the like operator and '+'
+ $tokens = $this->tokenizer->tokenize("~'*ball*'+~'*tennis*'");
+
+ $this->assertCount(3, $tokens);
+ $this->assertSame("~'*ball*'", $tokens[0]->getSequence());
+ $this->assertSame('+', $tokens[1]->getSequence());
+ $this->assertSame("~'*tennis*'", $tokens[2]->getSequence());
+
// Test with complex '+' '-'
$tokens = $this->tokenizer->tokenize('>=10.1+<50.2-=60.3');