per-containder indexing opt out
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabRepositoryHash() => r'2cd5b6bf5bb9a62c05b038b28acef59cecbeae4e';
|
||||
String _$tabRepositoryHash() => r'7d41038ac5a20ecf6de0645b99231fb8b2f6963a';
|
||||
|
||||
abstract class _$TabRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
@@ -40,7 +40,7 @@ import 'package:weblibre/features/search/domain/fts_tokenizer.dart';
|
||||
)
|
||||
class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
||||
@override
|
||||
final int schemaVersion = 13;
|
||||
final int schemaVersion = 14;
|
||||
|
||||
@override
|
||||
final int ftsTokenLimit = 10;
|
||||
@@ -63,7 +63,9 @@ class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
||||
|
||||
await customStatement('PRAGMA foreign_keys = ON');
|
||||
await definitionsDrift.optimizeFtsIndex();
|
||||
await definitionsDrift.optimizeHistoryFtsIndex();
|
||||
if (details.versionNow >= 12) {
|
||||
await definitionsDrift.optimizeHistoryFtsIndex();
|
||||
}
|
||||
},
|
||||
onUpgrade: (m, from, to) async {
|
||||
// Following the advice from https://drift.simonbinder.eu/Migrations/api/#general-tips
|
||||
@@ -234,5 +236,16 @@ class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
||||
rank = rank.genNext();
|
||||
}
|
||||
},
|
||||
from13To14: (m, schema) async {
|
||||
// Add per-container exclude-from-index gate to the tab→history
|
||||
// triggers, plus re-evaluation triggers for container assignment and
|
||||
// exclude-flag changes.
|
||||
await m.drop(schema.tabToHistoryOnInsert);
|
||||
await m.create(schema.tabToHistoryOnInsert);
|
||||
await m.drop(schema.tabToHistoryOnUpdate);
|
||||
await m.create(schema.tabToHistoryOnUpdate);
|
||||
await m.create(schema.tabToHistoryOnContainerUpdate);
|
||||
await m.create(schema.containerToHistoryOnMetadataUpdate);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ abstract class $TabDatabase extends i0.GeneratedDatabase {
|
||||
i1.historyAfterUpdate,
|
||||
i1.tabToHistoryOnInsert,
|
||||
i1.tabToHistoryOnUpdate,
|
||||
i1.tabToHistoryOnContainerUpdate,
|
||||
i1.containerToHistoryOnMetadataUpdate,
|
||||
];
|
||||
@override
|
||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||
@@ -149,6 +151,26 @@ abstract class $TabDatabase extends i0.GeneratedDatabase {
|
||||
),
|
||||
result: [i0.TableUpdate('history', kind: i0.UpdateKind.insert)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'tab',
|
||||
limitUpdateKind: i0.UpdateKind.update,
|
||||
),
|
||||
result: [
|
||||
i0.TableUpdate('history', kind: i0.UpdateKind.delete),
|
||||
i0.TableUpdate('history', kind: i0.UpdateKind.insert),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'container',
|
||||
limitUpdateKind: i0.UpdateKind.update,
|
||||
),
|
||||
result: [
|
||||
i0.TableUpdate('history', kind: i0.UpdateKind.delete),
|
||||
i0.TableUpdate('history', kind: i0.UpdateKind.insert),
|
||||
],
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1823,6 +1823,222 @@ class Shape12 extends i0.VersionedTable {
|
||||
columnsByName['metadata']! as i1.GeneratedColumn<String>;
|
||||
}
|
||||
|
||||
final class Schema14 extends i0.VersionedSchema {
|
||||
Schema14({required super.database}) : super(version: 14);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
container,
|
||||
tab,
|
||||
closedTabTombstone,
|
||||
idxTabParentContainer,
|
||||
captureTab,
|
||||
idxCaptureTabCaptureId,
|
||||
tabFts,
|
||||
tabMaintainParentChainOnDelete,
|
||||
tabAfterInsert,
|
||||
tabAfterDelete,
|
||||
tabAfterUpdate,
|
||||
localIndexSetting,
|
||||
history,
|
||||
idxHistoryHost,
|
||||
idxHistoryObserved,
|
||||
historyFts,
|
||||
historyAfterInsert,
|
||||
historyAfterDelete,
|
||||
historyAfterUpdate,
|
||||
tabToHistoryOnInsert,
|
||||
tabToHistoryOnUpdate,
|
||||
tabToHistoryOnContainerUpdate,
|
||||
containerToHistoryOnMetadataUpdate,
|
||||
];
|
||||
late final Shape12 container = Shape12(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'container',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_2,
|
||||
_column_6,
|
||||
_column_19,
|
||||
_column_3,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape8 tab = Shape8(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'tab',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'CHECK((tab_mode = 2 AND isolation_context_id IS NOT NULL)OR(tab_mode != 2 AND isolation_context_id IS NULL))',
|
||||
],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_16,
|
||||
_column_4,
|
||||
_column_5,
|
||||
_column_6,
|
||||
_column_7,
|
||||
_column_8,
|
||||
_column_17,
|
||||
_column_18,
|
||||
_column_19,
|
||||
_column_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_13,
|
||||
_column_14,
|
||||
_column_15,
|
||||
_column_27,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape6 closedTabTombstone = Shape6(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'closed_tab_tombstone',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_20, _column_21],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxTabParentContainer = i1.Index(
|
||||
'idx_tab_parent_container',
|
||||
'CREATE INDEX idx_tab_parent_container ON tab (parent_id, container_id)',
|
||||
);
|
||||
late final Shape7 captureTab = Shape7(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'capture_tab',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_22, _column_23, _column_24, _column_25, _column_26],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxCaptureTabCaptureId = i1.Index(
|
||||
'idx_capture_tab_capture_id',
|
||||
'CREATE INDEX idx_capture_tab_capture_id ON capture_tab (capture_id)',
|
||||
);
|
||||
late final Shape2 tabFts = Shape2(
|
||||
source: i0.VersionedVirtualTable(
|
||||
entityName: 'tab_fts',
|
||||
moduleAndArgs:
|
||||
'fts5(title, url, extracted_content_plain, full_content_plain, content=tab, tokenize="trigram")',
|
||||
columns: [_column_8, _column_7, _column_12, _column_14],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Trigger tabMaintainParentChainOnDelete = i1.Trigger(
|
||||
'CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN UPDATE tab SET parent_id = CASE WHEN OLD.parent_id IS NOT NULL AND EXISTS (SELECT 1 FROM tab WHERE id = OLD.parent_id) THEN OLD.parent_id ELSE NULL END WHERE parent_id = OLD.id;END',
|
||||
'tab_maintain_parent_chain_on_delete',
|
||||
);
|
||||
final i1.Trigger tabAfterInsert = i1.Trigger(
|
||||
'CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END',
|
||||
'tab_after_insert',
|
||||
);
|
||||
final i1.Trigger tabAfterDelete = i1.Trigger(
|
||||
'CREATE TRIGGER tab_after_delete AFTER DELETE ON tab BEGIN INSERT INTO tab_fts (tab_fts, "rowid", title, url, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url, old.extracted_content_plain, old.full_content_plain);END',
|
||||
'tab_after_delete',
|
||||
);
|
||||
final i1.Trigger tabAfterUpdate = i1.Trigger(
|
||||
'CREATE TRIGGER tab_after_update AFTER UPDATE OF title, url, extracted_content_plain, full_content_plain ON tab WHEN OLD.content_hash IS NOT NEW.content_hash OR OLD.url IS NOT NEW.url BEGIN INSERT INTO tab_fts (tab_fts, "rowid", title, url, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url, old.extracted_content_plain, old.full_content_plain);INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END',
|
||||
'tab_after_update',
|
||||
);
|
||||
late final Shape9 localIndexSetting = Shape9(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_index_setting',
|
||||
withoutRowId: false,
|
||||
isStrict: true,
|
||||
tableConstraints: [],
|
||||
columns: [_column_28, _column_29],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape10 history = Shape10(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'history',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_30,
|
||||
_column_31,
|
||||
_column_32,
|
||||
_column_8,
|
||||
_column_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_13,
|
||||
_column_14,
|
||||
_column_33,
|
||||
_column_34,
|
||||
_column_35,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxHistoryHost = i1.Index(
|
||||
'idx_history_host',
|
||||
'CREATE INDEX idx_history_host ON history (url_host)',
|
||||
);
|
||||
final i1.Index idxHistoryObserved = i1.Index(
|
||||
'idx_history_observed',
|
||||
'CREATE INDEX idx_history_observed ON history (observed_at DESC)',
|
||||
);
|
||||
late final Shape11 historyFts = Shape11(
|
||||
source: i0.VersionedVirtualTable(
|
||||
entityName: 'history_fts',
|
||||
moduleAndArgs:
|
||||
'fts5(title, url_host, url_path, extracted_content_plain, full_content_plain, content=history, tokenize="trigram")',
|
||||
columns: [_column_8, _column_36, _column_32, _column_12, _column_14],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Trigger historyAfterInsert = i1.Trigger(
|
||||
'CREATE TRIGGER history_after_insert AFTER INSERT ON history BEGIN INSERT INTO history_fts ("rowid", title, url_host, url_path, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url_host, new.url_path, new.extracted_content_plain, new.full_content_plain);END',
|
||||
'history_after_insert',
|
||||
);
|
||||
final i1.Trigger historyAfterDelete = i1.Trigger(
|
||||
'CREATE TRIGGER history_after_delete AFTER DELETE ON history BEGIN INSERT INTO history_fts (history_fts, "rowid", title, url_host, url_path, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url_host, old.url_path, old.extracted_content_plain, old.full_content_plain);END',
|
||||
'history_after_delete',
|
||||
);
|
||||
final i1.Trigger historyAfterUpdate = i1.Trigger(
|
||||
'CREATE TRIGGER history_after_update AFTER UPDATE OF title, url_host, url_path, extracted_content_plain, full_content_plain ON history BEGIN INSERT INTO history_fts (history_fts, "rowid", title, url_host, url_path, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url_host, old.url_path, old.extracted_content_plain, old.full_content_plain);INSERT INTO history_fts ("rowid", title, url_host, url_path, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url_host, new.url_path, new.extracted_content_plain, new.full_content_plain);END',
|
||||
'history_after_update',
|
||||
);
|
||||
final i1.Trigger tabToHistoryOnInsert = i1.Trigger(
|
||||
'CREATE TRIGGER tab_to_history_on_insert AFTER INSERT ON tab WHEN NEW.url IS NOT NULL AND url_indexable(CAST(NEW.url AS TEXT)) = 1 AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 AND(NEW.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = NEW.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) BEGIN INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) VALUES (url_canonical(CAST(NEW.url AS TEXT)), url_host(CAST(NEW.url AS TEXT)), url_path(CAST(NEW.url AS TEXT)), NEW.title, NEW.is_probably_readerable, NEW.extracted_content_markdown, NEW.extracted_content_plain, NEW.full_content_markdown, NEW.full_content_plain, NEW.content_hash, strftime(\'%s\', \'now\') * 1000, 1) ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1 WHERE history.content_hash IS NOT excluded.content_hash;END',
|
||||
'tab_to_history_on_insert',
|
||||
);
|
||||
final i1.Trigger tabToHistoryOnUpdate = i1.Trigger(
|
||||
'CREATE TRIGGER tab_to_history_on_update AFTER UPDATE OF title, url, extracted_content_plain, extracted_content_markdown, full_content_plain, full_content_markdown, is_probably_readerable ON tab WHEN NEW.url IS NOT NULL AND url_indexable(CAST(NEW.url AS TEXT)) = 1 AND(OLD.content_hash IS NOT NEW.content_hash OR OLD.url IS NOT NEW.url)AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 AND(NEW.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = NEW.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) BEGIN INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) VALUES (url_canonical(CAST(NEW.url AS TEXT)), url_host(CAST(NEW.url AS TEXT)), url_path(CAST(NEW.url AS TEXT)), NEW.title, NEW.is_probably_readerable, NEW.extracted_content_markdown, NEW.extracted_content_plain, NEW.full_content_markdown, NEW.full_content_plain, NEW.content_hash, strftime(\'%s\', \'now\') * 1000, 1) ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1 WHERE history.content_hash IS NOT excluded.content_hash;END',
|
||||
'tab_to_history_on_update',
|
||||
);
|
||||
final i1.Trigger tabToHistoryOnContainerUpdate = i1.Trigger(
|
||||
'CREATE TRIGGER tab_to_history_on_container_update AFTER UPDATE OF container_id ON tab WHEN NEW.url IS NOT NULL AND url_indexable(CAST(NEW.url AS TEXT)) = 1 AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 BEGIN DELETE FROM history WHERE url_canonical = url_canonical(CAST(NEW.url AS TEXT)) AND NOT EXISTS (SELECT 1 FROM tab AS candidate WHERE candidate.url IS NOT NULL AND url_indexable(CAST(candidate.url AS TEXT)) = 1 AND url_canonical(CAST(candidate.url AS TEXT)) = history.url_canonical AND(candidate.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = candidate.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1));INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) SELECT url_canonical(CAST(candidate.url AS TEXT)), url_host(CAST(candidate.url AS TEXT)), url_path(CAST(candidate.url AS TEXT)), candidate.title, candidate.is_probably_readerable, candidate.extracted_content_markdown, candidate.extracted_content_plain, candidate.full_content_markdown, candidate.full_content_plain, candidate.content_hash, strftime(\'%s\', \'now\') * 1000, 1 FROM tab AS candidate WHERE candidate.url IS NOT NULL AND url_indexable(CAST(candidate.url AS TEXT)) = 1 AND url_canonical(CAST(candidate.url AS TEXT)) = url_canonical(CAST(NEW.url AS TEXT)) AND(candidate.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = candidate.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) ORDER BY candidate.timestamp DESC, candidate."rowid" DESC LIMIT 1 ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1;END',
|
||||
'tab_to_history_on_container_update',
|
||||
);
|
||||
final i1.Trigger containerToHistoryOnMetadataUpdate = i1.Trigger(
|
||||
'CREATE TRIGGER container_to_history_on_metadata_update AFTER UPDATE OF metadata ON container WHEN COALESCE(json_extract(OLD.metadata, \'\$.excludeFromIndex\') = 1, 0) != COALESCE(json_extract(NEW.metadata, \'\$.excludeFromIndex\') = 1, 0) AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 BEGIN DELETE FROM history WHERE url_canonical IN (SELECT DISTINCT url_canonical(CAST(affected.url AS TEXT)) FROM tab AS affected WHERE affected.container_id = NEW.id AND affected.url IS NOT NULL AND url_indexable(CAST(affected.url AS TEXT)) = 1) AND NOT EXISTS (SELECT 1 FROM tab AS candidate WHERE candidate.url IS NOT NULL AND url_indexable(CAST(candidate.url AS TEXT)) = 1 AND url_canonical(CAST(candidate.url AS TEXT)) = history.url_canonical AND(candidate.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = candidate.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1));INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) SELECT url_canonical(CAST(candidate.url AS TEXT)), url_host(CAST(candidate.url AS TEXT)), url_path(CAST(candidate.url AS TEXT)), candidate.title, candidate.is_probably_readerable, candidate.extracted_content_markdown, candidate.extracted_content_plain, candidate.full_content_markdown, candidate.full_content_plain, candidate.content_hash, strftime(\'%s\', \'now\') * 1000, 1 FROM tab AS candidate WHERE candidate.url IS NOT NULL AND url_indexable(CAST(candidate.url AS TEXT)) = 1 AND url_canonical(CAST(candidate.url AS TEXT)) IN (SELECT DISTINCT url_canonical(CAST(affected.url AS TEXT)) FROM tab AS affected WHERE affected.container_id = NEW.id AND affected.url IS NOT NULL AND url_indexable(CAST(affected.url AS TEXT)) = 1) AND(candidate.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = candidate.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) AND NOT EXISTS (SELECT 1 FROM tab AS newer WHERE newer.url IS NOT NULL AND url_indexable(CAST(newer.url AS TEXT)) = 1 AND url_canonical(CAST(newer.url AS TEXT)) = url_canonical(CAST(candidate.url AS TEXT)) AND(newer.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = newer.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) AND(newer.timestamp > candidate.timestamp OR(newer.timestamp = candidate.timestamp AND newer."rowid" > candidate."rowid"))) ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1;END',
|
||||
'container_to_history_on_metadata_update',
|
||||
);
|
||||
}
|
||||
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||
@@ -1835,6 +2051,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
|
||||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
@@ -1893,6 +2110,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from12To13(migrator, schema);
|
||||
return 13;
|
||||
case 13:
|
||||
final schema = Schema14(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from13To14(migrator, schema);
|
||||
return 14;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
@@ -1911,6 +2133,7 @@ i1.OnUpgrade stepByStep({
|
||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
|
||||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from2To3: from2To3,
|
||||
@@ -1924,5 +2147,6 @@ i1.OnUpgrade stepByStep({
|
||||
from10To11: from10To11,
|
||||
from11To12: from11To12,
|
||||
from12To13: from12To13,
|
||||
from13To14: from13To14,
|
||||
),
|
||||
);
|
||||
|
||||
+183
-5
@@ -297,17 +297,28 @@ historyUrlsPage:
|
||||
deleteHistoryByCanonicalUrls:
|
||||
DELETE FROM history WHERE url_canonical IN :canonicalUrls;
|
||||
|
||||
-- Tab → history fan-out. Fires when content-bearing columns change AND the
|
||||
-- content actually changed (content_hash gate). Settings are read from
|
||||
-- local_index_setting at SQL level so the trigger is the single source of
|
||||
-- truth for indexing eligibility. tab_mode = 1 is the private tab marker;
|
||||
-- private tabs are skipped unless `index_private` is enabled.
|
||||
-- Tab -> history fan-out. Content triggers fire when content-bearing columns
|
||||
-- change AND the content actually changed (content_hash gate). Re-evaluation
|
||||
-- triggers fire when indexing eligibility changes without a content update
|
||||
-- (container assignment or container exclude flag changes). Settings are read
|
||||
-- from local_index_setting at SQL level so these triggers are the single source
|
||||
-- of truth for indexing eligibility. tab_mode = 1 is the private tab marker;
|
||||
-- private tabs are skipped unless `index_private` is enabled. Per-container
|
||||
-- opt-out lives in container.metadata.excludeFromIndex (JSON); tabs without
|
||||
-- a container fall through (NULL container_id -> no matching row -> indexable).
|
||||
-- Re-evaluation must refresh observed_at even when content_hash is unchanged,
|
||||
-- because the winning eligible tab for a canonical URL may have changed.
|
||||
CREATE TRIGGER tab_to_history_on_insert AFTER INSERT ON tab
|
||||
WHEN NEW.url IS NOT NULL
|
||||
AND url_indexable(CAST(NEW.url AS TEXT)) = 1
|
||||
AND (SELECT value FROM local_index_setting WHERE "key" = 'enabled') = 1
|
||||
AND (NEW.tab_mode != 1
|
||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM container
|
||||
WHERE container.id = NEW.container_id
|
||||
AND json_extract(container.metadata, '$.excludeFromIndex') = 1
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO history (
|
||||
url_canonical, url_host, url_path, title, is_probably_readerable,
|
||||
@@ -352,6 +363,11 @@ WHEN NEW.url IS NOT NULL
|
||||
AND (SELECT value FROM local_index_setting WHERE "key" = 'enabled') = 1
|
||||
AND (NEW.tab_mode != 1
|
||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM container
|
||||
WHERE container.id = NEW.container_id
|
||||
AND json_extract(container.metadata, '$.excludeFromIndex') = 1
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO history (
|
||||
url_canonical, url_host, url_path, title, is_probably_readerable,
|
||||
@@ -385,6 +401,168 @@ BEGIN
|
||||
WHERE history.content_hash IS NOT excluded.content_hash;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER tab_to_history_on_container_update AFTER UPDATE OF container_id
|
||||
ON tab
|
||||
WHEN NEW.url IS NOT NULL
|
||||
AND url_indexable(CAST(NEW.url AS TEXT)) = 1
|
||||
AND (SELECT value FROM local_index_setting WHERE "key" = 'enabled') = 1
|
||||
BEGIN
|
||||
DELETE FROM history
|
||||
WHERE url_canonical = url_canonical(CAST(NEW.url AS TEXT))
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM tab AS candidate
|
||||
WHERE candidate.url IS NOT NULL
|
||||
AND url_indexable(CAST(candidate.url AS TEXT)) = 1
|
||||
AND url_canonical(CAST(candidate.url AS TEXT)) = history.url_canonical
|
||||
AND (candidate.tab_mode != 1
|
||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM container
|
||||
WHERE container.id = candidate.container_id
|
||||
AND json_extract(container.metadata, '$.excludeFromIndex') = 1
|
||||
)
|
||||
);
|
||||
|
||||
INSERT INTO history (
|
||||
url_canonical, url_host, url_path, title, is_probably_readerable,
|
||||
extracted_content_markdown, extracted_content_plain,
|
||||
full_content_markdown, full_content_plain,
|
||||
content_hash, observed_at, observed_count
|
||||
)
|
||||
SELECT
|
||||
url_canonical(CAST(candidate.url AS TEXT)),
|
||||
url_host(CAST(candidate.url AS TEXT)),
|
||||
url_path(CAST(candidate.url AS TEXT)),
|
||||
candidate.title,
|
||||
candidate.is_probably_readerable,
|
||||
candidate.extracted_content_markdown,
|
||||
candidate.extracted_content_plain,
|
||||
candidate.full_content_markdown,
|
||||
candidate.full_content_plain,
|
||||
candidate.content_hash,
|
||||
strftime('%s','now') * 1000,
|
||||
1
|
||||
FROM tab AS candidate
|
||||
WHERE candidate.url IS NOT NULL
|
||||
AND url_indexable(CAST(candidate.url AS TEXT)) = 1
|
||||
AND url_canonical(CAST(candidate.url AS TEXT)) = url_canonical(CAST(NEW.url AS TEXT))
|
||||
AND (candidate.tab_mode != 1
|
||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM container
|
||||
WHERE container.id = candidate.container_id
|
||||
AND json_extract(container.metadata, '$.excludeFromIndex') = 1
|
||||
)
|
||||
ORDER BY candidate.timestamp DESC, candidate.rowid DESC
|
||||
LIMIT 1
|
||||
ON CONFLICT(url_canonical) DO UPDATE SET
|
||||
title = COALESCE(excluded.title, history.title),
|
||||
is_probably_readerable = excluded.is_probably_readerable,
|
||||
extracted_content_markdown = excluded.extracted_content_markdown,
|
||||
extracted_content_plain = excluded.extracted_content_plain,
|
||||
full_content_markdown = excluded.full_content_markdown,
|
||||
full_content_plain = excluded.full_content_plain,
|
||||
content_hash = excluded.content_hash,
|
||||
observed_at = excluded.observed_at,
|
||||
observed_count = history.observed_count + 1;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER container_to_history_on_metadata_update AFTER UPDATE OF metadata
|
||||
ON container
|
||||
WHEN COALESCE(json_extract(OLD.metadata, '$.excludeFromIndex') = 1, 0)
|
||||
!= COALESCE(json_extract(NEW.metadata, '$.excludeFromIndex') = 1, 0)
|
||||
AND (SELECT value FROM local_index_setting WHERE "key" = 'enabled') = 1
|
||||
BEGIN
|
||||
DELETE FROM history
|
||||
WHERE url_canonical IN (
|
||||
SELECT DISTINCT url_canonical(CAST(affected.url AS TEXT))
|
||||
FROM tab AS affected
|
||||
WHERE affected.container_id = NEW.id
|
||||
AND affected.url IS NOT NULL
|
||||
AND url_indexable(CAST(affected.url AS TEXT)) = 1
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM tab AS candidate
|
||||
WHERE candidate.url IS NOT NULL
|
||||
AND url_indexable(CAST(candidate.url AS TEXT)) = 1
|
||||
AND url_canonical(CAST(candidate.url AS TEXT)) = history.url_canonical
|
||||
AND (candidate.tab_mode != 1
|
||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM container
|
||||
WHERE container.id = candidate.container_id
|
||||
AND json_extract(container.metadata, '$.excludeFromIndex') = 1
|
||||
)
|
||||
);
|
||||
|
||||
INSERT INTO history (
|
||||
url_canonical, url_host, url_path, title, is_probably_readerable,
|
||||
extracted_content_markdown, extracted_content_plain,
|
||||
full_content_markdown, full_content_plain,
|
||||
content_hash, observed_at, observed_count
|
||||
)
|
||||
SELECT
|
||||
url_canonical(CAST(candidate.url AS TEXT)),
|
||||
url_host(CAST(candidate.url AS TEXT)),
|
||||
url_path(CAST(candidate.url AS TEXT)),
|
||||
candidate.title,
|
||||
candidate.is_probably_readerable,
|
||||
candidate.extracted_content_markdown,
|
||||
candidate.extracted_content_plain,
|
||||
candidate.full_content_markdown,
|
||||
candidate.full_content_plain,
|
||||
candidate.content_hash,
|
||||
strftime('%s','now') * 1000,
|
||||
1
|
||||
FROM tab AS candidate
|
||||
WHERE candidate.url IS NOT NULL
|
||||
AND url_indexable(CAST(candidate.url AS TEXT)) = 1
|
||||
AND url_canonical(CAST(candidate.url AS TEXT)) IN (
|
||||
SELECT DISTINCT url_canonical(CAST(affected.url AS TEXT))
|
||||
FROM tab AS affected
|
||||
WHERE affected.container_id = NEW.id
|
||||
AND affected.url IS NOT NULL
|
||||
AND url_indexable(CAST(affected.url AS TEXT)) = 1
|
||||
)
|
||||
AND (candidate.tab_mode != 1
|
||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM container
|
||||
WHERE container.id = candidate.container_id
|
||||
AND json_extract(container.metadata, '$.excludeFromIndex') = 1
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM tab AS newer
|
||||
WHERE newer.url IS NOT NULL
|
||||
AND url_indexable(CAST(newer.url AS TEXT)) = 1
|
||||
AND url_canonical(CAST(newer.url AS TEXT)) = url_canonical(CAST(candidate.url AS TEXT))
|
||||
AND (newer.tab_mode != 1
|
||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM container
|
||||
WHERE container.id = newer.container_id
|
||||
AND json_extract(container.metadata, '$.excludeFromIndex') = 1
|
||||
)
|
||||
AND (
|
||||
newer.timestamp > candidate.timestamp
|
||||
OR (newer.timestamp = candidate.timestamp AND newer.rowid > candidate.rowid)
|
||||
)
|
||||
)
|
||||
ON CONFLICT(url_canonical) DO UPDATE SET
|
||||
title = COALESCE(excluded.title, history.title),
|
||||
is_probably_readerable = excluded.is_probably_readerable,
|
||||
extracted_content_markdown = excluded.extracted_content_markdown,
|
||||
extracted_content_plain = excluded.extracted_content_plain,
|
||||
full_content_markdown = excluded.full_content_markdown,
|
||||
full_content_plain = excluded.full_content_plain,
|
||||
content_hash = excluded.content_hash,
|
||||
observed_at = excluded.observed_at,
|
||||
observed_count = history.observed_count + 1;
|
||||
END;
|
||||
|
||||
containersWithCount WITH ContainerDataWithCount:
|
||||
SELECT
|
||||
container.*,
|
||||
|
||||
+10
-2
@@ -5436,13 +5436,21 @@ i0.Trigger get historyAfterUpdate => i0.Trigger(
|
||||
'history_after_update',
|
||||
);
|
||||
i0.Trigger get tabToHistoryOnInsert => i0.Trigger(
|
||||
'CREATE TRIGGER tab_to_history_on_insert AFTER INSERT ON tab WHEN NEW.url IS NOT NULL AND url_indexable(CAST(NEW.url AS TEXT)) = 1 AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 AND(NEW.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)BEGIN INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) VALUES (url_canonical(CAST(NEW.url AS TEXT)), url_host(CAST(NEW.url AS TEXT)), url_path(CAST(NEW.url AS TEXT)), NEW.title, NEW.is_probably_readerable, NEW.extracted_content_markdown, NEW.extracted_content_plain, NEW.full_content_markdown, NEW.full_content_plain, NEW.content_hash, strftime(\'%s\', \'now\') * 1000, 1) ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1 WHERE history.content_hash IS NOT excluded.content_hash;END',
|
||||
'CREATE TRIGGER tab_to_history_on_insert AFTER INSERT ON tab WHEN NEW.url IS NOT NULL AND url_indexable(CAST(NEW.url AS TEXT)) = 1 AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 AND(NEW.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = NEW.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) BEGIN INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) VALUES (url_canonical(CAST(NEW.url AS TEXT)), url_host(CAST(NEW.url AS TEXT)), url_path(CAST(NEW.url AS TEXT)), NEW.title, NEW.is_probably_readerable, NEW.extracted_content_markdown, NEW.extracted_content_plain, NEW.full_content_markdown, NEW.full_content_plain, NEW.content_hash, strftime(\'%s\', \'now\') * 1000, 1) ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1 WHERE history.content_hash IS NOT excluded.content_hash;END',
|
||||
'tab_to_history_on_insert',
|
||||
);
|
||||
i0.Trigger get tabToHistoryOnUpdate => i0.Trigger(
|
||||
'CREATE TRIGGER tab_to_history_on_update AFTER UPDATE OF title, url, extracted_content_plain, extracted_content_markdown, full_content_plain, full_content_markdown, is_probably_readerable ON tab WHEN NEW.url IS NOT NULL AND url_indexable(CAST(NEW.url AS TEXT)) = 1 AND(OLD.content_hash IS NOT NEW.content_hash OR OLD.url IS NOT NEW.url)AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 AND(NEW.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)BEGIN INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) VALUES (url_canonical(CAST(NEW.url AS TEXT)), url_host(CAST(NEW.url AS TEXT)), url_path(CAST(NEW.url AS TEXT)), NEW.title, NEW.is_probably_readerable, NEW.extracted_content_markdown, NEW.extracted_content_plain, NEW.full_content_markdown, NEW.full_content_plain, NEW.content_hash, strftime(\'%s\', \'now\') * 1000, 1) ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1 WHERE history.content_hash IS NOT excluded.content_hash;END',
|
||||
'CREATE TRIGGER tab_to_history_on_update AFTER UPDATE OF title, url, extracted_content_plain, extracted_content_markdown, full_content_plain, full_content_markdown, is_probably_readerable ON tab WHEN NEW.url IS NOT NULL AND url_indexable(CAST(NEW.url AS TEXT)) = 1 AND(OLD.content_hash IS NOT NEW.content_hash OR OLD.url IS NOT NEW.url)AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 AND(NEW.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = NEW.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) BEGIN INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) VALUES (url_canonical(CAST(NEW.url AS TEXT)), url_host(CAST(NEW.url AS TEXT)), url_path(CAST(NEW.url AS TEXT)), NEW.title, NEW.is_probably_readerable, NEW.extracted_content_markdown, NEW.extracted_content_plain, NEW.full_content_markdown, NEW.full_content_plain, NEW.content_hash, strftime(\'%s\', \'now\') * 1000, 1) ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1 WHERE history.content_hash IS NOT excluded.content_hash;END',
|
||||
'tab_to_history_on_update',
|
||||
);
|
||||
i0.Trigger get tabToHistoryOnContainerUpdate => i0.Trigger(
|
||||
'CREATE TRIGGER tab_to_history_on_container_update AFTER UPDATE OF container_id ON tab WHEN NEW.url IS NOT NULL AND url_indexable(CAST(NEW.url AS TEXT)) = 1 AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 BEGIN DELETE FROM history WHERE url_canonical = url_canonical(CAST(NEW.url AS TEXT)) AND NOT EXISTS (SELECT 1 FROM tab AS candidate WHERE candidate.url IS NOT NULL AND url_indexable(CAST(candidate.url AS TEXT)) = 1 AND url_canonical(CAST(candidate.url AS TEXT)) = history.url_canonical AND(candidate.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = candidate.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1));INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) SELECT url_canonical(CAST(candidate.url AS TEXT)), url_host(CAST(candidate.url AS TEXT)), url_path(CAST(candidate.url AS TEXT)), candidate.title, candidate.is_probably_readerable, candidate.extracted_content_markdown, candidate.extracted_content_plain, candidate.full_content_markdown, candidate.full_content_plain, candidate.content_hash, strftime(\'%s\', \'now\') * 1000, 1 FROM tab AS candidate WHERE candidate.url IS NOT NULL AND url_indexable(CAST(candidate.url AS TEXT)) = 1 AND url_canonical(CAST(candidate.url AS TEXT)) = url_canonical(CAST(NEW.url AS TEXT)) AND(candidate.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = candidate.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) ORDER BY candidate.timestamp DESC, candidate."rowid" DESC LIMIT 1 ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1;END',
|
||||
'tab_to_history_on_container_update',
|
||||
);
|
||||
i0.Trigger get containerToHistoryOnMetadataUpdate => i0.Trigger(
|
||||
'CREATE TRIGGER container_to_history_on_metadata_update AFTER UPDATE OF metadata ON container WHEN COALESCE(json_extract(OLD.metadata, \'\$.excludeFromIndex\') = 1, 0) != COALESCE(json_extract(NEW.metadata, \'\$.excludeFromIndex\') = 1, 0) AND (SELECT value FROM local_index_setting WHERE "key" = \'enabled\') = 1 BEGIN DELETE FROM history WHERE url_canonical IN (SELECT DISTINCT url_canonical(CAST(affected.url AS TEXT)) FROM tab AS affected WHERE affected.container_id = NEW.id AND affected.url IS NOT NULL AND url_indexable(CAST(affected.url AS TEXT)) = 1) AND NOT EXISTS (SELECT 1 FROM tab AS candidate WHERE candidate.url IS NOT NULL AND url_indexable(CAST(candidate.url AS TEXT)) = 1 AND url_canonical(CAST(candidate.url AS TEXT)) = history.url_canonical AND(candidate.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = candidate.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1));INSERT INTO history (url_canonical, url_host, url_path, title, is_probably_readerable, extracted_content_markdown, extracted_content_plain, full_content_markdown, full_content_plain, content_hash, observed_at, observed_count) SELECT url_canonical(CAST(candidate.url AS TEXT)), url_host(CAST(candidate.url AS TEXT)), url_path(CAST(candidate.url AS TEXT)), candidate.title, candidate.is_probably_readerable, candidate.extracted_content_markdown, candidate.extracted_content_plain, candidate.full_content_markdown, candidate.full_content_plain, candidate.content_hash, strftime(\'%s\', \'now\') * 1000, 1 FROM tab AS candidate WHERE candidate.url IS NOT NULL AND url_indexable(CAST(candidate.url AS TEXT)) = 1 AND url_canonical(CAST(candidate.url AS TEXT)) IN (SELECT DISTINCT url_canonical(CAST(affected.url AS TEXT)) FROM tab AS affected WHERE affected.container_id = NEW.id AND affected.url IS NOT NULL AND url_indexable(CAST(affected.url AS TEXT)) = 1) AND(candidate.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = candidate.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) AND NOT EXISTS (SELECT 1 FROM tab AS newer WHERE newer.url IS NOT NULL AND url_indexable(CAST(newer.url AS TEXT)) = 1 AND url_canonical(CAST(newer.url AS TEXT)) = url_canonical(CAST(candidate.url AS TEXT)) AND(newer.tab_mode != 1 OR (SELECT value FROM local_index_setting WHERE "key" = \'index_private\') = 1)AND NOT EXISTS (SELECT 1 FROM container WHERE container.id = newer.container_id AND json_extract(container.metadata, \'\$.excludeFromIndex\') = 1) AND(newer.timestamp > candidate.timestamp OR(newer.timestamp = candidate.timestamp AND newer."rowid" > candidate."rowid"))) ON CONFLICT (url_canonical) DO UPDATE SET title = COALESCE(excluded.title, history.title), is_probably_readerable = excluded.is_probably_readerable, extracted_content_markdown = excluded.extracted_content_markdown, extracted_content_plain = excluded.extracted_content_plain, full_content_markdown = excluded.full_content_markdown, full_content_plain = excluded.full_content_plain, content_hash = excluded.content_hash, observed_at = excluded.observed_at, observed_count = history.observed_count + 1;END',
|
||||
'container_to_history_on_metadata_update',
|
||||
);
|
||||
|
||||
class DefinitionsDrift extends i9.ModularAccessor {
|
||||
DefinitionsDrift(i0.GeneratedDatabase db) : super(db);
|
||||
|
||||
@@ -44,6 +44,12 @@ class ContainerMetadata with FastEquatable {
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool clearDataOnExit;
|
||||
|
||||
// Read by the `tab_to_history_on_*` SQL triggers via
|
||||
// `json_extract(metadata, '$.excludeFromIndex')`. Keep this key name in
|
||||
// sync with the trigger gate in definitions.drift.
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool excludeFromIndex;
|
||||
|
||||
final List<Uri>? assignedSites;
|
||||
|
||||
ContainerMetadata({
|
||||
@@ -51,6 +57,7 @@ class ContainerMetadata with FastEquatable {
|
||||
required this.contextualIdentity,
|
||||
required this.proxyConnectionId,
|
||||
required this.clearDataOnExit,
|
||||
required this.excludeFromIndex,
|
||||
required this.assignedSites,
|
||||
});
|
||||
|
||||
@@ -59,12 +66,14 @@ class ContainerMetadata with FastEquatable {
|
||||
String? contextualIdentity,
|
||||
ProxyConnectionId? proxyConnectionId,
|
||||
bool? clearDataOnExit,
|
||||
bool? excludeFromIndex,
|
||||
List<Uri>? assignedSites,
|
||||
}) : this(
|
||||
iconData: iconData,
|
||||
contextualIdentity: contextualIdentity,
|
||||
proxyConnectionId: proxyConnectionId,
|
||||
clearDataOnExit: clearDataOnExit ?? false,
|
||||
excludeFromIndex: excludeFromIndex ?? false,
|
||||
assignedSites: assignedSites,
|
||||
);
|
||||
|
||||
@@ -81,6 +90,7 @@ class ContainerMetadata with FastEquatable {
|
||||
contextualIdentity,
|
||||
proxyConnectionId,
|
||||
clearDataOnExit,
|
||||
excludeFromIndex,
|
||||
assignedSites,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ abstract class _$ContainerMetadataCWProxy {
|
||||
|
||||
ContainerMetadata clearDataOnExit(bool clearDataOnExit);
|
||||
|
||||
ContainerMetadata excludeFromIndex(bool excludeFromIndex);
|
||||
|
||||
ContainerMetadata assignedSites(List<Uri>? assignedSites);
|
||||
|
||||
/// Creates a new instance with the provided field values.
|
||||
@@ -29,6 +31,7 @@ abstract class _$ContainerMetadataCWProxy {
|
||||
String? contextualIdentity,
|
||||
ProxyConnectionId? proxyConnectionId,
|
||||
bool clearDataOnExit,
|
||||
bool excludeFromIndex,
|
||||
List<Uri>? assignedSites,
|
||||
});
|
||||
}
|
||||
@@ -55,6 +58,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
||||
ContainerMetadata clearDataOnExit(bool clearDataOnExit) =>
|
||||
call(clearDataOnExit: clearDataOnExit);
|
||||
|
||||
@override
|
||||
ContainerMetadata excludeFromIndex(bool excludeFromIndex) =>
|
||||
call(excludeFromIndex: excludeFromIndex);
|
||||
|
||||
@override
|
||||
ContainerMetadata assignedSites(List<Uri>? assignedSites) =>
|
||||
call(assignedSites: assignedSites);
|
||||
@@ -72,6 +79,7 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
||||
Object? contextualIdentity = const $CopyWithPlaceholder(),
|
||||
Object? proxyConnectionId = const $CopyWithPlaceholder(),
|
||||
Object? clearDataOnExit = const $CopyWithPlaceholder(),
|
||||
Object? excludeFromIndex = const $CopyWithPlaceholder(),
|
||||
Object? assignedSites = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return ContainerMetadata(
|
||||
@@ -93,6 +101,12 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
||||
? _value.clearDataOnExit
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: clearDataOnExit as bool,
|
||||
excludeFromIndex:
|
||||
excludeFromIndex == const $CopyWithPlaceholder() ||
|
||||
excludeFromIndex == null
|
||||
? _value.excludeFromIndex
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: excludeFromIndex as bool,
|
||||
assignedSites: assignedSites == const $CopyWithPlaceholder()
|
||||
? _value.assignedSites
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
@@ -232,6 +246,7 @@ ContainerMetadata _$ContainerMetadataFromJson(Map<String, dynamic> json) =>
|
||||
json['proxyConnectionId'] as String?,
|
||||
),
|
||||
clearDataOnExit: json['clearDataOnExit'] as bool? ?? false,
|
||||
excludeFromIndex: json['excludeFromIndex'] as bool? ?? false,
|
||||
assignedSites: (json['assignedSites'] as List<dynamic>?)
|
||||
?.map((e) => Uri.parse(e as String))
|
||||
.toList(),
|
||||
@@ -247,6 +262,7 @@ Map<String, dynamic> _$ContainerMetadataToJson(
|
||||
'contextualIdentity': instance.contextualIdentity,
|
||||
'proxyConnectionId': _proxyConnectionIdToJson(instance.proxyConnectionId),
|
||||
'clearDataOnExit': instance.clearDataOnExit,
|
||||
'excludeFromIndex': instance.excludeFromIndex,
|
||||
'assignedSites': instance.assignedSites?.map((e) => e.toString()).toList(),
|
||||
};
|
||||
|
||||
|
||||
@@ -503,7 +503,7 @@ final class WatchTabDbDataProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$watchTabDbDataHash() => r'c505be1cdeaffced2615fdb075ed8a9a2132754a';
|
||||
String _$watchTabDbDataHash() => r'90d9f479d9d6c5475185b0490089ea67edff7894';
|
||||
|
||||
final class WatchTabDbDataFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<TabData?>, String> {
|
||||
|
||||
@@ -41,7 +41,7 @@ final class TabDataRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabDataRepositoryHash() => r'2208b806a97f239a5b3e6ed7e6c395cbd0276bdd';
|
||||
String _$tabDataRepositoryHash() => r'38750c2e0d27b407b1038d324ad22eeb202da6eb';
|
||||
|
||||
abstract class _$TabDataRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
+16
@@ -96,6 +96,9 @@ class ContainerEditScreen extends HookConsumerWidget {
|
||||
initialContainer.metadata.proxyConnectionId,
|
||||
);
|
||||
final clearDataOnExit = useState(initialContainer.metadata.clearDataOnExit);
|
||||
final excludeFromIndex = useState(
|
||||
initialContainer.metadata.excludeFromIndex,
|
||||
);
|
||||
final assignedSites = useState(initialContainer.metadata.assignedSites);
|
||||
final isPinned = useState(initialContainer.isPinned);
|
||||
|
||||
@@ -118,6 +121,7 @@ class ContainerEditScreen extends HookConsumerWidget {
|
||||
: null,
|
||||
clearDataOnExit:
|
||||
clearDataOnExit.value && contextualIdentity.value != null,
|
||||
excludeFromIndex: excludeFromIndex.value,
|
||||
assignedSites: assignedSites.value,
|
||||
),
|
||||
);
|
||||
@@ -490,6 +494,18 @@ class ContainerEditScreen extends HookConsumerWidget {
|
||||
}
|
||||
: null,
|
||||
),
|
||||
const Divider(height: 1, indent: 56),
|
||||
SwitchListTile.adaptive(
|
||||
value: excludeFromIndex.value,
|
||||
title: const Text('Exclude from Search Index'),
|
||||
subtitle: const Text(
|
||||
'Skip pages in this container from the local search index',
|
||||
),
|
||||
secondary: const Icon(MdiIcons.magnifyRemoveOutline),
|
||||
onChanged: (value) {
|
||||
excludeFromIndex.value = value;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'schema_v10.dart' as v10;
|
||||
import 'schema_v11.dart' as v11;
|
||||
import 'schema_v12.dart' as v12;
|
||||
import 'schema_v13.dart' as v13;
|
||||
import 'schema_v14.dart' as v14;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
@@ -45,10 +46,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
return v12.DatabaseAtV12(db);
|
||||
case 13:
|
||||
return v13.DatabaseAtV13(db);
|
||||
case 14:
|
||||
return v14.DatabaseAtV14(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
||||
static const versions = const [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,257 @@
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
|
||||
import 'package:weblibre/data/database/functions/url_functions.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
||||
|
||||
void main() {
|
||||
late TabDatabase db;
|
||||
|
||||
setUp(() async {
|
||||
db = TabDatabase(
|
||||
NativeDatabase.memory(
|
||||
setup: (database) {
|
||||
registerLexorankFunctions(database);
|
||||
registerUrlFunctions(database);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await db.customStatement(
|
||||
'INSERT INTO local_index_setting ("key", value) '
|
||||
"VALUES ('enabled', 1), ('index_private', 0)",
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test(
|
||||
'container exclude flag re-evaluates existing tab history rows',
|
||||
() async {
|
||||
await _insertContainer(db, 'container', excludeFromIndex: false);
|
||||
await _insertTab(
|
||||
db,
|
||||
id: 'tab',
|
||||
containerId: 'container',
|
||||
url: 'https://example.com/page',
|
||||
title: 'Visible title',
|
||||
);
|
||||
|
||||
expect(await _historyTitles(db), ['Visible title']);
|
||||
|
||||
await _updateContainerExclude(db, 'container', excludeFromIndex: true);
|
||||
|
||||
expect(await _historyTitles(db), isEmpty);
|
||||
|
||||
await _updateContainerExclude(db, 'container', excludeFromIndex: false);
|
||||
|
||||
expect(await _historyTitles(db), ['Visible title']);
|
||||
},
|
||||
);
|
||||
|
||||
test('tab container assignment re-evaluates existing history rows', () async {
|
||||
await _insertContainer(db, 'allowed', excludeFromIndex: false);
|
||||
await _insertContainer(db, 'excluded', excludeFromIndex: true);
|
||||
await _insertTab(
|
||||
db,
|
||||
id: 'tab',
|
||||
containerId: 'excluded',
|
||||
url: 'https://example.com/moved',
|
||||
title: 'Moved title',
|
||||
);
|
||||
|
||||
expect(await _historyTitles(db), isEmpty);
|
||||
|
||||
await db.customStatement(
|
||||
"UPDATE tab SET container_id = 'allowed' WHERE id = 'tab'",
|
||||
);
|
||||
|
||||
expect(await _historyTitles(db), ['Moved title']);
|
||||
|
||||
await db.customStatement(
|
||||
"UPDATE tab SET container_id = 'excluded' WHERE id = 'tab'",
|
||||
);
|
||||
|
||||
expect(await _historyTitles(db), isEmpty);
|
||||
});
|
||||
|
||||
test(
|
||||
'excluding one container preserves duplicate URLs from allowed tabs',
|
||||
() async {
|
||||
await _insertContainer(db, 'allowed', excludeFromIndex: false);
|
||||
await _insertContainer(db, 'excluded', excludeFromIndex: false);
|
||||
await _insertTab(
|
||||
db,
|
||||
id: 'allowed-tab',
|
||||
containerId: 'allowed',
|
||||
url: 'https://example.com/shared',
|
||||
title: 'Allowed title',
|
||||
timestamp: 1,
|
||||
);
|
||||
await _insertTab(
|
||||
db,
|
||||
id: 'excluded-tab',
|
||||
containerId: 'excluded',
|
||||
url: 'https://example.com/shared',
|
||||
title: 'Excluded title',
|
||||
timestamp: 2,
|
||||
);
|
||||
|
||||
expect(await _historyTitles(db), ['Excluded title']);
|
||||
|
||||
await _updateContainerExclude(db, 'excluded', excludeFromIndex: true);
|
||||
|
||||
expect(await _historyTitles(db), ['Allowed title']);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'container exclude re-evaluation refreshes recency for same-hash duplicate URLs',
|
||||
() async {
|
||||
await _insertContainer(db, 'allowed', excludeFromIndex: false);
|
||||
await _insertContainer(db, 'later-excluded', excludeFromIndex: false);
|
||||
await _insertTab(
|
||||
db,
|
||||
id: 'later-excluded-tab',
|
||||
containerId: 'later-excluded',
|
||||
url: 'https://example.com/same-hash',
|
||||
title: 'Shared title',
|
||||
timestamp: 1,
|
||||
);
|
||||
await _insertTab(
|
||||
db,
|
||||
id: 'allowed-tab',
|
||||
containerId: 'allowed',
|
||||
url: 'https://example.com/same-hash',
|
||||
title: 'Shared title',
|
||||
timestamp: 2,
|
||||
);
|
||||
|
||||
await _setAllHistoryObservedAt(db, 1);
|
||||
expect(await _historyObservedAt(db), 1);
|
||||
|
||||
await _updateContainerExclude(
|
||||
db,
|
||||
'later-excluded',
|
||||
excludeFromIndex: true,
|
||||
);
|
||||
|
||||
expect(await _historyTitles(db), ['Shared title']);
|
||||
expect(await _historyObservedAt(db), greaterThan(1));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'container move re-evaluation refreshes recency for same-hash duplicate URLs',
|
||||
() async {
|
||||
await _insertContainer(db, 'allowed-a', excludeFromIndex: false);
|
||||
await _insertContainer(db, 'allowed-b', excludeFromIndex: false);
|
||||
await _insertContainer(db, 'excluded', excludeFromIndex: true);
|
||||
await _insertTab(
|
||||
db,
|
||||
id: 'moving-tab',
|
||||
containerId: 'allowed-a',
|
||||
url: 'https://example.com/move-same-hash',
|
||||
title: 'Shared move title',
|
||||
timestamp: 1,
|
||||
);
|
||||
await _insertTab(
|
||||
db,
|
||||
id: 'fallback-tab',
|
||||
containerId: 'allowed-b',
|
||||
url: 'https://example.com/move-same-hash',
|
||||
title: 'Shared move title',
|
||||
timestamp: 2,
|
||||
);
|
||||
|
||||
await _setAllHistoryObservedAt(db, 1);
|
||||
expect(await _historyObservedAt(db), 1);
|
||||
|
||||
await db.customStatement(
|
||||
"UPDATE tab SET container_id = 'excluded' WHERE id = 'moving-tab'",
|
||||
);
|
||||
|
||||
expect(await _historyTitles(db), ['Shared move title']);
|
||||
expect(await _historyObservedAt(db), greaterThan(1));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _insertContainer(
|
||||
TabDatabase db,
|
||||
String id, {
|
||||
required bool excludeFromIndex,
|
||||
}) {
|
||||
return db.customStatement(
|
||||
'INSERT INTO container (id, color, order_key, is_pinned, metadata) '
|
||||
"VALUES (?, 0, ?, 0, ?)",
|
||||
[id, id, '{"excludeFromIndex":$excludeFromIndex}'],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateContainerExclude(
|
||||
TabDatabase db,
|
||||
String id, {
|
||||
required bool excludeFromIndex,
|
||||
}) {
|
||||
return db.customStatement('UPDATE container SET metadata = ? WHERE id = ?', [
|
||||
'{"excludeFromIndex":$excludeFromIndex}',
|
||||
id,
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> _insertTab(
|
||||
TabDatabase db, {
|
||||
required String id,
|
||||
required String containerId,
|
||||
required String url,
|
||||
required String title,
|
||||
int timestamp = 1,
|
||||
}) {
|
||||
return db.customStatement(
|
||||
'INSERT INTO tab (id, source, container_id, order_key, url, title, '
|
||||
'extracted_content_plain, full_content_plain, timestamp) '
|
||||
'VALUES (?, 2, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[
|
||||
id,
|
||||
containerId,
|
||||
id,
|
||||
url,
|
||||
title,
|
||||
'$title extracted content',
|
||||
'$title full content',
|
||||
timestamp,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<String?>> _historyTitles(TabDatabase db) async {
|
||||
final rows = await db
|
||||
.customSelect(
|
||||
'SELECT title FROM history ORDER BY url_canonical',
|
||||
readsFrom: {db.history},
|
||||
)
|
||||
.get();
|
||||
|
||||
return [for (final row in rows) row.read<String?>('title')];
|
||||
}
|
||||
|
||||
Future<void> _setAllHistoryObservedAt(TabDatabase db, int observedAt) {
|
||||
return db.customStatement('UPDATE history SET observed_at = ?', [observedAt]);
|
||||
}
|
||||
|
||||
Future<int?> _historyObservedAt(TabDatabase db) async {
|
||||
final row = await db
|
||||
.customSelect(
|
||||
'SELECT CAST(observed_at AS INTEGER) AS observed_at '
|
||||
'FROM history '
|
||||
'ORDER BY url_canonical '
|
||||
'LIMIT 1',
|
||||
readsFrom: {db.history},
|
||||
)
|
||||
.getSingleOrNull();
|
||||
|
||||
return row?.read<int>('observed_at');
|
||||
}
|
||||
@@ -2,8 +2,9 @@
|
||||
// ignore_for_file: unused_local_variable, unused_import
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_dev/api/migrations_native.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:weblibre/data/database/functions/url_functions.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'generated/schema.dart';
|
||||
|
||||
import 'generated/schema_v2.dart' as v2;
|
||||
@@ -14,7 +15,7 @@ void main() {
|
||||
late SchemaVerifier verifier;
|
||||
|
||||
setUpAll(() {
|
||||
verifier = SchemaVerifier(GeneratedHelper());
|
||||
verifier = SchemaVerifier(GeneratedHelper(), setup: registerUrlFunctions);
|
||||
});
|
||||
|
||||
group('simple database migrations', () {
|
||||
|
||||
Reference in New Issue
Block a user