Update documentation, fix multiline parsing

This commit is contained in:
Simon Binder 2019-07-01 21:20:59 +02:00
parent b2f79e97e6
commit a45d73a6cf
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
10 changed files with 131 additions and 32 deletions

View File

@ -141,3 +141,6 @@ now run your queries with fluent Dart code:
}
}
```
Visit the detailed [documentation](https://moor.simonbinder.eu/) to learn about advanced
features like transactions, DAOs, custom queries and more.

View File

@ -5,7 +5,7 @@ the sqlite dialect and some advanced features aren't supported yet.
## Features
This library can parse most statements and perform type analysis for parameters and returned
columns. It supports joins, `group by`, nested sql statements, updates and deletes and more.
columns. It supports joins, `group by`, nested sql statements, updates and deletes, and more.
### Just parsing
You can parse the abstract syntax tree of sqlite statements with `SqlEngine.parse`.
```dart
@ -30,7 +30,7 @@ Given information about all tables and a sql statement, this library can:
2. Make an educated guess about what type the variables in the query should have (it's not really
possible to be 100% accurate about this because sqlite is very flexible at types, but this library
gets it mostly right)
3. Issue warnings about queries that are syntactically valid but won't run (references unknown
3. Issue basic warnings about queries that are syntactically valid but won't run (references unknown
tables / columns, uses undefined functions, etc.)
To use the analyzer, first register all known tables via `SqlEngine.registerTable`. Then,

View File

@ -1 +1,81 @@
// todo write example
import 'package:sqlparser/sqlparser.dart';
// Example that parses a select statement on some tables defined below and
// prints what columns would be returned by that statement.
void main() {
final engine = SqlEngine()
..registerTable(frameworks)
..registerTable(languages)
..registerTable(frameworkToLanguage);
final result = engine.analyze('''
SELECT f.* FROM frameworks f
INNER JOIN uses_language ul ON ul.framework = f.id
INNER JOIN languages l ON l.id = ul.language
WHERE l.name = 'Dart'
ORDER BY f.name ASC, f.popularity DESC
LIMIT 5 OFFSET 5 * 3
''');
for (var error in result.errors) {
print(error);
}
final select = result.root as SelectStatement;
final columns = select.resolvedColumns;
print('the query returns ${columns.length} columns');
for (var column in columns) {
final type = result.typeOf(column);
print('${column.name}, which will be a $type');
}
}
// declare some tables. I know this is verbose and boring, but it's needed so
// that the analyzer knows what's going on.
final Table frameworks = Table(
name: 'frameworks',
resolvedColumns: [
TableColumn(
'id',
const ResolvedType(type: BasicType.int),
),
TableColumn(
'name',
const ResolvedType(type: BasicType.text),
),
TableColumn(
'popularity',
const ResolvedType(type: BasicType.real),
),
],
);
final Table languages = Table(
name: 'languages',
resolvedColumns: [
TableColumn(
'id',
const ResolvedType(type: BasicType.int),
),
TableColumn(
'name',
const ResolvedType(type: BasicType.text),
),
],
);
final Table frameworkToLanguage = Table(
name: 'uses_language',
resolvedColumns: [
TableColumn(
'framework',
const ResolvedType(type: BasicType.int),
),
TableColumn(
'language',
const ResolvedType(type: BasicType.int),
),
],
);

View File

@ -6,6 +6,19 @@ class AnalysisError {
final AnalysisErrorType type;
AnalysisError({@required this.type, this.message, this.relevantNode});
@override
String toString() {
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);
} else {
return 'Error: $type: $message at $relevantNode';
}
}
}
enum AnalysisErrorType {

View File

@ -2,7 +2,6 @@ part of '../ast.dart';
/// Marker interface for something that can appear after a "FROM" in a select
/// statement.
@sealed
abstract class Queryable extends AstNode {
@override
T accept<T>(AstVisitor<T> visitor) => visitor.visitQueryable(this);
@ -143,7 +142,6 @@ class Join extends AstNode {
}
/// https://www.sqlite.org/syntax/join-constraint.html
@sealed
abstract class JoinConstraint {}
class OnConstraint extends JoinConstraint {

View File

@ -55,11 +55,15 @@ class SqlEngine {
final context = AnalysisContext(node, sql);
final scope = _constructRootScope();
ReferenceFinder(globalScope: scope).start(node);
node
..accept(ColumnResolver(context))
..accept(ReferenceResolver(context))
..accept(TypeResolvingVisitor(context));
try {
ReferenceFinder(globalScope: scope).start(node);
node
..accept(ColumnResolver(context))
..accept(ReferenceResolver(context))
..accept(TypeResolvingVisitor(context));
} catch (e) {
// todo should we do now? Mostly, everything
}
return context;
}

View File

@ -667,15 +667,18 @@ class Parser {
_consume(TokenType.identifier, 'Expected a column name here')
as IdentifierToken;
return Reference(
tableName: first.identifier, columnName: second.identifier);
tableName: first.identifier, columnName: second.identifier)
..setSpan(first, second);
} else if (_matchOne(TokenType.leftParen)) {
final parameters = _functionParameters();
_consume(TokenType.rightParen,
final rightParen = _consume(TokenType.rightParen,
'Expected closing bracket after argument list');
return FunctionExpression(
name: first.identifier, parameters: parameters);
name: first.identifier, parameters: parameters)
..setSpan(first, rightParen);
} else {
return Reference(columnName: first.identifier);
return Reference(columnName: first.identifier)..setSpan(first, first);
}
break;
case TokenType.questionMark:

View File

@ -4,6 +4,7 @@ import 'package:sqlparser/src/reader/tokenizer/utils.dart';
class Scanner {
final String source;
final SourceFile _file;
final List<Token> tokens = [];
final List<TokenizerError> errors = [];
@ -12,20 +13,15 @@ class Scanner {
int _currentOffset = 0;
bool get _isAtEnd => _currentOffset >= source.length;
SourceSpan get _currentSpan {
return SourceSpanWithContext(_startLocation, _currentLocation,
source.substring(_startOffset, _currentOffset), source);
}
SourceLocation get _startLocation {
return SourceLocation(_startOffset);
FileSpan get _currentSpan {
return _file.span(_startOffset, _currentOffset);
}
SourceLocation get _currentLocation {
return SourceLocation(_currentOffset);
return _file.location(_currentOffset);
}
Scanner(this.source);
Scanner(this.source) : _file = SourceFile.fromString(source);
List<Token> scanTokens() {
while (!_isAtEnd) {
@ -33,8 +29,8 @@ class Scanner {
_scanToken();
}
final endLoc = SourceLocation(source.length);
tokens.add(Token(TokenType.eof, SourceSpan(endLoc, endLoc, '')));
final endSpan = _file.span(source.length);
tokens.add(Token(TokenType.eof, endSpan));
return tokens;
}

View File

@ -157,7 +157,7 @@ const Map<String, TokenType> keywords = {
class Token {
final TokenType type;
final SourceSpan span;
final FileSpan span;
String get lexeme => span.text;
const Token(this.type, this.span);
@ -174,7 +174,7 @@ class StringLiteralToken extends Token {
/// sqlite allows binary strings (x'literal') which are interpreted as blobs.
final bool binary;
const StringLiteralToken(this.value, SourceSpan span, {this.binary = false})
const StringLiteralToken(this.value, FileSpan span, {this.binary = false})
: super(TokenType.stringLiteral, span);
}
@ -190,7 +190,7 @@ class IdentifierToken extends Token {
}
}
const IdentifierToken(this.escaped, SourceSpan span)
const IdentifierToken(this.escaped, FileSpan span)
: super(TokenType.identifier, span);
}

View File

@ -1,16 +1,18 @@
name: sqlparser
description: Parsing and analysis for sql queries
description: Parses sqlite statements and performs static analysis on them
version: 0.1.0
repository: https://github.com/simolus3/moor/tree/develop/sqlparser
homepage: https://github.com/simolus3/moor/tree/develop/sqlparser
#homepage: https://moor.simonbinder.eu/
issue_tracker: https://github.com/simolus3/moor/issues
author: Simon Binder <oss@simonbinder.eu>
environment:
sdk: '>=2.2.2 <3.0.0'
meta: ^1.1.7
dependencies:
meta: ^1.1.0
collection: ^1.14.11
source_span: ^1.5.5
dev_dependencies:
test: ^1.0.0
test: ^1.6.0