Provide lints on insert statements that will fail

This commit is contained in:
Simon Binder 2019-08-29 21:09:20 +02:00
parent 3cb00a4b31
commit 876db0671e
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
5 changed files with 106 additions and 1 deletions

View File

@ -10,6 +10,8 @@ final _leadingDigits = RegExp(r'^\d*');
abstract class SqlQuery {
final String name;
final AnalysisContext fromContext;
List<AnalysisError> lints;
String get sql => fromContext.sql;
/// The variables that appear in the [sql] query. We support three kinds of

View File

@ -0,0 +1,82 @@
import 'package:sqlparser/sqlparser.dart';
import '../query_handler.dart';
class Linter {
final QueryHandler handler;
final List<AnalysisError> lints = [];
Linter(this.handler);
void reportLints() {
handler.context.root.accept(_LintingVisitor(this));
}
}
class _LintingVisitor extends RecursiveVisitor<void> {
final Linter linter;
_LintingVisitor(this.linter);
@override
void visitInsertStatement(InsertStatement e) {
final targeted = e.resolvedTargetColumns;
if (targeted == null) return;
// First, check that the amount of values matches the declaration.
e.source.when(
isValues: (values) {
for (var tuple in values.values) {
if (tuple.expressions.length != targeted.length) {
linter.lints.add(AnalysisError(
type: AnalysisErrorType.other,
message: 'Expected tuple to have ${targeted.length} values',
relevantNode: tuple,
));
}
}
},
isSelect: (select) {
final columns = select.stmt.resolvedColumns;
if (columns.length != targeted.length) {
linter.lints.add(AnalysisError(
type: AnalysisErrorType.other,
message: 'This select statement should return ${targeted.length} '
'columns, but actually returns ${columns.length}',
relevantNode: select.stmt,
));
}
},
);
// second, check that no required columns are left out
final specifiedTable =
linter.handler.mapper.tableToMoor(e.table.resolved as Table);
final required =
specifiedTable.columns.where((c) => c.requiredDuringInsert).toList();
if (required.isNotEmpty && e.source is DefaultValues) {
linter.lints.add(AnalysisError(
type: AnalysisErrorType.other,
message: 'This table has columns without default values, so defaults '
'can\'t be used for insert.',
relevantNode: e.table,
));
} else {
final notPresent = required.where((c) => !targeted
.any((t) => t.name.toUpperCase() == c.name.name.toUpperCase()));
if (notPresent.isNotEmpty) {
final msg = notPresent.join(', ');
linter.lints.add(AnalysisError(
type: AnalysisErrorType.other,
message: 'Some columns are required but not present here. Expected '
'values for $msg.',
relevantNode: e.source.childNodes.first,
));
}
}
}
}

View File

@ -5,6 +5,7 @@ import 'package:moor_generator/src/utils/type_converter_hint.dart';
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
import 'affected_tables_visitor.dart';
import 'lints/linter.dart';
class QueryHandler {
final String name;
@ -19,11 +20,20 @@ class QueryHandler {
QueryHandler(this.name, this.context, this.mapper);
SqlQuery handle() {
final root = context.root;
_foundVariables = mapper.extractVariables(context);
_verifyNoSkippedIndexes();
final query = _mapToMoor();
final linter = Linter(this);
linter.reportLints();
query.lints = linter.lints;
return query;
}
SqlQuery _mapToMoor() {
final root = context.root;
if (root is SelectStatement) {
return _handleSelect();
} else if (root is UpdateStatement ||

View File

@ -54,5 +54,15 @@ class SqlParser {
log.warning('Error while generating APIs for ${context.sql}', e, s);
}
});
// report lints
for (var query in foundQueries) {
for (var lint in query.lints) {
session.errors.add(MoorError(
critical: false,
message: 'Lint for ${query.name}: $lint',
));
}
}
}
}

View File

@ -56,4 +56,5 @@ enum AnalysisErrorType {
ambiguousReference,
unknownFunction,
other,
}