Skip to content

Commit 6faf2cd

Browse files
committed
Implement new Argument and ArgumentType
1 parent 5a3c3ef commit 6faf2cd

21 files changed

Lines changed: 399 additions & 234 deletions

‎src/Sql/AbstractSql.php‎

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use PhpDb\Adapter\ParameterContainer;
1010
use PhpDb\Adapter\Platform\PlatformInterface;
1111
use PhpDb\Adapter\Platform\Sql92 as DefaultAdapterPlatform;
12+
use PhpDb\Sql\Argument\ArgumentInterface;
13+
use PhpDb\Sql\Argument\ArgumentType;
1214
use PhpDb\Sql\Platform\PlatformDecoratorInterface;
1315
use ValueError;
1416

@@ -150,7 +152,7 @@ protected function processExpression(
150152
}
151153

152154
protected function processExpressionValue(
153-
Argument $argument,
155+
ArgumentInterface $argument,
154156
int &$expressionParamIndex,
155157
string $namedParameterPrefix,
156158
int $vIndex,
@@ -167,8 +169,9 @@ protected function processExpressionValue(
167169
$driver,
168170
$parameterContainer
169171
),
170-
ArgumentType::Identifier => $platform->quoteIdentifierInFragment($argument->getValueAsString()),
171-
ArgumentType::Literal => $argument->getValueAsString(),
172+
ArgumentType::Identifier => $platform->quoteIdentifierInFragment((string) $argument->getValue()),
173+
ArgumentType::Identifiers => $this->processIdentifiersArgument($argument, $platform),
174+
ArgumentType::Literal => (string) $argument->getValue(),
172175
ArgumentType::Value => $parameterContainer instanceof ParameterContainer ?
173176
$this->processExpressionParameterName(
174177
$argument->getValue(),
@@ -177,12 +180,20 @@ protected function processExpressionValue(
177180
$driver,
178181
$parameterContainer
179182
) :
180-
$platform->quoteValue($argument->getValueAsString())
183+
$platform->quoteValue((string) $argument->getValue()),
184+
ArgumentType::Values => $this->processValuesArgument(
185+
$argument,
186+
$expressionParamIndex,
187+
$namedParameterPrefix,
188+
$platform,
189+
$driver,
190+
$parameterContainer
191+
),
181192
};
182193
}
183194

184195
protected function processExpressionOrSelect(
185-
Argument $argument,
196+
ArgumentInterface $argument,
186197
string $namedParameterPrefix,
187198
int $vIndex,
188199
PlatformInterface $platform,
@@ -206,6 +217,48 @@ protected function processExpressionOrSelect(
206217
};
207218
}
208219

220+
protected function processValuesArgument(
221+
ArgumentInterface $argument,
222+
int &$expressionParamIndex,
223+
string $namedParameterPrefix,
224+
PlatformInterface $platform,
225+
?DriverInterface $driver = null,
226+
?ParameterContainer $parameterContainer = null
227+
): string {
228+
$values = $argument->getValue();
229+
$processedValues = [];
230+
231+
foreach ($values as $value) {
232+
if ($parameterContainer instanceof ParameterContainer) {
233+
$processedValues[] = $this->processExpressionParameterName(
234+
$value,
235+
$namedParameterPrefix,
236+
$expressionParamIndex,
237+
$driver,
238+
$parameterContainer
239+
);
240+
} else {
241+
$processedValues[] = $platform->quoteValue((string) $value);
242+
}
243+
}
244+
245+
return implode(', ', $processedValues);
246+
}
247+
248+
protected function processIdentifiersArgument(
249+
ArgumentInterface $argument,
250+
PlatformInterface $platform
251+
): string {
252+
$identifiers = $argument->getValue();
253+
$processedIdentifiers = [];
254+
255+
foreach ($identifiers as $identifier) {
256+
$processedIdentifiers[] = $platform->quoteIdentifierInFragment($identifier);
257+
}
258+
259+
return implode(', ', $processedIdentifiers);
260+
}
261+
209262
protected function processExpressionParameterName(
210263
int|float|string|bool $value,
211264
string $namedParameterPrefix,

‎src/Sql/Argument/Argument.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ public static function identifier(string $identifier): IdentifierArgument
2727
return new IdentifierArgument($identifier);
2828
}
2929

30+
/**
31+
* @param list<string> $identifiers
32+
*/
33+
public static function identifiers(array $identifiers): IdentifiersArgument
34+
{
35+
return new IdentifiersArgument($identifiers);
36+
}
37+
3038
public static function literal(string $literal): LiteralArgument
3139
{
3240
return new LiteralArgument($literal);

‎src/Sql/Argument/ArgumentType.php‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99
*
1010
* Specifies how values should be treated during SQL generation:
1111
* - Identifier: Column names, table names, and other identifiers that require quoting
12+
* - Identifiers: Multiple identifiers for multi-column clauses (e.g., multi-column IN predicates)
1213
* - Value: Data values that should be parameterized or escaped
14+
* - Values: Multiple values for IN clauses and similar constructs
1315
* - Literal: Raw SQL fragments that are inserted as-is without modification
1416
* - Select: Subquery objects (Expression or SqlInterface instances)
1517
*/
1618
enum ArgumentType: string
1719
{
18-
case Identifier = 'identifier';
19-
case Value = 'value';
20-
case Literal = 'literal';
21-
case Select = 'select';
20+
case Identifier = IdentifierArgument::class;
21+
case Identifiers = IdentifiersArgument::class;
22+
case Value = ValueArgument::class;
23+
case Values = ValuesArgument::class;
24+
case Literal = LiteralArgument::class;
25+
case Select = SelectArgument::class;
2226
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDb\Sql\Argument;
6+
7+
use function array_values;
8+
9+
/**
10+
* Represents multiple SQL identifiers (column names for multi-column clauses).
11+
*
12+
* Used for multi-column IN predicates like (col1, col2) IN (SELECT ...).
13+
* Each identifier will be quoted appropriately by the platform driver.
14+
*/
15+
final readonly class IdentifiersArgument implements ArgumentInterface
16+
{
17+
/** @var list<string> */
18+
private array $identifiers;
19+
20+
/**
21+
* @param list<string> $identifiers
22+
*/
23+
public function __construct(array $identifiers)
24+
{
25+
$this->identifiers = array_values($identifiers);
26+
}
27+
28+
public function getType(): ArgumentType
29+
{
30+
return ArgumentType::Identifiers;
31+
}
32+
33+
/**
34+
* @return list<string>
35+
*/
36+
public function getValue(): array
37+
{
38+
return $this->identifiers;
39+
}
40+
}

‎src/Sql/Expression.php‎

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55
namespace PhpDb\Sql;
66

77
use Override;
8+
use PhpDb\Sql\Argument\Argument;
9+
use PhpDb\Sql\Argument\ArgumentInterface;
10+
use PhpDb\Sql\Argument\ArgumentType as NewArgumentType;
11+
use PhpDb\Sql\ArgumentType as OldArgumentType;
812

913
use function array_slice;
1014
use function array_unique;
1115
use function count;
16+
use function current;
1217
use function func_get_args;
1318
use function func_num_args;
1419
use function is_array;
20+
use function key;
1521
use function preg_match_all;
1622
use function str_ireplace;
1723
use function str_replace;
@@ -25,15 +31,15 @@ class Expression extends AbstractExpression
2531

2632
protected string $expression = '';
2733

28-
/** @var Argument[] */
34+
/** @var ArgumentInterface[] */
2935
protected array $parameters = [];
3036

3137
/**
3238
* @todo Update documentation to show how parameters can be specifically typed
3339
*/
3440
public function __construct(
3541
string $expression = '',
36-
null|bool|string|float|int|array|Argument|ExpressionInterface $parameters = []
42+
null|bool|string|float|int|array|ArgumentInterface|ExpressionInterface $parameters = []
3743
) {
3844
if ($expression !== '') {
3945
$this->setExpression($expression);
@@ -72,7 +78,7 @@ public function getExpression(): string
7278
/**
7379
* @throws Exception\InvalidArgumentException
7480
*/
75-
public function setParameters(null|bool|string|float|int|array|ExpressionInterface|Argument $parameters = []): self
81+
public function setParameters(null|bool|string|float|int|array|ExpressionInterface|ArgumentInterface $parameters = []): self
7682
{
7783
if (func_num_args() > 1) {
7884
/**
@@ -87,12 +93,43 @@ public function setParameters(null|bool|string|float|int|array|ExpressionInterfa
8793
$parameters = [$parameters];
8894
}
8995

90-
/** @var null|bool|string|float|int|array|ExpressionInterface|Argument $parameter */
96+
/** @var null|bool|string|float|int|array|ExpressionInterface|ArgumentInterface $parameter */
9197
foreach ($parameters as $key => $parameter) {
92-
if ($parameter instanceof ArgumentType) {
93-
$parameter = new Argument($key, $parameter);
94-
} elseif (! $parameter instanceof Argument) {
95-
$parameter = new Argument($parameter);
98+
if ($parameter instanceof ArgumentInterface) {
99+
// Already an ArgumentInterface, use as-is
100+
} elseif ($parameter instanceof NewArgumentType || $parameter instanceof OldArgumentType) {
101+
// Legacy support: ['name' => ArgumentType::Identifier] syntax
102+
// Handle both old and new ArgumentType enums
103+
$parameter = match (true) {
104+
$parameter === NewArgumentType::Identifier || $parameter === OldArgumentType::Identifier => Argument::identifier($key),
105+
$parameter === NewArgumentType::Literal || $parameter === OldArgumentType::Literal => Argument::literal($key),
106+
default => Argument::value($key),
107+
};
108+
} elseif (is_array($parameter)) {
109+
// Legacy support: [['name' => ArgumentType::Identifier]] syntax
110+
// Check if this is a single-element array with ArgumentType as value
111+
if (count($parameter) === 1) {
112+
$arrayKey = key($parameter);
113+
$arrayValue = current($parameter);
114+
if ($arrayValue instanceof NewArgumentType || $arrayValue instanceof OldArgumentType) {
115+
$parameter = match (true) {
116+
$arrayValue === NewArgumentType::Identifier || $arrayValue === OldArgumentType::Identifier => Argument::identifier($arrayKey),
117+
$arrayValue === NewArgumentType::Literal || $arrayValue === OldArgumentType::Literal => Argument::literal($arrayKey),
118+
default => Argument::value($arrayKey),
119+
};
120+
} else {
121+
// Array of values
122+
$parameter = Argument::values($parameter);
123+
}
124+
} else {
125+
// Array of values
126+
$parameter = Argument::values($parameter);
127+
}
128+
} elseif ($parameter instanceof ExpressionInterface) {
129+
// Handle expressions/subqueries
130+
$parameter = Argument::select($parameter);
131+
} else {
132+
$parameter = Argument::value($parameter);
96133
}
97134

98135
$this->parameters[] = $parameter;

‎src/Sql/Predicate/In.php‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PhpDb\Sql\Argument\Argument;
1010
use PhpDb\Sql\Argument\ArgumentInterface;
1111
use PhpDb\Sql\Argument\IdentifierArgument;
12+
use PhpDb\Sql\Argument\IdentifiersArgument;
1213
use PhpDb\Sql\Argument\SelectArgument;
1314
use PhpDb\Sql\Argument\ValuesArgument;
1415
use PhpDb\Sql\Exception\InvalidArgumentException;
@@ -18,7 +19,6 @@
1819
use function array_fill;
1920
use function count;
2021
use function implode;
21-
use function is_array;
2222
use function sprintf;
2323
use function vsprintf;
2424

@@ -133,9 +133,8 @@ protected function getIdentifierSpecification(): string
133133
}
134134

135135
// Handle array identifiers for multi-column IN: (col1, col2) IN ...
136-
$value = $this->identifier->getValue();
137-
if (is_array($value)) {
138-
$count = count($value);
136+
if ($this->identifier instanceof IdentifiersArgument) {
137+
$count = count($this->identifier->getValue());
139138
return $count > 0
140139
? sprintf('(%s)', implode(', ', array_fill(0, $count, '%s')))
141140
: '(NULL)';

‎test/unit/Sql/AbstractSqlTest.php‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use PhpDb\Adapter\ParameterContainer;
1010
use PhpDb\Adapter\StatementContainer;
1111
use PhpDb\Sql\AbstractSql;
12-
use PhpDb\Sql\ArgumentType;
12+
use PhpDb\Sql\Argument\Argument;
1313
use PhpDb\Sql\Expression;
1414
use PhpDb\Sql\ExpressionInterface;
1515
use PhpDb\Sql\Predicate;
@@ -74,7 +74,7 @@ protected function setUp(): void
7474
*/
7575
public function testProcessExpressionWithoutParameterContainer(): void
7676
{
77-
$expression = new Expression('? > ? AND y < ?', [['x' => ArgumentType::Identifier], 5, 10]);
77+
$expression = new Expression('? > ? AND y < ?', [Argument::identifier('x'), 5, 10]);
7878
$sqlAndParams = $this->invokeProcessExpressionMethod($expression);
7979

8080
self::assertEquals("\"x\" > '5' AND y < '10'", $sqlAndParams);
@@ -86,7 +86,7 @@ public function testProcessExpressionWithoutParameterContainer(): void
8686
public function testProcessExpressionWithParameterContainerAndParameterizationTypeNamed(): void
8787
{
8888
$parameterContainer = new ParameterContainer();
89-
$expression = new Expression('? > ? AND y < ?', [['x' => ArgumentType::Identifier], 5, 10]);
89+
$expression = new Expression('? > ? AND y < ?', [Argument::identifier('x'), 5, 10]);
9090
$sqlAndParams = $this->invokeProcessExpressionMethod($expression, $parameterContainer);
9191

9292
$parameters = $parameterContainer->getNamedArray();

‎test/unit/Sql/Ddl/Index/IndexTest.php‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,31 @@
1313
#[CoversMethod(Index::class, 'getExpressionData')]
1414
final class IndexTest extends TestCase
1515
{
16+
private function compareExpressionTypeValues(array $expressionValues, array $comparisonValues): void
17+
{
18+
$diff = array_udiff($expressionValues, $comparisonValues, function($a, $b): int {
19+
return [$a->getType(), $a->getValue()] <=> [$b->getType(), $b->getValue()];
20+
});
21+
22+
var_dump($diff);
23+
exit;
24+
25+
self::assertCount(0, $diff);
26+
}
27+
1628
public function testGetExpressionData(): void
1729
{
1830
$uk = new Index('foo', 'my_uk');
1931

2032
$expressionData = $uk->getExpressionData();
21-
22-
self::assertEquals('INDEX %s(%s)', $expressionData->getExpressionSpecification());
23-
self::assertEquals([
33+
$expressionValues = $expressionData->getExpressionValues();
34+
$comparisonValues = [
2435
Argument::identifier('my_uk'),
2536
Argument::identifier('foo'),
26-
], $expressionData->getExpressionValues());
37+
];
38+
39+
self::assertEquals('INDEX %s(%s)', $expressionData->getExpressionSpecification());
40+
self::assertTrue($this->compareExpressionTypeValues($expressionValues, $comparisonValues));
2741
}
2842

2943
public function testGetExpressionDataWithLength(): void

‎test/unit/Sql/ExpressionDataTest.php‎

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

55
namespace PhpDbTest\Sql;
66

7-
use PhpDb\Sql\Argument;
7+
use PhpDb\Sql\Argument\Argument;
8+
use PhpDb\Sql\Argument\ArgumentInterface;
89
use PhpDb\Sql\Exception\InvalidArgumentException;
910
use PhpDb\Sql\ExpressionData;
1011
use PhpDb\Sql\ExpressionPart;

0 commit comments

Comments
 (0)