Skip to content

Commit c5fcbad

Browse files
committed
Support questionmark placeholders better
1 parent c387df2 commit c5fcbad

12 files changed

+85
-43
lines changed

src/FakePdoTrait.php

+7-4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public function bindValue($key, $value, $type = \PDO::PARAM_STR) : void
7575
{
7676
if (\is_string($key) && $key[0] !== ':') {
7777
$key = ':' . $key;
78+
} elseif (\is_int($key)) {
79+
// Parameter offsets start at 1, which is weird.
80+
--$key;
7881
}
7982

8083
$this->boundValues[$key] = $value;
@@ -137,7 +140,7 @@ public function universalExecute(?array $params = null)
137140
try {
138141
$raw_result = Processor\SelectProcessor::process(
139142
$this->conn,
140-
new Processor\Scope($this->boundValues),
143+
new Processor\Scope(array_merge($params ?? [], $this->boundValues)),
141144
$parsed_query
142145
);
143146
} catch (Processor\ProcessorException $runtime_exception) {
@@ -185,7 +188,7 @@ function ($row) {
185188
case Query\InsertQuery::class:
186189
$this->affectedRows = Processor\InsertProcessor::process(
187190
$this->conn,
188-
new Processor\Scope($this->boundValues),
191+
new Processor\Scope(array_merge($params ?? [], $this->boundValues)),
189192
$parsed_query
190193
);
191194

@@ -194,7 +197,7 @@ function ($row) {
194197
case Query\UpdateQuery::class:
195198
$this->affectedRows = Processor\UpdateProcessor::process(
196199
$this->conn,
197-
new Processor\Scope($this->boundValues),
200+
new Processor\Scope(array_merge($params ?? [], $this->boundValues)),
198201
$parsed_query
199202
);
200203

@@ -203,7 +206,7 @@ function ($row) {
203206
case Query\DeleteQuery::class:
204207
$this->affectedRows = Processor\DeleteProcessor::process(
205208
$this->conn,
206-
new Processor\Scope($this->boundValues),
209+
new Processor\Scope(array_merge($params ?? [], $this->boundValues)),
207210
$parsed_query
208211
);
209212

src/Parser/ExpressionParser.php

+8-8
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
use Vimeo\MysqlEngine\Query\Expression\InOperatorExpression;
1515
use Vimeo\MysqlEngine\Query\Expression\IntervalOperatorExpression;
1616
use Vimeo\MysqlEngine\Query\Expression\StubExpression;
17-
use Vimeo\MysqlEngine\Query\Expression\ParameterExpression;
18-
use Vimeo\MysqlEngine\Query\Expression\PlaceholderExpression;
17+
use Vimeo\MysqlEngine\Query\Expression\NamedPlaceholderExpression;
18+
use Vimeo\MysqlEngine\Query\Expression\QuestionMarkPlaceholderExpression;
1919
use Vimeo\MysqlEngine\Query\Expression\PositionExpression;
2020
use Vimeo\MysqlEngine\Query\Expression\RowExpression;
2121
use Vimeo\MysqlEngine\Query\Expression\SubqueryExpression;
@@ -228,15 +228,15 @@ public function tokenToExpression(Token $token)
228228
}
229229

230230
if ($token->value === '?') {
231-
if ($token->parameterName === null) {
232-
if ($token->parameterOffset !== null) {
233-
return new PlaceholderExpression($token, $token->parameterOffset);
234-
}
231+
if ($token->parameterOffset !== null) {
232+
return new QuestionMarkPlaceholderExpression($token, $token->parameterOffset);
233+
}
235234

236-
throw new ParserException('? encountered with unknown offset');
235+
if ($token->parameterName !== null) {
236+
return new NamedPlaceholderExpression($token, $token->parameterName);
237237
}
238238

239-
return new ParameterExpression($token, $token->parameterName);
239+
throw new ParserException('? encountered with unknown offset');
240240
}
241241

242242
if ($token->value[0] === '@') {

src/Parser/LimitParser.php

+11-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
use Vimeo\MysqlEngine\TokenType;
55
use Vimeo\MysqlEngine\Query\Expression\ConstantExpression;
6-
use Vimeo\MysqlEngine\Query\Expression\ParameterExpression;
6+
use Vimeo\MysqlEngine\Query\Expression\NamedPlaceholderExpression;
7+
use Vimeo\MysqlEngine\Query\Expression\QuestionMarkPlaceholderExpression;
78
use Vimeo\MysqlEngine\Query\LimitClause;
89

910
final class LimitParser
@@ -45,7 +46,13 @@ public function parse()
4546
if ($next->type === TokenType::NUMERIC_CONSTANT) {
4647
$limit = new ConstantExpression($next);
4748
} elseif ($next->type === TokenType::IDENTIFIER && $next->value === '?') {
48-
$limit = new ParameterExpression($next, $next->parameterName);
49+
if ($next->parameterOffset !== null) {
50+
$limit = new QuestionMarkPlaceholderExpression($next, $next->parameterOffset);
51+
} elseif ($next->parameterName !== null) {
52+
$limit = new NamedPlaceholderExpression($next, $next->parameterName);
53+
} else {
54+
throw new ParserException('? encountered with unknown offset');
55+
}
4956
} else {
5057
throw new ParserException("Expected integer or parameter after OFFSET");
5158
}
@@ -64,7 +71,7 @@ public function parse()
6471
if ($next->type === TokenType::NUMERIC_CONSTANT) {
6572
$offset = new ConstantExpression($next);
6673
} elseif ($next->type === TokenType::IDENTIFIER && $next->value === '?') {
67-
$offset = new ParameterExpression($next, $next->parameterName);
74+
$offset = new NamedPlaceholderExpression($next, $next->parameterName);
6875
} else {
6976
throw new ParserException("Expected integer or parameter after OFFSET");
7077
}
@@ -81,7 +88,7 @@ public function parse()
8188
if ($next->type === TokenType::NUMERIC_CONSTANT) {
8289
$limit = new ConstantExpression($next);
8390
} elseif ($next->type === TokenType::IDENTIFIER && $next->value === '?') {
84-
$limit = new ParameterExpression($next, $next->parameterName);
91+
$limit = new NamedPlaceholderExpression($next, $next->parameterName);
8592
} else {
8693
throw new ParserException("Expected integer or parameter after OFFSET");
8794
}

src/Parser/SQLParser.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,7 @@ private static function buildTokenListFromLexemes(array $tokens)
209209
$out = [];
210210
$count = \count($tokens);
211211

212-
// all parameters in PDO MySQL start at 1. I know, it's weird.
213-
$parameter_offset = 1;
212+
$parameter_offset = 0;
214213

215214
foreach ($tokens as $i => [$token, $start]) {
216215
$trimmed_token = \trim($token);

src/Processor/Expression/Evaluator.php

+6-10
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,11 @@ public static function evaluate(
9090
case \Vimeo\MysqlEngine\Query\Expression\VariableExpression::class:
9191
return VariableEvaluator::evaluate($scope, $expr);
9292

93-
case \Vimeo\MysqlEngine\Query\Expression\ParameterExpression::class:
94-
return ParameterEvaluator::evaluate($scope, $expr);
93+
case \Vimeo\MysqlEngine\Query\Expression\NamedPlaceholderExpression::class:
94+
return NamedPlaceholderEvaluator::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');
96+
case \Vimeo\MysqlEngine\Query\Expression\QuestionMarkPlaceholderExpression::class:
97+
return QuestionMarkPlaceholderEvaluator::evaluate($scope, $expr);
10298

10399
default:
104100
throw new ProcessorException('Unsupported expression ' . get_class($expr));
@@ -231,7 +227,7 @@ public static function getColumnSchema(
231227
// it defaults to string
232228
return new Column\Varchar(10);
233229

234-
case \Vimeo\MysqlEngine\Query\Expression\ParameterExpression::class:
230+
case \Vimeo\MysqlEngine\Query\Expression\NamedPlaceholderExpression::class:
235231
if (\array_key_exists($expr->parameterName, $scope->parameters)) {
236232
return self::getColumnTypeFromValue($expr, $scope->parameters[$expr->parameterName]);
237233
}
@@ -240,7 +236,7 @@ public static function getColumnSchema(
240236
// it defaults to string
241237
return new Column\Varchar(10);
242238

243-
case \Vimeo\MysqlEngine\Query\Expression\PlaceholderExpression::class:
239+
case \Vimeo\MysqlEngine\Query\Expression\QuestionMarkPlaceholderExpression::class:
244240
if (\array_key_exists($expr->offset, $scope->parameters)) {
245241
return self::getColumnTypeFromValue($expr, $scope->parameters[$expr->offset]);
246242
}

src/Processor/Expression/ParameterEvaluator.php renamed to src/Processor/Expression/NamedPlaceholderEvaluator.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
namespace Vimeo\MysqlEngine\Processor\Expression;
33

44
use Vimeo\MysqlEngine\Processor\ProcessorException;
5-
use Vimeo\MysqlEngine\Query\Expression\ParameterExpression;
5+
use Vimeo\MysqlEngine\Query\Expression\NamedPlaceholderExpression;
66
use Vimeo\MysqlEngine\Processor\Scope;
77

8-
final class ParameterEvaluator
8+
final class NamedPlaceholderEvaluator
99
{
1010
/**
1111
* @return mixed
1212
*/
13-
public static function evaluate(Scope $scope, ParameterExpression $expr)
13+
public static function evaluate(Scope $scope, NamedPlaceholderExpression $expr)
1414
{
1515
if (\array_key_exists($expr->parameterName, $scope->parameters)) {
1616
return $scope->parameters[$expr->parameterName];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
namespace Vimeo\MysqlEngine\Processor\Expression;
3+
4+
use Vimeo\MysqlEngine\Processor\ProcessorException;
5+
use Vimeo\MysqlEngine\Query\Expression\QuestionMarkPlaceholderExpression;
6+
use Vimeo\MysqlEngine\Processor\Scope;
7+
8+
final class QuestionMarkPlaceholderEvaluator
9+
{
10+
/**
11+
* @return mixed
12+
*/
13+
public static function evaluate(Scope $scope, QuestionMarkPlaceholderExpression $expr)
14+
{
15+
if (\array_key_exists($expr->offset, $scope->parameters)) {
16+
return $scope->parameters[$expr->offset];
17+
}
18+
19+
throw new ProcessorException('Parameter offset ' . $expr->offset . ' out of range');
20+
}
21+
}

src/Processor/Processor.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Vimeo\MysqlEngine\Query\Expression\BinaryOperatorExpression;
66
use Vimeo\MysqlEngine\Query\Expression\ColumnExpression;
77
use Vimeo\MysqlEngine\Query\Expression\ConstantExpression;
8+
use Vimeo\MysqlEngine\Query\Expression\NamedPlaceholderExpression;
89
use Vimeo\MysqlEngine\Query\LimitClause;
910
use Vimeo\MysqlEngine\Schema\Column\IntegerColumn;
1011
use Vimeo\MysqlEngine\Schema\TableDefinition;
@@ -109,14 +110,18 @@ protected static function applyLimit(?LimitClause $limit, Scope $scope, QueryRes
109110
$offset = 0;
110111
} elseif ($limit->offset instanceof ConstantExpression) {
111112
$offset = (int) $limit->offset->value;
113+
} elseif ($limit->offset instanceof NamedPlaceholderExpression) {
114+
$offset = (int) Expression\NamedPlaceholderEvaluator::evaluate($scope, $limit->offset);
112115
} else {
113-
$offset = (int) Expression\ParameterEvaluator::evaluate($scope, $limit->offset);
116+
$offset = (int) Expression\QuestionMarkPlaceholderEvaluator::evaluate($scope, $limit->offset);
114117
}
115118

116119
if ($limit->rowcount instanceof ConstantExpression) {
117120
$rowcount = (int) $limit->rowcount->value;
121+
} elseif ($limit->rowcount instanceof NamedPlaceholderExpression) {
122+
$rowcount = (int) Expression\NamedPlaceholderEvaluator::evaluate($scope, $limit->rowcount);
118123
} else {
119-
$rowcount = (int) Expression\ParameterEvaluator::evaluate($scope, $limit->rowcount);
124+
$rowcount = (int) Expression\QuestionMarkPlaceholderEvaluator::evaluate($scope, $limit->rowcount);
120125
}
121126

122127
return new QueryResult(

src/Query/Expression/ParameterExpression.php renamed to src/Query/Expression/NamedPlaceholderExpression.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use Vimeo\MysqlEngine\Parser\Token;
55
use Vimeo\MysqlEngine\TokenType;
66

7-
final class ParameterExpression extends Expression
7+
final class NamedPlaceholderExpression extends Expression
88
{
99
/**
1010
* @var string

src/Query/Expression/PlaceholderExpression.php renamed to src/Query/Expression/QuestionMarkPlaceholderExpression.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use Vimeo\MysqlEngine\Parser\Token;
55
use Vimeo\MysqlEngine\TokenType;
66

7-
final class PlaceholderExpression extends Expression
7+
final class QuestionMarkPlaceholderExpression extends Expression
88
{
99
/**
1010
* @var int

src/Query/LimitClause.php

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33

44
use Vimeo\MysqlEngine\Query\Expression\ConstantExpression;
55
use Vimeo\MysqlEngine\Query\Expression\Expression;
6-
use Vimeo\MysqlEngine\Query\Expression\ParameterExpression;
6+
use Vimeo\MysqlEngine\Query\Expression\NamedPlaceholderExpression;
7+
use Vimeo\MysqlEngine\Query\Expression\QuestionMarkPlaceholderExpression;
78

89
final class LimitClause
910
{
10-
/** @var ConstantExpression|ParameterExpression|null */
11+
/** @var ConstantExpression|NamedPlaceholderExpression|QuestionMarkPlaceholderExpression|null */
1112
public $offset;
1213

13-
/** @var ConstantExpression|ParameterExpression */
14+
/** @var ConstantExpression|NamedPlaceholderExpression|QuestionMarkPlaceholderExpression */
1415
public $rowcount;
1516

1617
/**
17-
* @param ConstantExpression|ParameterExpression $offset
18-
* @param ConstantExpression|ParameterExpression $rowcount
18+
* @param ConstantExpression|NamedPlaceholderExpression|QuestionMarkPlaceholderExpression $offset
19+
* @param ConstantExpression|NamedPlaceholderExpression|QuestionMarkPlaceholderExpression $rowcount
1920
*/
2021
public function __construct(?Expression $offset, Expression $rowcount)
2122
{

tests/EndToEndTest.php

+13-3
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,24 @@ public function testPlaceholders()
7070
{
7171
$pdo = self::getConnectionToFullDB(false);
7272

73-
$query = $pdo->prepare("SELECT id FROM `video_game_characters` WHERE `id` > ? ORDER BY `id` ASC");
73+
$query = $pdo->prepare("SELECT id FROM `video_game_characters` WHERE `id` > ? ORDER BY `id` ASC LIMIT ?");
7474
$query->bindValue(1, 14);
75+
$query->bindValue(2, 1);
7576
$query->execute();
7677

7778
$this->assertSame(
7879
[
79-
['id' => 15],
80-
['id' => 16]
80+
['id' => 15]
81+
],
82+
$query->fetchAll(\PDO::FETCH_ASSOC)
83+
);
84+
85+
$query = $pdo->prepare("SELECT id FROM `video_game_characters` WHERE `id` > ? ORDER BY `id` ASC LIMIT ?");
86+
$query->execute([14, 1]);
87+
88+
$this->assertSame(
89+
[
90+
['id' => 15]
8191
],
8292
$query->fetchAll(\PDO::FETCH_ASSOC)
8393
);

0 commit comments

Comments
 (0)