diff --git a/drift_dev/lib/src/analyzer/options.dart b/drift_dev/lib/src/analyzer/options.dart index 13b6edf0..b2559ff9 100644 --- a/drift_dev/lib/src/analyzer/options.dart +++ b/drift_dev/lib/src/analyzer/options.dart @@ -275,4 +275,8 @@ enum SqlModule { /// /// [math funs]: https://www.sqlite.org/lang_mathfunc.html math, + + /// Enables support for the rtree module and its functions when parsing sql + /// queries. + rtree, } diff --git a/drift_dev/lib/src/analyzer/options.g.dart b/drift_dev/lib/src/analyzer/options.g.dart index 86843646..e0af0af8 100644 --- a/drift_dev/lib/src/analyzer/options.g.dart +++ b/drift_dev/lib/src/analyzer/options.g.dart @@ -140,6 +140,7 @@ const _$SqlModuleEnumMap = { SqlModule.fts5: 'fts5', SqlModule.moor_ffi: 'moor_ffi', SqlModule.math: 'math', + SqlModule.rtree: 'rtree', }; DialectOptions _$DialectOptionsFromJson(Map json) => $checkedCreate( diff --git a/drift_dev/lib/src/analyzer/session.dart b/drift_dev/lib/src/analyzer/session.dart index 010baa14..148973f9 100644 --- a/drift_dev/lib/src/analyzer/session.dart +++ b/drift_dev/lib/src/analyzer/session.dart @@ -45,6 +45,7 @@ class MoorSession { if (options.hasModule(SqlModule.json1)) const Json1Extension(), if (options.hasModule(SqlModule.moor_ffi)) const MoorFfiExtension(), if (options.hasModule(SqlModule.math)) const BuiltInMathExtension(), + if (options.hasModule(SqlModule.rtree)) const RTreeExtension(), ], version: options.sqliteVersion, ); diff --git a/sqlparser/lib/sqlparser.dart b/sqlparser/lib/sqlparser.dart index 7324143e..a84a65bb 100644 --- a/sqlparser/lib/sqlparser.dart +++ b/sqlparser/lib/sqlparser.dart @@ -7,6 +7,7 @@ export 'src/ast/ast.dart'; export 'src/engine/module/fts5.dart' show Fts5Extension; 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/module.dart'; export 'src/engine/options.dart'; export 'src/engine/sql_engine.dart'; diff --git a/sqlparser/lib/src/engine/module/rtree.dart b/sqlparser/lib/src/engine/module/rtree.dart new file mode 100644 index 00000000..807aa70e --- /dev/null +++ b/sqlparser/lib/src/engine/module/rtree.dart @@ -0,0 +1,39 @@ +import 'package:sqlparser/sqlparser.dart'; + +class RTreeExtension implements Extension { + const RTreeExtension(); + + @override + void register(SqlEngine engine) { + engine.registerModule(_RTreeModule()); + } +} + +class _RTreeModule extends Module { + _RTreeModule() : super('rtree'); + + @override + Table parseTable(CreateVirtualTableStatement stmt) { + final columnNames = stmt.argumentContent; + + if (columnNames.length < 3 || columnNames.length > 11) { + throw ArgumentError( + 'An rtree virtual table is supposed to have between 3 and 11 columns'); + } + + if (columnNames.length.isEven) { + throw ArgumentError( + 'The rtree has not been initialized with a proper dimension. ' + 'Required is an index, follwoed by a even number of min/max pairs'); + } + + return Table(name: stmt.tableName, resolvedColumns: [ + for (final columnName in columnNames) + //First column is always an integer primary key + //followed by n floating point values + (columnName == columnNames.first) + ? TableColumn(columnName, const ResolvedType(type: BasicType.int)) + : TableColumn(columnName, const ResolvedType(type: BasicType.real)) + ]); + } +} diff --git a/sqlparser/test/engine/module/rtree_test.dart b/sqlparser/test/engine/module/rtree_test.dart new file mode 100644 index 00000000..4930bb27 --- /dev/null +++ b/sqlparser/test/engine/module/rtree_test.dart @@ -0,0 +1,65 @@ +import 'package:sqlparser/sqlparser.dart'; +import 'package:test/test.dart'; + +final _rtreeOptions = + EngineOptions(enabledExtensions: const [RTreeExtension()]); + +void main() { + group('creating rtree tables', () { + final engine = SqlEngine(_rtreeOptions); + + test('can create rtree table', () { + final result = engine.analyze(''' +CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate +);'''); + + final table = const SchemaFromCreateTable() + .read(result.root as TableInducingStatement); + + expect(table.name, 'demo_index'); + final columns = table.resultColumns; + expect(columns, hasLength(5)); + expect(columns.first.type.type, equals(BasicType.int)); + expect(columns.last.type.type, equals(BasicType.real)); + }); + }); + + group('type inference for function arguments', () { + late SqlEngine engine; + setUp(() { + engine = SqlEngine(_rtreeOptions); + // add an fts5 table for the following queries + final result = engine.analyze(''' +CREATE VIRTUAL TABLE demo_index USING rtree( + id, -- Integer primary key + minX, maxX, -- Minimum and maximum X coordinate + minY, maxY -- Minimum and maximum Y coordinate +);'''); + + engine.registerTable(const SchemaFromCreateTable() + .read(result.root as TableInducingStatement)); + }); + + test('insert', () { + final result = engine.analyze('INSERT INTO demo_index VALUES ' + '(28215, -80.781227, -80.604706, 35.208813, 35.297367);'); + expect(result.errors, isEmpty); + }); + + test('select', () { + final result = engine.analyze(''' +SELECT A.* FROM demo_index AS A, demo_index AS B + WHERE A.maxX>=B.minX AND A.minX<=B.maxX + AND A.maxY>=B.minY AND A.minY<=B.maxY + AND B.id=28269;'''); + + final columns = (result.root as SelectStatement).resolvedColumns; + + expect(result.errors, isEmpty); + expect(columns, hasLength(5)); + }); + }); +}