mirror of https://github.com/AMT-Cheif/drift.git
Implement custom column constraints
This commit is contained in:
parent
2e96ef1d56
commit
81fe2e7feb
|
@ -316,7 +316,6 @@ let me know by creating an issue!
|
|||
- Validation
|
||||
- Table joins
|
||||
- Bulk inserts
|
||||
- Custom column constraints
|
||||
- When inserts / updates fail, explain why that happened
|
||||
### Interesting stuff that would be nice to have
|
||||
Implementing this will very likely result in backwards-incompatible changes.
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:moor/src/runtime/expressions/expression.dart';
|
||||
import 'package:moor/src/runtime/expressions/comparable.dart';
|
||||
import 'package:moor/src/types/sql_types.dart';
|
||||
|
||||
abstract class Column<T, S extends SqlType<T>> extends Expression<T, S> {}
|
||||
|
||||
|
@ -47,6 +46,33 @@ class ColumnBuilder<Builder, ResultColumn> {
|
|||
/// primary key. Columns are non-null by default.
|
||||
Builder nullable() => null;
|
||||
|
||||
/// Tells moor to write a custom constraint after this column definition when
|
||||
/// writing this column, for instance in a CREATE TABLE statement.
|
||||
///
|
||||
/// When no custom constraint is set, columns will be written like this:
|
||||
/// `name TYPE NULLABILITY NATIVE_CONSTRAINTS`. Native constraints are used to
|
||||
/// enforce that booleans are either 0 or 1 (e.g.
|
||||
/// `field BOOLEAN NOT NULL CHECK (field in (0, 1)`). Auto-Increment
|
||||
/// columns also make use of the native constraints.
|
||||
/// If [customConstraint] has been called, the nullability information and
|
||||
/// native constraints will never be written. Instead, they will be replaced
|
||||
/// with the [constraint]. For example, if you call
|
||||
/// `customConstraint('UNIQUE')` on an [IntColumn] named "votes", the
|
||||
/// generated column definition will be `votes INTEGER UNIQUE`. Notice how the
|
||||
/// nullability information is lost - you'll have to include it in
|
||||
/// [constraint] if that is desired.
|
||||
///
|
||||
/// This can be used to implement constraints that moor does not (yet)
|
||||
/// support (e.g. unique keys, etc.). If you've found a common use-case for
|
||||
/// this, it should be considered a limitation of moor itself. Please feel
|
||||
/// free to open an issue at https://github.com/simolus3/moor/issues/new to
|
||||
/// report that.
|
||||
///
|
||||
/// See also:
|
||||
/// - https://www.sqlite.org/syntax/column-constraint.html
|
||||
/// - [GeneratedColumn.writeCustomConstraints]
|
||||
Builder customConstraint(String constraint) => null;
|
||||
|
||||
/// Turns this column builder into a column. This method won't actually be
|
||||
/// called in your code. Instead, moor_generator will take a look at your
|
||||
/// source code to figure out your table structure.
|
||||
|
|
|
@ -79,6 +79,19 @@ abstract class Query<Table, DataClass> {
|
|||
/// Applies a [where] statement so that the row with the same primary key as
|
||||
/// [d] will be matched.
|
||||
void whereSamePrimaryKey(DataClass d) {
|
||||
assert(
|
||||
table.$primaryKey != null && table.$primaryKey.isNotEmpty,
|
||||
'When using Query.whereSamePrimaryKey, which is also called from '
|
||||
'DeleteStatement.delete and UpdateStatement.replace, the affected table'
|
||||
'must have a primary key. You can either specify a primary implicitly '
|
||||
'by making an integer() column autoIncrement(), or by explictly '
|
||||
'overriding the primaryKey getter in your table class. You\'ll also '
|
||||
'have to re-run the code generation step.\n'
|
||||
'Alternatively, if you\'re using DeleteStatement.delete or '
|
||||
'UpdateStatement.replace, consider using DeleteStatement.go or '
|
||||
'UpdateStatement.write respectively. In that case, you need to use a '
|
||||
'custom where statement.');
|
||||
|
||||
final primaryKeys = table.$primaryKey.map((c) => c.$name);
|
||||
|
||||
final updatedFields = table.entityToSql(d, includeNulls: true);
|
||||
|
|
|
@ -16,7 +16,12 @@ abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
|
|||
/// Whether null values are allowed for this column.
|
||||
final bool $nullable;
|
||||
|
||||
GeneratedColumn(this.$name, this.$nullable);
|
||||
/// If custom constraints have been specified for this column via
|
||||
/// [ColumnBuilder.customConstraint], these are kept here. Otherwise, this
|
||||
/// field is going to be null.
|
||||
final String $customConstraints;
|
||||
|
||||
GeneratedColumn(this.$name, this.$nullable, {this.$customConstraints});
|
||||
|
||||
/// Writes the definition of this column, as defined
|
||||
/// [here](https://www.sqlite.org/syntax/column-def.html), into the given
|
||||
|
@ -56,8 +61,8 @@ class GeneratedTextColumn extends GeneratedColumn<String, StringType>
|
|||
final int maxTextLength;
|
||||
|
||||
GeneratedTextColumn(String name, bool nullable,
|
||||
{this.minTextLength, this.maxTextLength})
|
||||
: super(name, nullable);
|
||||
{this.minTextLength, this.maxTextLength, String $customConstraints})
|
||||
: super(name, nullable, $customConstraints: $customConstraints);
|
||||
|
||||
@override
|
||||
Expression<bool, BoolType> like(String pattern) =>
|
||||
|
@ -81,7 +86,8 @@ class GeneratedTextColumn extends GeneratedColumn<String, StringType>
|
|||
|
||||
class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType>
|
||||
implements BoolColumn {
|
||||
GeneratedBoolColumn(String name, bool nullable) : super(name, nullable);
|
||||
GeneratedBoolColumn(String name, bool nullable, {String $customConstraints})
|
||||
: super(name, nullable, $customConstraints: $customConstraints);
|
||||
|
||||
@override
|
||||
final String typeName = 'BOOLEAN';
|
||||
|
@ -108,8 +114,8 @@ class GeneratedIntColumn extends GeneratedColumn<int, IntType>
|
|||
final String typeName = 'INTEGER';
|
||||
|
||||
GeneratedIntColumn(String name, bool nullable,
|
||||
{this.hasAutoIncrement = false})
|
||||
: super(name, nullable);
|
||||
{this.hasAutoIncrement = false, String $customConstraints})
|
||||
: super(name, nullable, $customConstraints: $customConstraints);
|
||||
|
||||
@override
|
||||
void writeColumnDefinition(StringBuffer into) {
|
||||
|
@ -127,8 +133,9 @@ class GeneratedIntColumn extends GeneratedColumn<int, IntType>
|
|||
|
||||
class GeneratedDateTimeColumn extends GeneratedColumn<DateTime, DateTimeType>
|
||||
implements DateTimeColumn {
|
||||
GeneratedDateTimeColumn(String $name, bool $nullable)
|
||||
: super($name, $nullable);
|
||||
GeneratedDateTimeColumn(String $name, bool $nullable,
|
||||
{String $customConstraints})
|
||||
: super($name, $nullable, $customConstraints: $customConstraints);
|
||||
|
||||
@override
|
||||
String get typeName => 'INTEGER'; // date-times are stored as unix-timestamps
|
||||
|
@ -136,7 +143,8 @@ class GeneratedDateTimeColumn extends GeneratedColumn<DateTime, DateTimeType>
|
|||
|
||||
class GeneratedBlobColumn extends GeneratedColumn<Uint8List, BlobType>
|
||||
implements BlobColumn {
|
||||
GeneratedBlobColumn(String $name, bool $nullable) : super($name, $nullable);
|
||||
GeneratedBlobColumn(String $name, bool $nullable, {String $customConstraints})
|
||||
: super($name, $nullable, $customConstraints: $customConstraints);
|
||||
|
||||
@override
|
||||
final String typeName = 'BLOB';
|
||||
|
|
|
@ -316,7 +316,6 @@ let me know by creating an issue!
|
|||
- Validation
|
||||
- Table joins
|
||||
- Bulk inserts
|
||||
- Custom column constraints
|
||||
- When inserts / updates fail, explain why that happened
|
||||
### Interesting stuff that would be nice to have
|
||||
Implementing this will very likely result in backwards-incompatible changes.
|
||||
|
|
|
@ -63,6 +63,10 @@ class SpecifiedColumn {
|
|||
final bool declaredAsPrimaryKey;
|
||||
final List<ColumnFeature> features;
|
||||
|
||||
/// If this columns has custom constraints that should be used instead of the
|
||||
/// default ones.
|
||||
final String customConstraints;
|
||||
|
||||
/// 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 get dartTypeName => {
|
||||
|
@ -105,13 +109,15 @@ class SpecifiedColumn {
|
|||
ColumnType.blob: 'BlobType',
|
||||
}[type];
|
||||
|
||||
const SpecifiedColumn(
|
||||
{this.type,
|
||||
const SpecifiedColumn({
|
||||
this.type,
|
||||
this.dartGetterName,
|
||||
this.name,
|
||||
this.customConstraints,
|
||||
this.declaredAsPrimaryKey = false,
|
||||
this.nullable = false,
|
||||
this.features = const []});
|
||||
this.features = const [],
|
||||
});
|
||||
}
|
||||
|
||||
abstract class ColumnFeature {
|
||||
|
|
|
@ -20,6 +20,7 @@ const String functionReferences = 'references';
|
|||
const String functionAutoIncrement = 'autoIncrement';
|
||||
const String functionWithLength = 'withLength';
|
||||
const String functionNullable = 'nullable';
|
||||
const String functionCustomConstraint = 'customConstraint';
|
||||
|
||||
const String errorMessage = 'This getter does not create a valid column that '
|
||||
'can be parsed by moor. Please refer to the readme from moor to see how '
|
||||
|
@ -55,6 +56,7 @@ class ColumnParser extends ParserBase {
|
|||
|
||||
String foundStartMethod;
|
||||
String foundExplicitName;
|
||||
String foundCustomConstraint;
|
||||
var wasDeclaredAsPrimaryKey = false;
|
||||
var nullable = false;
|
||||
// todo parse reference
|
||||
|
@ -71,22 +73,28 @@ class ColumnParser extends ParserBase {
|
|||
switch (methodName) {
|
||||
case functionNamed:
|
||||
if (foundExplicitName != null) {
|
||||
generator.errors.add(MoorError(
|
||||
generator.errors.add(
|
||||
MoorError(
|
||||
critical: false,
|
||||
affectedElement: getter.declaredElement,
|
||||
message:
|
||||
"You're setting more than one name here, the first will "
|
||||
'be used'));
|
||||
'be used',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
foundExplicitName =
|
||||
readStringLiteral(remainingExpr.argumentList.arguments.first, () {
|
||||
generator.errors.add(MoorError(
|
||||
generator.errors.add(
|
||||
MoorError(
|
||||
critical: false,
|
||||
affectedElement: getter.declaredElement,
|
||||
message:
|
||||
'This table name is cannot be resolved! Please only use '
|
||||
'a constant string as parameter for .named().'));
|
||||
'a constant string as parameter for .named().',
|
||||
),
|
||||
);
|
||||
});
|
||||
break;
|
||||
case functionPrimaryKey:
|
||||
|
@ -110,6 +118,21 @@ class ColumnParser extends ParserBase {
|
|||
break;
|
||||
case functionNullable:
|
||||
nullable = true;
|
||||
break;
|
||||
case functionCustomConstraint:
|
||||
foundCustomConstraint =
|
||||
readStringLiteral(remainingExpr.argumentList.arguments.first, () {
|
||||
generator.errors.add(
|
||||
MoorError(
|
||||
critical: false,
|
||||
affectedElement: getter.declaredElement,
|
||||
message:
|
||||
'This constraint is cannot be resolved! Please only use '
|
||||
'a constant string as parameter for .customConstraint().',
|
||||
),
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// We're not at a starting method yet, so we need to go deeper!
|
||||
|
@ -129,6 +152,7 @@ class ColumnParser extends ParserBase {
|
|||
dartGetterName: getter.name.name,
|
||||
name: name.escapeIfSqlKeyword(),
|
||||
declaredAsPrimaryKey: wasDeclaredAsPrimaryKey,
|
||||
customConstraints: foundCustomConstraint,
|
||||
nullable: nullable,
|
||||
features: foundFeatures);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ void main() async {
|
|||
|
||||
class CustomPrimaryKey extends Table {
|
||||
IntColumn get partA => integer()();
|
||||
IntColumn get partB => integer()();
|
||||
IntColumn get partB => integer().customConstraint('custom')();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {partA, partB};
|
||||
|
@ -109,6 +109,19 @@ void main() async {
|
|||
expect(
|
||||
idColumn.features, contains(LimitingTextLength.withLength(max: 100)));
|
||||
});
|
||||
|
||||
test('parses custom constraints', () {
|
||||
final table =
|
||||
TableParser(generator).parse(testLib.getType('CustomPrimaryKey'));
|
||||
|
||||
final partA =
|
||||
table.columns.singleWhere((c) => c.dartGetterName == 'partA');
|
||||
final partB =
|
||||
table.columns.singleWhere((c) => c.dartGetterName == 'partB');
|
||||
|
||||
expect(partB.customConstraints, 'custom');
|
||||
expect(partA.customConstraints, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
test('parses custom primary keys', () {
|
||||
|
|
Loading…
Reference in New Issue