diff --git a/moor/test/data/tables/custom_tables.dart b/moor/test/data/tables/custom_tables.dart index 027b1735..71e9feb5 100644 --- a/moor/test/data/tables/custom_tables.dart +++ b/moor/test/data/tables/custom_tables.dart @@ -2,7 +2,10 @@ import 'package:moor/moor.dart'; part 'custom_tables.g.dart'; -@UseMoor(include: {'tables.moor'}) +@UseMoor( + include: {'tables.moor'}, + queries: {'writeConfig': 'REPLACE INTO config VALUES (:key, :value)'}, +) class CustomTablesDb extends _$CustomTablesDb { CustomTablesDb(QueryExecutor e) : super(e); diff --git a/moor/test/data/tables/custom_tables.g.dart b/moor/test/data/tables/custom_tables.g.dart index 7a7b75c9..c5d6c701 100644 --- a/moor/test/data/tables/custom_tables.g.dart +++ b/moor/test/data/tables/custom_tables.g.dart @@ -812,6 +812,21 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { Config get config => _config ??= Config(this); Mytable _mytable; Mytable get mytable => _mytable ??= Mytable(this); + Future writeConfig( + String key, + String value, + {@Deprecated('No longer needed with Moor 1.6 - see the changelog for details') + QueryEngine operateOn}) { + return (operateOn ?? this).customInsert( + 'REPLACE INTO config VALUES (:key, :value)', + variables: [ + Variable.withString(key), + Variable.withString(value), + ], + updates: {config}, + ); + } + @override List get allTables => [noIds, withDefaults, withConstraints, config, mytable]; diff --git a/moor_generator/lib/src/model/sql_query.dart b/moor_generator/lib/src/model/sql_query.dart index f5b19cc5..855a934f 100644 --- a/moor_generator/lib/src/model/sql_query.dart +++ b/moor_generator/lib/src/model/sql_query.dart @@ -51,9 +51,11 @@ class SqlSelectQuery extends SqlQuery { class UpdatingQuery extends SqlQuery { final List updates; + final bool isInsert; UpdatingQuery(String name, AnalysisContext fromContext, - List variables, this.updates) + List variables, this.updates, + {this.isInsert = false}) : super(name, fromContext, variables); } diff --git a/moor_generator/lib/src/parser/sql/affected_tables_visitor.dart b/moor_generator/lib/src/parser/sql/affected_tables_visitor.dart index 795e6882..39679a66 100644 --- a/moor_generator/lib/src/parser/sql/affected_tables_visitor.dart +++ b/moor_generator/lib/src/parser/sql/affected_tables_visitor.dart @@ -52,4 +52,10 @@ class UpdatedTablesVisitor extends RecursiveVisitor { _addIfResolved(e.table); visitChildren(e); } + + @override + void visitInsertStatement(InsertStatement e) { + _addIfResolved(e.table); + visitChildren(e); + } } diff --git a/moor_generator/lib/src/parser/sql/query_handler.dart b/moor_generator/lib/src/parser/sql/query_handler.dart index f93d3e6f..bcea44b3 100644 --- a/moor_generator/lib/src/parser/sql/query_handler.dart +++ b/moor_generator/lib/src/parser/sql/query_handler.dart @@ -26,7 +26,9 @@ class QueryHandler { if (root is SelectStatement) { return _handleSelect(); - } else if (root is UpdateStatement || root is DeleteStatement) { + } else if (root is UpdateStatement || + root is DeleteStatement || + root is InsertStatement) { return _handleUpdate(); } else { throw StateError( @@ -39,8 +41,11 @@ class QueryHandler { context.root.accept(updatedFinder); _foundTables = updatedFinder.foundTables; + final isInsert = context.root is InsertStatement; + return UpdatingQuery(name, context, _foundVariables, - _foundTables.map(mapper.tableToMoor).toList()); + _foundTables.map(mapper.tableToMoor).toList(), + isInsert: isInsert); } SqlSelectQuery _handleSelect() { diff --git a/moor_generator/lib/src/writer/query_writer.dart b/moor_generator/lib/src/writer/query_writer.dart index 8c98d325..bf40cd6d 100644 --- a/moor_generator/lib/src/writer/query_writer.dart +++ b/moor_generator/lib/src/writer/query_writer.dart @@ -155,6 +155,8 @@ class QueryWriter { return customUpdate('', variables: [], updates: {}); } */ + final implName = _update.isInsert ? 'customInsert' : 'customUpdate'; + buffer.write('Future ${query.name}('); _writeParameters(buffer); buffer.write(') {\n'); @@ -162,7 +164,7 @@ class QueryWriter { _writeExpandedDeclarations(buffer); buffer ..write('return (operateOn ?? this).') - ..write('customUpdate(${_queryCode()},'); + ..write('$implName(${_queryCode()},'); _writeVariables(buffer); buffer.write(','); diff --git a/sqlparser/lib/src/reader/parser/expressions.dart b/sqlparser/lib/src/reader/parser/expressions.dart index 594f0a6e..bdf6879c 100644 --- a/sqlparser/lib/src/reader/parser/expressions.dart +++ b/sqlparser/lib/src/reader/parser/expressions.dart @@ -326,8 +326,10 @@ mixin ExpressionParser on ParserBase { break; case TokenType.colon: final colon = token; - final identifier = _consume(TokenType.identifier, - 'Expected an identifier for the named variable') as IdentifierToken; + final identifier = _consumeIdentifier( + 'Expected an identifier for the named variable', + lenient: true); + final content = identifier.identifier; return ColonNamedVariable(':$content')..setSpan(colon, identifier); default: @@ -392,6 +394,7 @@ mixin ExpressionParser on ParserBase { )..setSpan(name, _previous); } + @override TupleExpression _consumeTuple() { final firstToken = _consume(TokenType.leftParen, 'Expected opening parenthesis for tuple'); diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart index 05a39cbd..a47fbc7f 100644 --- a/sqlparser/lib/src/reader/parser/parser.dart +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -123,7 +123,13 @@ abstract class ParserBase { _error(message); } - IdentifierToken _consumeIdentifier(String message) { + /// Consumes an identifier. If [lenient] is true and the next token is not + /// an identifier but rather a [KeywordToken], that token will be converted + /// to an identifier. + IdentifierToken _consumeIdentifier(String message, {bool lenient = false}) { + if (lenient && _peek is KeywordToken) { + return (_advance() as KeywordToken).convertToIdentifier(); + } return _consume(TokenType.identifier, message) as IdentifierToken; } diff --git a/sqlparser/lib/src/reader/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart index ebc12054..65f30733 100644 --- a/sqlparser/lib/src/reader/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -268,6 +268,11 @@ class IdentifierToken extends Token { /// Whether this identifier was escaped by putting it in "double ticks". final bool escaped; + /// Whether this identifier token is synthetic. We sometimes convert + /// [KeywordToken]s to identifiers if they're unambiguous, in which case + /// [synthetic] will be true on this token because it was not scanned as such. + final bool synthetic; + String get identifier { if (escaped) { return lexeme.substring(1, lexeme.length - 1); @@ -276,7 +281,7 @@ class IdentifierToken extends Token { } } - const IdentifierToken(this.escaped, FileSpan span) + const IdentifierToken(this.escaped, FileSpan span, {this.synthetic = false}) : super(TokenType.identifier, span); } @@ -295,7 +300,16 @@ class InlineDartToken extends Token { /// additional properties to ease syntax highlighting, as it allows us to find /// the keywords easily. class KeywordToken extends Token { + /// Whether this token has been used as an identifier while parsing. + bool isIdentifier; + KeywordToken(TokenType type, FileSpan span) : super(type, span); + + IdentifierToken convertToIdentifier() { + isIdentifier = true; + + return IdentifierToken(false, span, synthetic: false); + } } class TokenizerError {