Skip to content

Commit

Permalink
SQLDelight client and related Gradle plugin.
Browse files Browse the repository at this point in the history
  • Loading branch information
mpawliszyn committed Oct 16, 2024
1 parent 4b9186f commit a545275
Show file tree
Hide file tree
Showing 42 changed files with 1,627 additions and 5 deletions.
10 changes: 10 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ buildscript {
classpath(libs.mavenPublishGradlePlugin)
classpath(libs.spotlessPlugin)
classpath(libs.wireGradlePlugin)
classpath(libs.buildConfigPlugin)
classpath(libs.shadowJarPlugin)
}
}
Expand Down Expand Up @@ -120,6 +121,15 @@ subprojects {

allprojects {
plugins.withId("com.vanniktech.maven.publish.base") {
configure<PublishingExtension> {
// For the Gradle plugin's tests.
repositories {
maven {
name = "testMaven"
url = rootProject.layout.buildDirectory.dir("testMaven").get().asFile.toURI()
}
}
}
configure<MavenPublishBaseExtension> {
publishToMavenCentral(SonatypeHost.DEFAULT, automaticRelease = true)
signAllPublications()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface BoundingRangeStrategy<E : DbEntity<E>, Pkey : Any> {
/**
* Computes the raw table min and max based on the primary key. Returns null if the table is empty.
*/
fun computeAbsoluteMinMax(
fun computeAbsoluteRange(
backfill: HibernateBackfill<E, Pkey, *>,
partitionName: String,
): MinMax<Pkey>?
Expand Down Expand Up @@ -72,7 +72,7 @@ interface BoundingRangeStrategy<E : DbEntity<E>, Pkey : Any> {
class UnshardedHibernateBoundingRangeStrategy<E : DbEntity<E>, Pkey : Any>(
private val partitionProvider: PartitionProvider,
) : BoundingRangeStrategy<E, Pkey> {
override fun computeAbsoluteMinMax(
override fun computeAbsoluteRange(
backfill: HibernateBackfill<E, Pkey, *>,
partitionName: String,
): MinMax<Pkey>? {
Expand Down Expand Up @@ -127,7 +127,7 @@ class UnshardedHibernateBoundingRangeStrategy<E : DbEntity<E>, Pkey : Any>(
class VitessShardedBoundingRangeStrategy<E : DbEntity<E>, Pkey : Any>(
private val partitionProvider: PartitionProvider,
) : BoundingRangeStrategy<E, Pkey> {
override fun computeAbsoluteMinMax(
override fun computeAbsoluteRange(
backfill: HibernateBackfill<E, Pkey, *>,
partitionName: String,
): MinMax<Pkey>? {
Expand Down Expand Up @@ -184,7 +184,7 @@ class VitessSingleCursorBoundingRangeStrategy<E : DbEntity<E>, Pkey : Any>(
private val transacter: Transacter,
private val keyspace: Keyspace,
) : BoundingRangeStrategy<E, Pkey> {
override fun computeAbsoluteMinMax(
override fun computeAbsoluteRange(
backfill: HibernateBackfill<E, Pkey, *>,
partitionName: String,
): MinMax<Pkey>? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ internal class HibernateBackfillOperator<E : DbEntity<E>, Pkey : Any, Param : An
.estimated_record_count(null)
.build()
}
val minMax = boundingRangeStrategy.computeAbsoluteMinMax(backfill, partitionName)
val minMax = boundingRangeStrategy.computeAbsoluteRange(backfill, partitionName)
val keyRange: KeyRange = if (minMax == null) {
// Empty table, no work to do for this partition.
KeyRange.Builder().build()
Expand Down
114 changes: 114 additions & 0 deletions client-sqldelight-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import com.vanniktech.maven.publish.GradlePlugin
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.MavenPublishBaseExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("java-gradle-plugin")
kotlin("jvm")
`java-library`
id("com.github.gmazzo.buildconfig")
id("com.vanniktech.maven.publish.base")
}

dependencies {
implementation(libs.guava)
implementation(libs.kotlinGradlePlugin)
implementation(libs.kotlinPoet)
implementation(libs.moshiCore)
implementation(libs.moshiKotlin)
implementation(libs.wireRuntime)
implementation(libs.guice)
implementation(libs.kotlinStdLib)
implementation(libs.sqldelightJdbcDriver)
// /Users/mikepaw/.gradle/caches/jars-9/fd3f67ac2b6df3442dc9a5c8c256f2e1/gradle-plugin-2.0.2.jar
implementation(libs.sqldelightGradlePlugin)
implementation(libs.okHttp)
implementation(libs.okio)
implementation(libs.retrofit)
implementation(libs.retrofitMock)
implementation(libs.retrofitMoshi)
implementation(libs.retrofitWire)
implementation(libs.wireMoshiAdapter)

api(project(":client"))
// We do not want to leak client-base implementation details to customers.
implementation(project(":client-base"))


testImplementation(libs.assertj)
testImplementation(libs.junitEngine)
testImplementation(libs.kotlinTest)

testImplementation(project(":backfila-embedded"))
testImplementation(project(":client-testing"))

// ****************************************
// For TESTING purposes only. We only want Misk for easy testing.
// DO NOT turn these into regular dependencies.
// ****************************************
testImplementation(libs.misk)
testImplementation(libs.miskActions)
testImplementation(libs.miskInject)
testImplementation(libs.miskJdbc)
testImplementation(testFixtures(libs.miskJdbc))
testImplementation(libs.miskTesting)
testImplementation(project(":client-misk"))
}

tasks.withType<KotlinCompile> {
dependsOn("spotlessKotlinApply")
kotlinOptions {
jvmTarget = "17"
}
}

tasks.withType<JavaCompile> {
sourceCompatibility = JavaVersion.VERSION_17.toString()
targetCompatibility = JavaVersion.VERSION_17.toString()
}

gradlePlugin {
plugins {
create("backfila-client-sqldelight") {
id = "app.cash.backfila.client.sqldelight"
displayName = "backfilaClientSqlDelight"
description = "Gradle plugin to generate sqldelight files for Backfila backfills."
implementationClass = "app.cash.backfila.client.sqldelight.plugin.BackfilaSqlDelightGradlePlugin"
}
}
}

tasks {
test {
// The test in 'src/test/projects/android' needs Java 17+.
javaLauncher.set(
project.javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
}
)
systemProperty("backfilaVersion", rootProject.findProperty("VERSION_NAME") ?: "0.0-SNAPSHOT")
dependsOn(":client:publishAllPublicationsToTestMavenRepository")
dependsOn(":client-base:publishAllPublicationsToTestMavenRepository")
dependsOn(":client-sqldelight:publishAllPublicationsToTestMavenRepository")
dependsOn(":client-sqldelight-gradle-plugin:publishAllPublicationsToTestMavenRepository")
}
}

buildConfig {
useKotlinOutput {
internalVisibility = true
topLevelConstants = true
}

packageName("app.cash.backfila.client.sqldelight.plugin")
buildConfigField("String", "VERSION", "\"${project.version}\"")
}

configure<MavenPublishBaseExtension> {
configure(
GradlePlugin(
javadocJar = JavadocJar.Empty()
)
)
}
4 changes: 4 additions & 0 deletions client-sqldelight-gradle-plugin/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POM_ARTIFACT_ID=client-sqldelight-gradle-plugin
POM_NAME=client-sqldelight-gradle-plugin
POM_DESCRIPTION=sqldelight gradle plugin for backfila
POM_PACKAGING=jar
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package app.cash.backfila.client.sqldelight.plugin

import java.io.Serializable
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.provider.Property
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension

class BackfilaSqlDelightGradlePlugin : Plugin<Project> {
override fun apply(target: Project) {
val backfilaExtension = target.extensions.create("backfilaSqlDelight", BackfilaSqlDelightExtension::class.java)

backfilaExtension.backfills.all { backfill ->
check(!backfill.name.matches(Regex("\\s"))) { "Backfill `name` is not allowed to contain whitespace." }
val baseSqlDirectory = target.layout.buildDirectory.dir("backfilaSqlDelight/${backfill.name}/sql")
val baseKotlinDirectory = target.layout.buildDirectory.dir("backfilaSqlDelight/${backfill.name}/kotlin")
val packageProvider = backfill.backfill.map {
val fullDatabaseName = it.database
fullDatabaseName.substring(0, fullDatabaseName.lastIndexOf('.'))
}
val databaseClassNameProvider = backfill.backfill.map {
val fullDatabaseName = it.database
fullDatabaseName.substring(fullDatabaseName.lastIndexOf('.') + 1)
}

val sqlTask = target.tasks.register(
"generateBackfilaRecordSourceSql${backfill.name.replaceFirstChar { it.uppercase() }}",
GenerateBackfilaRecordSourceSqlTask::class.java,
) {
it.backfill.set(backfill.backfill)
it.sqlDirectory.set(baseSqlDirectory.map { baseDir -> baseDir.dir(packageProvider.get()) })
}

val sqlDelightExtension = target.extensions.findByType(app.cash.sqldelight.gradle.SqlDelightExtension::class.java)
check(sqlDelightExtension != null) {
"The Backfila gradle plugin requires the SqlDelight gradle plugin to function."
}

sqlDelightExtension.databases.all { sqlDelightDatabase ->
sqlDelightDatabase.srcDirs.from(
baseSqlDirectory.map<List<Directory>> { sqlDirectory ->
if (packageProvider.get() == sqlDelightDatabase.packageName.get() && databaseClassNameProvider.get() == sqlDelightDatabase.name) {
listOf(sqlDirectory)
} else {
listOf()
}
},
)
}

val kotlinTask = target.tasks.register(
"generateBackfilaRecordSourceQueries${backfill.name.replaceFirstChar { it.uppercase() }}",
GenerateBackfilaRecordSourceQueriesTask::class.java,
) {
it.dependsOn(sqlTask)
it.backfill.set(backfill.backfill)
it.packageName.set(packageProvider)
it.kotlinDirectory.set(baseKotlinDirectory.map { baseDir -> baseDir.dir(packageProvider.get()) })
}

target.kotlinExtension.sourceSets.getByName("main").kotlin.srcDir(kotlinTask)
target.dependencies.add("implementation", "app.cash.backfila:client-sqldelight:$VERSION")
}
}
}

abstract class BackfilaSqlDelightExtension {
abstract val backfills: NamedDomainObjectContainer<SqlDelightRecordSourceEntry>

fun addRecordSource(
name: String,
database: String,
tableName: String,
keyName: String,
keyType: String,
recordColumns: String,
recordType: String,
whereClause: String = "1 = 1",
) {
backfills.create(name) {
it.backfill.set(
SqlDelightRecordSource(
name = name,
database = database,
tableName = tableName,
keyName = keyName,
keyType = keyType,
recordColumns = recordColumns,
recordType = recordType,
whereClause = whereClause,
),
)
}
}
}

abstract class SqlDelightRecordSourceEntry(val name: String) {
abstract val backfill: Property<SqlDelightRecordSource>
}

data class SqlDelightRecordSource(
val name: String,
val database: String,
val tableName: String,
val keyName: String, // TODO: Eventually also support compound keys.
val keyType: String, // TODO: Get this information directly from SQLDelight
val recordColumns: String,
val recordType: String, // TODO: Get this information directly from SQLDelight
val whereClause: String,
) : Serializable
Loading

0 comments on commit a545275

Please sign in to comment.