Parse Dart placeholders based on their context

This commit is contained in:
Simon Binder 2019-09-14 11:44:15 +02:00
parent 1d7b4d01fe
commit f171098789
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
10 changed files with 107 additions and 38 deletions

View File

@ -193,7 +193,7 @@ abstract class AstVisitor<T> {
T visitMoorFile(MoorFile e);
T visitMoorImportStatement(ImportStatement e);
T visitMoorDeclaredStatement(DeclaredStatement e);
T visitInlineDartCode(InlineDart e);
T visitDartPlaceholder(DartPlaceholder e);
}
/// Visitor that walks down the entire tree, visiting all children in order.
@ -316,7 +316,7 @@ class RecursiveVisitor<T> extends AstVisitor<T> {
T visitMoorDeclaredStatement(DeclaredStatement e) => visitChildren(e);
@override
T visitInlineDartCode(InlineDart e) => visitChildren(e);
T visitDartPlaceholder(DartPlaceholder e) => visitChildren(e);
@protected
T visitChildren(AstNode e) {

View File

@ -1,5 +1,8 @@
part of '../ast.dart';
/// Base for limit statements. Without moor extensions, only [Limit] will be
/// parsed. With moor extensions, a [DartLimitPlaceholder] can be emitted as
/// well.
abstract class LimitBase implements AstNode {}
class Limit extends AstNode implements LimitBase {

View File

@ -1,7 +1,16 @@
part of '../ast.dart';
class OrderBy extends AstNode {
final List<OrderingTerm> terms;
/// Base for `ORDER BY` clauses. Without moor extensions, ony [OrderBy] will be
/// parsed. Otherwise, [DartOrderByPlaceholder] can be parsed as well.
abstract class OrderByBase extends AstNode {}
/// Base for a single ordering term that is a part of a [OrderBy]. Without moor
/// extensions, only [OrderingTerm] will be parsed. With moor extensions, a
/// [DartOrderingTermPlaceholder] can be parsed as well.
abstract class OrderingTermBase extends AstNode {}
class OrderBy extends AstNode implements OrderByBase {
final List<OrderingTermBase> terms;
OrderBy({this.terms});
@ -19,7 +28,7 @@ class OrderBy extends AstNode {
enum OrderingMode { ascending, descending }
class OrderingTerm extends AstNode {
class OrderingTerm extends AstNode implements OrderingTermBase {
final Expression expression;
final OrderingMode orderingMode;

View File

@ -74,7 +74,7 @@ class NamedWindowDeclaration with Referencable {
class WindowDefinition extends AstNode {
final String baseWindowName;
final List<Expression> partitionBy;
final OrderBy orderBy;
final OrderByBase orderBy;
final FrameSpec frameSpec;
WindowDefinition(

View File

@ -8,40 +8,45 @@ part of '../ast.dart';
/// 1. expressions: Any expression can be used for moor: `SELECT * FROM table
/// = $expr`. Generated code will write this as an `Expression` class from
/// moor.
/// 2. limits
/// 3. A single order-by clause
/// 4. A list of order-by clauses
abstract class InlineDart extends AstNode {
/// 2. limits, which will be exposed as a `Limit` component from moor
/// 3. A single order-by clause, which will be exposed as a `OrderingTerm` from
/// moor.
/// 4. A list of order-by clauses, which will be exposed as a `OrderBy` from
/// moor.
abstract class DartPlaceholder extends AstNode {
final String name;
DollarSignVariableToken token;
InlineDart._(this.name);
DartPlaceholder._(this.name);
@override
final Iterable<AstNode> childNodes = const Iterable.empty();
@override
T accept<T>(AstVisitor<T> visitor) => visitor.visitInlineDartCode(this);
T accept<T>(AstVisitor<T> visitor) => visitor.visitDartPlaceholder(this);
bool _dartEquals(covariant InlineDart other);
bool _dartEquals(covariant DartPlaceholder other) => true;
@override
bool contentEquals(InlineDart other) {
bool contentEquals(DartPlaceholder other) {
return other.name == name && other._dartEquals(other);
}
}
class InlineDartExpression extends InlineDart implements Expression {
InlineDartExpression({@required String name}) : super._(name);
@override
bool _dartEquals(InlineDartExpression other) => true;
class DartExpressionPlaceholder extends DartPlaceholder implements Expression {
DartExpressionPlaceholder({@required String name}) : super._(name);
}
class InlineDartLimit extends InlineDart implements LimitBase {
InlineDartLimit({@required String name}) : super._(name);
@override
bool _dartEquals(InlineDartLimit other) => true;
class DartLimitPlaceholder extends DartPlaceholder implements LimitBase {
DartLimitPlaceholder({@required String name}) : super._(name);
}
class DartOrderingTermPlaceholder extends DartPlaceholder
implements OrderingTermBase {
DartOrderingTermPlaceholder({@required String name}) : super._(name);
}
class DartOrderByPlaceholder extends DartPlaceholder implements OrderByBase {
DartOrderByPlaceholder({@required String name}) : super._(name);
}

View File

@ -9,7 +9,7 @@ class SelectStatement extends Statement with CrudStatement, ResultSet {
final GroupBy groupBy;
final List<NamedWindowDeclaration> windowDeclarations;
final OrderBy orderBy;
final OrderByBase orderBy;
final LimitBase limit;
/// The resolved list of columns returned by this select statements. Not

View File

@ -272,22 +272,41 @@ mixin CrudParser on ParserBase {
return declarations;
}
OrderBy _orderBy() {
OrderByBase _orderBy() {
if (_matchOne(TokenType.order)) {
_consume(TokenType.by, 'Expected "BY" after "ORDER" token');
final terms = <OrderingTerm>[];
final terms = <OrderingTermBase>[];
do {
terms.add(_orderingTerm());
} while (_matchOne(TokenType.comma));
// If we only hit a single ordering term and that term is a Dart
// placeholder, we can upgrade that term to a full order by placeholder.
// This gives users more control at runtime (they can specify multiple
// terms).
if (terms.length == 1 && terms.single is DartOrderingTermPlaceholder) {
final termPlaceholder = terms.single as DartOrderingTermPlaceholder;
return DartOrderByPlaceholder(name: termPlaceholder.name);
}
return OrderBy(terms: terms);
}
return null;
}
OrderingTerm _orderingTerm() {
OrderingTermBase _orderingTerm() {
final expr = expression();
final mode = _orderingModeOrNull();
return OrderingTerm(expression: expr, orderingMode: _orderingModeOrNull());
// if there is no ASC or DESC after a Dart placeholder, we can upgrade the
// expression to an ordering term placeholder and let users define the mode
// at runtime.
if (mode == null && expr is DartExpressionPlaceholder) {
return DartOrderingTermPlaceholder(name: expr.name)
..setSpan(expr.first, expr.last);
}
return OrderingTerm(expression: expr, orderingMode: mode);
}
@override
@ -326,8 +345,8 @@ mixin CrudParser on ParserBase {
// no offset or comma was parsed (so just LIMIT $expr). In that case, we
// want to provide additional flexibility to the user by interpreting the
// expression as a whole limit clause.
if (first is InlineDartExpression) {
return InlineDartLimit(name: first.name)
if (first is DartExpressionPlaceholder) {
return DartLimitPlaceholder(name: first.name)
..setSpan(limitToken, _previous);
}
return Limit(count: first)..setSpan(limitToken, _previous);
@ -457,7 +476,7 @@ mixin CrudParser on ParserBase {
final leftParen = _previous;
String baseWindowName;
OrderBy orderBy;
OrderByBase orderBy;
final partitionBy = <Expression>[];
if (_matchOne(TokenType.identifier)) {

View File

@ -322,7 +322,7 @@ mixin ExpressionParser on ParserBase {
case TokenType.dollarSignVariable:
if (enableMoorExtensions) {
final typedToken = token as DollarSignVariableToken;
return InlineDartExpression(name: typedToken.name)
return DartExpressionPlaceholder(name: typedToken.name)
..token = typedToken
..setSpan(token, token);
}

View File

@ -46,8 +46,9 @@ void main() {
expect(context.errors, isEmpty);
final select = context.root as SelectStatement;
final orderingTerm = select.orderBy.terms.single.expression as Reference;
final resolved = orderingTerm.resolved as ExpressionColumn;
final term = (select.orderBy as OrderBy).terms.single as OrderingTerm;
final expression = term.expression as Reference;
final resolved = expression.resolved as ExpressionColumn;
enforceEqual(
resolved.expression,

View File

@ -10,20 +10,20 @@ void main() {
SelectStatement(
columns: [StarResultColumn(null)],
from: [TableReference('tbl', null)],
limit: InlineDartLimit(name: 'limit'),
limit: DartLimitPlaceholder(name: 'limit'),
),
moorMode: true,
);
});
test('parses limit counts as expressions', () {
test('parses limit count as expressions', () {
testStatement(
r'SELECT * FROM tbl LIMIT $amount OFFSET 3',
SelectStatement(
columns: [StarResultColumn(null)],
from: [TableReference('tbl', null)],
limit: Limit(
count: InlineDartExpression(name: 'amount'),
count: DartExpressionPlaceholder(name: 'amount'),
offsetSeparator: token(TokenType.offset),
offset: NumericLiteral(3, token(TokenType.numberLiteral)),
),
@ -31,4 +31,36 @@ void main() {
moorMode: true,
);
});
test('parses ordering terms and ordering expressions', () {
testStatement(
r'SELECT * FROM tbl ORDER BY $term, $expr DESC',
SelectStatement(
columns: [StarResultColumn(null)],
from: [TableReference('tbl', null)],
orderBy: OrderBy(
terms: [
DartOrderingTermPlaceholder(name: 'term'),
OrderingTerm(
expression: DartExpressionPlaceholder(name: 'expr'),
orderingMode: OrderingMode.descending,
),
],
),
),
moorMode: true,
);
});
test('parses full order by placeholders', () {
testStatement(
r'SELECT * FROM tbl ORDER BY $order',
SelectStatement(
columns: [StarResultColumn(null)],
from: [TableReference('tbl', null)],
orderBy: DartOrderByPlaceholder(name: 'order'),
),
moorMode: true,
);
});
}