@@ -133,6 +133,121 @@ func TestImportIfChangedUsesIncrementalTailImport(t *testing.T) {
133133 require .Contains (t , state , `"file_manifests"` )
134134}
135135
136+ func TestImportIfChangedUsesMixedIncrementalPlanForMetadataChanges (t * testing.T ) {
137+ ctx := context .Background ()
138+ src := seedStore (t , filepath .Join (t .TempDir (), "src.db" ))
139+ defer func () { _ = src .Close () }()
140+
141+ repo := filepath .Join (t .TempDir (), "share" )
142+ manifest , err := Export (ctx , src , Options {RepoPath : repo , Branch : "main" })
143+ require .NoError (t , err )
144+
145+ dst , err := store .Open (ctx , filepath .Join (t .TempDir (), "dst.db" ))
146+ require .NoError (t , err )
147+ defer func () { _ = dst .Close () }()
148+ _ , changed , err := ImportIfChanged (ctx , dst , Options {RepoPath : repo , Branch : "main" })
149+ require .NoError (t , err )
150+ require .True (t , changed )
151+
152+ now := time .Now ().UTC ().Format (time .RFC3339Nano )
153+ require .NoError (t , src .UpsertChannel (ctx , store.ChannelRecord {ID : "c1" , GuildID : "g1" , Kind : "text" , Name : "launch" , RawJSON : `{}` }))
154+ require .NoError (t , src .UpsertMember (ctx , store.MemberRecord {
155+ GuildID : "g1" ,
156+ UserID : "u1" ,
157+ Username : "peter" ,
158+ DisplayName : "Launch Peter" ,
159+ RoleIDsJSON : `[]` ,
160+ RawJSON : `{"bio":"delta member"}` ,
161+ }))
162+ require .NoError (t , src .UpsertMessages (ctx , []store.MessageMutation {{
163+ Record : store.MessageRecord {
164+ ID : "m2" ,
165+ GuildID : "g1" ,
166+ ChannelID : "c1" ,
167+ ChannelName : "launch" ,
168+ AuthorID : "u1" ,
169+ AuthorName : "Peter" ,
170+ MessageType : 0 ,
171+ CreatedAt : now ,
172+ Content : "mixed delta landed" ,
173+ NormalizedContent : "mixed delta landed" ,
174+ RawJSON : `{"author":{"username":"Peter"}}` ,
175+ },
176+ EventType : "upsert" ,
177+ PayloadJSON : `{"id":"m2"}` ,
178+ Options : store.WriteOptions {AppendEvent : true },
179+ Attachments : []store.AttachmentRecord {{
180+ AttachmentID : "a2" ,
181+ MessageID : "m2" ,
182+ GuildID : "g1" ,
183+ ChannelID : "c1" ,
184+ AuthorID : "u1" ,
185+ Filename : "delta.txt" ,
186+ TextContent : "attached delta" ,
187+ }},
188+ Mentions : []store.MentionEventRecord {{
189+ MessageID : "m2" ,
190+ GuildID : "g1" ,
191+ ChannelID : "c1" ,
192+ AuthorID : "u1" ,
193+ TargetType : "role" ,
194+ TargetID : "r2" ,
195+ TargetName : "Launch" ,
196+ EventAt : now ,
197+ }},
198+ }}))
199+ updated , err := Export (ctx , src , Options {RepoPath : repo , Branch : "main" })
200+ require .NoError (t , err )
201+ require .NotEqual (t , manifest .GeneratedAt , updated .GeneratedAt )
202+
203+ previous , ok := PreviousImportedManifest (ctx , dst , Options {RepoPath : repo , Branch : "main" })
204+ require .True (t , ok )
205+ planned , supported := shareIncrementalPlan (snapshot .PlanIncrementalImport (snapshotManifest (previous ), snapshotManifest (updated )))
206+ require .True (t , supported , "%+v" , planned )
207+ require .Equal (t , snapshot .TableImportReplace , importPlanTable (t , planned , "channels" ).Mode )
208+ require .Equal (t , snapshot .TableImportReplace , importPlanTable (t , planned , "members" ).Mode )
209+ require .Equal (t , snapshot .TableImportFiles , importPlanTable (t , planned , "messages" ).Mode )
210+ require .Equal (t , snapshot .TableImportReplace , importPlanTable (t , planned , "message_events" ).Mode )
211+ require .Equal (t , snapshot .TableImportReplace , importPlanTable (t , planned , "message_attachments" ).Mode )
212+ require .Equal (t , snapshot .TableImportReplace , importPlanTable (t , planned , "mention_events" ).Mode )
213+
214+ var progress []ImportProgress
215+ imported , changed , err := ImportIfChanged (ctx , dst , Options {
216+ RepoPath : repo ,
217+ Branch : "main" ,
218+ Progress : func (p ImportProgress ) { progress = append (progress , p ) },
219+ })
220+ require .NoError (t , err )
221+ require .True (t , changed )
222+ require .Equal (t , updated .GeneratedAt , imported .GeneratedAt )
223+ require .Contains (t , progressPhases (progress ), "rebuild_fts" )
224+ require .Contains (t , progressPhases (progress ), "rebuild_member_fts" )
225+ require .Equal (t , importPlanRowCount (planned ), progressTotalRows (t , progress , "start" ))
226+ require .Positive (t , progressTotalRows (t , progress , "start" ))
227+
228+ results , err := dst .SearchMessages (ctx , store.SearchOptions {Query : "checklist" , Channel : "launch" , Limit : 10 })
229+ require .NoError (t , err )
230+ require .Len (t , results , 1 )
231+ require .Equal (t , "m1" , results [0 ].MessageID )
232+
233+ results , err = dst .SearchMessages (ctx , store.SearchOptions {Query : "mixed delta" , Limit : 10 })
234+ require .NoError (t , err )
235+ require .Len (t , results , 1 )
236+ require .Equal (t , "m2" , results [0 ].MessageID )
237+ _ , rows , err := dst .ReadOnlyQuery (ctx , "select name from channels where id = 'c1'" )
238+ require .NoError (t , err )
239+ require .Equal (t , "launch" , rows [0 ][0 ])
240+ _ , rows , err = dst .ReadOnlyQuery (ctx , "select count(*) from mention_events" )
241+ require .NoError (t , err )
242+ require .Equal (t , "2" , rows [0 ][0 ])
243+ _ , rows , err = dst .ReadOnlyQuery (ctx , "select count(*) from message_events" )
244+ require .NoError (t , err )
245+ require .Equal (t , "2" , rows [0 ][0 ])
246+ _ , rows , err = dst .ReadOnlyQuery (ctx , "select count(*) from member_fts where member_fts match 'delta'" )
247+ require .NoError (t , err )
248+ require .Equal (t , "1" , rows [0 ][0 ])
249+ }
250+
136251func TestImportIfChangedInfersLegacyManifestFilesFromGit (t * testing.T ) {
137252 ctx := context .Background ()
138253 src := seedStore (t , filepath .Join (t .TempDir (), "src.db" ))
@@ -1261,6 +1376,17 @@ func tableEntry(t *testing.T, manifest Manifest, name string) TableManifest {
12611376 return TableManifest {}
12621377}
12631378
1379+ func importPlanTable (t * testing.T , plan snapshot.ImportPlan , name string ) snapshot.TableImportPlan {
1380+ t .Helper ()
1381+ for _ , table := range plan .Tables {
1382+ if table .Table .Name == name {
1383+ return table
1384+ }
1385+ }
1386+ t .Fatalf ("plan table %s not found" , name )
1387+ return snapshot.TableImportPlan {}
1388+ }
1389+
12641390func tableNames (manifest Manifest ) []string {
12651391 names := make ([]string , 0 , len (manifest .Tables ))
12661392 for _ , table := range manifest .Tables {
@@ -1276,3 +1402,14 @@ func progressPhases(progress []ImportProgress) []string {
12761402 }
12771403 return phases
12781404}
1405+
1406+ func progressTotalRows (t * testing.T , progress []ImportProgress , phase string ) int {
1407+ t .Helper ()
1408+ for _ , item := range progress {
1409+ if item .Phase == phase {
1410+ return item .TotalRows
1411+ }
1412+ }
1413+ t .Fatalf ("progress phase %s not found" , phase )
1414+ return 0
1415+ }
0 commit comments