mirror of https://github.com/AMT-Cheif/drift.git
298 lines
10 KiB
Dart
298 lines
10 KiB
Dart
import 'package:sqlparser/sqlparser.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
final _fts5Options = EngineOptions(enabledExtensions: const [Fts5Extension()]);
|
|
|
|
void main() {
|
|
group('creating fts5 tables', () {
|
|
final engine = SqlEngine(_fts5Options);
|
|
|
|
test('can create fts5 tables', () {
|
|
final result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
|
"fts5(bar , tokenize = 'porter ascii')");
|
|
|
|
final table = const SchemaFromCreateTable()
|
|
.read(result.root as TableInducingStatement);
|
|
|
|
expect(table.name, 'foo');
|
|
final columns = table.resultColumns;
|
|
expect(columns, hasLength(1));
|
|
expect(columns.single.name, 'bar');
|
|
expect(
|
|
table,
|
|
isA<Fts5Table>()
|
|
.having((e) => e.contentTable, 'contentTable', null)
|
|
.having((e) => e.contentRowId, 'contentRowId', null),
|
|
);
|
|
});
|
|
|
|
test('can create fts5 tables with content table', () {
|
|
final result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
|
"fts5(bar , tokenize = 'porter ascii',content=tbl, content_rowid=d)");
|
|
|
|
final table = const SchemaFromCreateTable()
|
|
.read(result.root as TableInducingStatement);
|
|
|
|
expect(table.name, 'foo');
|
|
expect(table.resultColumns,
|
|
[isA<Column>().having((c) => c.name, 'name', 'bar')]);
|
|
expect(table.findColumn('oid'), isA<RowId>());
|
|
|
|
expect(
|
|
table,
|
|
isA<Fts5Table>()
|
|
.having((e) => e.contentTable, 'contentTable', 'tbl')
|
|
.having((e) => e.contentRowId, 'contentRowId', 'd'),
|
|
);
|
|
});
|
|
|
|
test('does clean option values', () {
|
|
final result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
|
"fts5(bar , tokenize = 'porter ascii', content='tbl', content_rowid='d')");
|
|
|
|
final table = const SchemaFromCreateTable()
|
|
.read(result.root as TableInducingStatement);
|
|
|
|
expect(
|
|
table,
|
|
isA<Fts5Table>()
|
|
.having((e) => e.contentTable, 'contentTable', 'tbl')
|
|
.having((e) => e.contentRowId, 'contentRowId', 'd'),
|
|
);
|
|
});
|
|
|
|
test('detects empty content table', () {
|
|
final result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
|
"fts5(bar , tokenize = 'porter ascii',content='', content_rowid='d')");
|
|
|
|
final table = const SchemaFromCreateTable()
|
|
.read(result.root as TableInducingStatement);
|
|
|
|
expect(
|
|
table,
|
|
isA<Fts5Table>()
|
|
.having((e) => e.contentTable, 'contentTable', null)
|
|
.having((e) => e.contentRowId, 'contentRowId', null),
|
|
);
|
|
});
|
|
|
|
test('content table undefined rowid falls back', () {
|
|
final result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
|
"fts5(bar , tokenize = 'porter ascii',content='tbl')");
|
|
|
|
final table = const SchemaFromCreateTable()
|
|
.read(result.root as TableInducingStatement);
|
|
|
|
expect(
|
|
table,
|
|
isA<Fts5Table>()
|
|
.having((e) => e.contentTable, 'contentTable', 'tbl')
|
|
.having((e) => e.contentRowId, 'contentRowId', 'rowid'),
|
|
);
|
|
});
|
|
|
|
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', () {
|
|
final result = engine
|
|
.analyze('CREATE VIRTUAL TABLE foo USING fts5(bar, baz UNINDEXED)');
|
|
|
|
final table = const SchemaFromCreateTable()
|
|
.read(result.root as TableInducingStatement);
|
|
|
|
expect(table.name, 'foo');
|
|
expect(table.resultColumns.map((c) => c.name), ['bar', 'baz']);
|
|
});
|
|
});
|
|
|
|
group('type inference for function calls', () {
|
|
late SqlEngine engine;
|
|
setUp(() {
|
|
engine = SqlEngine(_fts5Options);
|
|
// add an fts5 table for the following queries
|
|
final fts5Result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
|
'fts5(bar, baz);');
|
|
engine.registerTable(const SchemaFromCreateTable()
|
|
.read(fts5Result.root as TableInducingStatement));
|
|
});
|
|
|
|
test('return type of bm25()', () {
|
|
final result = engine
|
|
.analyze('SELECT *, bm25(foo) AS b FROM foo WHERE foo MATCH \'\'');
|
|
expect(result.errors, isEmpty);
|
|
|
|
final select = result.root as SelectStatement;
|
|
final column = select.resolvedColumns!.singleWhere((c) => c.name == 'b');
|
|
expect(result.typeOf(column),
|
|
const ResolveResult(ResolvedType(type: BasicType.real)));
|
|
});
|
|
|
|
test('return type of highlight()', () {
|
|
final result =
|
|
engine.analyze("SELECT *, highlight(foo, 0, '<b>', '</b>') AS b "
|
|
"FROM foo WHERE foo MATCH ''");
|
|
expect(result.errors, isEmpty);
|
|
|
|
final select = result.root as SelectStatement;
|
|
final column = select.resolvedColumns!.singleWhere((c) => c.name == 'b');
|
|
expect(result.typeOf(column),
|
|
const ResolveResult(ResolvedType(type: BasicType.text)));
|
|
});
|
|
|
|
test('return type of snippet()', () {
|
|
final result = engine
|
|
.analyze("SELECT *, snippet(foo, 0, '<b>', '</b>', '...', 20) AS b "
|
|
"FROM foo WHERE foo MATCH ''");
|
|
expect(result.errors, isEmpty);
|
|
|
|
final select = result.root as SelectStatement;
|
|
final column = select.resolvedColumns!.singleWhere((c) => c.name == 'b');
|
|
expect(result.typeOf(column),
|
|
const ResolveResult(ResolvedType(type: BasicType.text)));
|
|
});
|
|
});
|
|
|
|
group('type inference for function arguments', () {
|
|
late SqlEngine engine;
|
|
setUp(() {
|
|
engine = SqlEngine(_fts5Options);
|
|
// add an fts5 table for the following queries
|
|
final fts5Result = engine.analyze('CREATE VIRTUAL TABLE fts USING '
|
|
'fts5(bar, baz);');
|
|
engine.registerTable(const SchemaFromCreateTable()
|
|
.read(fts5Result.root as TableInducingStatement));
|
|
});
|
|
|
|
void checkVarTypes(String sql, List<BasicType> expected) {
|
|
final result = engine.analyze(sql);
|
|
expect(result.errors, isEmpty);
|
|
|
|
final foundVars = result.root.allDescendants.whereType<Variable>();
|
|
|
|
expect(
|
|
foundVars.map((Typeable t) => result.typeOf(t).type!.type),
|
|
expected,
|
|
);
|
|
}
|
|
|
|
test('for highlight()', () {
|
|
checkVarTypes(
|
|
'SELECT highlight(fts, ?, ?, ?) FROM fts;',
|
|
[
|
|
BasicType.int, // column index
|
|
BasicType.text, // text before phrase match
|
|
BasicType.text, // text after phrase match
|
|
],
|
|
);
|
|
});
|
|
|
|
test('for snippet()', () {
|
|
checkVarTypes(
|
|
'SELECT snippet(fts, ?, ?, ?, ?, ?) FROM fts;',
|
|
[
|
|
BasicType.int, // column index
|
|
BasicType.text, // text before match
|
|
BasicType.text, // text after match
|
|
BasicType.text, // text to add match isn't at start
|
|
BasicType.int, // maximum number of tokens
|
|
],
|
|
);
|
|
});
|
|
});
|
|
|
|
group('error reporting', () {
|
|
late SqlEngine engine;
|
|
setUp(() {
|
|
engine = SqlEngine(_fts5Options);
|
|
// add an fts5 table for the following queries
|
|
final fts5Result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
|
'fts5(bar, baz);');
|
|
engine.registerTable(const SchemaFromCreateTable()
|
|
.read(fts5Result.root as TableInducingStatement));
|
|
|
|
final normalResult = engine.analyze('CREATE TABLE other (bar TEXT);');
|
|
engine.registerTable(const SchemaFromCreateTable()
|
|
.read(normalResult.root as TableInducingStatement));
|
|
});
|
|
|
|
Matcher hasMessage(Object msgMatcher) {
|
|
return const TypeMatcher<AnalysisError>()
|
|
.having((e) => e.message, 'message', msgMatcher);
|
|
}
|
|
|
|
test('when using star function parameters', () {
|
|
final result = engine.analyze('SELECT bm25(*) FROM foo;');
|
|
expect(result.errors, [hasMessage(contains('star parameter'))]);
|
|
});
|
|
|
|
test('when using a non-fts5 table as parameter', () {
|
|
final result = engine.analyze('SELECT bm25(bar) FROM other');
|
|
expect(result.errors, [hasMessage(contains('fts5 table name'))]);
|
|
});
|
|
|
|
test('when using the wrong number or arguments', () {
|
|
final result = engine.analyze('SELECT highlight(foo, 3) FROM foo');
|
|
expect(
|
|
result.errors,
|
|
[
|
|
hasMessage(stringContainsInOrder(['highlight', '4', '2']))
|
|
],
|
|
);
|
|
});
|
|
});
|
|
|
|
test('does not include rank and table columns in result', () {
|
|
final engine = SqlEngine(_fts5Options);
|
|
final fts5Result = engine.analyze('CREATE VIRTUAL TABLE foo USING '
|
|
'fts5(bar, baz);');
|
|
engine.registerTable(const SchemaFromCreateTable()
|
|
.read(fts5Result.root as TableInducingStatement));
|
|
|
|
final selectResult = engine.analyze('SELECT * FROM foo;');
|
|
final columns = (selectResult.root as SelectStatement).resolvedColumns;
|
|
|
|
expect(columns, isNot(anyElement((Column c) => c.name == 'rank')));
|
|
expect(columns, isNot(anyElement((Column c) => c.name == 'foo')));
|
|
});
|
|
}
|