Parse "ORDER BY" clause

This commit is contained in:
Simon Binder 2019-06-18 14:49:30 +02:00
parent 1bc4bfc120
commit be5bcfd459
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
6 changed files with 108 additions and 3 deletions

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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,
];
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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)),
),
),
);
});