mirror of https://github.com/AMT-Cheif/drift.git
Merge branch 'simolus3:develop' into rtree
This commit is contained in:
commit
135433a692
|
@ -1,3 +1,9 @@
|
||||||
|
## 0.23.2-dev
|
||||||
|
|
||||||
|
- Support resolving the `fts5vocab` module when `fts5` is enabled - thanks to
|
||||||
|
[@FaFre](https://github.com/FaFre).
|
||||||
|
- Improve references resolving across subqueries.
|
||||||
|
|
||||||
## 0.23.1
|
## 0.23.1
|
||||||
|
|
||||||
- Gracefully handle tokenizer errors related to `@` or `$` variables.
|
- Gracefully handle tokenizer errors related to `@` or `$` variables.
|
||||||
|
|
|
@ -24,6 +24,13 @@ mixin Referencable {
|
||||||
abstract class ReferenceScope {
|
abstract class ReferenceScope {
|
||||||
RootScope get rootScope;
|
RootScope get rootScope;
|
||||||
|
|
||||||
|
/// All available result sets that can also be seen in child scopes.
|
||||||
|
///
|
||||||
|
/// Usually, this is the same list as the result sets being declared in this
|
||||||
|
/// scope. However, some exceptions apply (see e.g. [SubqueryInFromScope]).
|
||||||
|
Iterable<ResultSetAvailableInStatement> get resultSetAvailableToChildScopes =>
|
||||||
|
const Iterable.empty();
|
||||||
|
|
||||||
/// The list of column to which a `*` would expand to.
|
/// The list of column to which a `*` would expand to.
|
||||||
///
|
///
|
||||||
/// This is not necessary the same list of columns that could be resolved
|
/// This is not necessary the same list of columns that could be resolved
|
||||||
|
@ -101,6 +108,30 @@ class RootScope extends ReferenceScope {
|
||||||
final Map<String, Module> knownModules = CaseInsensitiveMap();
|
final Map<String, Module> knownModules = CaseInsensitiveMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mixin _HasParentScope on ReferenceScope {
|
||||||
|
ReferenceScope get _parentScopeForLookups;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RootScope get rootScope => _parentScopeForLookups.rootScope;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ResultSetAvailableInStatement> get resultSetAvailableToChildScopes =>
|
||||||
|
_parentScopeForLookups.resultSetAvailableToChildScopes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ResultSetAvailableInStatement? resolveResultSet(String name) =>
|
||||||
|
_parentScopeForLookups.resolveResultSet(name);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ResultSet? resolveResultSetToAdd(String name) =>
|
||||||
|
_parentScopeForLookups.resolveResultSetToAdd(name);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Column> resolveUnqualifiedReference(String columnName,
|
||||||
|
{bool allowReferenceToResultColumn = false}) =>
|
||||||
|
_parentScopeForLookups.resolveUnqualifiedReference(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
/// A scope used by statements.
|
/// A scope used by statements.
|
||||||
///
|
///
|
||||||
/// Tables added from `FROM` clauses are added to [resultSets], CTEs are added
|
/// Tables added from `FROM` clauses are added to [resultSets], CTEs are added
|
||||||
|
@ -118,9 +149,12 @@ class RootScope extends ReferenceScope {
|
||||||
/// [SubqueryInFromScope] is insertted as an intermediatet scope to prevent
|
/// [SubqueryInFromScope] is insertted as an intermediatet scope to prevent
|
||||||
/// the inner scope from seeing the outer columns.
|
/// the inner scope from seeing the outer columns.
|
||||||
|
|
||||||
class StatementScope extends ReferenceScope {
|
class StatementScope extends ReferenceScope with _HasParentScope {
|
||||||
final ReferenceScope parent;
|
final ReferenceScope parent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get _parentScopeForLookups => parent;
|
||||||
|
|
||||||
/// Additional tables (that haven't necessarily been added in a `FROM` clause
|
/// Additional tables (that haven't necessarily been added in a `FROM` clause
|
||||||
/// that are only visible in this scope).
|
/// that are only visible in this scope).
|
||||||
///
|
///
|
||||||
|
@ -153,29 +187,17 @@ class StatementScope extends ReferenceScope {
|
||||||
|
|
||||||
StatementScope(this.parent);
|
StatementScope(this.parent);
|
||||||
|
|
||||||
StatementScope? get parentStatementScope {
|
@override
|
||||||
final parent = this.parent;
|
Iterable<ResultSetAvailableInStatement> get resultSetAvailableToChildScopes {
|
||||||
if (parent is StatementScope) {
|
return allAvailableResultSets;
|
||||||
return parent;
|
|
||||||
} else if (parent is MiscStatementSubScope) {
|
|
||||||
return parent.parent;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All result sets available in this and parent scopes.
|
/// All result sets available in this and parent scopes.
|
||||||
Iterable<ResultSetAvailableInStatement> get allAvailableResultSets {
|
Iterable<ResultSetAvailableInStatement> get allAvailableResultSets {
|
||||||
final here = resultSets.values;
|
final here = resultSets.values;
|
||||||
final parent = parentStatementScope;
|
return parent.resultSetAvailableToChildScopes.followedBy(here);
|
||||||
return parent != null
|
|
||||||
? here.followedBy(parent.allAvailableResultSets)
|
|
||||||
: here;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
RootScope get rootScope => parent.rootScope;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addAlias(AstNode origin, ResultSet resultSet, String alias) {
|
void addAlias(AstNode origin, ResultSet resultSet, String alias) {
|
||||||
final createdAlias = TableAlias(resultSet, alias);
|
final createdAlias = TableAlias(resultSet, alias);
|
||||||
|
@ -183,22 +205,20 @@ class StatementScope extends ReferenceScope {
|
||||||
resultSets[alias] = ResultSetAvailableInStatement(origin, createdAlias);
|
resultSets[alias] = ResultSetAvailableInStatement(origin, createdAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
ResultSetAvailableInStatement? resolveResultSet(String name) {
|
|
||||||
return resultSets[name] ?? parentStatementScope?.resolveResultSet(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addResolvedResultSet(
|
void addResolvedResultSet(
|
||||||
String? name, ResultSetAvailableInStatement resultSet) {
|
String? name, ResultSetAvailableInStatement resultSet) {
|
||||||
resultSets[name] = resultSet;
|
resultSets[name] = resultSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ResultSetAvailableInStatement? resolveResultSet(String name) {
|
||||||
|
return resultSets[name] ?? parent.resolveResultSet(name);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ResultSet? resolveResultSetToAdd(String name) {
|
ResultSet? resolveResultSetToAdd(String name) {
|
||||||
return additionalKnownTables[name] ??
|
return additionalKnownTables[name] ?? parent.resolveResultSetToAdd(name);
|
||||||
parentStatementScope?.resolveResultSetToAdd(name) ??
|
|
||||||
rootScope.knownTables[name];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -212,14 +232,7 @@ class StatementScope extends ReferenceScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StatementScope? currentScope = this;
|
final available = resultSets.values;
|
||||||
|
|
||||||
// Search scopes for a matching column in an added result set. If a column
|
|
||||||
// reference is found in a closer scope, it takes precedence over outer
|
|
||||||
// scopes. However, it's an error if two columns with the same name are
|
|
||||||
// found in the same scope.
|
|
||||||
while (currentScope != null) {
|
|
||||||
final available = currentScope.resultSets.values;
|
|
||||||
final sourceColumns = <Column>{};
|
final sourceColumns = <Column>{};
|
||||||
final availableColumns = <AvailableColumn>[];
|
final availableColumns = <AvailableColumn>[];
|
||||||
|
|
||||||
|
@ -237,20 +250,12 @@ class StatementScope extends ReferenceScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (availableColumns.isEmpty) {
|
if (availableColumns.isEmpty) {
|
||||||
currentScope = currentScope.parentStatementScope;
|
return parent.resolveUnqualifiedReference(columnName);
|
||||||
if (currentScope == null) {
|
|
||||||
// Reached the outermost scope without finding a reference target.
|
|
||||||
return const [];
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
return availableColumns;
|
return availableColumns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return const [];
|
|
||||||
}
|
|
||||||
|
|
||||||
factory StatementScope.forStatement(RootScope root, Statement statement) {
|
factory StatementScope.forStatement(RootScope root, Statement statement) {
|
||||||
return StatementScope(statement.optionalScope ?? root);
|
return StatementScope(statement.optionalScope ?? root);
|
||||||
}
|
}
|
||||||
|
@ -269,13 +274,24 @@ class StatementScope extends ReferenceScope {
|
||||||
|
|
||||||
/// A special intermediate scope used for subqueries appearing in a `FROM`
|
/// A special intermediate scope used for subqueries appearing in a `FROM`
|
||||||
/// clause so that the subquery can't see outer columns and tables being added.
|
/// clause so that the subquery can't see outer columns and tables being added.
|
||||||
class SubqueryInFromScope extends ReferenceScope {
|
class SubqueryInFromScope extends ReferenceScope with _HasParentScope {
|
||||||
final StatementScope enclosingStatement;
|
final StatementScope enclosingStatement;
|
||||||
|
|
||||||
SubqueryInFromScope(this.enclosingStatement);
|
SubqueryInFromScope(this.enclosingStatement);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RootScope get rootScope => enclosingStatement.rootScope;
|
RootScope get rootScope => enclosingStatement.rootScope;
|
||||||
|
|
||||||
|
// This scope can't see elements from the enclosing statement, but it can see
|
||||||
|
// elements from grandparents.
|
||||||
|
@override
|
||||||
|
ReferenceScope get _parentScopeForLookups => enclosingStatement.parent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ResultSet? resolveResultSetToAdd(String name) {
|
||||||
|
// CTEs from the enclosing statement are also available here
|
||||||
|
return enclosingStatement.resolveResultSetToAdd(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A rarely used sub-scope for AST nodes that belong to a statement, but may
|
/// A rarely used sub-scope for AST nodes that belong to a statement, but may
|
||||||
|
@ -283,9 +299,12 @@ class SubqueryInFromScope extends ReferenceScope {
|
||||||
///
|
///
|
||||||
/// For instance, the body of an `ON CONFLICT DO UPDATE`-clause may refer to a
|
/// For instance, the body of an `ON CONFLICT DO UPDATE`-clause may refer to a
|
||||||
/// table alias `excluded` to get access to a conflicting table.
|
/// table alias `excluded` to get access to a conflicting table.
|
||||||
class MiscStatementSubScope extends ReferenceScope {
|
class MiscStatementSubScope extends ReferenceScope with _HasParentScope {
|
||||||
final StatementScope parent;
|
final StatementScope parent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get _parentScopeForLookups => parent;
|
||||||
|
|
||||||
final Map<String?, ResultSetAvailableInStatement> additionalResultSets =
|
final Map<String?, ResultSetAvailableInStatement> additionalResultSets =
|
||||||
CaseInsensitiveMap();
|
CaseInsensitiveMap();
|
||||||
|
|
||||||
|
@ -304,12 +323,6 @@ class MiscStatementSubScope extends ReferenceScope {
|
||||||
String? name, ResultSetAvailableInStatement resultSet) {
|
String? name, ResultSetAvailableInStatement resultSet) {
|
||||||
additionalResultSets[name] = resultSet;
|
additionalResultSets[name] = resultSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
List<Column> resolveUnqualifiedReference(String columnName,
|
|
||||||
{bool allowReferenceToResultColumn = false}) {
|
|
||||||
return parent.resolveUnqualifiedReference(columnName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reference scope that only allows a single added result set.
|
/// A reference scope that only allows a single added result set.
|
||||||
|
|
|
@ -6,6 +6,7 @@ class Fts5Extension implements Extension {
|
||||||
@override
|
@override
|
||||||
void register(SqlEngine engine) {
|
void register(SqlEngine engine) {
|
||||||
engine.registerModule(_Fts5Module());
|
engine.registerModule(_Fts5Module());
|
||||||
|
engine.registerModule(_Fts5VocabModule());
|
||||||
engine.registerFunctionHandler(const _Fts5Functions());
|
engine.registerFunctionHandler(const _Fts5Functions());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +36,59 @@ class _Fts5Module extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _Fts5VocabModule extends Module {
|
||||||
|
_Fts5VocabModule() : super('fts5vocab');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Table parseTable(CreateVirtualTableStatement stmt) {
|
||||||
|
if (stmt.argumentContent.length < 2 || stmt.argumentContent.length > 3) {
|
||||||
|
throw ArgumentError('''
|
||||||
|
fts5vocab table requires at least
|
||||||
|
two arguments (<referenced fts5 table name>, <type>)
|
||||||
|
and maximum three arguments when using an attached database
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
|
||||||
|
final type = stmt.argumentContent.last.replaceAll(RegExp(r'''["' ]'''), '');
|
||||||
|
switch (type) {
|
||||||
|
case 'row':
|
||||||
|
return Table(
|
||||||
|
name: stmt.tableName,
|
||||||
|
resolvedColumns: [
|
||||||
|
TableColumn('term', const ResolvedType(type: BasicType.text)),
|
||||||
|
TableColumn('doc', const ResolvedType(type: BasicType.int)),
|
||||||
|
TableColumn('cnt', const ResolvedType(type: BasicType.int)),
|
||||||
|
],
|
||||||
|
definition: stmt,
|
||||||
|
isVirtual: true);
|
||||||
|
case 'col':
|
||||||
|
return Table(
|
||||||
|
name: stmt.tableName,
|
||||||
|
resolvedColumns: [
|
||||||
|
TableColumn('term', const ResolvedType(type: BasicType.text)),
|
||||||
|
TableColumn('col', const ResolvedType(type: BasicType.text)),
|
||||||
|
TableColumn('doc', const ResolvedType(type: BasicType.int)),
|
||||||
|
TableColumn('cnt', const ResolvedType(type: BasicType.int)),
|
||||||
|
],
|
||||||
|
definition: stmt,
|
||||||
|
isVirtual: true);
|
||||||
|
case 'instance':
|
||||||
|
return Table(
|
||||||
|
name: stmt.tableName,
|
||||||
|
resolvedColumns: [
|
||||||
|
TableColumn('term', const ResolvedType(type: BasicType.text)),
|
||||||
|
TableColumn('doc', const ResolvedType(type: BasicType.int)),
|
||||||
|
TableColumn('col', const ResolvedType(type: BasicType.text)),
|
||||||
|
TableColumn('offset', const ResolvedType(type: BasicType.int)),
|
||||||
|
],
|
||||||
|
definition: stmt,
|
||||||
|
isVirtual: true);
|
||||||
|
default:
|
||||||
|
throw ArgumentError('Unknown fts5vocab table type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _Fts5Table extends Table {
|
class _Fts5Table extends Table {
|
||||||
_Fts5Table(
|
_Fts5Table(
|
||||||
{required String name,
|
{required String name,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: sqlparser
|
name: sqlparser
|
||||||
description: Parses sqlite statements and performs static analysis on them
|
description: Parses sqlite statements and performs static analysis on them
|
||||||
version: 0.23.1
|
version: 0.23.2
|
||||||
homepage: https://github.com/simolus3/drift/tree/develop/sqlparser
|
homepage: https://github.com/simolus3/drift/tree/develop/sqlparser
|
||||||
#homepage: https://drift.simonbinder.eu/
|
#homepage: https://drift.simonbinder.eu/
|
||||||
issue_tracker: https://github.com/simolus3/drift/issues
|
issue_tracker: https://github.com/simolus3/drift/issues
|
||||||
|
|
|
@ -163,7 +163,8 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resolves sub-queries', () {
|
group('sub-queries', () {
|
||||||
|
test('are resolved', () {
|
||||||
final engine = SqlEngine()..registerTable(demoTable);
|
final engine = SqlEngine()..registerTable(demoTable);
|
||||||
|
|
||||||
final context = engine.analyze(
|
final context = engine.analyze(
|
||||||
|
@ -172,6 +173,70 @@ void main() {
|
||||||
expect(context.errors, isEmpty);
|
expect(context.errors, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('cannot refer to outer tables if used in FROM', () {
|
||||||
|
final engine = SqlEngine()..registerTable(demoTable);
|
||||||
|
|
||||||
|
final context = engine.analyze(
|
||||||
|
'SELECT d.* FROM demo d, (SELECT * FROM demo WHERE id = d.id);');
|
||||||
|
|
||||||
|
context.expectError('d.id',
|
||||||
|
type: AnalysisErrorType.referencedUnknownTable);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can refer to CTEs if used in FROM', () {
|
||||||
|
final engine = SqlEngine()..registerTable(demoTable);
|
||||||
|
|
||||||
|
final context = engine.analyze('WITH cte AS (SELECT * FROM demo) '
|
||||||
|
'SELECT d.* FROM demo d, (SELECT * FROM cte);');
|
||||||
|
|
||||||
|
expect(context.errors, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can nest and see outer tables if that is a subquery', () {
|
||||||
|
final engine = SqlEngine()..registerTable(demoTable);
|
||||||
|
|
||||||
|
final context = engine.analyze('''
|
||||||
|
SELECT
|
||||||
|
(SELECT *
|
||||||
|
FROM
|
||||||
|
demo "inner",
|
||||||
|
(SELECT * FROM demo WHERE "inner".id = "outer".id)
|
||||||
|
)
|
||||||
|
FROM demo "outer";
|
||||||
|
''');
|
||||||
|
|
||||||
|
// note that "outer".id is visible and should not report an error
|
||||||
|
context.expectError('"inner".id',
|
||||||
|
type: AnalysisErrorType.referencedUnknownTable);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nested via FROM cannot see outer result sets', () {
|
||||||
|
final engine = SqlEngine()..registerTable(demoTable);
|
||||||
|
|
||||||
|
final context = engine.analyze('''
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
demo "outer",
|
||||||
|
(SELECT * FROM demo "inner",
|
||||||
|
(SELECT * FROM demo WHERE "inner".id = "outer".id))
|
||||||
|
''');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
context.errors,
|
||||||
|
[
|
||||||
|
analysisErrorWith(
|
||||||
|
lexeme: '"inner".id',
|
||||||
|
type: AnalysisErrorType.referencedUnknownTable,
|
||||||
|
),
|
||||||
|
analysisErrorWith(
|
||||||
|
lexeme: '"outer".id',
|
||||||
|
type: AnalysisErrorType.referencedUnknownTable,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('resolves sub-queries as data sources', () {
|
test('resolves sub-queries as data sources', () {
|
||||||
final engine = SqlEngine()
|
final engine = SqlEngine()
|
||||||
..registerTable(demoTable)
|
..registerTable(demoTable)
|
||||||
|
|
|
@ -188,4 +188,32 @@ WHERE EXISTS(SELECT *
|
||||||
|
|
||||||
expect(result.errors, isEmpty);
|
expect(result.errors, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('regression test for #2010', () {
|
||||||
|
// https://github.com/simolus3/drift/issues/2010
|
||||||
|
final engine = SqlEngine()
|
||||||
|
..registerTableFromSql('CREATE TABLE place_hierarchy (feature_id INT);')
|
||||||
|
..registerTableFromSql('CREATE TABLE place_name (feature_id INT);')
|
||||||
|
..registerTableFromSql('CREATE TABLE place (feature_id INT);');
|
||||||
|
|
||||||
|
final result = engine.analyze('''
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
true
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
'test' AS name
|
||||||
|
FROM
|
||||||
|
place_hierarchy ph
|
||||||
|
where
|
||||||
|
ph.feature_id = p.feature_id
|
||||||
|
) second_select
|
||||||
|
) first_select
|
||||||
|
FROM place_name matches
|
||||||
|
INNER JOIN place p ON p.feature_id = matches.feature_id;
|
||||||
|
''');
|
||||||
|
|
||||||
|
expect(result.errors, isEmpty);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,48 @@ void main() {
|
||||||
expect(columns.single.name, 'bar');
|
expect(columns.single.name, 'bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('creating fts5vocab tables', () {
|
||||||
|
final engine = SqlEngine(_fts5Options);
|
||||||
|
|
||||||
|
test('can create fts5vocab instance table', () {
|
||||||
|
final result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
||||||
|
'fts5vocab(bar, instance)');
|
||||||
|
|
||||||
|
final table = const SchemaFromCreateTable()
|
||||||
|
.read(result.root as TableInducingStatement);
|
||||||
|
|
||||||
|
expect(table.name, 'foo');
|
||||||
|
final columns = table.resultColumns;
|
||||||
|
expect(columns, hasLength(4));
|
||||||
|
expect(columns.map((e) => e.name), contains('offset'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can create fts5vocab row table', () {
|
||||||
|
final result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
||||||
|
'fts5vocab(bar, row)');
|
||||||
|
|
||||||
|
final table = const SchemaFromCreateTable()
|
||||||
|
.read(result.root as TableInducingStatement);
|
||||||
|
|
||||||
|
expect(table.name, 'foo');
|
||||||
|
final columns = table.resultColumns;
|
||||||
|
expect(columns, hasLength(3));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can create fts5vocab col table', () {
|
||||||
|
final result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
||||||
|
'fts5vocab(bar, col)');
|
||||||
|
|
||||||
|
final table = const SchemaFromCreateTable()
|
||||||
|
.read(result.root as TableInducingStatement);
|
||||||
|
|
||||||
|
expect(table.name, 'foo');
|
||||||
|
final columns = table.resultColumns;
|
||||||
|
expect(columns, hasLength(4));
|
||||||
|
expect(columns.map((e) => e.name), contains('col'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('handles the UNINDEXED column option', () {
|
test('handles the UNINDEXED column option', () {
|
||||||
final result = engine
|
final result = engine
|
||||||
.analyze('CREATE VIRTUAL TABLE foo USING fts5(bar, baz UNINDEXED)');
|
.analyze('CREATE VIRTUAL TABLE foo USING fts5(bar, baz UNINDEXED)');
|
||||||
|
|
Loading…
Reference in New Issue