Skip to content

Support for androidx.sqlite's SQLiteDriver #5001

@angusholder

Description

@angusholder

Problem Statement

androidx.sqlite 2.5.0 added SQLiteDriver, a new alternative to SupportSQLiteOpenHelper. The new SQLiteDriver API benefits from being Kotlin multi-platform, having a much simpler API, and supporting a bundle of the latest SQLite into your app (rather than getting whatever the Android framework has at runtime). We've switched to this API in our app, but now cannot get SQL query breadcrumbs in Sentry afaict, because we can't use the old SupportSQLiteOpenHelper anymore.

Solution Brainstorm

I sketched a basic MVP of how I think the integration would work:

SentrySQLiteDriver.kt:

import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
import androidx.sqlite.SQLiteStatement
import io.sentry.android.sqlite.SQLiteSpanManager


public class SentrySQLiteDriver(
  private val delegate: SQLiteDriver,
) : SQLiteDriver {
  override fun open(fileName: String): SQLiteConnection {
    val sqliteSpanManager = SQLiteSpanManager(
      // SQLiteDriver.open docs say:
      // >> To open an in-memory database use the special name :memory: as the fileName.
      // SQLiteSpanManager expects null for an in-memory databaseName, so replace ":memory:" with null.
      databaseName = fileName.takeIf { it != ":memory:" }
    )
    val connection = delegate.open(fileName)
    return SentrySQLiteConnection(connection, sqliteSpanManager)
  }
}

internal class SentrySQLiteConnection(
  private val delegate: SQLiteConnection,
  private val sqliteSpanManager: SQLiteSpanManager,
) : SQLiteConnection by delegate {
  override fun prepare(sql: String): SQLiteStatement {
    val statement = delegate.prepare(sql)
    return SentrySQLiteStatement(statement, sqliteSpanManager, sql)
  }
}

internal class SentrySQLiteStatement(
  private val delegate: SQLiteStatement,
  private val sqliteSpanManager: SQLiteSpanManager,
  private val sql: String,
) : SQLiteStatement by delegate {
  // We have to start the span only the first time, regardless of how many times its methods get
  // called.
  private var isSpanStarted = false

  override fun step(): Boolean {
    if (isSpanStarted) {
      return delegate.step()
    } else {
      isSpanStarted = true
      return sqliteSpanManager.performSql(sql) { delegate.step() }
    }
  }

  override fun reset() {
    isSpanStarted = false
    delegate.reset()
  }
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions