Skip to content

Commit e1ae6b7

Browse files
committed
Port search
1 parent 11018be commit e1ae6b7

File tree

9 files changed

+134
-129
lines changed

9 files changed

+134
-129
lines changed

demos/supabase-todolist-drift/lib/components/app_bar.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import 'package:powersync/powersync.dart';
66

77
import '../powersync/powersync.dart';
8+
import '../screens/search.dart';
89

910
final appBar = AppBar(
1011
title: const Text('PowerSync Flutter Demo'),
@@ -29,7 +30,7 @@ final class StatusAppBar extends ConsumerWidget implements PreferredSizeWidget {
2930
actions: <Widget>[
3031
IconButton(
3132
onPressed: () {
32-
// showSearch(context: context, delegate: FtsSearchDelegate());
33+
showSearch(context: context, delegate: FtsSearchDelegate());
3334
},
3435
icon: const Icon(Icons.search),
3536
),

demos/supabase-todolist-drift/lib/migrations/fts_setup.dart

Lines changed: 0 additions & 76 deletions
This file was deleted.

demos/supabase-todolist-drift/lib/migrations/helpers.dart

Lines changed: 0 additions & 38 deletions
This file was deleted.

demos/supabase-todolist-drift/lib/powersync/database.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:drift_sqlite_async/drift_sqlite_async.dart';
33
import 'package:flutter_riverpod/flutter_riverpod.dart';
44
import 'package:powersync/powersync.dart' show uuid;
55

6+
import 'fts5.dart';
67
import 'powersync.dart';
78

89
part 'database.g.dart';
@@ -54,7 +55,36 @@ class AppDatabase extends _$AppDatabase {
5455
AppDatabase(super.e);
5556

5657
@override
57-
int get schemaVersion => 1;
58+
int get schemaVersion => 2;
59+
60+
@override
61+
MigrationStrategy get migration {
62+
return MigrationStrategy(
63+
onCreate: (m) async {
64+
// We don't have to call createAll(), PowerSync instantiates the schema
65+
// for us. We can use the opportunity to create fts5 indexes though.
66+
await createFts5Tables(
67+
db: this,
68+
tableName: 'lists',
69+
columns: ['name'],
70+
);
71+
await createFts5Tables(
72+
db: this,
73+
tableName: 'todos',
74+
columns: ['description', 'list_id'],
75+
);
76+
},
77+
onUpgrade: (m, from, to) async {
78+
if (from == 1) {
79+
await createFts5Tables(
80+
db: this,
81+
tableName: 'todos',
82+
columns: ['description', 'list_id'],
83+
);
84+
}
85+
},
86+
);
87+
}
5888

5989
Future<void> addTodoPhoto(String todoId, String photoId) async {
6090
await (update(todoItems)..where((t) => t.id.equals(todoId)))
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'dart:async';
2+
3+
import 'package:drift/drift.dart';
4+
5+
import 'schema.dart';
6+
7+
Future<void> createFts5Tables({
8+
required DatabaseConnectionUser db,
9+
required String tableName,
10+
required List<String> columns,
11+
String tokenizationMethod = 'unicode61',
12+
}) async {
13+
String internalName =
14+
schema.tables.firstWhere((table) => table.name == tableName).internalName;
15+
String stringColumns = columns.join(', ');
16+
17+
await db.customStatement('''
18+
CREATE VIRTUAL TABLE IF NOT EXISTS fts_$tableName
19+
USING fts5(id UNINDEXED, $stringColumns, tokenize='$tokenizationMethod');
20+
''');
21+
// Copy over records already in table
22+
await db.customStatement('''
23+
INSERT INTO fts_$tableName(rowid, id, $stringColumns)
24+
SELECT rowid, id, ${generateJsonExtracts(ExtractType.columnOnly, 'data', columns)} FROM $internalName;
25+
''');
26+
// Add INSERT, UPDATE and DELETE and triggers to keep fts table in sync with table
27+
await db.customStatement('''
28+
CREATE TRIGGER IF NOT EXISTS fts_insert_trigger_$tableName AFTER INSERT ON $internalName
29+
BEGIN
30+
INSERT INTO fts_$tableName(rowid, id, $stringColumns)
31+
VALUES (
32+
NEW.rowid,
33+
NEW.id,
34+
${generateJsonExtracts(ExtractType.columnOnly, 'NEW.data', columns)}
35+
);
36+
END;
37+
''');
38+
await db.customStatement('''
39+
CREATE TRIGGER IF NOT EXISTS fts_update_trigger_$tableName AFTER UPDATE ON $internalName BEGIN
40+
UPDATE fts_$tableName
41+
SET ${generateJsonExtracts(ExtractType.columnInOperation, 'NEW.data', columns)}
42+
WHERE rowid = NEW.rowid;
43+
END;
44+
''');
45+
await db.customStatement('''
46+
CREATE TRIGGER IF NOT EXISTS fts_delete_trigger_$tableName AFTER DELETE ON $internalName BEGIN
47+
DELETE FROM fts_$tableName WHERE rowid = OLD.rowid;
48+
END;
49+
''');
50+
}
51+
52+
typedef ExtractGenerator = String Function(String, String);
53+
54+
enum ExtractType {
55+
columnOnly,
56+
columnInOperation,
57+
}
58+
59+
typedef ExtractGeneratorMap = Map<ExtractType, ExtractGenerator>;
60+
61+
String _createExtract(String jsonColumnName, String columnName) =>
62+
'json_extract($jsonColumnName, \'\$.$columnName\')';
63+
64+
ExtractGeneratorMap extractGeneratorsMap = {
65+
ExtractType.columnOnly: (
66+
String jsonColumnName,
67+
String columnName,
68+
) =>
69+
_createExtract(jsonColumnName, columnName),
70+
ExtractType.columnInOperation: (
71+
String jsonColumnName,
72+
String columnName,
73+
) =>
74+
'$columnName = ${_createExtract(jsonColumnName, columnName)}',
75+
};
76+
77+
String generateJsonExtracts(
78+
ExtractType type, String jsonColumnName, List<String> columns) {
79+
ExtractGenerator? generator = extractGeneratorsMap[type];
80+
if (generator == null) {
81+
throw StateError('Unexpected null generator for key: $type');
82+
}
83+
84+
if (columns.length == 1) {
85+
return generator(jsonColumnName, columns.first);
86+
}
87+
88+
return columns.map((column) => generator(jsonColumnName, column)).join(', ');
89+
}

demos/supabase-todolist-drift/lib/fts_helpers.dart renamed to demos/supabase-todolist-drift/lib/powersync/fts_helpers.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:flutter_riverpod/flutter_riverpod.dart';
22
import 'package:riverpod_annotation/riverpod_annotation.dart';
3-
import 'package:supabase_todolist_drift/powersync.dart';
3+
4+
import 'powersync.dart';
45

56
part 'fts_helpers.g.dart';
67

demos/supabase-todolist-drift/lib/fts_helpers.g.dart renamed to demos/supabase-todolist-drift/lib/powersync/fts_helpers.g.dart

File renamed without changes.

demos/supabase-todolist-drift/lib/widgets/fts_search_delegate.dart renamed to demos/supabase-todolist-drift/lib/screens/search.dart

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
import 'package:auto_route/auto_route.dart';
12
import 'package:flutter/material.dart';
2-
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:hooks_riverpod/hooks_riverpod.dart';
34
import 'package:logging/logging.dart';
45
import 'package:riverpod_annotation/riverpod_annotation.dart';
5-
import 'package:supabase_todolist_drift/powersync/database.dart';
6-
import 'package:supabase_todolist_drift/fts_helpers.dart' as fts_helpers;
7-
import 'package:supabase_todolist_drift/powersync.dart';
86

9-
import 'todo_list_page.dart';
7+
import '../navigation.dart';
8+
import '../powersync/database.dart';
9+
import '../powersync/fts_helpers.dart' as fts_helpers;
1010

11-
part 'fts_search_delegate.g.dart';
11+
part 'search.g.dart';
1212

1313
final log = Logger('powersync-supabase');
1414

@@ -63,8 +63,6 @@ class FtsSearchDelegate extends SearchDelegate {
6363

6464
@override
6565
Widget buildSuggestions(BuildContext context) {
66-
NavigatorState navigator = Navigator.of(context);
67-
6866
return Consumer(
6967
builder: (context, ref, _) {
7068
final results = ref.watch(_searchProvider(query));
@@ -78,9 +76,9 @@ class FtsSearchDelegate extends SearchDelegate {
7876
title: Text(rows[index]['name'] ?? ''),
7977
onTap: () async {
8078
ListItem list = await appDb.findList(rows[index]['id']);
81-
navigator.push(MaterialPageRoute(
82-
builder: (context) => TodoListPage(list: list),
83-
));
79+
if (context.mounted) {
80+
context.pushRoute(ListsDetailsRoute(list: list.id));
81+
}
8482
},
8583
);
8684
},

demos/supabase-todolist-drift/lib/widgets/fts_search_delegate.g.dart renamed to demos/supabase-todolist-drift/lib/screens/search.g.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)