mirror of https://github.com/AMT-Cheif/drift.git
Handle equality and hashes for blobs
This commit is contained in:
parent
04c3dbf1b5
commit
009056dc37
|
@ -1,5 +1,7 @@
|
|||
library drift;
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
// needed for the generated code that generates data classes with an Uint8List
|
||||
// field.
|
||||
export 'dart:typed_data' show Uint8List;
|
||||
|
@ -17,3 +19,7 @@ export 'src/runtime/query_builder/query_builder.dart';
|
|||
export 'src/runtime/types/converters.dart';
|
||||
export 'src/runtime/types/mapping.dart';
|
||||
export 'src/utils/lazy_database.dart';
|
||||
|
||||
/// A [ListEquality] instance used by generated drift code for the `==` and
|
||||
/// [Object.hashCode] implementation of generated classes if they contain lists.
|
||||
const ListEquality $driftBlobEquality = ListEquality();
|
||||
|
|
|
@ -650,8 +650,8 @@ class User extends DataClass implements Insertable<User> {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(id, name, isAwesome, profilePicture, creationTime);
|
||||
int get hashCode => Object.hash(id, name, isAwesome,
|
||||
$driftBlobEquality.hash(profilePicture), creationTime);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
|
@ -659,7 +659,8 @@ class User extends DataClass implements Insertable<User> {
|
|||
other.id == this.id &&
|
||||
other.name == this.name &&
|
||||
other.isAwesome == this.isAwesome &&
|
||||
other.profilePicture == this.profilePicture &&
|
||||
$driftBlobEquality.equals(
|
||||
other.profilePicture, this.profilePicture) &&
|
||||
other.creationTime == this.creationTime);
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,9 @@ class DriftDartType {
|
|||
}
|
||||
|
||||
extension OperationOnTypes on HasType {
|
||||
bool get isUint8ListInDart =>
|
||||
type == DriftSqlType.blob && typeConverter == null;
|
||||
|
||||
/// Whether this type is nullable in Dart
|
||||
bool get nullableInDart {
|
||||
if (isArray) return false; // Is a List<Something> in Dart, not nullable
|
||||
|
|
|
@ -12,7 +12,7 @@ class ResultSetWriter {
|
|||
|
||||
void write() {
|
||||
final className = query.resultClassName;
|
||||
final fieldNames = <String>[];
|
||||
final fields = <EqualityField>[];
|
||||
final nonNullableFields = <String>{};
|
||||
final into = scope.leaf();
|
||||
|
||||
|
@ -34,7 +34,7 @@ class ResultSetWriter {
|
|||
|
||||
into.write('$modifier $runtimeType $name\n;');
|
||||
|
||||
fieldNames.add(name);
|
||||
fields.add(EqualityField(name, isList: column.isUint8ListInDart));
|
||||
if (!column.nullable) nonNullableFields.add(name);
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ class ResultSetWriter {
|
|||
|
||||
into.write('$modifier $typeName $fieldName;\n');
|
||||
|
||||
fieldNames.add(fieldName);
|
||||
fields.add(EqualityField(fieldName));
|
||||
if (!nested.isNullable) nonNullableFields.add(fieldName);
|
||||
} else if (nested is NestedResultQuery) {
|
||||
final fieldName = nested.filedName();
|
||||
|
@ -61,7 +61,7 @@ class ResultSetWriter {
|
|||
|
||||
into.write('$modifier List<$typeName> $fieldName;\n');
|
||||
|
||||
fieldNames.add(fieldName);
|
||||
fields.add(EqualityField(fieldName));
|
||||
nonNullableFields.add(fieldName);
|
||||
}
|
||||
}
|
||||
|
@ -73,11 +73,11 @@ class ResultSetWriter {
|
|||
into.write('$className({');
|
||||
}
|
||||
|
||||
for (final column in fieldNames) {
|
||||
if (nonNullableFields.contains(column)) {
|
||||
for (final column in fields) {
|
||||
if (nonNullableFields.contains(column.lexeme)) {
|
||||
into.write('required ');
|
||||
}
|
||||
into.write('this.$column,');
|
||||
into.write('this.${column.lexeme},');
|
||||
}
|
||||
|
||||
if (scope.options.rawResultSetData) {
|
||||
|
@ -89,11 +89,11 @@ class ResultSetWriter {
|
|||
// if requested, override hashCode and equals
|
||||
if (scope.writer.options.overrideHashAndEqualsInResultSets) {
|
||||
into.write('@override int get hashCode => ');
|
||||
const HashCodeWriter().writeHashCode(fieldNames, into);
|
||||
writeHashCode(fields, into);
|
||||
into.write(';\n');
|
||||
|
||||
overrideEquals(fieldNames, className, into);
|
||||
overrideToString(className, fieldNames, into);
|
||||
overrideEquals(fields, className, into);
|
||||
overrideToString(className, fields.map((f) => f.lexeme).toList(), into);
|
||||
}
|
||||
|
||||
into.write('}\n');
|
||||
|
|
|
@ -87,7 +87,11 @@ class DataClassWriter {
|
|||
_writeHashCode();
|
||||
|
||||
overrideEquals(
|
||||
columns.map((c) => c.dartGetterName), table.dartTypeCode(), _buffer);
|
||||
columns.map(
|
||||
(c) => EqualityField(c.dartGetterName, isList: c.isUint8ListInDart)),
|
||||
table.dartTypeCode(),
|
||||
_buffer,
|
||||
);
|
||||
|
||||
// finish class declaration
|
||||
_buffer.write('}');
|
||||
|
@ -312,8 +316,11 @@ class DataClassWriter {
|
|||
void _writeHashCode() {
|
||||
_buffer.write('@override\n int get hashCode => ');
|
||||
|
||||
final fields = columns.map((c) => c.dartGetterName).toList();
|
||||
const HashCodeWriter().writeHashCode(fields, _buffer);
|
||||
final fields = columns
|
||||
.map(
|
||||
(c) => EqualityField(c.dartGetterName, isList: c.isUint8ListInDart))
|
||||
.toList();
|
||||
writeHashCode(fields, _buffer);
|
||||
_buffer.write(';');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
const int _maxArgsToObjectHash = 20;
|
||||
|
||||
class EqualityField {
|
||||
/// The Dart expression evaluating the field to include in the hash / equals
|
||||
/// check.
|
||||
final String lexeme;
|
||||
|
||||
/// Whether the field is a list that can't be compared with `==` directly.
|
||||
final bool isList;
|
||||
|
||||
EqualityField(this.lexeme, {this.isList = false});
|
||||
}
|
||||
|
||||
/// Writes an expression to calculate a hash code of an object that consists
|
||||
/// of the [fields].
|
||||
void writeHashCode(List<EqualityField> fields, StringBuffer into) {
|
||||
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})');
|
||||
} else {
|
||||
into.write('${field.lexeme}.hashCode');
|
||||
}
|
||||
} else {
|
||||
final needsHashAll = fields.length > _maxArgsToObjectHash;
|
||||
|
||||
into.write(needsHashAll ? 'Object.hashAll([' : 'Object.hash(');
|
||||
var first = true;
|
||||
for (final field in fields) {
|
||||
if (!first) into.write(', ');
|
||||
|
||||
if (field.isList) {
|
||||
into.write('\$driftBlobEquality.hash(${field.lexeme})');
|
||||
} else {
|
||||
into.write(field.lexeme);
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
into.write(needsHashAll ? '])' : ')');
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a operator == override for a class consisting of the [fields] into
|
||||
/// the buffer provided by [into].
|
||||
void overrideEquals(
|
||||
Iterable<EqualityField> fields, String className, StringBuffer into) {
|
||||
into
|
||||
..writeln('@override')
|
||||
..write('bool operator ==(Object other) => ')
|
||||
..write('identical(this, other) || (other is $className');
|
||||
|
||||
if (fields.isNotEmpty) {
|
||||
into
|
||||
..write(' && ')
|
||||
..write(fields.map((field) {
|
||||
final lexeme = field.lexeme;
|
||||
|
||||
if (field.isList) {
|
||||
return '\$driftBlobEquality.equals(other.$lexeme, this.$lexeme)';
|
||||
} else {
|
||||
return 'other.$lexeme == this.$lexeme';
|
||||
}
|
||||
}).join(' && '));
|
||||
}
|
||||
|
||||
into.writeln(');');
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
class HashCodeWriter {
|
||||
static const int _maxArgsToObjectHash = 20;
|
||||
|
||||
const HashCodeWriter();
|
||||
|
||||
/// Writes an expression to calculate a hash code of an object that consists
|
||||
/// of the [fields].
|
||||
void writeHashCode(List<String> fields, StringBuffer into) {
|
||||
if (fields.isEmpty) {
|
||||
into.write('identityHashCode(this)');
|
||||
} else if (fields.length == 1) {
|
||||
into.write('${fields[0]}.hashCode');
|
||||
} else {
|
||||
final needsHashAll = fields.length > _maxArgsToObjectHash;
|
||||
|
||||
into.write(needsHashAll ? 'Object.hashAll([' : 'Object.hash(');
|
||||
var first = true;
|
||||
for (final field in fields) {
|
||||
if (!first) into.write(', ');
|
||||
|
||||
into.write(field);
|
||||
first = false;
|
||||
}
|
||||
|
||||
into.write(needsHashAll ? '])' : ')');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
/// Writes a operator == override for a class consisting of the [fields] into
|
||||
/// the buffer provided by [into].
|
||||
void overrideEquals(
|
||||
Iterable<String> fields, String className, StringBuffer into) {
|
||||
into
|
||||
..write('@override\nbool operator ==(Object other) => ')
|
||||
..write('identical(this, other) || (other is $className');
|
||||
|
||||
if (fields.isNotEmpty) {
|
||||
into
|
||||
..write(' && ')
|
||||
..write(fields.map((field) {
|
||||
return 'other.$field == this.$field';
|
||||
}).join(' && '));
|
||||
}
|
||||
|
||||
into.write(');\n');
|
||||
}
|
|
@ -10,7 +10,6 @@ export 'src/writer/queries/result_set_writer.dart';
|
|||
export 'src/writer/tables/data_class_writer.dart';
|
||||
export 'src/writer/tables/table_writer.dart';
|
||||
export 'src/writer/tables/update_companion_writer.dart';
|
||||
export 'src/writer/utils/hash_code.dart';
|
||||
export 'src/writer/utils/memoized_getter.dart';
|
||||
export 'src/writer/utils/override_equals.dart';
|
||||
export 'src/writer/utils/hash_and_equals.dart';
|
||||
export 'src/writer/writer.dart';
|
||||
|
|
|
@ -1,30 +1,42 @@
|
|||
import 'package:charcode/ascii.dart';
|
||||
import 'package:drift_dev/src/writer/utils/hash_code.dart';
|
||||
import 'package:drift_dev/src/writer/utils/hash_and_equals.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('hash code for no fields', () {
|
||||
final buffer = StringBuffer();
|
||||
const HashCodeWriter().writeHashCode([], buffer);
|
||||
writeHashCode([], buffer);
|
||||
expect(buffer.toString(), r'identityHashCode(this)');
|
||||
});
|
||||
|
||||
test('hash code for a single field', () {
|
||||
test('hash code for a single field - not a list', () {
|
||||
final buffer = StringBuffer();
|
||||
const HashCodeWriter().writeHashCode(['a'], buffer);
|
||||
writeHashCode([EqualityField('a')], buffer);
|
||||
expect(buffer.toString(), 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)');
|
||||
});
|
||||
|
||||
test('hash code for multiple fields', () {
|
||||
final buffer = StringBuffer();
|
||||
const HashCodeWriter().writeHashCode(['a', 'b', 'c'], buffer);
|
||||
expect(buffer.toString(), r'Object.hash(a, b, c)');
|
||||
writeHashCode([
|
||||
EqualityField('a'),
|
||||
EqualityField('b', isList: true),
|
||||
EqualityField('c'),
|
||||
], buffer);
|
||||
expect(buffer.toString(), r'Object.hash(a, $driftBlobEquality.hash(b), c)');
|
||||
});
|
||||
|
||||
test('hash code for lots of fields', () {
|
||||
final buffer = StringBuffer();
|
||||
const HashCodeWriter().writeHashCode(
|
||||
List.generate(26, (index) => String.fromCharCode($a + index)), buffer);
|
||||
writeHashCode(
|
||||
List.generate(
|
||||
26, (index) => EqualityField(String.fromCharCode($a + index))),
|
||||
buffer);
|
||||
expect(
|
||||
buffer.toString(),
|
||||
r'Object.hashAll([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, '
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:drift_dev/src/writer/utils/override_equals.dart';
|
||||
import 'package:drift_dev/src/writer/utils/hash_and_equals.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -14,12 +14,17 @@ void main() {
|
|||
|
||||
test('overrides equals on class with fields', () {
|
||||
final buffer = StringBuffer();
|
||||
overrideEquals(['a', 'b', 'c'], 'Foo', buffer);
|
||||
overrideEquals([
|
||||
EqualityField('a'),
|
||||
EqualityField('b', isList: true),
|
||||
EqualityField('c'),
|
||||
], 'Foo', buffer);
|
||||
|
||||
expect(
|
||||
buffer.toString(),
|
||||
'@override\nbool operator ==(Object other) => '
|
||||
'identical(this, other) || (other is Foo && '
|
||||
'other.a == this.a && other.b == this.b && other.c == this.c);\n');
|
||||
r'other.a == this.a && $driftBlobEquality.equals(other.b, this.b) && '
|
||||
'other.c == this.c);\n');
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue