@@ -285,6 +285,158 @@ BOOL db_engine_unload_db(const char *path)
285285 return FALSE ;
286286}
287287
288+ /* *
289+ * @db: sqlite handle
290+ * @last_cn: counter for most recently assigned CN (GCV)
291+ * @q_list: query for obtaining some objects
292+ * (shall return 2 columns; id and eligibility)
293+ * @q_cn: query for updating per-object CN
294+ * @q_prop: query for updating per-object CK & PCL
295+ *
296+ * Iterate over an object set and assign new Change Numbers, Change Keys and
297+ * Predecessor Change Lists.
298+ */
299+ static bool cgkreset_3 (sqlite3 *db, uint64_t &last_cn, const GUID &store_guid,
300+ const char *q_list, const char *q_cn, const char *q_prop)
301+ {
302+ std::vector<std::pair<uint64_t , uint64_t >> gcv_list;
303+ auto stm = gx_sql_prep (db, q_list);
304+ if (stm == nullptr )
305+ return false ;
306+ while (stm.step () == SQLITE_ROW)
307+ gcv_list.emplace_back (stm.col_uint64 (0 ), stm.col_uint64 (1 ));
308+ stm = gx_sql_prep (db, q_cn);
309+ if (stm == nullptr )
310+ return false ;
311+ auto stm_prop = gx_sql_prep (db, q_prop);
312+ if (stm_prop == nullptr )
313+ return false ;
314+
315+ for (auto [objid, parent_attid] : gcv_list) {
316+ auto next_cn = last_cn + 1 ;
317+ stm.reset ();
318+ stm.bind_int64 (1 , next_cn);
319+ stm.bind_int64 (2 , objid);
320+ if (stm.step () != SQLITE_DONE)
321+ return false ;
322+ ++last_cn;
323+ if (parent_attid != 0 )
324+ /* CK/PCL on attachments makes no sense */
325+ continue ;
326+
327+ char buf[23 ];
328+ XID xid{store_guid, rop_util_make_eid_ex (1 , last_cn)};
329+ EXT_PUSH ep;
330+ if (!ep.init (&buf[1 ], sizeof (buf) - 1 , 0 ))
331+ return false ;
332+ ep.p_xid (xid);
333+ stm_prop.reset ();
334+ stm_prop.bind_blob (1 , &buf[1 ], ep.m_offset );
335+ stm_prop.bind_int64 (2 , objid);
336+ stm_prop.bind_int64 (3 , PR_CHANGE_KEY);
337+ if (stm_prop.step () != SQLITE_DONE)
338+ return false ;
339+
340+ stm_prop.reset ();
341+ buf[0 ] = 22 ;
342+ stm_prop.bind_blob (1 , buf, 23 );
343+ stm_prop.bind_int64 (2 , objid);
344+ stm_prop.bind_int64 (3 , PR_PREDECESSOR_CHANGE_LIST);
345+ if (stm_prop.step () != SQLITE_DONE)
346+ return false ;
347+ }
348+ return true ;
349+ }
350+
351+ static bool cgkreset_2 (sqlite3 *db, uint64_t &last_cn, const GUID &store_guid,
352+ unsigned int flags)
353+ {
354+ if (flags & CGKRESET_FOLDERS) {
355+ auto succ = cgkreset_3 (db, last_cn, store_guid,
356+ " SELECT folder_id, NULL FROM folders" ,
357+ " UPDATE folders SET change_number=? WHERE folder_id=?" ,
358+ " UPDATE folder_properties SET propval=? WHERE folder_id=? AND proptag=?" );
359+ if (!succ)
360+ return false ;
361+ }
362+ if (flags & CGKRESET_MESSAGES) {
363+ auto succ = cgkreset_3 (db, last_cn, store_guid,
364+ " SELECT message_id, parent_attid FROM messages" ,
365+ " UPDATE messages SET change_number=? WHERE message_id=?" ,
366+ " UPDATE message_properties SET propval=? WHERE message_id=? AND proptag=?" );
367+ if (!succ)
368+ return false ;
369+ }
370+ return true ;
371+ }
372+
373+ /* *
374+ * Obtain essential parameters for a global CN/CK reassignment. In doing so, it
375+ * performs the first sanity check and returns a bumped last_cn if necessary.
376+ */
377+ static bool cgkreset_load_param (sqlite3 *db, uint64_t &last_cn, GUID &store_guid)
378+ {
379+ auto stm = gx_sql_prep (db, " SELECT config_value FROM configurations WHERE config_id=?" );
380+ if (stm == nullptr )
381+ return false ;
382+ stm.bind_int64 (1 , CONFIG_ID_MAILBOX_GUID);
383+ if (stm.step () != SQLITE_ROW)
384+ return false ;
385+ store_guid.from_str (stm.col_text (0 ));
386+ stm.reset ();
387+ stm.bind_int64 (1 , CONFIG_ID_LAST_CHANGE_NUMBER);
388+ if (stm.step () != SQLITE_ROW)
389+ return false ;
390+ last_cn = stm.col_uint64 (0 );
391+
392+ stm = gx_sql_prep (db, " SELECT MAX(change_number) FROM folders" );
393+ if (stm == nullptr )
394+ return false ;
395+ if (stm.step () == SQLITE_ROW)
396+ last_cn = std::max (last_cn, stm.col_uint64 (0 ));
397+ stm = gx_sql_prep (db, " SELECT MAX(change_number) FROM messages" );
398+ if (stm == nullptr )
399+ return false ;
400+ if (stm.step () == SQLITE_ROW)
401+ last_cn = std::max (last_cn, stm.col_uint64 (0 ));
402+ return true ;
403+ }
404+
405+ static bool cgkreset_save_param (sqlite3 *db, uint64_t last_cn)
406+ {
407+ auto stm = gx_sql_prep (db, " UPDATE configurations SET config_value=? WHERE config_id=?" );
408+ if (stm == nullptr )
409+ return false ;
410+ stm.bind_int64 (1 , last_cn);
411+ stm.bind_int64 (2 , CONFIG_ID_LAST_CHANGE_NUMBER);
412+ return stm.step () == SQLITE_DONE;
413+ }
414+
415+ BOOL db_engine_cgkreset (const char *dir, uint32_t flags)
416+ {
417+ auto db = db_engine_get_db (dir);
418+ if (!db)
419+ return false ;
420+ auto xact = gx_sql_begin (db->psqlite , txn_mode::write);
421+ if (!xact)
422+ return false ;
423+ uint64_t last_cn = 0 ;
424+ GUID store_guid;
425+ if (!cgkreset_load_param (db->psqlite , last_cn, store_guid))
426+ return false ;
427+ if (flags & CGKRESET_ZERO_LASTCN)
428+ last_cn = 0 ;
429+ if (flags & (CGKRESET_ZERO_LASTCN | CGKRESET_FOLDERS | CGKRESET_MESSAGES)) {
430+ auto succ = cgkreset_2 (db->psqlite , last_cn, store_guid, flags);
431+ if (!succ)
432+ return false ;
433+ }
434+ auto succ = cgkreset_save_param (db->psqlite , last_cn);
435+ if (!succ)
436+ return false ;
437+ return xact.commit () == SQLITE_OK;
438+ }
439+
288440dynamic_node::dynamic_node (dynamic_node &&o) noexcept :
289441 folder_id(o.folder_id), search_flags(o.search_flags),
290442 prestriction(o.prestriction), folder_ids(o.folder_ids)
0 commit comments