Relax constraints on element order in drift files

This commit is contained in:
Simon Binder 2022-12-03 00:06:05 +01:00
parent 1a51dbf4d6
commit 387a21c580
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
7 changed files with 73 additions and 48 deletions

View File

@ -62,11 +62,10 @@ class DatabaseWriter {
..write('class $className extends ')
..writeDriftRef('GeneratedDatabase')
..writeln('{')
..writeln(
'$className(${firstLeaf.refDrift('QueryExecutor e')}): super(e);');
..writeln('$className(${firstLeaf.drift('QueryExecutor e')}): super(e);');
if (dbScope.options.generateConnectConstructor) {
final conn = firstLeaf.refDrift('DatabaseConnection');
final conn = firstLeaf.drift('DatabaseConnection');
firstLeaf.write('$className.connect($conn c): super.connect(c); \n');
}
@ -220,7 +219,7 @@ class DatabaseWriter {
if (scope.options.storeDateTimeValuesAsText) {
// Override database options to reflect that DateTimes are stored as text.
final options = schemaScope.refDrift('DriftDatabaseOptions');
final options = schemaScope.drift('DriftDatabaseOptions');
schemaScope
..writeln('@override')

View File

@ -664,14 +664,18 @@ class _ExpandedVariableWriter {
String constructVar(String dartExpr) {
// No longer an array here, we apply a for loop if necessary
final type = element.innerColumnType(nullable: false);
final buffer = StringBuffer('Variable<$type>(');
final varType = _emitter.drift('Variable');
final buffer = StringBuffer('$varType<$type>(');
final capture = element.forCaptured;
final converter = element.typeConverter;
if (converter != null) {
// Apply the converter.
if (element.nullable && converter.canBeSkippedForNulls) {
buffer.write('NullAwareTypeConverter.wrapToSql('
final nullAware = _emitter.drift('NullAwareTypeConverter');
buffer.write('$nullAware.wrapToSql('
'${_converter(_emitter, element.typeConverter!)}, $dartExpr)');
} else {
buffer.write(

View File

@ -7,8 +7,6 @@ import '../analysis/options.dart';
import 'import_manager.dart';
import 'queries/sql_writer.dart';
Uri _driftImport = Uri.parse('package:drift/drift.dart');
/// Manages a tree structure which we use to generate code.
///
/// Each leaf in the tree is a [StringBuffer] that contains some code. A
@ -287,9 +285,7 @@ class TextEmitter extends _Node {
return write(refUri(definition, element));
}
void writeDriftRef(String element) => write(refDrift(element));
String refDrift(String element) => refUri(_driftImport, element);
void writeDriftRef(String element) => write(drift(element));
void writeDart(AnnotatedDartCode code) => write(dartCode(code));

View File

@ -7,6 +7,10 @@ CREATE VIRTUAL TABLE search_in_posts USING fts5 (
content_rowid=id
);
search: WITH relevant_ports AS (SELECT rowid FROM search_in_posts WHERE search_in_posts MATCH ?)
SELECT posts.* FROM relevant_ports results
INNER JOIN posts ON id = results.rowid;
-- Keep fts5 table and posts synchronized
CREATE TRIGGER posts_insert AFTER INSERT ON posts BEGIN

View File

@ -205,5 +205,20 @@ i0.Trigger get postsDelete => i0.Trigger(
class SearchDrift extends i2.ModularAccessor {
SearchDrift(i0.GeneratedDatabase db) : super(db);
i0.Selectable<i3.Post> search(String var1) {
return customSelect(
'WITH relevant_ports AS (SELECT "rowid" FROM search_in_posts WHERE search_in_posts MATCH ?1) SELECT posts.* FROM relevant_ports AS results INNER JOIN posts ON id = results."rowid"',
variables: [
i0.Variable<String>(var1)
],
readsFrom: {
searchInPosts,
posts,
}).asyncMap(posts.mapFromRow);
}
i1.SearchInPosts get searchInPosts =>
this.resultSet<i1.SearchInPosts>('search_in_posts');
i3.Posts get posts => this.resultSet<i3.Posts>('posts');
i3.PostsDrift get postsDrift => this.accessor(i3.PostsDrift.new);
}

View File

@ -33,9 +33,6 @@ class Parser {
final List<ParsingError> errors = [];
final AutoCompleteEngine? autoComplete;
// todo remove this and don't be that lazy in driftFile()
var _lastStmtHadParsingError = false;
/// Whether to enable the extensions drift makes to the sql grammar.
final bool enableDriftExtensions;
@ -242,31 +239,8 @@ class Parser {
final first = _peek;
final foundComponents = <PartOfDriftFile?>[];
// (we try again if the last statement had a parsing error)
// first, parse import statements
for (var stmt = _parseAsStatement(_import);
stmt != null || _lastStmtHadParsingError;
stmt = _parseAsStatement(_import)) {
foundComponents.add(stmt);
}
// next, table declarations
for (var stmt = _parseAsStatement(_create);
stmt != null || _lastStmtHadParsingError;
stmt = _parseAsStatement(_create)) {
foundComponents.add(stmt);
}
// finally, declared statements
for (var stmt = _parseAsStatement(_declaredStatement);
stmt != null || _lastStmtHadParsingError;
stmt = _parseAsStatement(_declaredStatement)) {
foundComponents.add(stmt);
}
if (!_isAtEnd) {
_error('Expected the file to end here.');
while (!_isAtEnd) {
foundComponents.add(_parseAsStatement(_partOfDriftFile));
}
foundComponents.removeWhere((c) => c == null);
@ -275,11 +249,26 @@ class Parser {
if (foundComponents.isNotEmpty) {
file.setSpan(first, _previous);
} else {
_suggestHintForTokens([TokenType.create, TokenType.import]);
if (_reportAutoComplete) {}
file.setSpan(first, first); // empty file
}
return file;
}
PartOfDriftFile _partOfDriftFile() {
final found = _import() ?? _create() ?? _declaredStatement();
if (found != null) {
return found;
}
_error('Expected `IMPORT`, `CREATE`, or an identifier starting a compiled '
'query.');
}
ImportStatement? _import() {
if (_matchOne(TokenType.import)) {
final importToken = _previous;
@ -393,7 +382,6 @@ class Parser {
/// semicolon if one exists.
T? _parseAsStatement<T extends Statement>(T? Function() parser,
{bool requireSemicolon = true}) {
_lastStmtHadParsingError = false;
final first = _peek;
T? result;
try {
@ -405,7 +393,6 @@ class Parser {
result.setSpan(first, _previous);
}
} on ParsingError {
_lastStmtHadParsingError = true;
// the error is added to the list errors, so ignore. We skip after the
// next semicolon to parse the next statement.
_synchronize(TokenType.semicolon, skipTarget: true);
@ -422,13 +409,15 @@ class Parser {
return result;
}
List<CrudStatement> _crudStatements() {
List<CrudStatement> _crudStatements(bool Function() reachedEnd) {
final stmts = <CrudStatement>[];
for (var stmt = _parseAsStatement(_crud);
stmt != null || _lastStmtHadParsingError;
stmt = _parseAsStatement(_crud)) {
if (stmt != null) stmts.add(stmt);
while (!reachedEnd()) {
final stmt = _parseAsStatement(_crud);
if (stmt != null) {
stmts.add(stmt);
}
}
return stmts;
@ -437,7 +426,7 @@ class Parser {
/// Parses a block, which consists of statements between `BEGIN` and `END`.
Block _consumeBlock() {
final begin = _consume(TokenType.begin, 'Expected BEGIN');
final stmts = _crudStatements();
final stmts = _crudStatements(() => _check(TokenType.end));
final end = _consume(TokenType.end, 'Expected END');
return Block(stmts)
@ -449,7 +438,8 @@ class Parser {
TransactionBlock _transactionBlock() {
final first = _peek;
final begin = _beginStatement();
final stmts = _crudStatements();
final stmts = _crudStatements(
() => _checkAny(const [TokenType.commit, TokenType.end]));
final end = _commit();
return TransactionBlock(begin: begin, innerStatements: stmts, commit: end)

View File

@ -219,4 +219,21 @@ SELECT DISTINCT A.* FROM works A, works B ON A.id =
]),
);
});
test('allows statements to appear in any order', () {
final result =
SqlEngine(EngineOptions(useDriftExtensions: true)).parseDriftFile('''
CREATE TABLE foo (
a INTEGER NOT NULL
);
import 'b.dart';
a: SELECT * FROM foo;
CREATE INDEX x ON foo (a);
''');
expect(result.errors, isEmpty);
});
}