Skip to content

Commit 3c7ee57

Browse files
committed
Support placeholders
1 parent d578f99 commit 3c7ee57

File tree

7 files changed

+105
-21
lines changed

7 files changed

+105
-21
lines changed

src/Parser/ExpressionParser.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Vimeo\MysqlEngine\Query\Expression\IntervalOperatorExpression;
1616
use Vimeo\MysqlEngine\Query\Expression\StubExpression;
1717
use Vimeo\MysqlEngine\Query\Expression\ParameterExpression;
18+
use Vimeo\MysqlEngine\Query\Expression\PlaceholderExpression;
1819
use Vimeo\MysqlEngine\Query\Expression\PositionExpression;
1920
use Vimeo\MysqlEngine\Query\Expression\RowExpression;
2021
use Vimeo\MysqlEngine\Query\Expression\SubqueryExpression;
@@ -228,6 +229,10 @@ public function tokenToExpression(Token $token)
228229

229230
if ($token->value === '?') {
230231
if ($token->parameterName === null) {
232+
if ($token->parameterOffset !== null) {
233+
return new PlaceholderExpression($token, $token->parameterOffset);
234+
}
235+
231236
throw new ParserException('? encountered with unknown offset');
232237
}
233238

src/Parser/SQLParser.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ private static function buildTokenListFromLexemes(array $tokens)
208208
{
209209
$out = [];
210210
$count = \count($tokens);
211+
212+
// all parameters in PDO MySQL start at 1. I know, it's weird.
213+
$parameter_offset = 1;
214+
211215
foreach ($tokens as $i => [$token, $start]) {
212216
$trimmed_token = \trim($token);
213217

@@ -342,7 +346,12 @@ private static function buildTokenListFromLexemes(array $tokens)
342346
}
343347
}
344348

345-
if ($token_upper === 'NULL') {
349+
if ($token_upper === '?') {
350+
$token_obj = new Token(TokenType::IDENTIFIER, $token, $token, $start);
351+
$token_obj->parameterOffset = $parameter_offset;
352+
$out[] = $token_obj;
353+
$parameter_offset++;
354+
} elseif ($token_upper === 'NULL') {
346355
$out[] = new Token(TokenType::NULL_CONSTANT, $token, $token, $start);
347356
} elseif ($token_upper === 'TRUE') {
348357
$out[] = new Token(TokenType::NUMERIC_CONSTANT, '1', $token, $start);

src/Parser/Token.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ class Token
3030
*/
3131
public $parameterName;
3232

33+
/**
34+
* @var ?int
35+
*/
36+
public $parameterOffset;
37+
3338
/**
3439
* @param TokenType::* $type
3540
*/

src/Processor/Expression/Evaluator.php

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ public static function evaluate(
9393
case \Vimeo\MysqlEngine\Query\Expression\ParameterExpression::class:
9494
return ParameterEvaluator::evaluate($scope, $expr);
9595

96+
case \Vimeo\MysqlEngine\Query\Expression\PlaceholderExpression::class:
97+
if (\array_key_exists($expr->offset, $scope->parameters)) {
98+
return $scope->parameters[$expr->offset];
99+
}
100+
101+
throw new ProcessorException('Parameter offset ' . $expr->offset . ' out of range');
102+
96103
default:
97104
throw new ProcessorException('Unsupported expression ' . get_class($expr));
98105
}
@@ -226,25 +233,7 @@ public static function getColumnSchema(
226233

227234
case \Vimeo\MysqlEngine\Query\Expression\ParameterExpression::class:
228235
if (\array_key_exists($expr->parameterName, $scope->parameters)) {
229-
$value = $scope->parameters[$expr->parameterName];
230-
231-
if (\is_int($value)) {
232-
return $expr->column = new Column\IntColumn(false, 10);
233-
}
234-
235-
if (\is_float($value)) {
236-
return $expr->column = new Column\FloatColumn(10, 2);
237-
}
238-
239-
if (\is_bool($value)) {
240-
return $expr->column = new Column\TinyInt(true, 1);
241-
}
242-
243-
if ($value === null) {
244-
return $expr->column = new Column\NullColumn();
245-
}
246-
247-
return new Column\Varchar(10);
236+
return self::getColumnTypeFromValue($expr, $scope->parameters[$expr->parameterName]);
248237
}
249238

250239
// When MySQL can't figure out a variable column's type
@@ -255,6 +244,32 @@ public static function getColumnSchema(
255244
return $expr->column = new Column\Varchar(10);
256245
}
257246

247+
/**
248+
* @param mixed $value
249+
*/
250+
private static function getColumnTypeFromValue(
251+
\Vimeo\MysqlEngine\Query\Expression\Expression $expr,
252+
$value
253+
) : Column {
254+
if (\is_int($value)) {
255+
return $expr->column = new Column\IntColumn(false, 10);
256+
}
257+
258+
if (\is_float($value)) {
259+
return $expr->column = new Column\FloatColumn(10, 2);
260+
}
261+
262+
if (\is_bool($value)) {
263+
return $expr->column = new Column\TinyInt(true, 1);
264+
}
265+
266+
if ($value === null) {
267+
return $expr->column = new Column\NullColumn();
268+
}
269+
270+
return new Column\Varchar(10);
271+
}
272+
258273
/**
259274
* @param list<Column> $types
260275
*/

src/Processor/Scope.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Scope
99
public $variables = [];
1010

1111
/**
12-
* @var array<string, mixed>
12+
* @var array<string|int, mixed>
1313
*/
1414
public $parameters = [];
1515

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
namespace Vimeo\MysqlEngine\Query\Expression;
3+
4+
use Vimeo\MysqlEngine\Parser\Token;
5+
use Vimeo\MysqlEngine\TokenType;
6+
7+
final class PlaceholderExpression extends Expression
8+
{
9+
/**
10+
* @var int
11+
*/
12+
public $offset;
13+
14+
/**
15+
* @param Token $token
16+
*/
17+
public function __construct(Token $token, int $offset)
18+
{
19+
$this->type = $token->type;
20+
$this->precedence = 0;
21+
$this->offset = $offset;
22+
$this->name = '?';
23+
$this->start = $token->start;
24+
}
25+
26+
/**
27+
* @return bool
28+
*/
29+
public function isWellFormed()
30+
{
31+
return true;
32+
}
33+
}

tests/EndToEndTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@ public function testSelectFetchAssocConverted()
6666
);
6767
}
6868

69+
public function testPlaceholders()
70+
{
71+
$pdo = self::getConnectionToFullDB(false);
72+
73+
$query = $pdo->prepare("SELECT id FROM `video_game_characters` WHERE `id` > ? ORDER BY `id` ASC");
74+
$query->bindValue(1, 14);
75+
$query->execute();
76+
77+
$this->assertSame(
78+
[
79+
['id' => 15],
80+
['id' => 16]
81+
],
82+
$query->fetchAll(\PDO::FETCH_ASSOC)
83+
);
84+
}
85+
6986
public function testSumEmptySet()
7087
{
7188
$pdo = self::getConnectionToFullDB(false);

0 commit comments

Comments
 (0)