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 `customSelectStream` from `QueryEngine`. The `customSelect`
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
- Moor will no longer wait for query stream listeners to receive a done event when closing a database
or transaction.

View File

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

View File

@ -170,15 +170,6 @@ class MoorColumn implements HasDeclaration {
ColumnType.real: 'GeneratedRealColumn',
}[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
/// this column.
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
/// overriding `primaryKey` in the table class. `null` if the primary key has
/// not been defined that way.
///
/// For the full primary key, see [fullPrimaryKey].
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`
/// getter on the table class with this value.
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
String get displayName {
if (isFromSql) {

View File

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

View File

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

View File

@ -1,6 +1,6 @@
name: moor_generator
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
homepage: https://moor.simonbinder.eu/
issue_tracker: https://github.com/simolus3/moor/issues
@ -22,7 +22,7 @@ dependencies:
cli_util: ^0.1.0
# Moor-specific analysis
moor: ^2.3.0
moor: ^3.0.0-dev
sqlparser: ^0.7.0
# Dart analysis

View File

@ -65,6 +65,14 @@ void main() {
TextColumn get archivedBy => text()();
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);
});
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', () {
test('from abstract classes or mixins', () async {
final table = await parse('Foos');

View File

@ -1,9 +1,11 @@
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/session.dart';
import 'package:test/test.dart';
import '../../utils/test_backend.dart';
import '../utils.dart';
void main() {
const content = '''
@ -46,4 +48,34 @@ usersWithLongName: SELECT * FROM users WHERE LENGTH(name) > 25
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);
});
}