diff --git a/drift/test/generated/custom_tables.g.dart b/drift/test/generated/custom_tables.g.dart index 0ab23137..1a50dffc 100644 --- a/drift/test/generated/custom_tables.g.dart +++ b/drift/test/generated/custom_tables.g.dart @@ -1606,12 +1606,13 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { } Selectable readMultiple(List var1, - {OrderBy Function(ConfigTable config) clause = _$moor$default$0}) { + {ReadMultiple$clause? clause}) { var $arrayStartIndex = 1; final expandedvar1 = $expandVar($arrayStartIndex, var1.length); $arrayStartIndex += var1.length; - final generatedclause = - $write(clause(this.config), startIndex: $arrayStartIndex); + final generatedclause = $write( + clause?.call(this.config) ?? const OrderBy.nothing(), + startIndex: $arrayStartIndex); $arrayStartIndex += generatedclause.amountOfVariables; return customSelect( 'SELECT * FROM config WHERE config_key IN ($expandedvar1) ${generatedclause.sql}', @@ -1625,12 +1626,11 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { }).asyncMap(config.mapFromRow); } - Selectable readDynamic( - {Expression Function(ConfigTable config) predicate = - _$moor$default$1}) { + Selectable readDynamic({ReadDynamic$predicate? predicate}) { var $arrayStartIndex = 1; - final generatedpredicate = - $write(predicate(this.config), startIndex: $arrayStartIndex); + final generatedpredicate = $write( + predicate?.call(this.config) ?? const CustomExpression('(TRUE)'), + startIndex: $arrayStartIndex); $arrayStartIndex += generatedpredicate.amountOfVariables; return customSelect('SELECT * FROM config WHERE ${generatedpredicate.sql}', variables: [ @@ -1643,10 +1643,11 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { } Selectable typeConverterVar(SyncType? var1, List var2, - {Expression Function(ConfigTable config) pred = _$moor$default$2}) { + {TypeConverterVar$pred? pred}) { var $arrayStartIndex = 2; - final generatedpred = - $write(pred(this.config), startIndex: $arrayStartIndex); + final generatedpred = $write( + pred?.call(this.config) ?? const CustomExpression('(TRUE)'), + startIndex: $arrayStartIndex); $arrayStartIndex += generatedpred.amountOfVariables; final expandedvar2 = $expandVar($arrayStartIndex, var2.length); $arrayStartIndex += var2.length; @@ -1694,9 +1695,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { }); } - Selectable multiple( - {required Expression Function(WithDefaults d, WithConstraints c) - predicate}) { + Selectable multiple({required Multiple$predicate predicate}) { var $arrayStartIndex = 1; final generatedpredicate = $write( predicate( @@ -1734,8 +1733,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { }).asyncMap(email.mapFromRow); } - Selectable readRowId( - {required Expression Function(ConfigTable config) expr}) { + Selectable readRowId({required ReadRowId$expr expr}) { var $arrayStartIndex = 1; final generatedexpr = $write(expr(this.config), startIndex: $arrayStartIndex); @@ -1865,11 +1863,9 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { const DriftDatabaseOptions(storeDateTimeAsText: true); } -OrderBy _$moor$default$0(ConfigTable _) => const OrderBy.nothing(); -Expression _$moor$default$1(ConfigTable _) => - const CustomExpression('(TRUE)'); -Expression _$moor$default$2(ConfigTable _) => - const CustomExpression('(TRUE)'); +typedef ReadMultiple$clause = OrderBy Function(ConfigTable config); +typedef ReadDynamic$predicate = Expression Function(ConfigTable config); +typedef TypeConverterVar$pred = Expression Function(ConfigTable config); class JsonResult extends CustomResultSet { final String key; @@ -1927,6 +1923,9 @@ class MultipleResult extends CustomResultSet { } } +typedef Multiple$predicate = Expression Function( + WithDefaults d, WithConstraints c); + class ReadRowIdResult extends CustomResultSet { final int rowid; final String configKey; @@ -1966,6 +1965,8 @@ class ReadRowIdResult extends CustomResultSet { } } +typedef ReadRowId$expr = Expression Function(ConfigTable config); + class NestedResult extends CustomResultSet { final WithDefault defaults; final List nestedQuery0; diff --git a/drift_dev/lib/src/model/sql_query.dart b/drift_dev/lib/src/model/sql_query.dart index d42e2db4..9fc946ef 100644 --- a/drift_dev/lib/src/model/sql_query.dart +++ b/drift_dev/lib/src/model/sql_query.dart @@ -827,6 +827,12 @@ class FoundDartPlaceholder extends FoundElement { type is ExpressionDartPlaceholderType && (type as ExpressionDartPlaceholderType).defaultValue != null; + bool get hasDefaultOrImplicitFallback => + hasDefault || + (type is SimpleDartPlaceholderType && + (type as SimpleDartPlaceholderType).kind == + SimpleDartPlaceholderKind.orderBy); + FoundDartPlaceholder(this.type, this.name, this.availableResultSets); @override diff --git a/drift_dev/lib/src/writer/queries/query_writer.dart b/drift_dev/lib/src/writer/queries/query_writer.dart index 786f1a86..3cad971a 100644 --- a/drift_dev/lib/src/writer/queries/query_writer.dart +++ b/drift_dev/lib/src/writer/queries/query_writer.dart @@ -4,6 +4,7 @@ import 'package:drift_dev/src/analyzer/sql_queries/explicit_alias_transformer.da import 'package:drift_dev/src/analyzer/sql_queries/nested_queries.dart'; import 'package:drift_dev/src/utils/string_escaper.dart'; import 'package:drift_dev/writer.dart'; +import 'package:recase/recase.dart'; import 'package:sqlparser/sqlparser.dart' hide ResultColumn; import 'sql_writer.dart'; @@ -254,20 +255,25 @@ class QueryWriter { void _writeParameters(SqlQuery query) { final namedElements = []; + String scopedTypeName(FoundDartPlaceholder element) { + return '${ReCase(query.name).pascalCase}\$${ReCase(element.name).camelCase}'; + } + String typeFor(FoundElement element) { - var type = element.dartTypeCode(); + return element.dartTypeCode(); + } - if (element is FoundDartPlaceholder && - element.writeAsScopedFunction(options)) { - // Generate a function providing result sets that are in scope as args + String writeScopedTypeFor(FoundDartPlaceholder element) { + final root = scope.root; + final type = typeFor(element); + final scopedType = scopedTypeName(element); - final args = element.availableResultSets - .map((e) => '${e.argumentType} ${e.name}') - .join(', '); - type = '$type Function($args)'; - } + final args = element.availableResultSets + .map((e) => '${e.argumentType} ${e.name}') + .join(', '); + root.leaf().write('typedef $scopedType = $type Function($args);'); - return type; + return scopedType; } var needsComma = false; @@ -285,7 +291,11 @@ class QueryWriter { } else { if (needsComma) _buffer.write(', '); - final type = typeFor(element); + var type = typeFor(element); + if (element is FoundDartPlaceholder && + element.writeAsScopedFunction(options)) { + type = writeScopedTypeFor(element); + } _buffer.write('$type ${element.dartParameterName}'); needsComma = true; } @@ -302,62 +312,27 @@ class QueryWriter { needsComma = true; String? defaultCode; + var isNullable = false; + var type = typeFor(optional); if (optional is FoundDartPlaceholder) { - final kind = optional.type; - if (kind is ExpressionDartPlaceholderType && - kind.defaultValue != null) { - // Wrap the default expression in parentheses to avoid issues with - // the surrounding precedence in SQL. - final sql = SqlWriter(scope.options) - .writeNodeIntoStringLiteral(Parentheses(kind.defaultValue!)); - defaultCode = 'const CustomExpression($sql)'; - } else if (kind is SimpleDartPlaceholderType && - kind.kind == SimpleDartPlaceholderKind.orderBy) { - defaultCode = 'const OrderBy.nothing()'; - } + // If optional Dart placeholders are written as functions, they are + // generated as nullable parameters. The default is handled with a + // `??` in the method's body. + if (optional.writeAsScopedFunction(options)) { + isNullable = optional.hasDefaultOrImplicitFallback; + type = writeScopedTypeFor(optional); - // If the parameter is converted to a scoped function, we also need to - // generate a scoped function as a default value. Since defaults have - // to be constants, we generate a top-level function which is then - // used as a tear-off. - if (optional.writeAsScopedFunction(options) && defaultCode != null) { - final root = scope.root; - final counter = root.counter++; - // ignore: prefer_interpolation_to_compose_strings - final functionName = r'_$moor$default$' + counter.toString(); - - final buffer = root.leaf() - ..write(optional.dartTypeCode(scope.generationOptions)) - ..write(' ') - ..write(functionName) - ..write('('); - var i = 0; - for (final arg in optional.availableResultSets) { - if (i != 0) buffer.write(', '); - - buffer - ..write(arg.argumentType) - ..write(' ') - ..write('_' * (i + 1)); - i++; + if (isNullable) { + type += '?'; } - buffer - ..write(') => ') - ..write(defaultCode) - ..write(';'); - - // With the function being written, the default code is just a tear- - // off of that function - defaultCode = functionName; + } else { + defaultCode = _defaultForDartPlaceholder(optional, scope); } } - final type = typeFor(optional); - // No default value, this element is required if it's not nullable var isMarkedAsRequired = false; - var isNullable = false; if (optional is FoundVariable) { isMarkedAsRequired = optional.isRequired; isNullable = optional.nullableInDart; @@ -382,7 +357,7 @@ class QueryWriter { } void _writeExpandedDeclarations(SqlQuery query) { - _ExpandedDeclarationWriter(query, options, _buffer) + _ExpandedDeclarationWriter(query, options, scope, _buffer) .writeExpandedDeclarations(); } @@ -441,13 +416,15 @@ String _converter(UsedTypeConverter converter) { class _ExpandedDeclarationWriter { final SqlQuery query; final DriftOptions options; + final Scope _scope; final StringBuffer _buffer; bool indexCounterWasDeclared = false; bool needsIndexCounter = false; int highestIndexBeforeArray = 0; - _ExpandedDeclarationWriter(this.query, this.options, this._buffer); + _ExpandedDeclarationWriter( + this.query, this.options, this._scope, this._buffer); void writeExpandedDeclarations() { // When the SQL query is written to a Dart string, we give each variable an @@ -532,7 +509,17 @@ class _ExpandedDeclarationWriter { return table; } }).join(', '); - return '${element.dartParameterName}($args)'; + + final defaultValue = _defaultForDartPlaceholder(element, _scope); + + if (defaultValue != null) { + // Optional elements written as a function are generated as nullable + // parameters. We need to emit the default if the actual value is + // null at runtime. + return '${element.dartParameterName}?.call($args) ?? $defaultValue'; + } else { + return '${element.dartParameterName}($args)'; + } } else { // We can just use the parameter directly return element.dartParameterName; @@ -694,3 +681,21 @@ class _ExpandedVariableWriter { _buffer.write('...${placeholderContextName(element)}.introducedVariables'); } } + +String? _defaultForDartPlaceholder( + FoundDartPlaceholder placeholder, Scope scope) { + final kind = placeholder.type; + if (kind is ExpressionDartPlaceholderType && kind.defaultValue != null) { + // Wrap the default expression in parentheses to avoid issues with + // the surrounding precedence in SQL. + final sql = SqlWriter(scope.options) + .writeNodeIntoStringLiteral(Parentheses(kind.defaultValue!)); + return 'const CustomExpression($sql)'; + } else if (kind is SimpleDartPlaceholderType && + kind.kind == SimpleDartPlaceholderKind.orderBy) { + return 'const OrderBy.nothing()'; + } else { + assert(!placeholder.hasDefaultOrImplicitFallback); + return null; + } +} diff --git a/extras/integration_tests/drift_testcases/lib/database/database.g.dart b/extras/integration_tests/drift_testcases/lib/database/database.g.dart index 62d1477b..55e4b800 100644 --- a/extras/integration_tests/drift_testcases/lib/database/database.g.dart +++ b/extras/integration_tests/drift_testcases/lib/database/database.g.dart @@ -2,19 +2,6 @@ part of 'database.dart'; -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Preferences _$PreferencesFromJson(Map json) => Preferences( - json['receiveEmails'] as bool, - ); - -Map _$PreferencesToJson(Preferences instance) => - { - 'receiveEmails': instance.receiveEmails, - }; - // ************************************************************************** // DriftDatabaseGenerator // ************************************************************************** @@ -665,3 +652,16 @@ class FriendshipsOfResult { .toString(); } } + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Preferences _$PreferencesFromJson(Map json) => Preferences( + json['receiveEmails'] as bool, + ); + +Map _$PreferencesToJson(Preferences instance) => + { + 'receiveEmails': instance.receiveEmails, + };