Merge pull request #1774 from westito/unique-constraint

Unique constraint DSL for Dart tables
This commit is contained in:
Simon Binder 2022-04-03 12:53:24 +02:00 committed by GitHub
commit 5981d409c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 442 additions and 16 deletions

View File

@ -0,0 +1,17 @@
import 'package:drift/drift.dart';
// #docregion unique
class WithUniqueConstraints extends Table {
IntColumn get a => integer().unique()();
IntColumn get b => integer()();
IntColumn get c => integer()();
@override
List<Set<Column>> get uniqueKeys => [
{b, c}
];
// Effectively, this table has two unique key sets: (a) and (b, c).
}
// #enddocregion unique

View File

@ -1,7 +1,7 @@
---
data:
title: "Dart tables"
description: Further information on Dart tables
description: Further information on defining tables in Dart.
weight: 150
template: layouts/docs/single
---
@ -11,6 +11,8 @@ __Prefer sql?__ If you prefer, you can also declare tables via `CREATE TABLE` st
Drift's sql analyzer will generate matching Dart code. [Details]({{ "starting_with_sql.md" | pageUrl }}).
{% endblock %}
{% assign snippets = 'package:moor_documentation/snippets/tables/advanced.dart.excerpt.json' | readString | json_decode %}
As shown in the [getting started guide]({{ "index.md" | pageUrl }}), sql tables can be written in Dart:
```dart
class Todos extends Table {
@ -128,6 +130,19 @@ Note that the primary key must essentially be constant so that the generator can
- it must be defined with the `=>` syntax, function bodies aren't supported
- it must return a set literal without collection elements like `if`, `for` or spread operators
## Unique Constraints
Starting from version 1.6.0, `UNIQUE` SQL constraints can be defined on Dart tables too.
A unique constraint contains one or more columns. The combination of all columns in a constraint
must be unique in the table, or the databas will report an error on inserts.
With drift, a unique constraint can be added to a single column by marking it as `.unique()` in
the column builder.
A unique set spanning multiple columns can be added by overriding the `uniqueKeys` getter in the
`Table` class:
{% include "blocks/snippet" snippets = snippets name = 'unique' %}
## Supported column types
Drift supports a variety of column types out of the box. You can store custom classes in columns by using

View File

@ -1,5 +1,7 @@
## 1.6.0-dev
- Add the `unique()` method to columns and the `uniqueKeys` override to tables
to define unique constraints in Dart tables.
- Add the very experimental `package:drift/wasm.dart` library. It uses WebAssembly
to access sqlite3 without any external JavaScript libraries, but requires you to
add a [WebAssembly module](https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3#wasm-web-support)

View File

@ -172,6 +172,8 @@ class $TodoCategoriesTable extends TodoCategories
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
TodoCategory map(Map<String, dynamic> data, {String? tablePrefix}) {
return TodoCategory.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -460,6 +462,8 @@ class $TodoItemsTable extends TodoItems
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
TodoItem map(Map<String, dynamic> data, {String? tablePrefix}) {
return TodoItem.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);

View File

@ -95,8 +95,8 @@ extension BuildColumn<T> on ColumnBuilder<T> {
/// [constraint] if that is desired.
///
/// This can be used to implement constraints that drift 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 drift itself. Please feel
/// support. If you've found a common use-case for this, it should be
/// considered a limitation of drift itself. Please feel
/// free to open an issue at https://github.com/simolus3/drift/issues/new to
/// report that.
///
@ -252,6 +252,12 @@ extension BuildGeneralColumn<T> on _BaseColumnBuilder<T> {
/// primary key. Columns are non-null by default.
ColumnBuilder<T?> nullable() => _isGenerated();
/// Adds UNIQUE constraint to column.
///
/// Unique constraints spanning multiple keys can be added to a table by
/// overriding [Table.uniqueKeys].
ColumnBuilder<T?> unique() => _isGenerated();
/// Uses a custom [converter] to store custom Dart objects in a single column
/// and automatically mapping them from and to sql.
///

View File

@ -62,6 +62,34 @@ abstract class Table extends HasResultSet {
@visibleForOverriding
Set<Column>? get primaryKey => null;
/// Unique constraints in this table.
///
/// When two rows have the same value in _any_ set specified in [uniqueKeys],
/// the database will reject the second row for inserts.
///
/// Override this to specify unique keys:
///
/// ```dart
/// class IngredientInRecipes extends Table {
/// @override
/// Set<Column> get uniqueKeys =>
/// [{recipe, ingredient}, {recipe, amountInGrams}];
///
/// IntColumn get recipe => integer()();
/// IntColumn get ingredient => integer()();
///
/// IntColumn get amountInGrams => integer().named('amount')();
/// ```
///
/// The getter must return a list of set literals using the `=>` syntax so
/// that the drift generator can understand the code.
///
/// Note that individual columns can also be marked as unique with
/// [BuildGeneralColumn.unique]. This is equivalent to adding a single-element
/// set to this list.
@visibleForOverriding
List<Set<Column>>? get uniqueKeys => null;
/// Custom table constraints that should be added to the table.
///
/// See also:

View File

@ -288,6 +288,21 @@ class Migrator {
context.buffer.write(')');
}
if (table.uniqueKeys.isNotEmpty) {
for (final key in table.uniqueKeys) {
context.buffer.write(', UNIQUE (');
final uqList = key.toList(growable: false);
for (var i = 0; i < uqList.length; i++) {
final column = uqList[i];
context.buffer.write(escapeIfNeeded(column.name));
if (i != uqList.length - 1) context.buffer.write(', ');
}
context.buffer.write(')');
}
}
final constraints = dslTable.customConstraints;
for (var i = 0; i < constraints.length; i++) {

View File

@ -28,6 +28,14 @@ mixin TableInfo<TableDsl extends Table, D> on Table
@override
Set<Column> get primaryKey => $primaryKey;
/// The unique key of this table. Can be empty if no custom primary key has
/// been specified.
///
/// Additional to the [Table.primaryKey] columns declared by an user, this
/// also contains auto-increment integers, which are primary key by default.
@override
List<Set<GeneratedColumn>> get uniqueKeys => const [];
/// The table name in the sql table. This can be an alias for the actual table
/// name. See [actualTableName] for a table name that is not aliased.
@Deprecated('Use aliasedName instead')

View File

@ -271,6 +271,8 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
@override
Set<GeneratedColumn> get $primaryKey => {configKey};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
Config map(Map<String, dynamic> data, {String? tablePrefix}) {
return Config.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -458,6 +460,8 @@ class WithDefaults extends Table with TableInfo<WithDefaults, WithDefault> {
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
WithDefault map(Map<String, dynamic> data, {String? tablePrefix}) {
return WithDefault.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -546,6 +550,8 @@ class NoIds extends Table with TableInfo<NoIds, NoIdRow> {
@override
Set<GeneratedColumn> get $primaryKey => {payload};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
NoIdRow map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return NoIdRow(
@ -767,6 +773,8 @@ class WithConstraints extends Table
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
WithConstraint map(Map<String, dynamic> data, {String? tablePrefix}) {
return WithConstraint.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -1036,6 +1044,8 @@ class Mytable extends Table with TableInfo<Mytable, MytableData> {
@override
Set<GeneratedColumn> get $primaryKey => {someid};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
MytableData map(Map<String, dynamic> data, {String? tablePrefix}) {
return MytableData.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -1256,6 +1266,8 @@ class Email extends Table
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
EMail map(Map<String, dynamic> data, {String? tablePrefix}) {
return EMail.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -1443,6 +1455,8 @@ class WeirdTable extends Table with TableInfo<WeirdTable, WeirdData> {
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
WeirdData map(Map<String, dynamic> data, {String? tablePrefix}) {
return WeirdData.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);

View File

@ -16,13 +16,19 @@ class TodosTable extends Table with AutoIncrement {
TextColumn get title => text().withLength(min: 4, max: 16).nullable()();
TextColumn get content => text()();
@JsonKey('target_date')
DateTimeColumn get targetDate => dateTime().nullable()();
DateTimeColumn get targetDate => dateTime().nullable().unique()();
IntColumn get category => integer().references(Categories, #id).nullable()();
@override
List<Set<Column>>? get uniqueKeys => [
{title, category},
{title, targetDate},
];
}
class Users extends Table with AutoIncrement {
TextColumn get name => text().withLength(min: 6, max: 32)();
TextColumn get name => text().withLength(min: 6, max: 32).unique()();
BoolColumn get isAwesome => boolean().withDefault(const Constant(true))();
BlobColumn get profilePicture => blob()();

View File

@ -249,6 +249,8 @@ class $CategoriesTable extends Categories
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
Category map(Map<String, dynamic> data, {String? tablePrefix}) {
return Category.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -503,7 +505,9 @@ class $TodosTableTable extends TodosTable
@override
late final GeneratedColumn<DateTime?> targetDate = GeneratedColumn<DateTime?>(
'target_date', aliasedName, true,
type: const IntType(), requiredDuringInsert: false);
type: const IntType(),
requiredDuringInsert: false,
defaultConstraints: 'UNIQUE');
final VerificationMeta _categoryMeta = const VerificationMeta('category');
@override
late final GeneratedColumn<int?> category = GeneratedColumn<int?>(
@ -552,6 +556,11 @@ class $TodosTableTable extends TodosTable
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [
{title, category},
{title, targetDate},
];
@override
TodoEntry map(Map<String, dynamic> data, {String? tablePrefix}) {
return TodoEntry.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -782,7 +791,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
additionalChecks:
GeneratedColumn.checkTextLength(minTextLength: 6, maxTextLength: 32),
type: const StringType(),
requiredDuringInsert: true);
requiredDuringInsert: true,
defaultConstraints: 'UNIQUE');
final VerificationMeta _isAwesomeMeta = const VerificationMeta('isAwesome');
@override
late final GeneratedColumn<bool?> isAwesome = GeneratedColumn<bool?>(
@ -850,6 +860,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
User map(Map<String, dynamic> data, {String? tablePrefix}) {
return User.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -1030,6 +1042,8 @@ class $SharedTodosTable extends SharedTodos
@override
Set<GeneratedColumn> get $primaryKey => {todo, user};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
SharedTodo map(Map<String, dynamic> data, {String? tablePrefix}) {
return SharedTodo.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -1184,6 +1198,8 @@ class $TableWithoutPKTable extends TableWithoutPK
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
CustomRowClass map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return CustomRowClass.map(
@ -1341,6 +1357,8 @@ class $PureDefaultsTable extends PureDefaults
@override
Set<GeneratedColumn> get $primaryKey => {txt};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
PureDefault map(Map<String, dynamic> data, {String? tablePrefix}) {
return PureDefault.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);

View File

@ -61,7 +61,7 @@ void main() {
);
await db.into(db.users).insert(
UsersCompanion.insert(
name: 'User name',
name: 'User name 2',
profilePicture: Uint8List(0),
creationTime: Value(secondTime)),
);

View File

@ -19,7 +19,7 @@ void main() {
b.insertAll(db.users, [
for (var i = 0; i < 1000; i++)
UsersCompanion.insert(
name: 'user name', profilePicture: Uint8List(0)),
name: 'user name $i', profilePicture: Uint8List(0)),
]);
});

View File

@ -22,8 +22,9 @@ void main() {
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS todos '
'(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title TEXT NULL, '
'content TEXT NOT NULL, target_date INTEGER NULL, '
'category INTEGER NULL REFERENCES categories (id));',
'content TEXT NOT NULL, target_date INTEGER NULL UNIQUE, '
'category INTEGER NULL REFERENCES categories (id), '
'UNIQUE (title, category), UNIQUE (title, target_date));',
[]));
verify(mockExecutor.runCustom(
@ -39,7 +40,7 @@ void main() {
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS users '
'(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'name TEXT NOT NULL, '
'name TEXT NOT NULL UNIQUE, '
'is_awesome INTEGER NOT NULL DEFAULT 1 CHECK (is_awesome IN (0, 1)), '
'profile_picture BLOB NOT NULL, '
'creation_time INTEGER NOT NULL '
@ -91,7 +92,7 @@ void main() {
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS users '
'(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'name TEXT NOT NULL, '
'name TEXT NOT NULL UNIQUE, '
'is_awesome INTEGER NOT NULL DEFAULT 1 CHECK (is_awesome IN (0, 1)), '
'profile_picture BLOB NOT NULL, '
'creation_time INTEGER NOT NULL '

View File

@ -23,6 +23,7 @@ const String _methodReferences = 'references';
const String _methodAutoIncrement = 'autoIncrement';
const String _methodWithLength = 'withLength';
const String _methodNullable = 'nullable';
const String _methodUnique = 'unique';
const String _methodCustomConstraint = 'customConstraint';
const String _methodDefault = 'withDefault';
const String _methodClientDefault = 'clientDefault';
@ -218,6 +219,9 @@ class ColumnParser {
case _methodNullable:
nullable = true;
break;
case _methodUnique:
foundFeatures.add(const UniqueKey());
break;
case _methodCustomConstraint:
foundCustomConstraint = base.readStringLiteral(
remainingExpr.argumentList.arguments.first, () {
@ -355,6 +359,17 @@ class ColumnParser {
);
}
if (foundFeatures.contains(const UniqueKey()) &&
foundFeatures.contains(const PrimaryKey())) {
base.step.reportError(
ErrorInDartCode(
severity: Severity.error,
affectedElement: getter.declaredElement,
message: 'Primary key column cannot have UNIQUE constraint',
),
);
}
final docString =
getter.documentationComment?.tokens.map((t) => t.toString()).join('\n');
return MoorColumn(

View File

@ -12,6 +12,7 @@ class TableParser {
final columns = (await _parseColumns(element)).toList();
final primaryKey = await _readPrimaryKey(element, columns);
final uniqueKeys = await _readUniqueKeys(element, columns);
final dataClassInfo = _readDataClassInformation(columns, element);
@ -23,6 +24,7 @@ class TableParser {
existingRowClass: dataClassInfo.existingClass,
customParentClass: dataClassInfo.extending,
primaryKey: primaryKey,
uniqueKeys: uniqueKeys,
overrideWithoutRowId: await _overrideWithoutRowId(element),
declaration: DartTableDeclaration(element, base.step.file),
);
@ -34,6 +36,38 @@ class TableParser {
));
}
if (primaryKey != null &&
primaryKey.length == 1 &&
primaryKey.first.features.contains(const UniqueKey())) {
base.step.errors.report(ErrorInDartCode(
message: 'Primary key column cannot have UNIQUE constraint',
affectedElement: element,
));
}
if (uniqueKeys != null &&
uniqueKeys.any((key) =>
uniqueKeys.length == 1 &&
key.first.features.contains(const UniqueKey()))) {
base.step.errors.report(ErrorInDartCode(
message:
'Column provided in a single-column uniqueKey set already has a '
'column-level UNIQUE constraint',
affectedElement: element,
));
}
if (uniqueKeys != null &&
primaryKey != null &&
uniqueKeys
.any((unique) => const SetEquality().equals(unique, primaryKey))) {
base.step.errors.report(ErrorInDartCode(
message: 'The uniqueKeys override contains the primary key, which is '
'already unique by default.',
affectedElement: element,
));
}
var index = 0;
for (final converter in table.converters) {
converter
@ -188,6 +222,68 @@ class TableParser {
return parsedPrimaryKey;
}
Future<List<Set<MoorColumn>>?> _readUniqueKeys(
ClassElement element, List<MoorColumn> columns) async {
final uniqueKeyGetter = element.lookUpGetter('uniqueKeys', element.library);
if (uniqueKeyGetter == null || uniqueKeyGetter.isFromDefaultTable) {
// resolved uniqueKeys is from the Table dsl superclass. That means there
// is no unique key list
return null;
}
final ast =
await base.loadElementDeclaration(uniqueKeyGetter) as MethodDeclaration;
final body = ast.body;
if (body is! ExpressionFunctionBody) {
base.step.reportError(ErrorInDartCode(
affectedElement: uniqueKeyGetter,
message: 'This must return a list of set literal using the => '
'syntax!'));
return null;
}
final expression = body.expression;
final parsedUniqueKeys = <Set<MoorColumn>>[];
if (expression is ListLiteral) {
for (final keySet in expression.elements) {
if (keySet is SetOrMapLiteral) {
final uniqueKey = <MoorColumn>{};
for (final entry in keySet.elements) {
if (entry is Identifier) {
final column = columns.singleWhereOrNull(
(column) => column.dartGetterName == entry.name);
if (column == null) {
base.step.reportError(
ErrorInDartCode(
affectedElement: uniqueKeyGetter,
affectedNode: entry,
message: 'Column not found in this table',
),
);
} else {
uniqueKey.add(column);
}
} else {
print('Unexpected entry in expression.elements: $entry');
}
}
parsedUniqueKeys.add(uniqueKey);
} else {
base.step.reportError(ErrorInDartCode(
affectedElement: uniqueKeyGetter,
message: 'This must return a set list literal!'));
}
}
} else {
base.step.reportError(ErrorInDartCode(
affectedElement: uniqueKeyGetter,
message: 'This must return a set list literal!'));
}
return parsedUniqueKeys;
}
Future<bool?> _overrideWithoutRowId(ClassElement element) async {
final getter = element.lookUpGetter('withoutRowId', element.library);

View File

@ -203,6 +203,11 @@ class PrimaryKey extends ColumnFeature {
const PrimaryKey();
}
/// A `UNIQUE` column constraint.
class UniqueKey extends ColumnFeature {
const UniqueKey();
}
class AutoIncrement extends PrimaryKey {
static const AutoIncrement _instance = AutoIncrement._();

View File

@ -92,6 +92,10 @@ class MoorTable extends MoorEntityWithResultSet {
/// For the full primary key, see [fullPrimaryKey].
final Set<MoorColumn>? primaryKey;
/// The set of unique keys if they have been explicitly defined by
/// overriding `uniqueKeys` in the table class.
final List<Set<MoorColumn>>? uniqueKeys;
/// The primary key for this table.
///
/// Unlikely [primaryKey], this method is not limited to the `primaryKey`
@ -146,6 +150,7 @@ class MoorTable extends MoorEntityWithResultSet {
required this.sqlName,
required this.dartTypeName,
this.primaryKey,
this.uniqueKeys,
String? overriddenName,
this.overrideWithoutRowId,
this.overrideTableConstraints,

View File

@ -28,9 +28,21 @@ abstract class TableOrViewWriter {
: 'PRIMARY KEY');
wrotePkConstraint = true;
break;
}
}
}
if (!wrotePkConstraint) {
for (final feature in column.features) {
if (feature is UniqueKey) {
defaultConstraints.add('UNIQUE');
break;
}
}
}
for (final feature in column.features) {
if (feature is ResolvedDartForeignKeyReference) {
final tableName = escapeIfNeeded(feature.otherTable.sqlName);
final columnName = escapeIfNeeded(feature.otherColumn.name.name);
@ -324,6 +336,7 @@ class TableWriter extends TableOrViewWriter {
_writeValidityCheckMethod();
_writePrimaryKeyOverride();
_writeUniqueKeyOverride();
writeMappingMethod(scope);
// _writeReverseMappingMethod();
@ -425,6 +438,32 @@ class TableWriter extends TableOrViewWriter {
buffer.write('};\n');
}
void _writeUniqueKeyOverride() {
buffer.write('@override\nList<Set<GeneratedColumn>> get uniqueKeys => ');
final uniqueKeys = table.uniqueKeys ?? [];
if (uniqueKeys.isEmpty) {
buffer.write('[];');
return;
}
buffer.write('[');
for (final uniqueKey in uniqueKeys) {
buffer.write('{');
final uqList = uniqueKey.toList();
for (var i = 0; i < uqList.length; i++) {
final pk = uqList[i];
buffer.write(pk.dartGetterName);
if (i != uqList.length - 1) {
buffer.write(', ');
}
}
buffer.write('},\n');
}
buffer.write('];\n');
}
void _writeAliasGenerator() {
final typeName = table.entityInfoName;

View File

@ -0,0 +1,111 @@
import 'package:test/test.dart';
import '../utils.dart';
void main() {
test('does not allow autoIncrement() to have a unique constraint', () async {
final state = TestState.withContent({
'a|lib/main.dart': '''
import 'package:drift/drift.dart';
class Test extends Table {
IntColumn get a => integer().autoIncrement().unique()();
}
'''
});
addTearDown(state.close);
(await state.analyze('package:a/main.dart')).expectDartError(
'Primary key column cannot have UNIQUE constraint', 'a');
});
test('does not allow primary key to have a unique constraint', () async {
final state = TestState.withContent({
'a|lib/main.dart': '''
import 'package:drift/drift.dart';
class Test extends Table {
IntColumn get a => integer().unique()();
@override
Set<Column> get primaryKey => {a};
}
'''
});
addTearDown(state.close);
(await state.analyze('package:a/main.dart')).expectDartError(
'Primary key column cannot have UNIQUE constraint', 'Test');
});
test(
'does not allow primary key to have a unique constraint through override',
() async {
final state = TestState.withContent({
'a|lib/main.dart': '''
import 'package:drift/drift.dart';
class Test extends Table {
IntColumn get a => integer()();
@override
List<Set<Column>> get uniqueKeys => [{a}];
@override
Set<Column> get primaryKey => {a};
}
'''
});
addTearDown(state.close);
(await state.analyze('package:a/main.dart')).expectDartError(
'The uniqueKeys override contains the primary key, which is already '
'unique by default.',
'Test');
});
test('warns about duplicate unique declarations', () async {
final state = TestState.withContent({
'a|lib/main.dart': '''
import 'package:drift/drift.dart';
class Test extends Table {
IntColumn get a => integer().unique()();
@override
List<Set<Column>> get uniqueKeys => [{a}];
}
'''
});
addTearDown(state.close);
(await state.analyze('package:a/main.dart')).expectDartError(
contains('already has a column-level UNIQUE constraint'), 'Test');
});
test('parses unique key definitions', () async {
final state = TestState.withContent({
'a|lib/main.dart': '''
import 'package:drift/drift.dart';
class Test extends Table {
IntColumn get a => integer().unique()();
IntColumn get b => integer().unique()();
@override
List<Set<Column>> get uniqueKeys => [{a}, {b}, {a, b}];
}
'''
});
addTearDown(state.close);
final file = await state.analyze('package:a/main.dart');
expect(file.errors.errors, isEmpty);
final table = file.currentResult!.declaredTables.single;
expect(table.uniqueKeys, hasLength(3));
expect(table.uniqueKeys![0].map((e) => e.name.name), ['a']);
expect(table.uniqueKeys![1].map((e) => e.name.name), ['b']);
expect(table.uniqueKeys![2].map((e) => e.name.name), ['a', 'b']);
});
}

View File

@ -166,6 +166,8 @@ class Entries extends Table with TableInfo<Entries, Entrie> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
Entrie map(Map<String, dynamic> data, {String? tablePrefix}) {
return Entrie.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);

View File

@ -166,6 +166,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
User map(Map<String, dynamic> data, {String? tablePrefix}) {
return User.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -414,6 +416,8 @@ class Groups extends Table with TableInfo<Groups, Group> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
Group map(Map<String, dynamic> data, {String? tablePrefix}) {
return Group.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);

View File

@ -166,6 +166,8 @@ class Entries extends Table with TableInfo<Entries, Entrie> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
Entrie map(Map<String, dynamic> data, {String? tablePrefix}) {
return Entrie.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);

View File

@ -171,6 +171,8 @@ class Users extends Table with TableInfo<Users, User> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
User map(Map<String, dynamic> data, {String tablePrefix}) {
return User.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);

View File

@ -126,9 +126,10 @@ class KeyValuesCompanion extends UpdateCompanion<KeyValue> {
class $KeyValuesTable extends KeyValues
with TableInfo<$KeyValuesTable, KeyValue> {
final GeneratedDatabase _db;
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$KeyValuesTable(this._db, [this._alias]);
$KeyValuesTable(this.attachedDatabase, [this._alias]);
final VerificationMeta _keyMeta = const VerificationMeta('key');
@override
late final GeneratedColumn<String?> key = GeneratedColumn<String?>(
@ -168,6 +169,8 @@ class $KeyValuesTable extends KeyValues
@override
Set<GeneratedColumn> get $primaryKey => {key};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
KeyValue map(Map<String, dynamic> data, {String? tablePrefix}) {
return KeyValue.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -175,7 +178,7 @@ class $KeyValuesTable extends KeyValues
@override
$KeyValuesTable createAlias(String alias) {
return $KeyValuesTable(_db, alias);
return $KeyValuesTable(attachedDatabase, alias);
}
}

View File

@ -307,6 +307,8 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
User map(Map<String, dynamic> data, {String? tablePrefix}) {
return User.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -531,6 +533,8 @@ class $FriendshipsTable extends Friendships
@override
Set<GeneratedColumn> get $primaryKey => {firstUser, secondUser};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
Friendship map(Map<String, dynamic> data, {String? tablePrefix}) {
return Friendship.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);

View File

@ -137,6 +137,8 @@ class $FoosTable extends Foos with TableInfo<$FoosTable, Foo> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
Foo map(Map<String, dynamic> data, {String tablePrefix}) {
return Foo.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
@ -278,6 +280,8 @@ class $BarsTable extends Bars with TableInfo<$BarsTable, Bar> {
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [];
@override
Bar map(Map<String, dynamic> data, {String tablePrefix}) {
return Bar.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);