Better column name prediction for expressions

This commit is contained in:
Simon Binder 2019-06-27 15:30:29 +02:00
parent 6d54a21091
commit db92059610
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 40 additions and 11 deletions

View File

@ -12,6 +12,7 @@ part 'steps/column_resolver.dart';
part 'steps/reference_finder.dart';
part 'steps/reference_resolver.dart';
part 'steps/set_parent_visitor.dart';
part 'steps/type_resolver.dart';
part 'types/data.dart';
part 'types/resolution.dart';

View File

@ -3,8 +3,9 @@ part of 'analysis.dart';
class AnalysisContext {
final List<AnalysisError> errors = [];
final AstNode root;
final String sql;
AnalysisContext(this.root);
AnalysisContext(this.root, this.sql);
void reportError(AnalysisError error) {
errors.add(error);

View File

@ -75,8 +75,12 @@ class ColumnResolver extends RecursiveVisitor<void> {
return (c.expression as Reference).columnName;
}
// todo I think in this case it's just the literal lexeme?
return 'TODO';
// in this case it's just the literal expression. So for instance,
// "SELECT 3+ 5" has a result column called "3+ 5" (consecutive whitespace
// is removed).
final span = context.sql.substring(c.firstPosition, c.lastPosition);
// todo remove consecutive whitespace
return span;
}
void _resolveTableReference(TableReference r) {

View File

@ -0,0 +1,3 @@
part of '../analysis.dart';
class TypeResolver extends RecursiveVisitor<void> {}

View File

@ -24,6 +24,20 @@ abstract class AstNode {
/// by the analyzer after the tree has been parsed.
AstNode parent;
Token first;
Token last;
/// The first index in the source that belongs to this node
int get firstPosition => first.span.start.offset;
/// The last position that belongs to node, exclusive
int get lastPosition => last.span.end.offset;
void setSpan(Token first, Token last) {
this.first = first;
this.last = last;
}
Iterable<AstNode> get parents sync* {
var node = parent;
while (node != null) {

View File

@ -44,7 +44,7 @@ class SqlEngine {
final node = parse(sql);
const SetParentVisitor().startAtRoot(node);
final context = AnalysisContext(node);
final context = AnalysisContext(node, sql);
final scope = _constructRootScope();
ReferenceFinder(globalScope: scope).start(node);

View File

@ -99,6 +99,7 @@ class Parser {
/// https://www.sqlite.org/lang_select.html
SelectStatement select() {
if (!_match(const [TokenType.select])) return null;
final selectToken = _previous;
var distinct = false;
if (_matchOne(TokenType.distinct)) {
@ -127,14 +128,14 @@ class Parser {
groupBy: groupBy,
orderBy: orderBy,
limit: limit,
);
)..setSpan(selectToken, _previous);
}
/// Parses a [ResultColumn] or throws if none is found.
/// https://www.sqlite.org/syntax/result-column.html
ResultColumn _resultColumn() {
if (_match(const [TokenType.star])) {
return StarResultColumn(null);
return StarResultColumn(null)..setSpan(_previous, _previous);
}
final positionBefore = _current;
@ -146,7 +147,8 @@ class Parser {
final identifier = _previous;
if (_match(const [TokenType.dot]) && _match(const [TokenType.star])) {
return StarResultColumn((identifier as IdentifierToken).identifier);
return StarResultColumn((identifier as IdentifierToken).identifier)
..setSpan(identifier, _previous);
}
// not a star result column. go back and parse the expression.
@ -155,10 +157,13 @@ class Parser {
_current = positionBefore;
}
final tokenBefore = _peek;
final expr = expression();
final as = _as();
return ExpressionResultColumn(expression: expr, as: as?.identifier);
return ExpressionResultColumn(expression: expr, as: as?.identifier)
..setSpan(tokenBefore, _previous);
}
/// Returns an identifier followed after an optional "AS" token in sql.

View File

@ -21,13 +21,14 @@ void main() {
);
final engine = SqlEngine()..registerTable(demoTable);
final context = engine.analyze('SELECT id, d.content, * FROM demo AS d');
final context =
engine.analyze('SELECT id, d.content, *, 3 + 4 FROM demo AS d');
final select = context.root as SelectStatement;
final resolvedColumns = select.resolvedColumns;
expect(
resolvedColumns.map((c) => c.name), ['id', 'content', 'id', 'content']);
expect(resolvedColumns.map((c) => c.name),
['id', 'content', 'id', 'content', '3 + 4']);
final firstColumn = select.columns[0] as ExpressionResultColumn;
final secondColumn = select.columns[1] as ExpressionResultColumn;