Constraints on type parameters for data classes

This commit is contained in:
Simon Binder 2019-06-21 10:27:14 +02:00
parent 6ac9109c0a
commit ecf6740cb7
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
13 changed files with 98 additions and 120 deletions

View File

@ -1,3 +1,7 @@
## 1.5.0
- More consistent and reliable migration and opening callbacks
- TODO: Explain new companions
## 1.4.0
- Added the `RealColumn`, which stores floating point values
- Better configuration for the serializer with the `JsonKey` annotation and the ability to

View File

@ -7,7 +7,7 @@ part of 'example.dart';
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps
class Category extends DataClass with DelegatingCompanionMixin<Category> {
class Category extends DataClass {
final int id;
final String description;
Category({this.id, this.description});
@ -39,7 +39,7 @@ class Category extends DataClass with DelegatingCompanionMixin<Category> {
}
@override
UpdateCompanion<Category> createCompanion(bool nullToAbsent) {
CategoriesCompanion createCompanion(bool nullToAbsent) {
return CategoriesCompanion(
id: id == null && nullToAbsent ? const Value.absent() : Value.use(id),
description: description == null && nullToAbsent
@ -77,7 +77,7 @@ class CategoriesCompanion implements UpdateCompanion<Category> {
this.description = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
@ -160,7 +160,7 @@ class $CategoriesTable extends Categories
}
}
class Recipe extends DataClass with DelegatingCompanionMixin<Recipe> {
class Recipe extends DataClass {
final int id;
final String title;
final String instructions;
@ -202,7 +202,7 @@ class Recipe extends DataClass with DelegatingCompanionMixin<Recipe> {
}
@override
UpdateCompanion<Recipe> createCompanion(bool nullToAbsent) {
RecipesCompanion createCompanion(bool nullToAbsent) {
return RecipesCompanion(
id: id == null && nullToAbsent ? const Value.absent() : Value.use(id),
title: title == null && nullToAbsent
@ -262,7 +262,7 @@ class RecipesCompanion implements UpdateCompanion<Recipe> {
this.category = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
@ -380,7 +380,7 @@ class $RecipesTable extends Recipes with TableInfo<$RecipesTable, Recipe> {
}
}
class Ingredient extends DataClass with DelegatingCompanionMixin<Ingredient> {
class Ingredient extends DataClass {
final int id;
final String name;
final int caloriesPer100g;
@ -416,7 +416,7 @@ class Ingredient extends DataClass with DelegatingCompanionMixin<Ingredient> {
}
@override
UpdateCompanion<Ingredient> createCompanion(bool nullToAbsent) {
IngredientsCompanion createCompanion(bool nullToAbsent) {
return IngredientsCompanion(
id: id == null && nullToAbsent ? const Value.absent() : Value.use(id),
name:
@ -464,7 +464,7 @@ class IngredientsCompanion implements UpdateCompanion<Ingredient> {
this.caloriesPer100g = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
@ -567,8 +567,7 @@ class $IngredientsTable extends Ingredients
}
}
class IngredientInRecipe extends DataClass
with DelegatingCompanionMixin<IngredientInRecipe> {
class IngredientInRecipe extends DataClass {
final int recipe;
final int ingredient;
final int amountInGrams;
@ -605,7 +604,7 @@ class IngredientInRecipe extends DataClass
}
@override
UpdateCompanion<IngredientInRecipe> createCompanion(bool nullToAbsent) {
IngredientInRecipesCompanion createCompanion(bool nullToAbsent) {
return IngredientInRecipesCompanion(
recipe: recipe == null && nullToAbsent
? const Value.absent()
@ -660,7 +659,7 @@ class IngredientInRecipesCompanion
this.amountInGrams = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return recipe.present;

View File

@ -10,7 +10,7 @@ const Map<JoinType, String> _joinKeywords = {
JoinType.cross: 'CROSS',
};
class Join<T extends Table, D> extends Component {
class Join<T extends Table, D extends DataClass> extends Component {
final JoinType type;
final TableInfo<T, D> table;
final Expression<bool, BoolType> on;
@ -35,7 +35,7 @@ class Join<T extends Table, D> extends Component {
///
/// See also:
/// - http://www.sqlitetutorial.net/sqlite-inner-join/
Join innerJoin<T extends Table, D>(
Join innerJoin<T extends Table, D extends DataClass>(
TableInfo<T, D> other, Expression<bool, BoolType> on) {
return Join(JoinType.inner, other, on);
}
@ -45,7 +45,7 @@ Join innerJoin<T extends Table, D>(
///
/// See also:
/// - http://www.sqlitetutorial.net/sqlite-left-join/
Join leftOuterJoin<T extends Table, D>(
Join leftOuterJoin<T extends Table, D extends DataClass>(
TableInfo<T, D> other, Expression<bool, BoolType> on) {
return Join(JoinType.leftOuter, other, on);
}

View File

@ -26,6 +26,9 @@ abstract class DataClass {
static dynamic parseJson(String jsonString) {
return json.decode(jsonString);
}
/// Used internally by moor.
UpdateCompanion createCompanion(bool nullToAbsent);
}
/// An update companion for a [DataClass] which is used to write data into a
@ -37,37 +40,8 @@ abstract class UpdateCompanion<D extends DataClass> {
/// Used internally by moor.
///
/// Returns true if the column at the position [index] has been explicitly
/// set to a value. The [assumeNullAsAbsent] parameter exists for backwards
/// compatibility reasons, as [DataClass] implements [UpdateCompanion] but
/// doesn't have value fields.
bool isValuePresent(int index, bool assumeNullAsAbsent);
}
/// Used internally by moor for generated code.
///
/// Exists for backwards compatibility so that a [DataClass] can implement
/// [UpdateCompanion].
mixin DelegatingCompanionMixin<D extends DataClass>
implements UpdateCompanion<D> {
UpdateCompanion<D> _absent;
UpdateCompanion<D> _present;
@visibleForOverriding
UpdateCompanion<D> createCompanion(bool nullToAbsent);
UpdateCompanion<D> _resolveDelegate(bool nullToAbsent) {
if (nullToAbsent) {
return _absent ??= createCompanion(true);
} else {
return _present ??= createCompanion(false);
}
}
@override
bool isValuePresent(int index, bool assumeNullAsAbsent) {
final delegate = _resolveDelegate(assumeNullAsAbsent);
return delegate.isValuePresent(index, assumeNullAsAbsent);
}
/// set to a value.
bool isValuePresent(int index);
}
/// A wrapper around arbitrary data [T] to indicate presence or absence

View File

@ -76,7 +76,8 @@ abstract class DatabaseConnectionUser {
/// innerJoin(destination, routes.startPoint.equalsExp(destination.id)),
/// ]);
/// ```
T alias<T extends Table, D>(TableInfo<T, D> table, String alias) {
T alias<T extends Table, D extends DataClass>(
TableInfo<T, D> table, String alias) {
return table.createAlias(alias).asDslTable;
}
}
@ -96,8 +97,8 @@ mixin QueryEngine on DatabaseConnectionUser {
/// clause on that table and then use [UpdateStatement.write].
@protected
@visibleForTesting
UpdateStatement<Tbl, ReturnType> update<Tbl extends Table, ReturnType>(
TableInfo<Tbl, ReturnType> table) =>
UpdateStatement<Tbl, R> update<Tbl extends Table, R extends DataClass>(
TableInfo<Tbl, R> table) =>
UpdateStatement(this, table);
/// Starts a query on the given table. Queries can be limited with an limit
@ -105,17 +106,17 @@ mixin QueryEngine on DatabaseConnectionUser {
/// stream of data
@protected
@visibleForTesting
SimpleSelectStatement<T, ReturnType> select<T extends Table, ReturnType>(
TableInfo<T, ReturnType> table) {
return SimpleSelectStatement<T, ReturnType>(this, table);
SimpleSelectStatement<T, R> select<T extends Table, R extends DataClass>(
TableInfo<T, R> table) {
return SimpleSelectStatement<T, R>(this, table);
}
/// Starts a [DeleteStatement] that can be used to delete rows from a table.
@protected
@visibleForTesting
DeleteStatement<T, Entity> delete<T extends Table, Entity>(
TableInfo<T, Entity> table) {
return DeleteStatement<T, Entity>(this, table);
DeleteStatement<T, D> delete<T extends Table, D extends DataClass>(
TableInfo<T, D> table) {
return DeleteStatement<T, D>(this, table);
}
/// Executes a custom delete or update statement and returns the amount of

View File

@ -5,7 +5,7 @@ import 'package:moor/src/runtime/components/component.dart';
import 'package:moor/src/runtime/statements/query.dart';
import 'package:moor/src/runtime/structure/table_info.dart';
class DeleteStatement<T extends Table, D> extends Query<T, D>
class DeleteStatement<T extends Table, D extends DataClass> extends Query<T, D>
with SingleTableQueryMixin<T, D> {
/// This constructor should be called by [GeneratedDatabase.delete] for you.
DeleteStatement(QueryEngine database, TableInfo<T, D> table)

View File

@ -1,23 +1,19 @@
import 'package:meta/meta.dart';
import 'package:moor/src/dsl/table.dart';
import 'package:moor/moor.dart';
import 'package:moor/src/runtime/components/component.dart';
import 'package:moor/src/runtime/components/limit.dart';
import 'package:moor/src/runtime/components/order_by.dart';
import 'package:moor/src/runtime/components/where.dart';
import 'package:moor/src/runtime/database.dart';
import 'package:moor/src/runtime/expressions/bools.dart';
import 'package:moor/src/runtime/expressions/custom.dart';
import 'package:moor/src/runtime/expressions/expression.dart';
import 'package:moor/src/types/sql_types.dart';
import 'package:moor/src/runtime/structure/table_info.dart';
import 'package:moor/src/utils/single_transformer.dart';
/// Statement that operates with data that already exists (select, delete,
/// update).
abstract class Query<T extends Table, DataClass> {
abstract class Query<T extends Table, D extends DataClass> {
@protected
QueryEngine database;
TableInfo<T, DataClass> table;
TableInfo<T, D> table;
Query(this.database, this.table);
@ -110,7 +106,8 @@ abstract class Selectable<T> {
}
}
mixin SingleTableQueryMixin<T extends Table, DataClass> on Query<T, DataClass> {
mixin SingleTableQueryMixin<T extends Table, D extends DataClass>
on Query<T, D> {
void where(Expression<bool, BoolType> filter(T tbl)) {
final predicate = filter(table.asDslTable);
@ -123,7 +120,7 @@ mixin SingleTableQueryMixin<T extends Table, DataClass> on Query<T, DataClass> {
/// Applies a [where] statement so that the row with the same primary key as
/// [d] will be matched.
void whereSamePrimaryKey(DataClass d) {
void whereSamePrimaryKey(D d) {
assert(
table.$primaryKey != null && table.$primaryKey.isNotEmpty,
'When using Query.whereSamePrimaryKey, which is also called from '
@ -162,7 +159,7 @@ mixin SingleTableQueryMixin<T extends Table, DataClass> on Query<T, DataClass> {
}
}
mixin LimitContainerMixin<T extends Table, D> on Query<T, D> {
mixin LimitContainerMixin<T extends Table, D extends DataClass> on Query<T, D> {
/// Limits the amount of rows returned by capping them at [limit]. If [offset]
/// is provided as well, the first [offset] rows will be skipped and not
/// included in the result.

View File

@ -13,7 +13,7 @@ import 'package:moor/src/runtime/structure/table_info.dart';
typedef OrderingTerm OrderClauseGenerator<T>(T tbl);
class JoinedSelectStatement<FirstT extends Table, FirstD>
class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
extends Query<FirstT, FirstD>
with LimitContainerMixin, Selectable<TypedResult> {
JoinedSelectStatement(
@ -142,7 +142,8 @@ class JoinedSelectStatement<FirstT extends Table, FirstD>
}
/// A select statement that doesn't use joins
class SimpleSelectStatement<T extends Table, D> extends Query<T, D>
class SimpleSelectStatement<T extends Table, D extends DataClass>
extends Query<T, D>
with SingleTableQueryMixin<T, D>, LimitContainerMixin<T, D>, Selectable<D> {
SimpleSelectStatement(QueryEngine database, TableInfo<T, D> table)
: super(database, table);
@ -316,7 +317,7 @@ class TypedResult {
final QueryRow rawData;
/// Reads all data that belongs to the given [table] from this row.
D readTable<T extends Table, D>(TableInfo<T, D> table) {
D readTable<T extends Table, D extends DataClass>(TableInfo<T, D> table) {
return _parsedData[table] as D;
}
}

View File

@ -83,22 +83,29 @@ abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
}
/// Checks whether the given value fits into this column. The default
/// implementation checks whether the value is not null, as null values are
/// only allowed for updates or if the column is nullable.
/// If [duringInsert] is true, the method should check whether the value is
/// suitable for a new row that is being inserted. If it's false, we the
/// method should check whether the value is valid for an update. Null values
/// should always be accepted for updates, as the describe a value that should
/// not be replaced.
VerificationResult isAcceptableValue(
T value, bool duringInsert, VerificationMeta meta) {
final nullOk = !duringInsert || $nullable || defaultValue != null;
/// implementation only checks for nullability, but subclasses might enforce
/// additional checks. For instance, the [GeneratedTextColumn] can verify
/// that a text has a certain length.
///
/// Note: The behavior of this method was changed in moor 1.5. Before, null
/// values were interpreted as an absent value during updates or if the
/// [defaultValue] is set. Verification was skipped for absent values.
/// This is no longer the case, all null values are assumed to be an sql
/// `NULL`.
VerificationResult isAcceptableValue(T value, VerificationMeta meta) {
final nullOk = $nullable;
if (!nullOk && value == null) {
return _invalidNull;
} else {
return const VerificationResult.success();
}
}
/// Returns true if this column needs to be set when writing a new row into
/// a table.
bool get isRequired {
return !$nullable && defaultValue == null;
}
}
class GeneratedTextColumn extends GeneratedColumn<String, StringType>
@ -125,10 +132,9 @@ class GeneratedTextColumn extends GeneratedColumn<String, StringType>
final String typeName = 'VARCHAR';
@override
VerificationResult isAcceptableValue(
String value, bool duringInsert, VerificationMeta meta) {
VerificationResult isAcceptableValue(String value, VerificationMeta meta) {
// handle nullability check in common column
if (value == null) return super.isAcceptableValue(null, duringInsert, meta);
if (value == null) return super.isAcceptableValue(null, meta);
final length = value.length;
if (minTextLength != null && minTextLength > length) {
@ -189,12 +195,8 @@ class GeneratedIntColumn extends GeneratedColumn<int, IntType>
}
@override
VerificationResult isAcceptableValue(
int value, bool duringInsert, VerificationMeta meta) {
if (hasAutoIncrement) {
return const VerificationResult.success();
}
return super.isAcceptableValue(value, duringInsert, meta);
bool get isRequired {
return !hasAutoIncrement && super.isRequired;
}
}

View File

@ -2,9 +2,9 @@ import 'package:moor/moor.dart';
import 'package:moor/src/runtime/expressions/variables.dart';
/// Base class for generated classes. [TableDsl] is the type specified by the
/// user that extends [Table], [DataClass] is the type of the data class
/// user that extends [Table], [D] is the type of the data class
/// generated from the table.
mixin TableInfo<TableDsl extends Table, DataClass> {
mixin TableInfo<TableDsl extends Table, D extends DataClass> {
/// Type system sugar. Implementations are likely to inherit from both
/// [TableInfo] and [TableDsl] and can thus just return their instance.
TableDsl get asDslTable;
@ -36,22 +36,23 @@ mixin TableInfo<TableDsl extends Table, DataClass> {
/// Validates that the given entity can be inserted into this table, meaning
/// that it respects all constraints (nullability, text length, etc.).
/// During insertion mode, fields that have a default value or are
/// auto-incrementing are allowed to be null as they will be set by sqlite.
VerificationContext validateIntegrity(DataClass instance, bool isInserting);
///
/// The [nullsAreAbsent] parameter exists for backwards-compatibility. See the
/// discussion in the changelog at version 1.5 for details.
VerificationContext validateIntegrity(
UpdateCompanion<D> instance, bool nullsAreAbsent);
/// Maps the given data class to a [Map] that can be inserted into sql. The
/// keys should represent the column name in sql, the values the corresponding
/// values of the field.
///
/// If [includeNulls] is true, fields of the [DataClass] that are null will be
/// If [includeNulls] is true, fields of the [D] that are null will be
/// written as a [Variable] with a value of null. Otherwise, these fields will
/// not be written into the map at all.
Map<String, Variable> entityToSql(DataClass instance,
{bool includeNulls = false});
Map<String, Variable> entityToSql(D instance, {bool includeNulls = false});
/// Maps the given row returned by the database into the fitting data class.
DataClass map(Map<String, dynamic> data, {String tablePrefix});
D map(Map<String, dynamic> data, {String tablePrefix});
TableInfo<TableDsl, DataClass> createAlias(String alias);
TableInfo<TableDsl, D> createAlias(String alias);
}

View File

@ -7,7 +7,7 @@ part of 'todos.dart';
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps
class TodoEntry extends DataClass with DelegatingCompanionMixin<TodoEntry> {
class TodoEntry extends DataClass {
final int id;
final String title;
final String content;
@ -56,7 +56,7 @@ class TodoEntry extends DataClass with DelegatingCompanionMixin<TodoEntry> {
}
@override
UpdateCompanion<TodoEntry> createCompanion(bool nullToAbsent) {
TodosTableCompanion createCompanion(bool nullToAbsent) {
return TodosTableCompanion(
id: id == null && nullToAbsent ? const Value.absent() : Value.use(id),
title: title == null && nullToAbsent
@ -130,7 +130,7 @@ class TodosTableCompanion implements UpdateCompanion<TodoEntry> {
this.category = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
@ -271,7 +271,7 @@ class $TodosTableTable extends TodosTable
}
}
class Category extends DataClass with DelegatingCompanionMixin<Category> {
class Category extends DataClass {
final int id;
final String description;
Category({this.id, this.description});
@ -303,7 +303,7 @@ class Category extends DataClass with DelegatingCompanionMixin<Category> {
}
@override
UpdateCompanion<Category> createCompanion(bool nullToAbsent) {
CategoriesCompanion createCompanion(bool nullToAbsent) {
return CategoriesCompanion(
id: id == null && nullToAbsent ? const Value.absent() : Value.use(id),
description: description == null && nullToAbsent
@ -341,7 +341,7 @@ class CategoriesCompanion implements UpdateCompanion<Category> {
this.description = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
@ -421,7 +421,7 @@ class $CategoriesTable extends Categories
}
}
class User extends DataClass with DelegatingCompanionMixin<User> {
class User extends DataClass {
final int id;
final String name;
final bool isAwesome;
@ -475,7 +475,7 @@ class User extends DataClass with DelegatingCompanionMixin<User> {
}
@override
UpdateCompanion<User> createCompanion(bool nullToAbsent) {
UsersCompanion createCompanion(bool nullToAbsent) {
return UsersCompanion(
id: id == null && nullToAbsent ? const Value.absent() : Value.use(id),
name:
@ -549,7 +549,7 @@ class UsersCompanion implements UpdateCompanion<User> {
this.creationTime = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
@ -686,7 +686,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
}
}
class SharedTodo extends DataClass with DelegatingCompanionMixin<SharedTodo> {
class SharedTodo extends DataClass {
final int todo;
final int user;
SharedTodo({this.todo, this.user});
@ -716,7 +716,7 @@ class SharedTodo extends DataClass with DelegatingCompanionMixin<SharedTodo> {
}
@override
UpdateCompanion<SharedTodo> createCompanion(bool nullToAbsent) {
SharedTodosCompanion createCompanion(bool nullToAbsent) {
return SharedTodosCompanion(
todo:
todo == null && nullToAbsent ? const Value.absent() : Value.use(todo),
@ -754,7 +754,7 @@ class SharedTodosCompanion implements UpdateCompanion<SharedTodo> {
this.user = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return todo.present;
@ -838,8 +838,7 @@ class $SharedTodosTable extends SharedTodos
}
}
class TableWithoutPKData extends DataClass
with DelegatingCompanionMixin<TableWithoutPKData> {
class TableWithoutPKData extends DataClass {
final int notReallyAnId;
final double someFloat;
TableWithoutPKData({this.notReallyAnId, this.someFloat});
@ -873,7 +872,7 @@ class TableWithoutPKData extends DataClass
}
@override
UpdateCompanion<TableWithoutPKData> createCompanion(bool nullToAbsent) {
TableWithoutPKCompanion createCompanion(bool nullToAbsent) {
return TableWithoutPKCompanion(
notReallyAnId: notReallyAnId == null && nullToAbsent
? const Value.absent()
@ -917,7 +916,7 @@ class TableWithoutPKCompanion implements UpdateCompanion<TableWithoutPKData> {
this.someFloat = const Value.absent(),
});
@override
bool isValuePresent(int index, bool _) {
bool isValuePresent(int index) {
switch (index) {
case 0:
return notReallyAnId.present;

View File

@ -12,8 +12,7 @@ class DataClassWriter {
DataClassWriter(this.table, this.options);
void writeInto(StringBuffer buffer) {
buffer.write('class ${table.dartTypeName} extends DataClass '
'with DelegatingCompanionMixin<${table.dartTypeName}> {\n');
buffer.write('class ${table.dartTypeName} extends DataClass {\n');
// write individual fields
for (var column in table.columns) {
@ -220,9 +219,10 @@ class DataClassWriter {
void _writeCompanionOverride(StringBuffer buffer) {
// UpdateCompanion<D> createCompanion(bool nullToAbsent);
buffer.write('@override\nUpdateCompanion<${table.dartTypeName}> '
final companionClass = table.updateCompanionName;
buffer.write('@override\n$companionClass '
'createCompanion(bool nullToAbsent) {\n'
'return ${table.updateCompanionName}(');
'return $companionClass(');
for (var column in table.columns) {
final getter = column.dartGetterName;

View File

@ -36,7 +36,7 @@ class UpdateCompanionWriter {
void _writeIsPresentOverride(StringBuffer buffer) {
buffer
..write('@override\nbool isValuePresent(int index, bool _) {\n')
..write('@override\nbool isValuePresent(int index) {\n')
..write('switch (index) {');
for (var i = 0; i < table.columns.length; i++) {