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.
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);
@override

View File

@ -1113,10 +1113,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
Email _email;
Email get email => _email ??= Email(this);
Config _rowToConfig(QueryRow row) {
return Config(
configKey: row.readString('config_key'),
configValue: row.readString('config_value'),
);
return config.mapFromRow(row);
}
Selectable<Config> readConfig(String var1) {
@ -1157,6 +1154,17 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
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) {
return ReadRowIdResult(
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;
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;
cfeTest: WITH RECURSIVE

View File

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

View File

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

View File

@ -16,6 +16,7 @@ class TypeMapper {
Table extractStructure(SpecifiedTable table) {
final existingTable = table.declaration?.tableFromSqlParser;
if (existingTable != null) {
_engineTablesToSpecified[existingTable] = table;
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/sql_queries/meta/declarations.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:recase/recase.dart';
import 'package:sqlparser/sqlparser.dart';
@ -34,6 +34,7 @@ class SpecifiedTable {
/// The name for the data class associated with this table
final String dartTypeName;
/// The getter name used for this table in a generated database or dao class.
String get tableFieldName => _dbFieldName(_baseName);
String get tableInfoName {
// 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())) {
_buffer
..write('${_select.resultClassName} ${_nameOfMappingMethod()}')
..write('(QueryRow row) {\n')
..write('return ${_select.resultClassName}(');
..write('(QueryRow row) {\n');
for (final column in _select.resultSet.columns) {
final fieldName = _select.resultSet.dartNameFor(column);
_buffer.write('$fieldName: ${_readingCode(column)},');
// If we're matching an existing table from moor - let's just use the
// mapping method we're generating for each table!
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());
}
}

View File

@ -5,6 +5,12 @@ abstract class Column with Referencable, HasMetaMixin implements Typeable {
/// The name of this column in the result set.
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();
}
@ -85,6 +91,9 @@ class RowId extends TableColumn {
// note that such alias is always called "rowid" in the result set -
// "SELECT oid FROM table" yields a sinle column called "rowid"
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

View File

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

View File

@ -1,8 +1,6 @@
import 'package:meta/meta.dart';
import 'package:sqlparser/sqlparser.dart';
final _rankColumn = _Fts5RankColumn();
class Fts5Extension implements Extension {
const Fts5Extension();
@ -42,17 +40,40 @@ class _Fts5Table extends Table {
{@required String name,
List<TableColumn> columns,
CreateVirtualTableStatement definition})
: super(name: name, resolvedColumns: columns, definition: definition);
@override
Column findColumn(String name) {
if (name == 'rank') {
return _rankColumn;
}
return super.findColumn(name);
}
: super(
name: name,
resolvedColumns: [
...columns,
_Fts5RankColumn(),
_Fts5TableColumn(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 {
@override
bool get includedInResults => false;
_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));
}