Support custom fts5 queries

This commit is contained in:
Simon Binder 2019-12-12 18:56:37 +01:00
parent da9ca61e0c
commit 94634bd48b
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
11 changed files with 92 additions and 39 deletions

View File

@ -60,6 +60,11 @@ mixin TableInfo<TableDsl extends Table, D extends DataClass> on Table {
/// Maps the given row returned by the database into the fitting data class. /// Maps the given row returned by the database into the fitting data class.
D map(Map<String, dynamic> data, {String tablePrefix}); D map(Map<String, dynamic> data, {String tablePrefix});
/// Like [map], but from a [row] instead of the low-level map.
D mapFromRow(QueryRow row, {String tablePrefix}) {
return map(row.data, tablePrefix: tablePrefix);
}
TableInfo<TableDsl, D> createAlias(String alias); TableInfo<TableDsl, D> createAlias(String alias);
@override @override

View File

@ -1113,10 +1113,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
Email _email; Email _email;
Email get email => _email ??= Email(this); Email get email => _email ??= Email(this);
Config _rowToConfig(QueryRow row) { Config _rowToConfig(QueryRow row) {
return Config( return config.mapFromRow(row);
configKey: row.readString('config_key'),
configValue: row.readString('config_value'),
);
} }
Selectable<Config> readConfig(String var1) { Selectable<Config> readConfig(String var1) {
@ -1157,6 +1154,17 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
readsFrom: {config}).map(_rowToConfig); readsFrom: {config}).map(_rowToConfig);
} }
EMail _rowToEMail(QueryRow row) {
return email.mapFromRow(row);
}
Selectable<EMail> searchEmails(String term) {
return customSelectQuery(
'SELECT * FROM email WHERE email MATCH :term ORDER BY rank',
variables: [Variable.withString(term)],
readsFrom: {email}).map(_rowToEMail);
}
ReadRowIdResult _rowToReadRowIdResult(QueryRow row) { ReadRowIdResult _rowToReadRowIdResult(QueryRow row) {
return ReadRowIdResult( return ReadRowIdResult(
rowid: row.readInt('rowid'), rowid: row.readInt('rowid'),

View File

@ -34,6 +34,8 @@ readMultiple: SELECT * FROM config WHERE config_key IN ? ORDER BY $clause;
readDynamic: SELECT * FROM config WHERE $predicate; readDynamic: SELECT * FROM config WHERE $predicate;
findValidJsons: SELECT * FROM config WHERE json_valid(config_value); findValidJsons: SELECT * FROM config WHERE json_valid(config_value);
searchEmails: SELECT * FROM email WHERE email MATCH :term ORDER BY rank;
readRowId: SELECT oid, * FROM config WHERE _rowid_ = $expr; readRowId: SELECT oid, * FROM config WHERE _rowid_ = $expr;
cfeTest: WITH RECURSIVE cfeTest: WITH RECURSIVE

View File

@ -1372,13 +1372,7 @@ abstract class _$TodoDb extends GeneratedDatabase {
} }
TodoEntry _rowToTodoEntry(QueryRow row) { TodoEntry _rowToTodoEntry(QueryRow row) {
return TodoEntry( return todosTable.mapFromRow(row);
id: row.readInt('id'),
title: row.readString('title'),
content: row.readString('content'),
targetDate: row.readDateTime('target_date'),
category: row.readInt('category'),
);
} }
Selectable<TodoEntry> withInQuery(String var1, String var2, List<int> var3) { Selectable<TodoEntry> withInQuery(String var1, String var2, List<int> var3) {
@ -1499,13 +1493,7 @@ mixin _$SomeDaoMixin on DatabaseAccessor<TodoDb> {
$SharedTodosTable get sharedTodos => db.sharedTodos; $SharedTodosTable get sharedTodos => db.sharedTodos;
$TodosTableTable get todosTable => db.todosTable; $TodosTableTable get todosTable => db.todosTable;
TodoEntry _rowToTodoEntry(QueryRow row) { TodoEntry _rowToTodoEntry(QueryRow row) {
return TodoEntry( return todosTable.mapFromRow(row);
id: row.readInt('id'),
title: row.readString('title'),
content: row.readString('content'),
targetDate: row.readDateTime('target_date'),
category: row.readInt('category'),
);
} }
Selectable<TodoEntry> todosForUserQuery(int user) { Selectable<TodoEntry> todosForUserQuery(int user) {

View File

@ -23,6 +23,8 @@ class CreateTableReader {
final primaryKey = <SpecifiedColumn>{}; final primaryKey = <SpecifiedColumn>{};
for (final column in table.resolvedColumns) { for (final column in table.resolvedColumns) {
if (!column.includedInResults) continue;
var isPrimaryKey = false; var isPrimaryKey = false;
final features = <ColumnFeature>[]; final features = <ColumnFeature>[];
final sqlName = column.name; final sqlName = column.name;

View File

@ -16,6 +16,7 @@ class TypeMapper {
Table extractStructure(SpecifiedTable table) { Table extractStructure(SpecifiedTable table) {
final existingTable = table.declaration?.tableFromSqlParser; final existingTable = table.declaration?.tableFromSqlParser;
if (existingTable != null) { if (existingTable != null) {
_engineTablesToSpecified[existingTable] = table;
return existingTable; return existingTable;
} }

View File

@ -1,7 +1,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:moor_generator/src/analyzer/options.dart'; import 'package:moor_generator/src/analyzer/options.dart';
import 'package:moor_generator/src/analyzer/sql_queries/meta/declarations.dart'; import 'package:moor_generator/src/analyzer/sql_queries/meta/declarations.dart';
import 'package:moor_generator/src/model/specified_column.dart'; import 'package:moor_generator/src/model/specified_column.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:moor_generator/src/model/used_type_converter.dart'; import 'package:moor_generator/src/model/used_type_converter.dart';
import 'package:recase/recase.dart'; import 'package:recase/recase.dart';
import 'package:sqlparser/sqlparser.dart'; import 'package:sqlparser/sqlparser.dart';
@ -34,6 +34,7 @@ class SpecifiedTable {
/// The name for the data class associated with this table /// The name for the data class associated with this table
final String dartTypeName; final String dartTypeName;
/// The getter name used for this table in a generated database or dao class.
String get tableFieldName => _dbFieldName(_baseName); String get tableFieldName => _dbFieldName(_baseName);
String get tableInfoName { String get tableInfoName {
// if this table was parsed from sql, a user might want to refer to it // if this table was parsed from sql, a user might want to refer to it

View File

@ -96,15 +96,25 @@ class QueryWriter {
if (!_writtenMappingMethods.contains(_nameOfMappingMethod())) { if (!_writtenMappingMethods.contains(_nameOfMappingMethod())) {
_buffer _buffer
..write('${_select.resultClassName} ${_nameOfMappingMethod()}') ..write('${_select.resultClassName} ${_nameOfMappingMethod()}')
..write('(QueryRow row) {\n') ..write('(QueryRow row) {\n');
..write('return ${_select.resultClassName}(');
for (final column in _select.resultSet.columns) { // If we're matching an existing table from moor - let's just use the
final fieldName = _select.resultSet.dartNameFor(column); // mapping method we're generating for each table!
_buffer.write('$fieldName: ${_readingCode(column)},'); if (_select.resultSet.matchingTable != null) {
final table = _select.resultSet.matchingTable;
_buffer.write('return ${table.tableFieldName}.mapFromRow(row);\n');
} else {
// For more complex results, generate a custom constructor call
_buffer.write('return ${_select.resultClassName}(');
for (final column in _select.resultSet.columns) {
final fieldName = _select.resultSet.dartNameFor(column);
_buffer.write('$fieldName: ${_readingCode(column)},');
}
_buffer.write(');\n');
} }
_buffer.write('}\n');
_buffer.write(');\n}\n');
_writtenMappingMethods.add(_nameOfMappingMethod()); _writtenMappingMethods.add(_nameOfMappingMethod());
} }
} }

View File

@ -5,6 +5,12 @@ abstract class Column with Referencable, HasMetaMixin implements Typeable {
/// The name of this column in the result set. /// The name of this column in the result set.
String get name; String get name;
/// Whether this column is included in results when running a select query
/// like `SELECT * FROM table`.
///
/// Some columns, notably the rowid aliases, are exempt from this.
bool get includedInResults => true;
Column(); Column();
} }
@ -85,6 +91,9 @@ class RowId extends TableColumn {
// note that such alias is always called "rowid" in the result set - // note that such alias is always called "rowid" in the result set -
// "SELECT oid FROM table" yields a sinle column called "rowid" // "SELECT oid FROM table" yields a sinle column called "rowid"
RowId() : super('rowid', const ResolvedType(type: BasicType.int)); RowId() : super('rowid', const ResolvedType(type: BasicType.int));
@override
bool get includedInResults => false;
} }
/// A column that is created by an expression. For instance, in the select /// A column that is created by an expression. For instance, in the select

View File

@ -131,6 +131,8 @@ class ColumnResolver extends RecursiveVisitor<void> {
// result, but also expressions that appear as result columns // result, but also expressions that appear as result columns
for (final resultColumn in s.columns) { for (final resultColumn in s.columns) {
if (resultColumn is StarResultColumn) { if (resultColumn is StarResultColumn) {
Iterable<Column> visibleColumnsForStar;
if (resultColumn.tableName != null) { if (resultColumn.tableName != null) {
final tableResolver = scope final tableResolver = scope
.resolve<ResolvesToResultSet>(resultColumn.tableName, orElse: () { .resolve<ResolvesToResultSet>(resultColumn.tableName, orElse: () {
@ -141,11 +143,15 @@ class ColumnResolver extends RecursiveVisitor<void> {
)); ));
}); });
usedColumns.addAll(tableResolver.resultSet.resolvedColumns); visibleColumnsForStar = tableResolver.resultSet.resolvedColumns;
} else { } else {
// we have a * column, that would be all available columns // we have a * column without a table, that resolves to every columns
usedColumns.addAll(availableColumns); // available
visibleColumnsForStar = availableColumns;
} }
usedColumns
.addAll(visibleColumnsForStar.where((e) => e.includedInResults));
} else if (resultColumn is ExpressionResultColumn) { } else if (resultColumn is ExpressionResultColumn) {
final expression = resultColumn.expression; final expression = resultColumn.expression;
Column column; Column column;

View File

@ -1,8 +1,6 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:sqlparser/sqlparser.dart'; import 'package:sqlparser/sqlparser.dart';
final _rankColumn = _Fts5RankColumn();
class Fts5Extension implements Extension { class Fts5Extension implements Extension {
const Fts5Extension(); const Fts5Extension();
@ -42,17 +40,40 @@ class _Fts5Table extends Table {
{@required String name, {@required String name,
List<TableColumn> columns, List<TableColumn> columns,
CreateVirtualTableStatement definition}) CreateVirtualTableStatement definition})
: super(name: name, resolvedColumns: columns, definition: definition); : super(
name: name,
@override resolvedColumns: [
Column findColumn(String name) { ...columns,
if (name == 'rank') { _Fts5RankColumn(),
return _rankColumn; _Fts5TableColumn(name),
} ],
return super.findColumn(name); definition: definition,
} );
} }
/// The rank column, which we introduce to support queries like
/// ```
/// SELECT * FROM my_fts_table WHERE my_fts_table MATCH 'foo' ORDER BY rank;
/// ```
class _Fts5RankColumn extends TableColumn { class _Fts5RankColumn extends TableColumn {
@override
bool get includedInResults => false;
_Fts5RankColumn() : super('rank', const ResolvedType(type: BasicType.int)); _Fts5RankColumn() : super('rank', const ResolvedType(type: BasicType.int));
} }
/// A column that has the same name as the fts5 it's from. We introduce this
/// column to support constructs like
/// ```
/// CREATE VIRTUAL TABLE foo USING fts5(bar, baz);
/// query: SELECT * FROM foo WHERE foo MATCH 'something';
/// ```
/// The easiest way to support that is to just make "foo" a column on that
/// table.
class _Fts5TableColumn extends TableColumn {
@override
bool get includedInResults => false;
_Fts5TableColumn(String name)
: super(name, const ResolvedType(type: BasicType.text));
}