Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use JGit to filter newly added/modified sql queries only for codegen #188

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ mainClassName = "norm.cli.NormCliKt"
dependencies {
implementation project(":codegen")
implementation "com.github.ajalt:clikt:2.8.0"
testImplementation "io.mockk:mockk:1.11.0"
implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '5.11.0.202103091610-r'
}

104 changes: 77 additions & 27 deletions cli/src/main/kotlin/norm/cli/NormCli.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import norm.api.NormApi
import norm.fs.IO
import norm.fs.globSearch
import norm.util.withPgConnection
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import java.io.File


Expand All @@ -25,49 +27,78 @@ fun main(args: Array<String>) = NormCli().main(args)
* Can use env variable to pass in sensitive information
*/
class NormCli : CliktCommand( // command name is inferred as norm-cli
name = "norm-codegen",
help = """
name = "norm-codegen",
help = """
Generates Kotlin Source files for given SQL files using the Postgres database connection
"""
) {

private val jdbcUrl by option("-j", "--jdbc-url", help = "JDBC connection URL (can use env var PG_JDBC_URL)", envvar = "PG_JDBC_URL")
.default("jdbc:postgresql://localhost/postgres")

private val username by option("-u", "--username", help = "Username (can use env var PG_USERNAME)", envvar = "PG_USERNAME")
.default("postgres")

private val password by option("-p", "--password", help = "Password (can use env var PG_PASSWORD)", envvar = "PG_PASSWORD")
.default("")

private val basePath by option("-b", "--base-path", help = " relative path from this dir will be used to infer package name")
.file(canBeFile = false, canBeDir = true, mustExist = true)
.default(File(".")) // Current working dir

private val inputFilesAsOpts by option("-f", "--file", help = "[Multiple] SQL files, the file path relative to base path (-b) will be used to infer package name")
.file(canBeFile = true, canBeDir = false, mustExist = true)
.multiple()
.unique()
private val jdbcUrl by option(
"-j",
"--jdbc-url",
help = "JDBC connection URL (can use env var PG_JDBC_URL)",
envvar = "PG_JDBC_URL"
)
.default("jdbc:postgresql://localhost/postgres")

private val username by option(
"-u",
"--username",
help = "Username (can use env var PG_USERNAME)",
envvar = "PG_USERNAME"
)
.default("postgres")

private val password by option(
"-p",
"--password",
help = "Password (can use env var PG_PASSWORD)",
envvar = "PG_PASSWORD"
)
.default("")

private val basePath by option(
"-b",
"--base-path",
help = " relative path from this dir will be used to infer package name"
)
.file(canBeFile = false, canBeDir = true, mustExist = true)
.default(File(".")) // Current working dir

private val inputFilesAsOpts by option(
"-f",
"--file",
help = "[Multiple] SQL files, the file path relative to base path (-b) will be used to infer package name"
)
.file(canBeFile = true, canBeDir = false, mustExist = true)
.multiple()
.unique()

private val sqlFiles by argument() // give meaningful name for CLI help message
.file(canBeFile = true, canBeDir = false, mustExist = true)
.multiple()
.unique()
.file(canBeFile = true, canBeDir = false, mustExist = true)
.multiple()
.unique()

private val inputDir by option("-d", "--in-dir", help = "Dir containing .sql files, relative path from this dir will be used to infer package name")
.file(canBeFile = false, canBeDir = true, mustExist = true)
private val inputDir by option(
"-d",
"--in-dir",
help = "Dir containing .sql files, relative path from this dir will be used to infer package name"
)
.file(canBeFile = false, canBeDir = true, mustExist = true)

private val outDir by option("-o", "--out-dir", help = "Output dir where source should be generated")
.file(canBeFile = false, canBeDir = true, mustExist = true)
.required()
.file(canBeFile = false, canBeDir = true, mustExist = true)
.required()


override fun run() = withPgConnection(jdbcUrl, username, password) { connection ->
val normApi = NormApi(connection)

// If dir is provided, relativize to itself
inputDir?.let { dir ->
globSearch(dir, "**.sql").forEach { sqlFile ->
val fileList = globSearch(dir, "**.sql")
val modifiedFiles = modifiedFilesFromGit(dir,fileList)
modifiedFiles.forEach { sqlFile ->
IO(sqlFile, dir, outDir).process(normApi::generate)
}
}
Expand All @@ -77,6 +108,25 @@ class NormCli : CliktCommand( // command name is inferred as norm-cli
IO(sqlFile, basePath, outDir).process(normApi::generate)
}
}

private fun modifiedFilesFromGit(directory: File, fileList: Sequence<File>): List<File> {

val repo = FileRepositoryBuilder()
.setGitDir(File(directory.parent + "/.git"))
.setMustExist(false)
.build()
val git = Git(repo)
return when {
repo.objectDatabase.exists() -> {
return when {
git.status().call().untracked.isNotEmpty() -> git.status().call().untracked
git.status().call().modified.isNotEmpty() -> git.status().call().modified
else -> git.diff().call().map { diffEntry -> diffEntry.newPath }
}.map { File(directory.parent+"/"+it) }.filter { it.name.endsWith("sql") }
}
else -> fileList.toList()
}
}
}


22 changes: 22 additions & 0 deletions cli/src/test/kotlin/norm/cli/NormCliTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.contain
import io.kotest.matchers.string.startWith
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkConstructor
import norm.test.utils.PgContainer
import norm.test.utils.toArgs
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.internal.storage.file.FileRepository
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import java.io.File

class NormCliTest : StringSpec() {
Expand Down Expand Up @@ -74,5 +80,21 @@ class NormCliTest : StringSpec() {
exception.message should startWith("Invalid value for \"-f\":")
exception.message should contain("is a directory")
}
"Should generate kotlin file for untracked file in git"{

val repo = mockk<FileRepository>(relaxed = true)
mockkConstructor(FileRepositoryBuilder::class)
mockkConstructor(Git::class)
every { anyConstructed<FileRepositoryBuilder>().setGitDir(any()).setMustExist(any()).build() } returns repo
every { repo.objectDatabase.exists() } returns true
every { anyConstructed<Git>().status().call().untracked } returns setOf("sql/employees/add-new-employee.sql")
every { anyConstructed<Git>().status().call().modified } returns emptySet()
every { anyConstructed<Git>().diff().call() } returns emptyList()

val args = toArgs("-d src/test/resources/gitrepository/sql -b src/test/resources/gitrepository/sql -o $outputDir ${pgStr()}")
NormCli().parse(args)
File("$outputDir/employees/AddNewEmployee.kt").exists() shouldBe true

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INSERT INTO employees(first_name, last_name)
VALUES (:firstName, :lastName)