Skip to content

Commit 41f6396

Browse files
committed
Add run configuration for sqlite
1 parent 806d8c6 commit 41f6396

File tree

17 files changed

+573
-7
lines changed

17 files changed

+573
-7
lines changed

gradle/dependencies.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ ext.deps = [
7777
],
7878
sqlitePsi: "com.alecstrong:sqlite-psi-core:0.3.15",
7979
sqliteJdbc: "org.xerial:sqlite-jdbc:3.34.0",
80+
picnic: "com.jakewharton.picnic:picnic:0.5.0",
8081
robolectric: 'org.robolectric:robolectric:4.7.3',
8182
rxJava2: "io.reactivex.rxjava2:rxjava:2.2.5",
8283
rxJava3: "io.reactivex.rxjava3:rxjava:3.1.2",

sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/model/BindableQuery.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ abstract class BindableQuery(
4747
/**
4848
* The collection of parameters exposed in the generated api for this query.
4949
*/
50-
internal val parameters: List<IntermediateType> by lazy {
50+
val parameters: List<IntermediateType> by lazy {
5151
if (statement is SqlInsertStmt && statement.acceptsTableInterface()) {
5252
val table = statement.table.tableName.parent as SqlCreateTableStmt
5353
return@lazy listOf(
@@ -64,7 +64,7 @@ abstract class BindableQuery(
6464
/**
6565
* The collection of all bind expressions in this query.
6666
*/
67-
internal val arguments: List<Argument> by lazy {
67+
val arguments: List<Argument> by lazy {
6868
if (statement is SqlInsertStmt && statement.acceptsTableInterface()) {
6969
return@lazy statement.columns.mapIndexed { index, column ->
7070
Argument(
@@ -175,7 +175,7 @@ abstract class BindableQuery(
175175
private val SqlBindParameter.identifier: SqlIdentifier?
176176
get() = childOfType(SqlTypes.IDENTIFIER) as? SqlIdentifier
177177

178-
internal data class Argument(
178+
data class Argument(
179179
val index: Int,
180180
val type: IntermediateType,
181181
val bindArgs: MutableList<SqlBindExpr> = mutableListOf()

sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/dialect/api/DialectType.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package app.cash.sqldelight.core.dialect.api
33
import com.squareup.kotlinpoet.CodeBlock
44
import com.squareup.kotlinpoet.TypeName
55

6-
internal interface DialectType {
6+
interface DialectType {
77

88
val javaType: TypeName
99

sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/IntermediateType.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import com.squareup.kotlinpoet.asClassName
3737
* Internal representation for a column type, which has SQLite data affinity as well as JVM class
3838
* type.
3939
*/
40-
internal data class IntermediateType(
40+
data class IntermediateType(
4141
val dialectType: DialectType,
4242
val javaType: TypeName = dialectType.javaType,
4343
/**

sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/util/TreeUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ fun PsiElement.rawSqlText(
159159
).second
160160
}
161161

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

165165
fun Collection<SqlDelightQueriesFile>.forInitializationStatements(

sqldelight-gradle-plugin/src/test/kotlin/app/cash/sqldelight/properties/MultiModuleTests.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ class MultiModuleTests {
190190

191191
val runner = GradleRunner.create()
192192
.withCommonConfiguration(fixtureRoot)
193-
.withArguments("clean",":moduleA:check", "--stacktrace")
193+
.withArguments("clean", ":moduleA:check", "--stacktrace")
194194
.forwardOutput()
195195
.withDebug(true)
196196

sqldelight-idea-plugin/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ dependencies {
8181
implementation(deps.bugsnag) {
8282
exclude group: "org.slf4j"
8383
}
84+
implementation deps.picnic
8485

8586
testImplementation deps.truth
8687
testImplementation project(':test-util')
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package app.cash.sqldelight.intellij.run
2+
3+
import com.intellij.openapi.project.Project
4+
import com.intellij.openapi.ui.DialogWrapper
5+
import com.intellij.ui.layout.panel
6+
import javax.swing.JComponent
7+
8+
internal interface ArgumentsInputDialog {
9+
val result: List<SqlParameter>
10+
11+
fun showAndGet(): Boolean
12+
13+
interface Factory {
14+
fun create(project: Project, parameters: List<SqlParameter>): ArgumentsInputDialog
15+
}
16+
}
17+
18+
internal class ArgumentsInputDialogImpl(
19+
project: Project,
20+
private val parameters: List<SqlParameter>
21+
) : DialogWrapper(project), ArgumentsInputDialog {
22+
23+
init {
24+
init()
25+
}
26+
27+
private val _result = mutableListOf<SqlParameter>()
28+
override val result: List<SqlParameter> = _result
29+
30+
override fun createCenterPanel(): JComponent {
31+
return panel {
32+
parameters.forEach { parameter ->
33+
row("${parameter.name}:") {
34+
textField(parameter::value, {
35+
_result.add(parameter.copy(value = it))
36+
})
37+
}
38+
}
39+
}
40+
}
41+
}
42+
43+
internal class ArgumentsInputDialogFactoryImpl : ArgumentsInputDialog.Factory {
44+
override fun create(project: Project, parameters: List<SqlParameter>): ArgumentsInputDialog {
45+
return ArgumentsInputDialogImpl(project, parameters)
46+
}
47+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package app.cash.sqldelight.intellij.run
2+
3+
import app.cash.sqldelight.intellij.util.dialectPreset
4+
import app.cash.sqldelight.intellij.util.isSqlite
5+
import com.intellij.openapi.components.ServiceManager
6+
import com.intellij.openapi.project.Project
7+
import java.sql.Connection
8+
import java.sql.DriverManager
9+
import java.sql.SQLException
10+
11+
internal interface ConnectionManager {
12+
fun getConnection(): Connection
13+
14+
companion object {
15+
fun getInstance(project: Project): ConnectionManager {
16+
return ServiceManager.getService(project, ConnectionManager::class.java)!!
17+
}
18+
}
19+
}
20+
21+
internal class ConnectionManagerImpl(private val project: Project) : ConnectionManager {
22+
23+
private val connectionOptions = ConnectionOptions(project)
24+
25+
init {
26+
Class.forName("org.sqlite.JDBC")
27+
}
28+
29+
override fun getConnection(): Connection {
30+
val dialect = project.dialectPreset
31+
if (!dialect.isSqlite) {
32+
throw SQLException("Unsupported dialect $dialect")
33+
}
34+
35+
val connectionType = connectionOptions.connectionType
36+
if (connectionType != ConnectionType.FILE) {
37+
throw SQLException("Unsupported connection type $connectionType")
38+
}
39+
40+
val filePath = connectionOptions.filePath
41+
if (filePath.isEmpty()) {
42+
throw SQLException("The file path is empty")
43+
}
44+
45+
return DriverManager.getConnection("jdbc:sqlite:$filePath")
46+
}
47+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package app.cash.sqldelight.intellij.run
2+
3+
import app.cash.sqldelight.core.compiler.model.NamedMutator
4+
import app.cash.sqldelight.core.compiler.model.NamedQuery
5+
import app.cash.sqldelight.core.lang.SqlDelightFile
6+
import app.cash.sqldelight.core.lang.SqlDelightFileType
7+
import app.cash.sqldelight.core.lang.psi.StmtIdentifierMixin
8+
import app.cash.sqldelight.core.lang.util.findChildOfType
9+
import app.cash.sqldelight.core.lang.util.range
10+
import app.cash.sqldelight.core.lang.util.rawSqlText
11+
import app.cash.sqldelight.core.psi.SqlDelightStmtClojure
12+
import app.cash.sqldelight.core.psi.SqlDelightStmtClojureStmtList
13+
import com.alecstrong.sql.psi.core.psi.SqlStmt
14+
import com.alecstrong.sql.psi.core.psi.SqlStmtList
15+
import com.intellij.openapi.actionSystem.AnAction
16+
import com.intellij.openapi.actionSystem.AnActionEvent
17+
import com.intellij.openapi.application.runReadAction
18+
import com.intellij.openapi.project.Project
19+
import com.intellij.psi.PsiComment
20+
import com.intellij.psi.PsiElement
21+
import com.intellij.psi.PsiFileFactory
22+
import com.intellij.psi.PsiWhiteSpace
23+
import com.intellij.psi.util.PsiTreeUtil
24+
import org.jetbrains.annotations.VisibleForTesting
25+
26+
@VisibleForTesting
27+
internal class RunSqlAction(
28+
private val stmt: SqlStmt,
29+
private val project: Project = stmt.project,
30+
private val executor: SqlDelightStatementExecutor = SqlDelightStatementExecutor.getInstance(
31+
project
32+
),
33+
private val dialogFactory: ArgumentsInputDialog.Factory = ArgumentsInputDialogFactoryImpl()
34+
) : AnAction() {
35+
36+
override fun actionPerformed(e: AnActionEvent) {
37+
val sql = stmt.rawSqlText().trim().replace("\\s+".toRegex(), " ")
38+
39+
val identifier = stmt.identifier
40+
val parameters = identifier?.let { findParameters(stmt, it) } ?: emptyList()
41+
val sqlStmt = if (parameters.isEmpty()) {
42+
sql
43+
} else {
44+
val dialog = dialogFactory.create(project, parameters)
45+
val ok = dialog.showAndGet()
46+
if (!ok) return
47+
48+
bindParameters(sql, dialog.result) ?: return
49+
}
50+
executor.execute(sqlStmt)
51+
}
52+
53+
private val SqlStmt.identifier: StmtIdentifierMixin?
54+
get() {
55+
return when (parent) {
56+
is SqlStmtList -> prevVisibleSibling() as? StmtIdentifierMixin
57+
is SqlDelightStmtClojureStmtList -> {
58+
val stmtClojure = PsiTreeUtil.getParentOfType(this, SqlDelightStmtClojure::class.java)
59+
stmtClojure?.stmtIdentifierClojure as? StmtIdentifierMixin
60+
}
61+
else -> null
62+
}
63+
}
64+
65+
private fun PsiElement.prevVisibleSibling(): PsiElement? {
66+
return generateSequence(prevSibling) { it.prevSibling }
67+
.firstOrNull { it !is PsiWhiteSpace && it !is PsiComment }
68+
}
69+
70+
private fun findParameters(
71+
sqlStmt: SqlStmt,
72+
identifier: StmtIdentifierMixin
73+
): List<SqlParameter> {
74+
val bindableQuery = when {
75+
sqlStmt.compoundSelectStmt != null -> NamedQuery(
76+
identifier.name!!, sqlStmt.compoundSelectStmt!!, identifier
77+
)
78+
sqlStmt.deleteStmtLimited != null -> NamedMutator.Delete(
79+
sqlStmt.deleteStmtLimited!!, identifier
80+
)
81+
sqlStmt.insertStmt != null -> NamedMutator.Insert(
82+
sqlStmt.insertStmt!!, identifier
83+
)
84+
sqlStmt.updateStmtLimited != null -> NamedMutator.Update(
85+
sqlStmt.updateStmtLimited!!, identifier
86+
)
87+
else -> null
88+
} ?: return emptyList()
89+
val offset = sqlStmt.textOffset
90+
val argumentList: List<IntRange> = bindableQuery.arguments
91+
.flatMap { it.bindArgs }
92+
.map {
93+
val textRange = it.range
94+
IntRange(textRange.first - offset, textRange.last - offset)
95+
}
96+
val parameters: List<String> = bindableQuery.parameters
97+
.map { it.name }
98+
return argumentList.zip(parameters) { range, name ->
99+
SqlParameter(
100+
name = name,
101+
range = range
102+
)
103+
}
104+
}
105+
106+
private fun bindParameters(
107+
sql: String,
108+
parameters: List<SqlParameter>
109+
): String? {
110+
val replacements = parameters.mapNotNull { p ->
111+
if (p.value.isEmpty()) {
112+
return@mapNotNull null
113+
}
114+
p.range to "'${p.value}'"
115+
}
116+
if (replacements.isEmpty()) {
117+
return null
118+
}
119+
120+
val factory = PsiFileFactory.getInstance(project)
121+
return runReadAction {
122+
val dummyFile = factory.createFileFromText(
123+
"_Dummy_.${SqlDelightFileType.EXTENSION}",
124+
SqlDelightFileType,
125+
sql
126+
) as SqlDelightFile
127+
val stmt = dummyFile.findChildOfType<SqlStmt>()
128+
stmt?.rawSqlText(replacements)
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)