From cd2cf1f3b3c35fe1bc5852d775820c50504a0805 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 7 Feb 2023 22:27:53 +0100 Subject: [PATCH] Fix blob columns with modular code generation (#2306) --- drift_dev/lib/src/analysis/results/types.dart | 3 +- .../src/writer/queries/result_set_writer.dart | 4 +- .../src/writer/tables/data_class_writer.dart | 4 +- .../lib/src/writer/utils/hash_and_equals.dart | 16 ++- .../test/writer/utils/hash_code_test.dart | 40 +++++--- .../writer/utils/override_equals_test.dart | 31 ++++-- examples/modular/lib/src/users.drift | 3 +- examples/modular/lib/src/users.drift.dart | 98 ++++++++++++++++--- 8 files changed, 151 insertions(+), 48 deletions(-) diff --git a/drift_dev/lib/src/analysis/results/types.dart b/drift_dev/lib/src/analysis/results/types.dart index 578235cd..41eff1d6 100644 --- a/drift_dev/lib/src/analysis/results/types.dart +++ b/drift_dev/lib/src/analysis/results/types.dart @@ -47,7 +47,8 @@ Map dartTypeNames = Map.unmodifiable({ DriftSqlType.int: DartTopLevelSymbol('int', Uri.parse('dart:core')), DriftSqlType.bigInt: DartTopLevelSymbol('BigInt', Uri.parse('dart:core')), DriftSqlType.dateTime: DartTopLevelSymbol('DateTime', Uri.parse('dart:core')), - DriftSqlType.blob: DartTopLevelSymbol('Uint8List', Uri.parse('dart:convert')), + DriftSqlType.blob: + DartTopLevelSymbol('Uint8List', Uri.parse('dart:typed_data')), DriftSqlType.double: DartTopLevelSymbol('double', Uri.parse('dart:core')), DriftSqlType.any: DartTopLevelSymbol('DriftAny', AnnotatedDartCode.drift), }); diff --git a/drift_dev/lib/src/writer/queries/result_set_writer.dart b/drift_dev/lib/src/writer/queries/result_set_writer.dart index 82e33748..12da4752 100644 --- a/drift_dev/lib/src/writer/queries/result_set_writer.dart +++ b/drift_dev/lib/src/writer/queries/result_set_writer.dart @@ -87,10 +87,10 @@ class ResultSetWriter { // if requested, override hashCode and equals if (scope.writer.options.overrideHashAndEqualsInResultSets) { into.write('@override int get hashCode => '); - writeHashCode(fields, into.buffer); + writeHashCode(fields, into); into.write(';\n'); - overrideEquals(fields, className, into.buffer); + overrideEquals(fields, className, into); overrideToString( className, fields.map((f) => f.lexeme).toList(), into.buffer); } diff --git a/drift_dev/lib/src/writer/tables/data_class_writer.dart b/drift_dev/lib/src/writer/tables/data_class_writer.dart index 3bfb9c98..a0857448 100644 --- a/drift_dev/lib/src/writer/tables/data_class_writer.dart +++ b/drift_dev/lib/src/writer/tables/data_class_writer.dart @@ -122,7 +122,7 @@ class DataClassWriter { columns .map((c) => EqualityField(c.nameInDart, isList: c.isUint8ListInDart)), _emitter.dartCode(_emitter.writer.rowClass(table)), - _buffer, + _emitter, ); // finish class declaration @@ -306,7 +306,7 @@ class DataClassWriter { final fields = columns .map((c) => EqualityField(c.nameInDart, isList: c.isUint8ListInDart)) .toList(); - writeHashCode(fields, _buffer); + writeHashCode(fields, _emitter); _buffer.write(';'); } } diff --git a/drift_dev/lib/src/writer/utils/hash_and_equals.dart b/drift_dev/lib/src/writer/utils/hash_and_equals.dart index 855ecc96..ba53c7f1 100644 --- a/drift_dev/lib/src/writer/utils/hash_and_equals.dart +++ b/drift_dev/lib/src/writer/utils/hash_and_equals.dart @@ -1,3 +1,5 @@ +import '../writer.dart'; + const int _maxArgsToObjectHash = 20; class EqualityField { @@ -13,14 +15,16 @@ class EqualityField { /// Writes an expression to calculate a hash code of an object that consists /// of the [fields]. -void writeHashCode(List fields, StringBuffer into) { +void writeHashCode(List fields, TextEmitter into) { + late final equality = into.drift(r'$driftBlobEquality'); + if (fields.isEmpty) { into.write('identityHashCode(this)'); } else if (fields.length == 1) { final field = fields[0]; if (field.isList) { - into.write('\$driftBlobEquality.hash(${field.lexeme})'); + into.write('$equality.hash(${field.lexeme})'); } else { into.write('${field.lexeme}.hashCode'); } @@ -33,7 +37,7 @@ void writeHashCode(List fields, StringBuffer into) { if (!first) into.write(', '); if (field.isList) { - into.write('\$driftBlobEquality.hash(${field.lexeme})'); + into.write('$equality.hash(${field.lexeme})'); } else { into.write(field.lexeme); } @@ -48,7 +52,7 @@ void writeHashCode(List fields, StringBuffer into) { /// Writes a operator == override for a class consisting of the [fields] into /// the buffer provided by [into]. void overrideEquals( - Iterable fields, String className, StringBuffer into) { + Iterable fields, String className, TextEmitter into) { into ..writeln('@override') ..write('bool operator ==(Object other) => ') @@ -61,7 +65,9 @@ void overrideEquals( final lexeme = field.lexeme; if (field.isList) { - return '\$driftBlobEquality.equals(other.$lexeme, this.$lexeme)'; + final equality = into.drift(r'$driftBlobEquality'); + + return '$equality.equals(other.$lexeme, this.$lexeme)'; } else { return 'other.$lexeme == this.$lexeme'; } diff --git a/drift_dev/test/writer/utils/hash_code_test.dart b/drift_dev/test/writer/utils/hash_code_test.dart index 733a7289..9ce1b0ec 100644 --- a/drift_dev/test/writer/utils/hash_code_test.dart +++ b/drift_dev/test/writer/utils/hash_code_test.dart @@ -1,44 +1,54 @@ import 'package:charcode/ascii.dart'; +import 'package:drift_dev/src/analysis/options.dart'; +import 'package:drift_dev/src/writer/import_manager.dart'; import 'package:drift_dev/src/writer/utils/hash_and_equals.dart'; +import 'package:drift_dev/src/writer/writer.dart'; import 'package:test/test.dart'; void main() { + late Writer writer; + + setUp(() { + final imports = LibraryInputManager(Uri.parse('drift:test')); + final generationOptions = + GenerationOptions(imports: imports, isModular: true); + writer = Writer(const DriftOptions.defaults(), + generationOptions: generationOptions); + imports.linkToWriter(writer); + }); + test('hash code for no fields', () { - final buffer = StringBuffer(); - writeHashCode([], buffer); - expect(buffer.toString(), r'identityHashCode(this)'); + writeHashCode([], writer.leaf()); + expect(writer.writeGenerated(), r'identityHashCode(this)'); }); test('hash code for a single field - not a list', () { - final buffer = StringBuffer(); - writeHashCode([EqualityField('a')], buffer); - expect(buffer.toString(), r'a.hashCode'); + writeHashCode([EqualityField('a')], writer.leaf()); + expect(writer.writeGenerated(), r'a.hashCode'); }); test('hash code for a single field - list', () { - final buffer = StringBuffer(); - writeHashCode([EqualityField('a', isList: true)], buffer); - expect(buffer.toString(), r'$driftBlobEquality.hash(a)'); + writeHashCode([EqualityField('a', isList: true)], writer.leaf()); + expect(writer.writeGenerated(), contains(r'i0.$driftBlobEquality.hash(a)')); }); test('hash code for multiple fields', () { - final buffer = StringBuffer(); writeHashCode([ EqualityField('a'), EqualityField('b', isList: true), EqualityField('c'), - ], buffer); - expect(buffer.toString(), r'Object.hash(a, $driftBlobEquality.hash(b), c)'); + ], writer.leaf()); + expect(writer.writeGenerated(), + contains(r'Object.hash(a, i0.$driftBlobEquality.hash(b), c)')); }); test('hash code for lots of fields', () { - final buffer = StringBuffer(); writeHashCode( List.generate( 26, (index) => EqualityField(String.fromCharCode($a + index))), - buffer); + writer.leaf()); expect( - buffer.toString(), + writer.writeGenerated(), r'Object.hashAll([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, ' 's, t, u, v, w, x, y, z])', ); diff --git a/drift_dev/test/writer/utils/override_equals_test.dart b/drift_dev/test/writer/utils/override_equals_test.dart index 34ea94c1..4be20fa6 100644 --- a/drift_dev/test/writer/utils/override_equals_test.dart +++ b/drift_dev/test/writer/utils/override_equals_test.dart @@ -1,30 +1,45 @@ +import 'package:drift_dev/src/analysis/options.dart'; +import 'package:drift_dev/src/writer/import_manager.dart'; import 'package:drift_dev/src/writer/utils/hash_and_equals.dart'; +import 'package:drift_dev/src/writer/writer.dart'; import 'package:test/test.dart'; void main() { + late Writer writer; + + setUp(() { + final imports = LibraryInputManager(Uri.parse('drift:test')); + final generationOptions = + GenerationOptions(imports: imports, isModular: true); + writer = Writer(const DriftOptions.defaults(), + generationOptions: generationOptions); + imports.linkToWriter(writer); + }); + test('overrides equals on class without fields', () { - final buffer = StringBuffer(); - overrideEquals([], 'Foo', buffer); + overrideEquals([], 'Foo', writer.leaf()); expect( - buffer.toString(), + writer.writeGenerated(), '@override\nbool operator ==(Object other) => ' 'identical(this, other) || (other is Foo);\n'); }); test('overrides equals on class with fields', () { - final buffer = StringBuffer(); overrideEquals([ EqualityField('a'), EqualityField('b', isList: true), EqualityField('c'), - ], 'Foo', buffer); + ], 'Foo', writer.leaf()); expect( - buffer.toString(), + writer.writeGenerated(), + contains( '@override\nbool operator ==(Object other) => ' 'identical(this, other) || (other is Foo && ' - r'other.a == this.a && $driftBlobEquality.equals(other.b, this.b) && ' - 'other.c == this.c);\n'); + r'other.a == this.a && i0.$driftBlobEquality.equals(other.b, this.b) && ' + 'other.c == this.c);\n', + ), + ); }); } diff --git a/examples/modular/lib/src/users.drift b/examples/modular/lib/src/users.drift index 5acb96fd..a9ce57a5 100644 --- a/examples/modular/lib/src/users.drift +++ b/examples/modular/lib/src/users.drift @@ -4,7 +4,8 @@ CREATE TABLE users ( id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL, biography TEXT, - preferences TEXT MAPPED BY `const PreferencesConverter()` + preferences TEXT MAPPED BY `const PreferencesConverter()`, + profile_picture BLOB ); CREATE INDEX users_name ON users (name); diff --git a/examples/modular/lib/src/users.drift.dart b/examples/modular/lib/src/users.drift.dart index 8ffe4639..8cdc4b7c 100644 --- a/examples/modular/lib/src/users.drift.dart +++ b/examples/modular/lib/src/users.drift.dart @@ -2,6 +2,7 @@ import 'package:drift/drift.dart' as i0; import 'package:modular/src/users.drift.dart' as i1; import 'package:modular/src/preferences.dart' as i2; +import 'dart:typed_data' as i3; class Users extends i0.Table with i0.TableInfo { @override @@ -36,8 +37,16 @@ class Users extends i0.Table with i0.TableInfo { requiredDuringInsert: false, $customConstraints: '') .withConverter(i1.Users.$converterpreferencesn); + static const i0.VerificationMeta _profilePictureMeta = + const i0.VerificationMeta('profilePicture'); + late final i0.GeneratedColumn profilePicture = + i0.GeneratedColumn('profile_picture', aliasedName, true, + type: i0.DriftSqlType.blob, + requiredDuringInsert: false, + $customConstraints: ''); @override - List get $columns => [id, name, biography, preferences]; + List get $columns => + [id, name, biography, preferences, profilePicture]; @override String get aliasedName => _alias ?? 'users'; @override @@ -61,6 +70,12 @@ class Users extends i0.Table with i0.TableInfo { biography.isAcceptableOrUnknown(data['biography']!, _biographyMeta)); } context.handle(_preferencesMeta, const i0.VerificationResult.success()); + if (data.containsKey('profile_picture')) { + context.handle( + _profilePictureMeta, + profilePicture.isAcceptableOrUnknown( + data['profile_picture']!, _profilePictureMeta)); + } return context; } @@ -79,6 +94,8 @@ class Users extends i0.Table with i0.TableInfo { preferences: i1.Users.$converterpreferencesn.fromSql(attachedDatabase .typeMapping .read(i0.DriftSqlType.string, data['${effectivePrefix}preferences'])), + profilePicture: attachedDatabase.typeMapping.read( + i0.DriftSqlType.blob, data['${effectivePrefix}profile_picture']), ); } @@ -101,8 +118,13 @@ class User extends i0.DataClass implements i0.Insertable { final String name; final String? biography; final i2.Preferences? preferences; + final i3.Uint8List? profilePicture; const User( - {required this.id, required this.name, this.biography, this.preferences}); + {required this.id, + required this.name, + this.biography, + this.preferences, + this.profilePicture}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -115,6 +137,9 @@ class User extends i0.DataClass implements i0.Insertable { final converter = i1.Users.$converterpreferencesn; map['preferences'] = i0.Variable(converter.toSql(preferences)); } + if (!nullToAbsent || profilePicture != null) { + map['profile_picture'] = i0.Variable(profilePicture); + } return map; } @@ -128,6 +153,9 @@ class User extends i0.DataClass implements i0.Insertable { preferences: preferences == null && nullToAbsent ? const i0.Value.absent() : i0.Value(preferences), + profilePicture: profilePicture == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(profilePicture), ); } @@ -140,6 +168,8 @@ class User extends i0.DataClass implements i0.Insertable { biography: serializer.fromJson(json['biography']), preferences: i1.Users.$converterpreferencesn.fromJson( serializer.fromJson?>(json['preferences'])), + profilePicture: + serializer.fromJson(json['profile_picture']), ); } @override @@ -151,6 +181,7 @@ class User extends i0.DataClass implements i0.Insertable { 'biography': serializer.toJson(biography), 'preferences': serializer.toJson?>( i1.Users.$converterpreferencesn.toJson(preferences)), + 'profile_picture': serializer.toJson(profilePicture), }; } @@ -158,12 +189,15 @@ class User extends i0.DataClass implements i0.Insertable { {int? id, String? name, i0.Value biography = const i0.Value.absent(), - i0.Value preferences = const i0.Value.absent()}) => + i0.Value preferences = const i0.Value.absent(), + i0.Value profilePicture = const i0.Value.absent()}) => i1.User( id: id ?? this.id, name: name ?? this.name, biography: biography.present ? biography.value : this.biography, preferences: preferences.present ? preferences.value : this.preferences, + profilePicture: + profilePicture.present ? profilePicture.value : this.profilePicture, ); @override String toString() { @@ -171,13 +205,15 @@ class User extends i0.DataClass implements i0.Insertable { ..write('id: $id, ') ..write('name: $name, ') ..write('biography: $biography, ') - ..write('preferences: $preferences') + ..write('preferences: $preferences, ') + ..write('profilePicture: $profilePicture') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name, biography, preferences); + int get hashCode => Object.hash(id, name, biography, preferences, + i0.$driftBlobEquality.hash(profilePicture)); @override bool operator ==(Object other) => identical(this, other) || @@ -185,7 +221,9 @@ class User extends i0.DataClass implements i0.Insertable { other.id == this.id && other.name == this.name && other.biography == this.biography && - other.preferences == this.preferences); + other.preferences == this.preferences && + i0.$driftBlobEquality + .equals(other.profilePicture, this.profilePicture)); } class UsersCompanion extends i0.UpdateCompanion { @@ -193,29 +231,34 @@ class UsersCompanion extends i0.UpdateCompanion { final i0.Value name; final i0.Value biography; final i0.Value preferences; + final i0.Value profilePicture; const UsersCompanion({ this.id = const i0.Value.absent(), this.name = const i0.Value.absent(), this.biography = const i0.Value.absent(), this.preferences = const i0.Value.absent(), + this.profilePicture = const i0.Value.absent(), }); UsersCompanion.insert({ this.id = const i0.Value.absent(), required String name, this.biography = const i0.Value.absent(), this.preferences = const i0.Value.absent(), + this.profilePicture = const i0.Value.absent(), }) : name = i0.Value(name); static i0.Insertable custom({ i0.Expression? id, i0.Expression? name, i0.Expression? biography, i0.Expression? preferences, + i0.Expression? profilePicture, }) { return i0.RawValuesInsertable({ if (id != null) 'id': id, if (name != null) 'name': name, if (biography != null) 'biography': biography, if (preferences != null) 'preferences': preferences, + if (profilePicture != null) 'profile_picture': profilePicture, }); } @@ -223,12 +266,14 @@ class UsersCompanion extends i0.UpdateCompanion { {i0.Value? id, i0.Value? name, i0.Value? biography, - i0.Value? preferences}) { + i0.Value? preferences, + i0.Value? profilePicture}) { return i1.UsersCompanion( id: id ?? this.id, name: name ?? this.name, biography: biography ?? this.biography, preferences: preferences ?? this.preferences, + profilePicture: profilePicture ?? this.profilePicture, ); } @@ -249,6 +294,9 @@ class UsersCompanion extends i0.UpdateCompanion { map['preferences'] = i0.Variable(converter.toSql(preferences.value)); } + if (profilePicture.present) { + map['profile_picture'] = i0.Variable(profilePicture.value); + } return map; } @@ -258,7 +306,8 @@ class UsersCompanion extends i0.UpdateCompanion { ..write('id: $id, ') ..write('name: $name, ') ..write('biography: $biography, ') - ..write('preferences: $preferences') + ..write('preferences: $preferences, ') + ..write('profilePicture: $profilePicture') ..write(')')) .toString(); } @@ -453,8 +502,13 @@ class PopularUser extends i0.DataClass { final String name; final String? biography; final i2.Preferences? preferences; + final i3.Uint8List? profilePicture; const PopularUser( - {required this.id, required this.name, this.biography, this.preferences}); + {required this.id, + required this.name, + this.biography, + this.preferences, + this.profilePicture}); factory PopularUser.fromJson(Map json, {i0.ValueSerializer? serializer}) { serializer ??= i0.driftRuntimeOptions.defaultSerializer; @@ -464,6 +518,8 @@ class PopularUser extends i0.DataClass { biography: serializer.fromJson(json['biography']), preferences: i1.Users.$converterpreferencesn.fromJson( serializer.fromJson?>(json['preferences'])), + profilePicture: + serializer.fromJson(json['profile_picture']), ); } @override @@ -475,6 +531,7 @@ class PopularUser extends i0.DataClass { 'biography': serializer.toJson(biography), 'preferences': serializer.toJson?>( i1.Users.$converterpreferencesn.toJson(preferences)), + 'profile_picture': serializer.toJson(profilePicture), }; } @@ -482,12 +539,15 @@ class PopularUser extends i0.DataClass { {int? id, String? name, i0.Value biography = const i0.Value.absent(), - i0.Value preferences = const i0.Value.absent()}) => + i0.Value preferences = const i0.Value.absent(), + i0.Value profilePicture = const i0.Value.absent()}) => i1.PopularUser( id: id ?? this.id, name: name ?? this.name, biography: biography.present ? biography.value : this.biography, preferences: preferences.present ? preferences.value : this.preferences, + profilePicture: + profilePicture.present ? profilePicture.value : this.profilePicture, ); @override String toString() { @@ -495,13 +555,15 @@ class PopularUser extends i0.DataClass { ..write('id: $id, ') ..write('name: $name, ') ..write('biography: $biography, ') - ..write('preferences: $preferences') + ..write('preferences: $preferences, ') + ..write('profilePicture: $profilePicture') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name, biography, preferences); + int get hashCode => Object.hash(id, name, biography, preferences, + i0.$driftBlobEquality.hash(profilePicture)); @override bool operator ==(Object other) => identical(this, other) || @@ -509,7 +571,9 @@ class PopularUser extends i0.DataClass { other.id == this.id && other.name == this.name && other.biography == this.biography && - other.preferences == this.preferences); + other.preferences == this.preferences && + i0.$driftBlobEquality + .equals(other.profilePicture, this.profilePicture)); } class PopularUsers extends i0.ViewInfo @@ -519,7 +583,8 @@ class PopularUsers extends i0.ViewInfo final i0.GeneratedDatabase attachedDatabase; PopularUsers(this.attachedDatabase, [this._alias]); @override - List get $columns => [id, name, biography, preferences]; + List get $columns => + [id, name, biography, preferences, profilePicture]; @override String get aliasedName => _alias ?? entityName; @override @@ -542,6 +607,8 @@ class PopularUsers extends i0.ViewInfo preferences: i1.Users.$converterpreferencesn.fromSql(attachedDatabase .typeMapping .read(i0.DriftSqlType.string, data['${effectivePrefix}preferences'])), + profilePicture: attachedDatabase.typeMapping.read( + i0.DriftSqlType.blob, data['${effectivePrefix}profile_picture']), ); } @@ -558,6 +625,9 @@ class PopularUsers extends i0.ViewInfo preferences = i0.GeneratedColumn('preferences', aliasedName, true, type: i0.DriftSqlType.string) .withConverter(i1.Users.$converterpreferencesn); + late final i0.GeneratedColumn profilePicture = + i0.GeneratedColumn('profile_picture', aliasedName, true, + type: i0.DriftSqlType.blob); @override PopularUsers createAlias(String alias) { return PopularUsers(attachedDatabase, alias);