Skip to content

chore(android-sqlite): Skip wrapping SupportSQLiteDriver bridge to avoid duplicate spans#5514

Open
0xadam-brown wants to merge 3 commits into
feat/add-sqlite-to-android-sample-appfrom
feat/no-double-wrapping-sqlite-driver
Open

chore(android-sqlite): Skip wrapping SupportSQLiteDriver bridge to avoid duplicate spans#5514
0xadam-brown wants to merge 3 commits into
feat/add-sqlite-to-android-sample-appfrom
feat/no-double-wrapping-sqlite-driver

Conversation

@0xadam-brown
Copy link
Copy Markdown
Member

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

📜 Description

PR protects against generating duplicate db.sql.query spans when using the androidx.sqlite.driver.SupportSQLiteDriver bridge.

💡 Motivation and Context

The bridge is the sole Room (or SQLDelight) API that leaves open the possibility of using SentrySQLiteDriver and SentrySupportSQLiteOpenHelper together with the same db file – a possibility that would lead to redundant spans.

Example misuse this PR guards against

// Step 1: Developer wraps their open helper with Sentry, either manually or
// via the Sentry Android Gradle Plugin.
val sentryWrappedHelper: SupportSQLiteOpenHelper =
    SentrySupportSQLiteOpenHelper.create(
        FrameworkSQLiteOpenHelperFactory().create(configuration)
    )

// Step 2: Developer builds the support driver around that wrapped helper.
val driver: SQLiteDriver = SupportSQLiteDriver(sentryWrappedHelper)

// Step 3: Developer (wrongly) wraps the bridge with Sentry as well. All
// spans would be duplicated were it not for this PR, which returns 
// SupportSQLiteDrivers unwrapped.
val sentryWrappedDriver: SQLiteDriver = SentrySQLiteDriver.create(driver)

Room.databaseBuilder(context, MyDb::class.java, "mydb")
    .setDriver(sentryWrappedDriver)
    .build()

Addresses JAVA-275

⚠️ Callouts

[1] I've covered the no-span edge case via documentation: It's possible to use SupportSQLiteDriver without wrapping the open helper passed via its constructor, resulting in no spans being produced. That a temporary concern, as SupportSQLiteDriver goes away with Room 3.0+; the blast radius should also be limited, as most of our users rely on the Sentry Android Gradle Plugin to auto-wrap the open helper. I've flagged the issue in the CHANGELOG and the SentrySQLiteDriver KDoc.

[2] Support driver has open helper span semantics: Users who rely on SupportSQLiteDriver will have their spans auto-generated via the open helper, and so the span semantics will match its behavior rather than that of SQLiteDriver (for more details about SQLiteDriver's span policy, see here). (Wrapping the SQLiteDriver would've required reflection, which I wanted to avoid for performance reasons and to make it easier to eventually lift the driver into KMP common.)

[3] Changes in this PR are needed if we want to use ASM auto-wrapping: I've a forthcoming PR that introduces ASM auto-wrapping of SQLiteDriver via the Sentry Android Gradle Plugin. The approach in this PR makes that auto-wrapping safe, as SAGP will always convert the SQLiteDriver passed to RoomDatabase.Builder.setDriver(SQLiteDriver) into SentrySQLiteDriver.create(SQLiteDriver). If the provided driver is a SupportSQLiteDriver, this PR ensures SentrySQLiteDriver.create(SQLiteDriver) will return it unwrapped.

💚 How did you test it?

[1] Created a new "Bridge" section in the Android sample app that wires up the SupportSQLiteDriver and provides it with both a SentrySupportSQLiteOpenHelper and a SentrySQLiteDriver:

Bridge section

[2] Verified via the Sentry UI that spans are not duplicated:

Baseline: Existing behavior of SentrySupportSQLiteOpenHelper

Open helper baseline

This PR: Behavior of SupportSQLiteDriver

Bridge - no duplicate spans

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

…oid duplicate spans

SentrySQLiteDriver.create() now recognizes the Room 2.7+ androidx.sqlite.driver.SupportSQLiteDriver bridge adapter and returns it unwrapped. That lets us protect against the one known vector where using both SentrySQLiteDriver and SentrySupportSQLiteOpenHelper with the same db table is allowed under either the Room or SQLDelight APIs:

```kotlin
// AVOID — this configuration produces duplicate spans for every SQL statement.

// Step 1: Developer wraps their open helper with Sentry, either manually or
// via the Sentry Android Gradle Plugin.
val sentryWrappedHelper: SupportSQLiteOpenHelper =
    SentrySupportSQLiteOpenHelper.create(
        FrameworkSQLiteOpenHelperFactory().create(configuration)
    )

// Step 2: Developer builds the compat driver around that wrapped helper.
val driver: SQLiteDriver = SupportSQLiteDriver(sentryWrappedHelper)

// Step 3: Developer (wrongly!) wraps the driver with Sentry as well. All
// spans will now be duplicated.
val sentryWrappedDriver: SQLiteDriver = SentrySQLiteDriver.create(driver)

Room.databaseBuilder(context, MyDb::class.java, "mydb")
    .setDriver(sentryWrappedDriver)
    .build()
```

This commit lets us avoid step 3 by no-op'ing if a developer tries to pass a SupportSQLiteDriver to SentrySQLiteDriver.create().
… sample app to exercise duplicate-span guard

Replace the two-way integration switch with a three-way segmented control and manually construct the SupportSQLiteDriver stack so reviewers can confirm a single helper-layer span per statement when users access their db files through the SupportSQLiteDriver API.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 804f413. Configure here.

@sentry
Copy link
Copy Markdown

sentry Bot commented Jun 8, 2026

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.43.1 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 320.98 ms 350.34 ms 29.36 ms
Size 0 B 0 B 0 B

Baseline results on branch: feat/add-sqlite-to-android-sample-app

Startup times

Revision Plain With Sentry Diff
50412b5 313.85 ms 353.56 ms 39.71 ms
3f03258 321.26 ms 361.84 ms 40.58 ms
f6e3213 291.28 ms 357.63 ms 66.35 ms

App size

Revision Plain With Sentry Diff
50412b5 0 B 0 B 0 B
3f03258 0 B 0 B 0 B
f6e3213 0 B 0 B 0 B

@0xadam-brown 0xadam-brown changed the title Feat/no double wrapping sqlite driver chore(android-sqlite): Skip wrapping SupportSQLiteDriver bridge to avoid duplicate spans Jun 8, 2026
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.

1 participant