Skip to content

refactor(store): drive every FTS5 index from triggers, not hand-written sync - #347

Merged
getappz merged 2 commits into
masterfrom
task/341
Jul 27, 2026
Merged

getappz merged 2 commits into
masterfrom
task/341

Conversation

@getappz

@getappz getappz commented Jul 26, 2026 •

Copy link
Copy Markdown
Owner

Closes #341.

What

Converts all four fts5 indexes from standalone tables kept in sync by hand to
external-content tables driven by triggers, following the
observations_fts pattern in src/memory/schema.rs.

index crate manual write sites removed
store_docs_fts agentflare-store 3
store_docs_fts flare-docs 3
skills_fts skill-registry 2
tools_fts gateway-registry 2

Why the store_docs_fts triggers are guarded rather than straight mirrors

Soft-deleted documents stay out of the index — that is what the manual code
did, and it is what stops #339's future purge from being the only thing
keeping the index from growing without bound.

That constraint is load-bearing, not cosmetic. On an external-content table a
'delete' command naming a row that is not in the index corrupts it. A
resurrecting upsert (doc_upsert on a soft-deleted path) is an UPDATE whose
old row was already unindexed, so an unguarded AFTER UPDATE would issue
exactly that corrupting delete on every resurrection. Hence
WHERE old.deleted_at IS NULL / WHERE new.deleted_at IS NULL on each arm.
resurrecting_a_soft_deleted_document_indexes_it_exactly_once asserts this
via fts5's own 'integrity-check', which is what actually fails on the
unguarded version.

AFTER UPDATE is scoped to the indexed columns (OF content, deleted_at;
OF name, description, body, tags, neg_text). doc_upsert_with_opts follows
its content write with up to nine single-column UPDATEs, and skill-registry
writes last_used_at + bandit counters on every skill load — a bare
AFTER UPDATE would re-tokenize whole documents for each.

Upgrade path

  • agentflare-store has rusqlite_migration, so it is a new M::up.
  • The two registries do not. open_db detects a legacy standalone index
    (sqlite_master.sql NOT LIKE '%content=%'), drops it, and refills from the
    base table in the same open — waiting for the next rebuild would leave
    skill_search / gateway tool search empty in between.

Repair path

Adds Store::doc_fts_rebuild. store_documents has a TEXT primary key, so
its rowids are implicit and VACUUM may renumber them — which would desync
this index and doc_search's rowid JOIN (that coupling predates this PR).
Anything that VACUUMs, including the eviction work in #339, must call it
afterwards. It repopulates explicitly rather than using fts5 'rebuild',
which would index the soft-deleted rows the triggers deliberately keep out.

A trap worth flagging for reviewers

SELECT count(*) on an external-content fts5 table is answered from the
content table, so it reports every base-table row whether or not it is
indexed. Three pre-existing assertions named "fts stays in sync" were
measuring nothing once converted; they now go through MATCH, which is
index-driven, and assert the negative case too (a replaced/deleted row must
stop matching). This is also why the first draft of the real-database test
"failed" at 10858 vs 10857.

Verification

  • cargo test --workspace — green. One unrelated flake,
    flare-git-core worktree::run_output_timeout_kills_the_child_not_just_abandons_it,
    which passes in isolation on this branch and on clean master and passed on
    a prior full run; it is a process-kill timing test under parallel load.
  • cargo clippy --workspace --all-targets --all-features — no new findings
    (the 21 pre-existing local-toolchain findings from feat: add supervisor auto-dispatch loop (item #408) #376 are unchanged).
  • Migrated a real 9.2 MB / 10,858-document ~/.agentflare/flare-docs.db
    (crates/flare-docs/tests/real_db_migration.rs, skipped when no such file
    exists): applies cleanly, passes fts5 'integrity-check', every sampled
    live document stays matchable and the soft-deleted one does not.

Not included

The issue also suggests surfacing PRAGMA integrity_check and an FTS rebuild
escape hatch through agentflare doctor. doctor today is entirely about
agent config wiring (hosts, rules) with no database surface, so that is a
separate change; this PR ships the repair primitive it would call.

Summary by CodeRabbit

  • Improvements
    • Full-text search indexes for documents, tools, and skills now stay automatically synchronized via trigger-driven external-content indexing.
    • Soft-deleted documents/tools/skills remain excluded from search, even after edits or restores.
    • Added a document search index rebuild operation to recover searchability after intentional inconsistencies.
    • Existing databases are automatically upgraded to the trigger-driven FTS format during open.
    • Bulk write and rebuild paths were updated to avoid manual index maintenance.
  • Tests
    • Added/updated integration and real-database migration tests to verify search matchability and legacy upgrades.

…en sync

The four fts5 indexes in this repo were standalone tables kept in sync by
hand from Rust, across 10 write sites in 4 files. Every base-table write
path was a chance to desync -- not hypothetically: #334/#337 was exactly
that bug, a soft-delete that forgot its store_docs_fts row and left stale
documents matchable. Any new purge path (#339) would have inherited the
same rule.

Each index is now external-content with AFTER INSERT/DELETE/UPDATE
triggers, following src/memory/schema.rs. The FTS write is a property of
the base table, so a call site can no longer forget it, and the index no
longer stores its own copy of the indexed text.

The store_docs_fts triggers are guarded on deleted_at rather than being
straight mirrors: soft-deleted documents stay out of the index (what the
manual code did), and on an external-content table a 'delete' naming an
unindexed row corrupts it -- which an unguarded AFTER UPDATE would do on
every resurrecting upsert. AFTER UPDATE is scoped to the indexed columns
so the optional-field UPDATEs in doc_upsert_with_opts and the per-load
bandit writes in skill-registry don't re-tokenize whole documents.

The two registries have no migration framework, so open_db drops a
legacy standalone index and refills from the base table in the same
open -- waiting for the next rebuild would leave search empty in between.

Adds Store::doc_fts_rebuild as the repair path: store_documents has a
TEXT primary key, so VACUUM (which #339 proposes) may renumber the
implicit rowids this index and doc_search's JOIN are both keyed on.

Also fixes three assertions this change would otherwise have defanged:
`SELECT count(*)` on an external-content fts5 table is answered from the
content table, so it counts base-table rows whether or not they are
indexed. Index state has to be asserted through MATCH.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 337588f6-d63c-405d-9cb8-8785c50421db

📥 Commits

Reviewing files that changed from the base of the PR and between 9805b52 and e7b972b.

📒 Files selected for processing (4)
  • crates/agentflare-store/src/documents.rs
  • crates/flare-docs/tests/real_db_migration.rs
  • crates/gateway-registry/src/db.rs
  • crates/skill-registry/src/db.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/agentflare-store/src/documents.rs
  • crates/gateway-registry/src/db.rs
  • crates/skill-registry/src/db.rs

📝 Walkthrough

Walkthrough

The change converts document, gateway, and skill FTS5 indexes to external-content tables synchronized by SQLite triggers. Manual FTS writes are removed, legacy indexes are migrated on open, and rebuild and integration tests validate live-row searchability and soft-delete behavior.

Changes

External-content FTS migration

Layer / File(s) Summary
FTS schemas and migrations
crates/agentflare-store/src/migrations.rs
Adds external-content store_docs_fts triggers, rebuild SQL, migration sequencing, and migration tests covering live rows, soft deletes, schema shape, and trigger updates.
Document write paths
crates/agentflare-store/src/documents.rs
Adds Store::doc_fts_rebuild(), removes manual FTS synchronization from upserts and deletes, and updates tests for soft-delete, resurrection, edits, and rebuild behavior.
Docs cache batch writes
crates/flare-docs/src/store.rs, crates/flare-docs/tests/real_db_migration.rs
Removes manual FTS writes from reconciliation and batch upserts, and adds real-database migration coverage for live and soft-deleted documents.
Gateway FTS conversion
crates/gateway-registry/src/db.rs
Converts legacy tools_fts databases on open, adds trigger-driven synchronization, changes rebuild logic to write only tools, and updates matchability tests.
Skill FTS conversion
crates/skill-registry/src/db.rs
Converts legacy skills_fts databases on open, adds trigger-driven synchronization, changes rebuild logic to write only skills, and updates deletion and rebuild tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Store
  participant store_documents
  participant store_docs_fts
  Store->>store_documents: Insert or update indexed document
  store_documents->>store_docs_fts: Trigger updates external-content index
  Store->>store_documents: Soft-delete document
  store_documents->>store_docs_fts: Trigger removes searchable entry
  Store->>store_docs_fts: Execute rebuild SQL when requested
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it misses the required Summary, Test plan, and Notes for reviewers sections from the template. Add the required headings: ## Summary, ## Test plan with the three checkboxes, and ## Notes for reviewers covering risks and backwards compatibility.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: moving FTS5 indexes to trigger-driven synchronization instead of manual writes.
Linked Issues check ✅ Passed The changes match #341: all four FTS indexes become external-content tables with triggers, legacy indexes are migrated, and rebuild support is added.
Out of Scope Changes check ✅ Passed All visible changes support the FTS trigger migration, tests, legacy conversion, or repair path; no unrelated code appears introduced.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/341

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/agentflare-store/src/migrations.rs (1)

119-123: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider wrapping the rebuild in a transaction.

execute_batch autocommits each statement, so a failure after 'delete-all' leaves the index empty until someone reruns the rebuild. BEGIN; ... COMMIT; makes it all-or-nothing. (Note doc_fts_rebuild is also called on a connection that may already be in a transaction — if so, keep as-is.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agentflare-store/src/migrations.rs` around lines 119 - 123, Update
FTS_REBUILD_SQL to wrap the delete-all and repopulation statements in
BEGIN/COMMIT so rebuilding is atomic. Ensure doc_fts_rebuild preserves the
existing behavior when called on a connection already inside a transaction,
avoiding nested transaction errors.
crates/agentflare-store/src/documents.rs (1)

813-837: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assertion doesn't actually test what the comment claims.

fts_hits == 1 holds whether or not the nine optional-column UPDATEs fire the trigger — an unscoped AFTER UPDATE would still leave exactly one entry. To pin the UPDATE OF content, deleted_at scoping, assert on the trigger definition (e.g. that sqlite_master.sql for store_docs_fts_au contains UPDATE OF content, deleted_at), or do a second doc_upsert_with_opts that changes only optional fields and confirm the index is untouched.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agentflare-store/src/documents.rs` around lines 813 - 837, The test
optional_field_updates_do_not_disturb_the_index does not verify trigger scoping
because a single FTS hit passes even with an unscoped trigger. Strengthen it by
inspecting sqlite_master for the store_docs_fts_au trigger and asserting its SQL
contains UPDATE OF content, deleted_at, or perform a second optional-only
doc_upsert_with_opts and verify the index remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/gateway-registry/src/db.rs`:
- Around line 38-62: Update apply_schema so the legacy tools_fts drop, SCHEMA
recreation, and backfill INSERT execute within one transaction, committing only
after all steps succeed and rolling back on failure. Preserve the existing
legacy detection and avoid skipping recovery when the migration cannot complete.

---

Nitpick comments:
In `@crates/agentflare-store/src/documents.rs`:
- Around line 813-837: The test optional_field_updates_do_not_disturb_the_index
does not verify trigger scoping because a single FTS hit passes even with an
unscoped trigger. Strengthen it by inspecting sqlite_master for the
store_docs_fts_au trigger and asserting its SQL contains UPDATE OF content,
deleted_at, or perform a second optional-only doc_upsert_with_opts and verify
the index remains unchanged.

In `@crates/agentflare-store/src/migrations.rs`:
- Around line 119-123: Update FTS_REBUILD_SQL to wrap the delete-all and
repopulation statements in BEGIN/COMMIT so rebuilding is atomic. Ensure
doc_fts_rebuild preserves the existing behavior when called on a connection
already inside a transaction, avoiding nested transaction errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8387028f-cab9-4f77-b188-4ad47d8175a5

📥 Commits

Reviewing files that changed from the base of the PR and between 5dfb4a5 and 9805b52.

📒 Files selected for processing (6)
  • crates/agentflare-store/src/documents.rs
  • crates/agentflare-store/src/migrations.rs
  • crates/flare-docs/src/store.rs
  • crates/flare-docs/tests/real_db_migration.rs
  • crates/gateway-registry/src/db.rs
  • crates/skill-registry/src/db.rs

Comment thread crates/gateway-registry/src/db.rs
CodeRabbit's finding on #347: the drop/recreate/backfill in both registries
was three separate statements, and the halfway state is not
self-correcting. With the index dropped but not yet refilled, the next open
finds no such table, reads `legacy` as 0, and recreates an empty index over
a populated base table -- search silently missing everything until
something forces a full rebuild. One transaction each; SQLite makes DDL
transactional. Same treatment for doc_fts_rebuild's clear-then-refill.

Also splits optional_field_updates_do_not_disturb_the_index: its assertion
held whether or not the trigger was scoped, since an unscoped AFTER UPDATE
produces an identical index, just nine times over. Scoping is a property of
the DDL, so it is now asserted on the trigger definition.

And runs cargo fmt, which the first commit missed.
@getappz
getappz merged commit fc11f59 into master Jul 27, 2026
22 of 24 checks passed
@getappz
getappz deleted the task/341 branch July 27, 2026 03:08
getappz added a commit that referenced this pull request Aug 21, 2026
#573)

Same architectural gap as skill-registry (#519/PR #572), which
gateway-registry's own doc comments say it mirrors: hand-rolled
apply_schema() with no user_version tracking. Audited the git history
(#104 -> #158 -> #347) -- the tools table's columns have never changed
since creation, so there's no live "no such column" bug today, but the
next column addition would hit the identical class of bug.

Migrates to agentflare-db-kit's open_file/open_memory with a real
migration list: 0001_initial replays the original (#104) narrow schema,
0002_fts_triggers unconditionally drops and recreates tools_fts as the
external-content shape with sync triggers plus a backfill. No ALTER
TABLE/migration hook needed here (unlike #519) since tools's columns
are stable -- DROP ... IF EXISTS before a fresh CREATE is correct
regardless of which pre-migration shape existed.

Added GatewayError::DbInit for db_kit::open::Error and its error_kind
match arm. All 60 gateway-registry unit tests pass, including the
existing legacy-standalone-FTS conversion test; clippy (with the CI
gate flags) and fmt are clean; the full agentflare binary compiles.

Agentflare-Agent: claude-code_2-1-237_agent
Agentflare-Branch: task/520-fix-gateway-registry-adopt-agentflare-db
Agentflare-Item: 520
getappz added a commit that referenced this pull request Aug 21, 2026
…g-column schema drift

apply_schema() hand-rolled CREATE TABLE IF NOT EXISTS DDL, which is a
no-op against an existing table -- any skills.db created before #302
added body/neg_text/last_used_at/bandit_alpha/bandit_beta stayed
permanently stuck without them, while the FTS5 triggers added later
(#347) reference old.body/new.body etc. and throw "no such column:
old.body" the first time a DELETE or qualifying UPDATE fires (e.g.
rebuild()'s DELETE FROM skills). Reproduced live via skill_detect
against a real skills.db from before #302.

Migrate to agentflare-db-kit's open_file/open_memory with a real
migration list, matching the pattern already used by
agentflare-backend/agentflare-store/agentflare-artifacts/flare-docs/
flare-workflow/agentflare-jobs: 0001_initial replays the original (#92)
narrow schema, 0002_ranking_and_fts adds the ranking columns via a
migration hook (ALTER TABLE ADD COLUMN isn't idempotent, so it's
guarded by a PRAGMA table_info check first) plus the external-content
FTS5 table and sync triggers, drop-and-recreated unconditionally so it's
correct regardless of which pre-migration shape the database was in.

Verified against a copy of the actual broken skills.db from this
machine in addition to the new unit test that reproduces the bug
synthetically.

Agentflare-Agent: claude-code_2-1-237_agent
Agentflare-Branch: task/519-fix-skill-registry-adopt-agentflare-db-k
Agentflare-Item: 519
getappz added a commit that referenced this pull request Aug 21, 2026
…g-column schema drift (#572)

* fix(skill-registry): adopt agentflare-db-kit migrations to fix missing-column schema drift

apply_schema() hand-rolled CREATE TABLE IF NOT EXISTS DDL, which is a
no-op against an existing table -- any skills.db created before #302
added body/neg_text/last_used_at/bandit_alpha/bandit_beta stayed
permanently stuck without them, while the FTS5 triggers added later
(#347) reference old.body/new.body etc. and throw "no such column:
old.body" the first time a DELETE or qualifying UPDATE fires (e.g.
rebuild()'s DELETE FROM skills). Reproduced live via skill_detect
against a real skills.db from before #302.

Migrate to agentflare-db-kit's open_file/open_memory with a real
migration list, matching the pattern already used by
agentflare-backend/agentflare-store/agentflare-artifacts/flare-docs/
flare-workflow/agentflare-jobs: 0001_initial replays the original (#92)
narrow schema, 0002_ranking_and_fts adds the ranking columns via a
migration hook (ALTER TABLE ADD COLUMN isn't idempotent, so it's
guarded by a PRAGMA table_info check first) plus the external-content
FTS5 table and sync triggers, drop-and-recreated unconditionally so it's
correct regardless of which pre-migration shape the database was in.

Verified against a copy of the actual broken skills.db from this
machine in addition to the new unit test that reproduces the bug
synthetically.

Agentflare-Agent: claude-code_2-1-237_agent
Agentflare-Branch: task/519-fix-skill-registry-adopt-agentflare-db-k
Agentflare-Item: 519

* fix(skill-registry): open_or_repair must not delete the db on migration/SchemaAhead errors

CodeRabbit review on PR #572: the Err(_) arm deleted db_path for every
open_db failure, including db_kit::open::Error::Migration (a real bug
in a migration -- deleting the file would silently destroy
skill_impressions/ranking state the filesystem can't reconstruct,
hiding the bug instead of surfacing it) and Error::SchemaAhead (means
a newer build already migrated this file; its own error message says
not to touch it by hand, let alone delete it).

Now only deletes-and-retries on Error::Sqlite -- a genuine open/read
failure (corruption, not a valid SQLite file). Migration/SchemaAhead
propagate as real errors. Added regression tests for both paths, plus
documented FTS_AND_TRIGGERS as frozen migration-0002 history per the
review's nitpick.

Agentflare-Agent: claude-code_2-1-237_agent
Agentflare-Branch: task/519-fix-skill-registry-adopt-agentflare-db-k
Agentflare-Item: 519

* fix(skill-registry): don't treat a failed integrity_check query as corruption

integrity_check() previously folded a real corruption verdict from SQLite
and a failure to even run the check (locked db, permission denied, I/O
error) into the same Some(String), and open_or_repair() deleted the file
on either. A transient failure to run the check is not evidence of
corruption -- propagate it instead of destroying the database.

Agentflare-Agent: claude-code_2-1-237_agent
Agentflare-Branch: task/519-fix-skill-registry-adopt-agentflare-db-k
Agentflare-Item: 519

* chore: regenerate Cargo.lock

Agentflare-Agent: claude-code_2-1-237_agent
Agentflare-Branch: task/519-fix-skill-registry-adopt-agentflare-db-k
Agentflare-Item: 519
getappz added a commit that referenced this pull request Oct 6, 2026
Proposal file isn't in the tree — reconstructing the spec from the item, PR #884, and code evidence.
Context gathered. Writing the spec now.
Wrote `docs/superpowers/plans/2026-10-06-cross-instance-pr-identity-and-claims.md`.

Summary: design spec for item #347 (cross-instance PR identity and claims). Documents the observed bug (hand-opened PRs #868/#870/#872 adopted by the race winner `flared:c997d745ae66`; sequence-only `item_owns_branch` in `src/worktree/mod.rs`; write-only `beacon:` labels), then specifies P1–P6: origin stamp at PR creation, local-evidence branch ownership, tighter discovery marker matching, ownership-gated supervisor actions, readable beacon, claim-protocol hardening. 4-phase rollout with phase 1 mapped to already-merged PR #884; includes the P4 supervisor call-site audit checklist (self-repair, review-repair, branch update, mark-ready, merge/promotion, stray-heal, label writes), per-phase test plan, and risks. Phases 2–4 explicitly marked not implemented. No application code touched, no PR opened.

Agentflare-Branch: task/347-cross-instance-pr-identity-and-claims-st
Agentflare-Item: 347
getappz added a commit that referenced this pull request Oct 6, 2026
… evidence of the branch (#347 phase 1) (#884)

* fix(discovery): attach a PR to an item by branch name only with local evidence of the branch (#347 phase 1)

Agentflare-Agent: claude-code
Agentflare-Branch: task/347-cross-instance-pr-identity-and-claims-st
Agentflare-Item: 347
Agentflare-Session: 0b6a7478-1b0c-4629-9b30-7cba0dd0ea41

* Writing your design doc — first mapping the proposal to the codebase.
Proposal file isn't in the tree — reconstructing the spec from the item, PR #884, and code evidence.
Context gathered. Writing the spec now.
Wrote `docs/superpowers/plans/2026-10-06-cross-instance-pr-identity-and-claims.md`.

Summary: design spec for item #347 (cross-instance PR identity and claims). Documents the observed bug (hand-opened PRs #868/#870/#872 adopted by the race winner `flared:c997d745ae66`; sequence-only `item_owns_branch` in `src/worktree/mod.rs`; write-only `beacon:` labels), then specifies P1–P6: origin stamp at PR creation, local-evidence branch ownership, tighter discovery marker matching, ownership-gated supervisor actions, readable beacon, claim-protocol hardening. 4-phase rollout with phase 1 mapped to already-merged PR #884; includes the P4 supervisor call-site audit checklist (self-repair, review-repair, branch update, mark-ready, merge/promotion, stray-heal, label writes), per-phase test plan, and risks. Phases 2–4 explicitly marked not implemented. No application code touched, no PR opened.

Agentflare-Branch: task/347-cross-instance-pr-identity-and-claims-st
Agentflare-Item: 347
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FTS5: external-content tables + triggers

1 participant