mirror of https://github.com/AMT-Cheif/drift.git
Improve documentation of the sqlparser library
This commit is contained in:
parent
d9f5cf0e69
commit
ff530dd4ea
|
@ -60,6 +60,10 @@ class SqlParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnType _resolvedToMoor(ResolvedType type) {
|
ColumnType _resolvedToMoor(ResolvedType type) {
|
||||||
|
if (type == null) {
|
||||||
|
return ColumnType.text;
|
||||||
|
}
|
||||||
|
|
||||||
switch (type.type) {
|
switch (type.type) {
|
||||||
case BasicType.nullType:
|
case BasicType.nullType:
|
||||||
return ColumnType.text;
|
return ColumnType.text;
|
||||||
|
@ -87,7 +91,14 @@ class SqlParser {
|
||||||
final name = query.getField('name').toStringValue();
|
final name = query.getField('name').toStringValue();
|
||||||
final sql = query.getField('query').toStringValue();
|
final sql = query.getField('query').toStringValue();
|
||||||
|
|
||||||
final context = _engine.analyze(sql);
|
AnalysisContext context;
|
||||||
|
try {
|
||||||
|
context = _engine.analyze(sql);
|
||||||
|
} catch (e, s) {
|
||||||
|
errors.add(MoorError(
|
||||||
|
critical: true,
|
||||||
|
message: 'Error while trying to parse $sql: $e, $s'));
|
||||||
|
}
|
||||||
|
|
||||||
for (var error in context.errors) {
|
for (var error in context.errors) {
|
||||||
errors.add(MoorError(
|
errors.add(MoorError(
|
||||||
|
@ -99,7 +110,7 @@ class SqlParser {
|
||||||
if (root is SelectStatement) {
|
if (root is SelectStatement) {
|
||||||
_handleSelect(name, root, context);
|
_handleSelect(name, root, context);
|
||||||
} else {
|
} else {
|
||||||
throw StateError('Unexpected sql');
|
throw StateError('Unexpected sql, expected a select statement');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ You can parse the abstract syntax tree of sqlite statements with `SqlEngine.pars
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
|
|
||||||
final engine = SqlEngine();
|
final engine = SqlEngine();
|
||||||
final stmt = engine.parse('''
|
final result = engine.parse('''
|
||||||
SELECT f.* FROM frameworks f
|
SELECT f.* FROM frameworks f
|
||||||
INNER JOIN uses_language ul ON ul.framework = f.id
|
INNER JOIN uses_language ul ON ul.framework = f.id
|
||||||
INNER JOIN languages l ON l.id = ul.language
|
INNER JOIN languages l ON l.id = ul.language
|
||||||
|
@ -21,6 +21,7 @@ WHERE l.name = 'Dart'
|
||||||
ORDER BY f.name ASC, f.popularity DESC
|
ORDER BY f.name ASC, f.popularity DESC
|
||||||
LIMIT 5 OFFSET 5 * 3
|
LIMIT 5 OFFSET 5 * 3
|
||||||
''');
|
''');
|
||||||
|
// result.rootNode contains the select statement in tree form
|
||||||
```
|
```
|
||||||
|
|
||||||
### Analysis
|
### Analysis
|
||||||
|
@ -55,8 +56,15 @@ resolvedColumns.map((c) => c.name)); // id, content, id, content, 3 + 4
|
||||||
resolvedColumns.map((c) => context.typeOf(c).type.type) // int, text, int, text, int, int
|
resolvedColumns.map((c) => context.typeOf(c).type.type) // int, text, int, text, int, int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
- For now, only `SELECT` and `DELETE` expressions are implemented, `UPDATE` and `INSERT` will follow
|
||||||
|
soon.
|
||||||
|
- Windowing is not supported yet
|
||||||
|
- Common table expressions and compound select statements `UNION` / `INTERSECT` are not supported
|
||||||
|
and probably won't be in the near future.
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
- To [Bob Nystrom](https://github.com/munificent) for his amazing ["Crafting Interpreters"](https://craftinginterpreters.com/)
|
- To [Bob Nystrom](https://github.com/munificent) for his amazing ["Crafting Interpreters"](https://craftinginterpreters.com/)
|
||||||
book, which was incredibly helpful when writing the parser.
|
book, which was incredibly helpful when writing the parser.
|
||||||
- All authors of [SQLDelight](https://github.com/square/sqldelight). This library uses their algorithm
|
- To the authors of [SQLDelight](https://github.com/square/sqldelight). This library uses their algorithm
|
||||||
for type inference.
|
for type inference.
|
||||||
|
|
|
@ -4,4 +4,4 @@ library sqlparser;
|
||||||
export 'src/analysis/analysis.dart';
|
export 'src/analysis/analysis.dart';
|
||||||
export 'src/ast/ast.dart';
|
export 'src/ast/ast.dart';
|
||||||
export 'src/engine/sql_engine.dart';
|
export 'src/engine/sql_engine.dart';
|
||||||
export 'src/reader/tokenizer/token.dart';
|
export 'src/reader/tokenizer/token.dart' show CumulatedTokenizerException;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
|
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||||
|
|
||||||
part 'schema/column.dart';
|
part 'schema/column.dart';
|
||||||
part 'schema/references.dart';
|
part 'schema/references.dart';
|
||||||
|
|
|
@ -1,16 +1,32 @@
|
||||||
part of 'analysis.dart';
|
part of 'analysis.dart';
|
||||||
|
|
||||||
|
/// Result of parsing and analyzing an sql statement. Contains the AST with
|
||||||
|
/// resolved references, information about result columns and errors that were
|
||||||
|
/// reported during analysis.
|
||||||
class AnalysisContext {
|
class AnalysisContext {
|
||||||
|
/// All errors that occurred during analysis
|
||||||
final List<AnalysisError> errors = [];
|
final List<AnalysisError> errors = [];
|
||||||
|
|
||||||
|
/// The root node of the abstract syntax tree
|
||||||
final AstNode root;
|
final AstNode root;
|
||||||
|
|
||||||
|
/// The raw sql statement that was used to construct this [AnalysisContext].
|
||||||
final String sql;
|
final String sql;
|
||||||
|
|
||||||
|
/// A resolver that can be used to obtain the type of a [Typeable]. This
|
||||||
|
/// mostly applies to [Expression]s, [Reference]s, [Variable]s and
|
||||||
|
/// [ResultSet.resolvedColumns] of a select statement.
|
||||||
final TypeResolver types = TypeResolver();
|
final TypeResolver types = TypeResolver();
|
||||||
|
|
||||||
|
/// Constructs a new analysis context from the AST and the source sql.
|
||||||
AnalysisContext(this.root, this.sql);
|
AnalysisContext(this.root, this.sql);
|
||||||
|
|
||||||
|
/// Reports an analysis error.
|
||||||
void reportError(AnalysisError error) {
|
void reportError(AnalysisError error) {
|
||||||
errors.add(error);
|
errors.add(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Obtains the result of any typeable component. See the information at
|
||||||
|
/// [types] on important [Typeable]s.
|
||||||
ResolveResult typeOf(Typeable t) => types.resolveOrInfer(t);
|
ResolveResult typeOf(Typeable t) => types.resolveOrInfer(t);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,34 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
|
/// A column that appears in a [ResultSet]. Has a type and a name.
|
||||||
abstract class Column with Referencable implements Typeable {
|
abstract class Column with Referencable implements Typeable {
|
||||||
|
/// The name of this column in the result set.
|
||||||
String get name;
|
String get name;
|
||||||
|
|
||||||
const Column();
|
const Column();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A column that is part of a table.
|
||||||
class TableColumn extends Column {
|
class TableColumn extends Column {
|
||||||
@override
|
@override
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
|
/// The type of this column, which is immediately available.
|
||||||
final ResolvedType type;
|
final ResolvedType type;
|
||||||
|
|
||||||
|
/// The table this column belongs to.
|
||||||
Table table;
|
Table table;
|
||||||
|
|
||||||
TableColumn(this.name, this.type);
|
TableColumn(this.name, this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A column that is created by an expression. For instance, in the select
|
||||||
|
/// statement "SELECT 1 + 3", there is a column called "1 + 3" of type int.
|
||||||
class ExpressionColumn extends Column {
|
class ExpressionColumn extends Column {
|
||||||
@override
|
@override
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
|
/// The expression returned by this column.
|
||||||
final Expression expression;
|
final Expression expression;
|
||||||
|
|
||||||
ExpressionColumn({@required this.name, this.expression});
|
ExpressionColumn({@required this.name, this.expression});
|
||||||
|
|
|
@ -44,6 +44,7 @@ class ReferenceScope {
|
||||||
return ReferenceScope(this, root: effectiveRoot);
|
return ReferenceScope(this, root: effectiveRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers something that can be referenced in this and child scopes.
|
||||||
void register(String identifier, Referencable ref) {
|
void register(String identifier, Referencable ref) {
|
||||||
_references.putIfAbsent(identifier.toUpperCase(), () => []).add(ref);
|
_references.putIfAbsent(identifier.toUpperCase(), () => []).add(ref);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
/// Something that will resolve to a result set.
|
/// Something that will resolve to an [ResultSet] when referred to via
|
||||||
|
/// the [ReferenceScope].
|
||||||
abstract class ResolvesToResultSet with Referencable {
|
abstract class ResolvesToResultSet with Referencable {
|
||||||
ResultSet get resultSet;
|
ResultSet get resultSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Something that returns a set of columns when evaluated.
|
||||||
abstract class ResultSet implements ResolvesToResultSet {
|
abstract class ResultSet implements ResolvesToResultSet {
|
||||||
|
/// The columns that will be returned when evaluating this query.
|
||||||
List<Column> get resolvedColumns;
|
List<Column> get resolvedColumns;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -17,12 +20,17 @@ abstract class ResultSet implements ResolvesToResultSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A database table. The information stored here will be used to resolve
|
||||||
|
/// references and for type inference.
|
||||||
class Table with ResultSet, VisibleToChildren {
|
class Table with ResultSet, VisibleToChildren {
|
||||||
|
/// The name of this table, as it appears in sql statements. This should be
|
||||||
|
/// the raw name, not an escaped version.
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final List<TableColumn> resolvedColumns;
|
final List<TableColumn> resolvedColumns;
|
||||||
|
|
||||||
|
/// Constructs a table from the known [name] and [resolvedColumns].
|
||||||
Table({@required this.name, this.resolvedColumns}) {
|
Table({@required this.name, this.resolvedColumns}) {
|
||||||
for (var column in resolvedColumns) {
|
for (var column in resolvedColumns) {
|
||||||
column.table = this;
|
column.table = this;
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
|
/// Walks the AST and, for each select statement it sees, finds out which
|
||||||
|
/// columns are returned and which columns are available. For instance, when
|
||||||
|
/// we have a table "t" with two columns "a" and "b", the select statement
|
||||||
|
/// "SELECT a FROM t" has one result column but two columns available.
|
||||||
class ColumnResolver extends RecursiveVisitor<void> {
|
class ColumnResolver extends RecursiveVisitor<void> {
|
||||||
final AnalysisContext context;
|
final AnalysisContext context;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
|
/// Resolves any open [Reference] it finds in the AST.
|
||||||
class ReferenceResolver extends RecursiveVisitor<void> {
|
class ReferenceResolver extends RecursiveVisitor<void> {
|
||||||
final AnalysisContext context;
|
final AnalysisContext context;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
|
/// Resolves the type of columns in a select statement and the type of
|
||||||
|
/// expressions appearing in a select statement.
|
||||||
class TypeResolvingVisitor extends RecursiveVisitor<void> {
|
class TypeResolvingVisitor extends RecursiveVisitor<void> {
|
||||||
final AnalysisContext context;
|
final AnalysisContext context;
|
||||||
TypeResolver get types => context.types;
|
TypeResolver get types => context.types;
|
||||||
|
|
|
@ -11,6 +11,10 @@ enum BasicType {
|
||||||
|
|
||||||
class ResolvedType {
|
class ResolvedType {
|
||||||
final BasicType type;
|
final BasicType type;
|
||||||
|
|
||||||
|
/// We set hints for additional information that might be useful for
|
||||||
|
/// applications but aren't covered by just exposing a [BasicType]. See the
|
||||||
|
/// comment on [TypeHint] for examples.
|
||||||
final TypeHint hint;
|
final TypeHint hint;
|
||||||
final bool nullable;
|
final bool nullable;
|
||||||
|
|
||||||
|
@ -39,15 +43,18 @@ class ResolvedType {
|
||||||
|
|
||||||
/// Provides more precise hints than the [BasicType]. For instance, booleans are
|
/// Provides more precise hints than the [BasicType]. For instance, booleans are
|
||||||
/// stored as ints in sqlite, but it might be desirable to know whether an
|
/// stored as ints in sqlite, but it might be desirable to know whether an
|
||||||
/// expression will actually be a boolean.
|
/// expression will actually be a boolean, so we could set the
|
||||||
|
/// [ResolvedType.hint] to [IsBoolean].
|
||||||
abstract class TypeHint {
|
abstract class TypeHint {
|
||||||
const TypeHint();
|
const TypeHint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type hint to mark that this type will contain a boolean value.
|
||||||
class IsBoolean extends TypeHint {
|
class IsBoolean extends TypeHint {
|
||||||
const IsBoolean();
|
const IsBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type hint to mark that this type will contain a date time value.
|
||||||
class IsDateTime extends TypeHint {
|
class IsDateTime extends TypeHint {
|
||||||
const IsDateTime();
|
const IsDateTime();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
const comparisonOperators = [
|
const _comparisonOperators = [
|
||||||
TokenType.equal,
|
TokenType.equal,
|
||||||
TokenType.doubleEqual,
|
TokenType.doubleEqual,
|
||||||
TokenType.exclamationEqual,
|
TokenType.exclamationEqual,
|
||||||
|
@ -82,7 +82,7 @@ class TypeResolver {
|
||||||
return const ResolveResult(ResolvedType.bool());
|
return const ResolveResult(ResolvedType.bool());
|
||||||
} else if (expr is BinaryExpression) {
|
} else if (expr is BinaryExpression) {
|
||||||
final operator = expr.operator.type;
|
final operator = expr.operator.type;
|
||||||
if (comparisonOperators.contains(operator)) {
|
if (_comparisonOperators.contains(operator)) {
|
||||||
return const ResolveResult(ResolvedType.bool());
|
return const ResolveResult(ResolvedType.bool());
|
||||||
} else {
|
} else {
|
||||||
final type = _encapsulate(expr.childNodes.cast(),
|
final type = _encapsulate(expr.childNodes.cast(),
|
||||||
|
@ -277,9 +277,22 @@ class TypeResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Result of resolving a type. This can either have the resolved [type] set,
|
||||||
|
/// or it can inform the called that it [needsContext] to resolve the type
|
||||||
|
/// properly. Failure to resolve the type will have the [unknown] flag set.
|
||||||
|
///
|
||||||
|
/// When you see a [ResolveResult] that is unknown or needs context in the
|
||||||
|
/// final AST returned by [SqlEngine.analyze], assume that the type cannot be
|
||||||
|
/// determined.
|
||||||
class ResolveResult {
|
class ResolveResult {
|
||||||
|
/// The resolved type.
|
||||||
final ResolvedType type;
|
final ResolvedType type;
|
||||||
|
|
||||||
|
/// Whether more context is needed to resolve the type. Used internally by the
|
||||||
|
/// analyze.
|
||||||
final bool needsContext;
|
final bool needsContext;
|
||||||
|
|
||||||
|
/// Whether type resolution failed.
|
||||||
final bool unknown;
|
final bool unknown;
|
||||||
|
|
||||||
const ResolveResult(this.type)
|
const ResolveResult(this.type)
|
||||||
|
@ -296,6 +309,9 @@ class ResolveResult {
|
||||||
|
|
||||||
bool get nullable => type?.nullable ?? true;
|
bool get nullable => type?.nullable ?? true;
|
||||||
|
|
||||||
|
/// Copies the result with the [nullable] information, if there is one. If
|
||||||
|
/// there isn't, the failure state will be copied into the new
|
||||||
|
/// [ResolveResult].
|
||||||
ResolveResult withNullable(bool nullable) {
|
ResolveResult withNullable(bool nullable) {
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
return ResolveResult(type.withNullable(nullable));
|
return ResolveResult(type.withNullable(nullable));
|
||||||
|
|
|
@ -21,25 +21,35 @@ part 'expressions/variables.dart';
|
||||||
part 'statements/select.dart';
|
part 'statements/select.dart';
|
||||||
part 'statements/statement.dart';
|
part 'statements/statement.dart';
|
||||||
|
|
||||||
|
/// A node in the abstract syntax tree of an SQL statement.
|
||||||
abstract class AstNode {
|
abstract class AstNode {
|
||||||
/// The parent of this node, or null if this is the root node. Will be set
|
/// The parent of this node, or null if this is the root node. Will be set
|
||||||
/// by the analyzer after the tree has been parsed.
|
/// by the analyzer after the tree has been parsed.
|
||||||
AstNode parent;
|
AstNode parent;
|
||||||
|
|
||||||
|
/// The first token that appears in this node. This information is not set for
|
||||||
|
/// all nodes.
|
||||||
Token first;
|
Token first;
|
||||||
|
|
||||||
|
/// The last token that appears in this node. This information is not set for
|
||||||
|
/// all nodes.
|
||||||
Token last;
|
Token last;
|
||||||
|
|
||||||
/// The first index in the source that belongs to this node
|
/// The first index in the source that belongs to this node. Not set for all
|
||||||
|
/// nodes.
|
||||||
int get firstPosition => first.span.start.offset;
|
int get firstPosition => first.span.start.offset;
|
||||||
|
|
||||||
/// The last position that belongs to node, exclusive
|
/// The last position that belongs to node, exclusive. Not set for all nodes.
|
||||||
int get lastPosition => last.span.end.offset;
|
int get lastPosition => last.span.end.offset;
|
||||||
|
|
||||||
|
/// Sets the [AstNode.first] and [AstNode.last] property in one go.
|
||||||
void setSpan(Token first, Token last) {
|
void setSpan(Token first, Token last) {
|
||||||
this.first = first;
|
this.first = first;
|
||||||
this.last = last;
|
this.last = last;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all parents of this node up to the root. If this node is the root,
|
||||||
|
/// the iterable will be empty.
|
||||||
Iterable<AstNode> get parents sync* {
|
Iterable<AstNode> get parents sync* {
|
||||||
var node = parent;
|
var node = parent;
|
||||||
while (node != null) {
|
while (node != null) {
|
||||||
|
@ -48,6 +58,8 @@ abstract class AstNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively returns all descendants of this node, e.g. its children, their
|
||||||
|
/// children and so on. The tree will be pre-order traversed.
|
||||||
Iterable<AstNode> get allDescendants sync* {
|
Iterable<AstNode> get allDescendants sync* {
|
||||||
for (var child in childNodes) {
|
for (var child in childNodes) {
|
||||||
yield child;
|
yield child;
|
||||||
|
@ -56,10 +68,20 @@ abstract class AstNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<Type, dynamic> _metadata = {};
|
final Map<Type, dynamic> _metadata = {};
|
||||||
|
|
||||||
|
/// Returns the metadata of type [T] that might have been set on this node, or
|
||||||
|
/// null if none was found.
|
||||||
|
/// Nodes can have arbitrary annotations on them set via [setMeta] and
|
||||||
|
/// obtained via [meta]. This mechanism is used to, for instance, attach
|
||||||
|
/// variable scopes to a subtree.
|
||||||
T meta<T>() {
|
T meta<T>() {
|
||||||
return _metadata[T] as T;
|
return _metadata[T] as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the metadata of type [T] to the specified [value].
|
||||||
|
/// Nodes can have arbitrary annotations on them set via [setMeta] and
|
||||||
|
/// obtained via [meta]. This mechanism is used to, for instance, attach
|
||||||
|
/// variable scopes to a subtree.
|
||||||
void setMeta<T>(T value) {
|
void setMeta<T>(T value) {
|
||||||
_metadata[T] = value;
|
_metadata[T] = value;
|
||||||
}
|
}
|
||||||
|
@ -78,11 +100,17 @@ abstract class AstNode {
|
||||||
throw StateError('No reference scope found in this or any parent node');
|
throw StateError('No reference scope found in this or any parent node');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies a [ReferenceScope] to this node. Variables declared in [scope]
|
||||||
|
/// will be visible to this node and to [allDescendants].
|
||||||
set scope(ReferenceScope scope) {
|
set scope(ReferenceScope scope) {
|
||||||
setMeta<ReferenceScope>(scope);
|
setMeta<ReferenceScope>(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All direct children of this node.
|
||||||
Iterable<AstNode> get childNodes;
|
Iterable<AstNode> get childNodes;
|
||||||
|
|
||||||
|
/// Calls the appropriate method on the [visitor] to make it recognize this
|
||||||
|
/// node.
|
||||||
T accept<T>(AstVisitor<T> visitor);
|
T accept<T>(AstVisitor<T> visitor);
|
||||||
|
|
||||||
/// Whether the content of this node is equal to the [other] node of the same
|
/// Whether the content of this node is equal to the [other] node of the same
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:sqlparser/src/analysis/analysis.dart';
|
||||||
import 'package:sqlparser/src/ast/ast.dart';
|
import 'package:sqlparser/src/ast/ast.dart';
|
||||||
import 'package:sqlparser/src/reader/parser/parser.dart';
|
import 'package:sqlparser/src/reader/parser/parser.dart';
|
||||||
import 'package:sqlparser/src/reader/tokenizer/scanner.dart';
|
import 'package:sqlparser/src/reader/tokenizer/scanner.dart';
|
||||||
|
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||||
|
|
||||||
class SqlEngine {
|
class SqlEngine {
|
||||||
/// All tables registered with [registerTable].
|
/// All tables registered with [registerTable].
|
||||||
|
@ -26,13 +27,17 @@ class SqlEngine {
|
||||||
|
|
||||||
/// Parses the [sql] statement. At the moment, only SELECT statements are
|
/// Parses the [sql] statement. At the moment, only SELECT statements are
|
||||||
/// supported.
|
/// supported.
|
||||||
AstNode parse(String sql) {
|
ParseResult parse(String sql) {
|
||||||
final scanner = Scanner(sql);
|
final scanner = Scanner(sql);
|
||||||
final tokens = scanner.scanTokens();
|
final tokens = scanner.scanTokens();
|
||||||
// todo error handling from scanner
|
|
||||||
|
if (scanner.errors.isNotEmpty) {
|
||||||
|
throw CumulatedTokenizerException(scanner.errors);
|
||||||
|
}
|
||||||
|
|
||||||
final parser = Parser(tokens);
|
final parser = Parser(tokens);
|
||||||
return parser.statement();
|
final stmt = parser.statement();
|
||||||
|
return ParseResult._(stmt, parser.errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses and analyzes the [sql] statement, which at the moment has to be a
|
/// Parses and analyzes the [sql] statement, which at the moment has to be a
|
||||||
|
@ -43,7 +48,8 @@ class SqlEngine {
|
||||||
/// and result columns, so all known tables should be registered using
|
/// and result columns, so all known tables should be registered using
|
||||||
/// [registerTable] before calling this method.
|
/// [registerTable] before calling this method.
|
||||||
AnalysisContext analyze(String sql) {
|
AnalysisContext analyze(String sql) {
|
||||||
final node = parse(sql);
|
final result = parse(sql);
|
||||||
|
final node = result.rootNode;
|
||||||
const SetParentVisitor().startAtRoot(node);
|
const SetParentVisitor().startAtRoot(node);
|
||||||
|
|
||||||
final context = AnalysisContext(node, sql);
|
final context = AnalysisContext(node, sql);
|
||||||
|
@ -58,3 +64,17 @@ class SqlEngine {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result of parsing an sql query. Contains the root of the AST and all
|
||||||
|
/// errors that might have occurred during parsing.
|
||||||
|
class ParseResult {
|
||||||
|
/// The topmost node in the sql AST that was parsed.
|
||||||
|
final AstNode rootNode;
|
||||||
|
|
||||||
|
/// A list of all errors that occurred during parsing. [ParsingError.toString]
|
||||||
|
/// returns a helpful description of what went wrong, along with the position
|
||||||
|
/// where the error occurred.
|
||||||
|
final List<ParsingError> errors;
|
||||||
|
|
||||||
|
ParseResult._(this.rootNode, this.errors);
|
||||||
|
}
|
||||||
|
|
|
@ -174,3 +174,16 @@ class TokenizerError {
|
||||||
|
|
||||||
TokenizerError(this.message, this.location);
|
TokenizerError(this.message, this.location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Thrown by the sql engine when a sql statement can't be tokenized.
|
||||||
|
class CumulatedTokenizerException implements Exception {
|
||||||
|
final List<TokenizerError> errors;
|
||||||
|
CumulatedTokenizerException(this.errors);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
final explanation =
|
||||||
|
errors.map((e) => '${e.message} at ${e.location}').join(', ');
|
||||||
|
return 'Malformed sql: $explanation';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
import 'package:sqlparser/src/utils/ast_equality.dart';
|
import 'package:sqlparser/src/utils/ast_equality.dart';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
import 'package:sqlparser/src/utils/ast_equality.dart';
|
import 'package:sqlparser/src/utils/ast_equality.dart';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
import 'package:sqlparser/src/utils/ast_equality.dart';
|
import 'package:sqlparser/src/utils/ast_equality.dart';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
import 'package:sqlparser/src/utils/ast_equality.dart';
|
import 'package:sqlparser/src/utils/ast_equality.dart';
|
||||||
|
|
Loading…
Reference in New Issue