Skip to content

Commit

Permalink
analyse table value constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
schlndh committed Jul 21, 2023
1 parent 6eb0072 commit 96925e7
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/Analyser/AnalyserErrorMessageBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ public static function createInvalidHavingColumn(string $column): string
. " columns used in aggregate functions, columns from the SELECT list and outer subqueries can be used.";
}

public static function createTvcDifferentNumberOfValues(int $min, int $max): string
{
return "The used table value constructor has a different number of values: {$min} - {$max}.";
}

private static function formatDbType(DbType $type): string
{
if ($type::getTypeEnum() === DbTypeEnum::TUPLE) {
Expand Down
84 changes: 84 additions & 0 deletions src/Analyser/AnalyserState.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use MariaStan\Ast\Query\SelectQuery\SelectQuery;
use MariaStan\Ast\Query\SelectQuery\SelectQueryTypeEnum;
use MariaStan\Ast\Query\SelectQuery\SimpleSelectQuery;
use MariaStan\Ast\Query\SelectQuery\TableValueConstructorSelectQuery;
use MariaStan\Ast\Query\SelectQuery\WithSelectQuery;
use MariaStan\Ast\Query\SelectQueryCombinatorTypeEnum;
use MariaStan\Ast\Query\TableReference\Join;
Expand All @@ -29,6 +30,7 @@
use MariaStan\Ast\Query\TableReference\Table;
use MariaStan\Ast\Query\TableReference\TableReference;
use MariaStan\Ast\Query\TableReference\TableReferenceTypeEnum;
use MariaStan\Ast\Query\TableReference\TableValueConstructor;
use MariaStan\Ast\Query\TableReference\UsingJoinCondition;
use MariaStan\Ast\Query\TruncateQuery;
use MariaStan\Ast\Query\UpdateQuery;
Expand All @@ -54,6 +56,7 @@
use function assert;
use function count;
use function in_array;
use function max;
use function mb_strlen;
use function min;
use function stripos;
Expand Down Expand Up @@ -158,6 +161,10 @@ private function dispatchAnalyseSelectQuery(SelectQuery $select): array
assert($select instanceof WithSelectQuery);

return $this->analyseWithSelectQuery($select);
case SelectQueryTypeEnum::TABLE_VALUE_CONSTRUCTOR:
assert($select instanceof TableValueConstructorSelectQuery);

return $this->analyseTableValueConstructor($select->tableValueConstructor);
default:
$this->errors[] = new AnalyserError("Unhandled SELECT type {$select::getSelectQueryType()->value}");

Expand Down Expand Up @@ -278,6 +285,66 @@ private function analyseCombinedSelectQuery(CombinedSelectQuery $select): array
return [$fields, null];
}

/**
* @return array{0: array<QueryResultField>, 1: ?QueryResultRowCountRange}
* @throws AnalyserException
*/
private function analyseTableValueConstructor(TableValueConstructor $tvc): array
{
$colCounts = [];
$rowColTypes = [];

foreach ($tvc->values as $exprs) {
$colCounts[] = count($exprs);
$colTypes = [];

foreach ($exprs as $expr) {
$colTypes[] = $this->resolveExprType($expr);
}

$rowColTypes[] = $colTypes;
}

$minColCount = min($colCounts);
$maxColCount = max($colCounts);

if ($minColCount !== $maxColCount) {
$this->errors[] = new AnalyserError(
AnalyserErrorMessageBuilder::createTvcDifferentNumberOfValues($minColCount, $maxColCount),
);
}

$fields = [];
$rowCount = count($rowColTypes);

for ($i = 0; $i < $minColCount; $i++) {
$type = $rowColTypes[0][$i];

for ($j = 1; $j < $rowCount; $j++) {
$otherRowType = $rowColTypes[$j][$i];
$combinedType = $this->getCombinedType(
$type->type,
$otherRowType->type,
SelectQueryCombinatorTypeEnum::UNION,
);
$type = new ExprTypeResult(
$combinedType,
$type->isNullable || $otherRowType->isNullable,
// TODO: is there something to combined here?
null,
null,
);
}

$fields[] = new QueryResultField(
$this->getDefaultFieldNameForExpr($tvc->values[0][$i]),
$type,
);
}

return [$fields, new QueryResultRowCountRange($rowCount, $rowCount)];
}

/**
* @return array{0: array<QueryResultField>, 1: ?QueryResultRowCountRange}
* @throws AnalyserException
Expand Down Expand Up @@ -449,6 +516,23 @@ private function analyseTableReference(TableReference $fromClause, ColumnResolve
}

return [array_merge($leftTables, $rightTables), $columnResolver];
case TableReferenceTypeEnum::TABLE_VALUE_CONSTRUCTOR:
assert($fromClause instanceof TableValueConstructor);
$columnResolver = clone $columnResolver;
[$tvcFields] = $this->analyseTableValueConstructor($fromClause);

try {
$columnResolver->registerSubquery($tvcFields, $fromClause->getAliasOrThrow());
} catch (AnalyserException $e) {
$this->errors[] = new AnalyserError($e->getMessage());
}

return [[$fromClause->getAliasOrThrow()], $columnResolver];
default:
$this->errors[] = new AnalyserError(
'Unhandled table reference type ' . $fromClause::getTableReferenceType()->value,
);
break;
}

return [[], $columnResolver];
Expand Down
6 changes: 6 additions & 0 deletions src/Ast/Query/TableReference/TableValueConstructor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace MariaStan\Ast\Query\TableReference;

use MariaStan\Ast\BaseNode;
use MariaStan\Ast\Exception\InvalidAstException;
use MariaStan\Ast\Expr\Expr;
use MariaStan\Parser\Position;

Expand All @@ -24,4 +25,9 @@ public static function getTableReferenceType(): TableReferenceTypeEnum
{
return TableReferenceTypeEnum::TABLE_VALUE_CONSTRUCTOR;
}

public function getAliasOrThrow(): string
{
return $this->alias ?? throw new InvalidAstException('TVC was expected to have an alias.');
}
}
6 changes: 6 additions & 0 deletions src/Util/MariaDbErrorCodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ class MariaDbErrorCodes
// 4079 ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION Illegal parameter data type %s for operation '%s'
public const ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION = 4079;

// 4099 ER_WRONG_NUMBER_OF_VALUES_IN_TVC The used table value constructor has a different number of values
public const ER_WRONG_NUMBER_OF_VALUES_IN_TVC = 4099;

// 4100 ER_FIELD_REFERENCE_IN_TVC Field reference '%-.192s' can't be used in table value constructor
public const ER_FIELD_REFERENCE_IN_TVC = 4100;

// 4107 ER_INVALID_VALUE_TO_LIMIT Limit only accepts integer values
public const ER_INVALID_VALUE_TO_LIMIT = 4107;

Expand Down
44 changes: 44 additions & 0 deletions tests/Analyser/AnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ enum_bc ENUM ('b', 'c') NOT NULL
yield from $this->provideValidOtherQueryTestData();
yield from $this->provideValidUpdateTestData();
yield from $this->provideValidDeleteTestData();
yield from $this->provideValidTableValueConstructorData();
}

/** @return iterable<string, array<mixed>> */
Expand Down Expand Up @@ -1027,6 +1028,32 @@ private function provideValidWithTestData(): iterable
];
}

/** @return iterable<string, array<mixed>> */
private function provideValidTableValueConstructorData(): iterable
{
yield 'TVC - simple WITH' => [
'query' => "WITH tbl AS (VALUES (1, 2), (3, 4)) SELECT * FROM tbl",
];

yield 'TVC - simple WITH - column names' => [
'query' => "WITH tbl (a, b) AS (VALUES (1, 2), (3, 4)) SELECT * FROM tbl",
];

yield 'TVC - simple WITH - combined types' => [
'query' => "WITH tbl (a, b) AS (VALUES (1, 'a'), ('b', null)) SELECT * FROM tbl",
];

yield 'TVC - subquery' => [
'query' => "SELECT * FROM (VALUES (1 + 1, 2), (3, 4)) t",
];

foreach (SelectQueryCombinatorTypeEnum::cases() as $combinator) {
yield 'TVC - ' . $combinator->value => [
'query' => "SELECT id, id FROM analyser_test {$combinator->value} VALUES (1, 1)",
];
}
}

/** @return iterable<string, array<mixed>> */
private function provideValidOtherQueryTestData(): iterable
{
Expand Down Expand Up @@ -1899,6 +1926,7 @@ public function provideInvalidTestData(): iterable
yield from $this->provideInvalidOtherQueryTestData();
yield from $this->provideInvalidUpdateTestData();
yield from $this->provideInvalidDeleteTestData();
yield from $this->provideInvalidTableValueConstructorData();
}

/** @return iterable<string, array<mixed>> */
Expand Down Expand Up @@ -2736,6 +2764,22 @@ private function provideInvalidDeleteTestData(): iterable
// TODO: detect deleting non-tables
}

/** @return iterable<string, array<mixed>> */
private function provideInvalidTableValueConstructorData(): iterable
{
yield 'TVC - different number of values' => [
'query' => 'WITH t AS (VALUES (1, 2), (3)) SELECT * FROM t',
'error' => AnalyserErrorMessageBuilder::createTvcDifferentNumberOfValues(1, 2),
'DB error code' => MariaDbErrorCodes::ER_WRONG_NUMBER_OF_VALUES_IN_TVC,
];

yield 'TVC - unknown column' => [
'query' => 'WITH t AS (VALUES (1, id)) SELECT * FROM t',
'error' => AnalyserErrorMessageBuilder::createUnknownColumnErrorMessage('id'),
'DB error code' => MariaDbErrorCodes::ER_FIELD_REFERENCE_IN_TVC,
];
}

/**
* @param string|array<string> $error
* @dataProvider provideInvalidTestData
Expand Down

0 comments on commit 96925e7

Please sign in to comment.