├── 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 | 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 | 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 | 14 | 17 | 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 | 36 | 37 | 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 | 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 | 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 | --------------------------------------------------------------------------------