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> {
|
abstract class _$TabRepository extends $Notifier<void> {
|
||||||
void build();
|
void build();
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import 'package:weblibre/features/search/domain/fts_tokenizer.dart';
|
|||||||
)
|
)
|
||||||
class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
||||||
@override
|
@override
|
||||||
final int schemaVersion = 13;
|
final int schemaVersion = 14;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int ftsTokenLimit = 10;
|
final int ftsTokenLimit = 10;
|
||||||
@@ -63,7 +63,9 @@ class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
|||||||
|
|
||||||
await customStatement('PRAGMA foreign_keys = ON');
|
await customStatement('PRAGMA foreign_keys = ON');
|
||||||
await definitionsDrift.optimizeFtsIndex();
|
await definitionsDrift.optimizeFtsIndex();
|
||||||
|
if (details.versionNow >= 12) {
|
||||||
await definitionsDrift.optimizeHistoryFtsIndex();
|
await definitionsDrift.optimizeHistoryFtsIndex();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onUpgrade: (m, from, to) async {
|
onUpgrade: (m, from, to) async {
|
||||||
// Following the advice from https://drift.simonbinder.eu/Migrations/api/#general-tips
|
// 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();
|
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.historyAfterUpdate,
|
||||||
i1.tabToHistoryOnInsert,
|
i1.tabToHistoryOnInsert,
|
||||||
i1.tabToHistoryOnUpdate,
|
i1.tabToHistoryOnUpdate,
|
||||||
|
i1.tabToHistoryOnContainerUpdate,
|
||||||
|
i1.containerToHistoryOnMetadataUpdate,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||||
@@ -149,6 +151,26 @@ abstract class $TabDatabase extends i0.GeneratedDatabase {
|
|||||||
),
|
),
|
||||||
result: [i0.TableUpdate('history', kind: i0.UpdateKind.insert)],
|
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>;
|
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({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
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, Schema11 schema) from10To11,
|
||||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
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, Schema13 schema) from12To13,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -1893,6 +2110,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from12To13(migrator, schema);
|
await from12To13(migrator, schema);
|
||||||
return 13;
|
return 13;
|
||||||
|
case 13:
|
||||||
|
final schema = Schema14(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from13To14(migrator, schema);
|
||||||
|
return 14;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
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, Schema11 schema) from10To11,
|
||||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
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, Schema13 schema) from12To13,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from2To3: from2To3,
|
from2To3: from2To3,
|
||||||
@@ -1924,5 +2147,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
from10To11: from10To11,
|
from10To11: from10To11,
|
||||||
from11To12: from11To12,
|
from11To12: from11To12,
|
||||||
from12To13: from12To13,
|
from12To13: from12To13,
|
||||||
|
from13To14: from13To14,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+183
-5
@@ -297,17 +297,28 @@ historyUrlsPage:
|
|||||||
deleteHistoryByCanonicalUrls:
|
deleteHistoryByCanonicalUrls:
|
||||||
DELETE FROM history WHERE url_canonical IN :canonicalUrls;
|
DELETE FROM history WHERE url_canonical IN :canonicalUrls;
|
||||||
|
|
||||||
-- Tab → history fan-out. Fires when content-bearing columns change AND the
|
-- Tab -> history fan-out. Content triggers fire when content-bearing columns
|
||||||
-- content actually changed (content_hash gate). Settings are read from
|
-- change AND the content actually changed (content_hash gate). Re-evaluation
|
||||||
-- local_index_setting at SQL level so the trigger is the single source of
|
-- triggers fire when indexing eligibility changes without a content update
|
||||||
-- truth for indexing eligibility. tab_mode = 1 is the private tab marker;
|
-- (container assignment or container exclude flag changes). Settings are read
|
||||||
-- private tabs are skipped unless `index_private` is enabled.
|
-- 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
|
CREATE TRIGGER tab_to_history_on_insert AFTER INSERT ON tab
|
||||||
WHEN NEW.url IS NOT NULL
|
WHEN NEW.url IS NOT NULL
|
||||||
AND url_indexable(CAST(NEW.url AS TEXT)) = 1
|
AND url_indexable(CAST(NEW.url AS TEXT)) = 1
|
||||||
AND (SELECT value FROM local_index_setting WHERE "key" = 'enabled') = 1
|
AND (SELECT value FROM local_index_setting WHERE "key" = 'enabled') = 1
|
||||||
AND (NEW.tab_mode != 1
|
AND (NEW.tab_mode != 1
|
||||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 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
|
BEGIN
|
||||||
INSERT INTO history (
|
INSERT INTO history (
|
||||||
url_canonical, url_host, url_path, title, is_probably_readerable,
|
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 (SELECT value FROM local_index_setting WHERE "key" = 'enabled') = 1
|
||||||
AND (NEW.tab_mode != 1
|
AND (NEW.tab_mode != 1
|
||||||
OR (SELECT value FROM local_index_setting WHERE "key" = 'index_private') = 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
|
BEGIN
|
||||||
INSERT INTO history (
|
INSERT INTO history (
|
||||||
url_canonical, url_host, url_path, title, is_probably_readerable,
|
url_canonical, url_host, url_path, title, is_probably_readerable,
|
||||||
@@ -385,6 +401,168 @@ BEGIN
|
|||||||
WHERE history.content_hash IS NOT excluded.content_hash;
|
WHERE history.content_hash IS NOT excluded.content_hash;
|
||||||
END;
|
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:
|
containersWithCount WITH ContainerDataWithCount:
|
||||||
SELECT
|
SELECT
|
||||||
container.*,
|
container.*,
|
||||||
|
|||||||
+10
-2
@@ -5436,13 +5436,21 @@ i0.Trigger get historyAfterUpdate => i0.Trigger(
|
|||||||
'history_after_update',
|
'history_after_update',
|
||||||
);
|
);
|
||||||
i0.Trigger get tabToHistoryOnInsert => i0.Trigger(
|
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',
|
'tab_to_history_on_insert',
|
||||||
);
|
);
|
||||||
i0.Trigger get tabToHistoryOnUpdate => i0.Trigger(
|
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',
|
'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 {
|
class DefinitionsDrift extends i9.ModularAccessor {
|
||||||
DefinitionsDrift(i0.GeneratedDatabase db) : super(db);
|
DefinitionsDrift(i0.GeneratedDatabase db) : super(db);
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ class ContainerMetadata with FastEquatable {
|
|||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
final bool clearDataOnExit;
|
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;
|
final List<Uri>? assignedSites;
|
||||||
|
|
||||||
ContainerMetadata({
|
ContainerMetadata({
|
||||||
@@ -51,6 +57,7 @@ class ContainerMetadata with FastEquatable {
|
|||||||
required this.contextualIdentity,
|
required this.contextualIdentity,
|
||||||
required this.proxyConnectionId,
|
required this.proxyConnectionId,
|
||||||
required this.clearDataOnExit,
|
required this.clearDataOnExit,
|
||||||
|
required this.excludeFromIndex,
|
||||||
required this.assignedSites,
|
required this.assignedSites,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,12 +66,14 @@ class ContainerMetadata with FastEquatable {
|
|||||||
String? contextualIdentity,
|
String? contextualIdentity,
|
||||||
ProxyConnectionId? proxyConnectionId,
|
ProxyConnectionId? proxyConnectionId,
|
||||||
bool? clearDataOnExit,
|
bool? clearDataOnExit,
|
||||||
|
bool? excludeFromIndex,
|
||||||
List<Uri>? assignedSites,
|
List<Uri>? assignedSites,
|
||||||
}) : this(
|
}) : this(
|
||||||
iconData: iconData,
|
iconData: iconData,
|
||||||
contextualIdentity: contextualIdentity,
|
contextualIdentity: contextualIdentity,
|
||||||
proxyConnectionId: proxyConnectionId,
|
proxyConnectionId: proxyConnectionId,
|
||||||
clearDataOnExit: clearDataOnExit ?? false,
|
clearDataOnExit: clearDataOnExit ?? false,
|
||||||
|
excludeFromIndex: excludeFromIndex ?? false,
|
||||||
assignedSites: assignedSites,
|
assignedSites: assignedSites,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -81,6 +90,7 @@ class ContainerMetadata with FastEquatable {
|
|||||||
contextualIdentity,
|
contextualIdentity,
|
||||||
proxyConnectionId,
|
proxyConnectionId,
|
||||||
clearDataOnExit,
|
clearDataOnExit,
|
||||||
|
excludeFromIndex,
|
||||||
assignedSites,
|
assignedSites,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ abstract class _$ContainerMetadataCWProxy {
|
|||||||
|
|
||||||
ContainerMetadata clearDataOnExit(bool clearDataOnExit);
|
ContainerMetadata clearDataOnExit(bool clearDataOnExit);
|
||||||
|
|
||||||
|
ContainerMetadata excludeFromIndex(bool excludeFromIndex);
|
||||||
|
|
||||||
ContainerMetadata assignedSites(List<Uri>? assignedSites);
|
ContainerMetadata assignedSites(List<Uri>? assignedSites);
|
||||||
|
|
||||||
/// Creates a new instance with the provided field values.
|
/// Creates a new instance with the provided field values.
|
||||||
@@ -29,6 +31,7 @@ abstract class _$ContainerMetadataCWProxy {
|
|||||||
String? contextualIdentity,
|
String? contextualIdentity,
|
||||||
ProxyConnectionId? proxyConnectionId,
|
ProxyConnectionId? proxyConnectionId,
|
||||||
bool clearDataOnExit,
|
bool clearDataOnExit,
|
||||||
|
bool excludeFromIndex,
|
||||||
List<Uri>? assignedSites,
|
List<Uri>? assignedSites,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -55,6 +58,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
|||||||
ContainerMetadata clearDataOnExit(bool clearDataOnExit) =>
|
ContainerMetadata clearDataOnExit(bool clearDataOnExit) =>
|
||||||
call(clearDataOnExit: clearDataOnExit);
|
call(clearDataOnExit: clearDataOnExit);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ContainerMetadata excludeFromIndex(bool excludeFromIndex) =>
|
||||||
|
call(excludeFromIndex: excludeFromIndex);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ContainerMetadata assignedSites(List<Uri>? assignedSites) =>
|
ContainerMetadata assignedSites(List<Uri>? assignedSites) =>
|
||||||
call(assignedSites: assignedSites);
|
call(assignedSites: assignedSites);
|
||||||
@@ -72,6 +79,7 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
|||||||
Object? contextualIdentity = const $CopyWithPlaceholder(),
|
Object? contextualIdentity = const $CopyWithPlaceholder(),
|
||||||
Object? proxyConnectionId = const $CopyWithPlaceholder(),
|
Object? proxyConnectionId = const $CopyWithPlaceholder(),
|
||||||
Object? clearDataOnExit = const $CopyWithPlaceholder(),
|
Object? clearDataOnExit = const $CopyWithPlaceholder(),
|
||||||
|
Object? excludeFromIndex = const $CopyWithPlaceholder(),
|
||||||
Object? assignedSites = const $CopyWithPlaceholder(),
|
Object? assignedSites = const $CopyWithPlaceholder(),
|
||||||
}) {
|
}) {
|
||||||
return ContainerMetadata(
|
return ContainerMetadata(
|
||||||
@@ -93,6 +101,12 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
|||||||
? _value.clearDataOnExit
|
? _value.clearDataOnExit
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: clearDataOnExit as bool,
|
: clearDataOnExit as bool,
|
||||||
|
excludeFromIndex:
|
||||||
|
excludeFromIndex == const $CopyWithPlaceholder() ||
|
||||||
|
excludeFromIndex == null
|
||||||
|
? _value.excludeFromIndex
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: excludeFromIndex as bool,
|
||||||
assignedSites: assignedSites == const $CopyWithPlaceholder()
|
assignedSites: assignedSites == const $CopyWithPlaceholder()
|
||||||
? _value.assignedSites
|
? _value.assignedSites
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
@@ -232,6 +246,7 @@ ContainerMetadata _$ContainerMetadataFromJson(Map<String, dynamic> json) =>
|
|||||||
json['proxyConnectionId'] as String?,
|
json['proxyConnectionId'] as String?,
|
||||||
),
|
),
|
||||||
clearDataOnExit: json['clearDataOnExit'] as bool? ?? false,
|
clearDataOnExit: json['clearDataOnExit'] as bool? ?? false,
|
||||||
|
excludeFromIndex: json['excludeFromIndex'] as bool? ?? false,
|
||||||
assignedSites: (json['assignedSites'] as List<dynamic>?)
|
assignedSites: (json['assignedSites'] as List<dynamic>?)
|
||||||
?.map((e) => Uri.parse(e as String))
|
?.map((e) => Uri.parse(e as String))
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -247,6 +262,7 @@ Map<String, dynamic> _$ContainerMetadataToJson(
|
|||||||
'contextualIdentity': instance.contextualIdentity,
|
'contextualIdentity': instance.contextualIdentity,
|
||||||
'proxyConnectionId': _proxyConnectionIdToJson(instance.proxyConnectionId),
|
'proxyConnectionId': _proxyConnectionIdToJson(instance.proxyConnectionId),
|
||||||
'clearDataOnExit': instance.clearDataOnExit,
|
'clearDataOnExit': instance.clearDataOnExit,
|
||||||
|
'excludeFromIndex': instance.excludeFromIndex,
|
||||||
'assignedSites': instance.assignedSites?.map((e) => e.toString()).toList(),
|
'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
|
final class WatchTabDbDataFamily extends $Family
|
||||||
with $FunctionalFamilyOverride<Stream<TabData?>, String> {
|
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> {
|
abstract class _$TabDataRepository extends $Notifier<void> {
|
||||||
void build();
|
void build();
|
||||||
|
|||||||
+16
@@ -96,6 +96,9 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
initialContainer.metadata.proxyConnectionId,
|
initialContainer.metadata.proxyConnectionId,
|
||||||
);
|
);
|
||||||
final clearDataOnExit = useState(initialContainer.metadata.clearDataOnExit);
|
final clearDataOnExit = useState(initialContainer.metadata.clearDataOnExit);
|
||||||
|
final excludeFromIndex = useState(
|
||||||
|
initialContainer.metadata.excludeFromIndex,
|
||||||
|
);
|
||||||
final assignedSites = useState(initialContainer.metadata.assignedSites);
|
final assignedSites = useState(initialContainer.metadata.assignedSites);
|
||||||
final isPinned = useState(initialContainer.isPinned);
|
final isPinned = useState(initialContainer.isPinned);
|
||||||
|
|
||||||
@@ -118,6 +121,7 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
clearDataOnExit:
|
clearDataOnExit:
|
||||||
clearDataOnExit.value && contextualIdentity.value != null,
|
clearDataOnExit.value && contextualIdentity.value != null,
|
||||||
|
excludeFromIndex: excludeFromIndex.value,
|
||||||
assignedSites: assignedSites.value,
|
assignedSites: assignedSites.value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -490,6 +494,18 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
: null,
|
: 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_v11.dart' as v11;
|
||||||
import 'schema_v12.dart' as v12;
|
import 'schema_v12.dart' as v12;
|
||||||
import 'schema_v13.dart' as v13;
|
import 'schema_v13.dart' as v13;
|
||||||
|
import 'schema_v14.dart' as v14;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@@ -45,10 +46,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v12.DatabaseAtV12(db);
|
return v12.DatabaseAtV12(db);
|
||||||
case 13:
|
case 13:
|
||||||
return v13.DatabaseAtV13(db);
|
return v13.DatabaseAtV13(db);
|
||||||
|
case 14:
|
||||||
|
return v14.DatabaseAtV14(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
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
|
// ignore_for_file: unused_local_variable, unused_import
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_dev/api/migrations_native.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: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.dart';
|
||||||
|
|
||||||
import 'generated/schema_v2.dart' as v2;
|
import 'generated/schema_v2.dart' as v2;
|
||||||
@@ -14,7 +15,7 @@ void main() {
|
|||||||
late SchemaVerifier verifier;
|
late SchemaVerifier verifier;
|
||||||
|
|
||||||
setUpAll(() {
|
setUpAll(() {
|
||||||
verifier = SchemaVerifier(GeneratedHelper());
|
verifier = SchemaVerifier(GeneratedHelper(), setup: registerUrlFunctions);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('simple database migrations', () {
|
group('simple database migrations', () {
|
||||||
|
|||||||
Reference in New Issue
Block a user