mirror of https://github.com/AMT-Cheif/drift.git
Support custom fts5 queries
This commit is contained in:
parent
da9ca61e0c
commit
94634bd48b
|
@ -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
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}(');
|
|
||||||
|
|
||||||
|
// 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) {
|
for (final column in _select.resultSet.columns) {
|
||||||
final fieldName = _select.resultSet.dartNameFor(column);
|
final fieldName = _select.resultSet.dartNameFor(column);
|
||||||
_buffer.write('$fieldName: ${_readingCode(column)},');
|
_buffer.write('$fieldName: ${_readingCode(column)},');
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.write(');\n}\n');
|
_buffer.write(');\n');
|
||||||
|
}
|
||||||
|
_buffer.write('}\n');
|
||||||
|
|
||||||
_writtenMappingMethods.add(_nameOfMappingMethod());
|
_writtenMappingMethods.add(_nameOfMappingMethod());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue