Custom table constraints

This commit is contained in:
Simon Binder 2019-04-19 20:29:30 +02:00
parent f15e06fbb0
commit becb78afbc
No known key found for this signature in database
GPG Key ID: B807FDF954BA00CF
12 changed files with 57 additions and 39 deletions

View File

@ -25,8 +25,8 @@ abstract class Table {
/// @override /// @override
/// Set<Column> get primaryKey => {recipe, ingredient}; /// Set<Column> get primaryKey => {recipe, ingredient};
/// ///
/// IntColumn get recipe => integer().autoIncrement()(); /// IntColumn get recipe => integer()();
/// IntColumn get ingredient => integer().autoIncrement()(); /// IntColumn get ingredient => integer()();
/// ///
/// IntColumn get amountInGrams => integer().named('amount')(); /// IntColumn get amountInGrams => integer().named('amount')();
///} ///}
@ -39,6 +39,14 @@ abstract class Table {
@visibleForOverriding @visibleForOverriding
Set<Column> get primaryKey => null; Set<Column> get primaryKey => null;
/// Custom table constraints that should be added to the table.
///
/// See also:
/// - https://www.sqlite.org/syntax/table-constraint.html, which defines what
/// table constraints are supported.
@visibleForOverriding
List<String> get customConstraints => [];
/// Use this as the body of a getter to declare a column that holds integers. /// Use this as the body of a getter to declare a column that holds integers.
/// Example (inside the body of a table class): /// Example (inside the body of a table class):
/// ``` /// ```

View File

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

View File

@ -75,7 +75,7 @@ abstract class DatabaseConnectionUser {
/// innerJoin(destination, routes.startPoint.equalsExp(destination.id)), /// innerJoin(destination, routes.startPoint.equalsExp(destination.id)),
/// ]); /// ]);
/// ``` /// ```
T alias<T, D>(TableInfo<T, D> table, String alias) { T alias<T extends Table, D>(TableInfo<T, D> table, String alias) {
return table.createAlias(alias).asDslTable; return table.createAlias(alias).asDslTable;
} }
} }
@ -87,7 +87,7 @@ mixin QueryEngine on DatabaseConnectionUser {
/// to write data into the [table] by using [InsertStatement.insert]. /// to write data into the [table] by using [InsertStatement.insert].
@protected @protected
@visibleForTesting @visibleForTesting
InsertStatement<T> into<T>(TableInfo<dynamic, T> table) => InsertStatement<T> into<T>(TableInfo<Table, T> table) =>
InsertStatement<T>(this, table); InsertStatement<T>(this, table);
/// Starts an [UpdateStatement] for the given table. You can use that /// Starts an [UpdateStatement] for the given table. You can use that
@ -95,7 +95,7 @@ mixin QueryEngine on DatabaseConnectionUser {
/// clause on that table and then use [UpdateStatement.write]. /// clause on that table and then use [UpdateStatement.write].
@protected @protected
@visibleForTesting @visibleForTesting
UpdateStatement<Tbl, ReturnType> update<Tbl, ReturnType>( UpdateStatement<Tbl, ReturnType> update<Tbl extends Table, ReturnType>(
TableInfo<Tbl, ReturnType> table) => TableInfo<Tbl, ReturnType> table) =>
UpdateStatement(this, table); UpdateStatement(this, table);
@ -104,17 +104,17 @@ mixin QueryEngine on DatabaseConnectionUser {
/// stream of data /// stream of data
@protected @protected
@visibleForTesting @visibleForTesting
SimpleSelectStatement<Table, ReturnType> select<Table, ReturnType>( SimpleSelectStatement<T, ReturnType> select<T extends Table, ReturnType>(
TableInfo<Table, ReturnType> table) { TableInfo<T, ReturnType> table) {
return SimpleSelectStatement<Table, ReturnType>(this, table); return SimpleSelectStatement<T, ReturnType>(this, table);
} }
/// Starts a [DeleteStatement] that can be used to delete rows from a table. /// Starts a [DeleteStatement] that can be used to delete rows from a table.
@protected @protected
@visibleForTesting @visibleForTesting
DeleteStatement<Table, Entity> delete<Table, Entity>( DeleteStatement<T, Entity> delete<T extends Table, Entity>(
TableInfo<Table, Entity> table) { TableInfo<T, Entity> table) {
return DeleteStatement<Table, Entity>(this, table); return DeleteStatement<T, Entity>(this, table);
} }
/// Executes a custom delete or update statement and returns the amount of /// Executes a custom delete or update statement and returns the amount of

View File

@ -53,19 +53,17 @@ class Migrator {
/// Creates the given table if it doesn't exist /// Creates the given table if it doesn't exist
Future<void> createTable(TableInfo table) async { Future<void> createTable(TableInfo table) async {
final sql = StringBuffer(); final sql = StringBuffer()
..write('CREATE TABLE IF NOT EXISTS ${table.$tableName} (');
// ignore: cascade_invocations
sql.write('CREATE TABLE IF NOT EXISTS ${table.$tableName} (');
var hasAutoIncrement = false; var hasAutoIncrement = false;
for (var i = 0; i < table.$columns.length; i++) { for (var i = 0; i < table.$columns.length; i++) {
final column = table.$columns[i]; final column = table.$columns[i];
if (column is GeneratedIntColumn && column.hasAutoIncrement) if (column is GeneratedIntColumn && column.hasAutoIncrement) {
hasAutoIncrement = true; hasAutoIncrement = true;
}
// ignore: cascade_invocations
column.writeColumnDefinition(sql); column.writeColumnDefinition(sql);
if (i < table.$columns.length - 1) sql.write(', '); if (i < table.$columns.length - 1) sql.write(', ');
@ -84,6 +82,16 @@ class Migrator {
sql.write(')'); sql.write(')');
} }
final constraints = table.asDslTable.customConstraints ?? [];
for (var i = 0; i < constraints.length; i++) {
if (i != 0) {
sql.write(', ');
}
sql.write(constraints[i]);
}
sql.write(');'); sql.write(');');
return issueCustomQuery(sql.toString()); return issueCustomQuery(sql.toString());

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

View File

@ -9,7 +9,7 @@ class InsertStatement<DataClass> {
@protected @protected
final QueryEngine database; final QueryEngine database;
@protected @protected
final TableInfo<dynamic, DataClass> table; final TableInfo<Table, DataClass> table;
bool _orReplace = false; bool _orReplace = false;

View File

@ -1,4 +1,5 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:moor/src/dsl/table.dart';
import 'package:moor/src/runtime/components/component.dart'; import 'package:moor/src/runtime/components/component.dart';
import 'package:moor/src/runtime/components/limit.dart'; import 'package:moor/src/runtime/components/limit.dart';
import 'package:moor/src/runtime/components/order_by.dart'; import 'package:moor/src/runtime/components/order_by.dart';
@ -12,10 +13,10 @@ import 'package:moor/src/runtime/structure/table_info.dart';
/// Statement that operates with data that already exists (select, delete, /// Statement that operates with data that already exists (select, delete,
/// update). /// update).
abstract class Query<Table, DataClass> { abstract class Query<T extends Table, DataClass> {
@protected @protected
QueryEngine database; QueryEngine database;
TableInfo<Table, DataClass> table; TableInfo<T, DataClass> table;
Query(this.database, this.table); Query(this.database, this.table);
@ -67,8 +68,8 @@ abstract class Query<Table, DataClass> {
} }
} }
mixin SingleTableQueryMixin<Table, DataClass> on Query<Table, DataClass> { mixin SingleTableQueryMixin<T extends Table, DataClass> on Query<T, DataClass> {
void where(Expression<bool, BoolType> filter(Table tbl)) { void where(Expression<bool, BoolType> filter(T tbl)) {
final predicate = filter(table.asDslTable); final predicate = filter(table.asDslTable);
if (whereExpr == null) { if (whereExpr == null) {
@ -119,7 +120,7 @@ mixin SingleTableQueryMixin<Table, DataClass> on Query<Table, DataClass> {
} }
} }
mixin LimitContainerMixin<T, D> on Query<T, D> { mixin LimitContainerMixin<T extends Table, D> on Query<T, D> {
/// Limits the amount of rows returned by capping them at [limit]. If [offset] /// 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 /// is provided as well, the first [offset] rows will be skipped and not
/// included in the result. /// included in the result.

View File

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

View File

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
import 'package:moor/src/runtime/components/component.dart'; import 'package:moor/src/runtime/components/component.dart';
class UpdateStatement<T, D> extends Query<T, D> class UpdateStatement<T extends Table, D> extends Query<T, D>
with SingleTableQueryMixin<T, D> { with SingleTableQueryMixin<T, D> {
UpdateStatement(QueryEngine database, TableInfo<T, D> table) UpdateStatement(QueryEngine database, TableInfo<T, D> table)
: super(database, table); : super(database, table);

View File

@ -4,7 +4,7 @@ import 'package:moor/src/runtime/expressions/variables.dart';
/// Base class for generated classes. [TableDsl] is the type specified by the /// 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], [DataClass] is the type of the data class
/// generated from the table. /// generated from the table.
mixin TableInfo<TableDsl, DataClass> { mixin TableInfo<TableDsl extends Table, DataClass> {
/// Type system sugar. Implementations are likely to inherit from both /// Type system sugar. Implementations are likely to inherit from both
/// [TableInfo] and [TableDsl] and can thus just return their instance. /// [TableInfo] and [TableDsl] and can thus just return their instance.
TableDsl get asDslTable; TableDsl get asDslTable;

View File

@ -11,7 +11,9 @@ class Todos extends Table {
DateTimeColumn get targetDate => dateTime().nullable()(); DateTimeColumn get targetDate => dateTime().nullable()();
IntColumn get category => integer().nullable()(); IntColumn get category => integer()
.nullable()
.customConstraint('NULLABLE REFERENCES categories(id)')();
} }
@DataClassName('Category') @DataClassName('Category')

View File

@ -126,11 +126,8 @@ class $TodosTable extends Todos with TableInfo<$TodosTable, TodoEntry> {
GeneratedIntColumn _constructCategory() { GeneratedIntColumn _constructCategory() {
var cName = 'category'; var cName = 'category';
if (_alias != null) cName = '$_alias.$cName'; if (_alias != null) cName = '$_alias.$cName';
return GeneratedIntColumn( return GeneratedIntColumn('category', $tableName, true,
'category', $customConstraints: 'NULLABLE REFERENCES categories(id)');
$tableName,
true,
);
} }
@override @override