Skip to content

Commit 9931438

Browse files
committed
Fixes a bug where you cannot use a runtime operator in a SQL query
1 parent 42fc542 commit 9931438

File tree

8 files changed

+144
-28
lines changed

8 files changed

+144
-28
lines changed

src/Compiler/Compiler.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use RulerZ\Executor\Executor;
66
use RulerZ\Parser\Parser;
7+
use RulerZ\Target\Operators\CompileTimeOperator;
78

89
class Compiler
910
{
@@ -63,6 +64,8 @@ protected function compileToSource($rule, CompilationTarget $compilationTarget,
6364
}
6465

6566
$commentedRule = str_replace(PHP_EOL, PHP_EOL.' // ', $rule);
67+
$compiledRule = $executorModel->getCompiledRule();
68+
$escapedRule = is_string($compiledRule) ? $compiledRule : $compiledRule->format(false);
6669

6770
return <<<EXECUTOR
6871
namespace RulerZ\Compiled\Executor;
@@ -78,7 +81,7 @@ class {$context['executor_classname']} implements Executor
7881
// $commentedRule
7982
protected function execute(\$target, array \$operators, array \$parameters)
8083
{
81-
return {$executorModel->getCompiledRule()};
84+
return {$escapedRule};
8285
}
8386
}
8487
EXECUTOR;

src/Target/GenericSqlVisitor.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use RulerZ\Compiler\Context;
88
use RulerZ\Exception\OperatorNotFoundException;
99
use RulerZ\Model;
10+
use RulerZ\Target\Operators\CompileTimeOperator;
1011
use RulerZ\Target\Operators\Definitions as OperatorsDefinitions;
1112

1213
/**
@@ -46,7 +47,13 @@ public function visitModel(AST\Model $element, &$handle = null, $eldnah = null)
4647
{
4748
$sql = parent::visitModel($element, $handle, $eldnah);
4849

49-
return '"'.$sql.'"';
50+
if (is_string($sql)) {
51+
return '"' . $sql . '"';
52+
} elseif ($sql instanceof CompileTimeOperator) {
53+
return '"' . $sql->format(true) . '"';
54+
} else {
55+
return $sql->format(false);
56+
}
5057
}
5158

5259
/**

src/Target/GenericVisitor.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use RulerZ\Compiler\RuleVisitor;
99
use RulerZ\Exception\OperatorNotFoundException;
1010
use RulerZ\Model;
11+
use RulerZ\Target\Operators\CompileTimeOperator;
12+
use RulerZ\Target\Operators\RuntimeOperator;
1113
use RulerZ\Target\Operators\Definitions as OperatorsDefinitions;
1214

1315
/**
@@ -114,12 +116,12 @@ public function visitOperator(AST\Operator $element, &$handle = null, $eldnah =
114116
if ($this->operators->hasInlineOperator($operatorName)) {
115117
$callable = $this->operators->getInlineOperator($operatorName);
116118

117-
return call_user_func_array($callable, $arguments);
119+
return new CompileTimeOperator(
120+
call_user_func_array($callable, $arguments)
121+
);
118122
}
119123

120-
$inlinedArguments = empty($arguments) ? '' : ', '.implode(', ', $arguments);
121-
122124
// or defer it.
123-
return sprintf('call_user_func($operators["%s"]%s)', $operatorName, $inlinedArguments);
125+
return new RuntimeOperator(sprintf('$operators["%s"]', $operatorName), $arguments);
124126
}
125127
}

src/Target/Native/NativeOperators.php

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace RulerZ\Target\Native;
44

55
use RulerZ\Target\Operators\Definitions;
6+
use RulerZ\Target\Operators\OperatorTools;
67

78
class NativeOperators
89
{
@@ -13,37 +14,37 @@ public static function create(Definitions $customOperators)
1314
{
1415
$defaultInlineOperators = [
1516
'and' => function ($a, $b) {
16-
return sprintf('(%s && %s)', $a, $b);
17+
return sprintf('(%s)', OperatorTools::inlineMixedInstructions([$a, $b], '&&'));
1718
},
1819
'or' => function ($a, $b) {
19-
return sprintf('(%s || %s)', $a, $b);
20+
return sprintf('(%s)', OperatorTools::inlineMixedInstructions([$a, $b], '||'));
2021
},
2122
'not' => function ($a) {
22-
return sprintf('!(%s)', $a);
23+
return sprintf('!(%s)', OperatorTools::inlineMixedInstructions([$a]));
2324
},
2425
'=' => function ($a, $b) {
25-
return sprintf('%s == %s', $a, $b);
26+
return OperatorTools::inlineMixedInstructions([$a, $b], '==');
2627
},
2728
'is' => function ($a, $b) {
28-
return sprintf('%s === %s', $a, $b);
29+
return OperatorTools::inlineMixedInstructions([$a, $b], '===');
2930
},
3031
'!=' => function ($a, $b) {
31-
return sprintf('%s != %s', $a, $b);
32+
return OperatorTools::inlineMixedInstructions([$a, $b], '!=');
3233
},
3334
'>' => function ($a, $b) {
34-
return sprintf('%s > %s', $a, $b);
35+
return OperatorTools::inlineMixedInstructions([$a, $b], '>');
3536
},
3637
'>=' => function ($a, $b) {
37-
return sprintf('%s >= %s', $a, $b);
38+
return OperatorTools::inlineMixedInstructions([$a, $b], '>=');
3839
},
3940
'<' => function ($a, $b) {
40-
return sprintf('%s < %s', $a, $b);
41+
return OperatorTools::inlineMixedInstructions([$a, $b], '<');
4142
},
4243
'<=' => function ($a, $b) {
43-
return sprintf('%s <= %s', $a, $b);
44+
return OperatorTools::inlineMixedInstructions([$a, $b], '<=');
4445
},
4546
'in' => function ($a, $b) {
46-
return sprintf('in_array(%s, %s)', $a, $b);
47+
return sprintf('in_array(%s)', OperatorTools::inlineMixedInstructions([$a, $b], ','));
4748
},
4849
];
4950

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace RulerZ\Target\Operators;
4+
5+
class CompileTimeOperator
6+
{
7+
/**
8+
* @var string
9+
*/
10+
private $compiledOperator;
11+
12+
public function __construct($compiled)
13+
{
14+
$this->compiledOperator = $compiled;
15+
}
16+
17+
public function format($shouldBreakString)
18+
{
19+
return $this->compiledOperator;
20+
}
21+
}

src/Target/Operators/GenericSqlDefinitions.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,45 @@ public static function create(Definitions $customOperators)
1111
{
1212
$defaultInlineOperators = [
1313
'and' => function ($a, $b) {
14-
return sprintf('(%s AND %s)', $a, $b);
14+
return sprintf('(%s)', OperatorTools::inlineMixedInstructions([$a, $b], 'AND'));
1515
},
1616
'or' => function ($a, $b) {
17-
return sprintf('(%s OR %s)', $a, $b);
17+
return sprintf('(%s)', OperatorTools::inlineMixedInstructions([$a, $b], 'OR'));
1818
},
1919
'not' => function ($a) {
20-
return sprintf('NOT (%s)', $a);
20+
return sprintf('NOT (%s)', OperatorTools::inlineMixedInstructions([$a]));
2121
},
2222
'=' => function ($a, $b) {
23-
return sprintf('%s = %s', $a, $b);
23+
return OperatorTools::inlineMixedInstructions([$a, $b], '=');
2424
},
2525
'!=' => function ($a, $b) {
26-
return sprintf('%s != %s', $a, $b);
26+
return OperatorTools::inlineMixedInstructions([$a, $b], '!=');
2727
},
2828
'>' => function ($a, $b) {
29-
return sprintf('%s > %s', $a, $b);
29+
return OperatorTools::inlineMixedInstructions([$a, $b], '>');
3030
},
3131
'>=' => function ($a, $b) {
32-
return sprintf('%s >= %s', $a, $b);
32+
return OperatorTools::inlineMixedInstructions([$a, $b], '>=');
3333
},
3434
'<' => function ($a, $b) {
35-
return sprintf('%s < %s', $a, $b);
35+
return OperatorTools::inlineMixedInstructions([$a, $b], '<');
3636
},
3737
'<=' => function ($a, $b) {
38-
return sprintf('%s <= %s', $a, $b);
38+
return OperatorTools::inlineMixedInstructions([$a, $b], '<=');
3939
},
4040
'in' => function ($a, $b) {
41-
return sprintf('%s IN %s', $a, $b[0] === '(' ? $b : '('.$b.')');
41+
if ($b[0] === '(') {
42+
return OperatorTools::inlineMixedInstructions([$a, $b], 'IN');
43+
} else {
44+
return sprintf(
45+
'%s IN (%s)',
46+
OperatorTools::inlineMixedInstructions([$a]),
47+
OperatorTools::inlineMixedInstructions([$b])
48+
);
49+
}
4250
},
4351
'like' => function ($a, $b) {
44-
return sprintf('%s LIKE %s', $a, $b);
52+
return OperatorTools::inlineMixedInstructions([$a, $b], 'LIKE');
4553
},
4654
];
4755

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace RulerZ\Target\Operators;
4+
5+
class OperatorTools
6+
{
7+
public static function inlineMixedInstructions(array $instructions, $operator = null)
8+
{
9+
$elements = [];
10+
11+
foreach ($instructions as $instruction) {
12+
if ($instruction instanceof RuntimeOperator) {
13+
$elements[] = $instruction->format(false);
14+
} else if ($instruction instanceof CompileTimeOperator) {
15+
$elements[] = sprintf('%s', $instruction->format(false));
16+
} else {
17+
$elements[] = sprintf('%s', $instruction);
18+
}
19+
}
20+
21+
if (null === $operator) {
22+
return join('', $elements);
23+
} else {
24+
return join(' '.$operator.' ', $elements);
25+
}
26+
}
27+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace RulerZ\Target\Operators;
4+
5+
class RuntimeOperator
6+
{
7+
/**
8+
* @var string
9+
*/
10+
private $callable;
11+
12+
/**
13+
* @var array
14+
*/
15+
private $arguments;
16+
17+
public function __construct($callable, $arguments)
18+
{
19+
$this->callable = $callable;
20+
$this->arguments = $arguments;
21+
}
22+
23+
public function format($shouldBreakString)
24+
{
25+
$formattedArguments = join(',', array_map(function ($argument) {
26+
if ('$' === $argument[0]) {
27+
return $argument;
28+
} else {
29+
return sprintf('"%s"', $argument);
30+
}
31+
}, $this->arguments));
32+
33+
if (true === $shouldBreakString) {
34+
return sprintf(
35+
'".call_user_func_array(%s, [%s])."',
36+
$this->callable,
37+
$formattedArguments
38+
);
39+
} else {
40+
return sprintf(
41+
'call_user_func_array(%s, [%s])',
42+
$this->callable,
43+
$formattedArguments
44+
);
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)