mirror of https://github.com/AMT-Cheif/drift.git
Parse "ORDER BY" clause
This commit is contained in:
parent
1bc4bfc120
commit
be5bcfd459
|
@ -2,6 +2,7 @@ import 'package:meta/meta.dart';
|
|||
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||
|
||||
part 'clauses/limit.dart';
|
||||
part 'clauses/ordering.dart';
|
||||
|
||||
part 'expressions/expressions.dart';
|
||||
part 'expressions/literals.dart';
|
||||
|
@ -24,6 +25,8 @@ abstract class AstVisitor<T> {
|
|||
T visitSelectStatement(SelectStatement e);
|
||||
T visitResultColumn(ResultColumn e);
|
||||
|
||||
T visitOrderBy(OrderBy e);
|
||||
T visitOrderingTerm(OrderingTerm e);
|
||||
T visitLimit(Limit e);
|
||||
|
||||
T visitBinaryExpression(BinaryExpression e);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
part of '../ast.dart';
|
||||
|
||||
class OrderBy extends AstNode {
|
||||
final List<OrderingTerm> terms;
|
||||
|
||||
OrderBy({this.terms});
|
||||
|
||||
@override
|
||||
T accept<T>(AstVisitor<T> visitor) => visitor.visitOrderBy(this);
|
||||
|
||||
@override
|
||||
Iterable<AstNode> get childNodes => terms;
|
||||
|
||||
@override
|
||||
bool contentEquals(OrderBy other) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
enum OrderingMode { ascending, descending }
|
||||
|
||||
class OrderingTerm extends AstNode {
|
||||
final Expression expression;
|
||||
final OrderingMode orderingMode;
|
||||
|
||||
OrderingTerm({this.expression, this.orderingMode = OrderingMode.ascending});
|
||||
|
||||
@override
|
||||
T accept<T>(AstVisitor<T> visitor) => visitor.visitOrderingTerm(this);
|
||||
|
||||
@override
|
||||
Iterable<AstNode> get childNodes => [expression];
|
||||
|
||||
@override
|
||||
bool contentEquals(OrderingTerm other) {
|
||||
return other.orderingMode == orderingMode;
|
||||
}
|
||||
}
|
|
@ -3,9 +3,10 @@ part of '../ast.dart';
|
|||
class SelectStatement extends AstNode {
|
||||
final Expression where;
|
||||
final List<ResultColumn> columns;
|
||||
final OrderBy orderBy;
|
||||
final Limit limit;
|
||||
|
||||
SelectStatement({this.where, this.columns, this.limit});
|
||||
SelectStatement({this.where, this.columns, this.orderBy, this.limit});
|
||||
|
||||
@override
|
||||
T accept<T>(AstVisitor<T> visitor) {
|
||||
|
@ -18,6 +19,7 @@ class SelectStatement extends AstNode {
|
|||
if (where != null) where,
|
||||
...columns,
|
||||
if (limit != null) limit,
|
||||
if (orderBy != null) orderBy,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,11 @@ class ParsingError implements Exception {
|
|||
final String message;
|
||||
|
||||
ParsingError(this.token, this.message);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return token.span.message('Error: $message}');
|
||||
}
|
||||
}
|
||||
|
||||
// todo better error handling and synchronisation, like it's done here:
|
||||
|
@ -87,9 +92,11 @@ class Parser {
|
|||
} while (_match(const [TokenType.comma]));
|
||||
|
||||
final where = _where();
|
||||
final orderBy = _orderBy();
|
||||
final limit = _limit();
|
||||
|
||||
return SelectStatement(where: where, columns: resultColumns, limit: limit);
|
||||
return SelectStatement(
|
||||
where: where, columns: resultColumns, orderBy: orderBy, limit: limit);
|
||||
}
|
||||
|
||||
/// Parses a [ResultColumn] or throws if none is found.
|
||||
|
@ -139,6 +146,30 @@ class Parser {
|
|||
return null;
|
||||
}
|
||||
|
||||
OrderBy _orderBy() {
|
||||
if (_match(const [TokenType.order])) {
|
||||
_consume(TokenType.by, 'Expected "BY" after "ORDER" token');
|
||||
final terms = <OrderingTerm>[];
|
||||
do {
|
||||
terms.add(_orderingTerm());
|
||||
} while (_match(const [TokenType.comma]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
OrderingTerm _orderingTerm() {
|
||||
final expr = expression();
|
||||
|
||||
if (_match(const [TokenType.asc, TokenType.desc])) {
|
||||
final mode = _previous.type == TokenType.asc
|
||||
? OrderingMode.ascending
|
||||
: OrderingMode.descending;
|
||||
return OrderingTerm(expression: expr, orderingMode: mode);
|
||||
}
|
||||
|
||||
return OrderingTerm(expression: expr);
|
||||
}
|
||||
|
||||
/// Parses a [Limit] clause, or returns null if there is no limit token after
|
||||
/// the current position.
|
||||
Limit _limit() {
|
||||
|
@ -286,6 +317,18 @@ class Parser {
|
|||
final expr = expression();
|
||||
_consume(TokenType.rightParen, 'Expected a closing bracket');
|
||||
return Parentheses(left, expr, _previous);
|
||||
case TokenType.identifier:
|
||||
final first = _previous as IdentifierToken;
|
||||
if (_match(const [TokenType.dot])) {
|
||||
final second =
|
||||
_consume(TokenType.identifier, 'Expected a column name here')
|
||||
as IdentifierToken;
|
||||
return Reference(
|
||||
tableName: first.identifier, columnName: second.identifier);
|
||||
} else {
|
||||
return Reference(columnName: first.identifier);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,11 @@ enum TokenType {
|
|||
as,
|
||||
where,
|
||||
|
||||
order,
|
||||
by,
|
||||
asc,
|
||||
desc,
|
||||
|
||||
limit,
|
||||
offset,
|
||||
|
||||
|
@ -61,6 +66,10 @@ const Map<String, TokenType> keywords = {
|
|||
'FROM': TokenType.from,
|
||||
'AS': TokenType.as,
|
||||
'WHERE': TokenType.where,
|
||||
'ORDER': TokenType.order,
|
||||
'BY': TokenType.by,
|
||||
'ASC': TokenType.asc,
|
||||
'DESC': TokenType.desc,
|
||||
'LIMIT': TokenType.limit,
|
||||
'OFFSET': TokenType.offset,
|
||||
'IS': TokenType.$is,
|
||||
|
|
|
@ -33,7 +33,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('parses select statements', () {
|
||||
final scanner = Scanner('SELECT table.*, *, 1 as name');
|
||||
final scanner = Scanner(
|
||||
'SELECT table.*, *, 1 as name WHERE 1 ORDER BY name LIMIT 3 OFFSET 5');
|
||||
final tokens = scanner.scanTokens();
|
||||
final parser = Parser(tokens);
|
||||
|
||||
|
@ -49,6 +50,15 @@ void main() {
|
|||
as: 'name',
|
||||
),
|
||||
],
|
||||
where: NumericLiteral(1, token(TokenType.numberLiteral)),
|
||||
orderBy: OrderBy(terms: [
|
||||
OrderingTerm(expression: Reference(columnName: 'name')),
|
||||
]),
|
||||
limit: Limit(
|
||||
count: NumericLiteral(3, token(TokenType.numberLiteral)),
|
||||
offsetSeparator: token(TokenType.offset),
|
||||
offset: NumericLiteral(5, token(TokenType.numberLiteral)),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue