Always enable the new SQL generation mode

This commit is contained in:
Simon Binder 2022-07-03 21:53:17 +02:00
parent 19a35e8e1c
commit 116f98269d
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
17 changed files with 55 additions and 270 deletions

View File

@ -74,9 +74,6 @@ At the moment, drift supports these options:
to `null`.
* `named_parameters`: Generates named parameters for named variables in SQL queries.
* `named_parameters_always_required`: All named parameters (generated if `named_parameters` option is `true`) will be required in Dart.
* `new_sql_code_generation`: Generates SQL statements from the parsed AST instead of replacing substrings. This will also remove
unnecessary whitespace and comments.
If enabling this option breaks your queries, please file an issue!
* `scoped_dart_components`: Generates a function parameter for [Dart placeholders]({{ '../Using SQL/drift_files.md#dart-components-in-sql' | pageUrl }}) in SQL.
The function has a parameter for each table that is available in the query, making it easier to get aliases right when using
Dart placeholders.
@ -150,7 +147,6 @@ At the moment, they're opt-in to not break existing users. These options are:
- `apply_converters_on_variables`
- `generate_values_in_copy_with`
- `new_sql_code_generation`
- `scoped_dart_components`
We recommend enabling these options.

View File

@ -201,9 +201,6 @@ Internally, drift will split this query into two separate queries:
While `LIST()` subqueries are a very powerful feature, they can be costly when the outer query
has lots of rows (as the inner query is executed for each outer row).
Also, as `LIST()` needs a semantic rewrite of the original statement, this feature is only
supported with the `new_sql_code_generation` [build option]({{ '../Advanced Features/builder_options.md' | pageUrl }}).
## Dart interop
Drift files work perfectly together with drift's existing Dart API:

View File

@ -12,7 +12,6 @@ targets:
apply_converters_on_variables: true
generate_values_in_copy_with: true
named_parameters: true
new_sql_code_generation: true
scoped_dart_components: true
sql:
dialect: sqlite

View File

@ -1,7 +1,8 @@
## 2.0.0-dev
- Remove the `null_aware_type_converters` build option. Effectively, it is always
turned on now.
- Removes the following build options, which are always turned on now:
- `null_aware_type_converters`
- `new_sql_code_generation`
- Starting from this version, drift only supports generating non-nullable Dart code.
You'll have to at least opt your database code into null-safety to use this and
upcoming drift releases.

View File

@ -95,9 +95,6 @@ class MoorOptions {
@JsonKey(name: 'named_parameters_always_required', defaultValue: false)
final bool namedParametersAlwaysRequired;
@JsonKey(name: 'new_sql_code_generation', defaultValue: false)
final bool newSqlCodeGeneration;
@JsonKey(name: 'scoped_dart_components', defaultValue: false)
final bool scopedDartComponents;
@ -118,7 +115,6 @@ class MoorOptions {
this.generateValuesInCopyWith = false,
this.generateNamedParameters = false,
this.namedParametersAlwaysRequired = false,
this.newSqlCodeGeneration = false,
this.scopedDartComponents = false,
this.modules = const [],
this.sqliteAnalysisOptions,
@ -141,7 +137,6 @@ class MoorOptions {
required this.generateValuesInCopyWith,
required this.generateNamedParameters,
required this.namedParametersAlwaysRequired,
required this.newSqlCodeGeneration,
required this.scopedDartComponents,
required this.modules,
required this.sqliteAnalysisOptions,

View File

@ -31,7 +31,6 @@ MoorOptions _$MoorOptionsFromJson(Map json) => $checkedCreate(
'generate_values_in_copy_with',
'named_parameters',
'named_parameters_always_required',
'new_sql_code_generation',
'scoped_dart_components'
],
);
@ -68,8 +67,6 @@ MoorOptions _$MoorOptionsFromJson(Map json) => $checkedCreate(
$checkedConvert('named_parameters', (v) => v as bool? ?? false),
namedParametersAlwaysRequired: $checkedConvert(
'named_parameters_always_required', (v) => v as bool? ?? false),
newSqlCodeGeneration: $checkedConvert(
'new_sql_code_generation', (v) => v as bool? ?? false),
scopedDartComponents: $checkedConvert(
'scoped_dart_components', (v) => v as bool? ?? false),
modules: $checkedConvert(
@ -107,7 +104,6 @@ MoorOptions _$MoorOptionsFromJson(Map json) => $checkedCreate(
'generateValuesInCopyWith': 'generate_values_in_copy_with',
'generateNamedParameters': 'named_parameters',
'namedParametersAlwaysRequired': 'named_parameters_always_required',
'newSqlCodeGeneration': 'new_sql_code_generation',
'scopedDartComponents': 'scoped_dart_components',
'modules': 'sqlite_modules',
'sqliteAnalysisOptions': 'sqlite',

View File

@ -41,12 +41,8 @@ abstract class MoorDeclaration extends Declaration {
extension ToSql on MoorDeclaration {
String exportSql(MoorOptions options) {
if (options.newSqlCodeGeneration) {
final writer = SqlWriter(options, escapeForDart: false);
return writer.writeSql(node);
} else {
return node.span!.text;
}
final writer = SqlWriter(options, escapeForDart: false);
return writer.writeSql(node);
}
}

View File

@ -38,7 +38,7 @@ class SpecialQuery implements MoorSchemaEntity {
String formattedSql(MoorOptions options) {
final decl = declaration;
if (decl is MoorSpecialQueryDeclaration && options.newSqlCodeGeneration) {
if (decl is MoorSpecialQueryDeclaration) {
final writer = SqlWriter(options, escapeForDart: false);
return writer.writeSql(decl.node.statement);
}

View File

@ -70,8 +70,7 @@ class SchemaWriter {
type = 'view';
data = {
'name': entity.name,
'sql': entity
.createSql(const MoorOptions.defaults(newSqlCodeGeneration: true)),
'sql': entity.createSql(const MoorOptions.defaults()),
'dart_data_name': entity.dartTypeName,
'dart_info_name': entity.entityInfoName,
'columns': [for (final column in entity.columns) _columnData(column)],

View File

@ -1,5 +1,3 @@
import 'dart:math' show max;
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:drift_dev/src/analyzer/sql_queries/explicit_alias_transformer.dart';
@ -13,9 +11,6 @@ import 'sql_writer.dart';
const highestAssignedIndexVar = '\$arrayStartIndex';
int _compareNodes(AstNode a, AstNode b) =>
a.firstPosition.compareTo(b.firstPosition);
/// Writes the handling code for a query. The code emitted will be a method that
/// should be included in a generated database or dao class.
class QueryWriter {
@ -37,14 +32,17 @@ class QueryWriter {
ResultSetWriter(query, resultSetScope).write();
}
// The new sql code generation generates query code from the parsed AST,
// which eliminates unnecessary whitespace and comments. These can sometimes
// have a semantic meaning though, for instance when they're used in
// columns. So, we transform the query to add an explicit alias to every
// column!
// We generate the Dart string literal for the SQL query by walking the
// parsed AST. This eliminates unecessary whitespace and comments in the
// generated code.
// In some cases, the whitespace has an impact on the semantic of the
// query. For instance, `SELECT 1 + 2` has a different column name than
// `SELECT 1+2`. To work around this, we transform the query to add an
// explicit alias to every column (since whitespace doesn't matter if the
// query is written as `SELECT 1+2 AS c0`).
// We do this transformation so late because it shouldn't have an impact on
// analysis, Dart getter names stay the same.
if (resultSet != null && options.newSqlCodeGeneration) {
if (resultSet != null) {
_transformer = ExplicitAliasTransformer();
_transformer.rewrite(query.root!);
@ -66,13 +64,6 @@ class QueryWriter {
}
void _writeSelect(SqlSelectQuery select) {
if (select.hasNestedQuery && !scope.options.newSqlCodeGeneration) {
throw UnsupportedError(
'Using nested result queries (with `LIST`) requires the '
'`new_sql_code_generation` build option.',
);
}
_writeSelectStatementCreator(select);
if (!select.declaredInMoorFile && !options.compactQueryMethods) {
@ -174,10 +165,7 @@ class QueryWriter {
rawDartType = '$rawDartType?';
}
String? specialName;
if (options.newSqlCodeGeneration) {
specialName = _transformer.newNameFor(column.sqlParserColumn!);
}
final specialName = _transformer.newNameFor(column.sqlParserColumn!);
final dartLiteral = asDartLiteral(specialName ?? column.name);
var code = 'row.read<$rawDartType>($dartLiteral)';
@ -455,100 +443,7 @@ class QueryWriter {
/// been expanded. For instance, 'SELECT * FROM t WHERE x IN ?' will be turned
/// into 'SELECT * FROM t WHERE x IN ($expandedVar1)'.
String _queryCode(SqlQuery query) {
if (scope.options.newSqlCodeGeneration) {
return SqlWriter(scope.options, query: query).write();
} else {
return _legacyQueryCode(query);
}
}
String _legacyQueryCode(SqlQuery query) {
final root = query.root!;
final sql = query.fromContext!.sql;
// sort variables and placeholders by the order in which they appear
final toReplace = root.allDescendants
.where((node) =>
node is Variable ||
node is DartPlaceholder ||
node is NestedStarResultColumn)
.toList()
..sort(_compareNodes);
final buffer = StringBuffer("'");
// Index nested results by their syntactic origin for faster lookups later
var doubleStarColumnToResolvedTable =
const <NestedStarResultColumn, NestedResultTable>{};
if (query is SqlSelectQuery) {
doubleStarColumnToResolvedTable = {
for (final nestedResult in query.resultSet.nestedResults)
if (nestedResult is NestedResultTable) nestedResult.from: nestedResult
};
}
var lastIndex = root.firstPosition;
void replaceNode(AstNode node, String content) {
// write everything that comes before this var into the buffer
final currentIndex = node.firstPosition;
final queryPart = sql.substring(lastIndex, currentIndex);
buffer.write(escapeForDart(queryPart));
lastIndex = node.lastPosition;
// write the replaced content
buffer.write(content);
}
for (final rewriteTarget in toReplace) {
if (rewriteTarget is Variable) {
final moorVar = query.variables.singleWhere(
(f) => f.variable.resolvedIndex == rewriteTarget.resolvedIndex);
if (moorVar.isArray) {
replaceNode(rewriteTarget, '(\$${expandedName(moorVar)})');
}
} else if (rewriteTarget is DartPlaceholder) {
final moorPlaceholder =
query.placeholders.singleWhere((p) => p.astNode == rewriteTarget);
replaceNode(rewriteTarget,
'\${${placeholderContextName(moorPlaceholder)}.sql}');
} else if (rewriteTarget is NestedStarResultColumn) {
final result = doubleStarColumnToResolvedTable[rewriteTarget];
if (result == null) continue;
// weird cast here :O
final prefix =
(query as SqlSelectQuery).resultSet.nestedPrefixFor(result);
final table = rewriteTarget.tableName;
// Convert foo.** to "foo.a" AS "nested_0.a", ... for all columns in foo
final expanded = StringBuffer();
var isFirst = true;
for (final column in result.table.columns) {
if (isFirst) {
isFirst = false;
} else {
expanded.write(', ');
}
final columnName = column.name.name;
expanded.write('"$table"."$columnName" AS "$prefix.$columnName"');
}
replaceNode(rewriteTarget, expanded.toString());
}
}
// write the final part after the last variable, plus the ending '
final lastPosition = root.lastPosition;
buffer
..write(escapeForDart(sql.substring(lastIndex, lastPosition)))
..write("'");
return buffer.toString();
return SqlWriter(scope.options, query: query).write();
}
void _writeReadsFrom(SqlSelectQuery select) {
@ -604,10 +499,30 @@ class _ExpandedDeclarationWriter {
_ExpandedDeclarationWriter(this.query, this.options, this._buffer);
void writeExpandedDeclarations() {
if (options.newSqlCodeGeneration) {
_writeExpandedDeclarationsForNewQueryCode();
} else {
_writeLegacyExpandedDeclarations();
// When the SQL query is written to a Dart string, we give each variable an
// eplixit index (e.g `?2`), regardless of how it was declared in the
// source.
// Array variables are converted into multiple variables at runtime, but
// let's give variables before that an index, all other variables can be
// turned into explicit indices though. We ensure that array variables have
// higher indices than other variables.
var index = 0;
for (final variable in query.variables) {
if (!variable.isArray) {
// Re-assign continous indices to non-array variables
highestIndexBeforeArray = variable.index = ++index;
}
}
needsIndexCounter = true;
for (final element in query.elementsWithNestedQueries()) {
if (element is FoundVariable) {
if (element.isArray) {
_writeArrayVariable(element);
}
} else if (element is FoundDartPlaceholder) {
_writeDartPlaceholder(element);
}
}
}
@ -652,57 +567,6 @@ class _ExpandedDeclarationWriter {
}
}
void _writeLegacyExpandedDeclarations() {
for (final variable in query.variables) {
// Variables use an explicit index, we need to know the start index at
// runtime (can be dynamic when placeholders or other arrays appear before
// this one)
if (variable.isArray) {
needsIndexCounter = true;
break;
}
highestIndexBeforeArray = max(highestIndexBeforeArray, variable.index);
}
// query.elements are guaranteed to be sorted in the order in which they're
// going to have an effect when expanded. See TypeMapper.extractElements for
// the gory details.
for (final element in query.elements) {
if (element is FoundVariable) {
if (element.isArray) {
_writeArrayVariable(element);
}
} else if (element is FoundDartPlaceholder) {
_writeDartPlaceholder(element);
}
}
}
void _writeExpandedDeclarationsForNewQueryCode() {
// In the new code generation, each variable is given an explicit index,
// regardless of how it was declared. in the source. We then start writing
// expanded declarations with higher indices, but otherwise in order.
var index = 0;
for (final variable in query.variables) {
if (!variable.isArray) {
// Re-assign continous indices to non-array variables
highestIndexBeforeArray = variable.index = ++index;
}
}
needsIndexCounter = true;
for (final element in query.elementsWithNestedQueries()) {
if (element is FoundVariable) {
if (element.isArray) {
_writeArrayVariable(element);
}
} else if (element is FoundDartPlaceholder) {
_writeDartPlaceholder(element);
}
}
}
void _writeDartPlaceholder(FoundDartPlaceholder element) {
String useExpression() {
if (element.writeAsScopedFunction(options)) {
@ -738,11 +602,9 @@ class _ExpandedDeclarationWriter {
..write(r'$writeInsertable(this.')
..write(table?.dbGetterName)
..write(', ')
..write(useExpression());
..write(useExpression())
..write(', startIndex: $highestAssignedIndexVar');
if (options.newSqlCodeGeneration) {
_buffer.write(', startIndex: $highestAssignedIndexVar');
}
_buffer.write(');\n');
} else {
_buffer
@ -751,11 +613,9 @@ class _ExpandedDeclarationWriter {
if (query.hasMultipleTables) {
_buffer.write(', hasMultipleTables: true');
}
if (options.newSqlCodeGeneration) {
_buffer.write(', startIndex: $highestAssignedIndexVar');
}
_buffer.write(');\n');
_buffer
..write(', startIndex: $highestAssignedIndexVar')
..write(');\n');
}
// similar to the case for expanded array variables, we need to
@ -794,13 +654,7 @@ class _ExpandedVariableWriter {
void writeVariables() {
_buffer.write('variables: [');
if (scope.options.newSqlCodeGeneration) {
_writeNewVariables();
} else {
_writeLegacyVariables();
}
_writeNewVariables();
_buffer.write(']');
}
@ -835,18 +689,6 @@ class _ExpandedVariableWriter {
}
}
void _writeLegacyVariables() {
var first = true;
for (final element in query.elements) {
if (!first) {
_buffer.write(', ');
}
first = false;
_writeElement(element);
}
}
void _writeElement(FoundElement element) {
if (element is FoundVariable) {
_writeVariable(element);

View File

@ -266,7 +266,7 @@ const expected = r'''
2
],
"name": "delete_empty_groups",
"sql": "CREATE TRIGGER delete_empty_groups AFTER DELETE ON group_members BEGIN\n DELETE FROM \"groups\"\n WHERE NOT EXISTS (SELECT * FROM group_members WHERE \"group\" = \"groups\".id);\nEND;"
"sql": "CREATE TRIGGER delete_empty_groups AFTER DELETE ON group_members BEGIN DELETE FROM \"groups\" WHERE NOT EXISTS (SELECT * FROM group_members WHERE \"group\" = \"groups\".id);END"
}
},
{

View File

@ -43,8 +43,7 @@ void main() {
final file = await state.analyze('package:a/main.moor');
final fileState = file.currentResult as ParsedMoorFile;
final writer =
Writer(const MoorOptions.defaults(newSqlCodeGeneration: true));
final writer = Writer(const MoorOptions.defaults());
QueryWriter(writer.child()).write(fileState.resolvedQueries!.single);
expect(
@ -71,8 +70,7 @@ void main() {
final file = await state.analyze('package:a/main.moor');
final fileState = file.currentResult as ParsedMoorFile;
final writer =
Writer(const MoorOptions.defaults(newSqlCodeGeneration: true));
final writer = Writer(const MoorOptions.defaults());
QueryWriter(writer.child()).write(fileState.resolvedQueries!.single);
expect(
@ -115,22 +113,9 @@ void main() {
expect(writer.writeGenerated(), expectation);
}
test('with the old query generator', () {
return _runTest(
const MoorOptions.defaults(),
allOf(
contains(r'var $arrayStartIndex = 2;'),
contains(r'SELECT * FROM tbl WHERE a = :a AND b IN ($expandedb) '
'AND c = :c'),
contains(r'variables: [Variable<String?>(a), for (var $ in b) '
r'Variable<String?>($), Variable<String?>(c)]'),
),
);
});
test('with the new query generator', () {
return _runTest(
const MoorOptions.defaults(newSqlCodeGeneration: true),
const MoorOptions.defaults(),
allOf(
contains(r'var $arrayStartIndex = 3;'),
contains(r'SELECT * FROM tbl WHERE a = ?1 AND b IN ($expandedb) '
@ -181,25 +166,9 @@ void main() {
}
}
test('should error with old generator', () async {
final file = await state.analyze('package:a/main.moor');
final fileState = file.currentResult as ParsedMoorFile;
expect(file.errors.errors, isEmpty);
final writer =
Writer(const MoorOptions.defaults(newSqlCodeGeneration: false));
expect(
() => QueryWriter(writer.child())
.write(fileState.resolvedQueries!.single),
throwsA(isA<UnsupportedError>()),
);
});
test('should generate correct queries with variables', () {
return _runTest(
const MoorOptions.defaults(newSqlCodeGeneration: true),
const MoorOptions.defaults(),
[
contains(
r'SELECT parent.a, parent.a AS "\$n_0" FROM tbl AS parent WHERE parent.a = ?1',
@ -219,7 +188,7 @@ void main() {
test('should generate correct data class', () {
return _runTest(
const MoorOptions.defaults(newSqlCodeGeneration: true),
const MoorOptions.defaults(),
[
contains('QueryNestedQuery0({this.b,this.c,})'),
contains('QueryResult({this.a,required this.nestedQuery0,})'),

View File

@ -30,7 +30,6 @@ targets:
# These options are generally recommended: https://drift.simonbinder.eu/docs/advanced-features/builder_options/#recommended-options
apply_converters_on_variables: true
generate_values_in_copy_with: true
new_sql_code_generation: true
scoped_dart_components: true
# Configuring this builder isn't required for most apps. In our case, we

View File

@ -7,8 +7,7 @@ targets:
apply_converters_on_variables: true
generate_values_in_copy_with: true
named_parameters: true
new_sql_code_generation: true
build_web_compilers|entrypoint:
build_web_compilers:entrypoint:
generate_for:
- web/**.dart
options:

View File

@ -4,4 +4,3 @@ targets:
drift_dev:
options:
generate_connect_constructor: true
new_sql_code_generation: true

View File

@ -36,7 +36,6 @@ targets:
apply_converters_on_variables: true
generate_values_in_copy_with: true
named_parameters: true
new_sql_code_generation: true
build_web_compilers:entrypoint:
generate_for:
# This one is compiled in the other target

View File

@ -12,7 +12,6 @@ targets:
apply_converters_on_variables: true
generate_values_in_copy_with: true
named_parameters: false
new_sql_code_generation: true
scoped_dart_components: true
sql:
# As sqlite3 is compatible with the postgres dialect (but not vice-versa), we're