Provide better error messages at unknown tables

This commit is contained in:
Simon Binder 2019-08-26 22:26:38 +02:00
parent 37672dad2d
commit 4b0add64de
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
6 changed files with 63 additions and 11 deletions

View File

@ -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';

View File

@ -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,

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;
}