mirror of https://github.com/AMT-Cheif/drift.git
Analyze body of CREATE TRIGGER statements
This commit is contained in:
parent
75cbe74b53
commit
cf6824af82
|
@ -2,17 +2,21 @@ import 'package:moor_generator/moor_generator.dart';
|
|||
import 'package:moor_generator/src/analyzer/errors.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/sql_queries/lints/linter.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/query_analyzer.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
/// Handles `REFERENCES` clauses in tables by resolving their columns and
|
||||
/// reporting errors if they don't exist. Further, sets the
|
||||
/// [MoorTable.references] field for tables declared in moor.
|
||||
class EntityHandler {
|
||||
final AnalyzeMoorStep step;
|
||||
class EntityHandler extends BaseAnalyzer {
|
||||
final ParsedMoorFile file;
|
||||
final List<MoorTable> availableTables;
|
||||
|
||||
EntityHandler(this.step, this.file, this.availableTables);
|
||||
AnalyzeMoorStep get moorStep => step as AnalyzeMoorStep;
|
||||
|
||||
EntityHandler(
|
||||
AnalyzeMoorStep step, this.file, List<MoorTable> availableTables)
|
||||
: super(availableTables, step);
|
||||
|
||||
final Map<CreateTriggerStatement, MoorTrigger> _triggers = {};
|
||||
final Map<TableInducingStatement, MoorTable> _tables = {};
|
||||
|
@ -31,8 +35,17 @@ class EntityHandler {
|
|||
trigger.on = null;
|
||||
|
||||
final declaration = trigger.declaration as MoorTriggerDeclaration;
|
||||
_triggers[declaration.node] = trigger;
|
||||
declaration.node.acceptWithoutArg(referenceResolver);
|
||||
final node = declaration.node;
|
||||
_triggers[node] = trigger;
|
||||
node.acceptWithoutArg(referenceResolver);
|
||||
|
||||
// triggers can have complex statements, so run the linter on them
|
||||
final context = engine.analyzeNode(node, file.parseResult.sql);
|
||||
context.errors.forEach(report);
|
||||
|
||||
final linter = Linter(context, mapper);
|
||||
linter.reportLints();
|
||||
reportLints(linter.lints, name: trigger.displayName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,8 +64,7 @@ class _ReferenceResolvingVisitor extends RecursiveVisitor<void, void> {
|
|||
_ReferenceResolvingVisitor(this.handler);
|
||||
|
||||
MoorTable _resolveTable(TableReference reference) {
|
||||
return handler.availableTables.singleWhere(
|
||||
(t) => t.sqlName == reference.tableName,
|
||||
return handler.tables.singleWhere((t) => t.sqlName == reference.tableName,
|
||||
orElse: () => null);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'package:moor_generator/src/analyzer/runner/file_graph.dart';
|
|||
import 'package:moor_generator/src/analyzer/runner/results.dart';
|
||||
import 'package:moor_generator/src/analyzer/moor/inline_dart_resolver.dart';
|
||||
import 'package:moor_generator/src/analyzer/moor/parser.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/sql_parser.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/query_analyzer.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
|
||||
import 'package:moor_generator/src/analyzer/runner/task.dart';
|
||||
import 'package:moor_generator/src/model/sql_query.dart';
|
||||
|
|
|
@ -40,7 +40,8 @@ class AnalyzeDartStep extends AnalyzingStep {
|
|||
.expand((f) => f.resolvedQueries);
|
||||
|
||||
final availableTables = availableEntities.whereType<MoorTable>().toList();
|
||||
final parser = SqlParser(this, availableTables, accessor.declaredQueries);
|
||||
final parser =
|
||||
SqlAnalyzer(this, availableTables, accessor.declaredQueries);
|
||||
parser.parse();
|
||||
|
||||
accessor
|
||||
|
|
|
@ -13,7 +13,7 @@ class AnalyzeMoorStep extends AnalyzingStep {
|
|||
.followedBy(parseResult.declaredTables)
|
||||
.toList();
|
||||
|
||||
final parser = SqlParser(this, availableTables, parseResult.queries)
|
||||
final parser = SqlAnalyzer(this, availableTables, parseResult.queries)
|
||||
..parse();
|
||||
|
||||
EntityHandler(this, parseResult, availableTables).handle();
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
import '../query_handler.dart';
|
||||
import '../type_mapping.dart';
|
||||
|
||||
/// Provides additional hints that aren't implemented in the sqlparser because
|
||||
/// they're specific to moor.
|
||||
class Linter {
|
||||
final QueryHandler handler;
|
||||
final AnalysisContext context;
|
||||
final TypeMapper mapper;
|
||||
final List<AnalysisError> lints = [];
|
||||
|
||||
Linter(this.handler);
|
||||
Linter(this.context, this.mapper);
|
||||
|
||||
Linter.forHandler(QueryHandler handler)
|
||||
: context = handler.context,
|
||||
mapper = handler.mapper;
|
||||
|
||||
void reportLints() {
|
||||
handler.context.root.acceptWithoutArg(_LintingVisitor(this));
|
||||
context.root.acceptWithoutArg(_LintingVisitor(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +35,7 @@ class _LintingVisitor extends RecursiveVisitor<void, void> {
|
|||
// to know the column name (e.g. it's a Dart template without an AS), or
|
||||
// if the type is unknown.
|
||||
final expression = e.expression;
|
||||
final resolveResult = linter.handler.context.typeOf(expression);
|
||||
final resolveResult = linter.context.typeOf(expression);
|
||||
|
||||
if (resolveResult.type == null) {
|
||||
linter.lints.add(AnalysisError(
|
||||
|
@ -86,8 +92,7 @@ class _LintingVisitor extends RecursiveVisitor<void, void> {
|
|||
);
|
||||
|
||||
// second, check that no required columns are left out
|
||||
final specifiedTable =
|
||||
linter.handler.mapper.tableToMoor(e.table.resolved as Table);
|
||||
final specifiedTable = linter.mapper.tableToMoor(e.table.resolved as Table);
|
||||
final required =
|
||||
specifiedTable.columns.where((c) => c.requiredDuringInsert).toList();
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:build/build.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moor_generator/moor_generator.dart';
|
||||
import 'package:moor_generator/src/analyzer/errors.dart';
|
||||
import 'package:moor_generator/src/analyzer/runner/file_graph.dart';
|
||||
|
@ -8,27 +9,59 @@ import 'package:moor_generator/src/analyzer/sql_queries/query_handler.dart';
|
|||
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
|
||||
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
||||
|
||||
class SqlParser {
|
||||
abstract class BaseAnalyzer {
|
||||
final List<MoorTable> tables;
|
||||
final Step step;
|
||||
final List<DeclaredQuery> definedQueries;
|
||||
|
||||
final TypeMapper _mapper = TypeMapper();
|
||||
@protected
|
||||
final TypeMapper mapper = TypeMapper();
|
||||
SqlEngine _engine;
|
||||
|
||||
BaseAnalyzer(this.tables, this.step);
|
||||
|
||||
@protected
|
||||
SqlEngine get engine {
|
||||
if (_engine == null) {
|
||||
_engine = step.task.session.spawnEngine();
|
||||
tables.map(mapper.extractStructure).forEach(_engine.registerTable);
|
||||
}
|
||||
return _engine;
|
||||
}
|
||||
|
||||
@protected
|
||||
void report(AnalysisError error, {String Function() msg, Severity severity}) {
|
||||
if (step.file.type == FileType.moor) {
|
||||
step.reportError(
|
||||
ErrorInMoorFile.fromSqlParser(error, overrideSeverity: severity));
|
||||
} else {
|
||||
step.reportError(MoorError(
|
||||
severity: severity,
|
||||
message: msg(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void reportLints(List<AnalysisError> lints, {String name}) {
|
||||
for (final lint in lints) {
|
||||
report(
|
||||
lint,
|
||||
msg: () => 'Lint for $name: $lint',
|
||||
severity: Severity.warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SqlAnalyzer extends BaseAnalyzer {
|
||||
final List<DeclaredQuery> definedQueries;
|
||||
|
||||
final List<SqlQuery> foundQueries = [];
|
||||
|
||||
SqlParser(this.step, this.tables, this.definedQueries) {
|
||||
_engine = step.task.session.spawnEngine();
|
||||
}
|
||||
|
||||
void _spawnEngine() {
|
||||
tables.map(_mapper.extractStructure).forEach(_engine.registerTable);
|
||||
}
|
||||
SqlAnalyzer(Step step, List<MoorTable> tables, this.definedQueries)
|
||||
: super(tables, step);
|
||||
|
||||
void parse() {
|
||||
_spawnEngine();
|
||||
|
||||
for (final query in definedQueries) {
|
||||
final name = query.name;
|
||||
var declaredInMoor = false;
|
||||
|
@ -38,9 +71,9 @@ class SqlParser {
|
|||
try {
|
||||
if (query is DeclaredDartQuery) {
|
||||
final sql = query.sql;
|
||||
context = _engine.analyze(sql);
|
||||
context = engine.analyze(sql);
|
||||
} else if (query is DeclaredMoorQuery) {
|
||||
context = _engine.analyzeNode(
|
||||
context = engine.analyzeNode(
|
||||
query.query,
|
||||
query.file.parseResult.sql,
|
||||
stmtOptions: _createOptions(query.astNode),
|
||||
|
@ -55,13 +88,13 @@ class SqlParser {
|
|||
}
|
||||
|
||||
for (final error in context.errors) {
|
||||
_report(error,
|
||||
report(error,
|
||||
msg: () => 'The sql query $name is invalid: $error',
|
||||
severity: Severity.error);
|
||||
}
|
||||
|
||||
try {
|
||||
final query = QueryHandler(name, context, _mapper).handle()
|
||||
final query = QueryHandler(name, context, mapper).handle()
|
||||
..declaredInMoorFile = declaredInMoor;
|
||||
foundQueries.add(query);
|
||||
} catch (e, s) {
|
||||
|
@ -72,29 +105,12 @@ class SqlParser {
|
|||
|
||||
// report lints
|
||||
for (final query in foundQueries) {
|
||||
for (final lint in query.lints) {
|
||||
_report(lint,
|
||||
msg: () => 'Lint for ${query.name}: $lint',
|
||||
severity: Severity.warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _report(AnalysisError error,
|
||||
{String Function() msg, Severity severity}) {
|
||||
if (step.file.type == FileType.moor) {
|
||||
step.reportError(
|
||||
ErrorInMoorFile.fromSqlParser(error, overrideSeverity: severity));
|
||||
} else {
|
||||
step.reportError(MoorError(
|
||||
severity: severity,
|
||||
message: msg(),
|
||||
));
|
||||
reportLints(query.lints, name: query.name);
|
||||
}
|
||||
}
|
||||
|
||||
AnalyzeStatementOptions _createOptions(DeclaredStatement stmt) {
|
||||
final reader = _engine.schemaReader;
|
||||
final reader = engine.schemaReader;
|
||||
final indexedHints = <int, ResolvedType>{};
|
||||
final namedHints = <String, ResolvedType>{};
|
||||
|
|
@ -30,7 +30,7 @@ class QueryHandler {
|
|||
_verifyNoSkippedIndexes();
|
||||
final query = _mapToMoor();
|
||||
|
||||
final linter = Linter(this);
|
||||
final linter = Linter.forHandler(this);
|
||||
linter.reportLints();
|
||||
query.lints = linter.lints;
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import 'package:moor_generator/src/analyzer/errors.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/query_handler.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../utils.dart';
|
||||
|
||||
void main() {
|
||||
final engine = SqlEngine(useMoorExtensions: true);
|
||||
final mapper = TypeMapper();
|
||||
|
@ -22,4 +25,48 @@ void main() {
|
|||
expect(moorQuery.lints,
|
||||
anyElement((AnalysisError q) => q.message.contains('Dart template')));
|
||||
});
|
||||
|
||||
group('warns about insert column count mismatch', () {
|
||||
test('in top-level queries', () async {
|
||||
final state = TestState.withContent({
|
||||
'foo|lib/a.moor': '''
|
||||
CREATE TABLE foo (
|
||||
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
context VARCHAR
|
||||
);
|
||||
|
||||
test: INSERT INTO foo VALUES (?)
|
||||
''',
|
||||
});
|
||||
|
||||
final file = await state.analyze('package:foo/a.moor');
|
||||
expect(
|
||||
file.errors.errors,
|
||||
contains(const TypeMatcher<MoorError>().having(
|
||||
(e) => e.message, 'message', 'Expected tuple to have 2 values')),
|
||||
);
|
||||
});
|
||||
|
||||
test('in CREATE TRIGGER statements', () async {
|
||||
final state = TestState.withContent({
|
||||
'foo|lib/a.moor': '''
|
||||
CREATE TABLE foo (
|
||||
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
context VARCHAR
|
||||
);
|
||||
|
||||
CREATE TRIGGER my_trigger AFTER DELETE ON foo BEGIN
|
||||
INSERT INTO foo VALUES (old.context);
|
||||
END;
|
||||
''',
|
||||
});
|
||||
|
||||
final file = await state.analyze('package:foo/a.moor');
|
||||
expect(
|
||||
file.errors.errors,
|
||||
contains(const TypeMatcher<MoorError>().having(
|
||||
(e) => e.message, 'message', 'Expected tuple to have 2 values')),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue