<lambda>null1 package leakcanary.internal.activity.db
2 
3 import android.content.ContentValues
4 import android.database.sqlite.SQLiteDatabase
5 import org.intellij.lang.annotations.Language
6 import shark.Leak
7 import shark.LibraryLeak
8 
9 internal object LeakTable {
10 
11   @Language("RoomSql")
12   const val create = """
13         CREATE TABLE leak
14         (
15         id INTEGER PRIMARY KEY,
16         signature TEXT UNIQUE,
17         short_description TEXT,
18         is_library_leak INTEGER,
19         is_read INTEGER
20         )"""
21 
22   @Language("RoomSql")
23   const val createSignatureIndex = """
24         CREATE INDEX leak_signature
25         on leak (signature)
26     """
27 
28   @Language("RoomSql")
29   const val drop = "DROP TABLE IF EXISTS leak"
30 
31   fun insert(
32     db: SQLiteDatabase,
33     heapAnalysisId: Long,
34     leak: Leak
35   ): Long {
36     val values = ContentValues()
37     values.put("signature", leak.signature)
38     values.put("short_description", leak.shortDescription)
39     values.put("is_library_leak", if (leak is LibraryLeak) 1 else 0)
40     values.put("is_read", 0)
41 
42     db.insertWithOnConflict("leak", null, values, SQLiteDatabase.CONFLICT_IGNORE)
43 
44     val leakId =
45       db.rawQuery("SELECT id from leak WHERE signature = '${leak.signature}' LIMIT 1", null)
46         .use { cursor ->
47           if (cursor.moveToFirst()) cursor.getLong(0) else throw IllegalStateException(
48             "No id found for leak with signature '${leak.signature}'"
49           )
50         }
51 
52     leak.leakTraces.forEachIndexed { index, leakTrace ->
53       LeakTraceTable.insert(
54         db = db,
55         leakId = leakId,
56         heapAnalysisId = heapAnalysisId,
57         leakTraceIndex = index,
58         leakingObjectClassSimpleName = leakTrace.leakingObject.classSimpleName
59       )
60     }
61 
62     return leakId
63   }
64 
65   fun retrieveLeakReadStatuses(
66     db: SQLiteDatabase,
67     signatures: Set<String>
68   ): Map<String, Boolean> {
69     return db.rawQuery(
70       """
71       SELECT
72       signature
73       , is_read
74       FROM leak
75       WHERE signature IN (${signatures.joinToString { "'$it'" }})
76     """, null
77     )
78       .use { cursor ->
79         val leakReadStatuses = mutableMapOf<String, Boolean>()
80         while (cursor.moveToNext()) {
81           val signature = cursor.getString(0)
82           val isRead = cursor.getInt(1) == 1
83           leakReadStatuses[signature] = isRead
84         }
85         leakReadStatuses
86       }
87   }
88 
89   class AllLeaksProjection(
90     val signature: String,
91     val shortDescription: String,
92     val createdAtTimeMillis: Long,
93     val leakTraceCount: Int,
94     val isLibraryLeak: Boolean,
95     val isNew: Boolean
96   )
97 
98   fun retrieveAllLeaks(
99     db: SQLiteDatabase
100   ): List<AllLeaksProjection> {
101     return db.rawQuery(
102       """
103           SELECT
104           l.signature
105           , MIN(l.short_description)
106           , MAX(h.created_at_time_millis) as created_at_time_millis
107           , COUNT(*) as leak_trace_count
108           , MIN(l.is_library_leak) as is_library_leak
109           , MAX(l.is_read) as is_read
110           FROM leak_trace lt
111           LEFT JOIN leak l on lt.leak_id = l.id
112           LEFT JOIN heap_analysis h ON lt.heap_analysis_id = h.id
113           GROUP BY 1
114           ORDER BY leak_trace_count DESC, created_at_time_millis DESC
115           """, null
116     )
117       .use { cursor ->
118         val all = mutableListOf<AllLeaksProjection>()
119         while (cursor.moveToNext()) {
120           val group = AllLeaksProjection(
121             signature = cursor.getString(0),
122             shortDescription = cursor.getString(1),
123             createdAtTimeMillis = cursor.getLong(2),
124             leakTraceCount = cursor.getInt(3),
125             isLibraryLeak = cursor.getInt(4) == 1,
126             isNew = cursor.getInt(5) == 0
127           )
128           all.add(group)
129         }
130         all
131       }
132   }
133 
134   fun markAsRead(
135     db: SQLiteDatabase,
136     signature: String
137   ) {
138     val values = ContentValues().apply { put("is_read", 1) }
139     db.update("leak", values, "signature = ?", arrayOf(signature))
140   }
141 
142   class LeakProjection(
143     val shortDescription: String,
144     val isNew: Boolean,
145     val isLibraryLeak: Boolean,
146     val leakTraces: List<LeakTraceProjection>
147   )
148 
149   class LeakTraceProjection(
150     val leakTraceIndex: Int,
151     val heapAnalysisId: Long,
152     val classSimpleName: String,
153     val createdAtTimeMillis: Long
154   )
155 
156   fun retrieveLeakBySignature(
157     db: SQLiteDatabase,
158     signature: String
159   ): LeakProjection? {
160     return db.rawQuery(
161       """
162           SELECT
163           lt.leak_trace_index
164           , lt.heap_analysis_id
165           , lt.class_simple_name
166           , h.created_at_time_millis
167           , l.short_description
168           , l.is_read
169           , l.is_library_leak
170           FROM leak_trace lt
171           LEFT JOIN leak l on lt.leak_id = l.id
172           LEFT JOIN heap_analysis h ON lt.heap_analysis_id = h.id
173           WHERE l.signature = ?
174           ORDER BY h.created_at_time_millis DESC
175           """, arrayOf(signature)
176     )
177       .use { cursor ->
178         return if (cursor.moveToFirst()) {
179           val leakTraces = mutableListOf<LeakTraceProjection>()
180           val leakProjection = LeakProjection(
181             shortDescription = cursor.getString(4),
182             isNew = cursor.getInt(5) == 0,
183             isLibraryLeak = cursor.getInt(6) == 1,
184             leakTraces = leakTraces
185           )
186           leakTraces.addAll(generateSequence(cursor) {
187             if (cursor.moveToNext()) cursor else null
188           }.map {
189             LeakTraceProjection(
190               leakTraceIndex = cursor.getInt(0),
191               heapAnalysisId = cursor.getLong(1),
192               classSimpleName = cursor.getString(2),
193               createdAtTimeMillis = cursor.getLong(3)
194             )
195           })
196           leakProjection
197         } else {
198           null
199         }
200       }
201   }
202 
203   fun deleteByHeapAnalysisId(
204     db: SQLiteDatabase,
205     heapAnalysisId: Long
206   ) {
207     LeakTraceTable.deleteByHeapAnalysisId(db, heapAnalysisId)
208     db.execSQL(
209       """
210       DELETE
211       FROM leak
212       WHERE NOT EXISTS (
213       SELECT *
214       FROM leak_trace lt
215       WHERE leak.id = lt.leak_id)
216     """
217     )
218   }
219 }
220