├── src
├── main
│ ├── resources
│ │ └── sample-db
│ │ │ └── V0_0_0
│ │ │ ├── tables
│ │ │ └── sample.tbl.sql
│ │ │ ├── triggers
│ │ │ └── sample.trg.sql
│ │ │ ├── views
│ │ │ └── sample.view.sql
│ │ │ ├── migrations
│ │ │ ├── 0.sample.up.sql
│ │ │ └── 0.sample.down.sql
│ │ │ ├── procedures
│ │ │ └── sample.prc.sql
│ │ │ └── functions
│ │ │ └── sample.udf.sql
│ └── kotlin
│ │ └── com
│ │ └── vladsch
│ │ └── kotlin
│ │ └── jdbc
│ │ ├── InternalModelPropertyProvider.kt
│ │ ├── DbEntityFixer.kt
│ │ ├── Transaction.kt
│ │ ├── PropertyType.kt
│ │ ├── SqlQuery.kt
│ │ ├── DbEntityExtractor.kt
│ │ ├── ModelNoData.kt
│ │ ├── ModelPropertyProvider.kt
│ │ ├── Connection.kt
│ │ ├── TransactionImpl.kt
│ │ ├── Rows.kt
│ │ ├── DbEntityCaseFixer.kt
│ │ ├── HikariCP.kt
│ │ ├── Session.kt
│ │ ├── SqlCall.kt
│ │ ├── PropHelpers.kt
│ │ ├── MySqlEntityExtractor.kt
│ │ ├── ModelPropertyProviderBase.kt
│ │ ├── DbVersion.kt
│ │ ├── MigrationSession.kt
│ │ ├── Model.kt
│ │ ├── SqlQueryBase.kt
│ │ ├── SqlCallResults.kt
│ │ ├── DbEntity.kt
│ │ └── SessionImpl.kt
└── test
│ ├── resources
│ └── logback.xml
│ └── kotlin
│ └── com
│ └── vladsch
│ └── kotlin
│ └── jdbc
│ ├── ModelJsonTest.kt
│ ├── test-package.kt
│ ├── NamedParamTest.kt
│ └── UsageTest.kt
├── assets
└── images
│ ├── Language-Injections.png
│ ├── Scripted_Extensions_Data_Extractors.png
│ ├── Scripted_Extensions_Goto_Script_Dir.png
│ └── Scripted_Extensions_Generate_Kotlin-Model.png
├── .idea
├── markdown-navigator
│ ├── profiles_settings.xml
│ └── COPY_HTML_MIME.xml
├── sbt.xml
├── encodings.xml
├── vcs.xml
├── sqldialects.xml
├── misc.xml
├── compiler.xml
├── markdown-history.xml
├── markdown-navigator.xml
└── inspectionProfiles
│ └── Project_Default.xml
├── .travis.yml
├── .gitignore
├── LICENSE
├── extensions
└── com.intellij.database
│ ├── data
│ └── extractors
│ │ ├── Markdown-Table.md.js
│ │ ├── Kotlin-Enum.js
│ │ ├── Scala-Object-Enum.js
│ │ └── JavaScript-Enumerated-Value-Type.js
│ └── schema
│ └── Generate Kotlin-Model.groovy
├── kotlin-jdbc.iml
└── pom.xml
/src/main/resources/sample-db/V0_0_0/tables/sample.tbl.sql:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/sample-db/V0_0_0/triggers/sample.trg.sql:
--------------------------------------------------------------------------------
1 | CREATE TRIGGER __NAME__
2 |
--------------------------------------------------------------------------------
/src/main/resources/sample-db/V0_0_0/views/sample.view.sql:
--------------------------------------------------------------------------------
1 | CREATE VIEW __NAME_ AS
2 |
--------------------------------------------------------------------------------
/src/main/resources/sample-db/V0_0_0/migrations/0.sample.up.sql:
--------------------------------------------------------------------------------
1 | -- Version: __VERSION__
2 | -- Up Migration: __TITLE__
3 |
--------------------------------------------------------------------------------
/src/main/resources/sample-db/V0_0_0/procedures/sample.prc.sql:
--------------------------------------------------------------------------------
1 | CREATE PROCEDURE __NAME__()
2 | BEGIN
3 |
4 | END
5 |
--------------------------------------------------------------------------------
/assets/images/Language-Injections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vsch/kotlin-jdbc/HEAD/assets/images/Language-Injections.png
--------------------------------------------------------------------------------
/src/main/resources/sample-db/V0_0_0/migrations/0.sample.down.sql:
--------------------------------------------------------------------------------
1 | -- Version: __VERSION__
2 | -- Down Migration: __TITLE__
3 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/images/Scripted_Extensions_Data_Extractors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vsch/kotlin-jdbc/HEAD/assets/images/Scripted_Extensions_Data_Extractors.png
--------------------------------------------------------------------------------
/assets/images/Scripted_Extensions_Goto_Script_Dir.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vsch/kotlin-jdbc/HEAD/assets/images/Scripted_Extensions_Goto_Script_Dir.png
--------------------------------------------------------------------------------
/assets/images/Scripted_Extensions_Generate_Kotlin-Model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vsch/kotlin-jdbc/HEAD/assets/images/Scripted_Extensions_Generate_Kotlin-Model.png
--------------------------------------------------------------------------------
/.idea/sbt.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main/resources/sample-db/V0_0_0/functions/sample.udf.sql:
--------------------------------------------------------------------------------
1 | create function __NAME__( input varchar (1024)) returns varchar (1024)
2 | NO SQL
3 | BEGIN
4 | RETURN input;
5 | END
6 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: precise
2 | language: java
3 | sudo: required
4 | install: true
5 |
6 | jdk:
7 | - oraclejdk8
8 |
9 | env:
10 | - TEST=kotlin
11 |
12 | script:
13 | - 'if [[ $TEST = kotlin ]]; then mvn test -Dsurefire.useFile=false; fi'
14 |
15 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/InternalModelPropertyProvider.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | internal interface InternalModelPropertyProvider:ModelPropertyProvider {
4 | val columnName:String?
5 | val defaultValue: Any?
6 | }
7 |
--------------------------------------------------------------------------------
/.idea/sqldialects.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/DbEntityFixer.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | interface DbEntityFixer {
4 | object NULL : DbEntityFixer {
5 | override fun cleanScript(createScript: String): String {
6 | return createScript
7 | }
8 | }
9 |
10 | fun cleanScript(createScript: String): String
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %date %level %logger{32} %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/vladsch/kotlin/jdbc/ModelJsonTest.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import org.junit.Rule
4 | import org.junit.Test
5 | import org.junit.rules.ExpectedException
6 | import kotlin.test.assertEquals
7 |
8 | // TODO: add json conversion tests
9 | class ModelJsonTest {
10 | @Rule
11 | @JvmField
12 | var thrown = ExpectedException.none()
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/Transaction.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.sql.Savepoint
4 |
5 | interface Transaction: Session {
6 | fun commit()
7 | fun begin()
8 | fun rollback()
9 | fun setSavepoint(): Savepoint
10 | fun setSavepoint(name: String): Savepoint
11 | fun rollback(savepoint: Savepoint)
12 | fun releaseSavepoint(savepoint: Savepoint)
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/PropertyType.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | enum class PropertyType(val isKey: Boolean, val isAuto: Boolean, val isDefault: Boolean) {
4 | PROPERTY(false, false, false),
5 | KEY(true, false, false),
6 | AUTO(false, true, false),
7 | AUTO_KEY(true, true, false),
8 | DEFAULT(false, false, true),
9 | ;
10 |
11 | val isAutoKey get() = isAuto && isKey
12 | }
13 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/vladsch/kotlin/jdbc/test-package.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | fun describe(description: String, tests: () -> Unit) {
4 | println(description)
5 |
6 | tests()
7 | }
8 |
9 | fun withQueries(vararg stmts: String, assertions: (SqlQuery) -> Unit) {
10 | stmts.forEach {
11 | val query = sqlQuery(it)
12 |
13 | assertions(query)
14 | }
15 | }
16 |
17 | fun String.normalizeSpaces(): String {
18 | return Regex("[ \\t]+").replace(this, " ")
19 | }
20 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/SqlQuery.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.sql.PreparedStatement
4 |
5 | class SqlQuery(
6 | statement: String,
7 | params: List = listOf(),
8 | namedParams: Map = mapOf()
9 |
10 | ) : SqlQueryBase(statement, params, namedParams) {
11 |
12 | override fun toString(): String {
13 | return "SqlQuery(statement='$statement', params=${params.map { it.value }}, namedParams=${namedParams.map { it.key to it.value.value }}, replacementMap=$replacementMap, cleanStatement='$cleanStatement')"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/DbEntityExtractor.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | interface DbEntityExtractor {
4 | fun getExtractEntityNameRegEx(entity: DbEntity): Regex?
5 | fun getDropEntitySql(entityType: String, entityName: String): String
6 | fun getShowEntitySql(entity: DbEntity, entityName: String): String
7 | fun getListEntitiesSql(entity: DbEntity, schema: String): String
8 | fun entityScriptFixer(entity: DbEntity, session: Session): DbEntityFixer
9 | fun getDbEntities(dbEntity: DbEntity, session: Session): List
10 | fun getEntityQuery(dbEntity: DbEntity, session: Session): SqlQuery
11 | fun getDropEntitySql(entity: DbEntity, entityName: String): String
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/ModelNoData.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.lang.IllegalStateException
4 |
5 | @Suppress("MemberVisibilityCanBePrivate", "UNCHECKED_CAST", "PropertyName")
6 | abstract class ModelNoData>(session: Session?, sqlTable: String, dbCase: Boolean, allowSetAuto: Boolean = true, quote: String? = null) :
7 | Model(session, sqlTable, dbCase, allowSetAuto, quote) {
8 | data class DummyData(val dummy: Int)
9 |
10 | // create a data of this
11 | override fun toData(): DummyData = throw IllegalStateException("ModelNoData derived ${this::class.simpleName} for ${_db.tableName} should not try to convert to a non-existent data class")
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/ModelPropertyProvider.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import kotlin.reflect.KProperty
4 |
5 | interface ModelPropertyProvider {
6 | val autoKey: ModelPropertyProvider
7 | val key: ModelPropertyProvider
8 | val auto: ModelPropertyProvider
9 | val default: ModelPropertyProvider
10 | fun column(columnName: String?):ModelPropertyProvider
11 | fun default(value:Any? = null):ModelPropertyProvider
12 |
13 | operator fun provideDelegate(thisRef: T, prop: KProperty<*>): ModelPropertyProvider
14 |
15 | operator fun getValue(thisRef: T, property: KProperty<*>): V
16 | operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Kotlin template
3 | # Compiled class file
4 | *.class
5 |
6 | # Log file
7 | *.log
8 |
9 | # BlueJ files
10 | *.ctxt
11 |
12 | # Mobile Tools for Java (J2ME)
13 | .mtj.tmp/
14 |
15 | # Package Files #
16 | *.jar
17 | *.war
18 | *.nar
19 | *.ear
20 | *.zip
21 | *.tar.gz
22 | *.rar
23 |
24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
25 | hs_err_pid*
26 | ### Maven template
27 | target/
28 | pom.xml.tag
29 | pom.xml.releaseBackup
30 | pom.xml.versionsBackup
31 | pom.xml.next
32 | release.properties
33 | dependency-reduced-pom.xml
34 | buildNumber.properties
35 | .mvn/timing.properties
36 |
37 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
38 | !/.mvn/wrapper/maven-wrapper.jar
39 |
40 | /.idea/workspace.xml
41 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/Connection.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.sql.SQLException
4 |
5 | data class Connection(
6 | val underlying: java.sql.Connection,
7 | val driverName: String = "",
8 | val jtaCompatible: Boolean = false
9 | ) : java.sql.Connection by underlying {
10 |
11 | fun begin() {
12 | underlying.autoCommit = false
13 | if (!jtaCompatible) {
14 | underlying.isReadOnly = false
15 | }
16 | }
17 |
18 | override fun commit() {
19 | underlying.commit()
20 | underlying.autoCommit = true
21 | }
22 |
23 | override fun rollback() {
24 | underlying.rollback()
25 | try {
26 | underlying.autoCommit = true
27 | } catch (e: SQLException) {
28 |
29 | }
30 | }
31 |
32 | override fun close() {
33 | underlying.close()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/TransactionImpl.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.sql.Savepoint
4 |
5 | class TransactionImpl(
6 | connection: Connection,
7 | autoGeneratedKeys: List = listOf()
8 | ) : SessionImpl(connection, autoGeneratedKeys), Transaction {
9 |
10 | override fun commit() {
11 | connection.commit()
12 | }
13 |
14 | override fun begin() {
15 | connection.begin()
16 | }
17 |
18 | override fun rollback() {
19 | connection.rollback()
20 | }
21 |
22 | override fun setSavepoint(): Savepoint {
23 | return connection.setSavepoint()
24 | }
25 |
26 | override fun setSavepoint(name: String): Savepoint {
27 | return connection.setSavepoint(name)
28 | }
29 |
30 | override fun rollback(savepoint: Savepoint) {
31 | connection.rollback(savepoint)
32 | }
33 |
34 | override fun releaseSavepoint(savepoint: Savepoint) {
35 | connection.releaseSavepoint(savepoint)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/Rows.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.sql.ResultSet
4 |
5 | class Rows(rs: ResultSet) : Row(rs), Sequence {
6 | private var movedNext = false
7 |
8 | private class RowIterator(val row:Rows):Iterator {
9 |
10 | override fun hasNext(): Boolean {
11 | return row.hasNext()
12 | }
13 |
14 | override fun next(): Row {
15 | return row.next();
16 | }
17 | }
18 |
19 | override fun iterator(): Iterator {
20 | return RowIterator(this);
21 | }
22 |
23 | fun hasNext(): Boolean {
24 | if (rs.isClosed || (rs.type != ResultSet.TYPE_FORWARD_ONLY && (rs.isLast || rs.isAfterLast))) return false
25 | if (movedNext) return true
26 |
27 | movedNext = rs.next()
28 | return movedNext
29 | }
30 |
31 | fun next(): Rows {
32 | if (movedNext) {
33 | movedNext = false
34 | return this
35 | }
36 |
37 | rs.next()
38 | return this;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2018 - Vladimir Schneider
4 | Copyright (c) 2015 - Kazuhiro Sera
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining
7 | a copy of this software and associated documentation files (the
8 | 'Software'), to deal in the Software without restriction, including
9 | without limitation the rights to use, copy, modify, merge, publish,
10 | distribute, sublicense, and/or sell copies of the Software, and to
11 | permit persons to whom the Software is furnished to do so, subject to
12 | the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/DbEntityCaseFixer.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | abstract class DbEntityCaseFixer(entities: List) : DbEntityFixer {
4 | val entityCaseMap = HashMap()
5 |
6 | val caseFixRegex: Regex by lazy {
7 | val sb = StringBuilder()
8 | var sep = ""
9 | addRegExPrefix(sb)
10 | sb.append("(")
11 | entities.forEach { entityName ->
12 | val lowerCase = entityName.toLowerCase()
13 | if (lowerCase != entityName) {
14 | entityCaseMap.put(lowerCase, entityName)
15 | sb.append(sep)
16 | sep = "|"
17 | addEntityNameRegEx(sb, lowerCase)
18 | }
19 | }
20 | sb.append(")")
21 | addRegExSuffix(sb)
22 | sb.toString().toRegex(RegexOption.IGNORE_CASE)
23 | }
24 |
25 | open fun addRegExPrefix(sb: StringBuilder) {
26 | sb.append("`")
27 | }
28 |
29 | open fun addRegExSuffix(sb: StringBuilder) {
30 | sb.append("`")
31 | }
32 |
33 | open fun addEntityNameRegEx(sb: StringBuilder, entityName: String) {
34 | sb.append("\\Q").append(entityName).append("\\E")
35 | }
36 |
37 | override fun cleanScript(createScript: String): String {
38 | val fixedCaseSql = createScript.replace(caseFixRegex) { matchResult ->
39 | val tableName = matchResult.groupValues[1]
40 | val fixedCase = entityCaseMap[tableName.toLowerCase()]
41 | if (fixedCase != null) {
42 | matchResult.value.replace(tableName, fixedCase)
43 | } else matchResult.value
44 | }
45 | return fixedCaseSql
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.idea/markdown-history.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/HikariCP.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import com.zaxxer.hikari.HikariConfig
4 | import com.zaxxer.hikari.HikariDataSource
5 | import java.util.concurrent.ConcurrentHashMap
6 | import java.util.concurrent.ConcurrentMap
7 |
8 | object HikariCP {
9 | private val pools: ConcurrentMap = ConcurrentHashMap()
10 |
11 | fun default(url: String, username: String, password: String): HikariDataSource {
12 | return init("default", url, username, password)
13 | }
14 |
15 | fun custom(name: String, custom: HikariDataSource) {
16 | val existing: HikariDataSource? = pools[name]
17 | if (existing != null && existing.isClosed) {
18 | existing.close()
19 | }
20 | pools.put(name, custom)
21 | }
22 |
23 | fun defaultCustom(custom: HikariDataSource) = custom("default", custom)
24 |
25 | fun init(name: String, url: String, username: String, password: String): HikariDataSource {
26 | val config: HikariConfig = HikariConfig()
27 | config.jdbcUrl = url
28 | config.username = username
29 | config.password = password
30 | config.addDataSourceProperty("cachePrepStmts", "true")
31 | config.addDataSourceProperty("prepStmtCacheSize", "250")
32 | config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
33 | val existing: HikariDataSource? = pools[name]
34 | if (existing != null && existing.isClosed) {
35 | existing.close()
36 | }
37 | val ds = HikariDataSource(config)
38 | pools.put(name, ds)
39 | return ds
40 | }
41 |
42 | fun dataSource(name: String = "default"): HikariDataSource {
43 | val ds: HikariDataSource? = pools[name]
44 | if (ds != null && !ds.isClosed) {
45 | return ds
46 | } else {
47 | throw IllegalStateException("DataSource ($name) is absent.")
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/extensions/com.intellij.database/data/extractors/Markdown-Table.md.js:
--------------------------------------------------------------------------------
1 | /*
2 | Data extractor to Markdown Table
3 | */
4 |
5 | function eachWithIdx(iterable, f) {
6 | var i = iterable.iterator();
7 | var idx = 0;
8 | while (i.hasNext()) f(i.next(), idx++);
9 | }
10 |
11 | function mapEach(iterable, f) {
12 | var vs = [];
13 | eachWithIdx(iterable, function (i) {
14 | vs.push(f(i));
15 | });
16 | return vs;
17 | }
18 |
19 | function escape(str) {
20 | str = str.replaceAll("\t|\b|\\f", "");
21 | // str = com.intellij.openapi.util.text.StringUtil.escapeXml(str);
22 | str = str.replaceAll("\\r|\\n|\\r\\n", "");
23 | str = str.replaceAll("([\\[\\]\\|])", "\\$1");
24 | return str;
25 | }
26 |
27 | var NEWLINE = "\n";
28 |
29 | function output() {
30 | for (var i = 0; i < arguments.length; i++) {
31 | OUT.append(arguments[i]);
32 | }
33 | }
34 |
35 | function outputln() {
36 | for (var i = 0; i < arguments.length; i++) {
37 | OUT.append(arguments[i].toString());
38 | }
39 | OUT.append("\n");
40 | }
41 |
42 | function outputRow(items) {
43 | output("| ");
44 | for (var i = 0; i < items.length; i++) output(escape(items[i]), " |");
45 | output("", NEWLINE);
46 | }
47 |
48 | if (TRANSPOSED) {
49 | var values = mapEach(COLUMNS, function (col) {
50 | return [col.name()];
51 | });
52 | var heading = [];
53 | eachWithIdx(ROWS, function (row) {
54 | heading.push("---");
55 | eachWithIdx(COLUMNS, function (col, i) {
56 | values[i].push(FORMATTER.format(row, col));
57 | });
58 | });
59 | heading.push("---");
60 | outputRow(heading);
61 | eachWithIdx(COLUMNS, function (_, i) {
62 | outputRow(values[i]);
63 | });
64 | } else {
65 | outputRow(mapEach(COLUMNS, function (col) {
66 | return col.name();
67 | }));
68 | outputRow(mapEach(COLUMNS, function (col) {
69 | return "---";
70 | }));
71 | eachWithIdx(ROWS, function (row) {
72 | outputRow(mapEach(COLUMNS, function (col) {
73 | return FORMATTER.format(row, col);
74 | }));
75 | });
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/Session.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.sql.CallableStatement
4 | import java.sql.PreparedStatement
5 | import java.sql.ResultSet
6 | import javax.json.JsonArray
7 | import javax.json.JsonObject
8 |
9 | interface Session : AutoCloseable {
10 | val connection: Connection
11 | val autoGeneratedKeys: List
12 | val identifierQuoteString:String
13 |
14 | fun use(block: (Session) -> Unit)
15 |
16 | fun prepare(query: SqlQuery, returnGeneratedKeys: Boolean = false): PreparedStatement
17 | fun prepare(query: SqlCall): CallableStatement
18 | fun prepare(query: SqlQueryBase<*>): PreparedStatement
19 |
20 | fun query(query: SqlQueryBase<*>, consumer: (ResultSet) -> A): A
21 |
22 | fun execute(query: SqlQueryBase<*>, consumer: (PreparedStatement) -> A): A?
23 | fun execute(query: SqlCall, consumer: (CallableStatement) -> A): A?
24 |
25 | fun executeWithKeys(query: SqlQuery, consumer: (PreparedStatement) -> A): A?
26 | fun updateWithKeys(query: SqlQuery, consumer: (PreparedStatement) -> A): A?
27 | fun update(query: SqlQueryBase<*>, consumer: (PreparedStatement) -> A): A?
28 | fun list(query: SqlQueryBase<*>, extractor: (Row) -> A): List
29 | fun jsonArray(query: SqlQueryBase<*>, extractor: (Row) -> JsonObject): JsonArray
30 | fun count(query: SqlQueryBase<*>): Int
31 | fun first(query: SqlQueryBase<*>, extractor: (Row) -> A): A?
32 | fun hashMap(query: SqlQueryBase<*>, keyExtractor: (Row) -> K, extractor: (Row) -> A): Map
33 | fun jsonObject(query: SqlQueryBase<*>, keyExtractor: (Row) -> String, extractor: (Row) -> JsonObject): JsonObject
34 | fun forEach(query: SqlQueryBase<*>, operator: (Row) -> Unit)
35 |
36 | @Deprecated(message = "Use executeCall(query: SqlCall, stmtProc: (results: SqlCallResults) -> Unit) instead", replaceWith = ReplaceWith("executeCall"))
37 | fun forEach(query: SqlCall, stmtProc: (stmt: CallableStatement) -> Unit, operator: (rs: ResultSet, index: Int) -> Unit)
38 | fun executeCall(query: SqlCall, stmtProc: (results: SqlCallResults) -> Unit)
39 |
40 | fun execute(query: SqlQueryBase<*>): Boolean
41 | fun update(query: SqlQueryBase<*>): Int
42 | fun updateGetLongId(query: SqlQuery): Long?
43 | fun updateGetId(query: SqlQuery): Int?
44 | fun updateGetKey(query: SqlQuery, extractor: (Row) -> A): A?
45 | fun updateGetLongIds(query: SqlQuery): List?
46 | fun updateGetIds(query: SqlQuery): List?
47 | fun updateGetKeys(query: SqlQuery, extractor: (Row) -> A): List?
48 | fun transaction(operation: (Transaction) -> A): A
49 |
50 | override fun close()
51 | }
52 |
--------------------------------------------------------------------------------
/kotlin-jdbc.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/SqlCall.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.sql.CallableStatement
4 | import java.sql.PreparedStatement
5 |
6 | class SqlCall(
7 | statement: String,
8 | params: List = listOf(),
9 | namedParams: Map = mapOf()
10 | ) : SqlQueryBase(statement, params, namedParams) {
11 |
12 | @Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions")
13 | constructor(
14 | statement: String,
15 | params: List = listOf(),
16 | inputParams: Map = mapOf(),
17 | outputParams: Map = mapOf()
18 | ) : this(statement, params, inputParams.asParamMap(InOut.IN)) {
19 | params(outputParams.asParamMap(InOut.OUT))
20 | }
21 |
22 | override fun populateNamedParams(stmt: PreparedStatement) {
23 | super.populateNamedParams(stmt)
24 |
25 | stmt as CallableStatement? ?: return
26 |
27 | forEachNamedParam(InOut.OUT) { paramName, param, occurrences ->
28 | require(occurrences.size == 1) { "Output parameter $paramName should have exactly 1 occurrence, got ${occurrences.size}" }
29 | stmt.registerOutParameter(occurrences.first() + 1, param.sqlType())
30 | }
31 | }
32 |
33 | private fun outputParamIndices(stmt: CallableStatement): Map {
34 | val params = HashMap()
35 | forEachNamedParam(InOut.OUT) { paramName, _, occurrences ->
36 | require(occurrences.size == 1) { "Output parameter $paramName should have exactly 1 occurrence, got ${occurrences.size}" }
37 | params[paramName] = occurrences.first() + 1
38 | }
39 | return params
40 | }
41 |
42 | fun handleResults(stmt: CallableStatement, stmtProc: (results: SqlCallResults) -> Unit) {
43 | val results = stmt.execute();
44 | val outputParams = outputParamIndices(stmt as CallableStatement)
45 | val callableOutParams = SqlCallResults(stmt, results, outputParams)
46 | stmtProc.invoke(callableOutParams)
47 | }
48 |
49 | @Deprecated(message = "Use Session.executeCall which handles out parameters")
50 | fun outParamIndex(paramName: String): Int {
51 | require(outputParams.containsKey(paramName)) { "Parameter $paramName is not an outParam" }
52 | require(replacementMap[paramName]?.size == 1) { "Output parameter $paramName should have exactly 1 occurrence, got ${replacementMap[paramName]?.size}" }
53 | return replacementMap[paramName]?.first()!! + 1
54 | }
55 |
56 | @Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("params"))
57 | fun inOutParams(params: Map): SqlCall {
58 | return params(params.asParamMap(InOut.IN_OUT))
59 | }
60 |
61 | @Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("params"))
62 | fun inOutParams(vararg params: Pair): SqlCall {
63 | return params(params.asParamMap(InOut.IN_OUT))
64 | }
65 |
66 | @Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("params"))
67 | fun outParams(params: Map): SqlCall {
68 | return params(params.asParamMap(InOut.OUT))
69 | }
70 |
71 | @Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("params"))
72 | fun outParams(vararg params: Pair): SqlCall {
73 | return params(params.asParamMap(InOut.OUT))
74 | }
75 |
76 | override fun toString(): String {
77 | return "SqlCall(statement='$statement', params=${params.map { it.value }}, namedParams=${namedParams.map { it.key to it.value.value }}, replacementMap=$replacementMap, cleanStatement='$cleanStatement')"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/PropHelpers.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import com.vladsch.boxed.json.BoxedJsValue
4 | import java.math.BigDecimal
5 | import java.net.URL
6 | import kotlin.reflect.KProperty
7 |
8 | // TODO: convert this to using com.fasterxml.jackson if possible
9 | fun integralValue(modelName: String, prop: KProperty<*>, json: BoxedJsValue, min: Long, max: Long): Long? {
10 | if (json.isValid && json.isNumber && json.asJsNumber().isIntegral) {
11 | val value = json.asJsNumber().longValue()
12 | if (value < min || value > max) {
13 | val className = (prop.returnType.classifier as? Class<*>)?.simpleName ?: ""
14 | throw IllegalArgumentException("$modelName.${prop.name} of $value is out of legal range [$min, $max] for $className")
15 | }
16 | return value
17 | } else if (json.hadMissing() || json.hadNull()) {
18 | return null
19 | }
20 |
21 | val className = (prop.returnType.classifier as? Class<*>)?.simpleName ?: ""
22 | throw IllegalArgumentException("$modelName.${prop.name} cannot be set from json ${json.toString()}, type $className")
23 | }
24 |
25 | fun doubleValue(modelName: String, prop: KProperty<*>, json: BoxedJsValue, min: Double, max: Double): Double? {
26 | if (json.isValid && json.isNumber) {
27 | val value = json.asJsNumber().doubleValue()
28 | if (value < min || value > max) {
29 | val className = (prop.returnType.classifier as? Class<*>)?.simpleName ?: ""
30 | throw IllegalArgumentException("$modelName.${prop.name} of $value is out of legal range [$min, $max] for $className")
31 | }
32 | return value
33 | } else if (json.hadMissing() || json.hadNull()) {
34 | return null
35 | }
36 |
37 | val className = (prop.returnType.classifier as? Class<*>)?.simpleName ?: ""
38 | throw IllegalArgumentException("$modelName.${prop.name} cannot be set from json ${json.toString()}, type $className")
39 | }
40 |
41 | fun bigDecimalValue(modelName: String, prop: KProperty<*>, json: BoxedJsValue): BigDecimal? {
42 | if (json.isValid && json.isNumber) {
43 | return json.asJsNumber().bigDecimalValue()
44 | } else if (json.hadMissing() || json.hadNull()) {
45 | return null
46 | }
47 |
48 | val className = (prop.returnType.classifier as? Class<*>)?.simpleName ?: ""
49 | throw IllegalArgumentException("$modelName.${prop.name} cannot be set from json ${json.toString()}, type $className")
50 | }
51 |
52 | fun stringValue(modelName: String, prop: KProperty<*>, json: BoxedJsValue): String? {
53 | if (json.isValid && json.isLiteral) {
54 | if (json.isString) {
55 | return json.asJsString().string
56 | } else {
57 | // just use json toString, it has no quotes
58 | return json.toString()
59 | }
60 | } else if (json.hadMissing() || json.hadNull()) {
61 | return null
62 | }
63 |
64 | val className = (prop.returnType.classifier as? Class<*>)?.simpleName ?: ""
65 | throw IllegalArgumentException("$modelName.${prop.name} cannot be set from json ${json.toString()}, type $className")
66 | }
67 |
68 | fun booleanValue(modelName: String, prop: KProperty<*>, json: BoxedJsValue): Boolean? {
69 | if (json.isValid && (json.isTrue || json.isFalse)) {
70 | return json.isTrue
71 | } else if (json.hadMissing() || json.hadNull()) {
72 | return null
73 | }
74 |
75 | val className = (prop.returnType.classifier as? Class<*>)?.simpleName ?: ""
76 | throw IllegalArgumentException("$modelName.${prop.name} cannot be set from json ${json.toString()}, type $className")
77 | }
78 |
79 | fun booleanLikeValue(modelName: String, prop: KProperty<*>, json: BoxedJsValue): Boolean? {
80 | if (json.isValid) {
81 | if (json.isTrue || json.isFalse) {
82 | return json.isTrue
83 | } else if (json.isNumber) {
84 | val jsNumber = json.asJsNumber()
85 | if (jsNumber.isIntegral) {
86 | return jsNumber.longValue() != 0L
87 | } else {
88 | val doubleValue = jsNumber.doubleValue()
89 | return doubleValue.isFinite() && doubleValue != 0.0
90 | }
91 | } else if (json.isString) {
92 | val string = json.asJsString().string
93 | return string != "" && string != "0"
94 | }
95 | } else if (json.hadMissing() || json.hadNull()) {
96 | return null
97 | }
98 |
99 | val className = (prop.returnType.classifier as? Class<*>)?.simpleName ?: ""
100 | throw IllegalArgumentException("$modelName.${prop.name} cannot be set from json ${json.toString()}, type $className")
101 | }
102 |
103 | fun parsedString(modelName: String, prop: KProperty<*>, json: BoxedJsValue, parser: (String) -> T): T? {
104 | val value = stringValue(modelName, prop, json) ?: return null
105 | return parser.invoke(value)
106 | }
107 |
108 | fun urlString(modelName: String, prop: KProperty<*>, json: BoxedJsValue): URL? {
109 | val value = stringValue(modelName, prop, json) ?: return null
110 | return URL(value)
111 | }
112 |
113 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/MySqlEntityExtractor.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | object MySqlEntityExtractor : DbEntityExtractor {
4 | override fun getExtractEntityNameRegEx(entity: DbEntity): Regex? {
5 | return when (entity) {
6 | DbEntity.MIGRATION -> null
7 | DbEntity.ROLLBACK -> null
8 | else-> "^\\s*CREATE ${entity.dbEntity}\\s+(`[\\u0001-\\u005F\\u0061-\\uFFFF]+`|[0-9a-z_A-Z$\\u0080-\\uFFFF]+)".toRegex(RegexOption.IGNORE_CASE)
9 | // DbEntity.FUNCTION -> createScript
10 | // DbEntity.PROCEDURE -> createScript
11 | // DbEntity.TABLE -> createScript.replace(createTableCleanRegex, "")
12 | // DbEntity.TRIGGER -> createScript
13 | // DbEntity.VIEW -> createScript
14 | }
15 | }
16 |
17 |
18 | class TableScriptFixer(tables:List) : DbEntityCaseFixer(tables) {
19 | val createCleanRegex = "(?:\\s+ENGINE\\s*=\\s*[a-zA-Z0-9]+)|(?:\\s+AUTO_INCREMENT\\s*=\\s*\\d+)".toRegex();
20 |
21 | override fun addRegExPrefix(sb: StringBuilder) {
22 | sb.append("\\s+REFERENCES\\s+")
23 | super.addRegExPrefix(sb)
24 | }
25 |
26 | override fun addRegExSuffix(sb: StringBuilder) {
27 | super.addRegExSuffix(sb)
28 | }
29 |
30 | override fun addEntityNameRegEx(sb: StringBuilder, entityName: String) {
31 | super.addEntityNameRegEx(sb, entityName)
32 | }
33 |
34 | override fun cleanScript(createScript: String): String {
35 | val cleanedScript = createScript.replace(createCleanRegex, "")
36 | return super.cleanScript(cleanedScript)
37 | }
38 | }
39 |
40 | class ViewScriptFixer(tables:List) : DbEntityCaseFixer(tables) {
41 | val createCleanRegex = "(?:\\s+ENGINE\\s*=\\s*[a-zA-Z0-9]+)|(?:\\s+AUTO_INCREMENT\\s*=\\s*\\d+)".toRegex();
42 |
43 | override fun addRegExPrefix(sb: StringBuilder) {
44 | sb.append("\\s+REFERENCES\\s+")
45 | super.addRegExPrefix(sb)
46 | }
47 |
48 | override fun addRegExSuffix(sb: StringBuilder) {
49 | super.addRegExSuffix(sb)
50 | }
51 |
52 | override fun addEntityNameRegEx(sb: StringBuilder, entityName: String) {
53 | super.addEntityNameRegEx(sb, entityName)
54 | }
55 |
56 | override fun cleanScript(createScript: String): String {
57 | val cleanedScript = createScript.replace(createCleanRegex, "")
58 | return super.cleanScript(cleanedScript)
59 | }
60 | }
61 |
62 | override fun getEntityQuery(dbEntity: DbEntity, session: Session): SqlQuery {
63 | val defaultDb = session.connection.catalog
64 | val entityQuery = sqlQuery(getListEntitiesSql(dbEntity, defaultDb));
65 | return entityQuery
66 | }
67 |
68 | override fun getDbEntities(dbEntity: DbEntity, session: Session): List {
69 | val entityQuery = getEntityQuery(dbEntity, session)
70 | val entities = session.list(entityQuery) { it.string(1) }
71 | return entities;
72 | }
73 |
74 | override fun entityScriptFixer(entity: DbEntity, session: Session): DbEntityFixer {
75 | return when (entity) {
76 | DbEntity.FUNCTION -> DbEntityFixer.NULL
77 | DbEntity.PROCEDURE -> DbEntityFixer.NULL
78 | DbEntity.TABLE -> {
79 | val tables = getDbEntities(entity, session)
80 | return TableScriptFixer(tables)
81 | }
82 | DbEntity.TRIGGER -> DbEntityFixer.NULL
83 | DbEntity.VIEW -> DbEntityFixer.NULL
84 | DbEntity.MIGRATION -> DbEntityFixer.NULL
85 | DbEntity.ROLLBACK -> DbEntityFixer.NULL
86 | }
87 | }
88 |
89 | override fun getDropEntitySql(entityType: String, entityName: String): String {
90 | return "DROP $entityType IF EXISTS `$entityName`"
91 | }
92 |
93 | override fun getDropEntitySql(entity: DbEntity, entityName: String): String {
94 | return getDropEntitySql(entity.dbEntity, entityName)
95 | }
96 |
97 | override fun getShowEntitySql(entity: DbEntity, entityName: String): String {
98 | return when (entity) {
99 | DbEntity.FUNCTION -> ""
100 | DbEntity.PROCEDURE -> ""
101 | DbEntity.TABLE -> "SHOW CREATE TABLE `$entityName`"
102 | DbEntity.TRIGGER -> ""
103 | DbEntity.VIEW -> ""
104 | DbEntity.MIGRATION -> ""
105 | DbEntity.ROLLBACK -> ""
106 | }
107 | }
108 |
109 | override fun getListEntitiesSql(entity: DbEntity, schema: String): String {
110 | return when (entity) {
111 | DbEntity.FUNCTION -> "SELECT routine_name FROM information_schema.ROUTINES WHERE routine_schema = '$schema' AND routine_type = 'FUNCTION'"
112 | DbEntity.PROCEDURE -> "SELECT routine_name FROM information_schema.ROUTINES WHERE routine_schema = '$schema' AND routine_type = 'PROCEDURE'"
113 | DbEntity.TABLE -> "SELECT table_name FROM information_schema.TABLES WHERE table_schema = '$schema' AND TABLE_TYPE = 'BASE TABLE'"
114 | DbEntity.TRIGGER -> "SELECT trigger_name FROM information_schema.TRIGGERS WHERE trigger_schema = '$schema'"
115 | DbEntity.VIEW -> "SELECT table_name FROM information_schema.VIEWS WHERE table_schema = '$schema'"
116 | DbEntity.MIGRATION -> ""
117 | DbEntity.ROLLBACK -> ""
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | .markdown-body,
73 | .wiki-body {
74 | font-family: Arial, "Helvetica Neue", "Segoe UI", freesans, sans-serif;
75 | font-size: 16px;
76 | line-height: 1.6;
77 | }
78 |
79 | code {
80 | background-color:#ffe8f8;
81 | border-radius:2px;
82 | padding:1px 0 0 0;
83 | border:1px solid #eec5e1;
84 | font-family:Consolas,Inconsolata,Courier,monospace;
85 | font-size:0.9em;color:#b00042;
86 | }
87 |
88 | .search-highlight {
89 | border: solid 1px #000;
90 | padding: 1px;
91 | background-color: #ffff00;
92 | color: #000;
93 | }
94 |
95 | .selection-highlight {
96 | color:#000;
97 | padding:1px 0 1px 0;
98 | border: solid 1px #52CC7A;
99 | background-color: #B5FFCE;
100 | }
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/ModelPropertyProviderBase.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import kotlin.reflect.KProperty
4 |
5 | open class ModelPropertyProviderBase(val provider: ModelProperties, val propType: PropertyType, override val columnName: String?, override val defaultValue:Any? = Unit) : InternalModelPropertyProvider {
6 | final override fun provideDelegate(thisRef: T, prop: KProperty<*>): ModelProperties {
7 | return provider.registerProp(prop, propType, columnName, defaultValue)
8 | }
9 |
10 | final override val autoKey: ModelPropertyProvider
11 | get() = provider.autoKey
12 |
13 | override val key: ModelPropertyProvider
14 | get() = provider.key
15 |
16 | override val auto: ModelPropertyProvider
17 | get() = provider.auto
18 |
19 | override val default: ModelPropertyProvider
20 | get() = provider.default
21 |
22 | final override operator fun getValue(thisRef: T, property: KProperty<*>): V {
23 | return provider.getValue(thisRef, property)
24 | }
25 |
26 | final override operator fun setValue(thisRef: T, property: KProperty<*>, value: V) {
27 | provider.setValue(thisRef, property, value)
28 | }
29 |
30 | override fun column(columnName: String?): ModelPropertyProvider {
31 | return if (this.columnName == columnName) {
32 | this
33 | } else {
34 | if (columnName == null) {
35 | provider
36 | } else {
37 | ModelPropertyProviderBase(provider, propType, columnName)
38 | }
39 | }
40 | }
41 |
42 | override fun default(value: Any?): ModelPropertyProvider {
43 | return ModelPropertyProviderBase(provider, propType, columnName, value)
44 | }
45 | }
46 |
47 | class ModelPropertyProviderAutoKey(provider: ModelProperties, columnName: String?) : ModelPropertyProviderBase(provider, PropertyType.AUTO_KEY, columnName) {
48 | override val key: ModelPropertyProvider
49 | get() = this
50 |
51 | override val auto: ModelPropertyProvider
52 | get() = this
53 |
54 | override val default: ModelPropertyProvider
55 | get() = this
56 |
57 | override fun column(columnName: String?): ModelPropertyProvider {
58 | return if (this.columnName == columnName) {
59 | this
60 | } else {
61 | if (columnName == null) {
62 | provider.autoKey
63 | } else {
64 | ModelPropertyProviderAutoKey(provider, columnName)
65 | }
66 | }
67 | }
68 |
69 | override fun default(value: Any?): ModelPropertyProvider {
70 | if (value === Unit) return this
71 | throw IllegalStateException("Auto Key column cannot have a default value")
72 | }
73 | }
74 |
75 | class ModelPropertyProviderKey(provider: ModelProperties, columnName: String?) : ModelPropertyProviderBase(provider, PropertyType.KEY, columnName) {
76 | override val key: ModelPropertyProvider
77 | get() = this
78 |
79 | override val auto: ModelPropertyProvider
80 | get() = provider.autoKey
81 |
82 | override val default: ModelPropertyProvider
83 | get() = provider.autoKey
84 |
85 | override fun column(columnName: String?): ModelPropertyProvider {
86 | return if (this.columnName == columnName) {
87 | this
88 | } else {
89 | if (columnName == null) {
90 | provider.key
91 | } else {
92 | ModelPropertyProviderKey(provider, columnName)
93 | }
94 | }
95 | }
96 |
97 | override fun default(value: Any?): ModelPropertyProvider {
98 | if (value === Unit) return this
99 | throw IllegalStateException("Key column cannot have a default value")
100 | }
101 | }
102 |
103 | class ModelPropertyProviderAuto(provider: ModelProperties, columnName: String?) : ModelPropertyProviderBase(provider, PropertyType.AUTO, columnName) {
104 | override val key: ModelPropertyProvider
105 | get() = provider.autoKey
106 |
107 | override val auto: ModelPropertyProvider
108 | get() = this
109 |
110 | override val default: ModelPropertyProvider
111 | get() = this
112 |
113 | override fun column(columnName: String?): ModelPropertyProvider {
114 | return if (this.columnName == columnName) {
115 | this
116 | } else {
117 | if (columnName == null) {
118 | provider.auto
119 | } else {
120 | ModelPropertyProviderAuto(provider, columnName)
121 | }
122 | }
123 | }
124 |
125 | override fun default(value: Any?): ModelPropertyProvider {
126 | if (value === Unit) return this
127 | throw IllegalStateException("Auto column cannot have a default value")
128 | }
129 | }
130 |
131 | class ModelPropertyProviderDefault(provider: ModelProperties, columnName: String?, value: Any?) : ModelPropertyProviderBase(provider, PropertyType.DEFAULT, columnName, value) {
132 | override val key: ModelPropertyProvider
133 | get() = provider.autoKey
134 |
135 | override val auto: ModelPropertyProvider
136 | get() = provider.auto
137 |
138 | override val default: ModelPropertyProvider
139 | get() = this
140 |
141 | override fun column(columnName: String?): ModelPropertyProvider {
142 | return if (this.columnName == columnName) {
143 | this
144 | } else {
145 | if (columnName == null) {
146 | provider.default
147 | } else {
148 | ModelPropertyProviderDefault(provider, columnName, defaultValue)
149 | }
150 | }
151 | }
152 |
153 | override fun default(value: Any?): ModelPropertyProvider {
154 | if (value === defaultValue) return this
155 | return ModelPropertyProviderBase(provider, propType, columnName, value)
156 | }
157 | }
158 |
159 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/DbVersion.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | class DbVersion private constructor(val major: Int, val minor: Int?, val patch: Int?, val metadata: String?, dummy: Any?) : Comparable {
4 | constructor(major: Int) : this(major, null, null, null, null)
5 | constructor(major: Int, minor: Int) : this(major, minor, null, null, null)
6 | constructor(major: Int, minor: Int, patch: Int) : this(major, minor, patch, null, null)
7 | constructor(major: Int, minor: Int, patch: Int, metadata: String) : this(major, minor, patch, metadata, null)
8 |
9 | override fun compareTo(other: DbVersion): Int {
10 | return if (major == other.major) {
11 | if (minor == other.minor) {
12 | if (minor == null || other.minor == null) 0
13 | else {
14 | if (patch == other.patch) {
15 | if (patch == null || other.patch == null) 0
16 | else {
17 | if (metadata == other.metadata) 0
18 | else {
19 | if (metadata == null) -1
20 | else if (other.metadata == null) 1
21 | else metadata.compareTo(other.metadata)
22 | }
23 | }
24 | } else {
25 | if (patch == null) -1
26 | else if (other.patch == null) 1
27 | else patch.compareTo(other.patch)
28 | }
29 | }
30 | } else {
31 | if (minor == null) -1
32 | else if (other.minor == null) 1
33 | else minor.compareTo(other.minor)
34 | }
35 | } else {
36 | major.compareTo(other.major)
37 | }
38 | }
39 |
40 | /**
41 | * compare with nulls matching all versions
42 | *
43 | * @param other DbVersion
44 | * @return Boolean true if this version selects other
45 | */
46 | fun selects(other: DbVersion): Boolean {
47 | return 0 == if (major == other.major) {
48 | if (minor == other.minor) {
49 | if (minor == null || other.minor == null) 0
50 | else {
51 | if (patch == other.patch) {
52 | if (patch == null || other.patch == null) 0
53 | else {
54 | if (metadata == other.metadata) 0
55 | else {
56 | if (metadata == null) 0
57 | else if (other.metadata == null) 1
58 | else metadata.compareTo(other.metadata)
59 | }
60 | }
61 | } else {
62 | if (patch == null) 0
63 | else if (other.patch == null) 1
64 | else patch.compareTo(other.patch)
65 | }
66 | }
67 | } else {
68 | if (minor == null) 0
69 | else if (other.minor == null) 1
70 | else minor.compareTo(other.minor)
71 | }
72 | } else {
73 | major.compareTo(other.major)
74 | }
75 | }
76 |
77 | fun nextMajor():DbVersion {
78 | return DbVersion(major+1, if (minor == null) null else 0,if (patch == null) null else 0, null,null)
79 | }
80 |
81 | fun nextMinor():DbVersion {
82 | return DbVersion(major, (minor ?: 0) + 1,if (patch == null) null else 0, null,null)
83 | }
84 |
85 | fun nextPatch(metadata: String? = null):DbVersion {
86 | return DbVersion(major, minor ?: 0, (patch ?: 0) + 1, metadata, null)
87 | }
88 |
89 | fun withMetadata(metadata: String):DbVersion {
90 | return DbVersion(major, minor ?: 0, patch ?: 0, metadata)
91 | }
92 |
93 | override fun toString(): String {
94 | val sb = StringBuilder()
95 | sb.append('V').append(major)
96 | if (minor != null) {
97 | sb.append('_').append(minor)
98 | if (patch != null) {
99 | sb.append('_').append(patch)
100 | if (metadata != null) {
101 | sb.append('_').append(metadata)
102 | }
103 | }
104 | }
105 | return sb.toString()
106 | }
107 |
108 | override fun equals(other: Any?): Boolean {
109 | if (this === other) return true
110 | if (other !is DbVersion) return false
111 |
112 | if (major != other.major) return false
113 | if (minor != other.minor) return false
114 | if (patch != other.patch) return false
115 | if (metadata != other.metadata) return false
116 |
117 | return true
118 | }
119 |
120 | override fun hashCode(): Int {
121 | var result = major
122 | result = 31 * result + (minor ?: 0)
123 | result = 31 * result + (patch ?: 0)
124 | result = 31 * result + (metadata?.hashCode() ?: 0)
125 | return result
126 | }
127 |
128 | companion object {
129 | const val pattern = "^(V\\d+(?:_\\d+(?:_\\d+(?:_.*)?)?)?)$"
130 | val regex = pattern.toRegex()
131 |
132 | fun of(version: String): DbVersion {
133 | if (!version.matches(regex)) {
134 | throw IllegalArgumentException("Invalid version format: $version, expected regex match /$pattern/")
135 | }
136 |
137 | val parts = version.removePrefix("V").split('_', limit = 4)
138 |
139 | return when (parts.size) {
140 | 1 -> DbVersion(parts[0].toInt())
141 | 2 -> DbVersion(parts[0].toInt(), parts[1].toInt())
142 | 3 -> DbVersion(parts[0].toInt(), parts[1].toInt(), parts[2].toInt())
143 | 4 -> DbVersion(parts[0].toInt(), parts[1].toInt(), parts[2].toInt(), parts[3])
144 | else -> DbVersion(0, 0, 0)
145 | }
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/MigrationSession.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import org.intellij.lang.annotations.Language
4 | import java.time.LocalDateTime
5 |
6 | class MigrationSession(val batchId: Int, val version: String, val migrations: Migrations) {
7 | data class Migration(
8 | val migration_id: Int,
9 | val version: String,
10 | val batch_id: Int,
11 | val applied_at: LocalDateTime,
12 | val migration_type: Int?,
13 | val script_name: String,
14 | val script_sql: String,
15 | val rolled_back_id: Int?,
16 | val last_problem: String?
17 | ) {
18 |
19 | companion object {
20 | @JvmStatic
21 | val toModel: (Row) -> Migration = { row ->
22 | Migration(
23 | row.int("migration_id"),
24 | row.string("version"),
25 | row.int("batch_id"),
26 | row.localDateTime("applied_at"),
27 | row.intOrNull("migration_type"),
28 | row.string("script_name"),
29 | row.string("script_sql"),
30 | row.intOrNull("rolled_back_id"),
31 | row.stringOrNull("last_problem")
32 | )
33 | }
34 | }
35 | }
36 |
37 | @Language("SQL")
38 | val createTableSql = """
39 | CREATE TABLE `migrations` (
40 | `migration_id` INT(11) NOT NULL AUTO_INCREMENT,
41 | `version` VARCHAR(32) NOT NULL,
42 | `batch_id` INT(11) NOT NULL,
43 | `applied_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
44 | `migration_type` TINYINT(1),
45 | `rolled_back_id` INT(11), # migration_id of roll back script
46 | `script_name` VARCHAR(128),
47 | `script_sql` MEDIUMTEXT,
48 | `last_problem` MEDIUMTEXT,
49 | PRIMARY KEY (`migration_id`)
50 | ) DEFAULT CHARSET = utf8
51 | """
52 |
53 | @Language("SQL")
54 | val migrationSql = """
55 | INSERT INTO migrations (
56 | version,
57 | batch_id,
58 | script_name,
59 | script_sql,
60 | migration_type,
61 | last_problem
62 | ) VALUES (
63 | :version,
64 | :batchId,
65 | :scriptName,
66 | :scriptSql,
67 | :migrationType,
68 | :lastProblem
69 | )
70 | """
71 | var lastScriptName: String? = null
72 | var lastScriptSql: String? = null
73 |
74 | fun invokeWith(action: (Session) -> T?): T? {
75 | return action.invoke(migrations.migrationSession)
76 | }
77 |
78 | fun getMigrationSql(scriptName: String, scriptSql: String, migrationType:Int? = null): SqlQuery {
79 | lastScriptName = scriptName
80 | lastScriptSql = scriptSql
81 | return sqlQuery(migrationSql, mapOf("version" to version, "batchId" to batchId, "scriptName" to scriptName, "scriptSql" to scriptSql, "migrationType" to migrationType))
82 | }
83 |
84 | fun insertUpMigrationAfter(scriptName: String, scriptSql: String, action: () -> Unit) {
85 | val sqlQuery = getMigrationSql(scriptName, scriptSql, 1)
86 |
87 | action.invoke()
88 | val transId = migrations.migrationSession.updateGetId(sqlQuery)
89 |
90 | // now need to mark all rollbacks of this script that they were reversed
91 | val rollBackScriptName = DbEntity.ROLLBACK.addSuffix( DbEntity.MIGRATION.removeSuffix(scriptName))
92 | val updateSql = sqlQuery("""
93 | UPDATE migrations SET rolled_back_id = :transId WHERE (script_name = :scriptName OR script_name LIKE :scriptNameLike) AND version = :version AND migration_type = -1 AND rolled_back_id IS NULL
94 | """, mapOf("transId" to transId, "version" to version, "scriptName" to rollBackScriptName, "scriptNameLike" to "$rollBackScriptName[%]"))
95 | migrations.migrationSession.execute(updateSql)
96 | if (!migrations.migrationSession.connection.autoCommit) {
97 | // commit changes so the last success is not lost on next failure
98 | migrations.migrationSession.connection.commit()
99 | migrations.migrationSession.connection.begin()
100 | }
101 | }
102 |
103 | fun insertDownMigrationAfter(scriptName: String, scriptSql: String, action: () -> Unit) {
104 | val sqlQuery = getMigrationSql(scriptName, scriptSql, -1)
105 |
106 | action.invoke()
107 | val transId = migrations.migrationSession.updateGetId(sqlQuery)
108 |
109 | // now need to mark all rollbacks of this script that they were reversed
110 | val rollBackScriptName = DbEntity.MIGRATION.addSuffix( DbEntity.ROLLBACK.removeSuffix(scriptName))
111 | val updateSql = sqlQuery("""
112 | UPDATE migrations SET rolled_back_id = :transId WHERE (script_name = :scriptName OR script_name LIKE :scriptNameLike) AND version = :version AND migration_type = 1 AND rolled_back_id IS NULL
113 | """, mapOf("transId" to transId, "version" to version, "scriptName" to rollBackScriptName, "scriptNameLike" to "$rollBackScriptName[%]"))
114 | migrations.migrationSession.execute(updateSql)
115 | }
116 |
117 | fun insertMigrationAfter(scriptName: String, scriptSql: String, action: () -> Unit) {
118 | val sqlQuery = getMigrationSql(scriptName, scriptSql)
119 |
120 | action.invoke()
121 | migrations.migrationSession.execute(sqlQuery)
122 | }
123 |
124 | fun getVersionBatches(): List {
125 | return migrations.migrationSession.list(sqlQuery("""
126 | SELECT * FROM migrations
127 | WHERE rolled_back_id IS NULL AND last_problem IS NULL
128 | ORDER BY migration_id ASC
129 | """), Migration.toModel)
130 | }
131 |
132 | fun getVersionBatchesNameMap(): Map {
133 | val query = sqlQuery("""
134 | SELECT * FROM migrations
135 | WHERE rolled_back_id IS NULL AND last_problem IS NULL
136 | ORDER BY migration_id ASC
137 | """)
138 | val keyExtractor: (Row) -> String = { row ->
139 | row.string("script_name")
140 | }
141 |
142 | return migrations.migrationSession.hashMap(query, keyExtractor, Migration.toModel)
143 | }
144 |
145 | fun getAllVersionBatches(): List {
146 | return migrations.migrationSession.list(sqlQuery("""
147 | SELECT * FROM migrations
148 | ORDER BY migration_id ASC
149 | """), Migration.toModel)
150 | }
151 |
152 | fun withVersion(version: String): MigrationSession {
153 | return MigrationSession(batchId, version, migrations)
154 | }
155 | }
156 |
157 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/Model.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import javax.json.JsonArray
4 | import javax.json.JsonObject
5 | import kotlin.reflect.KProperty
6 |
7 | @Suppress("MemberVisibilityCanBePrivate", "UNCHECKED_CAST", "PropertyName")
8 | abstract class Model, D>(session: Session?, sqlTable: String, dbCase: Boolean, allowSetAuto: Boolean = true, quote: String? = null) {
9 | internal val _db = ModelProperties>(session ?: session(), sqlTable, dbCase, allowSetAuto, quote)
10 | protected val db: ModelPropertyProvider> get() = _db
11 |
12 | fun load(rs: Row): M {
13 | _db.load(rs)
14 | return this as M
15 | }
16 |
17 | fun load(json: JsonObject): M {
18 | _db.load(json)
19 | return this as M
20 | }
21 |
22 | fun load(other: Model<*, *>): M {
23 | _db.load(other)
24 | return this as M
25 | }
26 |
27 | val _session: Session get() = _db.session
28 | val _quote: String get() = _db.quote
29 |
30 | fun toJson() = _db.toJsonObject()
31 |
32 | val insertQuery: SqlQuery get() = _db.sqlInsertQuery()
33 | val deleteQuery: SqlQuery get() = _db.sqlDeleteQuery()
34 | val updateQuery: SqlQuery get() = _db.sqlUpdateQuery()
35 | val selectQuery: SqlQuery get() = _db.sqlSelectQuery()
36 | val selectSql: String get() = _db.sqlSelectTable()
37 |
38 | fun Appendable.appendQuoted(id: kotlin.String): Appendable {
39 | return _db.appendQuoted(this, id)
40 | }
41 |
42 | fun Appendable.appendSqlSelectTable(): Appendable {
43 | return _db.appendSelectSql(this)
44 | }
45 |
46 | fun insert() = _db.insert()
47 | fun insertIgnoreKeys() = _db.insertIgnoreKeys()
48 | fun select() = _db.select()
49 | fun insertReload() = _db.insertReload()
50 | fun clearAutoKeys() = _db.clearAutoKeys()
51 | fun delete() = _db.delete()
52 | fun update() = _db.update()
53 | fun updateReload() = _db.updateReload()
54 | fun deleteKeepAutoKeys() = _db.deleteKeepAutoKeys()
55 | fun snapshot() = _db.snapshot()
56 | fun isDirty(): Boolean = _db.isModified()
57 | fun isDirty(property: KProperty<*>): Boolean = _db.isModified(property)
58 | fun appendKeys(appendable: Appendable, params: ArrayList, delimiter: String = " AND ", sep: String = ""): String =
59 | _db.appendKeys(appendable, params, delimiter, sep, null)
60 |
61 | fun forEachKey(consumer: (prop: KProperty<*>, propType: PropertyType, value: Any?) -> Unit) = _db.forEachKey(consumer)
62 |
63 | fun forEachProp(consumer: (prop: KProperty<*>, propType: PropertyType, value: Any?) -> Unit) = _db.forEachProp(consumer)
64 |
65 | fun forEachKey(consumer: (prop: KProperty<*>, propType: PropertyType, columnName: String, value: Any?) -> Unit) =
66 | _db.forEachKey(consumer)
67 |
68 | fun forEachProp(consumer: (prop: KProperty<*>, propType: PropertyType, columnName: String, value: Any?) -> Unit) =
69 | _db.forEachProp(consumer)
70 |
71 | /**
72 | * set property value directly in the _model property map, by passing _model properties
73 | *
74 | * Can be used to set non-writable properties
75 | */
76 | protected fun setProperty(prop: KProperty<*>, value: V) {
77 | @Suppress("UNCHECKED_CAST")
78 | _db.setProperty(this, prop, value)
79 | }
80 |
81 | fun quoteIdentifier(id: String): String = _db.appendQuoted(StringBuilder(), id).toString()
82 |
83 | fun appendSelectSql(out: Appendable, alias: String? = null): Appendable = _db.appendSelectSql(out, alias)
84 |
85 | fun appendListQuery(out: Appendable, params: Array>, alias: String? = null): Appendable = _db.appendListQuery(out, params, alias)
86 |
87 | fun appendListQuery(out: Appendable, params: Map, alias: String? = null): Appendable = _db.appendListQuery(out, params, alias)
88 |
89 | fun listQuery(params: Map, alias: String? = null): SqlQuery = _db.listQuery(params, alias)
90 |
91 | fun listQuery(vararg params: Pair, alias: String? = null): SqlQuery = _db.listQuery(params, alias)
92 |
93 | fun listQuery(whereClause: String, params: Map, alias: String? = null): SqlQuery = _db.listQuery(whereClause, params, alias)
94 |
95 | fun listData(): List = _db.listData(toData)
96 |
97 | fun listData(whereClause: String): List = _db.listData(whereClause, toData)
98 |
99 | fun listData(sqlQuery: SqlQuery): List = _db.session.list(sqlQuery, toData)
100 |
101 | fun listData(params: Map, alias: String? = null): List = _db.session.list(listQuery(params, alias), toData)
102 |
103 | fun listData(whereClause: String, params: Map, alias: String? = null): List = _db.session.list(listQuery(whereClause, params, alias), toData)
104 |
105 | fun jsonArray(): JsonArray = _db.session.jsonArray(listQuery(), toJsonObject)
106 |
107 | fun jsonArray(whereClause: String): JsonArray = _db.session.jsonArray(listQuery(whereClause, mapOf()), toJsonObject)
108 |
109 | fun jsonArray(sqlQuery: SqlQuery): JsonArray = _db.session.jsonArray(sqlQuery, toJsonObject)
110 |
111 | fun jsonArray(params: Map, alias: String? = null): JsonArray = _db.session.jsonArray(listQuery(params, alias), toJsonObject)
112 |
113 | fun jsonArray(whereClause: String, params: Map, alias: String? = null): JsonArray = _db.session.jsonArray(listQuery(whereClause, params, alias), toJsonObject)
114 |
115 | fun listModel(): List = _db.listModel(toModel) as List
116 |
117 | fun listModel(whereClause: String): List = _db.listModel(whereClause, toModel) as List
118 |
119 | fun listModel(sqlQuery: SqlQuery): List = _db.session.list(sqlQuery, toModel)
120 |
121 | fun listModel(params: Map, alias: String? = null): List = _db.session.list(listQuery(params, alias), toModel)
122 |
123 | fun listModel(whereClause: String, params: Map, alias: String? = null): List = _db.session.list(listQuery(whereClause, params, alias), toModel)
124 |
125 | // create a new copy, same params
126 | abstract operator fun invoke(): M
127 |
128 | // create a data of this
129 | abstract fun toData(): D
130 |
131 | // instance in this model used to load lists
132 | private val loader: M by lazy { invoke() }
133 |
134 | val toData: (Row) -> D = {
135 | loader.load(it).toData()
136 | }
137 |
138 | val toModel: (Row) -> M = {
139 | loader.load(it)
140 | }
141 |
142 | override fun toString(): String {
143 | val sb = StringBuilder()
144 | var sep = ""
145 | sb.append(_db.tableName).append("(")
146 | forEachProp { prop, propType, value ->
147 | if (value !== Unit) {
148 | sb.append(sep)
149 | if (propType.isKey) sb.append('*')
150 | sb.append(prop.name).append("=").append(value)
151 | sep = ", "
152 | }
153 | }
154 | sb.append(")")
155 | return sb.toString()
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/SqlQueryBase.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import org.jetbrains.annotations.TestOnly
4 | import java.sql.PreparedStatement
5 |
6 | abstract class SqlQueryBase>(
7 | val statement: String,
8 | params: List = listOf(),
9 | namedParams: Map = mapOf()
10 | ) {
11 |
12 | protected val params = ArrayList(params.asParamList())
13 | protected val namedParams: HashMap> = HashMap(namedParams.asParamMap())
14 |
15 | private var _queryDetails: Details? = null
16 | private val queryDetails: Details get() = finalizedQuery()
17 |
18 | val replacementMap get() = queryDetails.replacementMap
19 | val cleanStatement get() = queryDetails.cleanStatement
20 |
21 | val inputParams: Map> get() = this.namedParams.filter { it.value.inOut.isIn }
22 | val outputParams: Map> get() = this.namedParams.filter { it.value.inOut.isOut }
23 |
24 | data class Details(
25 | val listParamsMap: Map,
26 | val replacementMap: Map>,
27 | val cleanStatement: String,
28 | val paramCount: Int
29 | )
30 |
31 | protected fun resetDetails() {
32 | _queryDetails = null
33 | }
34 |
35 | private fun finalizedQuery(): Details {
36 | if (_queryDetails == null) {
37 | // called when parameters are defined and have request for clean statement or populate params
38 | val listParamsMap: HashMap = HashMap()
39 | var idxOffset = 0
40 | val findAll = regex.findAll(statement)
41 | val replacementMap = findAll.filter { group ->
42 | if (!group.value.startsWith(":")) {
43 | // not a parameter
44 | false
45 | } else {
46 | // filter out commented lines
47 | val pos = statement.lastIndexOf('\n', group.range.first)
48 | val lineStart = if (pos == -1) 0 else pos + 1;
49 | !regexSqlComment.containsMatchIn(statement.subSequence(lineStart, statement.length))
50 | }
51 | }.map { group ->
52 | val paramName = group.value.substring(1);
53 | val paramValue = namedParams[paramName]
54 | val pair = Pair(paramName, idxOffset)
55 |
56 | if (paramValue?.value is Collection<*>) {
57 | val size = paramValue.value.size
58 | listParamsMap[paramName] = "?,".repeat(size).substring(0, size * 2 - 1)
59 | idxOffset += size - 1
60 | }
61 |
62 | idxOffset++
63 | pair
64 | }.groupBy({ it.first }, { it.second })
65 |
66 | val cleanStatement = regex.replace(statement) { matchResult ->
67 | if (!matchResult.value.startsWith(":")) {
68 | // not a parameter, leave as is
69 | matchResult.value
70 | } else {
71 | val paramName = matchResult.value.substring(1);
72 | listParamsMap[paramName] ?: "?"
73 | }
74 | }
75 | _queryDetails = Details(listParamsMap, replacementMap, cleanStatement, idxOffset)
76 | }
77 | return _queryDetails!!
78 | }
79 |
80 | fun populateParams(stmt: PreparedStatement) {
81 | // TODO: handle mix of ? and named params, by computing indices for ? params and populating based on index
82 | if (replacementMap.isNotEmpty()) {
83 | populateNamedParams(stmt)
84 | } else {
85 | params.forEachIndexed { index, value ->
86 | stmt.setTypedParam(index + 1, value.param())
87 | }
88 | }
89 | }
90 |
91 | protected fun forEachNamedParam(inOut: InOut, action: (paramName: String, param: Parameter<*>, occurrences: List) -> Unit) {
92 | replacementMap.forEach { (paramName, occurrences) ->
93 | val param = namedParams[paramName] ?: NULL_PARAMETER
94 | if (param.inOut.isOf(inOut)) action.invoke(paramName, param, occurrences)
95 | }
96 | }
97 |
98 | protected open fun populateNamedParams(stmt: PreparedStatement) {
99 | forEachNamedParam(InOut.IN) { _, param, occurrences ->
100 | if (param.value is Collection<*>) {
101 | param.value.forEachIndexed { idx, paramItem ->
102 | occurrences.forEach {
103 | when (paramItem) {
104 | is Parameter<*> -> stmt.setTypedParam(it + idx + 1, param)
105 | else -> stmt.setParam(it + idx + 1, paramItem)
106 | }
107 | }
108 | }
109 | } else {
110 | occurrences.forEach {
111 | stmt.setTypedParam(it + 1, param)
112 | }
113 | }
114 | }
115 | }
116 |
117 | @TestOnly
118 | fun getParams(): List {
119 | return if (replacementMap.isNotEmpty()) {
120 | val sqlParams = ArrayList(queryDetails.paramCount)
121 |
122 | for (i in 0 until queryDetails.paramCount) {
123 | sqlParams.add(null)
124 | }
125 |
126 | forEachNamedParam(InOut.IN) { _, param, occurrences ->
127 | occurrences.forEach {
128 | if (param.value is Collection<*>) {
129 | param.value.forEachIndexed { idx, paramItem ->
130 | sqlParams[it + idx] = paramItem
131 | }
132 | } else {
133 | sqlParams[it] = param.value
134 | }
135 | }
136 | }
137 |
138 | sqlParams
139 | } else {
140 | params
141 | }
142 | }
143 |
144 | fun params(vararg params: Any?): T {
145 | this.params.addAll(params.asParamList())
146 | this._queryDetails = null
147 | @Suppress("UNCHECKED_CAST")
148 | return this as T
149 | }
150 |
151 | fun paramsArray(params: Array): T {
152 | this.params.addAll(params.asParamList())
153 | this._queryDetails = null
154 | @Suppress("UNCHECKED_CAST")
155 | return this as T
156 | }
157 |
158 | fun paramsList(params: Collection): T {
159 | this.params.addAll(params.asParamList())
160 | this._queryDetails = null
161 | @Suppress("UNCHECKED_CAST")
162 | return this as T
163 | }
164 |
165 | fun params(params: Map): T {
166 | namedParams.putAll(params.asParamMap())
167 | this._queryDetails = null
168 | @Suppress("UNCHECKED_CAST")
169 | return this as T
170 | }
171 |
172 | fun params(vararg params: Pair): T {
173 | namedParams.putAll(params.asParamMap())
174 | this._queryDetails = null
175 | @Suppress("UNCHECKED_CAST")
176 | return this as T
177 | }
178 |
179 | fun paramsArray(params: Array>): T {
180 | namedParams.putAll(params.asParamMap())
181 | this._queryDetails = null
182 | @Suppress("UNCHECKED_CAST")
183 | return this as T
184 | }
185 |
186 | @Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("params"))
187 | fun inParams(params: Map): T {
188 | return params(params.asParamMap(InOut.IN))
189 | }
190 |
191 | @Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("params"))
192 | fun inParams(vararg params: Pair): T {
193 | return params(params.asParamMap(InOut.IN))
194 | }
195 |
196 | @Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("paramsArray"))
197 | fun inParamsArray(params: Array>): T {
198 | return paramsArray(params)
199 | }
200 |
201 | override fun toString(): String {
202 | return "SqlQueryBase(statement='$statement', params=$params, namedParams=$namedParams, replacementMap=$replacementMap, cleanStatement='$cleanStatement')"
203 | }
204 |
205 | companion object {
206 | // must begin with
207 | private val NULL_PARAMETER = Parameter(null, Any::class.java, InOut.IN)
208 | private val regex = Regex(":\\w+|'(?:[^']|'')*'|`(?:[^`])*`|\"(?:[^\"])*\"")
209 | private val regexSqlComment = Regex("""^\s*(?:--\s|#)""")
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/SqlCallResults.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.io.Reader
4 | import java.math.BigDecimal
5 | import java.sql.*
6 | import java.sql.Date
7 | import java.util.*
8 |
9 | class SqlCallResults(private val stmt: CallableStatement, val withResults: Boolean, private val indexMap: Map) {
10 |
11 | private fun getParamIndex(paramName: String): Int {
12 | require(indexMap.containsKey(paramName)) { "Param $paramName is not an out param" }
13 | return indexMap[paramName]!!
14 | }
15 |
16 | fun forEach(operator: (rs: ResultSet, index: Int) -> Unit) {
17 | var rsIndex = 0
18 | while (true) {
19 | val rs = stmt.resultSet ?: return
20 | operator.invoke(rs, rsIndex++)
21 | if (!stmt.moreResults) break
22 | }
23 | }
24 |
25 | fun getString(paramName: String): String {
26 | return getStringOrNull(paramName) ?: throw NullPointerException()
27 | }
28 |
29 | fun getBoolean(paramName: String): Boolean {
30 | return getBooleanOrNull(paramName) ?: throw NullPointerException()
31 | }
32 |
33 | fun getByte(paramName: String): Byte {
34 | return getByteOrNull(paramName) ?: throw NullPointerException()
35 | }
36 |
37 | fun getShort(paramName: String): Short {
38 | return getShortOrNull(paramName) ?: throw NullPointerException()
39 | }
40 |
41 | fun getInt(paramName: String): Int {
42 | return getIntOrNull(paramName) ?: throw NullPointerException()
43 | }
44 |
45 | fun getLong(paramName: String): Long {
46 | return getLongOrNull(paramName) ?: throw NullPointerException()
47 | }
48 |
49 | fun getFloat(paramName: String): Float {
50 | return getFloatOrNull(paramName) ?: throw NullPointerException()
51 | }
52 |
53 | fun getDouble(paramName: String): Double {
54 | return getDoubleOrNull(paramName) ?: throw NullPointerException()
55 | }
56 |
57 | fun getBytes(paramName: String): ByteArray {
58 | return getBytesOrNull(paramName) ?: throw NullPointerException()
59 | }
60 |
61 | fun getDate(paramName: String): Date {
62 | return getDateOrNull(paramName) ?: throw NullPointerException()
63 | }
64 |
65 | fun getTime(paramName: String): Time {
66 | return getTimeOrNull(paramName) ?: throw NullPointerException()
67 | }
68 |
69 | fun getTimestamp(paramName: String): Timestamp {
70 | return getTimestampOrNull(paramName) ?: throw NullPointerException()
71 | }
72 |
73 | fun getObject(paramName: String): Any {
74 | return getObjectOrNull(paramName) ?: throw NullPointerException()
75 | }
76 |
77 | fun getBigDecimal(paramName: String): BigDecimal {
78 | return getBigDecimalOrNull(paramName) ?: throw NullPointerException()
79 | }
80 |
81 | fun getObject(paramName: String, map: Map>): Any {
82 | return getObjectOrNull(paramName) ?: throw NullPointerException()
83 | }
84 |
85 | fun getRef(paramName: String): Ref {
86 | return getRefOrNull(paramName) ?: throw NullPointerException()
87 | }
88 |
89 | fun getBlob(paramName: String): Blob {
90 | return getBlobOrNull(paramName) ?: throw NullPointerException()
91 | }
92 |
93 | fun getClob(paramName: String): Clob {
94 | return getClobOrNull(paramName) ?: throw NullPointerException()
95 | }
96 |
97 | fun getArray(paramName: String): java.sql.Array {
98 | return getArrayOrNull(paramName) ?: throw NullPointerException()
99 | }
100 |
101 | fun getDate(paramName: String, cal: Calendar): Date {
102 | return getDateOrNull(paramName) ?: throw NullPointerException()
103 | }
104 |
105 | fun getTime(paramName: String, cal: Calendar): Time {
106 | return getTimeOrNull(paramName) ?: throw NullPointerException()
107 | }
108 |
109 | fun getTimestamp(paramName: String, cal: Calendar): Timestamp {
110 | return getTimestampOrNull(paramName) ?: throw NullPointerException()
111 | }
112 |
113 | fun getRowId(paramName: String): RowId {
114 | return getRowIdOrNull(paramName) ?: throw NullPointerException()
115 | }
116 |
117 | fun getNClob(paramName: String): NClob {
118 | return getNClobOrNull(paramName) ?: throw NullPointerException()
119 | }
120 |
121 | fun getSQLXML(paramName: String): SQLXML {
122 | return getSQLXMLOrNull(paramName) ?: throw NullPointerException()
123 | }
124 |
125 | fun getNString(paramName: String): String {
126 | return getNStringOrNull(paramName) ?: throw NullPointerException()
127 | }
128 |
129 | fun getNCharacterStream(paramName: String): Reader {
130 | return getNCharacterStreamOrNull(paramName) ?: throw NullPointerException()
131 | }
132 |
133 | fun getCharacterStream(paramName: String): Reader {
134 | return getCharacterStreamOrNull(paramName) ?: throw NullPointerException()
135 | }
136 |
137 | fun getStringOrNull(paramName: String): String? {
138 | return stmt.getString(getParamIndex(paramName))
139 | }
140 |
141 | fun getBooleanOrNull(paramName: String): Boolean? {
142 | val value = stmt.getBoolean(getParamIndex(paramName))
143 | return if (stmt.wasNull()) null else value
144 | }
145 |
146 | fun getByteOrNull(paramName: String): Byte? {
147 | val value = stmt.getByte(getParamIndex(paramName))
148 | return if (stmt.wasNull()) null else value
149 | }
150 |
151 | fun getShortOrNull(paramName: String): Short? {
152 | val value = stmt.getShort(getParamIndex(paramName))
153 | return if (stmt.wasNull()) null else value
154 | }
155 |
156 | fun getIntOrNull(paramName: String): Int? {
157 | val value = stmt.getInt(getParamIndex(paramName))
158 | return if (stmt.wasNull()) null else value
159 | }
160 |
161 | fun getLongOrNull(paramName: String): Long? {
162 | val value = stmt.getLong(getParamIndex(paramName))
163 | return if (stmt.wasNull()) null else value
164 | }
165 |
166 | fun getFloatOrNull(paramName: String): Float? {
167 | val value = stmt.getFloat(getParamIndex(paramName))
168 | return if (stmt.wasNull()) null else value
169 | }
170 |
171 | fun getDoubleOrNull(paramName: String): Double? {
172 | val value = stmt.getDouble(getParamIndex(paramName))
173 | return if (stmt.wasNull()) null else value
174 | }
175 |
176 | fun getBytesOrNull(paramName: String): ByteArray? {
177 | return stmt.getBytes(getParamIndex(paramName))
178 | }
179 |
180 | fun getDateOrNull(paramName: String): Date? {
181 | return stmt.getDate(getParamIndex(paramName))
182 | }
183 |
184 | fun getTimeOrNull(paramName: String): Time? {
185 | return stmt.getTime(getParamIndex(paramName))
186 | }
187 |
188 | fun getTimestampOrNull(paramName: String): Timestamp? {
189 | return stmt.getTimestamp(getParamIndex(paramName))
190 | }
191 |
192 | fun getObjectOrNull(paramName: String): Any? {
193 | return stmt.getObject(getParamIndex(paramName))
194 | }
195 |
196 | fun getBigDecimalOrNull(paramName: String): BigDecimal? {
197 | return stmt.getBigDecimal(getParamIndex(paramName))
198 | }
199 |
200 | fun getObjectOrNull(paramName: String, map: Map>): Any? {
201 | return stmt.getObject(getParamIndex(paramName), map)
202 | }
203 |
204 | fun getRefOrNull(paramName: String): Ref? {
205 | return stmt.getRef(getParamIndex(paramName))
206 | }
207 |
208 | fun getBlobOrNull(paramName: String): Blob? {
209 | return stmt.getBlob(getParamIndex(paramName))
210 | }
211 |
212 | fun getClobOrNull(paramName: String): Clob? {
213 | return stmt.getClob(getParamIndex(paramName))
214 | }
215 |
216 | fun getArrayOrNull(paramName: String): java.sql.Array? {
217 | return stmt.getArray(getParamIndex(paramName))
218 | }
219 |
220 | fun getDateOrNull(paramName: String, cal: Calendar): Date? {
221 | return stmt.getDate(getParamIndex(paramName), cal)
222 | }
223 |
224 | fun getTimeOrNull(paramName: String, cal: Calendar): Time? {
225 | return stmt.getTime(getParamIndex(paramName), cal)
226 | }
227 |
228 | fun getTimestampOrNull(paramName: String, cal: Calendar): Timestamp? {
229 | return stmt.getTimestamp(getParamIndex(paramName), cal)
230 | }
231 |
232 | fun getRowIdOrNull(paramName: String): RowId? {
233 | return stmt.getRowId(getParamIndex(paramName))
234 | }
235 |
236 | fun getNClobOrNull(paramName: String): NClob? {
237 | return stmt.getNClob(getParamIndex(paramName))
238 | }
239 |
240 | fun getSQLXMLOrNull(paramName: String): SQLXML? {
241 | return stmt.getSQLXML(getParamIndex(paramName))
242 | }
243 |
244 | fun getNStringOrNull(paramName: String): String? {
245 | return stmt.getNString(getParamIndex(paramName))
246 | }
247 |
248 | fun getNCharacterStreamOrNull(paramName: String): Reader? {
249 | return stmt.getNCharacterStream(getParamIndex(paramName))
250 | }
251 |
252 | fun getCharacterStreamOrNull(paramName: String): Reader? {
253 | return stmt.getCharacterStream(getParamIndex(paramName))
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/extensions/com.intellij.database/data/extractors/Kotlin-Enum.js:
--------------------------------------------------------------------------------
1 | /*
2 | Data extractor of table data to Kotlin Enum based on values
3 |
4 | First non-numeric column is considered the name of the enum values, the rest will be used as properties of the enum value
5 |
6 | Transposed status is ignored
7 | */
8 |
9 | var packageName = ""; // package used for generated class files
10 | var enumNameSuffix = ""; // appended to class file name
11 |
12 | function eachWithIdx(iterable, f) {
13 | var i = iterable.iterator();
14 | var idx = 0;
15 | while (i.hasNext()) f(i.next(), idx++);
16 | }
17 |
18 | function mapEach(iterable, f) {
19 | var vs = [];
20 | eachWithIdx(iterable, function (i) {
21 | vs.push(f(i));
22 | });
23 | return vs;
24 | }
25 |
26 | function escape(str) {
27 | str = str.replaceAll("\t|\b|\\f", "");
28 | // str = com.intellij.openapi.util.text.StringUtil.escapeXml(str);
29 | str = str.replaceAll("\\r|\\n|\\r\\n", "");
30 | str = str.replaceAll("([\\[\\]\\|])", "\\$1");
31 | return str;
32 | }
33 |
34 | var NEWLINE = "\n";
35 |
36 | function output() {
37 | for (var i = 0; i < arguments.length; i++) {
38 | OUT.append(arguments[i].toString());
39 | }
40 | }
41 |
42 | function outputln() {
43 | for (var i = 0; i < arguments.length; i++) {
44 | OUT.append(arguments[i].toString());
45 | }
46 | OUT.append("\n");
47 | }
48 |
49 | function outputRow(items) {
50 | output("| ");
51 | for (var i = 0; i < items.length; i++) output(escape(items[i]), " |");
52 | output("", NEWLINE);
53 | }
54 |
55 | function isObjectLike(param) {
56 | return !!param && typeof param === "object";
57 | }
58 |
59 | function isNumeric(arg) {
60 | return !!arg.match(/^(?:[+-]?[0-9]+(?:\.[0-9]*)?|[+-]?[0-9]*\.[0-9]+)(?:E[+-]?[0-9]+)?$/);
61 | }
62 |
63 | /**
64 | *
65 | * @param arr {Array}
66 | * @returns {boolean}
67 | */
68 | function allStrings(arr) {
69 | var iMax = arr.length;
70 | for (i = 0; i < iMax; i++) {
71 | if (!arr[i].toString().match(/[a-zA-Z_][a-zA-Z_0-9]*/)) {
72 | return false;
73 | }
74 | }
75 | return true;
76 | // return arr.every(function (value) {
77 | // return value.match(/[a-zA-Z_][a-zA-Z_0-9]*/);
78 | // });
79 | }
80 |
81 | // com.intellij.openapi.util.text.StringUtil.escapeXml(str)
82 | function javaName(str, capitalize, pluralize, dropLastId) {
83 | var s = [];
84 | var spl = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str);
85 |
86 | var iMax = spl.length;
87 | for (i = 0; i < iMax; i++) {
88 | var part = spl[i].toString();
89 | if (!(dropLastId && i + 1 === iMax && iMax > 1 && part.toLowerCase() === "id")) {
90 | s.push(part);
91 | }
92 | }
93 |
94 | s = s.map(function (value, i) {
95 | var part = value;
96 | if (pluralize && i + 1 === s.length) {
97 | part = com.intellij.openapi.util.text.StringUtil.pluralize(part) || part;
98 | }
99 |
100 | return part.substr(0, 1).toUpperCase() + part.substring(1).toLowerCase();
101 | });
102 |
103 | var name = s.map(function (it) {
104 | return it.replace(/[^a-zA-Z0-9_$]+/, "_");
105 | }).join("");
106 |
107 | return capitalize || name.length === 1 ? name : name.substr(0, 1).toLowerCase() + name.substring(1)
108 | }
109 |
110 | function snakeName(str, capitalize, pluralize, dropLastId) {
111 | var s = [];
112 | var spl = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str);
113 |
114 | var iMax = spl.length;
115 | for (i = 0; i < iMax; i++) {
116 | var part = spl[i].toString();
117 | if (!(dropLastId && i + 1 === iMax && iMax > 1 && part.toLowerCase() === "id")) {
118 | s.push(part);
119 | }
120 | }
121 |
122 | s = s.map(function (value, i) {
123 | var part = value;
124 | if (pluralize && i + 1 === s.length) {
125 | part = com.intellij.openapi.util.text.StringUtil.pluralize(part) || part;
126 | }
127 |
128 | return part.substr(0, 1).toUpperCase() + part.substring(1).toLowerCase();
129 | });
130 |
131 | return s.map(function (it) {
132 | return it.replace(/[^a-zA-Z0-9$]+/, "_");
133 | }).join("_");
134 | }
135 |
136 | function values(obj) {
137 | var key;
138 | var values = [];
139 |
140 | for (key in obj) {
141 | if (obj.hasOwnProperty(key)) {
142 | values.push(obj[key]);
143 | }
144 | }
145 | return values;
146 | }
147 |
148 | function keys(obj) {
149 | var key;
150 | var keys = [];
151 |
152 | for (key in obj) {
153 | if (obj.hasOwnProperty(key)) {
154 | keys.push(key);
155 | }
156 | }
157 | return keys;
158 | }
159 |
160 | var rows = [];
161 | var columnNames;
162 | var enumValueNames = [];
163 | var enumNames = [];
164 | var enumNameColumns = [];
165 | var enumNamesColumn = -1;
166 | var columns = [];
167 | var enumName = "";
168 |
169 | columnNames = mapEach(COLUMNS, function (col) {
170 | return col.name();
171 | });
172 |
173 | eachWithIdx(ROWS, function (row) {
174 | rows.push(mapEach(COLUMNS, function (col) {
175 | return FORMATTER.format(row, col);
176 | }));
177 | });
178 |
179 | columns = rows[0].map(function () {
180 | return [];
181 | });
182 |
183 | // need columns
184 | rows.forEach(function (row, rowIndex) {
185 | row.forEach(function (value, columnIndex) {
186 | columns[columnIndex].push(value);
187 | })
188 | });
189 |
190 | // name taken from the first column, less id if it is not just id
191 | enumName = javaName(columnNames[0], true, true, true);
192 | if (enumName === "") {
193 | enumName = "TestEnum";
194 | }
195 |
196 | // find first non-numeric column for enum names
197 | var iMax = columns.length;
198 | var i;
199 | for (i = 0; i < iMax; i++) {
200 | var column = columns[i];
201 | if (allStrings(column)) {
202 | enumNamesColumn = i;
203 | enumValueNames = [];
204 | var jMax = column.length;
205 | var j;
206 | for (j = 0; j < jMax; j++) {
207 | enumValueNames.push(snakeName(column[j], false, false, false).toUpperCase());
208 | }
209 |
210 | break;
211 | }
212 | }
213 |
214 | // use default enum value names
215 | if (enumNamesColumn < 0) {
216 | enumNamesColumn = 0;
217 | var valueName = snakeName(columnNames[0], false, false, true).toUpperCase();
218 | enumValueNames = columns[0].map(function (value) {
219 | return valueName + "_" + value;
220 | });
221 | }
222 |
223 | enumNameColumns = {};
224 | enumNames = columnNames
225 | .map(function (value, index) {
226 | var name = javaName(value, false, false, false);
227 |
228 | if (index === 0 && javaName(value, true, true, true).toLowerCase() === enumName.toLowerCase()) {
229 | name = "id";
230 | }
231 | enumNameColumns[name] = index;
232 | return name;
233 | });
234 |
235 | enumName = enumName + enumNameSuffix;
236 | var columnTypes = enumNames.map(function (value) {
237 | if (allStrings(columns[enumNameColumns[value]])) {
238 | return "String";
239 | } else {
240 | return "Int";
241 | }
242 | });
243 |
244 | /**
245 | *
246 | * @param callback (name, type, colIndex) return
247 | * @param prefix will be output iff enumNames is not empty and have output
248 | * @param delimiter will be used between outputs
249 | * @param suffix will be output iff enumNames is not empty and have output
250 | *
251 | *
252 | */
253 | function forAllEnumParams(callback, prefix, delimiter, suffix) {
254 | var sep = prefix;
255 | var hadOutput = false;
256 | enumNames.forEach(function (value) {
257 | var enumNameColumn = enumNameColumns[value];
258 | var type = columnTypes[enumNameColumn];
259 | var out = callback(value, type, enumNameColumn);
260 | if (out !== undefined && out !== null) {
261 | output(sep);
262 | sep = delimiter;
263 | output(out);
264 | hadOutput = true;
265 | }
266 | });
267 |
268 | if (hadOutput && suffix !== undefined && suffix !== null) {
269 | output(suffix);
270 | }
271 | }
272 |
273 | // Start of output
274 | // may need constructor
275 | if (packageName) {
276 | outputln("package " + packageName);
277 | outputln("");
278 | }
279 |
280 | output("enum class " + enumName);
281 |
282 | forAllEnumParams(function (name, type, isId, colIndex) {
283 | return "val " + name + ": " + type;
284 | }, "(", ", ", ")");
285 |
286 | outputln(" {");
287 |
288 | var sep = "";
289 | enumValueNames.forEach(function (value, enumValueIndex) {
290 | output(sep);
291 | sep = ",\n";
292 | output(" ", value);
293 |
294 | forAllEnumParams(function (name, type, colIndex) {
295 | var columnElement = columns[colIndex][enumValueIndex];
296 | return type === "String" ? '"' + columnElement.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"' : columnElement;
297 | }, "(", ", ", ")");
298 | });
299 | outputln(";");
300 |
301 | forAllEnumParams(function (name, type, colIndex) {
302 | return [" fun ", javaName(columnNames[colIndex]), "(", name, ": ", type, "): ", enumName, "? = values().find { it.", name, " == ", name, " }"].join("");
303 | }, "\n companion object {\n", "\n", "\n }\n");
304 |
305 | outputln("}");
306 |
307 |
--------------------------------------------------------------------------------
/extensions/com.intellij.database/data/extractors/Scala-Object-Enum.js:
--------------------------------------------------------------------------------
1 | /*
2 | Data extractor of table data to Kotlin Enum based on values
3 |
4 | First non-numeric column is considered the name of the enum values, the rest will be used as properties of the enum value
5 |
6 | Transposed status is ignored
7 | */
8 |
9 | var packageName = ""; // package used for generated class files
10 | var enumNameSuffix = ""; // appended to class file name
11 |
12 | function eachWithIdx(iterable, f) {
13 | var i = iterable.iterator();
14 | var idx = 0;
15 | while (i.hasNext()) f(i.next(), idx++);
16 | }
17 |
18 | function mapEach(iterable, f) {
19 | var vs = [];
20 | eachWithIdx(iterable, function (i) {
21 | vs.push(f(i));
22 | });
23 | return vs;
24 | }
25 |
26 | function escape(str) {
27 | str = str.replaceAll("\t|\b|\\f", "");
28 | // str = com.intellij.openapi.util.text.StringUtil.escapeXml(str);
29 | str = str.replaceAll("\\r|\\n|\\r\\n", "");
30 | str = str.replaceAll("([\\[\\]\\|])", "\\$1");
31 | return str;
32 | }
33 |
34 | var NEWLINE = "\n";
35 |
36 | function output() {
37 | for (var i = 0; i < arguments.length; i++) {
38 | OUT.append(arguments[i].toString());
39 | }
40 | }
41 |
42 | function outputln() {
43 | for (var i = 0; i < arguments.length; i++) {
44 | OUT.append(arguments[i].toString());
45 | }
46 | OUT.append("\n");
47 | }
48 |
49 | function outputRow(items) {
50 | output("| ");
51 | for (var i = 0; i < items.length; i++) output(escape(items[i]), " |");
52 | output("", NEWLINE);
53 | }
54 |
55 | function isObjectLike(param) {
56 | return !!param && typeof param === "object";
57 | }
58 |
59 | function isNumeric(arg) {
60 | return !!arg.match(/^(?:[+-]?[0-9]+(?:\.[0-9]*)?|[+-]?[0-9]*\.[0-9]+)(?:E[+-]?[0-9]+)?$/);
61 | }
62 |
63 | /**
64 | *
65 | * @param arr {Array}
66 | * @returns {boolean}
67 | */
68 | function allStrings(arr) {
69 | var iMax = arr.length;
70 | for (i = 0; i < iMax; i++) {
71 | if (!arr[i].toString().match(/[a-zA-Z_][a-zA-Z_0-9]*/)) {
72 | return false;
73 | }
74 | }
75 | return true;
76 | // return arr.every(function (value) {
77 | // return value.match(/[a-zA-Z_][a-zA-Z_0-9]*/);
78 | // });
79 | }
80 |
81 | // com.intellij.openapi.util.text.StringUtil.escapeXml(str)
82 | function javaName(str, capitalize, pluralize, dropLastId) {
83 | var s = [];
84 | var spl = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str);
85 |
86 | var iMax = spl.length;
87 | for (i = 0; i < iMax; i++) {
88 | var part = spl[i].toString();
89 | if (!(dropLastId && i + 1 === iMax && iMax > 1 && part.toLowerCase() === "id")) {
90 | s.push(part);
91 | }
92 | }
93 |
94 | s = s.map(function (value, i) {
95 | var part = value;
96 | if (pluralize && i + 1 === s.length) {
97 | part = com.intellij.openapi.util.text.StringUtil.pluralize(part) || part;
98 | }
99 |
100 | return part.substr(0, 1).toUpperCase() + part.substring(1).toLowerCase();
101 | });
102 |
103 | var name = s.map(function (it) {
104 | return it.replace(/[^a-zA-Z0-9_$]+/, "_");
105 | }).join("");
106 |
107 | return capitalize || name.length === 1 ? name : name.substr(0, 1).toLowerCase() + name.substring(1)
108 | }
109 |
110 | function snakeName(str, capitalize, pluralize, dropLastId) {
111 | var s = [];
112 | var spl = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str);
113 |
114 | var iMax = spl.length;
115 | for (i = 0; i < iMax; i++) {
116 | var part = spl[i].toString();
117 | if (!(dropLastId && i + 1 === iMax && iMax > 1 && part.toLowerCase() === "id")) {
118 | s.push(part);
119 | }
120 | }
121 |
122 | s = s.map(function (value, i) {
123 | var part = value;
124 | if (pluralize && i + 1 === s.length) {
125 | part = com.intellij.openapi.util.text.StringUtil.pluralize(part) || part;
126 | }
127 |
128 | return part.substr(0, 1).toUpperCase() + part.substring(1).toLowerCase();
129 | });
130 |
131 | return s.map(function (it) {
132 | return it.replace(/[^a-zA-Z0-9$]+/, "_");
133 | }).join("_");
134 | }
135 |
136 | function values(obj) {
137 | var key;
138 | var values = [];
139 |
140 | for (key in obj) {
141 | if (obj.hasOwnProperty(key)) {
142 | values.push(obj[key]);
143 | }
144 | }
145 | return values;
146 | }
147 |
148 | function keys(obj) {
149 | var key;
150 | var keys = [];
151 |
152 | for (key in obj) {
153 | if (obj.hasOwnProperty(key)) {
154 | keys.push(key);
155 | }
156 | }
157 | return keys;
158 | }
159 |
160 | var rows = [];
161 | var columnNames;
162 | var enumValueNames = [];
163 | var enumNames = [];
164 | var enumNameColumns = [];
165 | var enumNamesColumn = -1;
166 | var columns = [];
167 | var enumName = "";
168 |
169 | columnNames = mapEach(COLUMNS, function (col) {
170 | return col.name();
171 | });
172 |
173 | eachWithIdx(ROWS, function (row) {
174 | rows.push(mapEach(COLUMNS, function (col) {
175 | return FORMATTER.format(row, col);
176 | }));
177 | });
178 |
179 | columns = rows[0].map(function () {
180 | return [];
181 | });
182 |
183 | // need columns
184 | rows.forEach(function (row, rowIndex) {
185 | row.forEach(function (value, columnIndex) {
186 | columns[columnIndex].push(value);
187 | })
188 | });
189 |
190 | // name taken from the first column, less id if it is not just id
191 | enumName = javaName(columnNames[0], true, true, true);
192 | if (enumName === "") {
193 | enumName = "TestEnum";
194 | }
195 |
196 | // find first non-numeric column for enum names
197 | var iMax = columns.length;
198 | var i;
199 | for (i = 0; i < iMax; i++) {
200 | var column = columns[i];
201 | if (allStrings(column)) {
202 | enumNamesColumn = i;
203 | enumValueNames = [];
204 | var jMax = column.length;
205 | var j;
206 | for (j = 0; j < jMax; j++) {
207 | enumValueNames.push(snakeName(column[j], false, false, false).toUpperCase());
208 | }
209 |
210 | break;
211 | }
212 | }
213 |
214 | // use default enum value names
215 | if (enumNamesColumn < 0) {
216 | enumNamesColumn = 0;
217 | var valueName = snakeName(columnNames[0], false, false, true).toUpperCase();
218 | enumValueNames = columns[0].map(function (value) {
219 | return valueName + "_" + value;
220 | });
221 | }
222 |
223 | enumNameColumns = {};
224 | enumNames = columnNames
225 | .map(function (value, index) {
226 | var name = javaName(value, false, false, false);
227 |
228 | if (index === 0 && javaName(value, true, true, true).toLowerCase() === enumName.toLowerCase()) {
229 | name = "id";
230 | }
231 | enumNameColumns[name] = index;
232 | return name;
233 | });
234 |
235 | enumName = enumName + enumNameSuffix;
236 | var columnTypes = enumNames.map(function (value) {
237 | if (allStrings(columns[enumNameColumns[value]])) {
238 | return "String";
239 | } else {
240 | return "Int";
241 | }
242 | });
243 |
244 | /**
245 | *
246 | * @param callback (name, type, colIndex) return
247 | * @param prefix will be output iff enumNames is not empty and have output
248 | * @param delimiter will be used between outputs
249 | * @param suffix will be output iff enumNames is not empty and have output
250 | *
251 | *
252 | */
253 | function forAllEnumParams(callback, prefix, delimiter, suffix) {
254 | var sep = prefix;
255 | var hadOutput = false;
256 | enumNames.forEach(function (value) {
257 | var enumNameColumn = enumNameColumns[value];
258 | var type = columnTypes[enumNameColumn];
259 | var out = callback(value, type, enumNameColumn);
260 | if (out !== undefined && out !== null) {
261 | output(sep);
262 | sep = delimiter;
263 | output(out);
264 | hadOutput = true;
265 | }
266 | });
267 |
268 | if (hadOutput && suffix !== undefined && suffix !== null) {
269 | output(suffix);
270 | }
271 | }
272 |
273 | // Start of output
274 | // may need constructor
275 | if (packageName) {
276 | outputln("package " + packageName);
277 | outputln("");
278 | }
279 |
280 | output("object " + enumName);
281 |
282 | // forAllEnumParams(function (name, type, isId, colIndex) {
283 | // return "val " + name + ": " + type;
284 | // }, "(", ", ", ")");
285 |
286 | outputln(" {");
287 |
288 | var sep = "";
289 | enumValueNames.forEach(function (value, enumValueIndex) {
290 | output(sep);
291 | sep = ",\n";
292 | output(" val ", value, " = ", columns[0][enumValueIndex]);
293 |
294 | // forAllEnumParams(function (name, type, colIndex) {
295 | // var columnElement = columns[colIndex][enumValueIndex];
296 | // return type === "String" ? '"' + columnElement.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"' : columnElement;
297 | // }, "(", ", ", ")");
298 | });
299 | // outputln(";");
300 |
301 | // forAllEnumParams(function (name, type, colIndex) {
302 | // return [" fun ", javaName(columnNames[colIndex]), "(", name, ": ", type, "): ", enumName, "? = values().find { it.", name, " == ", name, " }"].join("");
303 | // }, "\n companion object {\n", "\n", "\n }\n");
304 |
305 | outputln("}");
306 |
307 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.0.0
5 |
6 | com.vladsch.kotlin-jdbc
7 | kotlin-jdbc
8 | 0.5.2
9 | jar
10 |
11 | Kotlin-JDBC
12 |
13 | A thin library that exposes JDBC API with the convenience of Kotlin and gets out of the way when not needed.
14 |
15 |
16 | https://github.com/vsch/reverse-regex
17 |
18 |
19 | UTF-8
20 | 1.3.71
21 | 4.12
22 | 0.9.9
23 | UTF-8
24 | ${project.basedir}/target/apidocs/
25 |
26 | false
27 |
28 |
29 |
30 |
31 |
32 | MIT
33 | https://github.com/vsch/kotlin-jdbc/blob/master/LICENSE
34 | repo
35 |
36 |
37 |
38 |
39 |
40 | Vladimir Schneider
41 | vladimir@vladsch.com
42 | vladsch.com
43 | https://vladsch.com/
44 |
45 |
46 |
47 |
48 | scm:git:git@github.com:vsch/kotlin-jdbc.git
49 | scm:git:git@github.com:vsch/kotlin-jdbc.git
50 | https://github.com/vsch/kotlin-jdbc
51 | HEAD
52 |
53 |
54 |
55 |
56 | ossrh
57 | https://oss.sonatype.org/content/repositories/snapshots
58 |
59 |
60 | ossrh
61 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
62 |
63 |
64 |
65 |
66 |
67 | spring
68 | http://repo.spring.io/plugins-release/
69 |
70 |
71 |
72 |
73 |
74 | org.jetbrains.kotlin
75 | kotlin-test-junit
76 | ${kotlin.version}
77 | test
78 |
79 |
80 | org.jetbrains.kotlin
81 | kotlin-reflect
82 | ${kotlin.version}
83 |
84 |
85 | org.jetbrains.kotlin
86 | kotlin-stdlib-common
87 | ${kotlin.version}
88 |
89 |
90 | org.jetbrains.kotlin
91 | kotlin-stdlib-jdk8
92 | ${kotlin.version}
93 |
94 |
95 | junit
96 | junit
97 | ${junit.version}
98 | test
99 |
100 |
101 | com.zaxxer
102 | HikariCP
103 | 3.1.0
104 |
105 |
106 | org.jetbrains
107 | annotations
108 | 15.0
109 |
110 |
111 | com.vladsch.boxed-json
112 | boxed-json
113 | 0.5.32
114 |
115 |
116 | com.h2database
117 | h2
118 | 1.4.196
119 | test
120 |
121 |
122 | joda-time
123 | joda-time
124 | 2.9.9
125 |
126 |
127 | org.hamcrest
128 | hamcrest-core
129 | 1.3
130 | test
131 |
132 |
133 | org.slf4j
134 | slf4j-api
135 | 1.7.25
136 |
137 |
138 |
139 |
140 | src/main/kotlin
141 | src/test/kotlin
142 |
143 |
144 |
145 | org.apache.maven.plugins
146 | maven-gpg-plugin
147 | 1.6
148 |
149 |
150 | sign-artifacts
151 | verify
152 |
153 | sign
154 |
155 |
156 |
157 |
158 |
159 | maven-deploy-plugin
160 | 2.8.1
161 |
162 | true
163 |
164 |
165 |
166 | maven-source-plugin
167 | 2.2.1
168 |
169 |
170 | package-sources
171 | package
172 |
173 | jar
174 |
175 |
176 |
177 |
178 |
179 |
180 | org.jetbrains.dokka
181 | dokka-maven-plugin
182 | 0.9.16
183 |
184 |
185 | prepare-package
186 |
187 | dokka
188 | javadoc
189 | javadocJar
190 |
191 |
192 |
193 |
194 |
199 |
200 |
201 |
202 | org.sonatype.plugins
203 | nexus-staging-maven-plugin
204 | 1.6
205 | true
206 |
207 | ossrh
208 | https://oss.sonatype.org/
209 |
210 |
211 |
212 | deploy-to-sonatype
213 | deploy
214 |
215 | deploy
216 |
217 |
218 |
219 |
220 |
221 | org.jetbrains.kotlin
222 | kotlin-maven-plugin
223 | ${kotlin.version}
224 |
225 |
226 | compile
227 | compile
228 |
229 | compile
230 |
231 |
232 |
233 | test-compile
234 | test-compile
235 |
236 | test-compile
237 |
238 |
239 |
240 |
241 | 1.8
242 |
243 |
244 |
245 |
246 |
247 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/DbEntity.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import java.io.File
4 | import java.io.FilenameFilter
5 |
6 | @Suppress("MemberVisibilityCanBePrivate")
7 | enum class DbEntity(val dbEntity: String, val displayName: String, val dbEntityDirectory: String, val fileSuffix: String) {
8 |
9 | FUNCTION("FUNCTION", "function", "functions", ".udf.sql"),
10 | PROCEDURE("PROCEDURE", "procedure", "procedures", ".prc.sql"),
11 | TABLE("TABLE", "table", "tables", ".tbl.sql"),
12 | TRIGGER("TRIGGER", "insert trigger", "triggers", ".trg.sql"),
13 | VIEW("VIEW", "view", "views", ".view.sql"),
14 | MIGRATION("", "migration", "migrations", ".up.sql"),
15 | ROLLBACK("", "rollback", "migrations", ".down.sql"),
16 | ;
17 |
18 | companion object {
19 | fun isEntityDirectory(name: String): Boolean {
20 | return values().any { it.dbEntityDirectory == name }
21 | }
22 | }
23 |
24 | fun getEntityDirectory(dbVersionDir: File, createDir: Boolean?): File {
25 | if (createDir != null) {
26 | dbVersionDir.ensureExistingDirectory("dbDir/dbVersion")
27 | }
28 |
29 | // may need to create table directory
30 | val entityDir = dbVersionDir + this.dbEntityDirectory
31 |
32 | if (createDir == true) {
33 | entityDir.ensureCreateDirectory("dbDir/dbVersion/${this.dbEntityDirectory}")
34 | } else if (createDir == false) {
35 | entityDir.ensureExistingDirectory("dbDir/dbVersion/${this.dbEntityDirectory}")
36 | }
37 | return entityDir
38 | }
39 |
40 | fun removeSuffix(fileName: String): String {
41 | return fileName.removeSuffix(fileSuffix)
42 | }
43 |
44 | fun addSuffix(fileName: String): String {
45 | return fileName + fileSuffix
46 | }
47 |
48 | fun getEntityDirectory(dbDir: File, dbProfile: String, dbVersion: String, createDir: Boolean?): File {
49 | val dbVersionDir = getVersionDirectory(dbDir, dbProfile, dbVersion, createDir)
50 | return getEntityDirectory(dbVersionDir, createDir)
51 | }
52 |
53 | fun getEntityResourceDirectory(dbProfile: String, dbVersion: String): File {
54 | val dbVersionDir = File("/db/$dbProfile") + dbVersion
55 |
56 | // may need to create table directory
57 | val entityDir = dbVersionDir + this.dbEntityDirectory
58 |
59 | return entityDir
60 | }
61 |
62 | fun getEntitySample(dbDir: File, resourceClass: Class<*>): String {
63 | val templateFile = ((dbDir + "templates") + dbEntityDirectory) + getEntitySampleName()
64 | var sampleText: String? = null
65 |
66 | if (templateFile.exists()) {
67 | sampleText = getFileContent(templateFile)
68 | }
69 |
70 | if (sampleText == null) {
71 | val samplesResourcePath = "/sample-db/V0_0_0/$dbEntityDirectory/${getEntitySampleName()}"
72 | sampleText = getResourceAsString(resourceClass, samplesResourcePath)
73 | }
74 | return sampleText
75 | }
76 |
77 | fun isEntityFile(file: String): Boolean {
78 | return file.endsWith(this.fileSuffix)
79 | }
80 |
81 | fun isEntityFile(file: File): Boolean {
82 | return file.name.endsWith(this.fileSuffix)
83 | }
84 |
85 | fun getEntitySampleName(): String {
86 | return if (this === MIGRATION || this === ROLLBACK) {
87 | "0.sample$fileSuffix"
88 | } else {
89 | "sample$fileSuffix"
90 | }
91 | }
92 |
93 | fun getEntityName(fileName: String): String {
94 | var entityName = fileName
95 |
96 | if (this === MIGRATION || this === ROLLBACK) {
97 | // has optional leading count with .
98 | val (count, name) = fileName.extractLeadingDigits()
99 | entityName = name
100 | }
101 |
102 | return entityName.substring(0, entityName.length - this.fileSuffix.length)
103 | }
104 |
105 | fun getEntityName(file: File): String {
106 | return getEntityName(file.name)
107 | }
108 |
109 | fun getEntityFiles(entityDir: File): List {
110 | val entityList = ArrayList()
111 | if (entityDir.exists()) {
112 | entityDir.list(FilenameFilter { file, name -> name.endsWith(this.fileSuffix) })
113 | .map { entityDir + it }
114 | .filter { it.isFile }
115 | .forEach {
116 | entityList.add(it.path)
117 | }
118 | }
119 | return entityList
120 | }
121 |
122 | fun getEntityFiles(dbDir: File, dbProfile: String, dbVersion: String, createDir: Boolean? = true): List {
123 | val entityDir = this.getEntityDirectory(dbDir, dbProfile, dbVersion, createDir)
124 | if (createDir == true) {
125 | entityDir.ensureCreateDirectory("dbDir/dbProfile/dbVersion/${this.dbEntityDirectory}")
126 | } else if (createDir == false) {
127 | entityDir.ensureExistingDirectory("dbDir/dbProfile/dbVersion/${this.dbEntityDirectory}")
128 | } else {
129 | return listOf()
130 | }
131 |
132 | return getEntityFiles(entityDir)
133 | }
134 |
135 | fun getEntityResourceFiles(resourceClass: Class<*>, dbProfile:String, dbVersion: String): List {
136 | val entityDir = this.getEntityResourceDirectory(dbProfile, dbVersion)
137 | return getResourceFiles(resourceClass, entityDir.path)
138 | }
139 |
140 | fun getEntityFile(entityDir: File, entityName: String): File {
141 | return entityDir + (entityName + this.fileSuffix)
142 | }
143 |
144 | fun getEntityFile(dbDir: File, dbProfile: String, dbVersion: String, entityName: String, createDir: Boolean? = true): File {
145 | val entityDir = getEntityDirectory(dbDir, dbProfile, dbVersion, createDir)
146 | return getEntityFile(entityDir, entityName)
147 | }
148 |
149 | data class EntityData(val entityName: String, val entityResourceName: String, val entityResourcePath: String, val entitySql: String)
150 |
151 | object MIGRATIONS_COMPARATOR : Comparator {
152 | override fun compare(o1: DbEntity.EntityData?, o2: DbEntity.EntityData?): Int {
153 | val (num1, name1) = o1?.entityResourceName.extractLeadingDigits()
154 | val (num2, name2) = o2?.entityResourceName.extractLeadingDigits()
155 |
156 | return if (num1 != null && num2 != null) {
157 | val nums = num1.compareTo(num2)
158 | if (nums == 0) name1.compareTo(name2)
159 | else nums
160 | } else {
161 | name1.compareTo(name2)
162 | }
163 | }
164 | }
165 |
166 | object MIGRATIONS_NAME_COMPARATOR : Comparator {
167 | override fun compare(o1: String?, o2: String?): Int {
168 | val (num1, name1) = o1.extractLeadingDigits()
169 | val (num2, name2) = o2.extractLeadingDigits()
170 |
171 | return if (num1 != null && num2 != null) {
172 | val nums = num1.compareTo(num2)
173 | if (nums == 0) name1.compareTo(name2)
174 | else nums
175 | } else {
176 | name1.compareTo(name2)
177 | }
178 | }
179 | }
180 |
181 | fun extractEntityName(dbEntityExtractor: DbEntityExtractor, entitySql: String): String? {
182 | val entityNameRegEx = dbEntityExtractor.getExtractEntityNameRegEx(this) ?: return null
183 | return extractEntityName(entityNameRegEx, entitySql)
184 | }
185 |
186 | fun extractEntityName(entityNameRegEx: Regex, entitySql: String): String? {
187 | val matchGroup = entityNameRegEx.find(entitySql)
188 | if (matchGroup != null) {
189 | return matchGroup.groupValues[1].removeSurrounding("`")
190 | }
191 | return null
192 | }
193 |
194 | fun validEntityFileName(entityName: String, entityNameFile: String): String? {
195 | if (entityName != entityNameFile) {
196 | return "$entityName${this.fileSuffix}"
197 | }
198 | return null
199 | }
200 |
201 | /**
202 | * Get entity to EntityScript
203 | *
204 | * @return Map
205 | */
206 | fun getEntityResourceScripts(resourceClass: Class<*>, dbEntityExtractor: DbEntityExtractor, entityDir: File): Map {
207 | val entityNameRegEx = dbEntityExtractor.getExtractEntityNameRegEx(this);
208 | val files = getResourceFiles(resourceClass, entityDir.path)
209 |
210 | val entities = HashMap()
211 | for (file in files) {
212 | if (file.endsWith(fileSuffix)) {
213 | val entityNameFile = file.substring(0, file.length - this.fileSuffix.length)
214 | val entitySql = getResourceAsString(resourceClass, (entityDir + file).path)
215 | if (entityNameRegEx == null) {
216 | // no name check
217 | val entityName = this.removeSuffix(entityNameFile)
218 | val entityLowercaseName = entityName.toLowerCase()
219 | entities[entityLowercaseName] = EntityData(entityName, file, (entityDir + file).path, entitySql);
220 | } else {
221 | val matchGroup = entityNameRegEx.find(entitySql)
222 | if (matchGroup != null) {
223 | val entityName = matchGroup.groupValues[1].removeSurrounding("`")
224 | val entityLowercaseName = entityName.toLowerCase()
225 |
226 | if (entityName != entityNameFile) {
227 | throw IllegalStateException("File $file for $displayName $entityName should be named $entityName${this.fileSuffix}")
228 | }
229 |
230 | if (entities.contains(entityLowercaseName)) {
231 | throw IllegalStateException("Duplicate SQL $displayName in File $file, $entityName already defined in ${entities[entityName]}")
232 | }
233 |
234 | entities[entityLowercaseName] = EntityData(entityName, file, (entityDir + file).path, entitySql);
235 | } else {
236 | throw IllegalStateException("Invalid SQL $displayName file $file, cannot find $displayName name")
237 | }
238 | }
239 | }
240 | }
241 | return entities
242 | }
243 |
244 | fun getEntityResourceScripts(resourceClass: Class<*>, dbEntityExtractor: DbEntityExtractor, dbProfile: String, dbVersion: String): Map {
245 | val entityDir = getEntityDirectory(File("/db"), dbProfile, dbVersion, null)
246 | return getEntityResourceScripts(resourceClass, dbEntityExtractor, entityDir)
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/vladsch/kotlin/jdbc/SessionImpl.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import com.vladsch.boxed.json.MutableJsArray
4 | import com.vladsch.boxed.json.MutableJsObject
5 | import org.slf4j.LoggerFactory
6 | import java.sql.CallableStatement
7 | import java.sql.PreparedStatement
8 | import java.sql.ResultSet
9 | import java.sql.Statement
10 | import javax.json.JsonArray
11 | import javax.json.JsonObject
12 | import javax.sql.DataSource
13 |
14 | open class SessionImpl(
15 | override val connection: Connection,
16 | override val autoGeneratedKeys: List = listOf()
17 | ) : Session {
18 |
19 | override val identifierQuoteString: String
20 | get() = connection.underlying.metaData.identifierQuoteString
21 |
22 | companion object {
23 | private val logger = LoggerFactory.getLogger(Session::class.java)
24 |
25 | var defaultDataSource: (() -> DataSource)? = null
26 | }
27 |
28 | override fun close() {
29 | connection.close()
30 | }
31 |
32 | override fun use(block: (Session) -> Unit) {
33 | this.use {
34 | block.invoke(this)
35 | }
36 | }
37 |
38 | override fun prepare(query: SqlQuery, returnGeneratedKeys: Boolean): PreparedStatement {
39 | val stmt = if (returnGeneratedKeys) {
40 | // if (connection.driverName == "oracle.jdbc.driver.OracleDriver") {
41 | // connection.underlying.prepareStatement(query.cleanStatement, autoGeneratedKeys.toTypedArray())
42 | // } else {
43 | // connection.underlying.prepareStatement(query.cleanStatement, Statement.RETURN_GENERATED_KEYS)
44 | // }
45 | connection.underlying.prepareStatement(query.cleanStatement, Statement.RETURN_GENERATED_KEYS)
46 | } else {
47 | connection.underlying.prepareStatement(query.cleanStatement)
48 | }
49 |
50 | query.populateParams(stmt)
51 | return stmt
52 | }
53 |
54 | override fun prepare(query: SqlQueryBase<*>): PreparedStatement {
55 | val stmt = connection.underlying.prepareStatement(query.cleanStatement)
56 |
57 | query.populateParams(stmt)
58 | return stmt
59 | }
60 |
61 | override fun prepare(query: SqlCall): CallableStatement {
62 | val stmt = connection.underlying.prepareCall(query.cleanStatement)
63 | query.populateParams(stmt)
64 | return stmt
65 | }
66 |
67 | override fun query(query: SqlQueryBase<*>, consumer: (ResultSet) -> A): A {
68 | return using(prepare(query)) { stmt ->
69 | using(stmt.executeQuery()) { rs ->
70 | consumer.invoke(rs)
71 | }
72 | }
73 | }
74 |
75 | override fun executeWithKeys(query: SqlQuery, consumer: (PreparedStatement) -> A): A? {
76 | return using(prepare(query, true)) { stmt ->
77 | if (stmt.execute()) {
78 | if (connection.driverName == "oracle.jdbc.driver.OracleDriver") {
79 | connection.underlying.prepareStatement(query.cleanStatement, autoGeneratedKeys.toTypedArray())
80 | } else {
81 | connection.underlying.prepareStatement(query.cleanStatement, Statement.RETURN_GENERATED_KEYS)
82 | }
83 | consumer.invoke(stmt)
84 | } else {
85 | null
86 | }
87 | }
88 | }
89 |
90 | override fun execute(query: SqlQueryBase<*>, consumer: (PreparedStatement) -> A): A? {
91 | return using(prepare(query)) { stmt ->
92 | if (stmt.execute()) {
93 | consumer.invoke(stmt)
94 | } else {
95 | null
96 | }
97 | }
98 | }
99 |
100 | override fun execute(query: SqlCall, consumer: (CallableStatement) -> A): A? {
101 | return using(prepare(query)) { stmt ->
102 | if (stmt.execute()) {
103 | consumer.invoke(stmt)
104 | } else {
105 | null
106 | }
107 | }
108 | }
109 |
110 | override fun updateWithKeys(query: SqlQuery, consumer: (PreparedStatement) -> A): A? {
111 | return using(prepare(query, true)) { stmt ->
112 | if (stmt.executeUpdate() > 0) {
113 | consumer.invoke(stmt)
114 | } else {
115 | null
116 | }
117 | }
118 | }
119 |
120 | override fun update(query: SqlQueryBase<*>, consumer: (PreparedStatement) -> A): A? {
121 | return using(prepare(query)) { stmt ->
122 | if (stmt.executeUpdate() > 0) {
123 | consumer.invoke(stmt)
124 | } else {
125 | null
126 | }
127 | }
128 | }
129 |
130 | override fun list(query: SqlQueryBase<*>, extractor: (Row) -> A): List {
131 | return query(query) { rs ->
132 | val rows = ArrayList()
133 | Rows(rs).forEach {
134 | rows.add(extractor.invoke(it))
135 | }
136 | rows
137 | }
138 | }
139 |
140 | override fun jsonArray(query: SqlQueryBase<*>, extractor: (Row) -> JsonObject): JsonArray {
141 | return query(query) { rs ->
142 | val rows = MutableJsArray()
143 | Rows(rs).forEach {
144 | rows.add(extractor.invoke(it))
145 | }
146 | rows
147 | }
148 | }
149 |
150 | override fun count(query: SqlQueryBase<*>): Int {
151 | return query(query) { rs ->
152 | Rows(rs).count()
153 | }
154 | }
155 |
156 | override fun first(query: SqlQueryBase<*>, extractor: (Row) -> A): A? {
157 | return query(query) { rs ->
158 | if (rs.next()) {
159 | extractor.invoke(Row(rs))
160 | } else {
161 | null
162 | }
163 | }
164 | }
165 |
166 | override fun hashMap(query: SqlQueryBase<*>, keyExtractor: (Row) -> K, extractor: (Row) -> A): Map {
167 | return query(query) { rs ->
168 | val rowMap = HashMap()
169 | Rows(rs).forEach { row ->
170 | rowMap[keyExtractor.invoke(row)] = extractor.invoke(row)
171 | }
172 | rowMap;
173 | }
174 | }
175 |
176 | override fun jsonObject(query: SqlQueryBase<*>, keyExtractor: (Row) -> String, extractor: (Row) -> JsonObject): JsonObject {
177 | return query(query) { rs ->
178 | val rowMap = MutableJsObject()
179 | Rows(rs).forEach { row ->
180 | rowMap[keyExtractor.invoke(row)] = extractor.invoke(row)
181 | }
182 | rowMap;
183 | }
184 | }
185 |
186 | override fun forEach(query: SqlQueryBase<*>, operator: (Row) -> Unit): Unit {
187 | return query(query) { rs ->
188 | Rows(rs).forEach { row ->
189 | operator.invoke(row)
190 | }
191 | }
192 | }
193 |
194 | @Deprecated(message = "Use executeCall(query: SqlCall, stmtProc: (results: SqlCallResults) -> Unit) instead", replaceWith = ReplaceWith("executeCall"))
195 | override fun forEach(query: SqlCall, stmtProc: (CallableStatement) -> Unit, operator: (rs: ResultSet, index: Int) -> Unit): Unit {
196 | execute(query) { stmt: CallableStatement ->
197 | var results = stmt.execute();
198 | stmtProc.invoke(stmt as CallableStatement)
199 |
200 | var rsIndex = 0
201 | while (results) {
202 | val rs = stmt.resultSet
203 | operator.invoke(rs, rsIndex++)
204 | results = stmt.moreResults
205 | }
206 | }
207 | }
208 |
209 | override fun executeCall(query: SqlCall, stmtProc: (results: SqlCallResults) -> Unit) {
210 | execute(query) { stmt: CallableStatement ->
211 | query.handleResults(stmt, stmtProc)
212 | }
213 | }
214 |
215 | override fun execute(query: SqlQueryBase<*>): Boolean {
216 | return using(prepare(query)) { stmt ->
217 | stmt.execute()
218 | }
219 | }
220 |
221 | override fun update(query: SqlQueryBase<*>): Int {
222 | return using(prepare(query)) { stmt ->
223 | stmt.executeUpdate()
224 | }
225 | }
226 |
227 | override fun updateGetLongId(query: SqlQuery): Long? {
228 | return updateWithKeys(query) { stmt ->
229 | val rs = stmt.generatedKeys
230 | if (rs.next()) rs.getLong(1)
231 | else null
232 | }
233 | }
234 |
235 | override fun updateGetId(query: SqlQuery): Int? {
236 | return updateWithKeys(query) { stmt ->
237 | val rs = stmt.generatedKeys
238 | if (rs.next()) rs.getInt(1)
239 | else null
240 | }
241 | }
242 |
243 | override fun updateGetKey(query: SqlQuery, extractor: (Row) -> A): A? {
244 | return updateWithKeys(query) { stmt ->
245 | val rs = stmt.generatedKeys
246 | if (rs.next()) extractor.invoke(Row(rs))
247 | else null
248 | }
249 | }
250 |
251 | override fun updateGetLongIds(query: SqlQuery): List? {
252 | return updateWithKeys(query) { stmt ->
253 | val keys = ArrayList()
254 | val rs = stmt.generatedKeys
255 | while (rs.next()) {
256 | val id = rs.getLong(1)
257 | if (!rs.wasNull()) keys.add(id)
258 | }
259 | keys
260 | }
261 | }
262 |
263 | override fun updateGetIds(query: SqlQuery): List? {
264 | return updateWithKeys(query) { stmt ->
265 | val keys = ArrayList()
266 | val rs = stmt.generatedKeys
267 | while (rs.next()) {
268 | val id = rs.getInt(1)
269 | if (!rs.wasNull()) keys.add(id)
270 | }
271 | keys
272 | }
273 | }
274 |
275 | override fun updateGetKeys(query: SqlQuery, extractor: (Row) -> A): List? {
276 | return updateWithKeys(query) { stmt ->
277 | val keys = ArrayList()
278 | val rows = Rows(stmt.generatedKeys)
279 | rows.forEach { row ->
280 | val value = extractor.invoke(row)
281 | keys.add(value)
282 | }
283 | keys
284 | }
285 | }
286 |
287 | override fun transaction(operation: (Transaction) -> A): A {
288 | try {
289 | connection.begin()
290 |
291 | val tx = TransactionImpl(connection, autoGeneratedKeys)
292 | val result: A = operation.invoke(tx)
293 |
294 | if (!connection.autoCommit) {
295 | connection.commit()
296 | }
297 | return result
298 | } catch (e: Exception) {
299 | if (!connection.autoCommit) {
300 | connection.rollback()
301 | }
302 | throw e
303 | } finally {
304 | if (!connection.autoCommit) {
305 | connection.commit()
306 | }
307 | }
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator/COPY_HTML_MIME.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | aside { margin:0;padding:0 0 0 10pt;border-left:2pt solid #C80010; }
78 | blockquote { margin:0;padding:0 0 0 10;border-left:2pt solid #bbb;color:#666; }
79 | blockquote h1 { margin:1.3em 0 1em;padding:0;font-weight:bold;font-size:1.6em;border-bottom:1pt solid #ddd;color:#666; }
80 | blockquote h2 { margin:1.3em 0 1em;padding:0;font-weight:bold;font-size:1.5em;border-bottom:1px solid #eee;color:#666; }
81 | blockquote h3 { margin:1.0em 0 1em;padding:0;font-weight:bold;font-size:1.4em;color:#666; }
82 | blockquote h4 { margin:1.0em 0 1em;padding:0;font-weight:bold;font-size:1.3em;color:#666; }
83 | blockquote h5 { margin:1.0em 0 1em;padding:0;font-weight:bold;font-size:1.2em;color:#666; }
84 | blockquote h6 { margin:1.0em 0 1em;padding:0;font-weight:bold;font-size:1.1em;color:#888; }
85 | blockquote pre > code { background-color:#f8f8f8;border-radius:3px;border:1px solid #ccc;display:block;font-family:Consolas,Inconsolata,Courier,monospace;font-size:0.9em;margin:0 0.15em;overflow:auto;padding:0.5em 0.7em;white-space:pre;color:#777; }
86 | blockquote table { margin:1.2em 0;padding:0;border-collapse:collapse;border-spacing:0;font:inherit;border:0 none;color:#666; }
87 | blockquote tbody { margin:0;padding:0;border:0 none;color:#666; }
88 | blockquote td { text-align:left;font-size:1em;border:1px solid #ddd;margin:0;padding:0.5em 1em;color:#666; }
89 | blockquote th { text-align:left;font-size:1em;border:1px solid #ddd;margin:0;padding:0.5em 1em;font-weight:bold;background-color:#eee;color:#666; }
90 | blockquote tr { color:#666; }
91 | blockquote tr.even { border-right:0 none;border-width:1px 0 0;border-style:solid none none;border-color:#ccc;margin:0;padding:0;background-color:#f8f8f8;color:#666; }
92 | blockquote tr.odd { border-right:0 none;border-width:1px 0 0;border-style:solid none none;border-color:#ccc;background-color:white;margin:0;padding:0;color:#666; }
93 | code { background-color:#ffe8f8;border-radius:2px;padding:1px 2px 0 2px;border:1px solid #eec5e1;font-family:Consolas,Inconsolata,Courier,monospace;font-size:0.9em;color:#bb002f; }
94 | h1 { margin:1.3em 0 1em;padding:0;font-weight:bold;font-size:1.6em;border-bottom:1pt solid #ddd; }
95 | h2 { margin:1.3em 0 1em;padding:0;font-weight:bold;font-size:1.5em;border-bottom:1px solid #eee; }
96 | h3 { margin:1.0em 0 1em;padding:0;font-weight:bold;font-size:1.4em; }
97 | h4 { margin:1.0em 0 1em;padding:0;font-weight:bold;font-size:1.3em; }
98 | h5 { margin:1.0em 0 1em;padding:0;font-weight:bold;font-size:1.2em; }
99 | h6 { margin:1.0em 0 1em;padding:0;font-weight:bold;font-size:1.1em;color:#666; }
100 | li { }
101 | li.loose { margin:1em 0 1em 0; }
102 | li.loose p.first { margin:0; }
103 | ol { margin-left:1em;padding-left:0.5em;text-indent:0; }
104 | ol ol { list-style-type:lower-roman;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
105 | ol ol ol { list-style-type:lower-alpha;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
106 | ol ol ul { list-style-type:square;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
107 | ol ul { list-style-type:circle;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
108 | ol ul ol { list-style-type:lower-alpha;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
109 | ol ul ul { list-style-type:square;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
110 | p { margin:1.2em 0; }
111 | pre { font-family:Consolas,Inconsolata,Courier,monospace;font-size:1em;line-height:1.3em;margin:1.2em 0; }
112 | pre > code { background-color:#f8f8f8;border-radius:3px;border:1px solid #ccc;display:block;font-family:Consolas,Inconsolata,Courier,monospace;font-size:0.9em;margin:0 0.15em;overflow:auto;padding:0.5em 0.7em;white-space:pre;color:#444; }
113 | table { margin:1.2em 0;padding:0;border-collapse:collapse;border-spacing:0;font:inherit;border:0 none; }
114 | tbody { margin:0;padding:0;border:0 none; }
115 | td { text-align:left;font-size:1em;border:1px solid #ddd;margin:0;padding:0.5em 1em; }
116 | th { text-align:left;font-size:1em;border:1px solid #ddd;margin:0;padding:0.5em 1em;font-weight:bold;background-color:#eee; }
117 | tr.even { border-right:0 none;border-width:1px 0 0;border-style:solid none none;border-color:#ccc;margin:0;padding:0;background-color:#f8f8f8; }
118 | tr.odd { border-right:0 none;border-width:1px 0 0;border-style:solid none none;border-color:#ccc;background-color:white;margin:0;padding:0; }
119 | ul { list-style-type:disc;margin-left:1em;padding-left:0.5em;text-indent:0; }
120 | ul ol { list-style-type:lower-roman;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
121 | ul ol ol { list-style-type:lower-alpha;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
122 | ul ol ul { list-style-type:square;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
123 | ul ul { list-style-type:circle;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
124 | ul ul ol { list-style-type:lower-alpha;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
125 | ul ul ul { list-style-type:square;margin-left:0.5em;padding-left:0.5em;text-indent:0; }
126 | dl { margin:1.5em 0;padding:0; }
127 | dl dt { font-size:1em;font-style:italic;font-weight:bold;margin-top:0.75em;padding:0; }
128 | dl dd { margin:0.3em 0 0 0;padding:0 1.5em; }
129 | dd > p { margin:1em 0 1.2em; }
130 | img { max-width:$MAX_WIDTH$; }
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/vladsch/kotlin/jdbc/NamedParamTest.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | //import kotlin.test.assertEquals
4 | import org.junit.Assert.assertEquals
5 | import org.junit.Test
6 |
7 | class NamedParamTest {
8 | companion object {
9 | private val EMPTY_REPLACEMENTS = mapOf>()
10 | }
11 |
12 | @Test
13 | fun paramExtraction() {
14 | describe("should extract a single param") {
15 | withQueries(
16 | "SELECT * FROM table t WHERE t.a = :param"
17 | ) { query ->
18 | assertEquals("""SELECT * FROM table t WHERE t.a = ?""".normalizeSpaces(), query.cleanStatement.normalizeSpaces())
19 | assertEquals(mapOf("param" to listOf(0)), query.replacementMap)
20 | }
21 | }
22 |
23 | describe("should not extract param in string") {
24 | withQueries(
25 | "INSERT INTO table VALUES (':param')"
26 | ) { query ->
27 | assertEquals("""INSERT INTO table VALUES (':param')""".normalizeSpaces(), query.cleanStatement.normalizeSpaces())
28 | assertEquals(EMPTY_REPLACEMENTS, query.replacementMap)
29 | }
30 | }
31 |
32 | describe("should not extract param in back quotes") {
33 | withQueries(
34 | "INSERT INTO `:table` VALUES (':param')"
35 | ) { query ->
36 | assertEquals("""INSERT INTO `:table` VALUES (':param')""".normalizeSpaces(), query.cleanStatement.normalizeSpaces())
37 | assertEquals(EMPTY_REPLACEMENTS, query.replacementMap)
38 | }
39 | }
40 |
41 | describe("should not extract param in double quotes") {
42 | withQueries(
43 | "INSERT INTO \":table\" VALUES (':param')"
44 | ) { query ->
45 | assertEquals("""INSERT INTO ":table" VALUES (':param')""".normalizeSpaces(), query.cleanStatement.normalizeSpaces())
46 | assertEquals(EMPTY_REPLACEMENTS, query.replacementMap)
47 | }
48 | }
49 |
50 | describe("should extract a single param") {
51 | withQueries(
52 | """SELECT * FROM table t WHERE t.a =
53 | :param"""
54 | ) { query ->
55 | assertEquals("""SELECT * FROM table t WHERE t.a =
56 | ?""".normalizeSpaces(), query.cleanStatement.normalizeSpaces())
57 | assertEquals(mapOf("param" to listOf(0)), query.replacementMap)
58 | }
59 | }
60 |
61 | describe("should extract multiple params") {
62 | withQueries(
63 | "SELECT * FROM table t WHERE t.a = :param1 AND t.b = :param2"
64 | ) { query ->
65 | assertEquals("SELECT * FROM table t WHERE t.a = ? AND t.b = ?", query.cleanStatement)
66 | assertEquals(mapOf(
67 | "param1" to listOf(0),
68 | "param2" to listOf(1)
69 | ), query.replacementMap)
70 | }
71 | }
72 |
73 | describe("should extract multiple repeated params") {
74 | withQueries(
75 | """SELECT * FROM table t WHERE (:param1 IS NULL OR t.a = :param2)
76 | AND (:param2 IS NULL OR t.b = :param3)
77 | AND (:param3 IS NULL OR t.c = :param1)"""
78 | ) { query ->
79 | assertEquals("message",
80 | """SELECT * FROM table t WHERE (? IS NULL OR t.a = ?)
81 | AND (? IS NULL OR t.b = ?)
82 | AND (? IS NULL OR t.c = ?)""".normalizeSpaces(), query.cleanStatement.normalizeSpaces()
83 | )
84 | assertEquals(mapOf(
85 | "param1" to listOf(0, 5),
86 | "param2" to listOf(1, 2),
87 | "param3" to listOf(3, 4)
88 | ), query.replacementMap)
89 | }
90 | }
91 |
92 | describe("should extract multiple repeated params") {
93 | withQueries(
94 | """SELECT * FROM table t WHERE (:param1 IS NULL OR t.a = :param2)
95 | --AND (:param2 IS NULL OR t.b = :param3)
96 | AND (:param3 IS NULL OR t.c = :param1)"""
97 | ) { query ->
98 | assertEquals("message",
99 | """SELECT * FROM table t WHERE (? IS NULL OR t.a = ?)
100 | --AND (? IS NULL OR t.b = ?)
101 | AND (? IS NULL OR t.c = ?)""".normalizeSpaces(), query.cleanStatement.normalizeSpaces()
102 | )
103 | assertEquals(mapOf(
104 | "param1" to listOf(0, 5),
105 | "param2" to listOf(1, 2),
106 | "param3" to listOf(3, 4)
107 | ), query.replacementMap)
108 | }
109 | }
110 | }
111 |
112 | @Test
113 | fun paramListExtraction() {
114 | describe("should extract a single list param") {
115 | withQueries(
116 | """SELECT * FROM table t WHERE t.a IN
117 | (:param)"""
118 | ) { query ->
119 | query.params("param" inTo listOf(0, 1, 2))
120 |
121 | val cleanStatement = query.cleanStatement
122 |
123 | assertEquals("""SELECT * FROM table t WHERE t.a IN
124 | (?,?,?)""".normalizeSpaces(), cleanStatement.normalizeSpaces())
125 | assertEquals(mapOf("param" to listOf(0)), query.replacementMap)
126 | }
127 | }
128 |
129 | describe("should extract a single list param after single param") {
130 | withQueries(
131 | """SELECT * FROM table t WHERE t.b = :paramB AND t.a IN (:param)""") { query ->
132 | query.inParams("param" to listOf(0, 1, 2), "paramB" to listOf(10))
133 |
134 | val cleanStatement = query.cleanStatement
135 |
136 | assertEquals("""SELECT * FROM table t WHERE t.b = ? AND t.a IN (?,?,?)""", cleanStatement.normalizeSpaces())
137 | assertEquals(mapOf("param" to listOf(1), "paramB" to listOf(0)), query.replacementMap)
138 | val actual = query.getParams()
139 | assertEquals(listOf(10, 0, 1, 2), actual)
140 | }
141 | }
142 |
143 | describe("should extract a single list param before single param") {
144 | withQueries(
145 | """SELECT * FROM table t WHERE t.a IN (:param) AND t.b = :paramB""") { query ->
146 | query.inParams("param" to listOf(0, 1, 2), "paramB" to listOf(10))
147 |
148 | val cleanStatement = query.cleanStatement
149 |
150 | assertEquals("""SELECT * FROM table t WHERE t.a IN (?,?,?) AND t.b = ?""", cleanStatement.normalizeSpaces())
151 | assertEquals(mapOf("param" to listOf(0), "paramB" to listOf(3)), query.replacementMap)
152 | assertEquals(listOf(0, 1, 2, 10), query.getParams())
153 | }
154 | }
155 |
156 | describe("should extract a single list param before single param") {
157 | withQueries(
158 | """SELECT * FROM table t WHERE t.a IN (:param) AND t.b = :paramB""") { query ->
159 | query.params("param" inTo listOf(1), "paramB" inTo listOf(10))
160 |
161 | val cleanStatement = query.cleanStatement
162 |
163 | assertEquals("""SELECT * FROM table t WHERE t.a IN (?) AND t.b = ?""", cleanStatement.normalizeSpaces())
164 | assertEquals(mapOf("param" to listOf(0), "paramB" to listOf(1)), query.replacementMap)
165 | assertEquals(listOf(1, 10), query.getParams())
166 | }
167 | }
168 |
169 | // issue: #17
170 | describe("should extract a single list param of one element") {
171 | withQueries(
172 | """SELECT * FROM country WHERE id IN (:ids)""") { query ->
173 | query.inParams("ids" to listOf(1))
174 |
175 | val cleanStatement = query.cleanStatement
176 |
177 | assertEquals("""SELECT * FROM country WHERE id IN (?)""", cleanStatement.normalizeSpaces())
178 | assertEquals(mapOf("ids" to listOf(0)), query.replacementMap)
179 | assertEquals(listOf(1), query.getParams())
180 | }
181 | }
182 |
183 | describe("should extract multiple repeated list params") {
184 | withQueries(
185 | """SELECT * FROM table t WHERE t.a IN (:param) OR t.b IN (:param)""") { query ->
186 | query.inParams("param" to listOf(0, 1, 2))
187 |
188 | val cleanStatement = query.cleanStatement
189 |
190 | assertEquals("""SELECT * FROM table t WHERE t.a IN (?,?,?) OR t.b IN (?,?,?)""", cleanStatement.normalizeSpaces())
191 | assertEquals(mapOf("param" to listOf(0, 3)), query.replacementMap)
192 | assertEquals(listOf(0, 1, 2, 0, 1, 2), query.getParams())
193 | }
194 | }
195 | }
196 |
197 | @Test
198 | fun commentsExtraction() {
199 | describe("should ignore -- commented out params") {
200 | val query = sqlQuery(
201 | """SELECT * FROM table t WHERE (:param1 IS NULL OR t.a = :param2)
202 | -- AND (:param2 IS NULL OR t.b = :param3)
203 | AND (:param3 IS NULL OR t.c = :param1)"""
204 | )
205 | assertEquals("message",
206 | """SELECT * FROM table t WHERE (? IS NULL OR t.a = ?)
207 | -- AND (? IS NULL OR t.b = ?)
208 | AND (? IS NULL OR t.c = ?)""".normalizeSpaces(), query.cleanStatement.normalizeSpaces()
209 | )
210 | assertEquals(mapOf(
211 | "param1" to listOf(0, 3),
212 | "param2" to listOf(1),
213 | "param3" to listOf(2)
214 | ), query.replacementMap)
215 | }
216 |
217 | describe("should ignore -- commented out params") {
218 | val query = sqlQuery(
219 | """-- leading comment
220 | SELECT * FROM table t WHERE (:param1 IS NULL OR t.a = :param2)
221 | -- AND (:param2 IS NULL OR t.b = :param3)
222 | AND (:param3 IS NULL OR t.c = :param1)"""
223 | )
224 | assertEquals("message",
225 | """-- leading comment
226 | SELECT * FROM table t WHERE (? IS NULL OR t.a = ?)
227 | -- AND (? IS NULL OR t.b = ?)
228 | AND (? IS NULL OR t.c = ?)""".normalizeSpaces(), query.cleanStatement.normalizeSpaces()
229 | )
230 | assertEquals(mapOf(
231 | "param1" to listOf(0, 3),
232 | "param2" to listOf(1),
233 | "param3" to listOf(2)
234 | ), query.replacementMap)
235 | }
236 |
237 | describe("should ignore -- commented out params") {
238 | val query = sqlQuery(
239 | """SELECT * FROM table t WHERE (:param1 IS NULL OR t.a = :param2)
240 | -- AND (:param2 IS NULL OR t.b = :param3)
241 | AND (:param3 IS NULL OR t.c = :param1)"""
242 | )
243 | assertEquals("message",
244 | """SELECT * FROM table t WHERE (? IS NULL OR t.a = ?)
245 | -- AND (? IS NULL OR t.b = ?)
246 | AND (? IS NULL OR t.c = ?)""".normalizeSpaces(), query.cleanStatement.normalizeSpaces()
247 | )
248 | assertEquals(mapOf(
249 | "param1" to listOf(0, 3),
250 | "param2" to listOf(1),
251 | "param3" to listOf(2)
252 | ), query.replacementMap)
253 | }
254 |
255 | describe("should ignore # commented out params") {
256 | withQueries(
257 | """SELECT * FROM table t WHERE (:param1 IS NULL OR t.a = :param2)
258 | #AND (:param2 IS NULL OR t.b = :param3)
259 | AND (:param3 IS NULL OR t.c = :param1)"""
260 | ) { query ->
261 | assertEquals("message",
262 | """SELECT * FROM table t WHERE (? IS NULL OR t.a = ?)
263 | #AND (? IS NULL OR t.b = ?)
264 | AND (? IS NULL OR t.c = ?)""".normalizeSpaces(), query.cleanStatement.normalizeSpaces()
265 | )
266 | assertEquals(mapOf(
267 | "param1" to listOf(0, 3),
268 | "param2" to listOf(1),
269 | "param3" to listOf(2)
270 | ), query.replacementMap)
271 | }
272 | }
273 |
274 | describe("should ignore # commented out params") {
275 | withQueries(
276 | """SELECT * FROM table t WHERE (:param1 IS NULL OR t.a = :param2)
277 | #AND (:param2 IS NULL OR t.b = :param3)
278 | AND (:param3 IS NULL OR t.c = :param1)"""
279 | ) { query ->
280 | assertEquals("message",
281 | """SELECT * FROM table t WHERE (? IS NULL OR t.a = ?)
282 | #AND (? IS NULL OR t.b = ?)
283 | AND (? IS NULL OR t.c = ?)""".normalizeSpaces(), query.cleanStatement.normalizeSpaces()
284 | )
285 | assertEquals(mapOf(
286 | "param1" to listOf(0, 3),
287 | "param2" to listOf(1),
288 | "param3" to listOf(2)
289 | ), query.replacementMap)
290 | }
291 | }
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/vladsch/kotlin/jdbc/UsageTest.kt:
--------------------------------------------------------------------------------
1 | package com.vladsch.kotlin.jdbc
2 |
3 | import org.junit.Test
4 | import java.sql.DriverManager
5 | import java.sql.PreparedStatement
6 | import java.sql.Timestamp
7 | import java.time.ZonedDateTime
8 | import java.util.*
9 | import kotlin.test.assertEquals
10 | import kotlin.test.assertNotNull
11 | import kotlin.test.assertNull
12 |
13 | class UsageTest {
14 |
15 | data class Member(
16 | val id: Int,
17 | val name: String?,
18 | val createdAt: ZonedDateTime)
19 |
20 | val toMember: (Row) -> Member = { row ->
21 | Member(row.int("id"), row.stringOrNull("name"), row.zonedDateTime("created_at"))
22 | }
23 |
24 | val insert = "insert into members (name, created_at) values (?, ?)"
25 |
26 | fun borrowConnection(): java.sql.Connection {
27 | return DriverManager.getConnection("jdbc:h2:mem:hello", "user", "pass")
28 | }
29 |
30 | val driverName = "org.h2.Driver"
31 |
32 | @Test
33 | fun sessionUsage() {
34 | using(borrowConnection()) { conn ->
35 |
36 | val session = SessionImpl(Connection(conn, driverName))
37 |
38 | session.execute(sqlQuery("drop table members if exists"))
39 | session.execute(sqlQuery("""
40 | create table members (
41 | id serial not null primary key,
42 | name varchar(64),
43 | created_at timestamp not null
44 | )
45 | """))
46 | session.update(sqlQuery(insert, "Alice", Date()))
47 | session.update(sqlQuery(insert, "Bob", Date()))
48 |
49 | val ids: List = session.list(sqlQuery("select id from members")) { row -> row.int("id") }
50 | assertEquals(2, ids.size)
51 |
52 | val members: List = session.list(sqlQuery("select id, name, created_at from members"), toMember)
53 | assertEquals(2, members.size)
54 |
55 | var count = 0
56 | session.forEach(sqlQuery("select id from members")) { row ->
57 | count++
58 | assertNotNull(row.int("id"))
59 | }
60 | assertEquals(2, count)
61 |
62 | val nameQuery = "select id, name, created_at from members where name = ?"
63 | val alice: Member? = session.first(sqlQuery(nameQuery, "Alice"), toMember)
64 | assertNotNull(alice)
65 |
66 | val bob: Member? = session.first(sqlQuery(nameQuery, "Bob"), toMember)
67 | assertNotNull(bob)
68 |
69 | val chris: Member? = session.first(sqlQuery(nameQuery, "Chris"), toMember)
70 | assertNull(chris)
71 | }
72 | }
73 |
74 | @Test
75 | fun sessionUsageIssue17() {
76 | using(borrowConnection()) { conn ->
77 |
78 | val session = SessionImpl(Connection(conn, driverName))
79 |
80 | session.execute(sqlQuery("drop table members if exists"))
81 | session.execute(sqlQuery("""
82 | create table members (
83 | id serial not null primary key,
84 | name varchar(64),
85 | created_at timestamp not null
86 | )
87 | """))
88 | session.update(sqlQuery(insert, "China", Date()))
89 | session.update(sqlQuery(insert, "Russia", Date()))
90 | session.update(sqlQuery(insert, "USA", Date()))
91 |
92 | val ids: List = session.list(sqlQuery("select id from members")) { row -> row.int("id") }
93 | assertEquals(3, ids.size)
94 |
95 | val members: List = session.list(sqlQuery("select id, name, created_at from members"), toMember)
96 | assertEquals(3, members.size)
97 |
98 | var count = 0
99 | session.forEach(sqlQuery("select id from members")) { row ->
100 | count++
101 | assertNotNull(row.int("id"))
102 | }
103 | assertEquals(3, count)
104 |
105 | val nameQuery = "select id, name, created_at from members where name = ?"
106 | val alice: Member? = session.first(sqlQuery(nameQuery, "China"), toMember)
107 | assertNotNull(alice)
108 |
109 | val bob: Member? = session.first(sqlQuery(nameQuery, "Russia"), toMember)
110 | assertNotNull(bob)
111 |
112 | val chris: Member? = session.first(sqlQuery(nameQuery, "USA"), toMember)
113 | assertNotNull(chris)
114 |
115 | val q = sqlQuery("SELECT * FROM members WHERE id IN (:ids)")
116 | .inParams("ids" to listOf(1))
117 |
118 | val countries: List = session.list(q, toMember)
119 | count = countries.size
120 | assertEquals(1, count)
121 |
122 | count = session.count(q)
123 | assertEquals(1, count)
124 | }
125 | }
126 |
127 | @Test
128 | fun addNewWithId() {
129 | using(borrowConnection()) { conn ->
130 | val session = SessionImpl(Connection(conn, driverName))
131 | session.execute(sqlQuery("drop table members if exists"))
132 | session.execute(sqlQuery("""
133 | create table members (
134 | id serial not null primary key,
135 | name varchar(64),
136 | created_at timestamp not null
137 | )
138 | """))
139 |
140 | // session usage example
141 | val createdID = session.updateGetId(sqlQuery(insert, "Fred", Date()))
142 | assertEquals(1, createdID)
143 |
144 | //action usage example
145 | val createdID2 = session.updateGetId(sqlQuery(insert, "Jane", Date()))
146 | assertEquals(2, createdID2)
147 | }
148 | }
149 |
150 | @Test
151 | fun actionUsage() {
152 | using(borrowConnection()) { conn ->
153 |
154 | val session = SessionImpl(Connection(conn, driverName))
155 |
156 | session.execute(sqlQuery("drop table members if exists"))
157 | session.execute(sqlQuery("""
158 | create table members (
159 | id serial not null primary key,
160 | name varchar(64),
161 | created_at timestamp not null
162 | )
163 | """))
164 |
165 | session.execute(sqlQuery(insert, "Alice", Date()))
166 | session.execute(sqlQuery(insert, "Bob", Date()))
167 |
168 | val ids: List = session.list(sqlQuery("select id from members")) { row -> row.int("id") }
169 | assertEquals(2, ids.size)
170 |
171 | val members: List = session.list(sqlQuery("select id, name, created_at from members"), toMember)
172 | assertEquals(2, members.size)
173 |
174 | var count = 0
175 | session.forEach(sqlQuery("select id from members")) { row ->
176 | count++
177 | assertNotNull(row.int("id"))
178 | }
179 | assertEquals(2, count)
180 |
181 | val nameQuery = "select id, name, created_at from members where name = ?"
182 | val alice: Member? = session.first(sqlQuery(nameQuery, "Alice"), toMember)
183 | assertNotNull(alice)
184 |
185 | val bob: Member? = session.first(sqlQuery(nameQuery, "Bob"), toMember)
186 | assertNotNull(bob)
187 |
188 | val chris: Member? = session.first(sqlQuery(nameQuery, "Chris"), toMember)
189 | assertNull(chris)
190 | }
191 | }
192 |
193 | @Test
194 | fun transactionUsage() {
195 | using(borrowConnection()) { conn ->
196 |
197 | val idsQuery = sqlQuery("select id from members")
198 |
199 | val session = SessionImpl(Connection(conn, driverName))
200 |
201 | session.execute(sqlQuery("drop table members if exists"))
202 | session.execute(sqlQuery("""
203 | create table members (
204 | id serial not null primary key,
205 | name varchar(64),
206 | created_at timestamp not null
207 | )
208 | """))
209 |
210 | session.execute(sqlQuery(insert, "Alice", Date()))
211 | session.transaction { tx ->
212 | tx.update(sqlQuery(insert, "Bob", Date()))
213 | tx.commit()
214 | }
215 | assertEquals(2, session.count(idsQuery))
216 |
217 | try {
218 | session.transaction { tx ->
219 | tx.update(sqlQuery(insert, "Chris", Date()))
220 | assertEquals(3, tx.count(idsQuery))
221 | throw RuntimeException()
222 | }
223 | } catch (e: RuntimeException) {
224 | }
225 | assertEquals(2, session.count(idsQuery))
226 |
227 | try {
228 | session.transaction { tx ->
229 | tx.update(sqlQuery(insert, "Chris", Date()))
230 | assertEquals(3, tx.count(idsQuery))
231 | tx.rollback()
232 | }
233 | } catch (e: RuntimeException) {
234 | }
235 | assertEquals(2, session.count(idsQuery))
236 | }
237 | }
238 |
239 | @Test
240 | fun HikariCPUsage() {
241 | HikariCP.default("jdbc:h2:mem:hello", "user", "pass")
242 |
243 | using(session(HikariCP.dataSource())) { session ->
244 | session.execute(sqlQuery("drop table members if exists"))
245 | session.execute(sqlQuery("""
246 | create table members (
247 | id serial not null primary key,
248 | name varchar(64),
249 | created_at timestamp not null
250 | )
251 | """))
252 |
253 | listOf("Alice", "Bob").forEach { name ->
254 | session.update(sqlQuery(insert, name, Date()))
255 | }
256 | val ids: List = session.list(sqlQuery("select id from members")) { row -> row.int("id") }
257 | assertEquals(2, ids.size)
258 | }
259 | }
260 |
261 | @Test
262 | fun stmtParamPopulation() {
263 | withPreparedStmt(sqlQuery("""SELECT * FROM dual t
264 | WHERE (:param1 IS NULL OR :param2 = :param2)
265 | AND (:param2 IS NULL OR :param1 = :param3)
266 | AND (:param3 IS NULL OR :param3 = :param1)""",
267 | inputParams = mapOf("param1" to "1",
268 | "param2" to 2,
269 | "param3" to true))
270 | ) { preparedStmt ->
271 | assertEquals("""SELECT * FROM dual t
272 | WHERE (? IS NULL OR ? = ?)
273 | AND (? IS NULL OR ? = ?)
274 | AND (? IS NULL OR ? = ?) {1: '1', 2: 2, 3: 2, 4: 2, 5: '1', 6: TRUE, 7: TRUE, 8: TRUE, 9: '1'}""".normalizeSpaces(),
275 | preparedStmt.toString().extractQueryFromPreparedStmt())
276 | }
277 |
278 | withPreparedStmt(sqlQuery("""SELECT * FROM dual t WHERE (:param1 IS NULL OR :param2 = :param2)""",
279 | inputParams = mapOf("param2" to 2))
280 | ) { preparedStmt ->
281 | assertEquals("""SELECT * FROM dual t WHERE (? IS NULL OR ? = ?) {1: NULL, 2: 2, 3: 2}""".normalizeSpaces(),
282 | preparedStmt.toString().extractQueryFromPreparedStmt())
283 | }
284 | }
285 |
286 | @Test
287 | fun nullParams() {
288 | withPreparedStmt(sqlQuery("SELECT * FROM dual t WHERE ? IS NULL", null)) { preparedStmt ->
289 | assertEquals("SELECT * FROM dual t WHERE ? IS NULL {1: NULL}",
290 | preparedStmt.toString().extractQueryFromPreparedStmt())
291 | }
292 |
293 | withPreparedStmt(sqlQuery("SELECT * FROM dual t WHERE ? = 1 AND ? IS NULL", 1, null)) { preparedStmt ->
294 | assertEquals("SELECT * FROM dual t WHERE ? = 1 AND ? IS NULL {1: 1, 2: NULL}",
295 | preparedStmt.toString().extractQueryFromPreparedStmt())
296 | }
297 |
298 | withPreparedStmt(sqlQuery("SELECT * FROM dual t WHERE ? = 1 AND ? IS NULL AND ? = 3", 1, null, 3)) { preparedStmt ->
299 | assertEquals("SELECT * FROM dual t WHERE ? = 1 AND ? IS NULL AND ? = 3 {1: 1, 2: NULL, 3: 3}",
300 | preparedStmt.toString().extractQueryFromPreparedStmt())
301 | }
302 | }
303 |
304 | @Test
305 | fun nullParamsJdbcHandling() {
306 | // this test could fail for PostgreSQL
307 | using(borrowConnection()) { conn ->
308 | val session = SessionImpl(Connection(conn, driverName))
309 |
310 | session.execute(sqlQuery("drop table if exists members"))
311 | session.execute(sqlQuery("""
312 | create table members (
313 | id serial not null primary key
314 | )
315 | """))
316 | session.execute(sqlQuery("insert into members(id) values (1)"))
317 |
318 | describe("typed param with value") {
319 | assertEquals(1, session.first(sqlQuery("select 1 from members where ? = id", 1)) { row -> row.int(1) })
320 | }
321 |
322 | describe("typed null params") {
323 | assertEquals(1, session.first(sqlQuery("select 1 from members where ? is null", null.param())) { row -> row.int(1) })
324 | }
325 |
326 | describe("typed null comparison") {
327 | assertEquals(1,
328 | session.first(sqlQuery("select 1 from members where ? is null or ? = now()",
329 | null.param(),
330 | null.param())) { row -> row.int(1) }
331 | )
332 | }
333 |
334 | describe("select null") {
335 | val param: String? = null
336 | assertNull(session.first(sqlQuery("select ? from members", Parameter(param, String::class.java))) { row -> row.stringOrNull(1) })
337 | }
338 |
339 | session.execute(sqlQuery("drop table if exists members"))
340 | }
341 | }
342 |
343 | fun withPreparedStmt(query: SqlQuery, closure: (PreparedStatement) -> Unit) {
344 | using(borrowConnection()) { conn ->
345 | val session = SessionImpl(Connection(conn, driverName))
346 |
347 | val preparedStmt = session.prepare(query)
348 |
349 | closure(preparedStmt)
350 | }
351 | }
352 |
353 | fun String.extractQueryFromPreparedStmt(): String {
354 | return this.replace(Regex("^.*?: "), "").normalizeSpaces()
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/extensions/com.intellij.database/schema/Generate Kotlin-Model.groovy:
--------------------------------------------------------------------------------
1 | import com.intellij.database.model.DasColumn
2 | import com.intellij.database.model.DasObject
3 | import com.intellij.database.model.DasTable
4 | import com.intellij.database.model.ObjectKind
5 | import com.intellij.database.util.Case
6 | import com.intellij.database.util.DasUtil
7 | import com.intellij.openapi.util.text.StringUtil
8 | import com.intellij.psi.codeStyle.NameUtil
9 | import groovy.json.JsonSlurper
10 |
11 | /*
12 | * Available context bindings:
13 | * SELECTION Iterable
14 | * PROJECT project
15 | * FILES files helper
16 | */
17 | DEBUG = false // if true output debug trace to debug.log
18 |
19 | // can be overridden in model-config.json
20 | classFileNameSuffix = "Model" // appended to class file name
21 | downsizeLongIdToInt = true // if true changes id columns which would be declared Long to Int, change this to false to leave them as Long
22 | fileExtension = ".kt" // file extension for generated models
23 | forceBooleanTinyInt = "" // regex for column names marked as boolean when tinyint, only needed if using jdbc introspection which does not report actual declared type so all tinyint are tinyint(3)
24 | snakeCaseTables = false // if true convert snake_case table names to Pascal case, else leave as is
25 | sp = " " // string to use for each indent level
26 |
27 | //forceBooleanTinyInt = (~/^(?:deleted|checkedStatus|checked_status|optionState|option_state)$/)
28 |
29 | typeMapping = [
30 | (~/(?i)tinyint\(1\)/) : "Boolean",
31 | (~/(?i)tinyint/) : "TinyInt", // changed to Int if column name not in forceBooleanTinyInt
32 | (~/(?i)bigint/) : "Long",
33 | (~/(?i)int/) : "Int",
34 | (~/(?i)float/) : "Float",
35 | (~/(?i)double|decimal|real/): "Double",
36 | (~/(?i)datetime|timestamp/) : "Timestamp",
37 | (~/(?i)date/) : "Date",
38 | (~/(?i)time/) : "Time",
39 | (~/(?i)/) : "String"
40 | ]
41 |
42 | FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { File dir ->
43 | // read in possible map of tables to subdirectories
44 | def tableMap = null
45 | String packagePrefix = ""
46 | String removePrefix = ""
47 | boolean skipUnmapped = false
48 | File exportDir = dir
49 | File mapFile = null
50 | File projectDir = null
51 |
52 | def File tryDir = dir
53 | while (tryDir != null && tryDir.exists() && tryDir.isDirectory()) {
54 | def File tryMap = new File(tryDir, "model-config.json")
55 | if (tryMap.isFile() && tryMap.canRead()) {
56 | mapFile = tryMap
57 | exportDir = tryDir
58 | break
59 | }
60 |
61 | // see if this directory has .idea, then must be project root
62 | tryMap = new File(tryDir, ".idea")
63 | if (tryMap.isDirectory() && tryMap.exists()) {
64 | projectDir = tryDir
65 | break
66 | }
67 |
68 | tryDir = tryDir.parentFile
69 | }
70 |
71 | String packageName
72 |
73 | if (projectDir != null) {
74 | packageName = exportDir.path.substring(projectDir.path.length() + 1).replace('/', '.')
75 | // now drop first part since it is most likely sources root and not part of the package path
76 | int dot = packageName.indexOf('.')
77 | if (dot > 0) packageName = packageName.substring(dot + 1)
78 | } else {
79 | packageName = "com.sample"
80 | }
81 |
82 | if (DEBUG) {
83 | new File(exportDir, "debug.log").withPrintWriter { PrintWriter dbg ->
84 | dbg.println("exportDir: ${exportDir.path}, mapFile: ${mapFile}")
85 |
86 | if (mapFile != null && mapFile.isFile() && mapFile.canRead()) {
87 | JsonSlurper jsonSlurper = new JsonSlurper()
88 | def reader = new BufferedReader(new InputStreamReader(new FileInputStream(mapFile), "UTF-8"))
89 | data = jsonSlurper.parse(reader)
90 | if (DEBUG && data != null) {
91 | packagePrefix = data["package-prefix"] ?: ""
92 | removePrefix = data["remove-prefix"] ?: ""
93 | skipUnmapped = data["skip-unmapped"] ?: false
94 | tableMap = data["file-map"]
95 |
96 | dbg.println("package-prefix: '$packagePrefix'")
97 | dbg.println ""
98 | dbg.println("skip-unmapped: $skipUnmapped")
99 | dbg.println ""
100 | dbg.println "file-map: {"
101 | tableMap.each { dbg.println " $it" }
102 | dbg.println "}"
103 | dbg.println ""
104 | }
105 | }
106 | SELECTION.filter { DasObject it -> it instanceof DasTable && it.getKind() == ObjectKind.TABLE }.each { DasTable it ->
107 | generate(dbg, it, exportDir, tableMap, packageName, packagePrefix, removePrefix, skipUnmapped)
108 | }
109 | }
110 | } else {
111 | PrintWriter dbg = null
112 |
113 | if (mapFile != null && mapFile.isFile() && mapFile.canRead()) {
114 | JsonSlurper jsonSlurper = new JsonSlurper()
115 | BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(mapFile), "UTF-8"))
116 | data = jsonSlurper.parse(reader)
117 | packagePrefix = data["package-prefix"] ?: ""
118 | removePrefix = data["remove-prefix"] ?: ""
119 | skipUnmapped = data["skip-unmapped"] ?: false
120 |
121 | // grab values from config
122 | classFileNameSuffix = data["classFileNameSuffix"] ?: classFileNameSuffix
123 | downsizeLongIdToInt = data["downsizeLongIdToInt"] ?: downsizeLongIdToInt
124 | fileExtension = data["fileExtension"] ?: fileExtension
125 | forceBooleanTinyInt = data["forceBooleanTinyInt"] ?: forceBooleanTinyInt
126 | snakeCaseTables = data["snakeCaseTables"] ?: snakeCaseTables
127 | sp = data["indent"] ?: sp
128 | tableMap = data["file-map"]
129 | }
130 |
131 | SELECTION.filter { DasObject it -> it instanceof DasTable && it.getKind() == ObjectKind.TABLE }.each { DasTable it ->
132 | generate(dbg, it, exportDir, tableMap, packageName, packagePrefix, removePrefix, skipUnmapped)
133 | }
134 | }
135 | }
136 |
137 | void generate(PrintWriter dbg, DasTable table, File dir, tableMap, String packageName, String packagePrefix, String removePrefix, boolean skipUnmapped) {
138 | String className = snakeCaseTables ? toJavaName(toSingular(table.getName()), true) : toSingular(table.getName())
139 | dbg.println("className: ${className}, tableName: ${table.getName()}, singular: ${toSingular(table.getName())}")
140 |
141 | def fields = calcFields(table)
142 | String fileName = className + "${classFileNameSuffix}$fileExtension"
143 |
144 | def mappedFile = tableMap != null ? tableMap[fileName] : null
145 | if (mappedFile == null && tableMap != null && tableMap[""] != null) {
146 | mappedFile = suffixOnceWith(tableMap[""], "/") + fileName
147 | }
148 |
149 | if (dbg != null) dbg.println "fileName ${fileName} mappedFile ${mappedFile}"
150 |
151 | def file
152 | if (mappedFile != null && !mappedFile.trim().isEmpty()) {
153 | file = new File(dir, mappedFile);
154 | String unprefixed = (removePrefix == "" || !mappedFile.startsWith(removePrefix)) ? mappedFile : mappedFile.substring(removePrefix.length())
155 | packageName = packagePrefix + new File(unprefixed).parent.replace("/", ".")
156 | } else {
157 | file = new File(dir, fileName)
158 | packageName = packageName
159 | if (dbg != null && (skipUnmapped || mappedFile != null)) dbg.println "skipped ${fileName}"
160 | if (skipUnmapped || mappedFile != null) return
161 | }
162 |
163 | file.withPrintWriter { out -> generateModel(dbg, out, (String) table.getName(), className, fields, packageName) }
164 | }
165 |
166 | static String defaultValue(def value) {
167 | String text = (String) value
168 | return text == null ? "" : (text == "CURRENT_TIMESTAMP" ? " $text" : " '$text'")
169 | }
170 |
171 | def calcFields(table) {
172 | def colIndex = 0
173 | DasUtil.getColumns(table).reduce([]) { def fields, DasColumn col ->
174 | def dataType = col.getDataType()
175 | def spec = dataType.getSpecification()
176 | def suffix = dataType.suffix
177 | def typeStr = (String) typeMapping.find { p, t -> p.matcher(Case.LOWER.apply(spec)).find() }.value
178 | def colName = (String) col.getName()
179 | def colNameLower = (String) Case.LOWER.apply(colName)
180 | colIndex++
181 | def colType = downsizeLongIdToInt && typeStr == "Long" && DasUtil.isPrimary(col) || DasUtil.isForeign(col) && colNameLower.endsWith("id") ? "Int" : typeStr
182 | def javaColName = (String) toJavaName(colName, false)
183 | if (typeStr == "TinyInt") {
184 | if (forceBooleanTinyInt && javaColName.matches(forceBooleanTinyInt)) {
185 | colType = "Boolean"
186 | } else {
187 | colType = "Int"
188 | }
189 | }
190 | def attrs = col.getTable().getColumnAttrs(col)
191 | def columnDefault = col.getDefault()
192 | def isAutoInc = DasUtil.isAutoGenerated(col)
193 | def isAuto = isAutoInc || columnDefault == "CURRENT_TIMESTAMP" || attrs.contains(DasColumn.Attribute.COMPUTED)
194 | fields += [[
195 | name : javaColName,
196 | column : colName,
197 | type : colType,
198 | suffix : suffix,
199 | col : col,
200 | spec : spec,
201 | attrs : attrs,
202 | default : columnDefault,
203 | // constraints : constraints.reduce("") { all, constraint ->
204 | // all += "[ name: ${constraint.name}, " + "kind: ${constraint.getKind()}," + "]"
205 | // },
206 | notNull : col.isNotNull(),
207 | autoInc : isAutoInc,
208 | nullable: !col.isNotNull() || isAuto || columnDefault != null,
209 | key : DasUtil.isPrimary(col),
210 | auto : isAuto,
211 | annos : ""]]
212 | fields
213 | }
214 | }
215 |
216 | static String rightPad(String text, int len) {
217 | def padded = text
218 | def pad = len - text.length()
219 | while (pad-- > 0) padded += " "
220 | return padded
221 | }
222 |
223 | static String suffixOnceWith(String str, String suffix) {
224 | return str.endsWith(suffix) ? str : "$str$suffix";
225 | }
226 |
227 | static String lowerFirst(String str) {
228 | return str.length() > 0 ? Case.LOWER.apply(str[0]) + str[1..-1] : str
229 | }
230 |
231 | static String toSingular(String str) {
232 | String[] s = NameUtil.splitNameIntoWords(str).collect { it }
233 | String lastSingular = StringUtil.unpluralize(s[-1]) ?: ""
234 | return str.substring(0, str.length() - s[-1].length()) + lastSingular
235 | }
236 |
237 | static String toPlural(String str) {
238 | String[] s = NameUtil.splitNameIntoWords(str).collect { it }
239 | String lastPlural = StringUtil.pluralize(s[-1]) ?: ""
240 | return str.substring(0, str.length() - s[-1].length()) + lastPlural
241 | }
242 |
243 | static String toJavaName(String str, boolean capitalize) {
244 | String s = NameUtil.splitNameIntoWords(str)
245 | .collect { Case.LOWER.apply(it).capitalize() }
246 | .join("")
247 | .replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
248 | (capitalize || s.length() == 1) ? s : Case.LOWER.apply(s[0]) + s[1..-1]
249 | }
250 |
251 | void generateModel(PrintWriter dbg, PrintWriter out, String tableName, String className, def fields, String packageName) {
252 | def dbCase = true
253 | def keyCount = 0
254 | def nonKeyCount = 0
255 | def timestampCount = 0
256 | def dateCount = 0
257 | def timeCount = 0
258 | fields.each() {
259 | if (it.name != it.column) dbCase = false
260 | if (it.key) keyCount++
261 | else nonKeyCount++
262 |
263 | if (it.type == "Timestamp") timestampCount++
264 | else if (it.type == "Date") dateCount++
265 | else if (it.type == "Time") timeCount++
266 | }
267 |
268 | // set single key to nullable and auto
269 | if (keyCount == 1 && nonKeyCount > 0) {
270 | fields.each() {
271 | if (it.key) {
272 | it.nullable = true
273 | it.auto = true
274 | }
275 | }
276 | }
277 |
278 | out.println "package $packageName"
279 | out.println ""
280 | out.println "import com.vladsch.kotlin.jdbc.Model"
281 | out.println "import com.vladsch.kotlin.jdbc.Session"
282 | // out.println "import com.vladsch.kotlin.jdbc.ModelCompanion"
283 | if (timestampCount > 0) out.println "import java.sql.Timestamp"
284 | if (dateCount > 0) out.println "import java.sql.Date"
285 | if (timeCount > 0) out.println "import java.sql.Time"
286 | // out.println "import javax.json.JsonObject"
287 | out.println ""
288 | out.println "data class $className("
289 |
290 | def sep = "";
291 | fields.each() {
292 | out.print sep
293 | sep = ",\n";
294 | if (it.annos != "") out.println "${sp}${it.annos}"
295 | out.print "${sp}val ${it.name}: ${it.type}"
296 | if (it.nullable) out.print "?"
297 | // if (it.attrs != null) out.print(" // attrs: '${it.attrs}'")
298 | // if (it.default != null) out.print(" // default: '${it.default}'")
299 | if (it.suffix != null && it.suffix != "") out.print(" // suffix: '${it.suffix}'")
300 | }
301 | out.println "\n)"
302 | out.println ""
303 |
304 | // model class
305 | out.println "@Suppress(\"MemberVisibilityCanBePrivate\")"
306 | out.println "class ${className}Model(session: Session? = null, quote: String? = null) : Model<${className}Model, ${className}>(session, tableName, dbCase = ${dbCase}, quote = quote) {"
307 | def maxWidth = 0
308 | def lines = []
309 |
310 | fields.each() {
311 | def line = ""
312 | if (it.annos != "") line += "${sp}${it.annos}"
313 | line += "${sp}var ${it.name}: ${it.type}"
314 | if (it.nullable) line += "?"
315 | line += " by db"
316 | if (it.auto && it.key) line += ".autoKey"
317 | else if (it.auto) line += ".auto"
318 | else if (it.key) line += ".key"
319 | else if (it.default) line += ".default"
320 |
321 | if (maxWidth < line.length()) maxWidth = line.length()
322 | lines.add(line)
323 | }
324 |
325 | maxWidth += 7 - (maxWidth % 4)
326 |
327 | def i = 0
328 | lines.each() { it ->
329 | out.print rightPad(it, maxWidth)
330 | out.println "// ${fields[i].column} ${fields[i].spec}${fields[i].notNull ? " NOT NULL" : ""}${fields[i].autoInc ? " AUTO_INCREMENT" : ""}${defaultValue(fields[i].default)}${fields[i].suffix != null ? " ${fields[i].suffix}" : ""}"
331 | i++
332 | }
333 |
334 | // data copy constructor
335 | out.println ""
336 | out.println "${sp}constructor(other: ${className}, session: Session? = null, quote: String? = null) : this(session, quote) {"
337 | fields.each() {
338 | out.println "${sp}${sp}${it.name} = other.${it.name}"
339 | }
340 | out.println "${sp}${sp}snapshot()"
341 | out.println "${sp}}"
342 | out.println ""
343 |
344 | // copy factory
345 | out.println "${sp}override operator fun invoke() = ${className}Model(_session, _quote)"
346 | out.println ""
347 |
348 | // data factory
349 | out.println "${sp}override fun toData() = ${className}("
350 | sep = "";
351 | fields.each() {
352 | out.print sep
353 | sep = ",\n";
354 | out.print "${sp}${sp}${it.name}"
355 | }
356 | out.println "\n${sp})"
357 | out.println ""
358 | out.println "${sp}companion object {"
359 | out.println "${sp}${sp}const val tableName = \"${tableName}\""
360 | // out.println "${sp}${sp}override fun createModel(quote:String?): ${className}Model = ${className}Model(quote)"
361 | // out.println "${sp}${sp}override fun createData(model: ${className}Model): ${className} = model.toData()"
362 | out.println "${sp}}"
363 | out.println "}"
364 | }
365 |
--------------------------------------------------------------------------------
/extensions/com.intellij.database/data/extractors/JavaScript-Enumerated-Value-Type.js:
--------------------------------------------------------------------------------
1 | /*
2 | Data extractor of table data to JavaScript Enum based on values using npm 'enumerated-type' package
3 |
4 | First non-numeric column is considered the name of the enum values, the rest will be used as properties of the enum value
5 |
6 | Transposed status is ignored
7 | */
8 |
9 | var packageName = ""; // package used for generated class files
10 | var enumNameSuffix = ""; // appended to class file name
11 | var DEBUG = false;
12 |
13 | function eachWithIdx(iterable, f) {
14 | var i = iterable.iterator();
15 | var idx = 0;
16 | while (i.hasNext()) f(i.next(), idx++);
17 | }
18 |
19 | function mapEach(iterable, f) {
20 | var vs = [];
21 | eachWithIdx(iterable, function (i) {
22 | vs.push(f(i));
23 | });
24 | return vs;
25 | }
26 |
27 | function escape(str) {
28 | str = str.replaceAll("\t|\b|\\f", "");
29 | // str = com.intellij.openapi.util.text.StringUtil.escapeXml(str);
30 | str = str.replaceAll("\\r|\\n|\\r\\n", "");
31 | str = str.replaceAll("([\\[\\]\\|])", "\\$1");
32 | return str;
33 | }
34 |
35 | var NEWLINE = "\n";
36 |
37 | function output() {
38 | for (var i = 0; i < arguments.length; i++) {
39 | OUT.append(arguments[i]);
40 | }
41 | }
42 |
43 | function outputln() {
44 | for (var i = 0; i < arguments.length; i++) {
45 | OUT.append(arguments[i].toString());
46 | }
47 | OUT.append("\n");
48 | }
49 |
50 | function outputRow(items) {
51 | output("| ");
52 | for (var i = 0; i < items.length; i++) output(escape(items[i]), " |");
53 | output("", NEWLINE);
54 | }
55 |
56 | function isObjectLike(param) {
57 | return !!param && typeof param === "object";
58 | }
59 |
60 | function isNumeric(arg) {
61 | return !!arg.match(/^(?:[+-]?[0-9]+(?:\.[0-9]*)?|[+-]?[0-9]*\.[0-9]+)(?:E[+-]?[0-9]+)?$/);
62 | }
63 |
64 | /**
65 | *
66 | * @param arr {Array}
67 | * @returns {boolean}
68 | */
69 | function allStrings(arr) {
70 | // if (DEBUG) outputln("// iMax: " + arr.length);
71 | for (var i = 0; i < arr.length; i++) {
72 | // if (DEBUG) outputln("// arr[", i, "]: ", arr[i]);
73 | if (!arr[i].toString().match(/[a-zA-Z_][a-zA-Z_0-9]*/)) {
74 | return false;
75 | }
76 | }
77 | return true;
78 | }
79 |
80 | // com.intellij.openapi.util.text.StringUtil.escapeXml(str)
81 | function javaName(str, capitalize, pluralize, dropLastId) {
82 | var s = [];
83 | var spl = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str);
84 |
85 | for (var i = 0; i < spl.length; i++) {
86 | var part = spl[i].toString();
87 | if (!(dropLastId && i + 1 === spl.length && spl.length > 1 && part.toLowerCase() === "id")) {
88 | s.push(part);
89 | }
90 | }
91 |
92 | s = s.map(function (value, i) {
93 | var part = value;
94 | if (pluralize && i + 1 === s.length) {
95 | part = com.intellij.openapi.util.text.StringUtil.pluralize(part) || part;
96 | }
97 |
98 | return part.substr(0, 1).toUpperCase() + part.substring(1).toLowerCase();
99 | });
100 |
101 | var name = s.map(function (it) {
102 | return it.replace(/[^a-zA-Z0-9_$]+/, "_");
103 | }).join("");
104 |
105 | return capitalize || name.length === 1 ? name : name.substr(0, 1).toLowerCase() + name.substring(1);
106 | }
107 |
108 | // com.intellij.openapi.util.text.StringUtil.escapeXml(str)
109 | function displayName(str, pluralize, dropLastId) {
110 | if (/^[A-Z_]+$/.test(str)) {
111 | return str;
112 | } else {
113 | var s = [];
114 | var spl = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str);
115 |
116 | for (var i = 0; i < spl.length; i++) {
117 | var part = spl[i].toString();
118 | if (!(dropLastId && i + 1 === spl.length && spl.length > 1 && part.toLowerCase() === "id")) {
119 | s.push(part);
120 | }
121 | }
122 |
123 | s = s.map(function (value, i) {
124 | var part = value;
125 | if (pluralize && i + 1 === s.length) {
126 | part = com.intellij.openapi.util.text.StringUtil.pluralize(part) || part;
127 | }
128 |
129 | return part.substr(0, 1).toUpperCase() + part.substring(1).toLowerCase();
130 | });
131 |
132 | return s.map(function (it) {
133 | return it.replace(/[^a-zA-Z0-9_$]+/, " ");
134 | }).join(" ").replace(/\\s+/, " ");
135 | }
136 | }
137 |
138 | function snakeName(str, capitalize, pluralize, dropLastId) {
139 | var s = [];
140 | var spl = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str);
141 |
142 | for (var i = 0; i < spl.length; i++) {
143 | var part = spl[i].toString();
144 | if (!(dropLastId && i + 1 === spl.length && spl.length > 1 && part.toLowerCase() === "id")) {
145 | s.push(part);
146 | }
147 | }
148 |
149 | s = s.map(function (value, i) {
150 | var part = value;
151 | if (pluralize && i + 1 === s.length) {
152 | part = com.intellij.openapi.util.text.StringUtil.pluralize(part) || part;
153 | }
154 |
155 | return part.substr(0, 1).toUpperCase() + part.substring(1).toLowerCase();
156 | });
157 |
158 | return s.map(function (it) {
159 | return it.replace(/[^a-zA-Z0-9$]+/, "_");
160 | }).join("_");
161 | }
162 |
163 | function values(obj) {
164 | var key;
165 | var values = [];
166 |
167 | for (key in obj) {
168 | if (obj.hasOwnProperty(key)) {
169 | values.push(obj[key]);
170 | }
171 | }
172 | return values;
173 | }
174 |
175 | function keys(obj) {
176 | var key;
177 | var keys = [];
178 |
179 | for (key in obj) {
180 | if (obj.hasOwnProperty(key)) {
181 | keys.push(key);
182 | }
183 | }
184 | return keys;
185 | }
186 |
187 | var rows = [];
188 | var columnNames;
189 | var enumValueNames = [];
190 | var enumNames = [];
191 | var enumNameColumns = [];
192 | var enumNamesColumn = -1;
193 | var columns = [];
194 | var enumName = "";
195 |
196 | columnNames = mapEach(COLUMNS, function (col) {
197 | return col.name();
198 | });
199 |
200 | if (DEBUG) outputln("// columnNames: " + columnNames);
201 |
202 | eachWithIdx(ROWS, function (row) {
203 | rows.push(mapEach(COLUMNS, function (col) {
204 | return FORMATTER.format(row, col);
205 | }));
206 | });
207 |
208 | if (DEBUG) outputln("// rows: " + rows);
209 |
210 | columns = rows[0].map(function () {
211 | return [];
212 | });
213 |
214 | // need columns
215 | rows.forEach(function (row, rowIndex) {
216 | row.forEach(function (value, columnIndex) {
217 | columns[columnIndex].push(value);
218 | });
219 | });
220 |
221 | if (DEBUG) outputln("// columns: " + columns);
222 |
223 | // name taken from the first column, less id if it is not just id
224 | enumName = javaName(columnNames[0], true, true, true);
225 | if (enumName === "") {
226 | enumName = "TestEnum";
227 | }
228 |
229 | if (DEBUG) outputln("// enumName: " + enumName);
230 |
231 | // find first non-numeric column for enum names
232 | for (var i = 0; i < columns.length; i++) {
233 | var column = columns[i];
234 | if (DEBUG) outputln("// columns[", i, "]: ", column, " allStrings: ", allStrings(column));
235 |
236 | if (allStrings(column)) {
237 | enumNamesColumn = i;
238 | enumValueNames = [];
239 | for (var j = 0; j < column.length; j++) {
240 | enumValueNames.push(javaName(column[j], false, false, false));
241 | }
242 | break;
243 | }
244 | }
245 |
246 | if (DEBUG) outputln("// enumValueNames: " + enumValueNames);
247 |
248 | // use default enum value names
249 | if (enumNamesColumn < 0) {
250 | enumNamesColumn = 0;
251 | enumValueNames = columns[0].map(function (value) {
252 | return columnNames[0] + "_" + value;
253 | });
254 | }
255 |
256 | if (DEBUG) outputln("// enumNamesColumn: " + enumNamesColumn);
257 |
258 | enumNameColumns = {};
259 | enumNames = columnNames
260 | .map(function (value, index) {
261 | var name = javaName(value, false, false, false);
262 |
263 | if (index === 0 && javaName(value, true, true, true).toLowerCase() === enumName.toLowerCase()) {
264 | name = "id";
265 | }
266 |
267 | enumNameColumns[name] = index;
268 | return name;
269 | });
270 |
271 | enumName = enumName + enumNameSuffix;
272 | var columnTypes = enumNames.map(function (value) {
273 | if (allStrings(columns[enumNameColumns[value]])) {
274 | return "String";
275 | } else {
276 | return "Int";
277 | }
278 | });
279 |
280 | /**
281 | *
282 | * @param callback (name, type, colIndex) return
283 | * @param prefix will be output iff enumNames is not empty and have output
284 | * @param delimiter will be used between outputs
285 | * @param suffix will be output iff enumNames is not empty and have output
286 | *
287 | *
288 | */
289 | function forAllEnumParams(callback, prefix, delimiter, suffix) {
290 | var sep = prefix;
291 | var hadOutput = false;
292 | enumNames.forEach(function (value) {
293 | var enumNameColumn = enumNameColumns[value];
294 | var type = columnTypes[enumNameColumn];
295 | var out = callback(value, type, enumNameColumn);
296 | if (out !== undefined && out !== null) {
297 | if (sep !== undefined) {
298 | output(sep);
299 | }
300 | sep = delimiter;
301 | output(out);
302 | hadOutput = true;
303 | }
304 | });
305 |
306 | if (hadOutput && suffix !== undefined && suffix !== null) {
307 | output(suffix);
308 | }
309 | }
310 |
311 | /**
312 | * @param callback (name, value, index) return
313 | * @param prefix will be output iff enumNames is not empty and have output
314 | * @param delimiter will be used between outputs
315 | * @param suffix will be output iff enumNames is not empty and have output
316 | */
317 | function forAllEnumValues(callback, prefix, delimiter, suffix) {
318 | var sep = prefix;
319 | var hadOutput = false;
320 | enumValueNames.forEach(function (value, index) {
321 | if (sep !== undefined) {
322 | output(sep);
323 | }
324 |
325 | sep = delimiter;
326 | var enumValue = columns[0][index];
327 | var out = callback(value, enumValue, index);
328 | if (out !== undefined && out !== null) {
329 | if (sep !== undefined) {
330 | output(sep);
331 | }
332 | sep = delimiter;
333 | output(out);
334 | hadOutput = true;
335 | }
336 | });
337 |
338 | if (hadOutput && suffix !== undefined && suffix !== null) {
339 | output(suffix);
340 | }
341 | }
342 |
343 | /**
344 | * @param callback (name, value, index) return
345 | * @param prefix will be output iff enumNames is not empty and have output
346 | * @param delimiter will be used between outputs
347 | * @param suffix will be output iff enumNames is not empty and have output
348 | */
349 | function forAllNamedEnumValues(callback, prefix, delimiter, suffix) {
350 | if (enumNamesColumn > 0) {
351 | forAllEnumValues(callback, prefix, delimiter, suffix);
352 | }
353 | }
354 |
355 | var sep = "";
356 |
357 | // Start of output
358 | outputln("import Enum from 'enumerated-type';");
359 | outputln();
360 | var idPrefix = (snakeName(columnNames[0], false, false, true) + "_").toUpperCase();
361 | var enumValueVar = javaName(columnNames[0], false, false, true);
362 | var enumIdVar = javaName(columnNames[0]);
363 |
364 | /*
365 | forAllNamedEnumValues(function (name, value, index) {
366 | return ["export const ", idPrefix, snakeName(name, false, false, true).toUpperCase(), "_ID = ", value, ";\n"].join("");
367 | }, "", "", "\n");
368 | */
369 |
370 | outputln("// NOTE: this definition is used for WebStorm Code Completions to work");
371 | outputln("const ", enumName, "Value = {");
372 |
373 | output(" value: {", enumIdVar, ": 0");
374 | sep = ", ";
375 | forAllEnumParams(function (nameAs, type, colIndex) {
376 | if (colIndex) {
377 | output(sep, javaName(nameAs, false, false, false), ": ");
378 | var elementValue = type === "String" ? '""' : "0";
379 | output(elementValue);
380 | }
381 | });
382 | outputln(sep, "_displayName: \"\"},\n");
383 |
384 | outputln(" get ", enumIdVar, "() { return this.value.", enumIdVar, "; },");
385 | forAllEnumParams(function (nameAs, type, colIndex) {
386 | if (colIndex) {
387 | outputln(" get ", javaName(nameAs, false, false, false), "() { return this.value.", javaName(nameAs, false, false, false), "; },");
388 | }
389 | });
390 | outputln();
391 |
392 | // output test functions for each named value
393 | forAllNamedEnumValues(function (name, value, index) {
394 | return [" get is", javaName(name, true, false, false), "() { return this === ", enumName, ".", javaName(name, false, false, false), "; },\n"].join("");
395 | }, "", "", "");
396 |
397 | outputln("};");
398 |
399 | outputln();
400 | outputln("// NOTE: this definition is used for WebStorm Code Completions to work");
401 | outputln("let ", enumName, " = {");
402 | forAllNamedEnumValues(function (name, value, index) {
403 | return [" ", name, ": ", enumName, "Value, // ", value, "\n"].join("");
404 | }, "", "", "");
405 | outputln();
406 | outputln(" value(arg, defaultValue = undefined) { return ", enumName, "Value; }, // return an item with ", enumIdVar, " matching arg or defaultValue");
407 | outputln(" ", enumIdVar, "(arg) { return ", enumName, "Value.", enumIdVar, "; }, // return the arg if it matches an item's ", enumIdVar, " or ", enumValueNames[0], ".", enumIdVar);
408 | outputln(" get dropdownChoices() { return [{value: 0, label: \"label\"}]; }, // return dropdownChoices array");
409 | outputln(" dropdownChoicesExcluding() { return [{value: 0, label: \"label\"}]; }, // return dropdownChoices array excluding ones for items passed as arguments to the function");
410 | outputln("};");
411 | outputln();
412 |
413 | outputln("// this definition is used for actual definition");
414 | outputln("let _", enumName, " = {");
415 |
416 | sep = ", ";
417 | if (DEBUG) outputln("// enumName: ", enumName, " enumIdVar: ", enumIdVar, " enumValueVar: ", enumValueVar);
418 |
419 | forAllNamedEnumValues(function (name, value, index) {
420 | var arr = [" ", name, ": {"];
421 |
422 | var enumDisplayValue;
423 |
424 | forAllEnumParams(function (nameAs, type, colIndex) {
425 | if (DEBUG) outputln("// nameAs: ", nameAs, " type: ", type, " colIndex: ", colIndex);
426 |
427 | if (!colIndex) {
428 | arr.push(enumIdVar, ": ");
429 | } else {
430 | arr.push(sep, javaName(nameAs, false, false, false), ": ");
431 | }
432 | var columnElement = columns[colIndex][index];
433 |
434 | if (colIndex === enumNamesColumn) {
435 | enumDisplayValue = displayName(columnElement, false, false);
436 | }
437 |
438 | var elementValue = type === "String" ? '"' + columnElement.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"' : columnElement;
439 | arr.push(elementValue);
440 | }, "", "", "");
441 |
442 | arr.push(sep, "_displayName: ", '"' + enumDisplayValue.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"');
443 | arr.push("},\n");
444 | return arr.join("");
445 | });
446 |
447 | outputln("};");
448 |
449 | outputln();
450 | outputln(enumName, " = new Enum(\"", enumName, "\", _", enumName, ", ", enumName, "Value, \"", enumIdVar, "\", \"_displayName\");");
451 | outputln();
452 | outputln("export default ", enumName, ";");
453 | outputln();
454 |
455 | outputln("/**");
456 | outputln(" * Sample function with switch for handling all enum values and generating exceptions,");
457 | outputln(" * use the copy/paste Luke, use the copy/paste");
458 | outputln(" *");
459 | outputln(" * @param ", enumIdVar, " {*} enum value or value which can be converted to enum value");
460 | outputln(" */");
461 | outputln("function dummy(", enumIdVar, ") {");
462 | outputln(" const ", enumValueVar, " = ", enumName, ".value(", enumIdVar, ");");
463 | outputln(" switch (", enumValueVar, ") {");
464 |
465 | forAllNamedEnumValues(function (name, value, index) {
466 | return [" case ", enumName, ".", name, ":\n"].join("");
467 | }, "", "", "");
468 |
469 | outputln(" break;");
470 | outputln("");
471 | outputln(" default:");
472 | outputln(" if (", enumValueVar, ") {");
473 | outputln(" // throw an error instead of silently doing nothing if more types are added in the future");
474 | outputln(" throw `IllegalStateException unhandled ", enumName, ".${", enumValueVar, ".name} value ${", enumValueVar, ".value}`;");
475 | outputln(" } else {");
476 | outputln(" // comment out if non-enum values are allowed and ignored");
477 | outputln(" throw `IllegalArgumentException unknown ", enumName, " value ${", enumIdVar, "}`;");
478 | outputln(" }");
479 | outputln(" }");
480 | outputln("}");
481 |
--------------------------------------------------------------------------------