@@ -97,6 +97,10 @@ func (s *SQLiteBackend) PurgeNamespace(ctx context.Context, db *gorm.DB) error {
9797 return db .WithContext (ctx ).Exec ("DELETE FROM " + sqliteFTSTable + " WHERE namespace = ?" , ctxns .FromContext (ctx )).Error
9898}
9999
100+ // rebuildTable clears all FTS rows for the current namespace in tableName and
101+ // repopulates them from search_documents in batches. Used by both full Rebuild
102+ // and the legacy-upgrade path.
103+ // @intent resynchronize one namespace-scoped SQLite FTS table from persisted search documents without disturbing other namespaces.
100104func (s * SQLiteBackend ) rebuildTable (ctx context.Context , db * gorm.DB , tableName string ) error {
101105 ns := ctxns .FromContext (ctx )
102106 clearSQL := fmt .Sprintf ("DELETE FROM %s WHERE namespace = ?" , tableName )
@@ -125,6 +129,10 @@ func (s *SQLiteBackend) rebuildTable(ctx context.Context, db *gorm.DB, tableName
125129 })
126130}
127131
132+ // rebuildTableNodes deletes and re-inserts FTS rows only for the given nodeIDs
133+ // within the current namespace, processing them in chunks of scopedRebuildChunkSize
134+ // to avoid oversized IN clauses.
135+ // @intent refresh only the requested node documents in SQLite FTS so incremental updates can avoid a full namespace rebuild.
128136func (s * SQLiteBackend ) rebuildTableNodes (ctx context.Context , db * gorm.DB , tableName string , nodeIDs []uint ) error {
129137 ns := ctxns .FromContext (ctx )
130138 return db .WithContext (ctx ).Transaction (func (outerTx * gorm.DB ) error {
@@ -154,6 +162,10 @@ func (s *SQLiteBackend) rebuildTableNodes(ctx context.Context, db *gorm.DB, tabl
154162 })
155163}
156164
165+ // insertSQLiteFTSBatch executes one bulk INSERT for a batch of search documents into an FTS table.
166+ // @intent push many rows in a single statement so rebuild paths avoid per-row round trips.
167+ // @sideEffect inserts rows into the supplied FTS virtual table.
168+ // @mutates search_fts virtual table contents
157169func insertSQLiteFTSBatch (ctx context.Context , tx * gorm.DB , tableName string , docs []model.SearchDocument ) error {
158170 if len (docs ) == 0 {
159171 return nil
@@ -162,6 +174,10 @@ func insertSQLiteFTSBatch(ctx context.Context, tx *gorm.DB, tableName string, do
162174 return tx .WithContext (ctx ).Exec (insertSQL , args ... ).Error
163175}
164176
177+ // buildSQLiteFTSInsert constructs a bulk INSERT statement for the FTS virtual
178+ // table, returning the SQL string and its positional arguments. Each document
179+ // maps to a (node_id, content, language, namespace) value row.
180+ // @intent batch SQLite FTS inserts into one statement so rebuild paths can stream many documents with minimal per-row overhead.
165181func buildSQLiteFTSInsert (tableName string , docs []model.SearchDocument ) (string , []any ) {
166182 if len (docs ) == 0 {
167183 return "" , nil
@@ -180,6 +196,12 @@ func buildSQLiteFTSInsert(tableName string, docs []model.SearchDocument) (string
180196 return insertSQL , args
181197}
182198
199+ // ftsRow scans node_id values from search_fts MATCH queries.
200+ // @intent decode the single-column FTS result before joining back to nodes.
201+ type ftsRow struct {
202+ NodeID uint
203+ }
204+
183205// Query는 FTS5 MATCH 질의로 관련 노드를 검색한다.
184206// @intent 사용자 검색어를 SQLite FTS prefix 질의로 변환해 노드를 찾는다.
185207// @requires limit는 0보다 커야 의미 있는 결과를 얻는다.
@@ -194,10 +216,6 @@ func (s *SQLiteBackend) Query(ctx context.Context, db *gorm.DB, query string, li
194216 }
195217 ns := ctxns .FromContext (ctx )
196218
197- type ftsRow struct {
198- NodeID uint
199- }
200-
201219 var rows []ftsRow
202220 querySQL := `SELECT CAST(node_id AS INTEGER) AS node_id
203221 FROM search_fts
@@ -245,6 +263,8 @@ func (s *SQLiteBackend) Query(ctx context.Context, db *gorm.DB, query string, li
245263 return result , nil
246264}
247265
266+ // sqliteColumnExists reports whether a column is present on a given SQLite table via PRAGMA table_info.
267+ // @intent gate schema migrations on actual table layout instead of guessing from version markers.
248268func sqliteColumnExists (db * gorm.DB , tableName , columnName string ) (bool , error ) {
249269 rows , err := db .Raw ("PRAGMA table_info(" + tableName + ")" ).Rows ()
250270 if err != nil {
@@ -266,6 +286,10 @@ func sqliteColumnExists(db *gorm.DB, tableName, columnName string) (bool, error)
266286 return false , rows .Err ()
267287}
268288
289+ // createSQLiteFTSTable issues a CREATE VIRTUAL TABLE … USING fts5 DDL for the
290+ // given tableName. When ifNotExists is true the statement is idempotent; when
291+ // false it is used for the upgrade shadow table where a fresh schema is required.
292+ // @intent create the namespace-aware SQLite FTS table shape used by both first-run migration and legacy upgrade flows.
269293func createSQLiteFTSTable (db * gorm.DB , tableName string , ifNotExists bool ) error {
270294 clause := ""
271295 if ifNotExists {
@@ -278,6 +302,11 @@ func createSQLiteFTSTable(db *gorm.DB, tableName string, ifNotExists bool) error
278302 return db .Exec (stmt ).Error
279303}
280304
305+ // upgradeLegacyFTSTable migrates a pre-namespace search_fts schema to the
306+ // current four-column layout (node_id, content, language, namespace). It builds
307+ // a shadow table, populates it via rebuildTable, then swaps it into place using
308+ // RENAME, keeping the old table as a backup until the swap succeeds.
309+ // @intent upgrade legacy SQLite FTS storage to the namespace-aware schema without losing the indexed search snapshot.
281310func (s * SQLiteBackend ) upgradeLegacyFTSTable (db * gorm.DB ) error {
282311 for _ , tableName := range []string {sqliteFTSUpgradeTable , sqliteFTSLegacyBackup } {
283312 if err := db .Exec (fmt .Sprintf ("DROP TABLE IF EXISTS %s" , tableName )).Error ; err != nil {
@@ -306,6 +335,8 @@ func (s *SQLiteBackend) upgradeLegacyFTSTable(db *gorm.DB) error {
306335 return nil
307336}
308337
338+ // sqliteTableExists reports whether a regular table with the given name exists in sqlite_master.
339+ // @intent let migration code branch on table presence without depending on GORM AutoMigrate side effects.
309340func sqliteTableExists (db * gorm.DB , tableName string ) (bool , error ) {
310341 var count int64
311342 if err := db .Raw ("SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = ?" , tableName ).Scan (& count ).Error ; err != nil {
0 commit comments