Skip to content

Commit

Permalink
Add run configuration for sqlite
Browse files Browse the repository at this point in the history
  • Loading branch information
aperfilyev committed Jan 14, 2022
1 parent 806d8c6 commit 41f6396
Show file tree
Hide file tree
Showing 17 changed files with 573 additions and 7 deletions.
1 change: 1 addition & 0 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ ext.deps = [
],
sqlitePsi: "com.alecstrong:sqlite-psi-core:0.3.15",
sqliteJdbc: "org.xerial:sqlite-jdbc:3.34.0",
picnic: "com.jakewharton.picnic:picnic:0.5.0",
robolectric: 'org.robolectric:robolectric:4.7.3',
rxJava2: "io.reactivex.rxjava2:rxjava:2.2.5",
rxJava3: "io.reactivex.rxjava3:rxjava:3.1.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ abstract class BindableQuery(
/**
* The collection of parameters exposed in the generated api for this query.
*/
internal val parameters: List<IntermediateType> by lazy {
val parameters: List<IntermediateType> by lazy {
if (statement is SqlInsertStmt && statement.acceptsTableInterface()) {
val table = statement.table.tableName.parent as SqlCreateTableStmt
return@lazy listOf(
Expand All @@ -64,7 +64,7 @@ abstract class BindableQuery(
/**
* The collection of all bind expressions in this query.
*/
internal val arguments: List<Argument> by lazy {
val arguments: List<Argument> by lazy {
if (statement is SqlInsertStmt && statement.acceptsTableInterface()) {
return@lazy statement.columns.mapIndexed { index, column ->
Argument(
Expand Down Expand Up @@ -175,7 +175,7 @@ abstract class BindableQuery(
private val SqlBindParameter.identifier: SqlIdentifier?
get() = childOfType(SqlTypes.IDENTIFIER) as? SqlIdentifier

internal data class Argument(
data class Argument(
val index: Int,
val type: IntermediateType,
val bindArgs: MutableList<SqlBindExpr> = mutableListOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package app.cash.sqldelight.core.dialect.api
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.TypeName

internal interface DialectType {
interface DialectType {

val javaType: TypeName

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import com.squareup.kotlinpoet.asClassName
* Internal representation for a column type, which has SQLite data affinity as well as JVM class
* type.
*/
internal data class IntermediateType(
data class IntermediateType(
val dialectType: DialectType,
val javaType: TypeName = dialectType.javaType,
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ fun PsiElement.rawSqlText(
).second
}

internal val PsiElement.range: IntRange
val PsiElement.range: IntRange
get() = node.startOffset until (node.startOffset + node.textLength)

fun Collection<SqlDelightQueriesFile>.forInitializationStatements(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ class MultiModuleTests {

val runner = GradleRunner.create()
.withCommonConfiguration(fixtureRoot)
.withArguments("clean",":moduleA:check", "--stacktrace")
.withArguments("clean", ":moduleA:check", "--stacktrace")
.forwardOutput()
.withDebug(true)

Expand Down
1 change: 1 addition & 0 deletions sqldelight-idea-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ dependencies {
implementation(deps.bugsnag) {
exclude group: "org.slf4j"
}
implementation deps.picnic

testImplementation deps.truth
testImplementation project(':test-util')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package app.cash.sqldelight.intellij.run

import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.layout.panel
import javax.swing.JComponent

internal interface ArgumentsInputDialog {
val result: List<SqlParameter>

fun showAndGet(): Boolean

interface Factory {
fun create(project: Project, parameters: List<SqlParameter>): ArgumentsInputDialog
}
}

internal class ArgumentsInputDialogImpl(
project: Project,
private val parameters: List<SqlParameter>
) : DialogWrapper(project), ArgumentsInputDialog {

init {
init()
}

private val _result = mutableListOf<SqlParameter>()
override val result: List<SqlParameter> = _result

override fun createCenterPanel(): JComponent {
return panel {
parameters.forEach { parameter ->
row("${parameter.name}:") {
textField(parameter::value, {
_result.add(parameter.copy(value = it))
})
}
}
}
}
}

internal class ArgumentsInputDialogFactoryImpl : ArgumentsInputDialog.Factory {
override fun create(project: Project, parameters: List<SqlParameter>): ArgumentsInputDialog {
return ArgumentsInputDialogImpl(project, parameters)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package app.cash.sqldelight.intellij.run

import app.cash.sqldelight.intellij.util.dialectPreset
import app.cash.sqldelight.intellij.util.isSqlite
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import java.sql.Connection
import java.sql.DriverManager
import java.sql.SQLException

internal interface ConnectionManager {
fun getConnection(): Connection

companion object {
fun getInstance(project: Project): ConnectionManager {
return ServiceManager.getService(project, ConnectionManager::class.java)!!
}
}
}

internal class ConnectionManagerImpl(private val project: Project) : ConnectionManager {

private val connectionOptions = ConnectionOptions(project)

init {
Class.forName("org.sqlite.JDBC")
}

override fun getConnection(): Connection {
val dialect = project.dialectPreset
if (!dialect.isSqlite) {
throw SQLException("Unsupported dialect $dialect")
}

val connectionType = connectionOptions.connectionType
if (connectionType != ConnectionType.FILE) {
throw SQLException("Unsupported connection type $connectionType")
}

val filePath = connectionOptions.filePath
if (filePath.isEmpty()) {
throw SQLException("The file path is empty")
}

return DriverManager.getConnection("jdbc:sqlite:$filePath")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package app.cash.sqldelight.intellij.run

import app.cash.sqldelight.core.compiler.model.NamedMutator
import app.cash.sqldelight.core.compiler.model.NamedQuery
import app.cash.sqldelight.core.lang.SqlDelightFile
import app.cash.sqldelight.core.lang.SqlDelightFileType
import app.cash.sqldelight.core.lang.psi.StmtIdentifierMixin
import app.cash.sqldelight.core.lang.util.findChildOfType
import app.cash.sqldelight.core.lang.util.range
import app.cash.sqldelight.core.lang.util.rawSqlText
import app.cash.sqldelight.core.psi.SqlDelightStmtClojure
import app.cash.sqldelight.core.psi.SqlDelightStmtClojureStmtList
import com.alecstrong.sql.psi.core.psi.SqlStmt
import com.alecstrong.sql.psi.core.psi.SqlStmtList
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.annotations.VisibleForTesting

@VisibleForTesting
internal class RunSqlAction(
private val stmt: SqlStmt,
private val project: Project = stmt.project,
private val executor: SqlDelightStatementExecutor = SqlDelightStatementExecutor.getInstance(
project
),
private val dialogFactory: ArgumentsInputDialog.Factory = ArgumentsInputDialogFactoryImpl()
) : AnAction() {

override fun actionPerformed(e: AnActionEvent) {
val sql = stmt.rawSqlText().trim().replace("\\s+".toRegex(), " ")

val identifier = stmt.identifier
val parameters = identifier?.let { findParameters(stmt, it) } ?: emptyList()
val sqlStmt = if (parameters.isEmpty()) {
sql
} else {
val dialog = dialogFactory.create(project, parameters)
val ok = dialog.showAndGet()
if (!ok) return

bindParameters(sql, dialog.result) ?: return
}
executor.execute(sqlStmt)
}

private val SqlStmt.identifier: StmtIdentifierMixin?
get() {
return when (parent) {
is SqlStmtList -> prevVisibleSibling() as? StmtIdentifierMixin
is SqlDelightStmtClojureStmtList -> {
val stmtClojure = PsiTreeUtil.getParentOfType(this, SqlDelightStmtClojure::class.java)
stmtClojure?.stmtIdentifierClojure as? StmtIdentifierMixin
}
else -> null
}
}

private fun PsiElement.prevVisibleSibling(): PsiElement? {
return generateSequence(prevSibling) { it.prevSibling }
.firstOrNull { it !is PsiWhiteSpace && it !is PsiComment }
}

private fun findParameters(
sqlStmt: SqlStmt,
identifier: StmtIdentifierMixin
): List<SqlParameter> {
val bindableQuery = when {
sqlStmt.compoundSelectStmt != null -> NamedQuery(
identifier.name!!, sqlStmt.compoundSelectStmt!!, identifier
)
sqlStmt.deleteStmtLimited != null -> NamedMutator.Delete(
sqlStmt.deleteStmtLimited!!, identifier
)
sqlStmt.insertStmt != null -> NamedMutator.Insert(
sqlStmt.insertStmt!!, identifier
)
sqlStmt.updateStmtLimited != null -> NamedMutator.Update(
sqlStmt.updateStmtLimited!!, identifier
)
else -> null
} ?: return emptyList()
val offset = sqlStmt.textOffset
val argumentList: List<IntRange> = bindableQuery.arguments
.flatMap { it.bindArgs }
.map {
val textRange = it.range
IntRange(textRange.first - offset, textRange.last - offset)
}
val parameters: List<String> = bindableQuery.parameters
.map { it.name }
return argumentList.zip(parameters) { range, name ->
SqlParameter(
name = name,
range = range
)
}
}

private fun bindParameters(
sql: String,
parameters: List<SqlParameter>
): String? {
val replacements = parameters.mapNotNull { p ->
if (p.value.isEmpty()) {
return@mapNotNull null
}
p.range to "'${p.value}'"
}
if (replacements.isEmpty()) {
return null
}

val factory = PsiFileFactory.getInstance(project)
return runReadAction {
val dummyFile = factory.createFileFromText(
"_Dummy_.${SqlDelightFileType.EXTENSION}",
SqlDelightFileType,
sql
) as SqlDelightFile
val stmt = dummyFile.findChildOfType<SqlStmt>()
stmt?.rawSqlText(replacements)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package app.cash.sqldelight.intellij.run

import com.alecstrong.sql.psi.core.psi.SqlStmt
import com.alecstrong.sql.psi.core.psi.SqlVisitor
import com.intellij.icons.AllIcons
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.editor.markup.GutterIconRenderer
import javax.swing.Icon

internal class RunSqliteAnnotator(
private val holder: AnnotationHolder,
private val connectionOptions: ConnectionOptions,
) : SqlVisitor() {

override fun visitStmt(o: SqlStmt) {
if (connectionOptions.connectionType != ConnectionType.FILE) {
return
}

val filePath = connectionOptions.filePath
if (filePath.isEmpty()) {
return
}

holder.newAnnotation(HighlightSeverity.INFORMATION, "")
.gutterIconRenderer(RunSqliteStatementGutterIconRenderer(o))
.create()
}

private data class RunSqliteStatementGutterIconRenderer(
private val stmt: SqlStmt,
) : GutterIconRenderer() {
override fun getIcon(): Icon = AllIcons.RunConfigurations.TestState.Run

override fun getTooltipText(): String = "Run statement"

override fun getClickAction(): AnAction = RunSqlAction(stmt)
}
}
Loading

0 comments on commit 41f6396

Please sign in to comment.