mirror of https://github.com/AMT-Cheif/drift.git
Provide better error messages at unknown tables
This commit is contained in:
parent
37672dad2d
commit
4b0add64de
|
@ -1,6 +1,7 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||
|
||||
|
|
|
@ -7,20 +7,49 @@ class AnalysisError {
|
|||
|
||||
AnalysisError({@required this.type, this.message, this.relevantNode});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
/// The relevant portion of the source code that caused this error. Some AST
|
||||
/// nodes don't have a span, in that case this error is going to be null.
|
||||
SourceSpan get span {
|
||||
final first = relevantNode?.first?.span;
|
||||
final last = relevantNode?.last?.span;
|
||||
|
||||
if (first != null && last != null) {
|
||||
final span = first.expand(last);
|
||||
return span.message(message ?? type.toString(), color: true);
|
||||
return first.expand(last);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final msgSpan = span;
|
||||
if (msgSpan != null) {
|
||||
return msgSpan.message(message ?? type.toString(), color: true);
|
||||
} else {
|
||||
return 'Error: $type: $message at $relevantNode';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UnresolvedReferenceError extends AnalysisError {
|
||||
/// The attempted reference that couldn't be resolved
|
||||
final String reference;
|
||||
|
||||
/// A list of alternative references that would be available for [reference].
|
||||
final Iterable<String> available;
|
||||
|
||||
UnresolvedReferenceError(
|
||||
{@required AnalysisErrorType type,
|
||||
this.reference,
|
||||
this.available,
|
||||
AstNode relevantNode})
|
||||
: super(type: type, relevantNode: relevantNode);
|
||||
|
||||
@override
|
||||
String get message {
|
||||
return 'Could not find $reference. Available are: ${available.join(', ')}';
|
||||
}
|
||||
}
|
||||
|
||||
enum AnalysisErrorType {
|
||||
referencedUnknownTable,
|
||||
referencedUnknownColumn,
|
||||
|
|
|
@ -13,7 +13,7 @@ mixin Referencable {}
|
|||
/// many things, basically only tables.
|
||||
///
|
||||
/// For instance: "SELECT *, 1 AS d, (SELECT id FROM demo WHERE id = out.id) FROM demo AS out;"
|
||||
/// is a valid sql query when the demo table as an id column. However,
|
||||
/// is a valid sql query when the demo table has an id column. However,
|
||||
/// "SELECT *, 1 AS d, (SELECT id FROM demo WHERE id = d) FROM demo AS out;" is
|
||||
/// not, the "d" referencable is not visible for the child select statement.
|
||||
mixin VisibleToChildren on Referencable {}
|
||||
|
@ -79,12 +79,20 @@ class ReferenceScope {
|
|||
/// Returns everything that is in scope and a subtype of [T].
|
||||
List<T> allOf<T>() {
|
||||
var scope = this;
|
||||
var isInCurrentScope = true;
|
||||
final collected = <T>[];
|
||||
|
||||
while (scope != null) {
|
||||
collected.addAll(
|
||||
scope._references.values.expand((list) => list).whereType<T>());
|
||||
var foundValues =
|
||||
scope._references.values.expand((list) => list).whereType<T>();
|
||||
|
||||
if (!isInCurrentScope) {
|
||||
foundValues = foundValues.whereType<VisibleToChildren>().cast();
|
||||
}
|
||||
|
||||
collected.addAll(foundValues);
|
||||
scope = scope.parent;
|
||||
isInCurrentScope = false;
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
|
|
|
@ -115,10 +115,13 @@ class ColumnResolver extends RecursiveVisitor<void> {
|
|||
Table _resolveTableReference(TableReference r) {
|
||||
final scope = r.scope;
|
||||
final resolvedTable = scope.resolve<Table>(r.tableName, orElse: () {
|
||||
context.reportError(AnalysisError(
|
||||
final available = scope.allOf<Table>().map((t) => t.name);
|
||||
|
||||
context.reportError(UnresolvedReferenceError(
|
||||
type: AnalysisErrorType.referencedUnknownTable,
|
||||
relevantNode: r,
|
||||
message: 'The table ${r.tableName} could not be found',
|
||||
reference: r.tableName,
|
||||
available: available,
|
||||
));
|
||||
});
|
||||
return r.resolved = resolvedTable;
|
||||
|
|
|
@ -23,4 +23,13 @@ class Reference extends Expression with ReferenceOwner {
|
|||
bool contentEquals(Reference other) {
|
||||
return other.tableName == tableName && other.columnName == columnName;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (tableName != null) {
|
||||
return '$tableName.$columnName';
|
||||
} else {
|
||||
return columnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,9 +130,11 @@ mixin CrudParser on ParserBase {
|
|||
if (_matchOne(TokenType.identifier)) {
|
||||
// ignore the schema name, it's not supported. Besides that, we're on the
|
||||
// first branch in the diagram here
|
||||
final tableName = (_previous as IdentifierToken).identifier;
|
||||
final firstToken = _previous as IdentifierToken;
|
||||
final tableName = firstToken.identifier;
|
||||
final alias = _as();
|
||||
return TableReference(tableName, alias?.identifier);
|
||||
return TableReference(tableName, alias?.identifier)
|
||||
..setSpan(firstToken, _previous);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue