Skip to content

Commit

Permalink
parse COLLATE operator
Browse files Browse the repository at this point in the history
  • Loading branch information
schlndh committed Jun 2, 2023
1 parent b7a94f7 commit e5a7a88
Show file tree
Hide file tree
Showing 10 changed files with 507 additions and 2 deletions.
25 changes: 25 additions & 0 deletions src/Ast/Expr/Collate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace MariaStan\Ast\Expr;

use MariaStan\Ast\BaseNode;
use MariaStan\Parser\Position;

final class Collate extends BaseNode implements Expr
{
public function __construct(
Position $startPosition,
Position $endPosition,
public readonly Expr $expression,
public readonly string $collation,
) {
parent::__construct($startPosition, $endPosition);
}

public static function getExprType(): ExprTypeEnum
{
return ExprTypeEnum::COLLATE;
}
}
3 changes: 3 additions & 0 deletions src/Ast/Expr/ExprTypeEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,7 @@ enum ExprTypeEnum: string

/** @see ColumnDefault */
case COLUMN_DEFAULT = 'COLUMN_DEFAULT';

/** @see Collate */
case COLLATE = 'COLLATE';
}
3 changes: 3 additions & 0 deletions src/Ast/Expr/SpecialOpTypeEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ enum SpecialOpTypeEnum: string
/** @see Between */
case BETWEEN = 'BETWEEN';

/** @see Collate */
case COLLATE = 'COLLATE';

/** @see Is */
case IS = 'IS';

Expand Down
9 changes: 9 additions & 0 deletions src/Parser/Exception/ShouldNotHappenException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace MariaStan\Parser\Exception;

class ShouldNotHappenException extends ParserException
{
}
21 changes: 19 additions & 2 deletions src/Parser/MariaDbParserState.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use MariaStan\Ast\Expr\CastType\FloatCastType;
use MariaStan\Ast\Expr\CastType\IntegerCastType;
use MariaStan\Ast\Expr\CastType\TimeCastType;
use MariaStan\Ast\Expr\Collate;
use MariaStan\Ast\Expr\Column;
use MariaStan\Ast\Expr\ColumnDefault;
use MariaStan\Ast\Expr\Exists;
Expand Down Expand Up @@ -86,6 +87,7 @@
use MariaStan\Database\FunctionInfo\FunctionInfoRegistry;
use MariaStan\Parser\Exception\MissingSubqueryAliasException;
use MariaStan\Parser\Exception\ParserException;
use MariaStan\Parser\Exception\ShouldNotHappenException;
use MariaStan\Parser\Exception\UnexpectedTokenException;
use MariaStan\Parser\Exception\UnsupportedQueryException;

Expand Down Expand Up @@ -1091,6 +1093,8 @@ private function parseExpression(int $precedence = 0): Expr
$exp = $this->parseRestOfIsOperator($exp);
} elseif ($operator === SpecialOpTypeEnum::IN) {
$exp = $this->parseRestOfInOperator($exp);
} elseif ($operator === SpecialOpTypeEnum::COLLATE) {
$exp = $this->parseRestOfCollateOperator($exp);
} else {
// INTERVAL is handled as a unary operator
assert($operator !== SpecialOpTypeEnum::INTERVAL);
Expand Down Expand Up @@ -1222,6 +1226,19 @@ private function parseRestOfInOperator(Expr $left): In
);
}

/** @throws ParserException */
private function parseRestOfCollateOperator(Expr $left): Collate
{
$token = $this->expectAnyOfTokens(TokenTypeEnum::LITERAL_STRING, TokenTypeEnum::IDENTIFIER);
$collation = match ($token->type) {
TokenTypeEnum::LITERAL_STRING => $this->cleanStringLiteral($token->content),
TokenTypeEnum::IDENTIFIER => $this->cleanIdentifier($token->content),
default => throw new ShouldNotHappenException(),
};

return new Collate($left->getStartPosition(), $token->getEndPosition(), $left, $collation);
}

/** @throws ParserException */
private function parseTimeUnit(): TimeUnitEnum
{
Expand Down Expand Up @@ -1266,6 +1283,7 @@ private function findBinaryOrSpecialOpFromToken(Token $token): BinaryOpTypeEnum|
TokenTypeEnum::BETWEEN => SpecialOpTypeEnum::BETWEEN,
TokenTypeEnum::IS => SpecialOpTypeEnum::IS,
TokenTypeEnum::LIKE => SpecialOpTypeEnum::LIKE,
TokenTypeEnum::COLLATE => SpecialOpTypeEnum::COLLATE,
default => null,
};
}
Expand All @@ -1288,8 +1306,7 @@ private function getOperatorPrecedence(BinaryOpTypeEnum|UnaryOpTypeEnum|SpecialO
// https://mariadb.com/kb/en/operator-precedence/
return match ($op) {
SpecialOpTypeEnum::INTERVAL => 17,
// COLLATE => 16
UnaryOpTypeEnum::BINARY => 16,
UnaryOpTypeEnum::BINARY, SpecialOpTypeEnum::COLLATE => 16,
UnaryOpTypeEnum::LOGIC_NOT => 15,
UnaryOpTypeEnum::PLUS, UnaryOpTypeEnum::MINUS, UnaryOpTypeEnum::BITWISE_NOT => 14,
// || as string concat => 13
Expand Down
3 changes: 3 additions & 0 deletions src/Util/MariaDbErrorCodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class MariaDbErrorCodes
// 1264 22003 ER_WARN_DATA_OUT_OF_RANGE Out of range value for column '%s' at row %ld
public const ER_WARN_DATA_OUT_OF_RANGE = 1264;

// 1273 HY000 ER_UNKNOWN_COLLATION Unknown collation: '%s'
public const ER_UNKNOWN_COLLATION = 1273;

// 1292 22007 ER_TRUNCATED_WRONG_VALUE Truncated incorrect %s value: '%s'
public const ER_TRUNCATED_WRONG_VALUE = 1292;

Expand Down
2 changes: 2 additions & 0 deletions tests/Parser/MariaDbParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ public function testParseInvalid(string $name, string $code, string $expectedOut
MariaDbErrorCodes::ER_UNKNOWN_DATA_TYPE,
// Some of these functions have custom syntax and in those cases it can be enforced on AST level.
MariaDbErrorCodes::ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT,
// E.g. 'x' COLLATE CONCAT(...) returns this error instead of parser error.
MariaDbErrorCodes::ER_UNKNOWN_COLLATION,
],
true,
)
Expand Down
8 changes: 8 additions & 0 deletions tests/code/Parser/MariaDbLexer/select.test
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,12 @@ SELECT 000123
-----
SELECT: [SELECT]
INT_LITERAL: [000123]
END_OF_INPUT: []
-----
SELECT 'aaa' COLLATE 'utf8mb4_unicode_ci'
-----
SELECT: [SELECT]
STRING_LITERAL: ['aaa']
COLLATE: [COLLATE]
STRING_LITERAL: ['utf8mb4_unicode_ci']
END_OF_INPUT: []
15 changes: 15 additions & 0 deletions tests/code/Parser/MariaDbParser/invalid/collate.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
COLLATE
-----
SELECT 'a' COLLATE CONCAT('utf8mb4', '_unicode_ci')
-----
MariaStan\Parser\Exception\UnexpectedTokenException
Expected END_OF_INPUT, got '(' after: SELECT 'a' COLLATE CONCAT
#####
1273: Unknown collation: 'CONCAT'
-----
SELECT 'a' COLLATE
-----
MariaStan\Parser\Exception\UnexpectedTokenException
Expected one of STRING_LITERAL, IDENTIFIER, got END_OF_INPUT after: SELECT 'a' COLLATE
#####
1064: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1
Loading

0 comments on commit e5a7a88

Please sign in to comment.