Static analysis support for spellfix1 (#2013)

This commit is contained in:
Fabian Freund 2022-09-16 17:02:39 +03:00 committed by GitHub
parent 87c58d0685
commit cd89379627
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 133 additions and 2 deletions

View File

@ -137,6 +137,9 @@ We currently support the following extensions:
of trigonometric functions. Details on those functions are available [here]({{ "../Other engines/vm.md#moor-only-functions" | pageUrl }}).
- `math`: Assumes that sqlite3 was compiled with [math functions](https://www.sqlite.org/lang_mathfunc.html).
This module is largely incompatible with the `moor_ffi` module.
- `spellfix1`: Assumes that the [spellfix1](https://www.sqlite.org/spellfix1.html)
module is available. Note that this is not the case for most sqlite3 builds,
including the ones shipping with `sqlite3_flutter_libs`.
## Recommended options

View File

@ -279,4 +279,6 @@ enum SqlModule {
/// Enables support for the rtree module and its functions when parsing sql
/// queries.
rtree,
spellfix1,
}

View File

@ -141,6 +141,7 @@ const _$SqlModuleEnumMap = {
SqlModule.moor_ffi: 'moor_ffi',
SqlModule.math: 'math',
SqlModule.rtree: 'rtree',
SqlModule.spellfix1: 'spellfix1',
};
DialectOptions _$DialectOptionsFromJson(Map json) => $checkedCreate(

View File

@ -46,6 +46,7 @@ class MoorSession {
if (options.hasModule(SqlModule.moor_ffi)) const MoorFfiExtension(),
if (options.hasModule(SqlModule.math)) const BuiltInMathExtension(),
if (options.hasModule(SqlModule.rtree)) const RTreeExtension(),
if (options.hasModule(SqlModule.spellfix1)) const Spellfix1Extension(),
],
version: options.sqliteVersion,
);

View File

@ -8,6 +8,7 @@ export 'src/engine/module/fts5.dart' show Fts5Extension, Fts5Table;
export 'src/engine/module/json1.dart' show Json1Extension;
export 'src/engine/module/math.dart' show BuiltInMathExtension;
export 'src/engine/module/rtree.dart' show RTreeExtension;
export 'src/engine/module/spellfix1.dart' show Spellfix1Extension;
export 'src/engine/module/module.dart';
export 'src/engine/options.dart';
export 'src/engine/sql_engine.dart';

View File

@ -41,6 +41,10 @@ class TableColumn extends Column implements ColumnWithType {
/// Generated columns can't be inserted or updated.
final bool isGenerated;
/// Whether this column is `HIDDEN`, as specified in
/// https://www.sqlite.org/vtab.html#hidden_columns_in_virtual_tables
final bool isHidden;
@override
ResultSet? get containingSet => table;
@ -69,8 +73,16 @@ class TableColumn extends Column implements ColumnWithType {
late final bool _isAliasForRowId = _computeIsAliasForRowId();
TableColumn(this.name, this._type,
{this.definition, this.isGenerated = false});
TableColumn(
this.name,
this._type, {
this.definition,
this.isGenerated = false,
this.isHidden = false,
});
@override
bool get includedInResults => !isHidden;
/// Applies a type hint to this column.
///

View File

@ -0,0 +1,60 @@
import 'package:sqlparser/sqlparser.dart';
class Spellfix1Extension implements Extension {
const Spellfix1Extension();
@override
void register(SqlEngine engine) {
engine.registerModule(_Spellfix1Module());
}
}
class _Spellfix1Module extends Module {
_Spellfix1Module() : super('spellfix1');
@override
Table parseTable(CreateVirtualTableStatement stmt) {
return Table(
name: stmt.tableName,
resolvedColumns: [
TableColumn('word', const ResolvedType(type: BasicType.text)),
TableColumn(
'rank', const ResolvedType(type: BasicType.int, nullable: true)),
TableColumn('distance',
const ResolvedType(type: BasicType.int, nullable: true)),
TableColumn('langid',
const ResolvedType(type: BasicType.int, nullable: true)),
TableColumn(
'score', const ResolvedType(type: BasicType.int, nullable: true)),
TableColumn('matchlen',
const ResolvedType(type: BasicType.int, nullable: true)),
TableColumn(
'top',
const ResolvedType(type: BasicType.int),
isHidden: true,
),
TableColumn(
'scope',
const ResolvedType(type: BasicType.int),
isHidden: true,
),
TableColumn(
'srchcnt',
const ResolvedType(type: BasicType.int),
isHidden: true,
),
TableColumn(
'soundslike',
const ResolvedType(type: BasicType.text),
isHidden: true,
),
TableColumn(
'command',
const ResolvedType(type: BasicType.int),
isHidden: true,
),
],
definition: stmt,
isVirtual: true);
}
}

View File

@ -0,0 +1,51 @@
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
final _spellfixOptions =
EngineOptions(enabledExtensions: const [Spellfix1Extension()]);
void main() {
final engine = SqlEngine(_spellfixOptions);
test('creating spellfix1 table', () {
final result = engine.analyze('CREATE VIRTUAL TABLE demo USING spellfix1;');
final table = const SchemaFromCreateTable()
.read(result.root as TableInducingStatement);
expect(table.name, 'demo');
final columns = table.resultColumns;
expect(columns, hasLength(6));
});
group('engine analyze spellfix1', () {
late SqlEngine engine;
setUp(() {
engine = SqlEngine(_spellfixOptions);
final fts5Result =
engine.analyze('CREATE VIRTUAL TABLE demo USING spellfix1;');
engine.registerTable(const SchemaFromCreateTable()
.read(fts5Result.root as TableInducingStatement));
});
test('ignore hidden columns in result', () {
final result =
engine.analyze('SELECT * FROM demo WHERE word MATCH \'none\'');
expect(result.errors, isEmpty);
final select = result.root as SelectStatement;
expect(select.resolvedColumns, hasLength(6));
});
test('accepts hidden columns in query', () {
final result = engine
.analyze('SELECT * FROM demo WHERE word MATCH \'none\' AND top=3');
expect(result.errors, isEmpty);
});
test('consideres hidden columns', () {
final result = engine.analyze(
'SELECT * FROM demo WHERE word MATCH \'none\' AND noneexistent=3');
expect(result.errors, hasLength(1));
});
});
}