Skip to content

Commit 6f0a16a

Browse files
committed
detect IN (SELECT ... LIMIT ...)
1 parent 8dfddb2 commit 6f0a16a

File tree

5 files changed

+86
-7
lines changed

5 files changed

+86
-7
lines changed

src/Analyser/AnalyserErrorBuilder.php

+8
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,14 @@ public static function createAssignToReadonlyColumnError(string $column, ?string
186186
);
187187
}
188188

189+
public static function createNoLimitInsideIn(): AnalyserError
190+
{
191+
return new AnalyserError(
192+
'MariaDB does not support LIMIT inside of IN/ALL/ANY/SOME subquery.',
193+
AnalyserErrorTypeEnum::DB_UNSUPPORTED_FEATURE,
194+
);
195+
}
196+
189197
private static function formatDbType(DbType $type): string
190198
{
191199
if ($type::getTypeEnum() === DbTypeEnum::TUPLE) {

src/Analyser/AnalyserErrorTypeEnum.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ enum AnalyserErrorTypeEnum: string
1010
case ASSIGN_TO_READONLY_COLUMN = 'assignToReadonlyColumn';
1111
case COLUMN_MISMATCH = 'columnMismatch';
1212
case DB_REFLECTION = 'dbReflection';
13+
case DB_UNSUPPORTED_FEATURE = 'dbUnsupportedFeature';
1314
case DUPLICATE_COLUMN = 'duplicateColumn';
1415
case INVALID_BINARY_OP = 'invalidBinaryOp';
1516
case INVALID_CTE = 'invalidCTE';
@@ -19,10 +20,10 @@ enum AnalyserErrorTypeEnum: string
1920
case INVALID_HAVING_COLUMN = 'invalidHavingColumn';
2021
case INVALID_TUPLE_USAGE = 'invalidTupleUsage';
2122
case NON_UNIQUE_TABLE_ALIAS = 'nonUniqueTableAlias';
23+
case MARIA_STAN_UNSUPPORTED_FEATURE = 'mariaStanUnsupportedFeature';
2224
case MISSING_COLUMN_VALUE = 'missingColumnValue';
2325
case OTHER = 'other';
2426
case PARSE = 'parse';
2527
case UNKNOWN_TABLE = 'unknownTable';
2628
case UNKNOWN_COLUMN = 'unknownColumn';
27-
case UNSUPPORTED_FEATURE = 'unsupportedFeature';
2829
}

src/Analyser/AnalyserState.php

+33-6
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public function analyse(): AnalyserResult
124124
[
125125
new AnalyserError(
126126
"Unsupported query: {$this->queryAst::getQueryType()->value}",
127-
AnalyserErrorTypeEnum::UNSUPPORTED_FEATURE,
127+
AnalyserErrorTypeEnum::MARIA_STAN_UNSUPPORTED_FEATURE,
128128
),
129129
],
130130
null,
@@ -176,7 +176,7 @@ private function dispatchAnalyseSelectQuery(SelectQuery $select): array
176176
assert(is_string($typeVal));
177177
$this->errors[] = new AnalyserError(
178178
"Unhandled SELECT type {$typeVal}",
179-
AnalyserErrorTypeEnum::UNSUPPORTED_FEATURE,
179+
AnalyserErrorTypeEnum::MARIA_STAN_UNSUPPORTED_FEATURE,
180180
);
181181

182182
return [[], null];
@@ -192,7 +192,7 @@ private function analyseWithSelectQuery(WithSelectQuery $select): array
192192
if ($select->allowRecursive) {
193193
$this->errors[] = new AnalyserError(
194194
"WITH RECURSIVE is not currently supported. There may be false positives!",
195-
AnalyserErrorTypeEnum::UNSUPPORTED_FEATURE,
195+
AnalyserErrorTypeEnum::MARIA_STAN_UNSUPPORTED_FEATURE,
196196
);
197197
}
198198

@@ -542,7 +542,7 @@ private function analyseTableReference(TableReference $fromClause, ColumnResolve
542542
default:
543543
$this->errors[] = new AnalyserError(
544544
'Unhandled table reference type ' . $fromClause::getTableReferenceType()->value,
545-
AnalyserErrorTypeEnum::UNSUPPORTED_FEATURE,
545+
AnalyserErrorTypeEnum::MARIA_STAN_UNSUPPORTED_FEATURE,
546546
);
547547
break;
548548
}
@@ -1288,6 +1288,12 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
12881288
$knowledgeBase = $leftResult->knowledgeBase->and($rightResult->knowledgeBase);
12891289
}
12901290

1291+
if ($expr->right instanceof Expr\Subquery) {
1292+
if ($this->hasLimitClause($expr->right->query)) {
1293+
$this->errors[] = AnalyserErrorBuilder::createNoLimitInsideIn();
1294+
}
1295+
}
1296+
12911297
return new ExprTypeResult(
12921298
new Schema\DbType\IntType(),
12931299
$leftResult->isNullable || $rightResult->isNullable,
@@ -1345,7 +1351,7 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
13451351
if ($functionInfo === null) {
13461352
$this->errors[] = new AnalyserError(
13471353
"Unhandled function: {$expr->getFunctionName()}",
1348-
AnalyserErrorTypeEnum::UNSUPPORTED_FEATURE,
1354+
AnalyserErrorTypeEnum::MARIA_STAN_UNSUPPORTED_FEATURE,
13491355
);
13501356
}
13511357

@@ -1472,7 +1478,7 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
14721478
default:
14731479
$this->errors[] = new AnalyserError(
14741480
"Unhandled expression type: {$expr::getExprType()->value}",
1475-
AnalyserErrorTypeEnum::UNSUPPORTED_FEATURE,
1481+
AnalyserErrorTypeEnum::MARIA_STAN_UNSUPPORTED_FEATURE,
14761482
);
14771483

14781484
return new ExprTypeResult(
@@ -1690,4 +1696,25 @@ private function runWithDifferentFieldBehavior(ColumnResolverFieldBehaviorEnum $
16901696
$this->fieldBehavior = $bak;
16911697
}
16921698
}
1699+
1700+
private function hasLimitClause(SelectQuery $selectQuery): bool
1701+
{
1702+
switch ($selectQuery::getSelectQueryType()) {
1703+
case SelectQueryTypeEnum::SIMPLE:
1704+
assert($selectQuery instanceof SimpleSelectQuery);
1705+
1706+
return $selectQuery->limit !== null;
1707+
case SelectQueryTypeEnum::WITH:
1708+
assert($selectQuery instanceof WithSelectQuery);
1709+
1710+
return $this->hasLimitClause($selectQuery->selectQuery);
1711+
case SelectQueryTypeEnum::TABLE_VALUE_CONSTRUCTOR:
1712+
return false;
1713+
case SelectQueryTypeEnum::COMBINED:
1714+
assert($selectQuery instanceof CombinedSelectQuery);
1715+
1716+
return $selectQuery->limit !== null || $this->hasLimitClause($selectQuery->left)
1717+
|| $this->hasLimitClause($selectQuery->right);
1718+
}
1719+
}
16931720
}

src/Util/MariaDbErrorCodes.php

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class MariaDbErrorCodes
4040
// 1222 21000 ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT The used SELECT statements have a different number of columns
4141
public const ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222;
4242

43+
// 1235 42000 ER_NOT_SUPPORTED_YET This version of MariaDB doesn't yet support '%s'
44+
public const ER_NOT_SUPPORTED_YET = 1235;
45+
4346
// 1241 21000 ER_OPERAND_COLUMNS Operand should contain %d column(s)
4447
public const ER_OPERAND_COLUMNS = 1241;
4548

tests/Analyser/AnalyserTest.php

+40
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ private function provideValidOperatorTestData(): iterable
474474
'NOW() - INTERVAL 10 DAY',
475475
'1 IN (1, 2)',
476476
'1 IN (NULL)',
477+
'1 IN (SELECT * FROM (SELECT id FROM analyser_test LIMIT 10) t)',
478+
'1 IN (WITH t AS (SELECT * FROM analyser_test LIMIT 10) SELECT id FROM t)',
477479
'NULL IN (1)',
478480
'NULL IN (NULL)',
479481
'(1, 2) IN ((1, NULL))',
@@ -2039,6 +2041,7 @@ public function provideInvalidTestData(): iterable
20392041
yield from $this->provideInvalidUpdateTestData();
20402042
yield from $this->provideInvalidDeleteTestData();
20412043
yield from $this->provideInvalidTableValueConstructorData();
2044+
yield from self::provideInvalidUnsupportedFeaturesData();
20422045
}
20432046

20442047
/** @return iterable<string, array<mixed>> */
@@ -2935,6 +2938,43 @@ private function provideInvalidTableValueConstructorData(): iterable
29352938
];
29362939
}
29372940

2941+
/** @return iterable<string, array<mixed>> */
2942+
private static function provideInvalidUnsupportedFeaturesData(): iterable
2943+
{
2944+
yield 'LIMIT inside of IN - simple' => [
2945+
'query' => 'SELECT 1 IN (SELECT id FROM analyser_test LIMIT 10)',
2946+
'error' => AnalyserErrorBuilder::createNoLimitInsideIn(),
2947+
'DB error code' => MariaDbErrorCodes::ER_NOT_SUPPORTED_YET,
2948+
];
2949+
2950+
yield 'LIMIT inside of IN - WITH ... SIMPLE' => [
2951+
'query' => 'SELECT 1 IN (WITH t AS (SELECT * FROM analyser_test) SELECT id FROM t LIMIT 10)',
2952+
'error' => AnalyserErrorBuilder::createNoLimitInsideIn(),
2953+
'DB error code' => MariaDbErrorCodes::ER_NOT_SUPPORTED_YET,
2954+
];
2955+
2956+
yield 'LIMIT inside of IN - WITH ... UNION' => [
2957+
'query' => 'SELECT 1 IN (
2958+
WITH t AS (SELECT * FROM analyser_test) SELECT id FROM t UNION ALL SELECT id FROM t LIMIT 10
2959+
)',
2960+
'error' => AnalyserErrorBuilder::createNoLimitInsideIn(),
2961+
'DB error code' => MariaDbErrorCodes::ER_NOT_SUPPORTED_YET,
2962+
];
2963+
2964+
yield 'LIMIT inside of IN - UNION top level LIMIT' => [
2965+
'query' => 'SELECT 1 IN (SELECT id FROM analyser_test UNION ALL SELECT id FROM analyser_test LIMIT 50)',
2966+
'error' => AnalyserErrorBuilder::createNoLimitInsideIn(),
2967+
'DB error code' => MariaDbErrorCodes::ER_NOT_SUPPORTED_YET,
2968+
];
2969+
2970+
// TODO: fix this. Currently we're unable to parse the query.
2971+
//yield 'LIMIT inside of IN - UNION subquery LIMIT' => [
2972+
// 'query' => 'SELECT 1 IN ((SELECT id FROM analyser_test LIMIT 50) UNION ALL SELECT id FROM analyser_test)',
2973+
// 'error' => AnalyserErrorBuilder::createNoLimitInsideIn(),
2974+
// 'DB error code' => MariaDbErrorCodes::ER_NOT_SUPPORTED_YET,
2975+
//];
2976+
}
2977+
29382978
/**
29392979
* @param AnalyserError|array<AnalyserError> $error
29402980
* @dataProvider provideInvalidTestData

0 commit comments

Comments
 (0)