@@ -4,13 +4,12 @@ import android.app.Application
44import org.signal.core.util.SqlUtil
55import org.signal.core.util.Stopwatch
66import org.signal.core.util.logging.Log
7- import org.signal.core.util.readToList
8- import org.signal.core.util.requireNonNullString
97import org.thoughtcrime.securesms.database.SQLiteDatabase
108
119/* *
1210 * Adds column to messages to track who has deleted a given message. Because of an
13- * OOM crash, we rebuild the table manually in batches instead of using the drop column syntax.
11+ * OOM crash, we do not drop the remote_deleted column. For users who already completed
12+ * this migration, we add it back in the future.
1413 */
1514@Suppress(" ClassName" )
1615object V302_AddDeletedByColumn : SignalDatabaseMigration {
@@ -25,208 +24,15 @@ object V302_AddDeletedByColumn : SignalDatabaseMigration {
2524
2625 val stopwatch = Stopwatch (" migration" , decimalPlaces = 2 )
2726
28- val dependentItems: List <SqlItem > = getAllDependentItems(db, " message" )
29- dependentItems.forEach { item ->
30- val sql = " DROP ${item.type} IF EXISTS ${item.name} "
31- Log .d(TAG , " Executing: $sql " )
32- db.execSQL(sql)
33- }
34- stopwatch.split(" drop-dependents" )
35-
3627 db.execSQL(" ALTER TABLE message ADD COLUMN deleted_by INTEGER DEFAULT NULL REFERENCES recipient (_id) ON DELETE CASCADE" )
3728 stopwatch.split(" add-column" )
3829
3930 db.execSQL(" UPDATE message SET deleted_by = from_recipient_id WHERE remote_deleted > 0" )
4031 stopwatch.split(" update-data" )
4132
42- db.execSQL(
43- """
44- CREATE TABLE message_tmp (
45- _id INTEGER PRIMARY KEY AUTOINCREMENT,
46- date_sent INTEGER NOT NULL,
47- date_received INTEGER NOT NULL,
48- date_server INTEGER DEFAULT -1,
49- thread_id INTEGER NOT NULL REFERENCES thread (_id) ON DELETE CASCADE,
50- from_recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
51- from_device_id INTEGER,
52- to_recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
53- type INTEGER NOT NULL,
54- body TEXT,
55- read INTEGER DEFAULT 0,
56- ct_l TEXT,
57- exp INTEGER,
58- m_type INTEGER,
59- m_size INTEGER,
60- st INTEGER,
61- tr_id TEXT,
62- subscription_id INTEGER DEFAULT -1,
63- receipt_timestamp INTEGER DEFAULT -1,
64- has_delivery_receipt INTEGER DEFAULT 0,
65- has_read_receipt INTEGER DEFAULT 0,
66- viewed INTEGER DEFAULT 0,
67- mismatched_identities TEXT DEFAULT NULL,
68- network_failures TEXT DEFAULT NULL,
69- expires_in INTEGER DEFAULT 0,
70- expire_started INTEGER DEFAULT 0,
71- notified INTEGER DEFAULT 0,
72- quote_id INTEGER DEFAULT 0,
73- quote_author INTEGER DEFAULT 0,
74- quote_body TEXT DEFAULT NULL,
75- quote_missing INTEGER DEFAULT 0,
76- quote_mentions BLOB DEFAULT NULL,
77- quote_type INTEGER DEFAULT 0,
78- shared_contacts TEXT DEFAULT NULL,
79- unidentified INTEGER DEFAULT 0,
80- link_previews TEXT DEFAULT NULL,
81- view_once INTEGER DEFAULT 0,
82- reactions_unread INTEGER DEFAULT 0,
83- reactions_last_seen INTEGER DEFAULT -1,
84- mentions_self INTEGER DEFAULT 0,
85- notified_timestamp INTEGER DEFAULT 0,
86- server_guid TEXT DEFAULT NULL,
87- message_ranges BLOB DEFAULT NULL,
88- story_type INTEGER DEFAULT 0,
89- parent_story_id INTEGER DEFAULT 0,
90- export_state BLOB DEFAULT NULL,
91- exported INTEGER DEFAULT 0,
92- scheduled_date INTEGER DEFAULT -1,
93- latest_revision_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE CASCADE,
94- original_message_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE CASCADE,
95- revision_number INTEGER DEFAULT 0,
96- message_extras BLOB DEFAULT NULL,
97- expire_timer_version INTEGER DEFAULT 1 NOT NULL,
98- votes_unread INTEGER DEFAULT 0,
99- votes_last_seen INTEGER DEFAULT 0,
100- pinned_until INTEGER DEFAULT 0,
101- pinning_message_id INTEGER DEFAULT 0,
102- pinned_at INTEGER DEFAULT 0,
103- deleted_by INTEGER DEFAULT NULL REFERENCES recipient (_id) ON DELETE CASCADE
104- )
105- """
106- )
107- stopwatch.split(" create-table" )
108-
109- copyMessages(db)
110- stopwatch.split(" copy-data" )
111-
112- db.execSQL(" DROP TABLE message" )
113- stopwatch.split(" drop-old-table" )
114-
115- db.execSQL(" ALTER TABLE message_tmp RENAME TO message" )
116- stopwatch.split(" rename-table" )
117-
118- dependentItems.forEach { item ->
119- val sql = item.createStatement
120- Log .d(TAG , " Executing: $sql " )
121- db.execSQL(sql)
122- }
123- stopwatch.split(" recreate-dependents" )
124-
12533 db.execSQL(" CREATE INDEX IF NOT EXISTS message_deleted_by_index ON message (deleted_by)" )
12634 stopwatch.split(" create-index" )
12735
128- val foreignKeyViolations: List <SqlUtil .ForeignKeyViolation > = SqlUtil .getForeignKeyViolations(db, " message" )
129- if (foreignKeyViolations.isNotEmpty()) {
130- Log .w(TAG , " Foreign key violations!\n ${foreignKeyViolations.joinToString(separator = " \n " )} " )
131- throw IllegalStateException (" Foreign key violations!" )
132- }
133- stopwatch.split(" fk-check" )
134-
13536 stopwatch.stop(TAG )
13637 }
137-
138- private fun copyMessages (db : SQLiteDatabase ) {
139- val batchSize = 50_000L
140-
141- val maxId = SqlUtil .getNextAutoIncrementId(db, " message" )
142-
143- for (i in 1 .. maxId step batchSize) {
144- db.execSQL(
145- """
146- INSERT INTO message_tmp
147- SELECT
148- _id,
149- date_sent,
150- date_received,
151- date_server,
152- thread_id,
153- from_recipient_id,
154- from_device_id,
155- to_recipient_id,
156- type,
157- body,
158- read,
159- ct_l,
160- exp,
161- m_type,
162- m_size,
163- st,
164- tr_id,
165- subscription_id,
166- receipt_timestamp,
167- has_delivery_receipt,
168- has_read_receipt,
169- viewed,
170- mismatched_identities,
171- network_failures,
172- expires_in,
173- expire_started,
174- notified,
175- quote_id,
176- quote_author,
177- quote_body,
178- quote_missing,
179- quote_mentions,
180- quote_type,
181- shared_contacts,
182- unidentified,
183- link_previews,
184- view_once,
185- reactions_unread,
186- reactions_last_seen,
187- mentions_self,
188- notified_timestamp,
189- server_guid,
190- message_ranges,
191- story_type,
192- parent_story_id,
193- export_state,
194- exported,
195- scheduled_date,
196- latest_revision_id,
197- original_message_id,
198- revision_number,
199- message_extras,
200- expire_timer_version,
201- votes_unread,
202- votes_last_seen,
203- pinned_until,
204- pinning_message_id,
205- pinned_at,
206- deleted_by
207- FROM
208- message
209- WHERE
210- _id >= $i AND
211- _id < ${i + batchSize}
212- """
213- )
214- }
215- }
216-
217- private fun getAllDependentItems (db : SQLiteDatabase , tableName : String ): List <SqlItem > {
218- return db.rawQuery(" SELECT type, name, sql FROM sqlite_schema WHERE tbl_name='$tableName ' AND type != 'table'" ).readToList { cursor ->
219- SqlItem (
220- type = cursor.requireNonNullString(" type" ),
221- name = cursor.requireNonNullString(" name" ),
222- createStatement = cursor.requireNonNullString(" sql" )
223- )
224- }
225- }
226-
227- data class SqlItem (
228- val type : String ,
229- val name : String ,
230- val createStatement : String
231- )
23238}
0 commit comments