SQLDelight for Android - 从SQL语句中生成Kotlin代码 - 4

378 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

迁移

.sq文件总是会描述如果在空数据库中创建最新的schema. 如果数据库当前处于较早的版本, 迁移文件将使这些数据库保持最新. 

如果驱动支持的话, 迁移将运行在事务中. 不要将迁移包围在BEGIN/END TRANSACTION中间, 因为这将引起某些驱动崩溃.

版本控制

Schema的首个版本是1. 迁移文件命名为<version to upgrade from>.sqm格式. 要迁移到版本2的话, 将迁移语句放入1.sqm文件中:

ALTER TABLE hockeyPlayer ADD COLUMN draft_year INTEGER;
ALTER TABLE hockeyPlayer ADD COLUMN draft_order INTEGER;

这些SQL语句被Database.Schema.migrate()运行. 迁移文件和.sq处于相同的源集.

验证迁移

可以在src/main/sqldelight文件夹下放置.db文件, 该文件格式和<version number>.db相同. 如果当前有.db, 那么新的verifySqlDelightMigration任务将添加到Gradle项目中, 而且它作为test任务的一部分运行, 这意味着将根据该.db文件验证迁移. 它确认迁移会产生具有最新schema的数据库.

要从最新schema中产生.db文件, 需要运行generateSqlDelightSchema任务, 一旦按照gradle.md描述的那样, 指定了schemaOutputDirectory, 文件就可用了. 应该在创建首次迁移之前这么做.

代码迁移

如果从代码中运行迁移, 并且想要执行数据迁移, 就要使用Database.Schema.migrateWithCallbacks:

Database.Schema.migrateWithCallbacks(
    driver = database,
    oldVersion = 0,
    newVersion = Database.Schema.version,
    AfterVersion(3) { database.execute(null, "INSERT INTO test (value) VALUES('hello')", 0) },
)

候选情况下, 接收SqlDriver参数通常很有用. 在这种情况下, 可以使用AfterVersionWithDriver类:

Database.Schema.migrateWithCallbacks(
    driver = database,
    oldVersion = 0,
    newVersion = Database.Schema.version,
    AfterVersionWithDriver(3) { it.execute(null, "INSERT INTO test (value) VALUES('hello')", 0) },
)

在接下来的例子中, 如果拥有1.sqm, 2.sqm, 3.sqm, 4.sqm, and 5.sqm用于迁移, 上面的回调会在数据库处于版本 4的时候, 在3.sqm完成之后触发. 回调执行之后, 它将恢复至4.sqm并且完成余下的迁移, 在这个事件中, 余下的迁移指的是4.sqm和5.sqm, 这意味着最终的版本是6.

如果在用AndroidSqliteDriver的话, 需要在驱动创建期间传递这些回调:

val driver: SqlDriver = AndroidSqliteDriver(
    schema = Database.Schema,
    context = context,
    name = "test.db",
    callback = AndroidSqliteDriver.Callback(
        schema = Database.Schema,
        AfterVersion(3) { database.execute(null, "INSERT INTO test (value) VALUES('hello')", 0) },
    )
)

测试

在一些测试中(比如迁移验证), 你可能想要用JVM驱动 换掉Android驱动, 这使得测试数据库关联代码可以不需要Android模拟器或者物理设备. 要这么做的话, 需要使用jvm SQLite驱动:

dependencies {
  testImplementation 'com.squareup.sqldelight:sqlite-driver:1.5.3'
}
// When your test needs a driver
@Before fun before() {
  driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
  Database.Schema.create(driver)
}

如果在用Android捆绑的SQLite(而不是安装的自己的), 你可以覆盖sqlite-jdbc到一个匹配自己的Android minSdkVersion, 比如API 23使用SQLite 3.8.10.2:

dependencies {
  testImplementation('org.xerial:sqlite-jdbc:3.8.10.2') {
    // Override the version of sqlite used by sqlite-driver to match Android API 23
    force = true
  }
}

IntelliJ插件

IntelliJ插件为.sq文件提供了语言级别的特性, 包括:

  • 语法高亮
  • 重构/找到使用
  • 代码自动补全
  • 编辑之后生成Queries文件
  • 右击作为合法SQLite复制
  • IDE中的编译器错误点击文件

从插件市场获取

Gradle

想要更多的自定义的话, 需要使用Gradle DSL显式地声明数据库.

build.gradle:

sqldelight {
  // Database name
  MyDatabase {
    // Package name used for the generated MyDatabase.kt
    packageName = "com.example.db"

    // An array of folders where the plugin will read your '.sq' and '.sqm' 
    // files. The folders are relative to the existing source set so if you
    // specify ["db"], the plugin will look into 'src/main/db' or 'src/commonMain/db' for KMM. 
    // Defaults to ["sqldelight"]
    sourceFolders = ["db"]

    // The directory where to store '.db' schema files relative to the root 
    // of the project. These files are used to verify that migrations yield 
    // a database with the latest schema. Defaults to null so the verification 
    // tasks will not be created.
    schemaOutputDirectory = file("src/main/sqldelight/databases")

    // Optionally specify schema dependencies on other gradle projects
    dependency project(':OtherProject')

    // The dialect version you would like to target
    // Defaults to "sqlite:3.18"
    dialect = "sqlite:3.24"

    // If set to true, migration files will fail during compilation if there are errors in them.
    // Defaults to false
    verifyMigrations = true
  }
}

如果你在Gradle文件中使用Kotlin:

build.gradle.kts

sqldelight {
  database("MyDatabase") {
    packageName = "com.example.db"
    sourceFolders = listOf("db")
    schemaOutputDirectory = file("build/dbs")
    dependency(project(":OtherProject"))
    dialect = "sqlite:3.24"
    verifyMigrations = true
  }
}

依赖

可以指定schema依赖于别的模块:

sqldelight {
  MyDatabase {
    package = "com.example.projecta"
    dependency project(":ProjectB")
  }
}

这将在ProjectB中查找MyDatabase, 并在编译期导入模块的schema. 这些要想工作, ProjectB必须有相同名字的数据库(这个例子中是MyDatabase), 却在不同的包下生成, 所以ProjectB的gradle也许看起来像下面这样:

sqldelight {
  MyDatabase {
    package = "com.example.projectb"
  }
}