Generate update companions for data classes

This commit is contained in:
Simon Binder 2019-06-21 09:14:56 +02:00
parent 214b5fd978
commit ec2592203c
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
9 changed files with 338 additions and 10 deletions

View File

@ -59,6 +59,28 @@ class Category extends DataClass {
(other is Category && other.id == id && other.description == description);
}
class CategoriesCompanion implements UpdateCompanion<Category> {
final Value<int> id;
final Value<String> description;
const CategoriesCompanion({
this.id = Value.absent(),
this.description = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
case 1:
return description.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $CategoriesTable extends Categories
with TableInfo<$CategoriesTable, Category> {
final GeneratedDatabase _db;
@ -203,6 +225,36 @@ class Recipe extends DataClass {
other.category == category);
}
class RecipesCompanion implements UpdateCompanion<Recipe> {
final Value<int> id;
final Value<String> title;
final Value<String> instructions;
final Value<int> category;
const RecipesCompanion({
this.id = Value.absent(),
this.title = Value.absent(),
this.instructions = Value.absent(),
this.category = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
case 1:
return title.present;
case 2:
return instructions.present;
case 3:
return category.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $RecipesTable extends Recipes with TableInfo<$RecipesTable, Recipe> {
final GeneratedDatabase _db;
final String _alias;
@ -366,6 +418,32 @@ class Ingredient extends DataClass {
other.caloriesPer100g == caloriesPer100g);
}
class IngredientsCompanion implements UpdateCompanion<Ingredient> {
final Value<int> id;
final Value<String> name;
final Value<int> caloriesPer100g;
const IngredientsCompanion({
this.id = Value.absent(),
this.name = Value.absent(),
this.caloriesPer100g = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
case 1:
return name.present;
case 2:
return caloriesPer100g.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $IngredientsTable extends Ingredients
with TableInfo<$IngredientsTable, Ingredient> {
final GeneratedDatabase _db;
@ -520,6 +598,33 @@ class IngredientInRecipe extends DataClass {
other.amountInGrams == amountInGrams);
}
class IngredientInRecipesCompanion
implements UpdateCompanion<IngredientInRecipe> {
final Value<int> recipe;
final Value<int> ingredient;
final Value<int> amountInGrams;
const IngredientInRecipesCompanion({
this.recipe = Value.absent(),
this.ingredient = Value.absent(),
this.amountInGrams = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return recipe.present;
case 1:
return ingredient.present;
case 2:
return amountInGrams.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $IngredientInRecipesTable extends IngredientInRecipes
with TableInfo<$IngredientInRecipesTable, IngredientInRecipe> {
final GeneratedDatabase _db;

View File

@ -18,6 +18,7 @@ export 'package:moor/src/runtime/expressions/user_api.dart';
export 'package:moor/src/runtime/executor/transactions.dart';
export 'package:moor/src/runtime/statements/query.dart';
export 'package:moor/src/runtime/statements/select.dart';
export 'package:moor/src/runtime/statements/update.dart';
export 'package:moor/src/runtime/statements/insert.dart';
export 'package:moor/src/runtime/statements/delete.dart';
export 'package:moor/src/runtime/structure/columns.dart';

View File

@ -1,10 +1,11 @@
import 'dart:convert';
import 'package:meta/meta.dart';
import 'package:moor/moor.dart';
/// A common supertype for all data classes generated by moor. Data classes are
/// immutable structures that represent a single row.
abstract class DataClass {
/// immutable structures that represent a single row in a database table.
abstract class DataClass implements UpdateCompanion {
const DataClass();
/// Converts this object into a representation that can be encoded with
@ -20,6 +21,9 @@ abstract class DataClass {
return json.encode(toJson(serializer: serializer));
}
@override
bool isValuePresent(int index) => true;
/// Used internally be generated code
@protected
static dynamic parseJson(String jsonString) {
@ -27,6 +31,31 @@ abstract class DataClass {
}
}
/// An update companion for a [DataClass] which is used to write data into a
/// database using [InsertStatement.insert] or [UpdateStatement.write].
///
/// See also:
/// - https://github.com/simolus3/moor/issues/25
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.
bool isValuePresent(int index);
}
/// A wrapper around arbitrary data [T] to indicate presence or absence
/// explicitly.
class Value<T> {
final bool present;
final T value;
const Value.use(this.value) : present = true;
const Value.absent()
: value = null,
present = false;
}
/// Serializer responsible for mapping atomic types from and to json.
abstract class ValueSerializer {
const ValueSerializer();

View File

@ -88,7 +88,7 @@ mixin QueryEngine on DatabaseConnectionUser {
/// to write data into the [table] by using [InsertStatement.insert].
@protected
@visibleForTesting
InsertStatement<T> into<T>(TableInfo<Table, T> table) =>
InsertStatement<T> into<T extends DataClass>(TableInfo<Table, T> table) =>
InsertStatement<T>(this, table);
/// Starts an [UpdateStatement] for the given table. You can use that

View File

@ -6,11 +6,11 @@ import 'package:moor/src/runtime/components/component.dart';
import 'update.dart';
class InsertStatement<DataClass> {
class InsertStatement<D extends DataClass> {
@protected
final QueryEngine database;
@protected
final TableInfo<Table, DataClass> table;
final TableInfo<Table, D> table;
InsertStatement(this.database, this.table);
@ -26,7 +26,7 @@ class InsertStatement<DataClass> {
///
/// If the table contains an auto-increment column, the generated value will
/// be returned.
Future<int> insert(DataClass entity, {bool orReplace = false}) async {
Future<int> insert(D entity, {bool orReplace = false}) async {
_validateIntegrity(entity);
final ctx = _createContext(entity, orReplace);
@ -45,7 +45,7 @@ class InsertStatement<DataClass> {
/// When a row with the same primary or unique key already exists in the
/// database, the insert will fail. Use [orReplace] to replace rows that
/// already exist.
Future<void> insertAll(List<DataClass> rows, {bool orReplace = false}) async {
Future<void> insertAll(List<D> rows, {bool orReplace = false}) async {
final statements = <String, List<GenerationContext>>{};
// Not every insert has the same sql, as fields which are set to null are
@ -78,11 +78,11 @@ class InsertStatement<DataClass> {
///
/// However, if no such row exists, a new row will be written instead.
@Deprecated('Use insert with orReplace: true instead')
Future<void> insertOrReplace(DataClass entity) async {
Future<void> insertOrReplace(D entity) async {
return await insert(entity, orReplace: true);
}
GenerationContext _createContext(DataClass entry, bool replace) {
GenerationContext _createContext(D entry, bool replace) {
final map = table.entityToSql(entry)
..removeWhere((_, value) => value == null);
@ -111,7 +111,7 @@ class InsertStatement<DataClass> {
return ctx;
}
void _validateIntegrity(DataClass d) {
void _validateIntegrity(D d) {
if (d == null) {
throw InvalidDataException(
'Cannot writee null row into ${table.$tableName}');

View File

@ -97,6 +97,40 @@ class TodoEntry extends DataClass {
other.category == category);
}
class TodosTableCompanion implements UpdateCompanion<TodoEntry> {
final Value<int> id;
final Value<String> title;
final Value<String> content;
final Value<DateTime> targetDate;
final Value<int> category;
const TodosTableCompanion({
this.id = Value.absent(),
this.title = Value.absent(),
this.content = Value.absent(),
this.targetDate = Value.absent(),
this.category = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
case 1:
return title.present;
case 2:
return content.present;
case 3:
return targetDate.present;
case 4:
return category.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $TodosTableTable extends TodosTable
with TableInfo<$TodosTableTable, TodoEntry> {
final GeneratedDatabase _db;
@ -271,6 +305,28 @@ class Category extends DataClass {
(other is Category && other.id == id && other.description == description);
}
class CategoriesCompanion implements UpdateCompanion<Category> {
final Value<int> id;
final Value<String> description;
const CategoriesCompanion({
this.id = Value.absent(),
this.description = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
case 1:
return description.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $CategoriesTable extends Categories
with TableInfo<$CategoriesTable, Category> {
final GeneratedDatabase _db;
@ -434,6 +490,40 @@ class User extends DataClass {
other.creationTime == creationTime);
}
class UsersCompanion implements UpdateCompanion<User> {
final Value<int> id;
final Value<String> name;
final Value<bool> isAwesome;
final Value<Uint8List> profilePicture;
final Value<DateTime> creationTime;
const UsersCompanion({
this.id = Value.absent(),
this.name = Value.absent(),
this.isAwesome = Value.absent(),
this.profilePicture = Value.absent(),
this.creationTime = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return id.present;
case 1:
return name.present;
case 2:
return isAwesome.present;
case 3:
return profilePicture.present;
case 4:
return creationTime.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $UsersTable extends Users with TableInfo<$UsersTable, User> {
final GeneratedDatabase _db;
final String _alias;
@ -602,6 +692,28 @@ class SharedTodo extends DataClass {
(other is SharedTodo && other.todo == todo && other.user == user);
}
class SharedTodosCompanion implements UpdateCompanion<SharedTodo> {
final Value<int> todo;
final Value<int> user;
const SharedTodosCompanion({
this.todo = Value.absent(),
this.user = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return todo.present;
case 1:
return user.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $SharedTodosTable extends SharedTodos
with TableInfo<$SharedTodosTable, SharedTodo> {
final GeneratedDatabase _db;
@ -731,6 +843,28 @@ class TableWithoutPKData extends DataClass {
other.someFloat == someFloat);
}
class TableWithoutPKCompanion implements UpdateCompanion<TableWithoutPKData> {
final Value<int> notReallyAnId;
final Value<double> someFloat;
const TableWithoutPKCompanion({
this.notReallyAnId = Value.absent(),
this.someFloat = Value.absent(),
});
@override
bool isValuePresent(int index) {
switch (index) {
case 0:
return notReallyAnId.present;
case 1:
return someFloat.present;
default:
throw ArgumentError(
'Hit an invalid state while serializing data. Did you run the build step?');
}
;
}
}
class $TableWithoutPKTable extends TableWithoutPK
with TableInfo<$TableWithoutPKTable, TableWithoutPKData> {
final GeneratedDatabase _db;

View File

@ -10,6 +10,7 @@ class SpecifiedTable {
final String dartTypeName;
String get tableInfoName => tableInfoNameForTableClass(fromClass);
String get updateCompanionName => _updateCompanionName(fromClass);
/// The set of primary keys, if they have been explicitly defined by
/// overriding `primaryKey` in the table class. `null` if the primary key has
@ -26,3 +27,6 @@ class SpecifiedTable {
String tableInfoNameForTableClass(ClassElement fromClass) =>
'\$${fromClass.name}Table';
String _updateCompanionName(ClassElement fromClass) =>
'${fromClass.name}Companion';

View File

@ -2,6 +2,7 @@ import 'package:moor_generator/src/model/specified_column.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/options.dart';
import 'package:moor_generator/src/writer/data_class_writer.dart';
import 'package:moor_generator/src/writer/update_companion_writer.dart';
import 'package:moor_generator/src/writer/utils.dart';
class TableWriter {
@ -17,6 +18,7 @@ class TableWriter {
void writeDataClass(StringBuffer buffer) {
DataClassWriter(table, options).writeInto(buffer);
UpdateCompanionWriter(table, options).writeInto(buffer);
}
void writeTableInfoClass(StringBuffer buffer) {

View File

@ -0,0 +1,53 @@
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/options.dart';
class UpdateCompanionWriter {
final SpecifiedTable table;
final MoorOptions options;
UpdateCompanionWriter(this.table, this.options);
void writeInto(StringBuffer buffer) {
buffer.write('class ${table.updateCompanionName} '
'implements UpdateCompanion<${table.dartTypeName}> {\n');
_writeFields(buffer);
_writeConstructor(buffer);
_writeIsPresentOverride(buffer);
buffer.write('}\n');
}
void _writeFields(StringBuffer buffer) {
for (var column in table.columns) {
buffer.write('final Value<${column.dartTypeName}>'
' ${column.dartGetterName};\n');
}
}
void _writeConstructor(StringBuffer buffer) {
buffer.write('const ${table.updateCompanionName}({');
for (var column in table.columns) {
buffer.write('this.${column.dartGetterName} = Value.absent(),');
}
buffer.write('});\n');
}
void _writeIsPresentOverride(StringBuffer buffer) {
buffer
..write('@override\nbool isValuePresent(int index) {\n')
..write('switch (index) {');
for (var i = 0; i < table.columns.length; i++) {
final getterName = table.columns[i].dartGetterName;
buffer.write('case $i: return $getterName.present;\n');
}
buffer
..write('default: throw ArgumentError('
"'Hit an invalid state while serializing data. Did you run the build "
"step?');")
..write('};}\n');
}
}