Skip to content

Commit

Permalink
operator expressions & embedded filters (#13)
Browse files Browse the repository at this point in the history
* Added ternary operator expression (inline if/else)

Added arithmetic operator expression

Added embedded filters

* code cleanup
  • Loading branch information
LaravelFreelancerNL authored Oct 27, 2020
1 parent b16de75 commit cc76101
Show file tree
Hide file tree
Showing 17 changed files with 348 additions and 106 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ Fluent PHP query builder for [ArangoDB’s](https://www.arangodb.com) Query Lang
3. [Installation](#installation)
4. [Usage](#usage)


## Purpose
Using a query builder mainly makes the life of a programmer much easier. You can write cleaner code
and be quicker at it. Which of course comes at the cost of application speed.
Expand Down Expand Up @@ -109,6 +108,7 @@ $qb->bind('your data', 'your-bind-id')
- [Statement clauses](docs/api/statement-clauses.md): data manipulation & variable declaration
- [Graph clauses](docs/api/graph-clauses.md): graph traversals
- [Functions](docs/api/functions.md): a list of all supported AQL functions
- [Operator expressions](docs/api/operator-expressions.md): if() & calc() expressions
- [Subqueries](docs/core-concepts/subqueries.md): how to create subqueries, joins etc.
- Core Concepts
- [Terminology](docs/core-concepts/terminology.md): definitions of terms used in the documentation
Expand Down
42 changes: 42 additions & 0 deletions docs/api/operator-expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Operator expressions
AQL has two special operator expressions that require their own functions:
- Ternary expressions (inline if/else)
- Arithmetic expressions (calculations)

## if()
```
if($conditions, $then, $else)
```

**Example 1: basic if expression**
```
$qb = new QueryBuilder();
$qb->let('x', 5)->return($qb->if(['x', '==', '5'], true, false));
```
Resulting AQL: `LET x = 5 RETURN (x == 5) ? true : false`

[ArangoDB Ternary Operator documentation](https://www.arangodb.com/docs/stable/aql/operators.html#ternary-operator)

## calc()
```
calc($leftOperand, $operator, $rightOperand)
```
The left and right operands can be numbers or should result in a number.
Number strings are considered to be numbers. So the string '5' equals the number 5.

**Example 1: basic calculation**
```
$qb = new QueryBuilder();
$qb->return($qb->calc(3, '*', 3));
```
Resulting AQL: `RETURN 3 * 3`


**Example 2: calculation with another calculation embedded**
```
$qb = new QueryBuilder();
$qb->return($qb->calc(3, '+', $qb->calc(3, '*', 3)));
```
Resulting AQL: `RETURN 3 + (3 * 3)`

[ArangoDB Arithmetic Operator documentation](https://www.arangodb.com/docs/stable/aql/operators.html#arithmetic-operators)
22 changes: 21 additions & 1 deletion docs/api/query-clauses.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ Resulting AQL: `... RETURN user`
```
filter($leftOperand, $comparisonOperator = null, $rightOperand = null, $logicalOperator = null)
```
Filter out data not matching the given predicate(s).
Filter out data not matching the given predicate(s). You may add a single predicate or a list of predicates.
Predicates can be embedded up to one level deep.

**Example 1 - single predicate:**
```
Expand All @@ -66,6 +67,25 @@ $qb->for('user', 'users')
```
Resulting AQL: `... FILTER u.age < 18 OR u.age > 65 ...`

**Example 4 - embedded predicates:**
```
$filter = [
[
['doc.attribute1', '!=', null, 'AND'],
['doc.attribute2', '!=', 'null', 'OR']
],
['doc.attribute3', '==', 'null', 'OR']
];
(new QueryBuilder())
->for('doc', 'documents')
->filter($filter)
->get();
```
Resulting AQL: `... FILTER (doc.attribute1 != null OR doc.attribute2 != null) OR doc.attribute3 == null ...`



[ArangoDB FILTER documentation](https://www.arangodb.com/docs/stable/aql/operations-filter.html)

## SEARCH
Expand Down
44 changes: 44 additions & 0 deletions src/AQL/HasOperatorExpressions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace LaravelFreelancerNL\FluentAQL\AQL;

use LaravelFreelancerNL\FluentAQL\Expressions\ArithmeticExpression;
use LaravelFreelancerNL\FluentAQL\Expressions\TernaryExpression;

/**
* Trait hasFunctions.
*
* AQL Function API calls.
*/
trait HasOperatorExpressions
{
/**
* Evaluate a condition
*
* @link https://www.arangodb.com/docs/stable/aql/operators.html#ternary-operator
*
* @param $conditions
* @param $then
* @param $else
* @return TernaryExpression
*/
public function if($conditions, $then, $else)
{
return new TernaryExpression($conditions, $then, $else);
}

/**
* Perform an arithmetic operation on two numbers
*
* @link https://www.arangodb.com/docs/stable/aql/operators.html#arithmetic-operators
*
* @param $leftOperand
* @param $operator
* @param $rightOperand
* @return ArithmeticExpression
*/
public function calc($leftOperand, $operator, $rightOperand)
{
return new ArithmeticExpression($leftOperand, $operator, $rightOperand);
}
}
21 changes: 1 addition & 20 deletions src/Clauses/FilterClause.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,8 @@ public function compile(QueryBuilder $queryBuilder): string
{
$this->predicates = $queryBuilder->normalizePredicates($this->predicates);

$compiledPredicates = $this->compilePredicates($queryBuilder, $this->predicates);
$compiledPredicates = $queryBuilder->compilePredicates($this->predicates);

return 'FILTER ' . rtrim($compiledPredicates);
}

protected function compilePredicates(QueryBuilder $queryBuilder, $predicates, $compiledPredicates = '')
{
$currentLogicalOperator = $this->defaultLogicalOperator;
foreach ($predicates as $predicate) {
if ($predicate instanceof PredicateExpression) {
if ($compiledPredicates != '') {
$compiledPredicates .= ' ' . $predicate->logicalOperator . ' ';
}
$compiledPredicates .= $predicate->compile($queryBuilder);
}

if (is_array($predicate)) {
$compiledPredicates = $this->compilePredicates($queryBuilder, $predicate, $compiledPredicates);
}
}

return $compiledPredicates;
}
}
2 changes: 1 addition & 1 deletion src/Clauses/PruneClause.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public function compile(QueryBuilder $queryBuilder): string
{
$this->predicates = $queryBuilder->normalizePredicates($this->predicates);

$compiledPredicates = $this->compilePredicates($queryBuilder, $this->predicates);
$compiledPredicates = $queryBuilder->compilePredicates($this->predicates);

return 'PRUNE ' . rtrim($compiledPredicates);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Clauses/SearchClause.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function compile(QueryBuilder $queryBuilder): string
{
$this->predicates = $queryBuilder->normalizePredicates($this->predicates);

$compiledPredicates = $this->compilePredicates($queryBuilder, $this->predicates);
$compiledPredicates = $queryBuilder->compilePredicates($this->predicates);

return 'SEARCH ' . rtrim($compiledPredicates);
}
Expand Down
75 changes: 75 additions & 0 deletions src/Expressions/ArithmeticExpression.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace LaravelFreelancerNL\FluentAQL\Expressions;

use LaravelFreelancerNL\FluentAQL\Exceptions\ExpressionTypeException;
use LaravelFreelancerNL\FluentAQL\QueryBuilder;

class ArithmeticExpression extends PredicateExpression implements ExpressionInterface
{

protected $calculation = [];

/**
* Create predicate expression.
*
* @param string $leftOperand
* @param string $rightOperand
* @param string $operator
*/
public function __construct($leftOperand, $operator, $rightOperand)
{
$this->calculation = [$leftOperand, $operator, $rightOperand];
}

/**
* Compile calculation.
*
* @param QueryBuilder|null $queryBuilder
* @return string
* @throws \Exception
*/
public function compile(QueryBuilder $queryBuilder = null): string
{
$normalizedCalculation = $this->normalizeCalculation($queryBuilder, $this->calculation);

$leftOperand = $normalizedCalculation['leftOperand']->compile($queryBuilder);
if ($normalizedCalculation['leftOperand'] instanceof ArithmeticExpression) {
$leftOperand = '(' . $leftOperand . ')';
}

$rightOperand = $normalizedCalculation['rightOperand']->compile($queryBuilder);
if ($normalizedCalculation['rightOperand'] instanceof ArithmeticExpression) {
$rightOperand = '(' . $rightOperand . ')';
}

return $leftOperand . ' ' . $normalizedCalculation['arithmeticOperator'] . ' ' . $rightOperand;
return $leftOperand . ' ' . $normalizedCalculation['arithmeticOperator'] . ' ' . $rightOperand;
}

/**
* @param QueryBuilder $queryBuilder
* @param array $calculation
* @return mixed
* @throws ExpressionTypeException
*/
public function normalizeCalculation(QueryBuilder $queryBuilder, array $calculation)
{
$normalizedCalculation = [];

$leftOperand = $queryBuilder->normalizeArgument($calculation[0]);

$arithmeticOperator = '+';
if ($queryBuilder->grammar->isArithmeticOperator($calculation[1])) {
$arithmeticOperator = $calculation[1];
}

$rightOperand = $queryBuilder->normalizeArgument($calculation[2]);

$normalizedCalculation['leftOperand'] = $leftOperand;
$normalizedCalculation['arithmeticOperator'] = $arithmeticOperator;
$normalizedCalculation['rightOperand'] = $rightOperand;

return $normalizedCalculation;
}
}
32 changes: 0 additions & 32 deletions src/Expressions/ConditionalExpression.php

This file was deleted.

29 changes: 0 additions & 29 deletions src/Expressions/MathExpression.php

This file was deleted.

14 changes: 9 additions & 5 deletions src/Expressions/TernaryExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class TernaryExpression extends Expression implements ExpressionInterface
{
/** @var string */
protected $if = '';
protected $predicates = [];

/** @var string */
protected $then = '';
Expand All @@ -18,20 +18,24 @@ class TernaryExpression extends Expression implements ExpressionInterface
/**
* Create predicate expression.
*
* @param string $if
* @param string $predicates
* @param string $then
* @param string $else
*/
public function __construct($if, $then, $else = null)
public function __construct($predicates, $then, $else = null)
{
$this->if = $if;
$this->predicates = $predicates;
$this->then = $then;
$this->else = $else;
}

public function compile(QueryBuilder $queryBuilder): string
{
return $this->if->compile($queryBuilder) .
$this->predicates = $queryBuilder->normalizePredicates($this->predicates);
$this->then = $queryBuilder->normalizeArgument($this->then);
$this->else = $queryBuilder->normalizeArgument($this->else);

return '(' . $queryBuilder->compilePredicates($this->predicates) . ')' .
' ? ' . $this->then->compile($queryBuilder) .
' : ' . $this->else->compile($queryBuilder);
}
Expand Down
4 changes: 4 additions & 0 deletions src/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use LaravelFreelancerNL\FluentAQL\AQL\HasFunctions;
use LaravelFreelancerNL\FluentAQL\AQL\HasGraphClauses;
use LaravelFreelancerNL\FluentAQL\AQL\HasOperatorExpressions;
use LaravelFreelancerNL\FluentAQL\AQL\HasQueryClauses;
use LaravelFreelancerNL\FluentAQL\AQL\HasStatementClauses;
use LaravelFreelancerNL\FluentAQL\AQL\HasSupportCommands;
Expand All @@ -12,6 +13,7 @@
use LaravelFreelancerNL\FluentAQL\Expressions\BindExpression;
use LaravelFreelancerNL\FluentAQL\Expressions\Expression;
use LaravelFreelancerNL\FluentAQL\Expressions\ExpressionInterface;
use LaravelFreelancerNL\FluentAQL\Traits\CompilesPredicates;
use LaravelFreelancerNL\FluentAQL\Traits\NormalizesExpressions;

/**
Expand All @@ -23,10 +25,12 @@
class QueryBuilder
{
use NormalizesExpressions;
use CompilesPredicates;
use HasQueryClauses;
use HasStatementClauses;
use HasGraphClauses;
use HasFunctions;
use HasOperatorExpressions;
use HasSupportCommands;

/**
Expand Down
Loading

0 comments on commit cc76101

Please sign in to comment.