<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