Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Automatically generates spans for all SQLite statements
- To use it, pass your `SQLiteDriver` to `SentrySQLiteDriver.create(...)`
- You'll need `androidx.sqlite:sqlite` (2.5.0+) on your app's classpath (Room usually provides it for you). androidx.sqlite 2.6.0+ requires minSdk 23.
- The Room 2.7+ `androidx.sqlite.driver.SupportSQLiteDriver` bridge adapter is recognized and skipped by `SentrySQLiteDriver.create(...)` so apps that wrap both the open helper and the bridge driver do not emit duplicate spans. Spans come from the open helper layer in that configuration.
- See https://docs.sentry.io/platforms/android/integrations/room-and-sqlite/ for more details, including info about migrating from `SentrySupportSQLiteOpenHelper`

## 8.43.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ import io.sentry.SentryLevel
* .build()
* ```
*
* **Warning:** Do not use [SentrySQLiteDriver] together with
* [SentrySupportSQLiteOpenHelper][io.sentry.android.sqlite.SentrySupportSQLiteOpenHelper] on the
* same database file. Both wrappers instrument at different layers, so combining them will produce
* duplicate spans for every SQL statement.
* Note: In order to avoid duplicate spans, wrapping no-ops in the case of the
* `androidx.sqlite.driver.SupportSQLiteDriver`. Wrap the open helper passed to its constructor via
* `SentrySupportSQLiteOpenHelper` instead.
*
* @param delegate The [SQLiteDriver] instance to delegate calls to.
*/
Expand Down Expand Up @@ -68,8 +67,23 @@ public class SentrySQLiteDriver private constructor(private val delegate: SQLite

public companion object {

/**
* Name of the bridge adapter often used with Room 2.7+. It implements the `SQLiteDriver`
* interface and its constructor consumes a `SupportSQLiteOpenHelper`. (Users of the Sentry
* Android Gradle Plugin will have the `SupportSQLiteOpenHelper` wrapped for them
* automatically.) We deliberately avoid wrapping the adapter to prevent duplicate spans.
*
* String (rather than an `is` check) lets us avoid a compile-time dependency on
* androidx.sqlite:sqlite-framework.
*/
private const val SUPPORT_SQLITE_DRIVER_FQN = "androidx.sqlite.driver.SupportSQLiteDriver"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a safety net we should add the class to proguard as well, to ensure this runtime check works as expected. (-keepnames to the proguard-rules.pro file). This might be covered by the androidx libs already, but I'd still add it nevertheless.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, we should add the rule here just in case


@JvmStatic
public fun create(delegate: SQLiteDriver): SQLiteDriver =
delegate as? SentrySQLiteDriver ?: SentrySQLiteDriver(delegate)
if (delegate is SentrySQLiteDriver || delegate.javaClass.name == SUPPORT_SQLITE_DRIVER_FQN) {
delegate
} else {
SentrySQLiteDriver(delegate)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package androidx.sqlite.driver

import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver

/**
* Minimal stub of `androidx.sqlite.driver.SupportSQLiteDriver` (which lives in
Comment thread
markushi marked this conversation as resolved.
* `androidx.sqlite:sqlite-framework`, not on this module's compile/test classpath) for verifying
* behavior of `SentrySQLiteDriver.create(SupportSQLiteDriver)`.
*/
internal class SupportSQLiteDriver : SQLiteDriver {

override val hasConnectionPool: Boolean = false

override fun open(fileName: String): SQLiteConnection {
throw UnsupportedOperationException("Test stub; not for runtime use")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.sentry.sqlite
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
import androidx.sqlite.SQLiteStatement
import androidx.sqlite.driver.SupportSQLiteDriver
import io.sentry.IScopes
import io.sentry.Sentry
import io.sentry.SentryIntegrationPackageStorage
Expand Down Expand Up @@ -64,6 +65,16 @@ class SentrySQLiteDriverTest {
assertSame(wrapped, doubleWrapped)
}

@Test
fun `create with SupportSQLiteDriver bridge returns same instance without wrapping`() {
val bridge = SupportSQLiteDriver()

val result = SentrySQLiteDriver.create(bridge)

assertSame(bridge, result)
assertFalse(result is SentrySQLiteDriver)
}

@Test
fun `hasConnectionPool forwards delegate value when supported`() {
whenever(fixture.mockDriver.hasConnectionPool).thenReturn(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ internal val OPENHELPER_ROOM =
.trimIndent(),
)

// Bridge demos run the same SQL as the driver paths; spans come from the open-helper layer.
internal val BRIDGE_DIRECT = DRIVER_DIRECT

internal val BRIDGE_ROOM2 = DRIVER_ROOM2

internal val OPENHELPER_SQLDELIGHT =
DisplayInfo(
sql =
Expand Down
Loading
Loading