Skip to content

feat(plugin): Auto-instrument SQLiteDriver for Room users (GRADLE-107)#1285

Open
0xadam-brown wants to merge 5 commits into
mainfrom
feat/wrap-sqlite-driver
Open

feat(plugin): Auto-instrument SQLiteDriver for Room users (GRADLE-107)#1285
0xadam-brown wants to merge 5 commits into
mainfrom
feat/wrap-sqlite-driver

Conversation

@0xadam-brown

@0xadam-brown 0xadam-brown commented Jun 9, 2026

Copy link
Copy Markdown
Member

📜 Description

Adds a new bytecode method visitor that lets us auto-wrap all occurrences of SQLiteDriver with SentrySQLiteDriver whenever the driver is passed to Room.DatabaseBuilder.setDriver(SQLiteDriver) (Room's sole driver access point – see the "Coverage" section from #1281 for why we don't need to worry about anything else).

In other words, this:

val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName")
     .setDriver(AndroidSQLiteDriver())
     .build()

becomes:

val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName")
     .setDriver(SentrySQLiteDriver.create(AndroidSQLiteDriver()))
     .build()

The wrapping policy is naive in that every SQLiteDriver passed to Room.DatabaseBuilder.setDriver() is wrapped. That's deliberate because SentrySQLiteDriver protects against double-wrapping internally, which lets us keep our visitor implementation simple.

💡 Motivation and Context

Room 2.7 introduced RoomDatabase.Builder.setDriver(SQLiteDriver) as the new way to configure an underlying SQLite implementation (link), with the intention of eventually replacing SupportSQLiteOpenHelper. (Room 3.0+ does just that.)

The Sentry Android Gradle Plugin currently auto-instruments only the open helper path. Apps that adopt the driver-based API will lose automatic SQL performance instrumentation unless they manually wrap their driver with SentrySQLiteDriver.create(...) at every setDriver(...) call site.

This PR provides auto-wrapping for the driver path too.

Addresses GRADLE-107 / #1281

Preconditions

  1. sentry-android-sqlite version in the SAGP build must include SentrySQLiteDriver
  2. InstrumentationFeature.DATABASE must be enabled (⚠️ in other words, we piggyback on the existing DATABASE toggle used to auto-instrument the SupportSQLiteOpenHelper + Room DAO spans)

Coverage

  • Auto-wraps SQLiteDriver for all Room users.
  • SQLDelight users don't need driver auto-wrapping (they still use SupportSQLiteOpenHelper, which we already auto-instrument).
  • The (very) few developers who use SQLiteDriver directly will need to wrap it manually.

Impact on build times

Near zero. The new instrumentation gates on a two-entry allowlist (androidx.room.RoomDatabase$Builder / androidx.room3.RoomDatabase$Builder), which means 0-1 classes will be visited per build in virtually all scenarios. The rest of the per-class cost is a single Set.contains check.

Interactions with other db instrumentation

SupportSQLiteOpenHelper

Driver instrumentation operates alongside our existing instrumentation of SupportSQLiteOpenHelper. (It's fine to use both drivers and open helpers in the same project, so long as a given db file is associated with exactly one of them. Room's APIs help enforce that restriction.)

The Sentry Android SDK – and not the SAGP – is responsible for protecting against double-wrapping and duplicate spans. In general that's easy (just check the type being passed to the wrapper). In the case of the SupportSQLiteDriver used with Room [2.7, 3.0) things get a bit trickier, as it's both a driver and consumes an open helper. But the SDK has that covered as well (see #5514).

Room DAO methods

Driver instrumentation also operates in tandem with our existing Room DAO spans – in theory, at least. Unfortunately DAO instrumentation broke under API changes in Room 2.7, which is the same version that introduced SQLiteDriver. So for now the two are never active at the same time. Fixing that is a task for another day...

💚 How did you test it?

[1] Added unit tests + integration tests in this PR

[2] Verified that all the (for now) @Ignored integration tests pass against a local sentry-java SDK artifact containing SentrySQLiteDriver.

[3] Built out a local version of the Sentry Android sample app that let me manually test SAGP + sentry-java compatibility end-to-end. Verified that auto-instrumentation of SQLiteDriver works as expected.

📝 Checklist

  • I reviewed the submitted code
  • I added tests to verify the changes
  • I updated the docs if needed
  • No breaking changes

🔮 Next steps

  • ⚠️ Prior to merging this PR: Update VERSION_SQLITE_DRIVER to match the sentry-android version that introduces SentrySQLiteDriver (PR here) + address other inline TODOs.
  • Consider a CI cron job that checks Room's latest API for changes that would impact our implementation here and in the Sentry Android SDK.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against c1a418f

@0xadam-brown 0xadam-brown linked an issue Jun 9, 2026 that may be closed by this pull request
Comment thread CHANGELOG.md Outdated
Comment thread plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt Outdated
@0xadam-brown 0xadam-brown force-pushed the feat/wrap-sqlite-driver branch 2 times, most recently from f64ad3f to 1c821b7 Compare June 10, 2026 14:41
… users (GRADLE-107)

Adds a new ASM bytecode method visitor that lets us auto-wrap all occurrences of SQLiteDriver with SentrySQLiteDriver whenever the driver is passed to Room.DatabaseBuilder.setDriver(...).

For instance:

val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName")
     .setDriver(AndroidSQLiteDriver())
     .build()

becomes:

val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName")
     .setDriver(SentrySQLiteDriver.create(AndroidSQLiteDriver()))
     .build()

The wrapping policy is naive in that every SQLiteDriver passed to setDriver() is wrapped. That's deliberate because SentrySQLiteDriver protects against double-wrapping internally, which lets us keep our visitor implementation simple.

Preconditions:

1. InstrumentationFeature.DATABASE is enabled
2. The owning app is using a version of sentry-android-sqlite that includes SentrySQLiteDriver

Coverage:

- Auto-wraps SQLiteDriver for all Room users (sole Room access point is via its Room.DatabaseBuilder.setDriver() method).
- SQLDelight users don't need driver auto-wrapping (they still use SupportSQLiteOpenHelper, which we already auto-wrap).
- The few developers who use SQLiteDriver directly will need to wrap it manually.
@0xadam-brown 0xadam-brown force-pushed the feat/wrap-sqlite-driver branch from 1c821b7 to 1548c9c Compare June 10, 2026 15:27
@0xadam-brown 0xadam-brown marked this pull request as ready for review June 10, 2026 15:31
@0xadam-brown 0xadam-brown changed the title feat(plugin): Auto-wrap SQLiteDriver with SentrySQLiteDriver for Room users feat(plugin): Auto-instrument SQLiteDriver for Room users Jun 10, 2026
@0xadam-brown 0xadam-brown changed the title feat(plugin): Auto-instrument SQLiteDriver for Room users feat(plugin): Auto-instrument SQLiteDriver for Room users (GRADLE-107) Jun 10, 2026
@linear-code

linear-code Bot commented Jun 10, 2026

Copy link
Copy Markdown

GRADLE-107

@runningcode runningcode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks this looks good! I just don't think I'm enough of an expert in how we instrument classes to approve this.
I left some questions for my better understanding of the patterns we are using.

Comment thread gradle/libs.versions.toml
# test
mockitoKotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" }
arscLib = { group = "io.github.reandroid", name = "ARSCLib", version = "1.1.4" }
mockitoKotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why was this line moved?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Just to alphabetize (in line with the other sections in this file...mostly). I can move it back if you prefer, of course.

Comment thread CHANGELOG.md Outdated
Comment thread plugin-build/build.gradle.kts Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we depend on room already can we load the class files from the artifact instead of checking in the binaries?

testImplementationAar(libs.roomRuntimeAndroid)
testImplementationAar(libs.room3RuntimeAndroid)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, and good question: we could load via ClassLoader.getResourceAsStream rather than pinning bytecode. Your approach would be a win, too, because we'd keep testing against the latest as Dependabot bumps us.

That said, this matches existing instrumentation tests. Happy to switch if folks want to move generally...

Comment thread plugin-build/src/test/kotlin/io/sentry/sqlite/SentrySQLiteDriver.kt Outdated
Comment on lines +31 to +32
internal const val SENTRY_CREATE_DESCRIPTOR =
"($SQLITE_DRIVER_TYPE_DESCRIPTOR)$SQLITE_DRIVER_TYPE_DESCRIPTOR"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The hardcoded method signature for SentrySQLiteDriver.create() in the bytecode visitor may not match the actual library's implementation, risking a NoSuchMethodError runtime crash.
Severity: HIGH

Suggested Fix

Verify that the method signature of SentrySQLiteDriver.create() in the sentry-android-sqlite library matches the descriptor (Landroidx/sqlite/SQLiteDriver;)Landroidx/sqlite/SQLiteDriver;. If they differ, update the descriptor in SetDriverMethodVisitor to match the actual implementation to prevent a runtime NoSuchMethodError.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/driver/visitor/SetDriverMethodVisitor.kt#L31-L32

Potential issue: The bytecode instrumentation in `SetDriverMethodVisitor` hardcodes the
method descriptor for `SentrySQLiteDriver.create()` to be
`(Landroidx/sqlite/SQLiteDriver;)Landroidx/sqlite/SQLiteDriver;`. While tests pass using
a stub with this signature, a runtime crash will occur if the actual
`sentry-android-sqlite` library implements this method with a different signature, such
as returning the more specific `SentrySQLiteDriver` type instead of the `SQLiteDriver`
interface. This mismatch would lead to a `NoSuchMethodError` when the instrumented code
is executed, causing the application to crash during database initialization.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment thread CHANGELOG.md
### Dependencies

- Bump Android SDK from v8.43.2 to v8.45.0 ([#TODO](https://github.com/getsentry/sentry-android-gradle-plugin/pull/TODO))
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#TODO)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TODO: Update URLs once we publish Android SDK 8.45.0.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2ee2ad8. Configure here.

SentryVersions.VERSION_SQLITE_DRIVER.major,
SentryVersions.VERSION_SQLITE_DRIVER.minor,
SentryVersions.VERSION_SQLITE_DRIVER.patch - 1,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Patch-below threshold test breaks

Low Severity

The test that asserts behavior one patch below VERSION_SQLITE_DRIVER builds a SemVer with patch - 1. When VERSION_SQLITE_DRIVER is set to a release like 8.45.0 (patch 0), that subtraction yields -1, which violates SemVer's non-negative patch requirement and fails during test setup instead of exercising the gate.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2ee2ad8. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TODO: Address once we bump the Android SDK to 8.45.0.

Comment thread gradle/libs.versions.toml
ktfmt = "0.51"
sqlite = "2.1.0"
sentry = "8.43.2"
sentry = "8.44.1"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TODO: Bump to 8.45.0 once we publish the Android SDK.

internal val VERSION_LOGCAT = SemVer(6, 17, 0)
internal val VERSION_APP_START = SemVer(7, 1, 0)
internal val VERSION_SQLITE = SemVer(6, 21, 0)
internal val VERSION_SQLITE_DRIVER = SemVer(8, 44, 1)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TODO: Bump to 8.45.0 once we publish the Android SDK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-instrument SQLiteDriver for Room users

2 participants