mirror of https://github.com/AMT-Cheif/drift.git
235 lines
7.0 KiB
Dart
235 lines
7.0 KiB
Dart
import 'package:build/build.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:drift_dev/moor_generator.dart';
|
|
import 'package:drift_dev/src/analyzer/errors.dart';
|
|
import 'package:drift_dev/src/analyzer/runner/file_graph.dart';
|
|
import 'package:drift_dev/src/analyzer/runner/steps.dart';
|
|
import 'package:drift_dev/src/analyzer/sql_queries/lints/linter.dart';
|
|
import 'package:drift_dev/src/analyzer/sql_queries/query_handler.dart';
|
|
import 'package:drift_dev/src/analyzer/sql_queries/type_mapping.dart';
|
|
import 'package:drift_dev/src/model/sql_query.dart';
|
|
import 'package:drift_dev/src/model/view.dart';
|
|
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
|
import 'package:sqlparser/utils/find_referenced_tables.dart';
|
|
|
|
import 'required_variables.dart';
|
|
|
|
abstract class BaseAnalyzer {
|
|
final List<MoorTable> tables;
|
|
final List<MoorView> views;
|
|
final Step step;
|
|
|
|
@protected
|
|
final TypeMapper mapper;
|
|
SqlEngine? _engine;
|
|
|
|
BaseAnalyzer(this.tables, this.views, this.step)
|
|
: mapper = TypeMapper(
|
|
applyTypeConvertersToVariables:
|
|
step.task.session.options.applyConvertersOnVariables,
|
|
);
|
|
|
|
@protected
|
|
SqlEngine get engine {
|
|
if (_engine == null) {
|
|
final engine = _engine = step.task.session.spawnEngine();
|
|
tables.map(mapper.extractStructure).forEach(engine.registerTable);
|
|
views.map(mapper.extractView).forEach(engine.registerView);
|
|
}
|
|
return _engine!;
|
|
}
|
|
|
|
@protected
|
|
Iterable<MoorSchemaEntity> findReferences(AstNode node,
|
|
{bool includeViews = true}) {
|
|
final finder = ReferencedTablesVisitor();
|
|
node.acceptWithoutArg(finder);
|
|
|
|
var entities =
|
|
finder.foundTables.map<MoorSchemaEntity?>(mapper.tableToMoor);
|
|
if (includeViews) {
|
|
entities = entities.followedBy(finder.foundViews.map(mapper.viewToMoor));
|
|
}
|
|
|
|
return entities.whereType();
|
|
}
|
|
|
|
@protected
|
|
void lintContext(AnalysisContext context, String displayName) {
|
|
context.errors.forEach(report);
|
|
|
|
// Additional, moor-specific analysis
|
|
final linter = Linter(context, mapper);
|
|
linter.reportLints();
|
|
reportLints(linter.lints, name: displayName);
|
|
}
|
|
|
|
@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 = [];
|
|
|
|
SqlAnalyzer(Step step, List<MoorTable> tables, List<MoorView> views,
|
|
this.definedQueries)
|
|
: super(tables, views, step);
|
|
|
|
void parse() {
|
|
for (final query in definedQueries) {
|
|
final name = query.name;
|
|
|
|
AnalysisContext? context;
|
|
var requiredVariables = RequiredVariables.empty;
|
|
|
|
try {
|
|
if (query is DeclaredDartQuery) {
|
|
final sql = query.sql;
|
|
context = engine.analyze(sql);
|
|
} else if (query is DeclaredMoorQuery) {
|
|
final options = _createOptionsAndVars(query.astNode);
|
|
final statements = query.query;
|
|
|
|
if (statements.length > 1) {
|
|
_handleMultiStatementQuery(query, statements, options);
|
|
continue;
|
|
} else {
|
|
requiredVariables = options.variables;
|
|
context = engine.analyzeNode(
|
|
statements.single,
|
|
query.file!.parseResult.sql,
|
|
stmtOptions: options.options,
|
|
);
|
|
}
|
|
}
|
|
} catch (e, s) {
|
|
step.reportError(MoorError(
|
|
severity: Severity.criticalError,
|
|
message: 'Error while trying to parse $name: $e, $s'));
|
|
continue;
|
|
}
|
|
|
|
_handleQuery(query, context!, mapper, requiredVariables);
|
|
}
|
|
|
|
// report lints
|
|
for (final query in foundQueries) {
|
|
reportLints(query.lints ?? const <Never>[], name: query.name);
|
|
}
|
|
}
|
|
|
|
void _handleMultiStatementQuery(
|
|
DeclaredMoorQuery query,
|
|
Iterable<CrudStatement> statements,
|
|
_OptionsAndRequiredVariables options,
|
|
) {
|
|
for (final stmt in statements) {
|
|
final context = engine.analyzeNode(
|
|
stmt,
|
|
query.file!.parseResult.sql,
|
|
stmtOptions: options.options,
|
|
);
|
|
|
|
_handleQuery(query, context, mapper, options.variables);
|
|
}
|
|
}
|
|
|
|
void _handleQuery(DeclaredQuery query, AnalysisContext context,
|
|
TypeMapper mapper, RequiredVariables variables) {
|
|
final name = query.name;
|
|
for (final error in context.errors) {
|
|
report(error,
|
|
msg: () => 'The sql query $name is invalid: $error',
|
|
severity: Severity.error);
|
|
}
|
|
|
|
try {
|
|
final handled =
|
|
QueryHandler(query, context, mapper, requiredVariables: variables)
|
|
.handle()
|
|
..declaredInMoorFile = query is DeclaredMoorQuery;
|
|
foundQueries.add(handled);
|
|
} catch (e, s) {
|
|
// todo remove dependency on build package here
|
|
log.warning('Error while generating APIs for $name', e, s);
|
|
}
|
|
}
|
|
|
|
_OptionsAndRequiredVariables _createOptionsAndVars(DeclaredStatement stmt) {
|
|
final reader = engine.schemaReader;
|
|
final indexedHints = <int, ResolvedType>{};
|
|
final namedHints = <String, ResolvedType>{};
|
|
final defaultValues = <String, Expression>{};
|
|
final requiredIndex = <int>{};
|
|
final requiredName = <String>{};
|
|
|
|
for (final parameter in stmt.parameters) {
|
|
if (parameter is VariableTypeHint) {
|
|
final variable = parameter.variable;
|
|
|
|
if (parameter.isRequired) {
|
|
if (variable is ColonNamedVariable) {
|
|
requiredName.add(variable.name);
|
|
} else if (variable is NumberedVariable) {
|
|
requiredIndex.add(variable.resolvedIndex!);
|
|
}
|
|
}
|
|
|
|
if (parameter.typeName != null) {
|
|
final type = reader
|
|
.resolveColumnType(parameter.typeName)
|
|
.withNullable(parameter.orNull);
|
|
|
|
if (variable is ColonNamedVariable) {
|
|
namedHints[variable.name] = type;
|
|
} else if (variable is NumberedVariable) {
|
|
indexedHints[variable.resolvedIndex!] = type;
|
|
}
|
|
}
|
|
} else if (parameter is DartPlaceholderDefaultValue) {
|
|
defaultValues[parameter.variableName] = parameter.defaultValue;
|
|
}
|
|
}
|
|
|
|
return _OptionsAndRequiredVariables(
|
|
AnalyzeStatementOptions(
|
|
indexedVariableTypes: indexedHints,
|
|
namedVariableTypes: namedHints,
|
|
defaultValuesForPlaceholder: defaultValues,
|
|
),
|
|
RequiredVariables(requiredIndex, requiredName),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _OptionsAndRequiredVariables {
|
|
final AnalyzeStatementOptions options;
|
|
final RequiredVariables variables;
|
|
|
|
_OptionsAndRequiredVariables(this.options, this.variables);
|
|
}
|