From 69f0b9b3931c5a404d108e3811a0583cd0bb7ff5 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 2 Jun 2019 22:30:23 +0200 Subject: [PATCH 1/9] Begin with simple ast and parser --- moor/lib/src/dsl/database.dart | 18 +- .../lib/src/model/specified_column.g.dart | 14 +- .../src/sql/ast/expressions/expressions.dart | 3 + .../lib/src/sql/ast/expressions/literals.dart | 65 ++++ .../src/sql/ast/expressions/literals.g.dart | 338 ++++++++++++++++++ .../lib/src/sql/ast/select/columns.dart | 34 ++ .../lib/src/sql/ast/select/columns.g.dart | 175 +++++++++ .../lib/src/sql/ast/select/select.dart | 40 +++ .../lib/src/sql/ast/select/select.g.dart | 308 ++++++++++++++++ .../lib/src/sql/parser/grammar/grammar.dart | 67 ++++ .../src/sql/parser/grammar/not_a_keyword.dart | 27 ++ .../lib/src/sql/parser/grammar/select.dart | 0 moor_generator/lib/src/sql/parser/parser.dart | 3 + .../lib/src/sql/parser/parser/parser.dart | 85 +++++ moor_generator/lib/src/sql/sql.dart | 0 .../test/sql/not_a_keyword_test.dart | 18 + moor_generator/test/sql/parser_tests.dart | 12 + 17 files changed, 1199 insertions(+), 8 deletions(-) create mode 100644 moor_generator/lib/src/sql/ast/expressions/expressions.dart create mode 100644 moor_generator/lib/src/sql/ast/expressions/literals.dart create mode 100644 moor_generator/lib/src/sql/ast/expressions/literals.g.dart create mode 100644 moor_generator/lib/src/sql/ast/select/columns.dart create mode 100644 moor_generator/lib/src/sql/ast/select/columns.g.dart create mode 100644 moor_generator/lib/src/sql/ast/select/select.dart create mode 100644 moor_generator/lib/src/sql/ast/select/select.g.dart create mode 100644 moor_generator/lib/src/sql/parser/grammar/grammar.dart create mode 100644 moor_generator/lib/src/sql/parser/grammar/not_a_keyword.dart create mode 100644 moor_generator/lib/src/sql/parser/grammar/select.dart create mode 100644 moor_generator/lib/src/sql/parser/parser.dart create mode 100644 moor_generator/lib/src/sql/parser/parser/parser.dart create mode 100644 moor_generator/lib/src/sql/sql.dart create mode 100644 moor_generator/test/sql/not_a_keyword_test.dart create mode 100644 moor_generator/test/sql/parser_tests.dart diff --git a/moor/lib/src/dsl/database.dart b/moor/lib/src/dsl/database.dart index ba19ffba..aab66657 100644 --- a/moor/lib/src/dsl/database.dart +++ b/moor/lib/src/dsl/database.dart @@ -25,9 +25,21 @@ class UseMoor { /// For instructions on how to write a dao, see the documentation of [UseDao] final List daos; + /// Optionally, a list of queries. Moor will generate matching methods for the + /// variables and return types. + // todo better documentation + final List queries; + /// Use this class as an annotation to inform moor_generator that a database /// class should be generated using the specified [UseMoor.tables]. - const UseMoor({@required this.tables, this.daos = const []}); + const UseMoor({@required this.tables, this.daos = const [], this.queries}); +} + +class Sql { + final String name; + final String query; + + const Sql(this.name, this.query); } /// Annotation to use on classes that implement [DatabaseAccessor]. It specifies @@ -51,6 +63,8 @@ class UseMoor { class UseDao { /// The tables accessed by this DAO. final List tables; + // todo better documentation + final List queries; - const UseDao({@required this.tables}); + const UseDao({@required this.tables, this.queries}); } diff --git a/moor_generator/lib/src/model/specified_column.g.dart b/moor_generator/lib/src/model/specified_column.g.dart index 4e1d3fed..b9dcd6e2 100644 --- a/moor_generator/lib/src/model/specified_column.g.dart +++ b/moor_generator/lib/src/model/specified_column.g.dart @@ -12,7 +12,7 @@ class _$ColumnName extends ColumnName { @override final String name; - factory _$ColumnName([void updates(ColumnNameBuilder b)]) => + factory _$ColumnName([void Function(ColumnNameBuilder) updates]) => (new ColumnNameBuilder()..update(updates)).build(); _$ColumnName._({this.implicit, this.name}) : super._() { @@ -25,7 +25,7 @@ class _$ColumnName extends ColumnName { } @override - ColumnName rebuild(void updates(ColumnNameBuilder b)) => + ColumnName rebuild(void Function(ColumnNameBuilder) updates) => (toBuilder()..update(updates)).build(); @override @@ -84,7 +84,7 @@ class ColumnNameBuilder implements Builder { } @override - void update(void updates(ColumnNameBuilder b)) { + void update(void Function(ColumnNameBuilder) updates) { if (updates != null) updates(this); } @@ -102,13 +102,15 @@ class _$LimitingTextLength extends LimitingTextLength { @override final int maxLength; - factory _$LimitingTextLength([void updates(LimitingTextLengthBuilder b)]) => + factory _$LimitingTextLength( + [void Function(LimitingTextLengthBuilder) updates]) => (new LimitingTextLengthBuilder()..update(updates)).build(); _$LimitingTextLength._({this.minLength, this.maxLength}) : super._(); @override - LimitingTextLength rebuild(void updates(LimitingTextLengthBuilder b)) => + LimitingTextLength rebuild( + void Function(LimitingTextLengthBuilder) updates) => (toBuilder()..update(updates)).build(); @override @@ -169,7 +171,7 @@ class LimitingTextLengthBuilder } @override - void update(void updates(LimitingTextLengthBuilder b)) { + void update(void Function(LimitingTextLengthBuilder) updates) { if (updates != null) updates(this); } diff --git a/moor_generator/lib/src/sql/ast/expressions/expressions.dart b/moor_generator/lib/src/sql/ast/expressions/expressions.dart new file mode 100644 index 00000000..b31ffed9 --- /dev/null +++ b/moor_generator/lib/src/sql/ast/expressions/expressions.dart @@ -0,0 +1,3 @@ +abstract class Expression { + const Expression(); +} diff --git a/moor_generator/lib/src/sql/ast/expressions/literals.dart b/moor_generator/lib/src/sql/ast/expressions/literals.dart new file mode 100644 index 00000000..e74a5c12 --- /dev/null +++ b/moor_generator/lib/src/sql/ast/expressions/literals.dart @@ -0,0 +1,65 @@ +import 'package:built_value/built_value.dart'; + +import 'expressions.dart'; + +part 'literals.g.dart'; + +// https://www.sqlite.org/syntax/literal-value.html + +class NullLiteral extends Expression { + const NullLiteral(); + + @override + String toString() => 'NULL'; + + @override + int get hashCode => runtimeType.hashCode; + + @override + bool operator ==(other) => identical(this, other) || other is NullLiteral; +} + +abstract class BooleanLiteral extends Expression + implements Built { + bool get value; + + BooleanLiteral._(); + factory BooleanLiteral.from(bool value) => + BooleanLiteral((b) => b.value = value); + + factory BooleanLiteral(Function(BooleanLiteralBuilder) updates) = + _$BooleanLiteral; +} + +enum CurrentTimeAccessor { currentTime, currentDate, currentTimestamp } + +/// Represents the CURRENT_TIME, CURRENT_DATE or CURRENT_TIMESTAMP mode. +abstract class CurrentTimeResolver extends Expression + implements Built { + CurrentTimeAccessor get mode; + + CurrentTimeResolver._(); + factory CurrentTimeResolver.mode(CurrentTimeAccessor mode) { + return CurrentTimeResolver((b) => b.mode = mode); + } + factory CurrentTimeResolver(Function(CurrentTimeResolverBuilder) updates) = + _$CurrentTimeResolver; +} + +abstract class NumericLiteral extends Expression + implements Built { + num get value; + NumericLiteral._(); + factory NumericLiteral(Function(NumericLiteralBuilder) updates) = + _$NumericLiteral; +} + +abstract class StringLiteral extends Expression + implements Built { + bool get isBlob; + String get content; + + StringLiteral._(); + factory StringLiteral(Function(StringLiteralBuilder) updates) = + _$StringLiteral; +} diff --git a/moor_generator/lib/src/sql/ast/expressions/literals.g.dart b/moor_generator/lib/src/sql/ast/expressions/literals.g.dart new file mode 100644 index 00000000..fb88b05c --- /dev/null +++ b/moor_generator/lib/src/sql/ast/expressions/literals.g.dart @@ -0,0 +1,338 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'literals.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +class _$BooleanLiteral extends BooleanLiteral { + @override + final bool value; + + factory _$BooleanLiteral([void Function(BooleanLiteralBuilder) updates]) => + (new BooleanLiteralBuilder()..update(updates)).build(); + + _$BooleanLiteral._({this.value}) : super._() { + if (value == null) { + throw new BuiltValueNullFieldError('BooleanLiteral', 'value'); + } + } + + @override + BooleanLiteral rebuild(void Function(BooleanLiteralBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + BooleanLiteralBuilder toBuilder() => + new BooleanLiteralBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is BooleanLiteral && value == other.value; + } + + @override + int get hashCode { + return $jf($jc(0, value.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('BooleanLiteral')..add('value', value)) + .toString(); + } +} + +class BooleanLiteralBuilder + implements Builder { + _$BooleanLiteral _$v; + + bool _value; + bool get value => _$this._value; + set value(bool value) => _$this._value = value; + + BooleanLiteralBuilder(); + + BooleanLiteralBuilder get _$this { + if (_$v != null) { + _value = _$v.value; + _$v = null; + } + return this; + } + + @override + void replace(BooleanLiteral other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$BooleanLiteral; + } + + @override + void update(void Function(BooleanLiteralBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$BooleanLiteral build() { + final _$result = _$v ?? new _$BooleanLiteral._(value: value); + replace(_$result); + return _$result; + } +} + +class _$CurrentTimeResolver extends CurrentTimeResolver { + @override + final CurrentTimeAccessor mode; + + factory _$CurrentTimeResolver( + [void Function(CurrentTimeResolverBuilder) updates]) => + (new CurrentTimeResolverBuilder()..update(updates)).build(); + + _$CurrentTimeResolver._({this.mode}) : super._() { + if (mode == null) { + throw new BuiltValueNullFieldError('CurrentTimeResolver', 'mode'); + } + } + + @override + CurrentTimeResolver rebuild( + void Function(CurrentTimeResolverBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + CurrentTimeResolverBuilder toBuilder() => + new CurrentTimeResolverBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is CurrentTimeResolver && mode == other.mode; + } + + @override + int get hashCode { + return $jf($jc(0, mode.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('CurrentTimeResolver') + ..add('mode', mode)) + .toString(); + } +} + +class CurrentTimeResolverBuilder + implements Builder { + _$CurrentTimeResolver _$v; + + CurrentTimeAccessor _mode; + CurrentTimeAccessor get mode => _$this._mode; + set mode(CurrentTimeAccessor mode) => _$this._mode = mode; + + CurrentTimeResolverBuilder(); + + CurrentTimeResolverBuilder get _$this { + if (_$v != null) { + _mode = _$v.mode; + _$v = null; + } + return this; + } + + @override + void replace(CurrentTimeResolver other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$CurrentTimeResolver; + } + + @override + void update(void Function(CurrentTimeResolverBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$CurrentTimeResolver build() { + final _$result = _$v ?? new _$CurrentTimeResolver._(mode: mode); + replace(_$result); + return _$result; + } +} + +class _$NumericLiteral extends NumericLiteral { + @override + final num value; + + factory _$NumericLiteral([void Function(NumericLiteralBuilder) updates]) => + (new NumericLiteralBuilder()..update(updates)).build(); + + _$NumericLiteral._({this.value}) : super._() { + if (value == null) { + throw new BuiltValueNullFieldError('NumericLiteral', 'value'); + } + } + + @override + NumericLiteral rebuild(void Function(NumericLiteralBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + NumericLiteralBuilder toBuilder() => + new NumericLiteralBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is NumericLiteral && value == other.value; + } + + @override + int get hashCode { + return $jf($jc(0, value.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('NumericLiteral')..add('value', value)) + .toString(); + } +} + +class NumericLiteralBuilder + implements Builder { + _$NumericLiteral _$v; + + num _value; + num get value => _$this._value; + set value(num value) => _$this._value = value; + + NumericLiteralBuilder(); + + NumericLiteralBuilder get _$this { + if (_$v != null) { + _value = _$v.value; + _$v = null; + } + return this; + } + + @override + void replace(NumericLiteral other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$NumericLiteral; + } + + @override + void update(void Function(NumericLiteralBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$NumericLiteral build() { + final _$result = _$v ?? new _$NumericLiteral._(value: value); + replace(_$result); + return _$result; + } +} + +class _$StringLiteral extends StringLiteral { + @override + final bool isBlob; + @override + final String content; + + factory _$StringLiteral([void Function(StringLiteralBuilder) updates]) => + (new StringLiteralBuilder()..update(updates)).build(); + + _$StringLiteral._({this.isBlob, this.content}) : super._() { + if (isBlob == null) { + throw new BuiltValueNullFieldError('StringLiteral', 'isBlob'); + } + if (content == null) { + throw new BuiltValueNullFieldError('StringLiteral', 'content'); + } + } + + @override + StringLiteral rebuild(void Function(StringLiteralBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + StringLiteralBuilder toBuilder() => new StringLiteralBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is StringLiteral && + isBlob == other.isBlob && + content == other.content; + } + + @override + int get hashCode { + return $jf($jc($jc(0, isBlob.hashCode), content.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('StringLiteral') + ..add('isBlob', isBlob) + ..add('content', content)) + .toString(); + } +} + +class StringLiteralBuilder + implements Builder { + _$StringLiteral _$v; + + bool _isBlob; + bool get isBlob => _$this._isBlob; + set isBlob(bool isBlob) => _$this._isBlob = isBlob; + + String _content; + String get content => _$this._content; + set content(String content) => _$this._content = content; + + StringLiteralBuilder(); + + StringLiteralBuilder get _$this { + if (_$v != null) { + _isBlob = _$v.isBlob; + _content = _$v.content; + _$v = null; + } + return this; + } + + @override + void replace(StringLiteral other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$StringLiteral; + } + + @override + void update(void Function(StringLiteralBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$StringLiteral build() { + final _$result = + _$v ?? new _$StringLiteral._(isBlob: isBlob, content: content); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/moor_generator/lib/src/sql/ast/select/columns.dart b/moor_generator/lib/src/sql/ast/select/columns.dart new file mode 100644 index 00000000..3097ff4f --- /dev/null +++ b/moor_generator/lib/src/sql/ast/select/columns.dart @@ -0,0 +1,34 @@ +import 'package:built_value/built_value.dart'; + +import 'package:moor_generator/src/sql/ast/expressions/expressions.dart'; + +part 'columns.g.dart'; + +/// https://www.sqlite.org/syntax/result-column.html +abstract class ResultColumn {} + +abstract class ExprResultColumn extends ResultColumn + implements Built { + Expression get expr; + @nullable + String get alias; + + ExprResultColumn._(); + factory ExprResultColumn(Function(ExprResultColumnBuilder builder) updates) = + _$ExprResultColumn; +} + +abstract class StarResultColumn extends ResultColumn + implements Built { + /// When non-null, refers to the "table.*" select expression. Otherwise, + /// refers to all tables in the query (just *). + @nullable + String get table; + + StarResultColumn._(); + factory StarResultColumn.from({String table}) => + StarResultColumn((b) => b.table = table); + + factory StarResultColumn(Function(StarResultColumnBuilder builder) updates) = + _$StarResultColumn; +} diff --git a/moor_generator/lib/src/sql/ast/select/columns.g.dart b/moor_generator/lib/src/sql/ast/select/columns.g.dart new file mode 100644 index 00000000..72dcf5cc --- /dev/null +++ b/moor_generator/lib/src/sql/ast/select/columns.g.dart @@ -0,0 +1,175 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'columns.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +class _$ExprResultColumn extends ExprResultColumn { + @override + final Expression expr; + @override + final String alias; + + factory _$ExprResultColumn( + [void Function(ExprResultColumnBuilder) updates]) => + (new ExprResultColumnBuilder()..update(updates)).build(); + + _$ExprResultColumn._({this.expr, this.alias}) : super._() { + if (expr == null) { + throw new BuiltValueNullFieldError('ExprResultColumn', 'expr'); + } + } + + @override + ExprResultColumn rebuild(void Function(ExprResultColumnBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + ExprResultColumnBuilder toBuilder() => + new ExprResultColumnBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is ExprResultColumn && + expr == other.expr && + alias == other.alias; + } + + @override + int get hashCode { + return $jf($jc($jc(0, expr.hashCode), alias.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('ExprResultColumn') + ..add('expr', expr) + ..add('alias', alias)) + .toString(); + } +} + +class ExprResultColumnBuilder + implements Builder { + _$ExprResultColumn _$v; + + Expression _expr; + Expression get expr => _$this._expr; + set expr(Expression expr) => _$this._expr = expr; + + String _alias; + String get alias => _$this._alias; + set alias(String alias) => _$this._alias = alias; + + ExprResultColumnBuilder(); + + ExprResultColumnBuilder get _$this { + if (_$v != null) { + _expr = _$v.expr; + _alias = _$v.alias; + _$v = null; + } + return this; + } + + @override + void replace(ExprResultColumn other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$ExprResultColumn; + } + + @override + void update(void Function(ExprResultColumnBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$ExprResultColumn build() { + final _$result = _$v ?? new _$ExprResultColumn._(expr: expr, alias: alias); + replace(_$result); + return _$result; + } +} + +class _$StarResultColumn extends StarResultColumn { + @override + final String table; + + factory _$StarResultColumn( + [void Function(StarResultColumnBuilder) updates]) => + (new StarResultColumnBuilder()..update(updates)).build(); + + _$StarResultColumn._({this.table}) : super._(); + + @override + StarResultColumn rebuild(void Function(StarResultColumnBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + StarResultColumnBuilder toBuilder() => + new StarResultColumnBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is StarResultColumn && table == other.table; + } + + @override + int get hashCode { + return $jf($jc(0, table.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('StarResultColumn') + ..add('table', table)) + .toString(); + } +} + +class StarResultColumnBuilder + implements Builder { + _$StarResultColumn _$v; + + String _table; + String get table => _$this._table; + set table(String table) => _$this._table = table; + + StarResultColumnBuilder(); + + StarResultColumnBuilder get _$this { + if (_$v != null) { + _table = _$v.table; + _$v = null; + } + return this; + } + + @override + void replace(StarResultColumn other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$StarResultColumn; + } + + @override + void update(void Function(StarResultColumnBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$StarResultColumn build() { + final _$result = _$v ?? new _$StarResultColumn._(table: table); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/moor_generator/lib/src/sql/ast/select/select.dart b/moor_generator/lib/src/sql/ast/select/select.dart new file mode 100644 index 00000000..e4605363 --- /dev/null +++ b/moor_generator/lib/src/sql/ast/select/select.dart @@ -0,0 +1,40 @@ +import 'package:built_value/built_value.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:moor_generator/src/sql/ast/expressions/expressions.dart'; +import 'columns.dart'; + +part 'select.g.dart'; + +abstract class SelectStatement + implements Built { + BuiltList get columns; + BuiltList get from; + @nullable + Expression get where; + @nullable + Limit get limit; + + SelectStatement._(); + factory SelectStatement(void Function(SelectStatementBuilder) builder) = + _$SelectStatement; +} + +/// Anything that can appear behind a "FROM" clause in a select statement. +abstract class SelectTarget {} + +abstract class TableTarget implements Built { + String get table; + + TableTarget._(); + factory TableTarget(void Function(TableTargetBuilder) builder) = + _$TableTarget; +} + +abstract class Limit implements Built { + Expression get amount; + @nullable + Expression get offset; + + Limit._(); + factory Limit(void Function(LimitBuilder) builder) = _$Limit; +} diff --git a/moor_generator/lib/src/sql/ast/select/select.g.dart b/moor_generator/lib/src/sql/ast/select/select.g.dart new file mode 100644 index 00000000..0d3a4104 --- /dev/null +++ b/moor_generator/lib/src/sql/ast/select/select.g.dart @@ -0,0 +1,308 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'select.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +class _$SelectStatement extends SelectStatement { + @override + final BuiltList columns; + @override + final BuiltList from; + @override + final Expression where; + @override + final Limit limit; + + factory _$SelectStatement([void Function(SelectStatementBuilder) updates]) => + (new SelectStatementBuilder()..update(updates)).build(); + + _$SelectStatement._({this.columns, this.from, this.where, this.limit}) + : super._() { + if (columns == null) { + throw new BuiltValueNullFieldError('SelectStatement', 'columns'); + } + if (from == null) { + throw new BuiltValueNullFieldError('SelectStatement', 'from'); + } + } + + @override + SelectStatement rebuild(void Function(SelectStatementBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + SelectStatementBuilder toBuilder() => + new SelectStatementBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is SelectStatement && + columns == other.columns && + from == other.from && + where == other.where && + limit == other.limit; + } + + @override + int get hashCode { + return $jf($jc( + $jc($jc($jc(0, columns.hashCode), from.hashCode), where.hashCode), + limit.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('SelectStatement') + ..add('columns', columns) + ..add('from', from) + ..add('where', where) + ..add('limit', limit)) + .toString(); + } +} + +class SelectStatementBuilder + implements Builder { + _$SelectStatement _$v; + + ListBuilder _columns; + ListBuilder get columns => + _$this._columns ??= new ListBuilder(); + set columns(ListBuilder columns) => _$this._columns = columns; + + ListBuilder _from; + ListBuilder get from => + _$this._from ??= new ListBuilder(); + set from(ListBuilder from) => _$this._from = from; + + Expression _where; + Expression get where => _$this._where; + set where(Expression where) => _$this._where = where; + + LimitBuilder _limit; + LimitBuilder get limit => _$this._limit ??= new LimitBuilder(); + set limit(LimitBuilder limit) => _$this._limit = limit; + + SelectStatementBuilder(); + + SelectStatementBuilder get _$this { + if (_$v != null) { + _columns = _$v.columns?.toBuilder(); + _from = _$v.from?.toBuilder(); + _where = _$v.where; + _limit = _$v.limit?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(SelectStatement other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$SelectStatement; + } + + @override + void update(void Function(SelectStatementBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$SelectStatement build() { + _$SelectStatement _$result; + try { + _$result = _$v ?? + new _$SelectStatement._( + columns: columns.build(), + from: from.build(), + where: where, + limit: _limit?.build()); + } catch (_) { + String _$failedField; + try { + _$failedField = 'columns'; + columns.build(); + _$failedField = 'from'; + from.build(); + + _$failedField = 'limit'; + _limit?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + 'SelectStatement', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$TableTarget extends TableTarget { + @override + final String table; + + factory _$TableTarget([void Function(TableTargetBuilder) updates]) => + (new TableTargetBuilder()..update(updates)).build(); + + _$TableTarget._({this.table}) : super._() { + if (table == null) { + throw new BuiltValueNullFieldError('TableTarget', 'table'); + } + } + + @override + TableTarget rebuild(void Function(TableTargetBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + TableTargetBuilder toBuilder() => new TableTargetBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is TableTarget && table == other.table; + } + + @override + int get hashCode { + return $jf($jc(0, table.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('TableTarget')..add('table', table)) + .toString(); + } +} + +class TableTargetBuilder implements Builder { + _$TableTarget _$v; + + String _table; + String get table => _$this._table; + set table(String table) => _$this._table = table; + + TableTargetBuilder(); + + TableTargetBuilder get _$this { + if (_$v != null) { + _table = _$v.table; + _$v = null; + } + return this; + } + + @override + void replace(TableTarget other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$TableTarget; + } + + @override + void update(void Function(TableTargetBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$TableTarget build() { + final _$result = _$v ?? new _$TableTarget._(table: table); + replace(_$result); + return _$result; + } +} + +class _$Limit extends Limit { + @override + final Expression amount; + @override + final Expression offset; + + factory _$Limit([void Function(LimitBuilder) updates]) => + (new LimitBuilder()..update(updates)).build(); + + _$Limit._({this.amount, this.offset}) : super._() { + if (amount == null) { + throw new BuiltValueNullFieldError('Limit', 'amount'); + } + } + + @override + Limit rebuild(void Function(LimitBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + LimitBuilder toBuilder() => new LimitBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Limit && amount == other.amount && offset == other.offset; + } + + @override + int get hashCode { + return $jf($jc($jc(0, amount.hashCode), offset.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('Limit') + ..add('amount', amount) + ..add('offset', offset)) + .toString(); + } +} + +class LimitBuilder implements Builder { + _$Limit _$v; + + Expression _amount; + Expression get amount => _$this._amount; + set amount(Expression amount) => _$this._amount = amount; + + Expression _offset; + Expression get offset => _$this._offset; + set offset(Expression offset) => _$this._offset = offset; + + LimitBuilder(); + + LimitBuilder get _$this { + if (_$v != null) { + _amount = _$v.amount; + _offset = _$v.offset; + _$v = null; + } + return this; + } + + @override + void replace(Limit other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$Limit; + } + + @override + void update(void Function(LimitBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$Limit build() { + final _$result = _$v ?? new _$Limit._(amount: amount, offset: offset); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/moor_generator/lib/src/sql/parser/grammar/grammar.dart b/moor_generator/lib/src/sql/parser/grammar/grammar.dart new file mode 100644 index 00000000..11ec259d --- /dev/null +++ b/moor_generator/lib/src/sql/parser/grammar/grammar.dart @@ -0,0 +1,67 @@ +import '../parser.dart'; + +class SqlGrammar extends GrammarParser { + SqlGrammar() : super(const SqlGrammarDefinition()); +} + +class SqlGrammarDefinition extends GrammarDefinition { + const SqlGrammarDefinition(); + @override + Parser start() => ref(select).end(); + + Parser select() => string('SELECT') & ref(resultColumns).trim(); + + Parser resultColumns() => ref(resultColumn) + .separatedBy(ref(comma).trim().flatten(), includeSeparators: false); + Parser resultColumn() => ref(exprColumn).or(ref(starColumn)); + Parser exprColumn() => + ref(expression) & + (string('AS').trim() & ref(identifier).trim()).optional(); + Parser starColumn() => (tableName() & ref(dot)).optional() & ref(star); + + // todo these + Parser tableName() => string('tableName'); + Parser identifier() => string('id'); + + Parser comma() => char(','); + Parser dot() => char('.'); + Parser star() => char('*'); + Parser hexDigit() => anyOf('0123456789abcdefABCDEF'); + + // expressions + Parser expression() => ref(literal); + + Parser literal() => + ref(stringLiteral) | + ref(numericLiteral) | + ref(nullLiteral) | + ref(trueLiteral) | + ref(falseLiteral) | + ref(currentTimeLiteral) | + ref(currentDateLiteral) | + ref(currentTimestampLiteral); + Parser stringLiteral() => + (anyOf('xX').optional() & char("'") & noneOf("'").star() & char("'")) + .flatten(); // todo support '' for escaping + Parser numericLiteral() { + final hex = stringIgnoreCase('0x') & ref(hexDigit).plus(); + final exponent = + (anyOf('eE') & (char('+') | char('-')).optional('+') & digit().plus()) + .flatten(); + + final numberWithTrailingDecimalPoint = ref(dot) & digit().plus(); + final regularNumber = + digit().plus() & (ref(dot) & digit().star()).optional(); + + final numeric = (numberWithTrailingDecimalPoint | regularNumber).flatten(); + + return hex | (numeric & exponent.optional()); + } + + Parser nullLiteral() => stringIgnoreCase('NULL'); + Parser trueLiteral() => stringIgnoreCase('TRUE'); + Parser falseLiteral() => stringIgnoreCase('FALSE'); + Parser currentTimeLiteral() => stringIgnoreCase('CURRENT_TIME'); + Parser currentDateLiteral() => stringIgnoreCase('CURRENT_DATE'); + Parser currentTimestampLiteral() => stringIgnoreCase('CURRENT_TIMESTAMP'); +} diff --git a/moor_generator/lib/src/sql/parser/grammar/not_a_keyword.dart b/moor_generator/lib/src/sql/parser/grammar/not_a_keyword.dart new file mode 100644 index 00000000..b2d3f84e --- /dev/null +++ b/moor_generator/lib/src/sql/parser/grammar/not_a_keyword.dart @@ -0,0 +1,27 @@ +import 'package:moor/sqlite_keywords.dart'; +import 'package:petitparser/petitparser.dart'; + +/// A parser that accepts any word as long as it's not a sql keyword. +class NotAKeywordParser extends Parser { + final Parser _inner = word().star().flatten(); + + @override + Parser copy() { + return this; + } + + @override + Result parseOn(Context context) { + final innerResult = _inner.parseOn(context); + + if (innerResult.isFailure) { + return innerResult; + } + + if (sqliteKeywords.contains(innerResult.value.toUpperCase())) { + return innerResult.failure('did not expect a sqlite keyword'); + } + + return innerResult; + } +} diff --git a/moor_generator/lib/src/sql/parser/grammar/select.dart b/moor_generator/lib/src/sql/parser/grammar/select.dart new file mode 100644 index 00000000..e69de29b diff --git a/moor_generator/lib/src/sql/parser/parser.dart b/moor_generator/lib/src/sql/parser/parser.dart new file mode 100644 index 00000000..a2214006 --- /dev/null +++ b/moor_generator/lib/src/sql/parser/parser.dart @@ -0,0 +1,3 @@ +export 'package:petitparser/petitparser.dart'; +export 'grammar/grammar.dart'; +export 'parser/parser.dart'; diff --git a/moor_generator/lib/src/sql/parser/parser/parser.dart b/moor_generator/lib/src/sql/parser/parser/parser.dart new file mode 100644 index 00000000..03e13a7d --- /dev/null +++ b/moor_generator/lib/src/sql/parser/parser/parser.dart @@ -0,0 +1,85 @@ +import 'package:moor_generator/src/sql/ast/expressions/expressions.dart'; +import 'package:moor_generator/src/sql/ast/expressions/literals.dart'; +import 'package:moor_generator/src/sql/ast/select/columns.dart'; +import 'package:moor_generator/src/sql/parser/parser.dart'; + +class SemanticSqlParser extends GrammarParser { + SemanticSqlParser() : super(SemanticSqlParserDefinition()); +} + +class SemanticSqlParserDefinition extends SqlGrammarDefinition { + @override + Parser starColumn() { + return super.starColumn().map((each) { + final typedEach = each as List; + if (typedEach.first == null) { + // just a simple star column + return StarResultColumn.from(); + } else { + return StarResultColumn.from( + table: (typedEach.first as List).first as String); + } + }); + } + + @override + Parser exprColumn() { + return super.exprColumn().map((each) { + // [expression, [AS, "column_name"]] or just [expression, null] + final typedEach = each as List; + final expression = typedEach.first as Expression; + final alias = typedEach[1] != null ? typedEach[1][1] as String : null; + + return ExprResultColumn((b) => b + ..expr = expression + ..alias = alias); + }); + } + + @override + Parser currentTimestampLiteral() { + return super.currentTimestampLiteral().map( + (e) => CurrentTimeResolver.mode(CurrentTimeAccessor.currentTimestamp)); + } + + @override + Parser currentDateLiteral() { + return super + .currentDateLiteral() + .map((_) => CurrentTimeResolver.mode(CurrentTimeAccessor.currentDate)); + } + + @override + Parser currentTimeLiteral() { + return super + .currentTimeLiteral() + .map((_) => CurrentTimeResolver.mode(CurrentTimeAccessor.currentTime)); + } + + @override + Parser falseLiteral() => + super.falseLiteral().map((_) => BooleanLiteral.from(false)); + + @override + Parser trueLiteral() => + super.falseLiteral().map((_) => BooleanLiteral.from(true)); + + @override + Parser nullLiteral() => super.nullLiteral().map((_) => const NullLiteral()); + + @override + Parser numericLiteral() { + // we don't really care about the value, we just need to know the type + return super + .numericLiteral() + .map((_) => NumericLiteral((b) => b.value = 0)); + } + + @override + Parser stringLiteral() { + // same here, only the type is relevant, the actual content doesn't matter. + return super + .stringLiteral() + .map((_) => StringLiteral((b) => b.content = 'hi')); + } +} diff --git a/moor_generator/lib/src/sql/sql.dart b/moor_generator/lib/src/sql/sql.dart new file mode 100644 index 00000000..e69de29b diff --git a/moor_generator/test/sql/not_a_keyword_test.dart b/moor_generator/test/sql/not_a_keyword_test.dart new file mode 100644 index 00000000..14675d7d --- /dev/null +++ b/moor_generator/test/sql/not_a_keyword_test.dart @@ -0,0 +1,18 @@ +import 'package:moor_generator/src/sql/parser/grammar/not_a_keyword.dart'; +import 'package:test_api/test_api.dart'; + +final parser = NotAKeywordParser(); +final withTrim = parser.trim(); + +void main() { + test('does not accept sqlite keywords', () { + expect(parser.parse('SELECT').isSuccess, isFalse); + expect(parser.parse('USING').isSuccess, isFalse); + expect(withTrim.parse(' PRAGMA ').isSuccess, isFalse); + }); + + test('does accept words that are not sqlite keywords', () { + expect(parser.parse('users').isSuccess, isTrue); + expect(parser.parse('is_awesome').isSuccess, isTrue); + }); +} diff --git a/moor_generator/test/sql/parser_tests.dart b/moor_generator/test/sql/parser_tests.dart new file mode 100644 index 00000000..aa15e1ac --- /dev/null +++ b/moor_generator/test/sql/parser_tests.dart @@ -0,0 +1,12 @@ +import 'package:moor_generator/src/sql/parser/parser.dart'; +import 'package:test_api/test_api.dart'; + +void main() { + test('test', () { + final grammar = SqlGrammar(); + final semantics = SemanticSqlParser(); + + print(grammar.parse("SELECT 'lol' AS test")); + print(semantics.parse('SELECT *, tableName.*, NULL AS id')); + }); +} From cafaafe2eb98976aa4a542089c456a65f03b44c5 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 5 Jun 2019 22:30:40 +0200 Subject: [PATCH 2/9] Start with new custom scanner --- .../lib/src/sql/parser/grammar/grammar.dart | 67 ----- .../src/sql/parser/grammar/not_a_keyword.dart | 27 -- .../lib/src/sql/parser/grammar/select.dart | 0 moor_generator/lib/src/sql/parser/parser.dart | 3 - .../lib/src/sql/parser/parser/parser.dart | 85 ------- .../lib/src/sql/parser/tokenizer/scanner.dart | 237 ++++++++++++++++++ .../lib/src/sql/parser/tokenizer/token.dart | 47 ++++ .../lib/src/sql/parser/tokenizer/utils.dart | 18 ++ moor_generator/pubspec.yaml | 1 + .../test/sql/not_a_keyword_test.dart | 18 -- moor_generator/test/sql/parser_tests.dart | 12 - .../test/sql/scanner/single_token_tests.dart | 33 +++ 12 files changed, 336 insertions(+), 212 deletions(-) delete mode 100644 moor_generator/lib/src/sql/parser/grammar/grammar.dart delete mode 100644 moor_generator/lib/src/sql/parser/grammar/not_a_keyword.dart delete mode 100644 moor_generator/lib/src/sql/parser/grammar/select.dart delete mode 100644 moor_generator/lib/src/sql/parser/parser.dart delete mode 100644 moor_generator/lib/src/sql/parser/parser/parser.dart create mode 100644 moor_generator/lib/src/sql/parser/tokenizer/scanner.dart create mode 100644 moor_generator/lib/src/sql/parser/tokenizer/token.dart create mode 100644 moor_generator/lib/src/sql/parser/tokenizer/utils.dart delete mode 100644 moor_generator/test/sql/not_a_keyword_test.dart delete mode 100644 moor_generator/test/sql/parser_tests.dart create mode 100644 moor_generator/test/sql/scanner/single_token_tests.dart diff --git a/moor_generator/lib/src/sql/parser/grammar/grammar.dart b/moor_generator/lib/src/sql/parser/grammar/grammar.dart deleted file mode 100644 index 11ec259d..00000000 --- a/moor_generator/lib/src/sql/parser/grammar/grammar.dart +++ /dev/null @@ -1,67 +0,0 @@ -import '../parser.dart'; - -class SqlGrammar extends GrammarParser { - SqlGrammar() : super(const SqlGrammarDefinition()); -} - -class SqlGrammarDefinition extends GrammarDefinition { - const SqlGrammarDefinition(); - @override - Parser start() => ref(select).end(); - - Parser select() => string('SELECT') & ref(resultColumns).trim(); - - Parser resultColumns() => ref(resultColumn) - .separatedBy(ref(comma).trim().flatten(), includeSeparators: false); - Parser resultColumn() => ref(exprColumn).or(ref(starColumn)); - Parser exprColumn() => - ref(expression) & - (string('AS').trim() & ref(identifier).trim()).optional(); - Parser starColumn() => (tableName() & ref(dot)).optional() & ref(star); - - // todo these - Parser tableName() => string('tableName'); - Parser identifier() => string('id'); - - Parser comma() => char(','); - Parser dot() => char('.'); - Parser star() => char('*'); - Parser hexDigit() => anyOf('0123456789abcdefABCDEF'); - - // expressions - Parser expression() => ref(literal); - - Parser literal() => - ref(stringLiteral) | - ref(numericLiteral) | - ref(nullLiteral) | - ref(trueLiteral) | - ref(falseLiteral) | - ref(currentTimeLiteral) | - ref(currentDateLiteral) | - ref(currentTimestampLiteral); - Parser stringLiteral() => - (anyOf('xX').optional() & char("'") & noneOf("'").star() & char("'")) - .flatten(); // todo support '' for escaping - Parser numericLiteral() { - final hex = stringIgnoreCase('0x') & ref(hexDigit).plus(); - final exponent = - (anyOf('eE') & (char('+') | char('-')).optional('+') & digit().plus()) - .flatten(); - - final numberWithTrailingDecimalPoint = ref(dot) & digit().plus(); - final regularNumber = - digit().plus() & (ref(dot) & digit().star()).optional(); - - final numeric = (numberWithTrailingDecimalPoint | regularNumber).flatten(); - - return hex | (numeric & exponent.optional()); - } - - Parser nullLiteral() => stringIgnoreCase('NULL'); - Parser trueLiteral() => stringIgnoreCase('TRUE'); - Parser falseLiteral() => stringIgnoreCase('FALSE'); - Parser currentTimeLiteral() => stringIgnoreCase('CURRENT_TIME'); - Parser currentDateLiteral() => stringIgnoreCase('CURRENT_DATE'); - Parser currentTimestampLiteral() => stringIgnoreCase('CURRENT_TIMESTAMP'); -} diff --git a/moor_generator/lib/src/sql/parser/grammar/not_a_keyword.dart b/moor_generator/lib/src/sql/parser/grammar/not_a_keyword.dart deleted file mode 100644 index b2d3f84e..00000000 --- a/moor_generator/lib/src/sql/parser/grammar/not_a_keyword.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:moor/sqlite_keywords.dart'; -import 'package:petitparser/petitparser.dart'; - -/// A parser that accepts any word as long as it's not a sql keyword. -class NotAKeywordParser extends Parser { - final Parser _inner = word().star().flatten(); - - @override - Parser copy() { - return this; - } - - @override - Result parseOn(Context context) { - final innerResult = _inner.parseOn(context); - - if (innerResult.isFailure) { - return innerResult; - } - - if (sqliteKeywords.contains(innerResult.value.toUpperCase())) { - return innerResult.failure('did not expect a sqlite keyword'); - } - - return innerResult; - } -} diff --git a/moor_generator/lib/src/sql/parser/grammar/select.dart b/moor_generator/lib/src/sql/parser/grammar/select.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/moor_generator/lib/src/sql/parser/parser.dart b/moor_generator/lib/src/sql/parser/parser.dart deleted file mode 100644 index a2214006..00000000 --- a/moor_generator/lib/src/sql/parser/parser.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'package:petitparser/petitparser.dart'; -export 'grammar/grammar.dart'; -export 'parser/parser.dart'; diff --git a/moor_generator/lib/src/sql/parser/parser/parser.dart b/moor_generator/lib/src/sql/parser/parser/parser.dart deleted file mode 100644 index 03e13a7d..00000000 --- a/moor_generator/lib/src/sql/parser/parser/parser.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:moor_generator/src/sql/ast/expressions/expressions.dart'; -import 'package:moor_generator/src/sql/ast/expressions/literals.dart'; -import 'package:moor_generator/src/sql/ast/select/columns.dart'; -import 'package:moor_generator/src/sql/parser/parser.dart'; - -class SemanticSqlParser extends GrammarParser { - SemanticSqlParser() : super(SemanticSqlParserDefinition()); -} - -class SemanticSqlParserDefinition extends SqlGrammarDefinition { - @override - Parser starColumn() { - return super.starColumn().map((each) { - final typedEach = each as List; - if (typedEach.first == null) { - // just a simple star column - return StarResultColumn.from(); - } else { - return StarResultColumn.from( - table: (typedEach.first as List).first as String); - } - }); - } - - @override - Parser exprColumn() { - return super.exprColumn().map((each) { - // [expression, [AS, "column_name"]] or just [expression, null] - final typedEach = each as List; - final expression = typedEach.first as Expression; - final alias = typedEach[1] != null ? typedEach[1][1] as String : null; - - return ExprResultColumn((b) => b - ..expr = expression - ..alias = alias); - }); - } - - @override - Parser currentTimestampLiteral() { - return super.currentTimestampLiteral().map( - (e) => CurrentTimeResolver.mode(CurrentTimeAccessor.currentTimestamp)); - } - - @override - Parser currentDateLiteral() { - return super - .currentDateLiteral() - .map((_) => CurrentTimeResolver.mode(CurrentTimeAccessor.currentDate)); - } - - @override - Parser currentTimeLiteral() { - return super - .currentTimeLiteral() - .map((_) => CurrentTimeResolver.mode(CurrentTimeAccessor.currentTime)); - } - - @override - Parser falseLiteral() => - super.falseLiteral().map((_) => BooleanLiteral.from(false)); - - @override - Parser trueLiteral() => - super.falseLiteral().map((_) => BooleanLiteral.from(true)); - - @override - Parser nullLiteral() => super.nullLiteral().map((_) => const NullLiteral()); - - @override - Parser numericLiteral() { - // we don't really care about the value, we just need to know the type - return super - .numericLiteral() - .map((_) => NumericLiteral((b) => b.value = 0)); - } - - @override - Parser stringLiteral() { - // same here, only the type is relevant, the actual content doesn't matter. - return super - .stringLiteral() - .map((_) => StringLiteral((b) => b.content = 'hi')); - } -} diff --git a/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart b/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart new file mode 100644 index 00000000..a24fa6d4 --- /dev/null +++ b/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart @@ -0,0 +1,237 @@ +import 'package:moor_generator/src/sql/parser/tokenizer/token.dart'; +import 'package:moor_generator/src/sql/parser/tokenizer/utils.dart'; +import 'package:source_span/source_span.dart'; + +class Scanner { + final String source; + + final List tokens = []; + final List errors = []; + + int _startOffset; + int _currentOffset = 0; + bool get _isAtEnd => _currentOffset >= source.length; + + SourceSpan get _currentSpan { + return SourceSpan(_startLocation, _currentLocation, + source.substring(_startOffset, _currentOffset)); + } + + SourceLocation get _startLocation { + return SourceLocation(_startOffset); + } + + SourceLocation get _currentLocation { + return SourceLocation(_currentOffset); + } + + Scanner(this.source); + + List scanTokens() { + while (!_isAtEnd) { + _startOffset = _currentOffset; + _scanToken(); + } + + final endLoc = SourceLocation(source.length); + tokens.add(Token(TokenType.eof, SourceSpan(endLoc, endLoc, ''))); + return tokens; + } + + void _scanToken() { + final char = _nextChar(); + switch (char) { + case '(': + _addToken(TokenType.leftParen); + break; + case ')': + _addToken(TokenType.rightParen); + break; + case ',': + _addToken(TokenType.comma); + break; + case '.': + if (!_isAtEnd && isDigit(_peek())) { + _numeric(char); + } else { + _addToken(TokenType.dot); + } + break; + case '+': + _addToken(TokenType.plus); + break; + case '-': + _addToken(TokenType.minus); + break; + case '*': + _addToken(TokenType.star); + break; + case '/': + _addToken(TokenType.slash); + break; + + case '<': + _addToken(_match('=') ? TokenType.lessEqual : TokenType.less); + break; + case '>': + _addToken(_match('=') ? TokenType.moreEqual : TokenType.more); + break; + case '=': + _addToken(TokenType.equal); + break; + + case 'x': + if (_match("'")) { + _string(binary: false); + } else { + // todo probably an identifier if it doesn't start a string literal? + } + break; + case "'": + _string(); + break; + case ' ': + case '\t': + case '\n': + // ignore whitespace + break; + + default: + if (isDigit(char)) { + _numeric(char); + } + errors.add(TokenizerError( + 'Unexpected character.', SourceLocation(_currentOffset))); + break; + } + } + + String _nextChar() { + _currentOffset++; + return source.substring(_currentOffset - 1, _currentOffset); + } + + String _peek() { + if (_isAtEnd) throw StateError('Reached end of source'); + return source.substring(_currentOffset, _currentOffset + 1); + } + + bool _match(String expected) { + if (_isAtEnd) return false; + if (source.substring(_currentOffset, 1) != expected) return false; + _currentOffset++; + return true; + } + + void _addToken(TokenType type) { + tokens.add(Token(type, _currentSpan)); + } + + void _string({bool binary = false}) { + while (_peek() != "'" && !_isAtEnd) { + _nextChar(); + } + + // Issue an error if the string is unterminated + if (_isAtEnd) { + errors.add(TokenizerError('Unterminated string', _currentLocation)); + } + + // consume the closing "'" + _nextChar(); + + final value = source.substring(_startOffset + 1, _currentOffset - 1); + tokens.add(StringLiteral(value, _currentSpan, binary: binary)); + } + + void _numeric(String firstChar) { + // https://www.sqlite.org/syntax/numeric-literal.html + + // We basically have three cases: hexadecimal numbers (starting with 0x), + // numbers starting with a decimal dot and numbers starting with a digit. + if (firstChar == '0') { + if (!_isAtEnd && _peek() == 'x') { + _nextChar(); // consume the x + // advance hexadecimal digits + while (isDigit(_peek()) && _isAtEnd) { + _nextChar(); + _addToken(TokenType.numberLiteral); + return; + } + } + } + + void consumeDigits() { + while (!_isAtEnd && isDigit(_peek())) { + _nextChar(); + } + } + + /// Returns true without advancing if the next char is a digit. Returns + /// false and logs an error with the message otherwise. + bool _requireDigit(String message) { + final noDigit = _isAtEnd || !isDigit(_peek()); + if (noDigit) { + errors.add(TokenizerError(message, _currentLocation)); + } + return !noDigit; + } + + // ok, we're not dealing with a hexadecimal number. + if (firstChar == '.') { + // started with a decimal point. the next char has to be numeric + if (_requireDigit('Expected a digit after the decimal dot')) { + consumeDigits(); + } + } else { + // ok, not starting with a decimal dot. In that case, the first char must + // be a digit + if (!isDigit(firstChar)) { + errors.add(TokenizerError('Expected a digit', _currentLocation)); + return; + } + consumeDigits(); + + // optional decimal part + if (!_isAtEnd && _peek() == '.') { + _nextChar(); + // if there is a decimal separator, there must be at least one digit + // after it + if (_requireDigit('Expected a digit after the decimal dot')) { + consumeDigits(); + } else { + return; + } + } + } + + // ok, we've read the first part of the number. But there's more! If it's + // not a hexadecimal number, it could be in scientific notation. + if (!_isAtEnd && _peek() == 'e' || _peek() == 'E') { + _nextChar(); // consume e or E + + if (_isAtEnd) { + errors.add(TokenizerError( + 'Unexpected end of file. Expected digits for the scientific notation', + _currentLocation)); + return; + } + + final char = _nextChar(); + if (isDigit(char)) { + consumeDigits(); + _addToken(TokenType.numberLiteral); + return; + } else { + if (char == '+' || char == '-') { + _requireDigit('Expected digits for the exponent'); + consumeDigits(); + _addToken(TokenType.numberLiteral); + } else { + errors + .add(TokenizerError('Expected plus or minus', _currentLocation)); + } + } + } + } +} diff --git a/moor_generator/lib/src/sql/parser/tokenizer/token.dart b/moor_generator/lib/src/sql/parser/tokenizer/token.dart new file mode 100644 index 00000000..5554b7b9 --- /dev/null +++ b/moor_generator/lib/src/sql/parser/tokenizer/token.dart @@ -0,0 +1,47 @@ +import 'package:source_span/source_span.dart'; + +enum TokenType { + leftParen, + rightParen, + comma, + dot, + plus, + minus, + star, + slash, + less, + lessEqual, + more, + moreEqual, + equal, + + stringLiteral, + numberLiteral, + + eof, +} + +class Token { + final TokenType type; + + final SourceSpan span; + + const Token(this.type, this.span); +} + +class StringLiteral extends Token { + final String value; + + /// sqlite allows binary strings (x'literal') which are interpreted as blobs. + final bool binary; + + const StringLiteral(this.value, SourceSpan span, {this.binary = false}) + : super(TokenType.stringLiteral, span); +} + +class TokenizerError { + final String message; + final SourceLocation location; + + TokenizerError(this.message, this.location); +} diff --git a/moor_generator/lib/src/sql/parser/tokenizer/utils.dart b/moor_generator/lib/src/sql/parser/tokenizer/utils.dart new file mode 100644 index 00000000..ae09b872 --- /dev/null +++ b/moor_generator/lib/src/sql/parser/tokenizer/utils.dart @@ -0,0 +1,18 @@ +const _charCodeZero = 48; // '0'.codeUnitAt(0); +const _charCodeNine = 57; // '9'.codeUnitAt(0); +const _charCodeLowerA = 97; // 'a'.codeUnitAt(0); +const _charCodeLowerF = 102; // 'f'.codeUnitAt(0); +const _charCodeA = 65; // 'A'.codeUnitAt(0); +const _charCodeF = 79; // 'F'.codeUnitAt(0); + +bool isDigit(String char) { + final code = char.codeUnitAt(0); + return _charCodeZero <= code && code <= _charCodeNine; +} + +bool isHexDigit(String char) { + final code = char.codeUnitAt(0); + + return (_charCodeLowerA <= code && code <= _charCodeLowerF) || + (_charCodeA <= code && code <= _charCodeF); +} diff --git a/moor_generator/pubspec.yaml b/moor_generator/pubspec.yaml index c8303dc3..6026f727 100644 --- a/moor_generator/pubspec.yaml +++ b/moor_generator/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: recase: ^2.0.1 built_value: '>=6.3.0 <7.0.0' source_gen: ^0.9.4 + source_span: ^1.5.5 build: ^1.1.0 build_config: '>=0.3.1 <1.0.0' moor: ^1.4.0 diff --git a/moor_generator/test/sql/not_a_keyword_test.dart b/moor_generator/test/sql/not_a_keyword_test.dart deleted file mode 100644 index 14675d7d..00000000 --- a/moor_generator/test/sql/not_a_keyword_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:moor_generator/src/sql/parser/grammar/not_a_keyword.dart'; -import 'package:test_api/test_api.dart'; - -final parser = NotAKeywordParser(); -final withTrim = parser.trim(); - -void main() { - test('does not accept sqlite keywords', () { - expect(parser.parse('SELECT').isSuccess, isFalse); - expect(parser.parse('USING').isSuccess, isFalse); - expect(withTrim.parse(' PRAGMA ').isSuccess, isFalse); - }); - - test('does accept words that are not sqlite keywords', () { - expect(parser.parse('users').isSuccess, isTrue); - expect(parser.parse('is_awesome').isSuccess, isTrue); - }); -} diff --git a/moor_generator/test/sql/parser_tests.dart b/moor_generator/test/sql/parser_tests.dart deleted file mode 100644 index aa15e1ac..00000000 --- a/moor_generator/test/sql/parser_tests.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:moor_generator/src/sql/parser/parser.dart'; -import 'package:test_api/test_api.dart'; - -void main() { - test('test', () { - final grammar = SqlGrammar(); - final semantics = SemanticSqlParser(); - - print(grammar.parse("SELECT 'lol' AS test")); - print(semantics.parse('SELECT *, tableName.*, NULL AS id')); - }); -} diff --git a/moor_generator/test/sql/scanner/single_token_tests.dart b/moor_generator/test/sql/scanner/single_token_tests.dart new file mode 100644 index 00000000..dba86e83 --- /dev/null +++ b/moor_generator/test/sql/scanner/single_token_tests.dart @@ -0,0 +1,33 @@ +import 'package:moor_generator/src/sql/parser/tokenizer/scanner.dart'; +import 'package:moor_generator/src/sql/parser/tokenizer/token.dart'; +import 'package:test_api/test_api.dart'; + +void expectFullToken(String token, TokenType type) { + final scanner = Scanner(token); + List tokens; + try { + tokens = scanner.scanTokens(); + } catch (e, s) { + print(e); + print(s); + fail('Parsing error while parsing $token'); + } + + if (tokens.length != 2 || tokens.last.type != TokenType.eof) { + fail( + 'Expected exactly one token when parsing $token, got ${tokens.length}'); + } + + expect(tokens.first.type, type, reason: '$token is a $type'); +} + +Map testCases = { + '.': TokenType.dot, + "'hello there'": TokenType.stringLiteral, +}; + +void main() { + test('parses single tokens', () { + testCases.forEach(expectFullToken); + }); +} From c297b27f603d25544df295c6a7de3e5ce487e542 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 6 Jun 2019 22:13:42 +0200 Subject: [PATCH 3/9] Scan identifiers --- .../lib/src/sql/parser/tokenizer/scanner.dart | 49 ++++++++++++++++--- .../lib/src/sql/parser/tokenizer/token.dart | 10 ++++ .../lib/src/sql/parser/tokenizer/utils.dart | 16 +++++- .../test/sql/scanner/single_token_tests.dart | 21 +++++++- 4 files changed, 87 insertions(+), 9 deletions(-) diff --git a/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart b/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart index a24fa6d4..afcbd616 100644 --- a/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart +++ b/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart @@ -84,12 +84,16 @@ class Scanner { if (_match("'")) { _string(binary: false); } else { - // todo probably an identifier if it doesn't start a string literal? + _identifier(); } break; case "'": _string(); break; + case '"': + // todo sqlite also allows string literals with double ticks, we don't + _identifier(escapedInQuotes: true); + break; case ' ': case '\t': case '\n': @@ -99,6 +103,8 @@ class Scanner { default: if (isDigit(char)) { _numeric(char); + } else if (canStartColumnName(char)) { + _identifier(); } errors.add(TokenizerError( 'Unexpected character.', SourceLocation(_currentOffset))); @@ -118,7 +124,9 @@ class Scanner { bool _match(String expected) { if (_isAtEnd) return false; - if (source.substring(_currentOffset, 1) != expected) return false; + if (source.substring(_currentOffset, _currentOffset + 1) != expected) { + return false; + } _currentOffset++; return true; } @@ -150,14 +158,14 @@ class Scanner { // We basically have three cases: hexadecimal numbers (starting with 0x), // numbers starting with a decimal dot and numbers starting with a digit. if (firstChar == '0') { - if (!_isAtEnd && _peek() == 'x') { + if (!_isAtEnd && (_peek() == 'x' || _peek() == 'X')) { _nextChar(); // consume the x // advance hexadecimal digits - while (isDigit(_peek()) && _isAtEnd) { + while (!_isAtEnd && isHexDigit(_peek())) { _nextChar(); - _addToken(TokenType.numberLiteral); - return; } + _addToken(TokenType.numberLiteral); + return; } } @@ -207,7 +215,7 @@ class Scanner { // ok, we've read the first part of the number. But there's more! If it's // not a hexadecimal number, it could be in scientific notation. - if (!_isAtEnd && _peek() == 'e' || _peek() == 'E') { + if (!_isAtEnd && (_peek() == 'e' || _peek() == 'E')) { _nextChar(); // consume e or E if (_isAtEnd) { @@ -232,6 +240,33 @@ class Scanner { .add(TokenizerError('Expected plus or minus', _currentLocation)); } } + } else { + // ok, no scientific notation + _addToken(TokenType.numberLiteral); + } + } + + void _identifier({bool escapedInQuotes = false}) { + if (escapedInQuotes) { + // find the closing quote + while (_peek() != '"' && !_isAtEnd) { + _nextChar(); + } + // Issue an error if the column name is unterminated + if (_isAtEnd) { + errors + .add(TokenizerError('Unterminated column name', _currentLocation)); + } else { + // consume the closing double quote + _nextChar(); + tokens.add(IdentifierToken(true, _currentSpan)); + } + } else { + while (!_isAtEnd && continuesColumnName(_peek())) { + _nextChar(); + } + + tokens.add(IdentifierToken(false, _currentSpan)); } } } diff --git a/moor_generator/lib/src/sql/parser/tokenizer/token.dart b/moor_generator/lib/src/sql/parser/tokenizer/token.dart index 5554b7b9..570fd2ea 100644 --- a/moor_generator/lib/src/sql/parser/tokenizer/token.dart +++ b/moor_generator/lib/src/sql/parser/tokenizer/token.dart @@ -17,6 +17,7 @@ enum TokenType { stringLiteral, numberLiteral, + identifier, eof, } @@ -39,6 +40,15 @@ class StringLiteral extends Token { : super(TokenType.stringLiteral, span); } +class IdentifierToken extends Token { + /// In sql, identifiers can be put in "double quotes", in which case they are + /// always interpreted as an column name. + final bool escapedColumnName; + + const IdentifierToken(this.escapedColumnName, SourceSpan span) + : super(TokenType.identifier, span); +} + class TokenizerError { final String message; final SourceLocation location; diff --git a/moor_generator/lib/src/sql/parser/tokenizer/utils.dart b/moor_generator/lib/src/sql/parser/tokenizer/utils.dart index ae09b872..9fe64eb4 100644 --- a/moor_generator/lib/src/sql/parser/tokenizer/utils.dart +++ b/moor_generator/lib/src/sql/parser/tokenizer/utils.dart @@ -4,6 +4,8 @@ const _charCodeLowerA = 97; // 'a'.codeUnitAt(0); const _charCodeLowerF = 102; // 'f'.codeUnitAt(0); const _charCodeA = 65; // 'A'.codeUnitAt(0); const _charCodeF = 79; // 'F'.codeUnitAt(0); +const _charCodeZ = 90; // 'Z'.codeUnitAt(0); +const _charCodeLowerZ = 122; // 'z'.codeUnitAt(0); bool isDigit(String char) { final code = char.codeUnitAt(0); @@ -14,5 +16,17 @@ bool isHexDigit(String char) { final code = char.codeUnitAt(0); return (_charCodeLowerA <= code && code <= _charCodeLowerF) || - (_charCodeA <= code && code <= _charCodeF); + (_charCodeA <= code && code <= _charCodeF) || + (_charCodeZero <= code && code <= _charCodeNine); +} + +bool canStartColumnName(String char) { + final code = char.codeUnitAt(0); + return char == '_' || + (_charCodeLowerA <= code && code <= _charCodeLowerZ) || + (_charCodeA <= code && code <= _charCodeZ); +} + +bool continuesColumnName(String char) { + return canStartColumnName(char) || isDigit(char); } diff --git a/moor_generator/test/sql/scanner/single_token_tests.dart b/moor_generator/test/sql/scanner/single_token_tests.dart index dba86e83..05ac1d6b 100644 --- a/moor_generator/test/sql/scanner/single_token_tests.dart +++ b/moor_generator/test/sql/scanner/single_token_tests.dart @@ -15,15 +15,34 @@ void expectFullToken(String token, TokenType type) { if (tokens.length != 2 || tokens.last.type != TokenType.eof) { fail( - 'Expected exactly one token when parsing $token, got ${tokens.length}'); + 'Expected exactly one token when parsing $token, got ${tokens.length - 1}'); } expect(tokens.first.type, type, reason: '$token is a $type'); + expect(tokens.first.span.text, token); } Map testCases = { + '(': TokenType.leftParen, + ')': TokenType.rightParen, + ',': TokenType.comma, '.': TokenType.dot, + '+': TokenType.plus, + '-': TokenType.minus, + '*': TokenType.star, + '/': TokenType.slash, + '<=': TokenType.lessEqual, + '<': TokenType.less, + '>=': TokenType.moreEqual, + '>': TokenType.more, "'hello there'": TokenType.stringLiteral, + '1.123': TokenType.numberLiteral, + '1.32e5': TokenType.numberLiteral, + '.123e-3': TokenType.numberLiteral, + '0xFF13': TokenType.numberLiteral, + '0Xf13A': TokenType.numberLiteral, + 'SELECT': TokenType.identifier, + '"UPDATE"': TokenType.identifier, }; void main() { From 52f3ee045fcce46ab48aef10527fcc078034fbe4 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 15 Jun 2019 23:01:10 +0200 Subject: [PATCH 4/9] Extract sql parser to yet another subpackage --- .../lib/src/sql/ast/expressions/literals.dart | 65 ---- .../src/sql/ast/expressions/literals.g.dart | 338 ------------------ .../lib/src/sql/ast/select/columns.dart | 34 -- .../lib/src/sql/ast/select/columns.g.dart | 175 --------- .../lib/src/sql/ast/select/select.dart | 40 --- .../lib/src/sql/ast/select/select.g.dart | 308 ---------------- moor_generator/lib/src/sql/sql.dart | 0 sqlparser/.gitignore | 11 + sqlparser/CHANGELOG.md | 2 + sqlparser/LICENSE | 21 ++ sqlparser/README.md | 3 + sqlparser/analysis_options.yaml | 1 + sqlparser/example/sqlparser_example.dart | 1 + sqlparser/lib/sqlparser.dart | 4 + .../lib/src}/ast/expressions/expressions.dart | 0 .../lib/src/ast/expressions/literals.dart | 16 + sqlparser/lib/src/ast/expressions/unary.dart | 10 + sqlparser/lib/src/reader/parser/parser.dart | 75 ++++ .../lib/src/reader}/tokenizer/scanner.dart | 44 ++- .../lib/src/reader}/tokenizer/token.dart | 40 ++- .../lib/src/reader}/tokenizer/utils.dart | 0 sqlparser/pubspec.yaml | 13 + .../test}/scanner/single_token_tests.dart | 8 +- 23 files changed, 237 insertions(+), 972 deletions(-) delete mode 100644 moor_generator/lib/src/sql/ast/expressions/literals.dart delete mode 100644 moor_generator/lib/src/sql/ast/expressions/literals.g.dart delete mode 100644 moor_generator/lib/src/sql/ast/select/columns.dart delete mode 100644 moor_generator/lib/src/sql/ast/select/columns.g.dart delete mode 100644 moor_generator/lib/src/sql/ast/select/select.dart delete mode 100644 moor_generator/lib/src/sql/ast/select/select.g.dart delete mode 100644 moor_generator/lib/src/sql/sql.dart create mode 100644 sqlparser/.gitignore create mode 100644 sqlparser/CHANGELOG.md create mode 100644 sqlparser/LICENSE create mode 100644 sqlparser/README.md create mode 120000 sqlparser/analysis_options.yaml create mode 100644 sqlparser/example/sqlparser_example.dart create mode 100644 sqlparser/lib/sqlparser.dart rename {moor_generator/lib/src/sql => sqlparser/lib/src}/ast/expressions/expressions.dart (100%) create mode 100644 sqlparser/lib/src/ast/expressions/literals.dart create mode 100644 sqlparser/lib/src/ast/expressions/unary.dart create mode 100644 sqlparser/lib/src/reader/parser/parser.dart rename {moor_generator/lib/src/sql/parser => sqlparser/lib/src/reader}/tokenizer/scanner.dart (84%) rename {moor_generator/lib/src/sql/parser => sqlparser/lib/src/reader}/tokenizer/token.dart (66%) rename {moor_generator/lib/src/sql/parser => sqlparser/lib/src/reader}/tokenizer/utils.dart (100%) create mode 100644 sqlparser/pubspec.yaml rename {moor_generator/test/sql => sqlparser/test}/scanner/single_token_tests.dart (85%) diff --git a/moor_generator/lib/src/sql/ast/expressions/literals.dart b/moor_generator/lib/src/sql/ast/expressions/literals.dart deleted file mode 100644 index e74a5c12..00000000 --- a/moor_generator/lib/src/sql/ast/expressions/literals.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:built_value/built_value.dart'; - -import 'expressions.dart'; - -part 'literals.g.dart'; - -// https://www.sqlite.org/syntax/literal-value.html - -class NullLiteral extends Expression { - const NullLiteral(); - - @override - String toString() => 'NULL'; - - @override - int get hashCode => runtimeType.hashCode; - - @override - bool operator ==(other) => identical(this, other) || other is NullLiteral; -} - -abstract class BooleanLiteral extends Expression - implements Built { - bool get value; - - BooleanLiteral._(); - factory BooleanLiteral.from(bool value) => - BooleanLiteral((b) => b.value = value); - - factory BooleanLiteral(Function(BooleanLiteralBuilder) updates) = - _$BooleanLiteral; -} - -enum CurrentTimeAccessor { currentTime, currentDate, currentTimestamp } - -/// Represents the CURRENT_TIME, CURRENT_DATE or CURRENT_TIMESTAMP mode. -abstract class CurrentTimeResolver extends Expression - implements Built { - CurrentTimeAccessor get mode; - - CurrentTimeResolver._(); - factory CurrentTimeResolver.mode(CurrentTimeAccessor mode) { - return CurrentTimeResolver((b) => b.mode = mode); - } - factory CurrentTimeResolver(Function(CurrentTimeResolverBuilder) updates) = - _$CurrentTimeResolver; -} - -abstract class NumericLiteral extends Expression - implements Built { - num get value; - NumericLiteral._(); - factory NumericLiteral(Function(NumericLiteralBuilder) updates) = - _$NumericLiteral; -} - -abstract class StringLiteral extends Expression - implements Built { - bool get isBlob; - String get content; - - StringLiteral._(); - factory StringLiteral(Function(StringLiteralBuilder) updates) = - _$StringLiteral; -} diff --git a/moor_generator/lib/src/sql/ast/expressions/literals.g.dart b/moor_generator/lib/src/sql/ast/expressions/literals.g.dart deleted file mode 100644 index fb88b05c..00000000 --- a/moor_generator/lib/src/sql/ast/expressions/literals.g.dart +++ /dev/null @@ -1,338 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'literals.dart'; - -// ************************************************************************** -// BuiltValueGenerator -// ************************************************************************** - -class _$BooleanLiteral extends BooleanLiteral { - @override - final bool value; - - factory _$BooleanLiteral([void Function(BooleanLiteralBuilder) updates]) => - (new BooleanLiteralBuilder()..update(updates)).build(); - - _$BooleanLiteral._({this.value}) : super._() { - if (value == null) { - throw new BuiltValueNullFieldError('BooleanLiteral', 'value'); - } - } - - @override - BooleanLiteral rebuild(void Function(BooleanLiteralBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - BooleanLiteralBuilder toBuilder() => - new BooleanLiteralBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is BooleanLiteral && value == other.value; - } - - @override - int get hashCode { - return $jf($jc(0, value.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('BooleanLiteral')..add('value', value)) - .toString(); - } -} - -class BooleanLiteralBuilder - implements Builder { - _$BooleanLiteral _$v; - - bool _value; - bool get value => _$this._value; - set value(bool value) => _$this._value = value; - - BooleanLiteralBuilder(); - - BooleanLiteralBuilder get _$this { - if (_$v != null) { - _value = _$v.value; - _$v = null; - } - return this; - } - - @override - void replace(BooleanLiteral other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$BooleanLiteral; - } - - @override - void update(void Function(BooleanLiteralBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$BooleanLiteral build() { - final _$result = _$v ?? new _$BooleanLiteral._(value: value); - replace(_$result); - return _$result; - } -} - -class _$CurrentTimeResolver extends CurrentTimeResolver { - @override - final CurrentTimeAccessor mode; - - factory _$CurrentTimeResolver( - [void Function(CurrentTimeResolverBuilder) updates]) => - (new CurrentTimeResolverBuilder()..update(updates)).build(); - - _$CurrentTimeResolver._({this.mode}) : super._() { - if (mode == null) { - throw new BuiltValueNullFieldError('CurrentTimeResolver', 'mode'); - } - } - - @override - CurrentTimeResolver rebuild( - void Function(CurrentTimeResolverBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - CurrentTimeResolverBuilder toBuilder() => - new CurrentTimeResolverBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is CurrentTimeResolver && mode == other.mode; - } - - @override - int get hashCode { - return $jf($jc(0, mode.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('CurrentTimeResolver') - ..add('mode', mode)) - .toString(); - } -} - -class CurrentTimeResolverBuilder - implements Builder { - _$CurrentTimeResolver _$v; - - CurrentTimeAccessor _mode; - CurrentTimeAccessor get mode => _$this._mode; - set mode(CurrentTimeAccessor mode) => _$this._mode = mode; - - CurrentTimeResolverBuilder(); - - CurrentTimeResolverBuilder get _$this { - if (_$v != null) { - _mode = _$v.mode; - _$v = null; - } - return this; - } - - @override - void replace(CurrentTimeResolver other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$CurrentTimeResolver; - } - - @override - void update(void Function(CurrentTimeResolverBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$CurrentTimeResolver build() { - final _$result = _$v ?? new _$CurrentTimeResolver._(mode: mode); - replace(_$result); - return _$result; - } -} - -class _$NumericLiteral extends NumericLiteral { - @override - final num value; - - factory _$NumericLiteral([void Function(NumericLiteralBuilder) updates]) => - (new NumericLiteralBuilder()..update(updates)).build(); - - _$NumericLiteral._({this.value}) : super._() { - if (value == null) { - throw new BuiltValueNullFieldError('NumericLiteral', 'value'); - } - } - - @override - NumericLiteral rebuild(void Function(NumericLiteralBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - NumericLiteralBuilder toBuilder() => - new NumericLiteralBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is NumericLiteral && value == other.value; - } - - @override - int get hashCode { - return $jf($jc(0, value.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('NumericLiteral')..add('value', value)) - .toString(); - } -} - -class NumericLiteralBuilder - implements Builder { - _$NumericLiteral _$v; - - num _value; - num get value => _$this._value; - set value(num value) => _$this._value = value; - - NumericLiteralBuilder(); - - NumericLiteralBuilder get _$this { - if (_$v != null) { - _value = _$v.value; - _$v = null; - } - return this; - } - - @override - void replace(NumericLiteral other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$NumericLiteral; - } - - @override - void update(void Function(NumericLiteralBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$NumericLiteral build() { - final _$result = _$v ?? new _$NumericLiteral._(value: value); - replace(_$result); - return _$result; - } -} - -class _$StringLiteral extends StringLiteral { - @override - final bool isBlob; - @override - final String content; - - factory _$StringLiteral([void Function(StringLiteralBuilder) updates]) => - (new StringLiteralBuilder()..update(updates)).build(); - - _$StringLiteral._({this.isBlob, this.content}) : super._() { - if (isBlob == null) { - throw new BuiltValueNullFieldError('StringLiteral', 'isBlob'); - } - if (content == null) { - throw new BuiltValueNullFieldError('StringLiteral', 'content'); - } - } - - @override - StringLiteral rebuild(void Function(StringLiteralBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - StringLiteralBuilder toBuilder() => new StringLiteralBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is StringLiteral && - isBlob == other.isBlob && - content == other.content; - } - - @override - int get hashCode { - return $jf($jc($jc(0, isBlob.hashCode), content.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('StringLiteral') - ..add('isBlob', isBlob) - ..add('content', content)) - .toString(); - } -} - -class StringLiteralBuilder - implements Builder { - _$StringLiteral _$v; - - bool _isBlob; - bool get isBlob => _$this._isBlob; - set isBlob(bool isBlob) => _$this._isBlob = isBlob; - - String _content; - String get content => _$this._content; - set content(String content) => _$this._content = content; - - StringLiteralBuilder(); - - StringLiteralBuilder get _$this { - if (_$v != null) { - _isBlob = _$v.isBlob; - _content = _$v.content; - _$v = null; - } - return this; - } - - @override - void replace(StringLiteral other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$StringLiteral; - } - - @override - void update(void Function(StringLiteralBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$StringLiteral build() { - final _$result = - _$v ?? new _$StringLiteral._(isBlob: isBlob, content: content); - replace(_$result); - return _$result; - } -} - -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/moor_generator/lib/src/sql/ast/select/columns.dart b/moor_generator/lib/src/sql/ast/select/columns.dart deleted file mode 100644 index 3097ff4f..00000000 --- a/moor_generator/lib/src/sql/ast/select/columns.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:built_value/built_value.dart'; - -import 'package:moor_generator/src/sql/ast/expressions/expressions.dart'; - -part 'columns.g.dart'; - -/// https://www.sqlite.org/syntax/result-column.html -abstract class ResultColumn {} - -abstract class ExprResultColumn extends ResultColumn - implements Built { - Expression get expr; - @nullable - String get alias; - - ExprResultColumn._(); - factory ExprResultColumn(Function(ExprResultColumnBuilder builder) updates) = - _$ExprResultColumn; -} - -abstract class StarResultColumn extends ResultColumn - implements Built { - /// When non-null, refers to the "table.*" select expression. Otherwise, - /// refers to all tables in the query (just *). - @nullable - String get table; - - StarResultColumn._(); - factory StarResultColumn.from({String table}) => - StarResultColumn((b) => b.table = table); - - factory StarResultColumn(Function(StarResultColumnBuilder builder) updates) = - _$StarResultColumn; -} diff --git a/moor_generator/lib/src/sql/ast/select/columns.g.dart b/moor_generator/lib/src/sql/ast/select/columns.g.dart deleted file mode 100644 index 72dcf5cc..00000000 --- a/moor_generator/lib/src/sql/ast/select/columns.g.dart +++ /dev/null @@ -1,175 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'columns.dart'; - -// ************************************************************************** -// BuiltValueGenerator -// ************************************************************************** - -class _$ExprResultColumn extends ExprResultColumn { - @override - final Expression expr; - @override - final String alias; - - factory _$ExprResultColumn( - [void Function(ExprResultColumnBuilder) updates]) => - (new ExprResultColumnBuilder()..update(updates)).build(); - - _$ExprResultColumn._({this.expr, this.alias}) : super._() { - if (expr == null) { - throw new BuiltValueNullFieldError('ExprResultColumn', 'expr'); - } - } - - @override - ExprResultColumn rebuild(void Function(ExprResultColumnBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - ExprResultColumnBuilder toBuilder() => - new ExprResultColumnBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is ExprResultColumn && - expr == other.expr && - alias == other.alias; - } - - @override - int get hashCode { - return $jf($jc($jc(0, expr.hashCode), alias.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('ExprResultColumn') - ..add('expr', expr) - ..add('alias', alias)) - .toString(); - } -} - -class ExprResultColumnBuilder - implements Builder { - _$ExprResultColumn _$v; - - Expression _expr; - Expression get expr => _$this._expr; - set expr(Expression expr) => _$this._expr = expr; - - String _alias; - String get alias => _$this._alias; - set alias(String alias) => _$this._alias = alias; - - ExprResultColumnBuilder(); - - ExprResultColumnBuilder get _$this { - if (_$v != null) { - _expr = _$v.expr; - _alias = _$v.alias; - _$v = null; - } - return this; - } - - @override - void replace(ExprResultColumn other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$ExprResultColumn; - } - - @override - void update(void Function(ExprResultColumnBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$ExprResultColumn build() { - final _$result = _$v ?? new _$ExprResultColumn._(expr: expr, alias: alias); - replace(_$result); - return _$result; - } -} - -class _$StarResultColumn extends StarResultColumn { - @override - final String table; - - factory _$StarResultColumn( - [void Function(StarResultColumnBuilder) updates]) => - (new StarResultColumnBuilder()..update(updates)).build(); - - _$StarResultColumn._({this.table}) : super._(); - - @override - StarResultColumn rebuild(void Function(StarResultColumnBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - StarResultColumnBuilder toBuilder() => - new StarResultColumnBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is StarResultColumn && table == other.table; - } - - @override - int get hashCode { - return $jf($jc(0, table.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('StarResultColumn') - ..add('table', table)) - .toString(); - } -} - -class StarResultColumnBuilder - implements Builder { - _$StarResultColumn _$v; - - String _table; - String get table => _$this._table; - set table(String table) => _$this._table = table; - - StarResultColumnBuilder(); - - StarResultColumnBuilder get _$this { - if (_$v != null) { - _table = _$v.table; - _$v = null; - } - return this; - } - - @override - void replace(StarResultColumn other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$StarResultColumn; - } - - @override - void update(void Function(StarResultColumnBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$StarResultColumn build() { - final _$result = _$v ?? new _$StarResultColumn._(table: table); - replace(_$result); - return _$result; - } -} - -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/moor_generator/lib/src/sql/ast/select/select.dart b/moor_generator/lib/src/sql/ast/select/select.dart deleted file mode 100644 index e4605363..00000000 --- a/moor_generator/lib/src/sql/ast/select/select.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:built_value/built_value.dart'; -import 'package:built_collection/built_collection.dart'; -import 'package:moor_generator/src/sql/ast/expressions/expressions.dart'; -import 'columns.dart'; - -part 'select.g.dart'; - -abstract class SelectStatement - implements Built { - BuiltList get columns; - BuiltList get from; - @nullable - Expression get where; - @nullable - Limit get limit; - - SelectStatement._(); - factory SelectStatement(void Function(SelectStatementBuilder) builder) = - _$SelectStatement; -} - -/// Anything that can appear behind a "FROM" clause in a select statement. -abstract class SelectTarget {} - -abstract class TableTarget implements Built { - String get table; - - TableTarget._(); - factory TableTarget(void Function(TableTargetBuilder) builder) = - _$TableTarget; -} - -abstract class Limit implements Built { - Expression get amount; - @nullable - Expression get offset; - - Limit._(); - factory Limit(void Function(LimitBuilder) builder) = _$Limit; -} diff --git a/moor_generator/lib/src/sql/ast/select/select.g.dart b/moor_generator/lib/src/sql/ast/select/select.g.dart deleted file mode 100644 index 0d3a4104..00000000 --- a/moor_generator/lib/src/sql/ast/select/select.g.dart +++ /dev/null @@ -1,308 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'select.dart'; - -// ************************************************************************** -// BuiltValueGenerator -// ************************************************************************** - -class _$SelectStatement extends SelectStatement { - @override - final BuiltList columns; - @override - final BuiltList from; - @override - final Expression where; - @override - final Limit limit; - - factory _$SelectStatement([void Function(SelectStatementBuilder) updates]) => - (new SelectStatementBuilder()..update(updates)).build(); - - _$SelectStatement._({this.columns, this.from, this.where, this.limit}) - : super._() { - if (columns == null) { - throw new BuiltValueNullFieldError('SelectStatement', 'columns'); - } - if (from == null) { - throw new BuiltValueNullFieldError('SelectStatement', 'from'); - } - } - - @override - SelectStatement rebuild(void Function(SelectStatementBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - SelectStatementBuilder toBuilder() => - new SelectStatementBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is SelectStatement && - columns == other.columns && - from == other.from && - where == other.where && - limit == other.limit; - } - - @override - int get hashCode { - return $jf($jc( - $jc($jc($jc(0, columns.hashCode), from.hashCode), where.hashCode), - limit.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('SelectStatement') - ..add('columns', columns) - ..add('from', from) - ..add('where', where) - ..add('limit', limit)) - .toString(); - } -} - -class SelectStatementBuilder - implements Builder { - _$SelectStatement _$v; - - ListBuilder _columns; - ListBuilder get columns => - _$this._columns ??= new ListBuilder(); - set columns(ListBuilder columns) => _$this._columns = columns; - - ListBuilder _from; - ListBuilder get from => - _$this._from ??= new ListBuilder(); - set from(ListBuilder from) => _$this._from = from; - - Expression _where; - Expression get where => _$this._where; - set where(Expression where) => _$this._where = where; - - LimitBuilder _limit; - LimitBuilder get limit => _$this._limit ??= new LimitBuilder(); - set limit(LimitBuilder limit) => _$this._limit = limit; - - SelectStatementBuilder(); - - SelectStatementBuilder get _$this { - if (_$v != null) { - _columns = _$v.columns?.toBuilder(); - _from = _$v.from?.toBuilder(); - _where = _$v.where; - _limit = _$v.limit?.toBuilder(); - _$v = null; - } - return this; - } - - @override - void replace(SelectStatement other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$SelectStatement; - } - - @override - void update(void Function(SelectStatementBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$SelectStatement build() { - _$SelectStatement _$result; - try { - _$result = _$v ?? - new _$SelectStatement._( - columns: columns.build(), - from: from.build(), - where: where, - limit: _limit?.build()); - } catch (_) { - String _$failedField; - try { - _$failedField = 'columns'; - columns.build(); - _$failedField = 'from'; - from.build(); - - _$failedField = 'limit'; - _limit?.build(); - } catch (e) { - throw new BuiltValueNestedFieldError( - 'SelectStatement', _$failedField, e.toString()); - } - rethrow; - } - replace(_$result); - return _$result; - } -} - -class _$TableTarget extends TableTarget { - @override - final String table; - - factory _$TableTarget([void Function(TableTargetBuilder) updates]) => - (new TableTargetBuilder()..update(updates)).build(); - - _$TableTarget._({this.table}) : super._() { - if (table == null) { - throw new BuiltValueNullFieldError('TableTarget', 'table'); - } - } - - @override - TableTarget rebuild(void Function(TableTargetBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - TableTargetBuilder toBuilder() => new TableTargetBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is TableTarget && table == other.table; - } - - @override - int get hashCode { - return $jf($jc(0, table.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('TableTarget')..add('table', table)) - .toString(); - } -} - -class TableTargetBuilder implements Builder { - _$TableTarget _$v; - - String _table; - String get table => _$this._table; - set table(String table) => _$this._table = table; - - TableTargetBuilder(); - - TableTargetBuilder get _$this { - if (_$v != null) { - _table = _$v.table; - _$v = null; - } - return this; - } - - @override - void replace(TableTarget other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$TableTarget; - } - - @override - void update(void Function(TableTargetBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$TableTarget build() { - final _$result = _$v ?? new _$TableTarget._(table: table); - replace(_$result); - return _$result; - } -} - -class _$Limit extends Limit { - @override - final Expression amount; - @override - final Expression offset; - - factory _$Limit([void Function(LimitBuilder) updates]) => - (new LimitBuilder()..update(updates)).build(); - - _$Limit._({this.amount, this.offset}) : super._() { - if (amount == null) { - throw new BuiltValueNullFieldError('Limit', 'amount'); - } - } - - @override - Limit rebuild(void Function(LimitBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - LimitBuilder toBuilder() => new LimitBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is Limit && amount == other.amount && offset == other.offset; - } - - @override - int get hashCode { - return $jf($jc($jc(0, amount.hashCode), offset.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('Limit') - ..add('amount', amount) - ..add('offset', offset)) - .toString(); - } -} - -class LimitBuilder implements Builder { - _$Limit _$v; - - Expression _amount; - Expression get amount => _$this._amount; - set amount(Expression amount) => _$this._amount = amount; - - Expression _offset; - Expression get offset => _$this._offset; - set offset(Expression offset) => _$this._offset = offset; - - LimitBuilder(); - - LimitBuilder get _$this { - if (_$v != null) { - _amount = _$v.amount; - _offset = _$v.offset; - _$v = null; - } - return this; - } - - @override - void replace(Limit other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } - _$v = other as _$Limit; - } - - @override - void update(void Function(LimitBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$Limit build() { - final _$result = _$v ?? new _$Limit._(amount: amount, offset: offset); - replace(_$result); - return _$result; - } -} - -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/moor_generator/lib/src/sql/sql.dart b/moor_generator/lib/src/sql/sql.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/sqlparser/.gitignore b/sqlparser/.gitignore new file mode 100644 index 00000000..50602ac6 --- /dev/null +++ b/sqlparser/.gitignore @@ -0,0 +1,11 @@ +# Files and directories created by pub +.dart_tool/ +.packages +# Remove the following pattern if you wish to check in your lock file +pubspec.lock + +# Conventional directory for build outputs +build/ + +# Directory created by dartdoc +doc/api/ diff --git a/sqlparser/CHANGELOG.md b/sqlparser/CHANGELOG.md new file mode 100644 index 00000000..2a2fb414 --- /dev/null +++ b/sqlparser/CHANGELOG.md @@ -0,0 +1,2 @@ +## 0.1.0 +Initial version \ No newline at end of file diff --git a/sqlparser/LICENSE b/sqlparser/LICENSE new file mode 100644 index 00000000..e4b21941 --- /dev/null +++ b/sqlparser/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Simon Binder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sqlparser/README.md b/sqlparser/README.md new file mode 100644 index 00000000..adc63c01 --- /dev/null +++ b/sqlparser/README.md @@ -0,0 +1,3 @@ +# sqlparser + +Parser and analyzer for sql queries, written in pure Dart. Heavy work in progress \ No newline at end of file diff --git a/sqlparser/analysis_options.yaml b/sqlparser/analysis_options.yaml new file mode 120000 index 00000000..c9e0d9fb --- /dev/null +++ b/sqlparser/analysis_options.yaml @@ -0,0 +1 @@ +../analysis_options.yaml \ No newline at end of file diff --git a/sqlparser/example/sqlparser_example.dart b/sqlparser/example/sqlparser_example.dart new file mode 100644 index 00000000..1f641687 --- /dev/null +++ b/sqlparser/example/sqlparser_example.dart @@ -0,0 +1 @@ +// todo write example diff --git a/sqlparser/lib/sqlparser.dart b/sqlparser/lib/sqlparser.dart new file mode 100644 index 00000000..252dfc8b --- /dev/null +++ b/sqlparser/lib/sqlparser.dart @@ -0,0 +1,4 @@ +/// Sql parser and analyzer, written in pure dart. +/// +/// More dartdocs go here. +library sqlparser; diff --git a/moor_generator/lib/src/sql/ast/expressions/expressions.dart b/sqlparser/lib/src/ast/expressions/expressions.dart similarity index 100% rename from moor_generator/lib/src/sql/ast/expressions/expressions.dart rename to sqlparser/lib/src/ast/expressions/expressions.dart diff --git a/sqlparser/lib/src/ast/expressions/literals.dart b/sqlparser/lib/src/ast/expressions/literals.dart new file mode 100644 index 00000000..3d56d184 --- /dev/null +++ b/sqlparser/lib/src/ast/expressions/literals.dart @@ -0,0 +1,16 @@ +import 'expressions.dart'; + +// https://www.sqlite.org/syntax/literal-value.html + +class NullLiteral extends Expression { + const NullLiteral(); + + @override + String toString() => 'NULL'; + + @override + int get hashCode => runtimeType.hashCode; + + @override + bool operator ==(other) => identical(this, other) || other is NullLiteral; +} diff --git a/sqlparser/lib/src/ast/expressions/unary.dart b/sqlparser/lib/src/ast/expressions/unary.dart new file mode 100644 index 00000000..8fc72c35 --- /dev/null +++ b/sqlparser/lib/src/ast/expressions/unary.dart @@ -0,0 +1,10 @@ +import 'package:sqlparser/src/reader/tokenizer/token.dart'; + +import 'expressions.dart'; + +class UnaryExpression extends Expression { + final Token operator; + final Expression inner; + + UnaryExpression(this.operator, this.inner); +} diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart new file mode 100644 index 00000000..1d7ca73d --- /dev/null +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -0,0 +1,75 @@ +import 'package:sqlparser/src/ast/expressions/expressions.dart'; +import 'package:sqlparser/src/ast/expressions/unary.dart'; +import 'package:sqlparser/src/reader/tokenizer/token.dart'; + +class Parser { + final List tokens; + int _current = 0; + + Parser(this.tokens); + + bool get _isAtEnd => _peek.type == TokenType.eof; + Token get _peek => tokens[_current]; + Token get _previous => tokens[_current - 1]; + + bool _match(List types) { + for (var type in types) { + if (_check(type)) { + _advance(); + return true; + } + } + return false; + } + + bool _check(TokenType type) { + if (_isAtEnd) return false; + return _peek.type == type; + } + + Token _advance() { + if (!_isAtEnd) { + _current++; + } + return _previous; + } + + /* We parse expressions here. + * Operators have the following precedence: + * - + ~ NOT (unary) + * || (concatenation) + * * / % + * + - + * << >> & | + * < <= > >= + * = == != <> IS IS NOT IN LIKE GLOB MATCH REGEXP + * AND + * OR + * We also treat expressions in parentheses and literals with the highest + * priority. Parsing methods are written in ascending precedence, and each + * parsing method calls the next higher precedence if unsuccessful. + * */ + + Expression expression() { + return _unary(); + } + + Expression _unary() { + if (_match(const [ + TokenType.minus, + TokenType.plus, + TokenType.tilde, + TokenType.not + ])) { + final operator = _previous; + final expression = _unary(); + return UnaryExpression(operator, expression); + } + + return _primary(); + } + + Expression _primary() { + return null; + } +} diff --git a/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart b/sqlparser/lib/src/reader/tokenizer/scanner.dart similarity index 84% rename from moor_generator/lib/src/sql/parser/tokenizer/scanner.dart rename to sqlparser/lib/src/reader/tokenizer/scanner.dart index afcbd616..2e0302b8 100644 --- a/moor_generator/lib/src/sql/parser/tokenizer/scanner.dart +++ b/sqlparser/lib/src/reader/tokenizer/scanner.dart @@ -1,6 +1,6 @@ -import 'package:moor_generator/src/sql/parser/tokenizer/token.dart'; -import 'package:moor_generator/src/sql/parser/tokenizer/utils.dart'; import 'package:source_span/source_span.dart'; +import 'package:sqlparser/src/reader/tokenizer/token.dart'; +import 'package:sqlparser/src/reader/tokenizer/utils.dart'; class Scanner { final String source; @@ -69,15 +69,41 @@ class Scanner { case '/': _addToken(TokenType.slash); break; + case '%': + _addToken(TokenType.percent); + break; + case '&': + _addToken(TokenType.ampersand); + break; + case '|': + _addToken(_match('|') ? TokenType.doublePipe : TokenType.pipe); + break; case '<': - _addToken(_match('=') ? TokenType.lessEqual : TokenType.less); + if (_match('=')) { + _addToken(TokenType.lessEqual); + } else if (_match('<')) { + _addToken(TokenType.shiftLeft); + } else if (_match('>')) { + _addToken(TokenType.lessMore); + } else { + _addToken(TokenType.less); + } break; case '>': - _addToken(_match('=') ? TokenType.moreEqual : TokenType.more); + if (_match('=')) { + _addToken(TokenType.moreEqual); + } else if (_match('>')) { + _addToken(TokenType.shiftRight); + } else { + _addToken(TokenType.more); + } break; case '=': - _addToken(TokenType.equal); + _addToken(_match('=') ? TokenType.doubleEqual : TokenType.equal); + break; + case '~': + _addToken(TokenType.tilde); break; case 'x': @@ -266,7 +292,13 @@ class Scanner { _nextChar(); } - tokens.add(IdentifierToken(false, _currentSpan)); + // not escaped, so it could be a keyword + final text = _currentSpan.text.toUpperCase(); + if (keywords.containsKey(text)) { + _addToken(keywords[text]); + } else { + tokens.add(IdentifierToken(false, _currentSpan)); + } } } } diff --git a/moor_generator/lib/src/sql/parser/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart similarity index 66% rename from moor_generator/lib/src/sql/parser/tokenizer/token.dart rename to sqlparser/lib/src/reader/tokenizer/token.dart index 570fd2ea..4e19a283 100644 --- a/moor_generator/lib/src/sql/parser/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -5,23 +5,59 @@ enum TokenType { rightParen, comma, dot, - plus, - minus, + doublePipe, star, slash, + percent, + plus, + minus, + shiftLeft, + shiftRight, + ampersand, + pipe, less, lessEqual, more, moreEqual, equal, + doubleEqual, + exclamationEqual, + lessMore, + $is, + $in, + not, + like, + glob, + match, + regexp, + and, + or, + tilde, stringLiteral, numberLiteral, identifier, + select, + from, + where, + eof, } +const Map keywords = { + 'SELECT': TokenType.select, + 'FROM': TokenType.from, + 'WHERE': TokenType.where, + 'IS': TokenType.$is, + 'IN': TokenType.$in, + 'LIKE': TokenType.like, + 'GLOB': TokenType.glob, + 'MATCH': TokenType.match, + 'REGEXP': TokenType.regexp, + 'NOT': TokenType.not, +}; + class Token { final TokenType type; diff --git a/moor_generator/lib/src/sql/parser/tokenizer/utils.dart b/sqlparser/lib/src/reader/tokenizer/utils.dart similarity index 100% rename from moor_generator/lib/src/sql/parser/tokenizer/utils.dart rename to sqlparser/lib/src/reader/tokenizer/utils.dart diff --git a/sqlparser/pubspec.yaml b/sqlparser/pubspec.yaml new file mode 100644 index 00000000..80cc4ec5 --- /dev/null +++ b/sqlparser/pubspec.yaml @@ -0,0 +1,13 @@ +name: sqlparser +description: Parsing and analysis for sql queries +version: 0.1.0 +repository: https://github.com/simolus3/moor +#homepage: https://moor.simonbinder.eu/ +issue_tracker: https://github.com/simolus3/moor/issues +author: Simon Binder + +environment: + sdk: '>=2.2.0 <3.0.0' + +dev_dependencies: + test: ^1.0.0 diff --git a/moor_generator/test/sql/scanner/single_token_tests.dart b/sqlparser/test/scanner/single_token_tests.dart similarity index 85% rename from moor_generator/test/sql/scanner/single_token_tests.dart rename to sqlparser/test/scanner/single_token_tests.dart index 05ac1d6b..764b2c6c 100644 --- a/moor_generator/test/sql/scanner/single_token_tests.dart +++ b/sqlparser/test/scanner/single_token_tests.dart @@ -1,6 +1,6 @@ -import 'package:moor_generator/src/sql/parser/tokenizer/scanner.dart'; -import 'package:moor_generator/src/sql/parser/tokenizer/token.dart'; -import 'package:test_api/test_api.dart'; +import 'package:sqlparser/src/reader/tokenizer/scanner.dart'; +import 'package:sqlparser/src/reader/tokenizer/token.dart'; +import 'package:test/test.dart'; void expectFullToken(String token, TokenType type) { final scanner = Scanner(token); @@ -41,7 +41,7 @@ Map testCases = { '.123e-3': TokenType.numberLiteral, '0xFF13': TokenType.numberLiteral, '0Xf13A': TokenType.numberLiteral, - 'SELECT': TokenType.identifier, + 'SELECT': TokenType.select, '"UPDATE"': TokenType.identifier, }; From 7da3ef3d64a703d262d676c9a49f74e6ecbcf5df Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 15 Jun 2019 23:03:43 +0200 Subject: [PATCH 5/9] Integrate new sql parser library into CI --- .travis.yml | 1 + tool/mono_repo_wrapper.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9722331e..5e75137d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ dart: env: - PKG="moor" - PKG="moor_generator" + - PKG="sqlparser" script: ./tool/mono_repo_wrapper.sh after_success: ./tool/upload_coverage.sh diff --git a/tool/mono_repo_wrapper.sh b/tool/mono_repo_wrapper.sh index e34587c4..6601664b 100755 --- a/tool/mono_repo_wrapper.sh +++ b/tool/mono_repo_wrapper.sh @@ -4,6 +4,9 @@ case $PKG in moor_generator) ./tool/travis.sh dartfmt dartanalyzer test ;; +sqlparser) + ./tool/travis.sh dartfmt dartanalyzer test +;; moor) ./tool/travis.sh dartfmt dartanalyzer command ;; From b442d32a873c2063d954ab197977437f8844d940 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 16 Jun 2019 20:50:07 +0200 Subject: [PATCH 6/9] Parse simple expressions --- sqlparser/lib/src/ast/ast.dart | 14 ++ .../lib/src/ast/expressions/expressions.dart | 4 +- .../lib/src/ast/expressions/literals.dart | 41 ++++- sqlparser/lib/src/ast/expressions/simple.dart | 63 ++++++++ sqlparser/lib/src/ast/expressions/unary.dart | 10 -- sqlparser/lib/src/reader/parser/parser.dart | 144 +++++++++++++++++- .../lib/src/reader/tokenizer/scanner.dart | 2 +- sqlparser/lib/src/reader/tokenizer/token.dart | 16 +- sqlparser/lib/src/utils/ast_equality.dart | 46 ++++++ sqlparser/test/parser/expression_test.dart | 35 +++++ sqlparser/test/parser/utils.dart | 5 + 11 files changed, 356 insertions(+), 24 deletions(-) create mode 100644 sqlparser/lib/src/ast/ast.dart create mode 100644 sqlparser/lib/src/ast/expressions/simple.dart delete mode 100644 sqlparser/lib/src/ast/expressions/unary.dart create mode 100644 sqlparser/lib/src/utils/ast_equality.dart create mode 100644 sqlparser/test/parser/expression_test.dart create mode 100644 sqlparser/test/parser/utils.dart diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart new file mode 100644 index 00000000..f6c5630e --- /dev/null +++ b/sqlparser/lib/src/ast/ast.dart @@ -0,0 +1,14 @@ +import 'package:sqlparser/src/ast/expressions/literals.dart'; +import 'package:sqlparser/src/ast/expressions/simple.dart'; + +abstract class AstNode { + Iterable get childNodes; + T accept(AstVisitor visitor); +} + +abstract class AstVisitor { + T visitBinaryExpression(BinaryExpression e); + T visitUnaryExpression(UnaryExpression e); + T visitIsExpression(IsExpression e); + T visitLiteral(Literal e); +} diff --git a/sqlparser/lib/src/ast/expressions/expressions.dart b/sqlparser/lib/src/ast/expressions/expressions.dart index b31ffed9..ab458506 100644 --- a/sqlparser/lib/src/ast/expressions/expressions.dart +++ b/sqlparser/lib/src/ast/expressions/expressions.dart @@ -1,3 +1,5 @@ -abstract class Expression { +import 'package:sqlparser/src/ast/ast.dart'; + +abstract class Expression implements AstNode { const Expression(); } diff --git a/sqlparser/lib/src/ast/expressions/literals.dart b/sqlparser/lib/src/ast/expressions/literals.dart index 3d56d184..a8998e42 100644 --- a/sqlparser/lib/src/ast/expressions/literals.dart +++ b/sqlparser/lib/src/ast/expressions/literals.dart @@ -1,16 +1,43 @@ +import 'package:sqlparser/src/ast/ast.dart'; +import 'package:sqlparser/src/reader/tokenizer/token.dart'; + import 'expressions.dart'; // https://www.sqlite.org/syntax/literal-value.html -class NullLiteral extends Expression { - const NullLiteral(); +abstract class Literal extends Expression { + final Token token; + + Literal(this.token); @override - String toString() => 'NULL'; + T accept(AstVisitor visitor) => visitor.visitLiteral(this); @override - int get hashCode => runtimeType.hashCode; - - @override - bool operator ==(other) => identical(this, other) || other is NullLiteral; + final Iterable childNodes = const []; +} + +class NullLiteral extends Literal { + NullLiteral(Token token) : super(token); +} + +class NumericLiteral extends Literal { + final num number; + + NumericLiteral(this.number, Token token) : super(token); +} + +class BooleanLiteral extends NumericLiteral { + BooleanLiteral.withFalse(Token token) : super(0, token); + BooleanLiteral.withTrue(Token token) : super(1, token); +} + +class StringLiteral extends Literal { + final String data; + final bool isBinary; + + StringLiteral(StringLiteralToken token) + : data = token.value, + isBinary = token.binary, + super(token); } diff --git a/sqlparser/lib/src/ast/expressions/simple.dart b/sqlparser/lib/src/ast/expressions/simple.dart new file mode 100644 index 00000000..8dcc5ef2 --- /dev/null +++ b/sqlparser/lib/src/ast/expressions/simple.dart @@ -0,0 +1,63 @@ +import 'package:sqlparser/src/ast/ast.dart'; +import 'package:sqlparser/src/reader/tokenizer/token.dart'; + +import 'expressions.dart'; + +class UnaryExpression extends Expression { + final Token operator; + final Expression inner; + + UnaryExpression(this.operator, this.inner); + + @override + T accept(AstVisitor visitor) => visitor.visitUnaryExpression(this); + + @override + Iterable get childNodes => [inner]; +} + +class BinaryExpression extends Expression { + final Token operator; + final Expression left; + final Expression right; + + BinaryExpression(this.left, this.operator, this.right); + + @override + T accept(AstVisitor visitor) => visitor.visitBinaryExpression(this); + + @override + Iterable get childNodes => [left, right]; +} + +class IsExpression extends Expression { + final bool negated; + final Expression left; + final Expression right; + + IsExpression(this.negated, this.left, this.right); + + @override + T accept(AstVisitor visitor) { + return visitor.visitIsExpression(this); + } + + @override + Iterable get childNodes => [left, right]; +} + +class Parentheses extends Expression { + final Token openingLeft; + final Expression expression; + final Token closingRight; + + Parentheses(this.openingLeft, this.expression, this.closingRight); + + @override + T accept(AstVisitor visitor) { + return expression.accept(visitor); + } + + @override + Iterable get childNodes => [expression]; +} diff --git a/sqlparser/lib/src/ast/expressions/unary.dart b/sqlparser/lib/src/ast/expressions/unary.dart deleted file mode 100644 index 8fc72c35..00000000 --- a/sqlparser/lib/src/ast/expressions/unary.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:sqlparser/src/reader/tokenizer/token.dart'; - -import 'expressions.dart'; - -class UnaryExpression extends Expression { - final Token operator; - final Expression inner; - - UnaryExpression(this.operator, this.inner); -} diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart index 1d7ca73d..730f45c0 100644 --- a/sqlparser/lib/src/reader/parser/parser.dart +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -1,9 +1,35 @@ +import 'package:meta/meta.dart'; import 'package:sqlparser/src/ast/expressions/expressions.dart'; -import 'package:sqlparser/src/ast/expressions/unary.dart'; +import 'package:sqlparser/src/ast/expressions/literals.dart'; +import 'package:sqlparser/src/ast/expressions/simple.dart'; import 'package:sqlparser/src/reader/tokenizer/token.dart'; +const _comparisonOperators = [ + TokenType.less, + TokenType.lessEqual, + TokenType.more, + TokenType.moreEqual, +]; +const _binaryOperators = const [ + TokenType.shiftLeft, + TokenType.shiftRight, + TokenType.ampersand, + TokenType.pipe, +]; + +class ParsingError implements Exception { + final Token token; + final String message; + + ParsingError(this.token, this.message); +} + +// todo better error handling and synchronisation, like it's done here: +// https://craftinginterpreters.com/parsing-expressions.html#synchronizing-a-recursive-descent-parser + class Parser { final List tokens; + final List errors = []; int _current = 0; Parser(this.tokens); @@ -34,6 +60,18 @@ class Parser { return _previous; } + @alwaysThrows + void _error(String message) { + final error = ParsingError(_peek, message); + errors.add(error); + throw error; + } + + Token _consume(TokenType type, String message) { + if (_check(type)) return _advance(); + _error(message); + } + /* We parse expressions here. * Operators have the following precedence: * - + ~ NOT (unary) @@ -48,10 +86,84 @@ class Parser { * We also treat expressions in parentheses and literals with the highest * priority. Parsing methods are written in ascending precedence, and each * parsing method calls the next higher precedence if unsuccessful. + * https://www.sqlite.org/lang_expr.html * */ Expression expression() { - return _unary(); + return _or(); + } + + /// Parses an expression of the form a b, where is in [types] and + /// both a and b are expressions with a higher precedence parsed from + /// [higherPrecedence]. + Expression _parseSimpleBinary( + List types, Expression Function() higherPrecedence) { + var expression = higherPrecedence(); + + while (_match(types)) { + final operator = _previous; + final right = higherPrecedence(); + expression = BinaryExpression(expression, operator, right); + } + return expression; + } + + Expression _or() => _parseSimpleBinary(const [TokenType.or], _and); + Expression _and() => _parseSimpleBinary(const [TokenType.and], _equals); + + Expression _equals() { + var expression = _comparison(); + final ops = const [ + TokenType.equal, + TokenType.doubleEqual, + TokenType.exclamationEqual, + TokenType.lessMore, + TokenType.$is, + TokenType.$in, + TokenType.like, + TokenType.glob, + TokenType.match, + TokenType.regexp, + ]; + + while (_match(ops)) { + final operator = _previous; + if (operator.type == TokenType.$is) { + final not = _match(const [TokenType.not]); + // special case: is not expression + expression = IsExpression(not, expression, _comparison()); + } else { + expression = BinaryExpression(expression, operator, _comparison()); + } + } + return expression; + } + + Expression _comparison() { + return _parseSimpleBinary(_comparisonOperators, _binaryOperation); + } + + Expression _binaryOperation() { + return _parseSimpleBinary(_binaryOperators, _addition); + } + + Expression _addition() { + return _parseSimpleBinary(const [ + TokenType.plus, + TokenType.minus, + ], _multiplication); + } + + Expression _multiplication() { + return _parseSimpleBinary(const [ + TokenType.star, + TokenType.slash, + TokenType.percent, + ], _concatenation); + } + + Expression _concatenation() { + return _parseSimpleBinary(const [TokenType.doublePipe], _unary); } Expression _unary() { @@ -70,6 +182,32 @@ class Parser { } Expression _primary() { - return null; + final token = _advance(); + final type = token.type; + switch (type) { + case TokenType.numberLiteral: + // todo get the proper value out of this one + return NumericLiteral(42, _peek); + case TokenType.stringLiteral: + final token = _peek as StringLiteralToken; + return StringLiteral(token); + case TokenType.$null: + return NullLiteral(_peek); + case TokenType.$true: + return BooleanLiteral.withTrue(_peek); + case TokenType.$false: + return BooleanLiteral.withFalse(_peek); + // todo CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP + case TokenType.leftParen: + final left = _previous; + final expr = expression(); + _consume(TokenType.rightParen, 'Expected a closing bracket'); + return Parentheses(left, expr, _previous); + default: + break; + } + + // nothing found -> issue error + _error('Could not parse this expression'); } } diff --git a/sqlparser/lib/src/reader/tokenizer/scanner.dart b/sqlparser/lib/src/reader/tokenizer/scanner.dart index 2e0302b8..891fa261 100644 --- a/sqlparser/lib/src/reader/tokenizer/scanner.dart +++ b/sqlparser/lib/src/reader/tokenizer/scanner.dart @@ -175,7 +175,7 @@ class Scanner { _nextChar(); final value = source.substring(_startOffset + 1, _currentOffset - 1); - tokens.add(StringLiteral(value, _currentSpan, binary: binary)); + tokens.add(StringLiteralToken(value, _currentSpan, binary: binary)); } void _numeric(String firstChar) { diff --git a/sqlparser/lib/src/reader/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart index 4e19a283..a55760c5 100644 --- a/sqlparser/lib/src/reader/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -36,6 +36,12 @@ enum TokenType { stringLiteral, numberLiteral, + $true, + $false, + $null, + currentTime, + currentDate, + currentTimestamp, identifier, select, @@ -56,6 +62,12 @@ const Map keywords = { 'MATCH': TokenType.match, 'REGEXP': TokenType.regexp, 'NOT': TokenType.not, + 'TRUE': TokenType.$true, + 'FALSE': TokenType.$false, + 'NULL': TokenType.$null, + 'CURRENT_TIME': TokenType.currentTime, + 'CURRENT_DATE': TokenType.currentDate, + 'CURRENT_TIMESTAMP': TokenType.currentTimestamp, }; class Token { @@ -66,13 +78,13 @@ class Token { const Token(this.type, this.span); } -class StringLiteral extends Token { +class StringLiteralToken extends Token { final String value; /// sqlite allows binary strings (x'literal') which are interpreted as blobs. final bool binary; - const StringLiteral(this.value, SourceSpan span, {this.binary = false}) + const StringLiteralToken(this.value, SourceSpan span, {this.binary = false}) : super(TokenType.stringLiteral, span); } diff --git a/sqlparser/lib/src/utils/ast_equality.dart b/sqlparser/lib/src/utils/ast_equality.dart new file mode 100644 index 00000000..810d467f --- /dev/null +++ b/sqlparser/lib/src/utils/ast_equality.dart @@ -0,0 +1,46 @@ +import 'package:sqlparser/src/ast/ast.dart'; +import 'package:sqlparser/src/ast/expressions/simple.dart'; + +/// Checks whether [a] and [b] are equal. If they aren't, throws an exception. +void enforceEqual(AstNode a, AstNode b) { + if (a.runtimeType != b.runtimeType) { + throw ArgumentError('Not equal: First was $a, second $b'); + } + _checkAdditional(a, b); + + final childrenA = a.childNodes.iterator; + final childrenB = b.childNodes.iterator; + + // always move both iterators + while (childrenA.moveNext() & childrenB.moveNext()) { + enforceEqual(childrenA.current, childrenB.current); + } + + if (childrenA.moveNext() || childrenB.moveNext()) { + throw ArgumentError("$a and $b don't have an equal amount of children"); + } +} + +void _checkAdditional(T a, T b) { + if (a is IsExpression) { + return _checkIsExpr(a, b as IsExpression); + } else if (a is UnaryExpression) { + _checkUnaryExpression(a, b as UnaryExpression); + } else if (a is BinaryExpression) { + _checkBinaryExpression(a, b as BinaryExpression); + } +} + +void _checkIsExpr(IsExpression a, IsExpression b) { + if (a.negated != b.negated) { + throw ArgumentError('Negation status not the same'); + } +} + +void _checkUnaryExpression(UnaryExpression a, UnaryExpression b) { + if (a.operator.type != b.operator.type) throw ArgumentError('Different type'); +} + +void _checkBinaryExpression(BinaryExpression a, BinaryExpression b) { + if (a.operator.type != b.operator.type) throw ArgumentError('Different type'); +} diff --git a/sqlparser/test/parser/expression_test.dart b/sqlparser/test/parser/expression_test.dart new file mode 100644 index 00000000..540c67a1 --- /dev/null +++ b/sqlparser/test/parser/expression_test.dart @@ -0,0 +1,35 @@ +import 'package:sqlparser/src/ast/expressions/literals.dart'; +import 'package:sqlparser/src/ast/expressions/simple.dart'; +import 'package:sqlparser/src/reader/parser/parser.dart'; +import 'package:sqlparser/src/reader/tokenizer/scanner.dart'; +import 'package:sqlparser/src/reader/tokenizer/token.dart'; +import 'package:sqlparser/src/utils/ast_equality.dart'; +import 'package:test/test.dart'; + +import 'utils.dart'; + +void main() { + test('parses simple expressions', () { + final scanner = Scanner('3 * 4 + 5 == 17'); + final tokens = scanner.scanTokens(); + final parser = Parser(tokens); + + final expression = parser.expression(); + enforceEqual( + expression, + BinaryExpression( + BinaryExpression( + BinaryExpression( + NumericLiteral(3, token(TokenType.numberLiteral)), + token(TokenType.star), + NumericLiteral(4, token(TokenType.numberLiteral)), + ), + token(TokenType.plus), + NumericLiteral(5, token(TokenType.numberLiteral)), + ), + token(TokenType.doubleEqual), + NumericLiteral(17, token(TokenType.numberLiteral)), + ), + ); + }); +} diff --git a/sqlparser/test/parser/utils.dart b/sqlparser/test/parser/utils.dart new file mode 100644 index 00000000..4c9b605f --- /dev/null +++ b/sqlparser/test/parser/utils.dart @@ -0,0 +1,5 @@ +import 'package:sqlparser/src/reader/tokenizer/token.dart'; + +Token token(TokenType type) { + return Token(type, null); +} From d125a844da7c00e3684e047cd1d06761eaf8ad11 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 16 Jun 2019 21:23:33 +0200 Subject: [PATCH 7/9] Start implementing SELECT statements for parser --- sqlparser/lib/src/ast/ast.dart | 15 ++++++- sqlparser/lib/src/ast/clauses/limit.dart | 17 ++++++++ .../lib/src/ast/expressions/expressions.dart | 2 +- .../lib/src/ast/expressions/literals.dart | 6 +-- sqlparser/lib/src/ast/expressions/simple.dart | 5 +-- sqlparser/lib/src/ast/statements/select.dart | 16 +++++++ sqlparser/lib/src/reader/parser/parser.dart | 42 +++++++++++++++++-- sqlparser/lib/src/reader/tokenizer/token.dart | 6 +++ sqlparser/lib/src/utils/ast_equality.dart | 1 - sqlparser/pubspec.yaml | 2 +- sqlparser/test/parser/expression_test.dart | 3 +- 11 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 sqlparser/lib/src/ast/clauses/limit.dart create mode 100644 sqlparser/lib/src/ast/statements/select.dart diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart index f6c5630e..d5c8c539 100644 --- a/sqlparser/lib/src/ast/ast.dart +++ b/sqlparser/lib/src/ast/ast.dart @@ -1,5 +1,12 @@ -import 'package:sqlparser/src/ast/expressions/literals.dart'; -import 'package:sqlparser/src/ast/expressions/simple.dart'; +import 'package:sqlparser/src/reader/tokenizer/token.dart'; + +part 'clauses/limit.dart'; + +part 'expressions/expressions.dart'; +part 'expressions/literals.dart'; +part 'expressions/simple.dart'; + +part 'statements/select.dart'; abstract class AstNode { Iterable get childNodes; @@ -7,6 +14,10 @@ abstract class AstNode { } abstract class AstVisitor { + T visitSelectStatement(SelectStatement e); + + T visitLimit(Limit e); + T visitBinaryExpression(BinaryExpression e); T visitUnaryExpression(UnaryExpression e); T visitIsExpression(IsExpression e); diff --git a/sqlparser/lib/src/ast/clauses/limit.dart b/sqlparser/lib/src/ast/clauses/limit.dart new file mode 100644 index 00000000..9acf9641 --- /dev/null +++ b/sqlparser/lib/src/ast/clauses/limit.dart @@ -0,0 +1,17 @@ +part of '../ast.dart'; + +class Limit extends AstNode { + Expression count; + Token offsetSeparator; // can either be OFFSET or just a comma + Expression offset; + + Limit({this.count, this.offsetSeparator, this.offset}); + + @override + T accept(AstVisitor visitor) { + return visitor.visitLimit(this); + } + + @override + Iterable get childNodes => [count, if (offset != null) offset]; +} diff --git a/sqlparser/lib/src/ast/expressions/expressions.dart b/sqlparser/lib/src/ast/expressions/expressions.dart index ab458506..66a50d07 100644 --- a/sqlparser/lib/src/ast/expressions/expressions.dart +++ b/sqlparser/lib/src/ast/expressions/expressions.dart @@ -1,4 +1,4 @@ -import 'package:sqlparser/src/ast/ast.dart'; +part of '../ast.dart'; abstract class Expression implements AstNode { const Expression(); diff --git a/sqlparser/lib/src/ast/expressions/literals.dart b/sqlparser/lib/src/ast/expressions/literals.dart index a8998e42..ad029421 100644 --- a/sqlparser/lib/src/ast/expressions/literals.dart +++ b/sqlparser/lib/src/ast/expressions/literals.dart @@ -1,8 +1,4 @@ -import 'package:sqlparser/src/ast/ast.dart'; -import 'package:sqlparser/src/reader/tokenizer/token.dart'; - -import 'expressions.dart'; - +part of '../ast.dart'; // https://www.sqlite.org/syntax/literal-value.html abstract class Literal extends Expression { diff --git a/sqlparser/lib/src/ast/expressions/simple.dart b/sqlparser/lib/src/ast/expressions/simple.dart index 8dcc5ef2..0c9ef4a4 100644 --- a/sqlparser/lib/src/ast/expressions/simple.dart +++ b/sqlparser/lib/src/ast/expressions/simple.dart @@ -1,7 +1,4 @@ -import 'package:sqlparser/src/ast/ast.dart'; -import 'package:sqlparser/src/reader/tokenizer/token.dart'; - -import 'expressions.dart'; +part of '../ast.dart'; class UnaryExpression extends Expression { final Token operator; diff --git a/sqlparser/lib/src/ast/statements/select.dart b/sqlparser/lib/src/ast/statements/select.dart new file mode 100644 index 00000000..e3f43b9c --- /dev/null +++ b/sqlparser/lib/src/ast/statements/select.dart @@ -0,0 +1,16 @@ +part of '../ast.dart'; + +class SelectStatement extends AstNode { + final Expression where; + final Limit limit; + + SelectStatement({this.where, this.limit}); + + @override + T accept(AstVisitor visitor) { + return visitor.visitSelectStatement(this); + } + + @override + Iterable get childNodes => null; +} diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart index 730f45c0..a590620c 100644 --- a/sqlparser/lib/src/reader/parser/parser.dart +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -1,7 +1,5 @@ import 'package:meta/meta.dart'; -import 'package:sqlparser/src/ast/expressions/expressions.dart'; -import 'package:sqlparser/src/ast/expressions/literals.dart'; -import 'package:sqlparser/src/ast/expressions/simple.dart'; +import 'package:sqlparser/src/ast/ast.dart'; import 'package:sqlparser/src/reader/tokenizer/token.dart'; const _comparisonOperators = [ @@ -72,6 +70,44 @@ class Parser { _error(message); } + /// Parses a [SelectStatement], or returns null if there is no select token + /// after the current position. + SelectStatement select() { + if (!_match(const [TokenType.select])) return null; + + // todo parse result column + + final where = _where(); + final limit = _limit(); + + return SelectStatement(where: where, limit: limit); + } + + /// Parses a where clause if there is one at the current position + Expression _where() { + if (_match(const [TokenType.where])) { + return expression(); + } + return null; + } + + /// Parses a [Limit] clause, or returns null if there is no limit token after + /// the current position. + Limit _limit() { + if (!_match(const [TokenType.limit])) return null; + + final count = expression(); + Token offsetSep; + Expression offset; + + if (_match(const [TokenType.comma, TokenType.offset])) { + offsetSep = _previous; + offset = expression(); + } + + return Limit(count: count, offsetSeparator: offsetSep, offset: offset); + } + /* We parse expressions here. * Operators have the following precedence: * - + ~ NOT (unary) diff --git a/sqlparser/lib/src/reader/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart index a55760c5..c5023a61 100644 --- a/sqlparser/lib/src/reader/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -45,9 +45,13 @@ enum TokenType { identifier, select, + from, where, + limit, + offset, + eof, } @@ -55,6 +59,8 @@ const Map keywords = { 'SELECT': TokenType.select, 'FROM': TokenType.from, 'WHERE': TokenType.where, + 'LIMIT': TokenType.limit, + 'OFFSET': TokenType.offset, 'IS': TokenType.$is, 'IN': TokenType.$in, 'LIKE': TokenType.like, diff --git a/sqlparser/lib/src/utils/ast_equality.dart b/sqlparser/lib/src/utils/ast_equality.dart index 810d467f..21b96160 100644 --- a/sqlparser/lib/src/utils/ast_equality.dart +++ b/sqlparser/lib/src/utils/ast_equality.dart @@ -1,5 +1,4 @@ import 'package:sqlparser/src/ast/ast.dart'; -import 'package:sqlparser/src/ast/expressions/simple.dart'; /// Checks whether [a] and [b] are equal. If they aren't, throws an exception. void enforceEqual(AstNode a, AstNode b) { diff --git a/sqlparser/pubspec.yaml b/sqlparser/pubspec.yaml index 80cc4ec5..c41c5211 100644 --- a/sqlparser/pubspec.yaml +++ b/sqlparser/pubspec.yaml @@ -7,7 +7,7 @@ issue_tracker: https://github.com/simolus3/moor/issues author: Simon Binder environment: - sdk: '>=2.2.0 <3.0.0' + sdk: '>=2.2.2 <3.0.0' dev_dependencies: test: ^1.0.0 diff --git a/sqlparser/test/parser/expression_test.dart b/sqlparser/test/parser/expression_test.dart index 540c67a1..8b0a76be 100644 --- a/sqlparser/test/parser/expression_test.dart +++ b/sqlparser/test/parser/expression_test.dart @@ -1,5 +1,4 @@ -import 'package:sqlparser/src/ast/expressions/literals.dart'; -import 'package:sqlparser/src/ast/expressions/simple.dart'; +import 'package:sqlparser/src/ast/ast.dart'; import 'package:sqlparser/src/reader/parser/parser.dart'; import 'package:sqlparser/src/reader/tokenizer/scanner.dart'; import 'package:sqlparser/src/reader/tokenizer/token.dart'; From 1bc4bfc1201efb400a7426b96a55c81590093a2e Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 17 Jun 2019 22:33:28 +0200 Subject: [PATCH 8/9] Simplify equality check, some more SELECT parsing --- sqlparser/lib/src/ast/ast.dart | 9 ++++ sqlparser/lib/src/ast/clauses/limit.dart | 5 ++ .../lib/src/ast/expressions/literals.dart | 11 ++++ .../lib/src/ast/expressions/reference.dart | 20 +++++++ sqlparser/lib/src/ast/expressions/simple.dart | 18 +++++++ sqlparser/lib/src/ast/statements/select.dart | 52 +++++++++++++++++- .../lib/src/reader/parser/num_parser.dart | 11 ++++ sqlparser/lib/src/reader/parser/parser.dart | 53 +++++++++++++++++-- sqlparser/lib/src/reader/tokenizer/token.dart | 5 ++ sqlparser/lib/src/utils/ast_equality.dart | 29 ++-------- sqlparser/test/parser/expression_test.dart | 21 ++++++++ 11 files changed, 204 insertions(+), 30 deletions(-) create mode 100644 sqlparser/lib/src/ast/expressions/reference.dart create mode 100644 sqlparser/lib/src/reader/parser/num_parser.dart diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart index d5c8c539..2b37ddff 100644 --- a/sqlparser/lib/src/ast/ast.dart +++ b/sqlparser/lib/src/ast/ast.dart @@ -1,9 +1,11 @@ +import 'package:meta/meta.dart'; import 'package:sqlparser/src/reader/tokenizer/token.dart'; part 'clauses/limit.dart'; part 'expressions/expressions.dart'; part 'expressions/literals.dart'; +part 'expressions/reference.dart'; part 'expressions/simple.dart'; part 'statements/select.dart'; @@ -11,10 +13,16 @@ part 'statements/select.dart'; abstract class AstNode { Iterable get childNodes; T accept(AstVisitor visitor); + + /// Whether the content of this node is equal to the [other] node of the same + /// type. The "content" refers to anything stored only in this node, children + /// are ignored. + bool contentEquals(covariant AstNode other); } abstract class AstVisitor { T visitSelectStatement(SelectStatement e); + T visitResultColumn(ResultColumn e); T visitLimit(Limit e); @@ -22,4 +30,5 @@ abstract class AstVisitor { T visitUnaryExpression(UnaryExpression e); T visitIsExpression(IsExpression e); T visitLiteral(Literal e); + T visitReference(Reference e); } diff --git a/sqlparser/lib/src/ast/clauses/limit.dart b/sqlparser/lib/src/ast/clauses/limit.dart index 9acf9641..59b5a9c5 100644 --- a/sqlparser/lib/src/ast/clauses/limit.dart +++ b/sqlparser/lib/src/ast/clauses/limit.dart @@ -14,4 +14,9 @@ class Limit extends AstNode { @override Iterable get childNodes => [count, if (offset != null) offset]; + + @override + bool contentEquals(Limit other) { + return other.offsetSeparator?.type == offsetSeparator?.type; + } } diff --git a/sqlparser/lib/src/ast/expressions/literals.dart b/sqlparser/lib/src/ast/expressions/literals.dart index ad029421..12568b0e 100644 --- a/sqlparser/lib/src/ast/expressions/literals.dart +++ b/sqlparser/lib/src/ast/expressions/literals.dart @@ -15,12 +15,18 @@ abstract class Literal extends Expression { class NullLiteral extends Literal { NullLiteral(Token token) : super(token); + + @override + bool contentEquals(NullLiteral other) => true; } class NumericLiteral extends Literal { final num number; NumericLiteral(this.number, Token token) : super(token); + + @override + bool contentEquals(NumericLiteral other) => other.number == number; } class BooleanLiteral extends NumericLiteral { @@ -36,4 +42,9 @@ class StringLiteral extends Literal { : data = token.value, isBinary = token.binary, super(token); + + @override + bool contentEquals(StringLiteral other) { + return other.isBinary == isBinary && other.data == data; + } } diff --git a/sqlparser/lib/src/ast/expressions/reference.dart b/sqlparser/lib/src/ast/expressions/reference.dart new file mode 100644 index 00000000..e3cc993d --- /dev/null +++ b/sqlparser/lib/src/ast/expressions/reference.dart @@ -0,0 +1,20 @@ +part of '../ast.dart'; + +/// Expression that refers to an individual column. +class Reference extends Expression { + final String tableName; + final String columnName; + + Reference({this.tableName, this.columnName}); + + @override + T accept(AstVisitor visitor) => visitor.visitReference(this); + + @override + Iterable get childNodes => const []; + + @override + bool contentEquals(Reference other) { + return other.tableName == tableName && other.columnName == columnName; + } +} diff --git a/sqlparser/lib/src/ast/expressions/simple.dart b/sqlparser/lib/src/ast/expressions/simple.dart index 0c9ef4a4..61ce3adc 100644 --- a/sqlparser/lib/src/ast/expressions/simple.dart +++ b/sqlparser/lib/src/ast/expressions/simple.dart @@ -11,6 +11,11 @@ class UnaryExpression extends Expression { @override Iterable get childNodes => [inner]; + + @override + bool contentEquals(UnaryExpression other) { + return other.operator.type == operator.type; + } } class BinaryExpression extends Expression { @@ -25,6 +30,11 @@ class BinaryExpression extends Expression { @override Iterable get childNodes => [left, right]; + + @override + bool contentEquals(BinaryExpression other) { + return other.operator.type == operator.type; + } } class IsExpression extends Expression { @@ -41,6 +51,11 @@ class IsExpression extends Expression { @override Iterable get childNodes => [left, right]; + + @override + bool contentEquals(IsExpression other) { + return other.negated == negated; + } } class Parentheses extends Expression { @@ -57,4 +72,7 @@ class Parentheses extends Expression { @override Iterable get childNodes => [expression]; + + @override + bool contentEquals(Parentheses other) => true; } diff --git a/sqlparser/lib/src/ast/statements/select.dart b/sqlparser/lib/src/ast/statements/select.dart index e3f43b9c..2ef6fe7a 100644 --- a/sqlparser/lib/src/ast/statements/select.dart +++ b/sqlparser/lib/src/ast/statements/select.dart @@ -2,9 +2,10 @@ part of '../ast.dart'; class SelectStatement extends AstNode { final Expression where; + final List columns; final Limit limit; - SelectStatement({this.where, this.limit}); + SelectStatement({this.where, this.columns, this.limit}); @override T accept(AstVisitor visitor) { @@ -12,5 +13,52 @@ class SelectStatement extends AstNode { } @override - Iterable get childNodes => null; + Iterable get childNodes { + return [ + if (where != null) where, + ...columns, + if (limit != null) limit, + ]; + } + + @override + bool contentEquals(SelectStatement other) { + return true; + } +} + +abstract class ResultColumn extends AstNode { + @override + T accept(AstVisitor visitor) => visitor.visitResultColumn(this); +} + +/// A result column that either yields all columns or all columns from a table +/// by using "*" or "table.*". +class StarResultColumn extends ResultColumn { + final String tableName; + + StarResultColumn(this.tableName); + + @override + Iterable get childNodes => const []; + + @override + bool contentEquals(StarResultColumn other) { + return other.tableName == tableName; + } +} + +class ExpressionResultColumn extends ResultColumn { + final Expression expression; + final String as; + + ExpressionResultColumn({@required this.expression, this.as}); + + @override + Iterable get childNodes => [expression]; + + @override + bool contentEquals(ExpressionResultColumn other) { + return other.as == as; + } } diff --git a/sqlparser/lib/src/reader/parser/num_parser.dart b/sqlparser/lib/src/reader/parser/num_parser.dart new file mode 100644 index 00000000..9d3db55f --- /dev/null +++ b/sqlparser/lib/src/reader/parser/num_parser.dart @@ -0,0 +1,11 @@ +part of 'parser.dart'; + +/// Parses a number from the [lexeme] assuming it has a form conforming to +/// https://www.sqlite.org/syntax/numeric-literal.html +num _parseNumber(String lexeme) { + if (lexeme.startsWith('0x')) { + return int.parse(lexeme.substring(2), radix: 16); + } + + return double.parse(lexeme); +} diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart index a590620c..1f0297c0 100644 --- a/sqlparser/lib/src/reader/parser/parser.dart +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -2,6 +2,8 @@ import 'package:meta/meta.dart'; import 'package:sqlparser/src/ast/ast.dart'; import 'package:sqlparser/src/reader/tokenizer/token.dart'; +part 'num_parser.dart'; + const _comparisonOperators = [ TokenType.less, TokenType.lessEqual, @@ -72,15 +74,61 @@ class Parser { /// Parses a [SelectStatement], or returns null if there is no select token /// after the current position. + /// + /// See also: + /// https://www.sqlite.org/lang_select.html SelectStatement select() { if (!_match(const [TokenType.select])) return null; // todo parse result column + final resultColumns = []; + do { + resultColumns.add(_resultColumn()); + } while (_match(const [TokenType.comma])); final where = _where(); final limit = _limit(); - return SelectStatement(where: where, limit: limit); + return SelectStatement(where: where, columns: resultColumns, limit: limit); + } + + /// Parses a [ResultColumn] or throws if none is found. + /// https://www.sqlite.org/syntax/result-column.html + ResultColumn _resultColumn() { + if (_match(const [TokenType.star])) { + return StarResultColumn(null); + } + + final positionBefore = _current; + + if (_match(const [TokenType.identifier])) { + // two options. the identifier could be followed by ".*", in which case + // we have a star result column. If it's followed by anything else, it can + // still refer to a column in a table as part of a expression result column + final identifier = _previous; + + if (_match(const [TokenType.dot]) && _match(const [TokenType.star])) { + return StarResultColumn((identifier as IdentifierToken).identifier); + } + + // not a star result column. go back and parse the expression. + // todo this is a bit unorthodox. is there a better way to parse the + // expression from before? + _current = positionBefore; + } + + final expr = expression(); + // todo in sqlite, the as is optional + if (_match(const [TokenType.as])) { + if (_match(const [TokenType.identifier])) { + final identifier = (_previous as IdentifierToken).identifier; + return ExpressionResultColumn(expression: expr, as: identifier); + } else { + throw ParsingError(_peek, 'Expected an identifier as the column name'); + } + } + + return ExpressionResultColumn(expression: expr); } /// Parses a where clause if there is one at the current position @@ -222,8 +270,7 @@ class Parser { final type = token.type; switch (type) { case TokenType.numberLiteral: - // todo get the proper value out of this one - return NumericLiteral(42, _peek); + return NumericLiteral(_parseNumber(token.lexeme), _peek); case TokenType.stringLiteral: final token = _peek as StringLiteralToken; return StringLiteral(token); diff --git a/sqlparser/lib/src/reader/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart index c5023a61..d2ee0359 100644 --- a/sqlparser/lib/src/reader/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -47,6 +47,7 @@ enum TokenType { select, from, + as, where, limit, @@ -58,6 +59,7 @@ enum TokenType { const Map keywords = { 'SELECT': TokenType.select, 'FROM': TokenType.from, + 'AS': TokenType.as, 'WHERE': TokenType.where, 'LIMIT': TokenType.limit, 'OFFSET': TokenType.offset, @@ -80,6 +82,7 @@ class Token { final TokenType type; final SourceSpan span; + String get lexeme => span.text; const Token(this.type, this.span); } @@ -99,6 +102,8 @@ class IdentifierToken extends Token { /// always interpreted as an column name. final bool escapedColumnName; + String get identifier => lexeme; + const IdentifierToken(this.escapedColumnName, SourceSpan span) : super(TokenType.identifier, span); } diff --git a/sqlparser/lib/src/utils/ast_equality.dart b/sqlparser/lib/src/utils/ast_equality.dart index 21b96160..27cc9b30 100644 --- a/sqlparser/lib/src/utils/ast_equality.dart +++ b/sqlparser/lib/src/utils/ast_equality.dart @@ -5,7 +5,10 @@ void enforceEqual(AstNode a, AstNode b) { if (a.runtimeType != b.runtimeType) { throw ArgumentError('Not equal: First was $a, second $b'); } - _checkAdditional(a, b); + + if (!a.contentEquals(b)) { + throw ArgumentError('Content not equal: $a and $b'); + } final childrenA = a.childNodes.iterator; final childrenB = b.childNodes.iterator; @@ -19,27 +22,3 @@ void enforceEqual(AstNode a, AstNode b) { throw ArgumentError("$a and $b don't have an equal amount of children"); } } - -void _checkAdditional(T a, T b) { - if (a is IsExpression) { - return _checkIsExpr(a, b as IsExpression); - } else if (a is UnaryExpression) { - _checkUnaryExpression(a, b as UnaryExpression); - } else if (a is BinaryExpression) { - _checkBinaryExpression(a, b as BinaryExpression); - } -} - -void _checkIsExpr(IsExpression a, IsExpression b) { - if (a.negated != b.negated) { - throw ArgumentError('Negation status not the same'); - } -} - -void _checkUnaryExpression(UnaryExpression a, UnaryExpression b) { - if (a.operator.type != b.operator.type) throw ArgumentError('Different type'); -} - -void _checkBinaryExpression(BinaryExpression a, BinaryExpression b) { - if (a.operator.type != b.operator.type) throw ArgumentError('Different type'); -} diff --git a/sqlparser/test/parser/expression_test.dart b/sqlparser/test/parser/expression_test.dart index 8b0a76be..b80e7bd1 100644 --- a/sqlparser/test/parser/expression_test.dart +++ b/sqlparser/test/parser/expression_test.dart @@ -31,4 +31,25 @@ void main() { ), ); }); + + test('parses select statements', () { + final scanner = Scanner('SELECT table.*, *, 1 as name'); + final tokens = scanner.scanTokens(); + final parser = Parser(tokens); + + final stmt = parser.select(); + enforceEqual( + stmt, + SelectStatement( + columns: [ + StarResultColumn('table'), + StarResultColumn(null), + ExpressionResultColumn( + expression: NumericLiteral(1, token(TokenType.numberLiteral)), + as: 'name', + ), + ], + ), + ); + }); } From be5bcfd459e92963d675d75075deba9039cfb2f8 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 18 Jun 2019 14:49:30 +0200 Subject: [PATCH 9/9] Parse "ORDER BY" clause --- sqlparser/lib/src/ast/ast.dart | 3 ++ sqlparser/lib/src/ast/clauses/ordering.dart | 38 ++++++++++++++++ sqlparser/lib/src/ast/statements/select.dart | 4 +- sqlparser/lib/src/reader/parser/parser.dart | 45 ++++++++++++++++++- sqlparser/lib/src/reader/tokenizer/token.dart | 9 ++++ sqlparser/test/parser/expression_test.dart | 12 ++++- 6 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 sqlparser/lib/src/ast/clauses/ordering.dart diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart index 2b37ddff..5586e25a 100644 --- a/sqlparser/lib/src/ast/ast.dart +++ b/sqlparser/lib/src/ast/ast.dart @@ -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 visitSelectStatement(SelectStatement e); T visitResultColumn(ResultColumn e); + T visitOrderBy(OrderBy e); + T visitOrderingTerm(OrderingTerm e); T visitLimit(Limit e); T visitBinaryExpression(BinaryExpression e); diff --git a/sqlparser/lib/src/ast/clauses/ordering.dart b/sqlparser/lib/src/ast/clauses/ordering.dart new file mode 100644 index 00000000..4e36bdd6 --- /dev/null +++ b/sqlparser/lib/src/ast/clauses/ordering.dart @@ -0,0 +1,38 @@ +part of '../ast.dart'; + +class OrderBy extends AstNode { + final List terms; + + OrderBy({this.terms}); + + @override + T accept(AstVisitor visitor) => visitor.visitOrderBy(this); + + @override + Iterable 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(AstVisitor visitor) => visitor.visitOrderingTerm(this); + + @override + Iterable get childNodes => [expression]; + + @override + bool contentEquals(OrderingTerm other) { + return other.orderingMode == orderingMode; + } +} diff --git a/sqlparser/lib/src/ast/statements/select.dart b/sqlparser/lib/src/ast/statements/select.dart index 2ef6fe7a..742b4780 100644 --- a/sqlparser/lib/src/ast/statements/select.dart +++ b/sqlparser/lib/src/ast/statements/select.dart @@ -3,9 +3,10 @@ part of '../ast.dart'; class SelectStatement extends AstNode { final Expression where; final List 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(AstVisitor visitor) { @@ -18,6 +19,7 @@ class SelectStatement extends AstNode { if (where != null) where, ...columns, if (limit != null) limit, + if (orderBy != null) orderBy, ]; } diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart index 1f0297c0..02b3f7ef 100644 --- a/sqlparser/lib/src/reader/parser/parser.dart +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -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 = []; + 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; } diff --git a/sqlparser/lib/src/reader/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart index d2ee0359..23349f02 100644 --- a/sqlparser/lib/src/reader/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -50,6 +50,11 @@ enum TokenType { as, where, + order, + by, + asc, + desc, + limit, offset, @@ -61,6 +66,10 @@ const Map 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, diff --git a/sqlparser/test/parser/expression_test.dart b/sqlparser/test/parser/expression_test.dart index b80e7bd1..1c1de828 100644 --- a/sqlparser/test/parser/expression_test.dart +++ b/sqlparser/test/parser/expression_test.dart @@ -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)), + ), ), ); });