Skip to content

Commit

Permalink
Prepare version 1.1.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
bgaillard committed Nov 28, 2017
1 parent 11d6093 commit 5b51067
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .buildpath
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
<attribute name="composer" value="source"/>
</attributes>
</buildpathentry>
<buildpathentry kind="src" path="src/test/php/Gomoob">
<attributes>
<attribute name="composer" value="source"/>
</attributes>
</buildpathentry>
<buildpathentry kind="src" path="vendor/cilex/cilex/src">
<attributes>
<attribute name="composer" value="vendor"/>
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 6 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 33 additions & 16 deletions src/main/php/Gomoob/Filter/Sql/SqlFilterConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]);
}
}
2 changes: 1 addition & 1 deletion src/main/php/Gomoob/Filter/Tokenizer/FilterTokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions src/main/php/Gomoob/Filter/Tokenizer/LogicOperatorTokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,13 @@
*/
class LogicOperatorTokenizer extends AbstractTokenizer
{

/**
* Creates a new instance of the logic operator tokenizer.
*
* @return \Gomoob\Filter\Tokenizer\LogicOperatorTokenizer the created instance.
*/
public function __construct()
{

// This allows to clean our matched tokens a little
$this->trim = true;

Expand All @@ -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);
}
}
98 changes: 81 additions & 17 deletions src/test/php/Gomoob/Filter/Sql/SqlFilterConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,38 +79,66 @@ 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');
$this->fail('Must have thrown a ConverterException !');
} 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());
}
}

/**
Expand Down Expand Up @@ -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]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
*/
class LogicOperatorTokenizerTest extends TestCase
{

/**
* An instance of the logic operator tokenizer to test.
*
Expand All @@ -56,27 +55,50 @@ 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);
$this->assertSame('>=10', $tokens[0]->getSequence());
$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);
$this->assertSame('>=10.1', $tokens[0]->getSequence());
$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');

Expand Down

0 comments on commit 5b51067

Please sign in to comment.