Merge remote-tracking branch 'simolus3/develop' into fix-lint-warning

This commit is contained in:
westito 2022-08-08 11:30:57 +02:00
commit 40554c8166
39 changed files with 284 additions and 170 deletions

View File

@ -288,6 +288,12 @@ describes how to migrate stored columns between the format:
{% assign snippets = "package:drift_docs/snippets/migrations/datetime_conversion.dart.excerpt.json" | readString | json_decode %}
Note that the JSON serialization generated by default is not affected by the
datetime mode chosen. By default, drift will serialize `DateTime` values to a
unix timestamp in milliseconds. You can change this by creating a
`ValueSerializer.defaults(serializeDateTimeValuesAsString: true)` and assigning
it to `driftRuntimeOptions.defaultSerializer`.
##### Migrating from unix timestamps to text
To migrate from using timestamps (the default option) to storing datetimes as

View File

@ -1,5 +1,5 @@
---
data:
data:
title: "Documentation & Guides"
description: Welcome to drift's documentation. This site shows you what drift can do and how to use it.
template: layouts/docs/list
@ -8,7 +8,7 @@ template: layouts/docs/list
## Welcome to drift
Drift is a reactive persistence library for Dart and Flutter applications. It's built on top
of database libraries like [sqflite](https://pub.dev/packages/sqflite) or [sql.js](https://github.com/sql-js/sql.js/)
of database libraries like [the sqlite3 package](https://pub.dev/packages/sqlite3), [sqflite](https://pub.dev/packages/sqflite) or [sql.js](https://github.com/sql-js/sql.js/)
and provides additional features, like:
- __Type safety__: Instead of writing sql queries manually and parsing the `List<Map<String, dynamic>>` that they

View File

@ -7,47 +7,76 @@ data:
show_favorites: true
---
{% block "blocks/cover.html" title="Drift: Persistence library for Dart" image_anchor="top" height="med" color="indigo" %}
{% block "blocks/cover.html" title="Drift: Persistence library for Dart" image_anchor="top" height="med" %}
<div class="mx-auto">
<p class="h4">
Drift is <em>the</em> relational persistence library for your Dart and Flutter apps.
Write type-safe queries in Dart or SQL, enjoy auto-updating streams, easily managed transactions
and so much more to make persistence fun.
</p>
<a class="btn btn-lg btn-primary mr-3 mb-4" href="{{ 'docs/index' | pageUrl }}">
Learn more <i class="fas fa-arrow-alt-circle-right ml-2"></i>
</a>
<a class="btn btn-lg btn-secondary mr-3 mb-4" href="https://pub.dev/packages/drift">
Get from pub <i class="fas fa-code ml-2 "></i>
Get started <i class="fas fa-code ml-2 "></i>
</a>
<p class="lead mt-5">
With a fluent query api, a powerful sql analyzer, auto-updating streams and much more,
drift makes persistence fun. Scroll down to learn about its key features, or visit the
<a href="{{ 'docs/Getting started/index' | pageUrl }}">getting started guide</a> for a step-by-step guide on using drift.
</p>
</div>
{% endblock %}
{% block "blocks/lead.html" color="dark" %}
Drift is an easy to use, reactive persistence library for Flutter apps. Define tables in Dart or
SQL and enjoy a fluent query API, auto-updating streams and more!
{% endblock %}
{% block "blocks/section.html" color="primary" %}
{% block "blocks/feature.html" icon="fas fa-lightbulb" title="Declarative tables, fluent queries" %}
{% block "blocks/markdown.html" %}
With drift, you can write your database tables in pure Dart without having to miss out on
advanced sqlite features. Drift will take care of creating the tables and generate code
With drift, you can declare your database tables and queries in pure Dart without having to miss out on
advanced SQL features. Drift will take care of creating the tables and generate code
that allows you run fluent queries on your data.
[Get started now]({{ "docs/Getting started/index.md" | pageUrl }})
{% endblock %}
{% endblock %}
{% block "blocks/feature.html" icon="fas fa-database" title="Prefer SQL? Drift got you covered!" %}
{% block "blocks/feature.html" icon="fas fa-database" title="Definitely relational" %}
{% block "blocks/markdown.html" %}
Drift contains a powerful sql parser and analyzer, allowing it to create typesafe APIs for all your sql queries. All sql queries are
Drift is not the kind of ORM that tries to hide SQL away and then breaks down at the first
aggregation or non-obvious join.
Instead, drift embraces relational databases with an Dart API that's easy to learn
while still being close to SQL. Advanced expressions or subqueries are supported out of
the box.
{% endblock %}
{% endblock %}
{% block "blocks/feature.html" icon="fas fa-lightbulb" title="Safe schemas" %}
{% block "blocks/markdown.html" %}
A well-chosen SQL schema enables type-safe queries and avoids hard-to-spot mistakes.
Thanks to drift's extensive support for schema migrations, changing schemas is a safe
and easy process.
Further, drift provides a complete test toolkit to help you test migrations
between all your revisions.
[All about schema migrations]({{ "docs/Advanced Features/migrations.md" | pageUrl }})
{% endblock %}
{% endblock %}
{% block "blocks/feature.html" icon="fas fa-database" title="Prefer SQL? Drift's got you covered!" %}
{% block "blocks/markdown.html" %}
Drift ships a powerful sql parser and analyzer, allowing it to create typesafe methods for all your sql queries. All sql queries are
validated and analyzed during build-time, so drift can provide hints about potential errors quickly and generate efficient mapping
code.
Of course, you can mix SQL and Dart to your liking.
[Learn more]({{ 'docs/Using SQL/index.md' | pageUrl }})
[Using SQL with Drift]({{ 'docs/Using SQL/index.md' | pageUrl }})
{% endblock %}
{% endblock %}
{% block "blocks/feature.html" icon="fas fa-lightbulb" title="Supported on your favorite platform" %}
{% block "blocks/markdown.html" %}
Drift's core APIs are written to support a range of database libraries as backends, it doesn't even require Flutter.
Drift has primary first-class support for Android, iOS, macOS, Linux Windows and the web.
Other database libraries can easily be integrated into drift as well.
[All platforms]({{ "docs/platforms.md" | pageUrl }})
{% endblock %}
{% endblock %}
@ -60,25 +89,3 @@ When using drift, working with databases in Dart is fun!
{% endblock %}
{% endblock %}
{% block "blocks/section.html" color="light" type="section" %}
{% block "blocks/markdown.html" %}
## Key features
Here are some of the many ways drift helps you write awesome database code:
* __Auto-updating streams__: With drift, any query - no matter how complex - can be turned into a stream that emits new data as the underlying data changes.
* __Polyglot__: Drift lets you write queries in a fluent Dart api or directly in SQL - you can even embed Dart expressions in SQL.
* __Boilerplate-free__: Stop writing mapping code yourself - drift can take of that. Drift generates Dart code around your data so you can focus
on building great apps.
* __Flexible__: Want to write queries in SQL? Drift verifies them at compile time and generates Dart apis for them. Prefer to write them in Dart?
Drift will generate efficient SQL for Dart queries too.
* __Easy to learn__: Instead of having to learn yet another ORM, drift lets you write queries in SQL and generates typesafe wrappers. Queries and tables
can also be written in Dart that looks similar to SQL without loosing type-safety.
* __Fast _and_ powerful__: With the new `ffi` backend, drift can outperform key-value stores without putting any compromises on the integrity
and flexibility that relational databases provide. Drift is the only major persistence library with builtin support for multiple isolates.
* __Well tested and production ready__: Each component of drift is verified by a wide range of unit and integration tests. Drift powers many Flutter apps
in production.
* __Cross-Platform__: Drift works on iOS, Android, Linux, macOS, Windows and on the web. It doesn't even require Flutter. See [supported platforms]({{ "docs/platforms.md" | pageUrl }}).
{% endblock %}
{% endblock %}

View File

@ -38,8 +38,6 @@ dev_dependencies:
drift_dev:
dependency_overrides:
# Waiting for dartdoc, https://github.com/dart-lang/dartdoc/pull/3033
analyzer: ^4.0.0
moor_generator:
path: ../moor_generator
drift:

31
docs/templates/partials/footer.html vendored Normal file
View File

@ -0,0 +1,31 @@
{% assign links = site.links %}
<footer class="bg-dark py-5 row d-print-none">
<div class="container-fluid mx-sm-5">
<div class="row">
<div class="col-6 col-sm-4 text-xs-center order-sm-2">
{% if links.user %}
{% assign links = links.user %}
{% include "partials/footer-links-block.html" %}
{% endif %}
</div>
<div class="col-6 col-sm-4 text-right text-xs-center order-sm-3">
{% if links.developer %}
{% assign links = links.developer %}
{% include "partials/footer-links-block.html" %}
{% endif %}
</div>
<div class="col-12 col-sm-4 text-center py-2 order-sm-2">
{% if site.copyright %}
<small class="text-white"><div>&copy; {{ site.copyright }}</div>
Except where otherwise noted, content on this site is licensed under a <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="license noopener noreferrer">CC BY 4.0</a> license.
Code snippets are marked with <a href="http://creativecommons.org/publicdomain/zero/1.0" target="_blank" rel="license noopener noreferrer">CC0 1.0</a>,
drift itself is <a href="https://github.com/simolus3/drift/blob/develop/LICENSE" rel="license" target="_blank">MIT-licensed</a>.
</small>
{% endif %}
{% if site.privacy_policy %}
<small class="ml-1"><a href="{{ site.privacy_policy }}" target="_blank">{{ "footer_privacy_policy" | i18n }}</a></small>
{% endif %}
</div>
</div>
</div>
</footer>

View File

@ -158,11 +158,19 @@ abstract class Table extends HasResultSet {
ColumnBuilder<bool> boolean() => _isGenerated();
/// Use this as the body of a getter to declare a column that holds date and
/// time. Note that [DateTime] values are stored on a second-accuracy.
/// time.
///
/// Drift supports two modes for storing date times: As unix timestamp with
/// second accuracy (the default) and as ISO 8601 string with microsecond
/// accuracy. For more information between the modes, and information on how
/// to change them, see [the documentation].
///
/// Note that [DateTime] values are stored on a second-accuracy.
/// Example (inside the body of a table class):
/// ```
/// DateTimeColumn get accountCreatedAt => dateTime()();
/// ```
/// [the documentation]: https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/#supported-column-types
@protected
ColumnBuilder<DateTime> dateTime() => _isGenerated();

View File

@ -188,7 +188,14 @@ abstract class ValueSerializer {
///
/// To override the default serializer drift uses, you can change the
/// [DriftRuntimeOptions.defaultSerializer] field.
const factory ValueSerializer.defaults() = _DefaultValueSerializer;
///
/// The [serializeDateTimeValuesAsString] option (which defaults to `false`)
/// describes whether [DateTime] values should be serialized to a unix
/// timestamp ([DateTime.millisecondsSinceEpoch]) or a string
/// ([DateTime.toIso8601String]).
/// In either case, date time values can be _deserialized_ from both formats.
const factory ValueSerializer.defaults(
{bool serializeDateTimeValuesAsString}) = _DefaultValueSerializer;
/// Converts the [value] to something that can be passed to
/// [JsonCodec.encode].
@ -200,7 +207,9 @@ abstract class ValueSerializer {
}
class _DefaultValueSerializer extends ValueSerializer {
const _DefaultValueSerializer();
final bool serializeDateTimeValuesAsString;
const _DefaultValueSerializer({this.serializeDateTimeValuesAsString = false});
@override
T fromJson<T>(dynamic json) {
@ -211,7 +220,11 @@ class _DefaultValueSerializer extends ValueSerializer {
final _typeList = <T>[];
if (_typeList is List<DateTime?>) {
return DateTime.fromMillisecondsSinceEpoch(json as int) as T;
if (json is int) {
return DateTime.fromMillisecondsSinceEpoch(json) as T;
} else {
return DateTime.parse(json.toString()) as T;
}
}
if (_typeList is List<double?> && json is int) {
@ -231,7 +244,9 @@ class _DefaultValueSerializer extends ValueSerializer {
@override
dynamic toJson<T>(T value) {
if (value is DateTime) {
return value.millisecondsSinceEpoch;
return serializeDateTimeValuesAsString
? value.toIso8601String()
: value.millisecondsSinceEpoch;
}
return value;

View File

@ -48,6 +48,12 @@ void _testWith(TodoDb Function() openDb, {bool dateTimeAsText = false}) {
expect(await eval(Variable(local)), local);
},
);
test('preserves milliseconds', () async {
final local = DateTime(2020, 09, 03, 23, 55, 0, 123);
expect(await eval(Variable(local)), local);
});
}
test('plus and minus', () async {

View File

@ -17,8 +17,8 @@ void main() {
await expectLater(
db.validateDatabaseSchema(),
throwsA(isA<SchemaMismatch>().having(
(e) => e.toString(), 'toString()', contains('TEXT and INTEGER'))),
throwsA(isA<SchemaMismatch>().having((e) => e.toString(), 'toString()',
contains('Expected TEXT, got INTEGER'))),
);
});
}

View File

@ -2,29 +2,37 @@ import 'package:drift/drift.dart';
import 'package:test/test.dart';
import 'generated/todos.dart';
final DateTime someDate = DateTime(2019, 06, 08);
final DateTime _someDate = DateTime(2019, 06, 08);
final TodoEntry someTodoEntry = TodoEntry(
final TodoEntry _someTodoEntry = TodoEntry(
id: 3,
title: null,
content: 'content',
targetDate: someDate,
targetDate: _someDate,
category: 3,
);
final Map<String, dynamic> regularSerialized = {
final Map<String, dynamic> _regularSerialized = {
'id': 3,
'title': null,
'content': 'content',
'target_date': someDate.millisecondsSinceEpoch,
'target_date': _someDate.millisecondsSinceEpoch,
'category': 3,
};
final Map<String, dynamic> customSerialized = {
final Map<String, dynamic> _asTextSerialized = {
'id': 3,
'title': null,
'content': 'content',
'target_date': _someDate.toIso8601String(),
'category': 3,
};
final Map<String, dynamic> _customSerialized = {
'id': 3,
'title': 'set to null',
'content': 'content',
'target_date': someDate.toIso8601String(),
'target_date': _someDate.toIso8601String(),
'category': 3,
};
@ -61,7 +69,16 @@ void main() {
group('serialization', () {
test('with defaults', () {
expect(someTodoEntry.toJson(), equals(regularSerialized));
expect(_someTodoEntry.toJson(), equals(_regularSerialized));
});
test('with default serializer, date as text', () {
expect(
_someTodoEntry.toJson(
serializer: const ValueSerializer.defaults(
serializeDateTimeValuesAsString: true)),
equals(_asTextSerialized),
);
});
test('applies json type converter', () {
@ -71,20 +88,31 @@ void main() {
});
test('with custom serializer', () {
expect(someTodoEntry.toJson(serializer: CustomSerializer()),
equals(customSerialized));
expect(_someTodoEntry.toJson(serializer: CustomSerializer()),
equals(_customSerialized));
});
});
group('deserialization', () {
test('with defaults', () {
expect(TodoEntry.fromJson(regularSerialized), equals(someTodoEntry));
expect(TodoEntry.fromJson(_regularSerialized), equals(_someTodoEntry));
expect(TodoEntry.fromJson(_asTextSerialized), equals(_someTodoEntry));
});
test('with date-as-text serializer', () {
const serializer =
ValueSerializer.defaults(serializeDateTimeValuesAsString: true);
expect(TodoEntry.fromJson(_regularSerialized, serializer: serializer),
equals(_someTodoEntry));
expect(TodoEntry.fromJson(_asTextSerialized, serializer: serializer),
equals(_someTodoEntry));
});
test('with custom serializer', () {
expect(
TodoEntry.fromJson(customSerialized, serializer: CustomSerializer()),
equals(someTodoEntry));
TodoEntry.fromJson(_customSerialized, serializer: CustomSerializer()),
equals(_someTodoEntry));
});
});
}

View File

@ -337,7 +337,7 @@ class ColumnParser {
if (foundExplicitName != null) {
name = ColumnName.explicitly(foundExplicitName);
} else {
name = ColumnName.implicitly(ReCase(getter.name.name).snakeCase);
name = ColumnName.implicitly(ReCase(getter.name2.lexeme).snakeCase);
}
final columnType = _startMethodToColumnType(foundStartMethod);
@ -424,7 +424,7 @@ class ColumnParser {
getter.documentationComment?.tokens.map((t) => t.toString()).join('\n');
return DriftColumn(
type: columnType,
dartGetterName: getter.name.name,
dartGetterName: getter.name2.lexeme,
name: name,
overriddenJsonName: _readJsonKey(element),
customConstraints: foundCustomConstraint,
@ -456,9 +456,11 @@ class ColumnParser {
final annotations = getter.metadata;
final object = annotations.firstWhereOrNull((e) {
final value = e.computeConstantValue();
return value != null &&
isFromMoor(value.type!) &&
value.type!.element!.name == 'JsonKey';
final valueType = value?.type;
return valueType is InterfaceType &&
isFromDrift(valueType) &&
valueType.element2.name == 'JsonKey';
});
if (object == null) return null;

View File

@ -85,7 +85,7 @@ class TableParser {
for (final annotation in element.metadata) {
final computed = annotation.computeConstantValue();
final annotationClass = computed!.type!.element!.name;
final annotationClass = computed!.type!.nameIfInterfaceType;
if (annotationClass == 'DataClassName') {
dataClassName = computed;
@ -124,8 +124,8 @@ class TableParser {
useRowClass.getField('generateInsertable')!.toBoolValue()!;
if (type is InterfaceType) {
existingClass = FoundDartClass(type.element, type.typeArguments);
name = type.element.name;
existingClass = FoundDartClass(type.element2, type.typeArguments);
name = type.element2.name;
} else {
base.step.reportError(ErrorInDartCode(
message: 'The @UseRowClass annotation must be used with a class',
@ -309,7 +309,7 @@ class TableParser {
Future<Iterable<DriftColumn>> _parseColumns(ClassElement element) async {
final columnNames = element.allSupertypes
.map((t) => t.element)
.map((t) => t.element2)
.followedBy([element])
.expand((e) => e.fields)
.where((field) =>
@ -350,10 +350,10 @@ class _DataClassInformation {
extension on Element {
bool get isFromDefaultTable {
final parent = enclosingElement2;
final parent = enclosingElement3;
return parent is ClassElement &&
parent.name == 'Table' &&
isFromMoor(parent.thisType);
isFromDrift(parent.thisType);
}
}

View File

@ -9,7 +9,7 @@ class UseDaoParser {
/// declared by that class and the referenced tables.
Future<Dao?> parseDao(ClassElement element, ConstantReader annotation) async {
final dbType = element.allSupertypes
.firstWhereOrNull((i) => i.element.name == 'DatabaseAccessor');
.firstWhereOrNull((i) => i.element2.name == 'DatabaseAccessor');
if (dbType == null) {
step.reportError(ErrorInDartCode(

View File

@ -41,7 +41,7 @@ class ViewParser {
for (final annotation in element.metadata) {
final computed = annotation.computeConstantValue();
final annotationClass = computed!.type!.element!.name;
final annotationClass = computed!.type!.nameIfInterfaceType;
if (annotationClass == 'DriftView') {
driftView = computed;
@ -80,8 +80,8 @@ class ViewParser {
useRowClass.getField('generateInsertable')!.toBoolValue()!;
if (type is InterfaceType) {
existingClass = FoundDartClass(type.element, type.typeArguments);
name = type.element.name;
existingClass = FoundDartClass(type.element2, type.typeArguments);
name = type.element2.name;
} else {
base.step.reportError(ErrorInDartCode(
message: 'The @UseRowClass annotation must be used with a class',
@ -100,7 +100,7 @@ class ViewParser {
Future<String> _parseViewName(ClassElement element) async {
for (final annotation in element.metadata) {
final computed = annotation.computeConstantValue();
final annotationClass = computed!.type!.element!.name;
final annotationClass = computed!.type!.nameIfInterfaceType;
if (annotationClass == 'DriftView') {
final name = computed.getField('name')?.toStringValue();
@ -116,7 +116,7 @@ class ViewParser {
Future<Iterable<DriftColumn>> _parseColumns(ClassElement element) async {
final columnNames = element.allSupertypes
.map((t) => t.element)
.map((t) => t.element2)
.followedBy([element])
.expand((e) => e.fields)
.where((field) =>
@ -134,7 +134,7 @@ class ViewParser {
final results = await Future.wait(fields.map((field) async {
final dartType = (field.type as InterfaceType).typeArguments[0];
final typeName = dartType.element!.name!;
final typeName = dartType.nameIfInterfaceType!;
final sqlType = _dartTypeToColumnType(typeName);
if (sqlType == null) {
@ -181,7 +181,7 @@ class ViewParser {
Future<List<TableReferenceInDartView>> _parseStaticReferences(
ClassElement element, List<DriftTable> tables) async {
return await Stream.fromIterable(element.allSupertypes
.map((t) => t.element)
.map((t) => t.element2)
.followedBy([element]).expand((e) => e.fields))
.asyncMap((field) => _getStaticReference(field, tables))
.where((ref) => ref != null)
@ -198,7 +198,7 @@ class ViewParser {
final type = tables.firstWhereOrNull(
(tbl) => tbl.fromClass!.name == node.returnType.toString());
if (type != null) {
final name = node.name.toString();
final name = node.name2.lexeme;
return TableReferenceInDartView(type, name);
}
}

View File

@ -11,7 +11,7 @@ import 'package:drift_dev/src/analyzer/runner/steps.dart';
import 'helper.dart';
class FoundDartClass {
final ClassElement classElement;
final InterfaceElement classElement;
/// The instantiation of the [classElement], if the found type was a generic
/// typedef.
@ -230,8 +230,8 @@ void _checkType(
final isAllowedUint8List = typeConverter == null &&
columnType == DriftSqlType.blob &&
typeToCheck is InterfaceType &&
typeToCheck.element.name == 'Uint8List' &&
typeToCheck.element.library.name == 'dart.typed_data';
typeToCheck.element2.name == 'Uint8List' &&
typeToCheck.element2.library.name == 'dart.typed_data';
if (!typeSystem.isAssignableTo(expectedDartType.type, typeToCheck) &&
!isAllowedUint8List) {
@ -246,14 +246,14 @@ extension on TypeProvider {
case DriftSqlType.int:
return intType;
case DriftSqlType.bigInt:
return intElement.library.getType('BigInt')!.instantiate(
return intElement.library.getClass('BigInt')!.instantiate(
typeArguments: const [], nullabilitySuffix: NullabilitySuffix.none);
case DriftSqlType.string:
return stringType;
case DriftSqlType.bool:
return boolType;
case DriftSqlType.dateTime:
return intElement.library.getType('DateTime')!.instantiate(
return intElement.library.getClass('DateTime')!.instantiate(
typeArguments: const [], nullabilitySuffix: NullabilitySuffix.none);
case DriftSqlType.blob:
return listType(intType);

View File

@ -28,8 +28,8 @@ String? parseCustomParentClass(String dartTypeName, DartObject dataClassName,
if (extending != null && !extending.isNull) {
final extendingType = extending.toTypeValue();
if (extendingType is InterfaceType) {
final superType = extendingType.allSupertypes
.any((type) => isFromMoor(type) && type.element.name == 'DataClass');
final superType = extendingType.allSupertypes.any(
(type) => isFromDrift(type) && type.element2.name == 'DataClass');
if (!superType) {
base.step.reportError(
ErrorInDartCode(
@ -52,10 +52,10 @@ String? parseCustomParentClass(String dartTypeName, DartObject dataClassName,
return null;
}
final className = extendingType.element.name;
final className = extendingType.nameIfInterfaceType;
if (extendingType.typeArguments.length == 1) {
final genericType = extendingType.typeArguments[0].element?.name;
if (genericType == 'Object' || genericType == 'dynamic') {
final genericType = extendingType.typeArguments[0];
if (genericType.isDartCoreObject || genericType.isDynamic) {
return '$className<$dartTypeName>';
} else {
base.step.reportError(

View File

@ -28,7 +28,7 @@ Future<FoundDartClass?> findDartClass(
} else if (foundElement is TypeAliasElement) {
final innerType = foundElement.aliasedType;
if (innerType is InterfaceType) {
return FoundDartClass(innerType.element, innerType.typeArguments);
return FoundDartClass(innerType.element2, innerType.typeArguments);
}
}
}

View File

@ -122,7 +122,7 @@ class ParseDartStep extends Step {
Future<List<DriftTable>> parseTables(
Iterable<DartType> types, Element initializedBy) {
return Future.wait(types.map((type) {
if (!_tableTypeChecker.isAssignableFrom(type.element!)) {
if (!_tableTypeChecker.isAssignableFromType(type)) {
reportError(ErrorInDartCode(
severity: Severity.criticalError,
message: 'The type $type is not a moor table',
@ -130,7 +130,7 @@ class ParseDartStep extends Step {
));
return Future.value(null);
} else {
return _parseTable(type.element as ClassElement);
return _parseTable((type as InterfaceType).element2 as ClassElement);
}
})).then((list) {
// only keep tables that were resolved successfully
@ -145,7 +145,7 @@ class ParseDartStep extends Step {
Future<List<MoorView>> parseViews(Iterable<DartType> types,
Element initializedBy, List<DriftTable> tables) {
return Future.wait(types.map((type) {
if (!_viewTypeChecker.isAssignableFrom(type.element!)) {
if (!_viewTypeChecker.isAssignableFromType(type)) {
reportError(ErrorInDartCode(
severity: Severity.criticalError,
message: 'The type $type is not a drift view',
@ -153,7 +153,8 @@ class ParseDartStep extends Step {
));
return Future.value(null);
} else {
return _parseView(type.element as ClassElement, tables);
return _parseView(
(type as InterfaceType).element2 as ClassElement, tables);
}
})).then((list) {
// only keep tables that were resolved successfully

View File

@ -21,7 +21,7 @@ class DaoGenerator extends Generator implements BaseGenerator {
final daoName = element!.displayName;
final dbTypeName = dao.dbClass.codeString(writer.generationOptions);
final dbTypeName = dao.dbClass.codeString();
classScope.leaf().write('mixin _\$${daoName}Mixin on '
'DatabaseAccessor<$dbTypeName> {\n');

View File

@ -413,7 +413,8 @@ class _Moor2DriftDartRewriter extends GeneralizingAstVisitor<void> {
if (type is! InterfaceType) continue;
if (type.element.library.isDartCore && type.element.name == 'pragma') {
if (type.element2.library.isDartCore &&
type.element2.name == 'pragma') {
final name = value.getField('name')!.toStringValue()!;
if (name == 'moor2drift') {
@ -452,8 +453,8 @@ class _Moor2DriftDartRewriter extends GeneralizingAstVisitor<void> {
if (type is! InterfaceType ||
// note that even old moor code uses these names since UseMoor/UseDao
// are type aliases to the new interfaces.
(type.element.name != 'DriftDatabase' &&
type.element.name != 'DriftAccessor')) {
(type.element2.name != 'DriftDatabase' &&
type.element2.name != 'DriftAccessor')) {
return;
}

View File

@ -40,7 +40,7 @@ abstract class DriftEntityWithResultSet extends DriftSchemaEntity {
/// The type name of the Dart row class for this result set.
///
/// This may contain generics.
String dartTypeCode([GenerationOptions options = const GenerationOptions()]);
String dartTypeCode();
/// The name of the Dart class storing additional properties like type
/// converters.
@ -65,7 +65,7 @@ abstract class DriftEntityWithResultSet extends DriftSchemaEntity {
/// Information used by the generator to generate code for a custom data class
/// written by users.
class ExistingRowClass {
final ClassElement targetClass;
final InterfaceElement targetClass;
/// The Dart types that should be used to instantiate the [targetClass].
final List<DartType> typeInstantiation;

View File

@ -142,11 +142,11 @@ abstract class SqlQuery {
}
if (resultSet.matchingTable != null) {
return resultSet.matchingTable!.table.dartTypeCode(options);
return resultSet.matchingTable!.table.dartTypeCode();
}
if (resultSet.singleColumn) {
return resultSet.columns.single.dartTypeCode(options);
return resultSet.columns.single.dartTypeCode();
}
return resultClassName;
@ -593,7 +593,7 @@ abstract class FoundElement {
bool get hidden => false;
/// Dart code for a type representing tis element.
String dartTypeCode([GenerationOptions options = const GenerationOptions()]);
String dartTypeCode();
}
/// A semantic interpretation of a [Variable] in a sql statement.
@ -676,8 +676,8 @@ class FoundVariable extends FoundElement implements HasType {
}
@override
String dartTypeCode([GenerationOptions options = const GenerationOptions()]) {
return OperationOnTypes(this).dartTypeCode(options);
String dartTypeCode() {
return OperationOnTypes(this).dartTypeCode();
}
}

View File

@ -2,7 +2,6 @@ import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:drift_dev/src/model/model.dart';
import 'package:drift_dev/src/utils/type_utils.dart';
import 'package:drift_dev/writer.dart';
/// Something that has a type.
///
@ -60,14 +59,14 @@ class DriftDartType {
return type.getDisplayString(withNullability: withNullability);
}
String codeString([GenerationOptions options = const GenerationOptions()]) {
String codeString() {
if (overiddenSource != null) {
if (nullabilitySuffix == NullabilitySuffix.star) {
return getDisplayString(withNullability: false);
}
return getDisplayString(withNullability: true);
} else {
return type.codeString(options);
return type.codeString();
}
}
}
@ -127,10 +126,10 @@ extension OperationOnTypes on HasType {
/// The dart type that matches the values of this column. For instance, if a
/// table has declared an `IntColumn`, the matching dart type name would be
/// [int].
String dartTypeCode([GenerationOptions options = const GenerationOptions()]) {
String dartTypeCode() {
final converter = typeConverter;
if (converter != null) {
var inner = converter.dartType.codeString(options);
var inner = converter.dartType.codeString();
if (converter.canBeSkippedForNulls && nullable) inner += '?';
return isArray ? 'List<$inner>' : inner;
}

View File

@ -4,7 +4,6 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:drift_dev/src/model/table.dart';
import 'package:drift_dev/src/utils/type_utils.dart';
import 'package:drift_dev/src/writer/writer.dart';
import 'types.dart';
@ -92,12 +91,12 @@ class UsedTypeConverter {
bool nullable,
TypeProvider typeProvider,
) {
if (enumType.element is! ClassElement) {
if (enumType is! InterfaceType) {
throw InvalidTypeForEnumConverterException('Not a class', enumType);
}
final creatingClass = enumType.element as ClassElement;
if (!creatingClass.isEnum) {
final creatingClass = enumType.element2;
if (creatingClass is! EnumElement) {
throw InvalidTypeForEnumConverterException('Not an enum', enumType);
}
@ -132,23 +131,22 @@ class UsedTypeConverter {
return dartTypeIsNullable || (canBeSkippedForNulls && nullableInSql);
}
String dartTypeCode(GenerationOptions options, bool nullableInSql) {
var type = dartType.codeString(options);
String dartTypeCode(bool nullableInSql) {
var type = dartType.codeString();
if (canBeSkippedForNulls && nullableInSql) type += '?';
return type;
}
/// A suitable typename to store an instance of the type converter used here.
String converterNameInCode(GenerationOptions options,
{bool makeNullable = false}) {
String converterNameInCode({bool makeNullable = false}) {
var sqlDartType = sqlType.getDisplayString(withNullability: true);
if (makeNullable) sqlDartType += '?';
final className =
alsoAppliesToJsonConversion ? 'JsonTypeConverter' : 'TypeConverter';
return '$className<${dartTypeCode(options, makeNullable)}, $sqlDartType>';
return '$className<${dartTypeCode(makeNullable)}, $sqlDartType>';
}
}

View File

@ -166,7 +166,7 @@ class FindSchemaDifferences {
if (refType != actType) {
return FoundDifference(
'Different types: ${ref.typeName} and ${act.typeName}');
'Different types: Expected ${ref.typeName}, got ${act.typeName}');
}
try {
@ -174,19 +174,20 @@ class FindSchemaDifferences {
} catch (e) {
final firstSpan = ref.constraints.spanOrNull?.text ?? '';
final secondSpan = act.constraints.spanOrNull?.text ?? '';
return FoundDifference('Not equal: `$firstSpan` and `$secondSpan`');
return FoundDifference(
'Not equal: `$firstSpan` (expected) and `$secondSpan` (actual)');
}
return const Success();
}
CompareResult _compareByAst(AstNode a, AstNode b) {
CompareResult _compareByAst(AstNode reference, AstNode actual) {
try {
enforceEqual(a, b);
enforceEqual(reference, actual);
return const Success();
} catch (e) {
return FoundDifference(
'Not equal: `${a.span?.text}` and `${b.span?.text}`');
return FoundDifference('Not equal: Expected `${reference.span?.text}`, '
'got `${actual.span?.text}`');
}
}

View File

@ -1,34 +1,40 @@
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:drift_dev/src/writer/writer.dart';
bool isFromMoor(DartType type) {
final firstComponent = type.element?.library?.location?.components.first;
bool isFromDrift(DartType type) {
if (type is! InterfaceType) return false;
final firstComponent = type.element2.library.location?.components.first;
if (firstComponent == null) return false;
return firstComponent.contains('drift') || firstComponent.contains('moor');
}
bool isColumn(DartType type) {
final name = type.element?.name ?? '';
final name = type.nameIfInterfaceType;
return isFromMoor(type) &&
return isFromDrift(type) &&
name != null &&
name.contains('Column') &&
!name.contains('Builder');
}
bool isExpression(DartType type) {
final name = type.element?.name ?? '';
final name = type.nameIfInterfaceType;
return isFromMoor(type) && name.startsWith('Expression');
return name != null && isFromDrift(type) && name.startsWith('Expression');
}
extension TypeUtils on DartType {
String? get nameIfInterfaceType {
final $this = this;
return $this is InterfaceType ? $this.element2.name : null;
}
String get userVisibleName => getDisplayString(withNullability: true);
/// How this type should look like in generated code.
String codeString([GenerationOptions options = const GenerationOptions()]) {
String codeString() {
if (nullabilitySuffix == NullabilitySuffix.star) {
// We can't actually use the legacy star in code, so don't show it.
return getDisplayString(withNullability: false);

View File

@ -99,7 +99,7 @@ class DatabaseWriter {
// Write fields to access an dao. We use a lazy getter for that.
for (final dao in db.daos) {
final typeName = dao.codeString(scope.generationOptions);
final typeName = dao.codeString();
final getterName = ReCase(typeName).camelCase;
final databaseImplName = db.fromClass!.name;

View File

@ -293,7 +293,7 @@ class QueryWriter {
final namedElements = <FoundElement>[];
String typeFor(FoundElement element) {
var type = element.dartTypeCode(scope.generationOptions);
var type = element.dartTypeCode();
if (element is FoundDartPlaceholder &&
element.writeAsScopedFunction(options)) {

View File

@ -30,7 +30,7 @@ class ResultSetWriter {
// write fields
for (final column in resultSet.columns) {
final name = resultSet.dartNameFor(column);
final runtimeType = column.dartTypeCode(scope.generationOptions);
final runtimeType = column.dartTypeCode();
into.write('$modifier $runtimeType $name\n;');
@ -40,7 +40,7 @@ class ResultSetWriter {
for (final nested in resultSet.nestedResults) {
if (nested is NestedResultTable) {
var typeName = nested.table.dartTypeCode(scope.generationOptions);
var typeName = nested.table.dartTypeCode();
final fieldName = nested.dartFieldName;
if (nested.isNullable) {

View File

@ -48,7 +48,7 @@ class DataClassWriter {
_buffer.write('${column.documentationComment}\n');
}
final modifier = scope.options.fieldModifier;
_buffer.write('$modifier ${column.dartTypeCode(scope.generationOptions)} '
_buffer.write('$modifier ${column.dartTypeCode()} '
'${column.dartGetterName}; \n');
}
@ -120,7 +120,7 @@ class DataClassWriter {
typeConverter.tableAndField(forNullableColumn: column.nullable);
deserialized = '$converterField.fromJson($fromConverter)';
} else {
final type = column.dartTypeCode(scope.generationOptions);
final type = column.dartTypeCode();
deserialized = "serializer.fromJson<$type>(json['$jsonKey'])";
}
@ -151,7 +151,7 @@ class DataClassWriter {
final getter = column.dartGetterName;
final needsThis = getter == 'serializer';
var value = needsThis ? 'this.$getter' : getter;
var dartType = column.dartTypeCode(scope.generationOptions);
var dartType = column.dartTypeCode();
final typeConverter = column.typeConverter;
if (typeConverter != null && typeConverter.alsoAppliesToJsonConversion) {
@ -177,7 +177,7 @@ class DataClassWriter {
final last = i == columns.length - 1;
final isNullable = column.nullableInDart;
final typeName = column.dartTypeCode(scope.generationOptions);
final typeName = column.dartTypeCode();
if (wrapNullableInValue && isNullable) {
_buffer
..write('Value<$typeName> ${column.dartGetterName} ')

View File

@ -100,7 +100,7 @@ abstract class TableOrViewWriter {
if (converter != null) {
// Generate a GeneratedColumnWithTypeConverter instance, as it has
// additional methods to check for equality against a mapped value.
final mappedType = converter.dartTypeCode(options, column.nullable);
final mappedType = converter.dartTypeCode(column.nullable);
final converterCode =
converter.tableAndField(forNullableColumn: column.nullable);
@ -134,7 +134,7 @@ abstract class TableOrViewWriter {
return;
}
final dataClassName = tableOrView.dartTypeCode(scope.generationOptions);
final dataClassName = tableOrView.dartTypeCode();
buffer
..write('@override\n$dataClassName map(Map<String, dynamic> data, '
@ -321,7 +321,7 @@ class TableWriter extends TableOrViewWriter {
void _writeConvertersAsStaticFields() {
for (final converter in table.converters) {
final typeName = converter.converterNameInCode(scope.generationOptions);
final typeName = converter.converterNameInCode();
final code = converter.expression;
buffer.write('static $typeName ${converter.fieldName} = $code;');
@ -334,8 +334,8 @@ class TableWriter extends TableOrViewWriter {
if (converter != null &&
converter.canBeSkippedForNulls &&
column.nullable) {
final nullableTypeName = converter
.converterNameInCode(scope.generationOptions, makeNullable: true);
final nullableTypeName =
converter.converterNameInCode(makeNullable: true);
final wrap = converter.alsoAppliesToJsonConversion
? 'JsonTypeConverter.asNullable'

View File

@ -43,7 +43,7 @@ class UpdateCompanionWriter {
void _writeFields() {
for (final column in columns) {
final modifier = scope.options.fieldModifier;
final type = column.dartTypeCode(scope.generationOptions);
final type = column.dartTypeCode();
_buffer.write('$modifier Value<$type> ${column.dartGetterName};\n');
}
}
@ -83,7 +83,7 @@ class UpdateCompanionWriter {
if (table.isColumnRequiredForInsert(column)) {
requiredColumns.add(column);
final typeName = column.dartTypeCode(scope.generationOptions);
final typeName = column.dartTypeCode();
_buffer.write('required $typeName $param,');
} else {
@ -151,7 +151,7 @@ class UpdateCompanionWriter {
}
first = false;
final typeName = column.dartTypeCode(scope.generationOptions);
final typeName = column.dartTypeCode();
_buffer.write('Value<$typeName>? ${column.dartGetterName}');
}

View File

@ -30,7 +30,7 @@ dependencies:
sqlparser: ^0.22.0
# Dart analysis
analyzer: "^4.3.0"
analyzer: "^4.4.0"
analyzer_plugin: ^0.11.0
source_span: ^1.5.5
package_config: ^2.0.0

View File

@ -44,7 +44,7 @@ void main() {
expect(parser.returnExpressionOfMethod(node)!.toSource(), source);
}
final testClass = library.getType('Test');
final testClass = library.getClass('Test');
await _verifyReturnExpressionMatches(
testClass!.getGetter('getter')!, "'foo'");

View File

@ -146,7 +146,7 @@ void main() {
});
Future<DriftTable?> parse(String name) async {
return parser.parseTable(dartStep.library.getType(name)!);
return parser.parseTable(dartStep.library.getClass(name)!);
}
group('table names', () {

View File

@ -1,5 +1,6 @@
@Tags(['analyzer'])
import 'package:drift_dev/src/analyzer/runner/results.dart';
import 'package:drift_dev/src/utils/type_utils.dart';
import 'package:test/test.dart';
import '../utils.dart';
@ -48,7 +49,7 @@ class ProductsDao extends BaseProductsDao with _$ProductDaoMixin {
expect(file.errors.errors, isEmpty);
final dao = (file.currentResult as ParsedDartFile).declaredDaos.single;
expect(dao.dbClass.element!.name, 'MyDatabase');
expect(dao.dbClass.nameIfInterfaceType, 'MyDatabase');
state.close();
});

View File

@ -70,7 +70,7 @@ void main() {
expect(result, hasChanges);
expect(
result.describe(),
contains('Different types: TEXT and INTEGER'),
contains('Different types: Expected TEXT, got INTEGER'),
);
});
@ -83,7 +83,8 @@ void main() {
expect(result, hasChanges);
expect(
result.describe(),
contains('Not equal: `PRIMARY KEY NOT NULL` and ``'),
contains(
'Not equal: `PRIMARY KEY NOT NULL` (expected) and `` (actual)'),
);
});
});

View File

@ -4,6 +4,7 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:build/build.dart';
import 'package:build_test/build_test.dart';
import 'package:collection/collection.dart';
import 'package:drift_dev/src/backends/build/drift_builder.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
@ -81,15 +82,18 @@ class _GeneratesConstDataClasses extends Matcher {
final definedClasses = parsed.declarations.whereType<ClassDeclaration>();
for (final definedClass in definedClasses) {
if (expectedWithConstConstructor.contains(definedClass.name.name)) {
final constructor = definedClass.getConstructor(null);
if (expectedWithConstConstructor.contains(definedClass.name2.lexeme)) {
final constructor = definedClass.members
.whereType<ConstructorDeclaration>()
.firstWhereOrNull((e) => e.name2 == null);
if (constructor?.constKeyword == null) {
matchState['desc'] = 'Constructor ${definedClass.name.name} is not '
matchState['desc'] =
'Constructor ${definedClass.name2.lexeme} is not '
'const.';
return false;
}
remaining.remove(definedClass.name.name);
remaining.remove(definedClass.name2.lexeme);
}
}

View File

@ -80,25 +80,26 @@ class _GeneratesWithoutFinalFields extends Matcher {
final definedClasses = parsed.declarations.whereType<ClassDeclaration>();
for (final definedClass in definedClasses) {
if (expectedWithoutFinals.contains(definedClass.name.name)) {
final definedClassName = definedClass.name2.lexeme;
if (expectedWithoutFinals.contains(definedClassName)) {
for (final member in definedClass.members) {
if (member is FieldDeclaration) {
if (member.fields.isFinal) {
matchState['desc'] =
'Field ${member.fields.variables.first.name.name} in '
'${definedClass.name.name} is final.';
'Field ${member.fields.variables.first.name2.lexeme} in '
'$definedClassName is final.';
return false;
}
} else if (member is ConstructorDeclaration) {
if (member.constKeyword != null) {
matchState['desc'] = 'Constructor ${member.name?.name ?? ''} in '
'${definedClass.name.name} is constant.';
matchState['desc'] = 'Constructor ${member.name2?.lexeme ?? ''} '
'in $definedClassName is constant.';
return false;
}
}
}
remaining.remove(definedClass.name.name);
remaining.remove(definedClassName);
}
}