Don't consider rowid aliases required (#445)

This commit is contained in:
Simon Binder 2020-03-16 20:36:03 +01:00
parent 8d9b0874b9
commit 5b675a811b
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
9 changed files with 92 additions and 15 deletions

View File

@ -14,6 +14,8 @@
- __Breaking__: Remove the second type variable on `Expression` and subclasses. - __Breaking__: Remove the second type variable on `Expression` and subclasses.
- __Breaking__: Remove `customSelectStream` from `QueryEngine`. The `customSelect` - __Breaking__: Remove `customSelectStream` from `QueryEngine`. The `customSelect`
method now returns an `Selectable` (like `customSelectQuery`, which in turn has been deprecated). method now returns an `Selectable` (like `customSelectQuery`, which in turn has been deprecated).
- __Breaking__: Columns that are aliases to sqlite's `rowid` column are now longer considered required
for inserts
- Experimentally support IndexedDB to store sqlite data on the web - Experimentally support IndexedDB to store sqlite data on the web
- Moor will no longer wait for query stream listeners to receive a done event when closing a database - Moor will no longer wait for query stream listeners to receive a done event when closing a database
or transaction. or transaction.

View File

@ -93,8 +93,9 @@ class _LintingVisitor extends RecursiveVisitor<void, void> {
// second, check that no required columns are left out // second, check that no required columns are left out
final specifiedTable = linter.mapper.tableToMoor(e.table.resolved as Table); final specifiedTable = linter.mapper.tableToMoor(e.table.resolved as Table);
final required = final required = specifiedTable.columns
specifiedTable.columns.where((c) => c.requiredDuringInsert).toList(); .where(specifiedTable.isColumnRequiredForInsert)
.toList();
if (required.isNotEmpty && e.source is DefaultValues) { if (required.isNotEmpty && e.source is DefaultValues) {
linter.lints.add(AnalysisError( linter.lints.add(AnalysisError(

View File

@ -170,15 +170,6 @@ class MoorColumn implements HasDeclaration {
ColumnType.real: 'GeneratedRealColumn', ColumnType.real: 'GeneratedRealColumn',
}[type]; }[type];
/// Whether this column is required for insert statements, meaning that a
/// non-absent value must be provided for an insert statement to be valid.
bool get requiredDuringInsert {
final hasDefault = defaultArgument != null || clientDefaultCode != null;
final aliasForPk = type == ColumnType.integer &&
features.any((f) => f is PrimaryKey || f is AutoIncrement);
return !nullable && !hasDefault && !aliasForPk;
}
/// The class inside the moor library that represents the same sql type as /// The class inside the moor library that represents the same sql type as
/// this column. /// this column.
String get sqlTypeName => sqlTypes[type]; String get sqlTypeName => sqlTypes[type];

View File

@ -69,8 +69,20 @@ class MoorTable implements MoorSchemaEntity {
/// The set of primary keys, if they have been explicitly defined by /// The set of primary keys, if they have been explicitly defined by
/// overriding `primaryKey` in the table class. `null` if the primary key has /// overriding `primaryKey` in the table class. `null` if the primary key has
/// not been defined that way. /// not been defined that way.
///
/// For the full primary key, see [fullPrimaryKey].
final Set<MoorColumn> primaryKey; final Set<MoorColumn> primaryKey;
/// The primary key for this table.
///
/// Unlikely [primaryKey], this method is not limited to the `primaryKey`
/// override in Dart table declarations.
Set<MoorColumn> get fullPrimaryKey {
if (primaryKey != null) return primaryKey;
return columns.where((c) => c.features.any((f) => f is PrimaryKey)).toSet();
}
/// When non-null, the generated table class will override the `withoutRowId` /// When non-null, the generated table class will override the `withoutRowId`
/// getter on the table class with this value. /// getter on the table class with this value.
final bool overrideWithoutRowId; final bool overrideWithoutRowId;
@ -138,6 +150,30 @@ class MoorTable implements MoorSchemaEntity {
} }
} }
/// Determines whether [column] would be required for inserts performed via
/// companions.
bool isColumnRequiredForInsert(MoorColumn column) {
assert(columns.contains(column));
if (column.defaultArgument != null ||
column.clientDefaultCode != null ||
column.nullable) {
// default value would be applied, so it's not required for inserts
return false;
}
// A column isn't required if it's an alias for the rowid, as explained
// at https://www.sqlite.org/lang_createtable.html#rowid
final isWithoutRowId = overrideWithoutRowId ?? false;
final fullPk = fullPrimaryKey;
final isAliasForRowId = !isWithoutRowId &&
column.type == ColumnType.integer &&
fullPk.length == 1 &&
fullPk.single == column;
return !isAliasForRowId;
}
@override @override
String get displayName { String get displayName {
if (isFromSql) { if (isFromSql) {

View File

@ -235,7 +235,7 @@ class TableWriter {
'$getterName.isAcceptableValue(d.$getterName.value, $metaName));') '$getterName.isAcceptableValue(d.$getterName.value, $metaName));')
..write('}'); ..write('}');
if (column.requiredDuringInsert) { if (table.isColumnRequiredForInsert(column)) {
_buffer _buffer
..write(' else if (isInserting) {\n') ..write(' else if (isInserting) {\n')
..write('context.missing($metaName);\n') ..write('context.missing($metaName);\n')

View File

@ -59,7 +59,7 @@ class UpdateCompanionWriter {
for (final column in table.columns) { for (final column in table.columns) {
final param = column.dartGetterName; final param = column.dartGetterName;
if (column.requiredDuringInsert) { if (table.isColumnRequiredForInsert(column)) {
requiredColumns.add(column); requiredColumns.add(column);
_buffer.write('@required ${column.dartTypeName} $param,'); _buffer.write('@required ${column.dartTypeName} $param,');

View File

@ -1,6 +1,6 @@
name: moor_generator name: moor_generator
description: Dev-dependency to generate table and dataclasses together with the moor package. description: Dev-dependency to generate table and dataclasses together with the moor package.
version: 2.4.0 version: 3.0.0-dev
repository: https://github.com/simolus3/moor repository: https://github.com/simolus3/moor
homepage: https://moor.simonbinder.eu/ homepage: https://moor.simonbinder.eu/
issue_tracker: https://github.com/simolus3/moor/issues issue_tracker: https://github.com/simolus3/moor/issues
@ -22,7 +22,7 @@ dependencies:
cli_util: ^0.1.0 cli_util: ^0.1.0
# Moor-specific analysis # Moor-specific analysis
moor: ^2.3.0 moor: ^3.0.0-dev
sqlparser: ^0.7.0 sqlparser: ^0.7.0
# Dart analysis # Dart analysis

View File

@ -65,6 +65,14 @@ void main() {
TextColumn get archivedBy => text()(); TextColumn get archivedBy => text()();
DateTimeColumn get archivedOn => dateTime()(); DateTimeColumn get archivedOn => dateTime()();
} }
class WithAliasForRowId extends Table {
IntColumn get id => integer()();
TextColumn get name => text()();
@override
Set<Column> get primaryKey => {id};
}
''' '''
}); });
}); });
@ -175,6 +183,13 @@ void main() {
expect(table.columns.any((column) => column.hasAI), isFalse); expect(table.columns.any((column) => column.hasAI), isFalse);
}); });
test('recognizes aliases for rowid', () async {
final table = await parse('WithAliasForRowId');
final idColumn = table.columns.singleWhere((c) => c.name.name == 'id');
expect(table.isColumnRequiredForInsert(idColumn), isFalse);
});
group('inheritance', () { group('inheritance', () {
test('from abstract classes or mixins', () async { test('from abstract classes or mixins', () async {
final table = await parse('Foos'); final table = await parse('Foos');

View File

@ -1,9 +1,11 @@
import 'package:build/build.dart'; import 'package:build/build.dart';
import 'package:moor_generator/src/analyzer/runner/results.dart';
import 'package:moor_generator/src/analyzer/runner/steps.dart'; import 'package:moor_generator/src/analyzer/runner/steps.dart';
import 'package:moor_generator/src/analyzer/session.dart'; import 'package:moor_generator/src/analyzer/session.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../../utils/test_backend.dart'; import '../../utils/test_backend.dart';
import '../utils.dart';
void main() { void main() {
const content = ''' const content = '''
@ -46,4 +48,34 @@ usersWithLongName: SELECT * FROM users WHERE LENGTH(name) > 25
backend.finish(); backend.finish();
}); });
test('recognizes aliases to rowid', () async {
final state = TestState.withContent({
'foo|lib/a.moor': '''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE users2 (
id INTEGER,
name TEXT NOT NULL,
PRIMARY KEY (id)
);
'''
});
final result = await state.analyze('package:foo/a.moor');
final file = result.currentResult as ParsedMoorFile;
final users1 = file.declaredTables.singleWhere((t) => t.sqlName == 'users');
final users2 =
file.declaredTables.singleWhere((t) => t.sqlName == 'users2');
expect(users1.isColumnRequiredForInsert(users1.columns[0]), isFalse);
expect(users1.isColumnRequiredForInsert(users1.columns[1]), isTrue);
expect(users2.isColumnRequiredForInsert(users2.columns[0]), isFalse);
expect(users2.isColumnRequiredForInsert(users2.columns[1]), isTrue);
});
} }