From e88985625596262877548b5f07bfeac2e4b420b1 Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 12 May 2021 01:04:39 +0800 Subject: [PATCH 001/126] prepare for next iteration --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 67f86634..9d5681f5 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { allprojects { group = "org.ktorm" - version = "3.4.1" + version = "3.5.0-SNAPSHOT" } subprojects { project -> From 041f76281f7b5e33f79b8b8882b0705679b5a5a4 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 13 May 2021 00:13:59 +0800 Subject: [PATCH 002/126] upgrade gradle --- gradle/wrapper/gradle-wrapper.jar | Bin 55741 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 53 ++++++++++++++--------- gradlew.bat | 43 ++++++++++-------- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 457aad0d98108420a977756b7145c93c8910b076..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 32671 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 delta 29482 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 Xl$U}8nGXl$9Q3CH>hr|=o}d07!s7Wl diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 64741d47..d435ce29 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Nov 30 17:47:45 CST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip diff --git a/gradlew b/gradlew index af6708ff..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 0f8d5937..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From 0be9df366b075cd84119e187ba4631070a66e5ea Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 13 May 2021 00:57:39 +0800 Subject: [PATCH 003/126] init buildSrc project --- buildSrc/build.gradle.kts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 buildSrc/build.gradle.kts diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..9eccb279 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,14 @@ + +plugins { + id("org.gradle.kotlin.kotlin-dsl") version "2.1.4" +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +dependencies { + api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32") + api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.17.0-RC2") +} From 5dcb018201bb7c62d4e89b387c7b670c5a699162 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 13 May 2021 01:10:52 +0800 Subject: [PATCH 004/126] update git ignore --- .gitignore | 3 ++- .../main/kotlin/org/ktorm/build/module-conventions.gradle.kts | 0 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 buildSrc/src/main/kotlin/org/ktorm/build/module-conventions.gradle.kts diff --git a/.gitignore b/.gitignore index 586af0eb..7029e9fb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,9 +21,10 @@ logs/ ### NetBeans ### nbproject/private/ -build/ nbbuild/ dist/ nbdist/ .nb-gradle/ +# Include Ktorm build sources +!buildSrc/src/main/kotlin/org/ktorm/build diff --git a/buildSrc/src/main/kotlin/org/ktorm/build/module-conventions.gradle.kts b/buildSrc/src/main/kotlin/org/ktorm/build/module-conventions.gradle.kts new file mode 100644 index 00000000..e69de29b From 9920c9eb6d7399cb007862d12a54da65d52f184f Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 13 May 2021 01:43:53 +0800 Subject: [PATCH 005/126] change buildSrc package --- .gitignore | 3 --- .../src/main/kotlin/module-conventions.gradle.kts | 15 +++++++++++++++ .../org/ktorm/build/module-conventions.gradle.kts | 0 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 buildSrc/src/main/kotlin/module-conventions.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/org/ktorm/build/module-conventions.gradle.kts diff --git a/.gitignore b/.gitignore index 7029e9fb..c74ce9f5 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,3 @@ nbbuild/ dist/ nbdist/ .nb-gradle/ - -# Include Ktorm build sources -!buildSrc/src/main/kotlin/org/ktorm/build diff --git a/buildSrc/src/main/kotlin/module-conventions.gradle.kts b/buildSrc/src/main/kotlin/module-conventions.gradle.kts new file mode 100644 index 00000000..5037f854 --- /dev/null +++ b/buildSrc/src/main/kotlin/module-conventions.gradle.kts @@ -0,0 +1,15 @@ + +plugins { + id("kotlin") + id("signing") + id("maven-publish") + id("io.gitlab.arturbosch.detekt") +} + +detekt { + +} + +publishing { + +} diff --git a/buildSrc/src/main/kotlin/org/ktorm/build/module-conventions.gradle.kts b/buildSrc/src/main/kotlin/org/ktorm/build/module-conventions.gradle.kts deleted file mode 100644 index e69de29b..00000000 From 7090f195bb0f18e7d550869f93a7e2aca98c6b3c Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 14 May 2021 00:15:23 +0800 Subject: [PATCH 006/126] migrate module conventions --- build.gradle | 200 +----------------- .../main/kotlin/module-conventions.gradle.kts | 178 +++++++++++++++- 2 files changed, 179 insertions(+), 199 deletions(-) diff --git a/build.gradle b/build.gradle index 9d5681f5..5d9806ef 100644 --- a/build.gradle +++ b/build.gradle @@ -1,207 +1,11 @@ -buildscript { - ext { - kotlinVersion = "1.4.32" - detektVersion = "1.17.0-RC2" - } - repositories { - mavenCentral() - gradlePluginPortal() - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}" - classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:${detektVersion}" - } -} - allprojects { group = "org.ktorm" version = "3.5.0-SNAPSHOT" } -subprojects { project -> - apply plugin: "kotlin" - apply plugin: "signing" - apply plugin: "maven-publish" - apply plugin: "io.gitlab.arturbosch.detekt" - apply from: "${project.rootDir}/check-source-header.gradle" - - repositories { - mavenCentral() - } - - dependencies { - api "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}" - api "org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}" - testImplementation "junit:junit:4.12" - detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:${detektVersion}" - } - - compileKotlin { - kotlinOptions.jvmTarget = "1.6" - kotlinOptions.allWarningsAsErrors = true - kotlinOptions.freeCompilerArgs = [ - "-Xexplicit-api=strict", - "-Xopt-in=kotlin.RequiresOptIn" - ] - } - - task generateSources(type: Jar) { - archiveClassifier = "sources" - from sourceSets.main.allSource - } - - task generateJavadoc(type: Jar) { - archiveClassifier = "javadoc" - } - - detekt { - toolVersion = detektVersion - input = files("src/main/kotlin") - config = files("${project.rootDir}/detekt.yml") - } - - publishing { - publications { - dist(MavenPublication) { - from components.java - artifact generateSources - artifact generateJavadoc - - groupId = project.group - artifactId = project.name - version = project.version - - pom { - name = "${project.group}:${project.name}" - description = "A lightweight ORM Framework for Kotlin with strong typed SQL DSL and sequence APIs." - url = "https://www.ktorm.org" - licenses { - license { - name = "The Apache Software License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - scm { - url = "https://github.com/kotlin-orm/ktorm" - connection = "scm:git:https://github.com/kotlin-orm/ktorm.git" - developerConnection = "scm:git:ssh://git@github.com/kotlin-orm/ktorm.git" - } - developers { - developer { - id = "vincentlauvlwj" - name = "vince" - email = "me@liuwj.me" - } - developer { - id = "waluo" - name = "waluo" - email = "1b79349b@gmail.com" - } - developer { - id = "clydebarrow" - name = "Clyde" - email = "clyde@control-j.com" - } - developer { - id = "Ray-Eldath" - name = "Ray Eldath" - email = "ray.eldath@outlook.com" - } - developer { - id = "hangingman" - name = "hiroyuki.nagata" - email = "idiotpanzer@gmail.com" - } - developer { - id = "onXoot" - name = "beetlerx" - email = "beetlerx@gmail.com" - } - developer { - id = "arustleund" - name = "Andrew Rustleund" - email = "andrew@rustleund.com" - } - developer { - id = "afezeria" - name = "afezeria" - email = "zodal@outlook.com" - } - developer { - id = "scorsi" - name = "Sylvain Corsini" - email = "sylvain.corsini@protonmail.com" - } - developer { - id = "lyndsysimon" - name = "Lyndsy Simon" - email = "lyndsy@lyndsysimon.com" - } - developer { - id = "antonydenyer" - name = "Antony Denyer" - email = "git@antonydenyer.co.uk" - } - developer { - id = "mik629" - name = "Mikhail Erkhov" - email = "mikhail.erkhov@gmail.com" - } - developer { - id = "sinzed" - name = "Saeed Zahedi" - email = "saeedzhd@gmail.com" - } - developer { - id = "smn-dv" - name = "Simon Schoof" - email = "simon.schoof@hey.com" - } - developer { - id = "pedrod" - name = "Pedro Domingues" - email = "pedro.domingues.pt@gmail.com" - } - developer { - id = "efenderbosch" - name = "Eric Fenderbosch" - email = "eric@fender.net" - } - } - } - } - } - - repositories { - maven { - name = "central" - url = "https://oss.sonatype.org/service/local/staging/deploy/maven2" - credentials { - username = System.getenv("OSSRH_USER") - password = System.getenv("OSSRH_PASSWORD") - } - } - maven { - name = "snapshot" - url = "https://oss.sonatype.org/content/repositories/snapshots" - credentials { - username = System.getenv("OSSRH_USER") - password = System.getenv("OSSRH_PASSWORD") - } - } - } - } - - signing { - def keyId = System.getenv("GPG_KEY_ID") - def secretKey = System.getenv("GPG_SECRET_KEY") - def password = System.getenv("GPG_PASSWORD") - - required { !project.version.endsWith("SNAPSHOT") } - useInMemoryPgpKeys(keyId, secretKey, password) - sign publishing.publications.dist - } +subprojects { + apply plugin: "module-conventions" } task printClasspath() { diff --git a/buildSrc/src/main/kotlin/module-conventions.gradle.kts b/buildSrc/src/main/kotlin/module-conventions.gradle.kts index 5037f854..1102f2f2 100644 --- a/buildSrc/src/main/kotlin/module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/module-conventions.gradle.kts @@ -6,10 +6,186 @@ plugins { id("io.gitlab.arturbosch.detekt") } -detekt { +repositories { + mavenCentral() +} + +dependencies { + api(kotlin("stdlib")) + api(kotlin("reflect")) + testImplementation(kotlin("test")) + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.17.0-RC2") +} +tasks { + withType { + kotlinOptions { + jvmTarget = "1.6" + allWarningsAsErrors = true + freeCompilerArgs = listOf( + "-Xexplicit-api=strict", + "-Xopt-in=kotlin.RequiresOptIn" + ) + } + } + + register("generateSources") { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) + } + + register("generateJavadoc") { + archiveClassifier.set("javadoc") + } +} + +detekt { + toolVersion = "1.17.0-RC2" + input = files("src/main/kotlin") + config = files("${project.rootDir}/detekt.yml") } publishing { + publications { + create("dist") { + from(components["java"]) + artifact("generateSources") + artifact("generateJavadoc") + + groupId = project.group.toString() + artifactId = project.name + version = project.version.toString() + + pom { + name.set("${project.group}:${project.name}") + description.set("A lightweight ORM Framework for Kotlin with strong typed SQL DSL and sequence APIs.") + url.set("https://www.ktorm.org") + licenses { + license { + name.set("The Apache Software License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + scm { + url.set("https://github.com/kotlin-orm/ktorm") + connection.set("scm:git:https://github.com/kotlin-orm/ktorm.git") + developerConnection.set("scm:git:ssh://git@github.com/kotlin-orm/ktorm.git") + } + developers { + developer { + id.set("vincentlauvlwj") + name.set("vince") + email.set("me@liuwj.me") + } + developer { + id.set("waluo") + name.set("waluo") + email.set("1b79349b@gmail.com") + } + developer { + id.set("clydebarrow") + name.set("Clyde") + email.set("clyde@control-j.com") + } + developer { + id.set("Ray-Eldath") + name.set("Ray Eldath") + email.set("ray.eldath@outlook.com") + } + developer { + id.set("hangingman") + name.set("hiroyuki.nagata") + email.set("idiotpanzer@gmail.com") + } + developer { + id.set("onXoot") + name.set("beetlerx") + email.set("beetlerx@gmail.com") + } + developer { + id.set("arustleund") + name.set("Andrew Rustleund") + email.set("andrew@rustleund.com") + } + developer { + id.set("afezeria") + name.set("afezeria") + email.set("zodal@outlook.com") + } + developer { + id.set("scorsi") + name.set("Sylvain Corsini") + email.set("sylvain.corsini@protonmail.com") + } + developer { + id.set("lyndsysimon") + name.set("Lyndsy Simon") + email.set("lyndsy@lyndsysimon.com") + } + developer { + id.set("antonydenyer") + name.set("Antony Denyer") + email.set("git@antonydenyer.co.uk") + } + developer { + id.set("mik629") + name.set("Mikhail Erkhov") + email.set("mikhail.erkhov@gmail.com") + } + developer { + id.set("sinzed") + name.set("Saeed Zahedi") + email.set("saeedzhd@gmail.com") + } + developer { + id.set("smn-dv") + name.set("Simon Schoof") + email.set("simon.schoof@hey.com") + } + developer { + id.set("pedrod") + name.set("Pedro Domingues") + email.set("pedro.domingues.pt@gmail.com") + } + developer { + id.set("efenderbosch") + name.set("Eric Fenderbosch") + email.set("eric@fender.net") + } + } + } + } + + repositories { + maven { + name = "central" + url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2") + credentials { + username = System.getenv("OSSRH_USER") + password = System.getenv("OSSRH_PASSWORD") + } + } + maven { + name = "snapshot" + url = uri("https://oss.sonatype.org/content/repositories/snapshots") + credentials { + username = System.getenv("OSSRH_USER") + password = System.getenv("OSSRH_PASSWORD") + } + } + } + } +} + +signing { + val keyId = System.getenv("GPG_KEY_ID") + val secretKey = System.getenv("GPG_SECRET_KEY") + val password = System.getenv("GPG_PASSWORD") + + setRequired({ + !project.version.toString().endsWith("SNAPSHOT") + }) + useInMemoryPgpKeys(keyId, secretKey, password) + sign(publishing.publications["dist"]) } From 80f8bc20eb06ac79c3ebac0d8c293a283b4e1b8b Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 14 May 2021 22:22:05 +0800 Subject: [PATCH 007/126] migrate check source header --- .../kotlin/check-source-header.gradle.kts | 40 +++++++++++++++++++ .../main/kotlin/module-conventions.gradle.kts | 1 + 2 files changed, 41 insertions(+) create mode 100644 buildSrc/src/main/kotlin/check-source-header.gradle.kts diff --git a/buildSrc/src/main/kotlin/check-source-header.gradle.kts b/buildSrc/src/main/kotlin/check-source-header.gradle.kts new file mode 100644 index 00000000..8806a00f --- /dev/null +++ b/buildSrc/src/main/kotlin/check-source-header.gradle.kts @@ -0,0 +1,40 @@ + +plugins { + id("kotlin") +} + +val licenseHeaderText = """/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +""" + +val checkSourceHeader by tasks.registering { + doLast { + val sources = sourceSets.main.get() + + for (dir in sources.allSource.srcDirs) { + val tree = fileTree(dir) + tree.include("**/*.kt") + + tree.visit { + if (file.isFile && !file.readText().startsWith(licenseHeaderText)) { + throw IllegalStateException("Copyright header not found in file: $file") + } + } + } + } +} + +tasks.getByName("check").dependsOn(checkSourceHeader) diff --git a/buildSrc/src/main/kotlin/module-conventions.gradle.kts b/buildSrc/src/main/kotlin/module-conventions.gradle.kts index 1102f2f2..45dfdfec 100644 --- a/buildSrc/src/main/kotlin/module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/module-conventions.gradle.kts @@ -4,6 +4,7 @@ plugins { id("signing") id("maven-publish") id("io.gitlab.arturbosch.detekt") + id("check-source-header") } repositories { From 2fd7c75924fe5daf865c35dd48ccc26d381e1e60 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 15 May 2021 00:33:33 +0800 Subject: [PATCH 008/126] migrate generate tuples --- .../main/kotlin/generate-tuples.gradle.kts | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 buildSrc/src/main/kotlin/generate-tuples.gradle.kts diff --git a/buildSrc/src/main/kotlin/generate-tuples.gradle.kts b/buildSrc/src/main/kotlin/generate-tuples.gradle.kts new file mode 100644 index 00000000..e7923283 --- /dev/null +++ b/buildSrc/src/main/kotlin/generate-tuples.gradle.kts @@ -0,0 +1,449 @@ + +plugins { + id("kotlin") +} + +val generatedSourceDir = "${project.buildDir.absolutePath}/generated/source/main/kotlin" +val maxTupleNumber = 9 + +fun generateTuple(writer: java.io.Writer, tupleNumber: Int) { + val typeParams = (1..tupleNumber).joinToString(separator = ", ") { "out E$it" } + val propertyDefinitions = (1..tupleNumber).joinToString(separator = ",\n ") { "val element$it: E$it" } + val toStringTemplate = (1..tupleNumber).joinToString(separator = ", ") { "\$element$it" } + + writer.write(""" + /** + * Represents a tuple of $tupleNumber values. + * + * There is no meaning attached to values in this class, it can be used for any purpose. + * Two tuples are equal if all the components are equal. + */ + public data class Tuple$tupleNumber<$typeParams>( + $propertyDefinitions + ) : Serializable { + + override fun toString(): String { + return "($toStringTemplate)" + } + + private companion object { + private const val serialVersionUID = 1L + } + } + """.trimIndent()) +} + +fun generateTupleOf(writer: java.io.Writer, tupleNumber: Int) { + val typeParams = (1..tupleNumber).joinToString(separator = ", ") { "E$it" } + val params = (1..tupleNumber).joinToString(separator = ",\n ") { "element$it: E$it" } + val elements = (1..tupleNumber).joinToString(separator = ", ") { "element$it" } + + writer.write(""" + /** + * Create a tuple of $tupleNumber values. + * + * @since 2.7 + */ + public fun <$typeParams> tupleOf( + $params + ): Tuple$tupleNumber<$typeParams> { + return Tuple$tupleNumber($elements) + } + """.trimIndent()) +} + +fun generateToList(writer: java.io.Writer, tupleNumber: Int) { + val typeParams = (1..tupleNumber).joinToString(separator = ", ") { "E" } + val elements = (1..tupleNumber).joinToString(separator = ", ") { "element$it" } + + writer.write(""" + /** + * Convert this tuple into a list. + * + * @since 2.7 + */ + public fun Tuple$tupleNumber<$typeParams>.toList(): List { + return listOf($elements) + } + """.trimIndent()) +} + +fun generateMapColumns(writer: java.io.Writer, tupleNumber: Int) { + val typeParams = (1..tupleNumber).joinToString(separator = ", ") { "C$it : Any" } + val columnDeclarings = (1..tupleNumber).joinToString(separator = ", ") { "ColumnDeclaring" } + val resultTypes = (1..tupleNumber).joinToString(separator = ", ") { "C$it?" } + val variableNames = (1..tupleNumber).joinToString(separator = ", ") { "c$it" } + val resultExtractors = (1..tupleNumber).joinToString(separator = ", ") { "c${it}.sqlType.getResult(row, $it)" } + + writer.write(""" + /** + * Customize the selected columns of the internal query by the given [columnSelector] function, and return a [List] + * containing the query results. + * + * See [EntitySequence.mapColumns] for more details. + * + * The operation is terminal. + * + * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. + * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. + * @return a list of the query results. + */ + @Deprecated( + message = "This function will be removed in the future. Please use mapColumns { .. } instead.", + replaceWith = ReplaceWith("mapColumns(isDistinct, columnSelector)") + ) + public inline fun , $typeParams> EntitySequence.mapColumns$tupleNumber( + isDistinct: Boolean = false, + columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): List> { + return mapColumns(isDistinct, columnSelector) + } + + /** + * Customize the selected columns of the internal query by the given [columnSelector] function, and append the query + * results to the given [destination]. + * + * See [EntitySequence.mapColumnsTo] for more details. + * + * The operation is terminal. + * + * @param destination a [MutableCollection] used to store the results. + * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. + * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. + * @return the [destination] collection of the query results. + */ + @Deprecated( + message = "This function will be removed in the future. Please use mapColumnsTo(destination) { .. } instead.", + replaceWith = ReplaceWith("mapColumnsTo(destination, isDistinct, columnSelector)") + ) + public inline fun , $typeParams, R> EntitySequence.mapColumns${tupleNumber}To( + destination: R, + isDistinct: Boolean = false, + columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): R where R : MutableCollection> { + return mapColumnsTo(destination, isDistinct, columnSelector) + } + + /** + * Customize the selected columns of the internal query by the given [columnSelector] function, and return a [List] + * containing the query results. + * + * This function is similar to [EntitySequence.map], but the [columnSelector] closure accepts the current table + * object [T] as the parameter, so what we get in the closure by `it` is the table object instead of an entity + * element. Besides, the function’s return type is a tuple of `ColumnDeclaring`s, and we should return some + * columns or expressions to customize the `select` clause of the generated SQL. + * + * Ktorm supports selecting two or more columns, we just need to wrap our selected columns by [tupleOf] + * in the closure, then the function’s return type becomes `List>`. + * + * The operation is terminal. + * + * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. + * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. + * @return a list of the query results. + * @since 3.1.0 + */ + @JvmName("_mapColumns$tupleNumber") + @OptIn(ExperimentalTypeInference::class) + @OverloadResolutionByLambdaReturnType + public inline fun , $typeParams> EntitySequence.mapColumns( + isDistinct: Boolean = false, + columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): List> { + return mapColumnsTo(ArrayList(), isDistinct, columnSelector) + } + + /** + * Customize the selected columns of the internal query by the given [columnSelector] function, and append the query + * results to the given [destination]. + * + * This function is similar to [EntitySequence.mapTo], but the [columnSelector] closure accepts the current table + * object [T] as the parameter, so what we get in the closure by `it` is the table object instead of an entity + * element. Besides, the function’s return type is a tuple of `ColumnDeclaring`s, and we should return some + * columns or expressions to customize the `select` clause of the generated SQL. + * + * Ktorm supports selecting two or more columns, we just need to wrap our selected columns by [tupleOf] + * in the closure, then the function’s return type becomes `List>`. + * + * The operation is terminal. + * + * @param destination a [MutableCollection] used to store the results. + * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. + * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. + * @return the [destination] collection of the query results. + * @since 3.1.0 + */ + @JvmName("_mapColumns${tupleNumber}To") + @OptIn(ExperimentalTypeInference::class) + @OverloadResolutionByLambdaReturnType + public inline fun , $typeParams, R> EntitySequence.mapColumnsTo( + destination: R, + isDistinct: Boolean = false, + columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): R where R : MutableCollection> { + val ($variableNames) = columnSelector(sourceTable) + + val expr = expression.copy( + columns = listOf($variableNames).map { it.aliased(null) }, + isDistinct = isDistinct + ) + + return Query(database, expr).mapTo(destination) { row -> tupleOf($resultExtractors) } + } + """.trimIndent()) +} + +fun generateAggregateColumns(writer: java.io.Writer, tupleNumber: Int) { + val typeParams = (1..tupleNumber).joinToString(separator = ", ") { "C$it : Any" } + val columnDeclarings = (1..tupleNumber).joinToString(separator = ", ") { "ColumnDeclaring" } + val resultTypes = (1..tupleNumber).joinToString(separator = ", ") { "C$it?" } + val variableNames = (1..tupleNumber).joinToString(separator = ", ") { "c$it" } + val resultExtractors = (1..tupleNumber).joinToString(separator = ", ") { "c${it}.sqlType.getResult(rowSet, $it)" } + + writer.write(""" + /** + * Perform a tuple of aggregations given by [aggregationSelector] for all elements in the sequence, + * and return the aggregate results. + * + * The operation is terminal. + * + * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. + * @return a tuple of the aggregate results. + */ + @Deprecated( + message = "This function will be removed in the future. Please use aggregateColumns { .. } instead.", + replaceWith = ReplaceWith("aggregateColumns(aggregationSelector)") + ) + public inline fun , $typeParams> EntitySequence.aggregateColumns$tupleNumber( + aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): Tuple$tupleNumber<$resultTypes> { + return aggregateColumns(aggregationSelector) + } + + /** + * Perform a tuple of aggregations given by [aggregationSelector] for all elements in the sequence, + * and return the aggregate results. + * + * Ktorm supports aggregating two or more columns, we just need to wrap our aggregate expressions by + * [tupleOf] in the closure, then the function’s return type becomes `TupleN`. + * + * The operation is terminal. + * + * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. + * @return a tuple of the aggregate results. + * @since 3.1.0 + */ + @JvmName("_aggregateColumns$tupleNumber") + @OptIn(ExperimentalTypeInference::class) + @OverloadResolutionByLambdaReturnType + public inline fun , $typeParams> EntitySequence.aggregateColumns( + aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): Tuple$tupleNumber<$resultTypes> { + val ($variableNames) = aggregationSelector(sourceTable) + + val expr = expression.copy( + columns = listOf($variableNames).map { it.aliased(null) } + ) + + val rowSet = Query(database, expr).rowSet + + if (rowSet.size() == 1) { + check(rowSet.next()) + return tupleOf($resultExtractors) + } else { + val (sql, _) = database.formatExpression(expr, beautifySql = true) + throw IllegalStateException("Expected 1 row but ${'$'}{rowSet.size()} returned from sql: \n\n${'$'}sql") + } + } + """.trimIndent()) +} + +fun generateGroupingAggregateColumns(writer: java.io.Writer, tupleNumber: Int) { + val typeParams = (1..tupleNumber).joinToString(separator = ", ") { "C$it : Any" } + val columnDeclarings = (1..tupleNumber).joinToString(separator = ", ") { "ColumnDeclaring" } + val resultTypes = (1..tupleNumber).joinToString(separator = ", ") { "C$it?" } + val variableNames = (1..tupleNumber).joinToString(separator = ", ") { "c$it" } + val resultExtractors = (1..tupleNumber).joinToString(separator = ", ") { "c${it}.sqlType.getResult(row, ${it + 1})" } + + writer.write(""" + /** + * Group elements from the source sequence by key and perform the given aggregations for elements in each group, + * then store the results in a new [Map]. + * + * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: + * `select key, aggregation from source group by key`. + * + * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. + * @return a [Map] associating the key of each group with the results of aggregations of the group elements. + */ + @Deprecated( + message = "This function will be removed in the future. Please use aggregateColumns { .. } instead.", + replaceWith = ReplaceWith("aggregateColumns(aggregationSelector)") + ) + public inline fun , K : Any, $typeParams> EntityGrouping.aggregateColumns$tupleNumber( + aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): Map> { + return aggregateColumns(aggregationSelector) + } + + /** + * Group elements from the source sequence by key and perform the given aggregations for elements in each group, + * then store the results in the [destination] map. + * + * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: + * `select key, aggregation from source group by key`. + * + * @param destination a [MutableMap] used to store the results. + * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. + * @return the [destination] map associating the key of each group with the result of aggregations of the group elements. + */ + @Deprecated( + message = "This function will be removed in the future. Please use aggregateColumns(destination) { .. } instead.", + replaceWith = ReplaceWith("aggregateColumns(destination, aggregationSelector)") + ) + public inline fun , K : Any, $typeParams, M> EntityGrouping.aggregateColumns${tupleNumber}To( + destination: M, + aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): M where M : MutableMap> { + return aggregateColumnsTo(destination, aggregationSelector) + } + + /** + * Group elements from the source sequence by key and perform the given aggregations for elements in each group, + * then store the results in a new [Map]. + * + * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: + * `select key, aggregation from source group by key`. + * + * Ktorm supports aggregating two or more columns, we just need to wrap our aggregate expressions by [tupleOf] + * in the closure, then the function’s return type becomes `Map>`. + * + * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. + * @return a [Map] associating the key of each group with the results of aggregations of the group elements. + * @since 3.1.0 + */ + @JvmName("_aggregateColumns$tupleNumber") + @OptIn(ExperimentalTypeInference::class) + @OverloadResolutionByLambdaReturnType + public inline fun , K : Any, $typeParams> EntityGrouping.aggregateColumns( + aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): Map> { + return aggregateColumnsTo(LinkedHashMap(), aggregationSelector) + } + + /** + * Group elements from the source sequence by key and perform the given aggregations for elements in each group, + * then store the results in the [destination] map. + * + * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: + * `select key, aggregation from source group by key`. + * + * Ktorm supports aggregating two or more columns, we just need to wrap our aggregate expressions by [tupleOf] + * in the closure, then the function’s return type becomes `Map>`. + * + * @param destination a [MutableMap] used to store the results. + * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. + * @return the [destination] map associating the key of each group with the result of aggregations of the group elements. + * @since 3.1.0 + */ + @JvmName("_aggregateColumns${tupleNumber}To") + @OptIn(ExperimentalTypeInference::class) + @OverloadResolutionByLambdaReturnType + public inline fun , K : Any, $typeParams, M> EntityGrouping.aggregateColumnsTo( + destination: M, + aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> + ): M where M : MutableMap> { + val keyColumn = keySelector(sequence.sourceTable) + val ($variableNames) = aggregationSelector(sequence.sourceTable) + + val expr = sequence.expression.copy( + columns = listOf(keyColumn, $variableNames).map { it.aliased(null) }, + groupBy = listOf(keyColumn.asExpression()) + ) + + for (row in Query(sequence.database, expr)) { + val key = keyColumn.sqlType.getResult(row, 1) + destination[key] = tupleOf($resultExtractors) + } + + return destination + } + """.trimIndent()) +} + +val generateTuples by tasks.registering { + doLast { + val outputFile = file("$generatedSourceDir/org/ktorm/entity/Tuples.kt") + outputFile.parentFile.mkdirs() + + outputFile.bufferedWriter().use { writer -> + writer.write("""/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + // This file is auto-generated by generate-tuples.gradle, DO NOT EDIT! + + package org.ktorm.entity + + import org.ktorm.dsl.Query + import org.ktorm.dsl.mapTo + import org.ktorm.schema.ColumnDeclaring + import org.ktorm.schema.BaseTable + import java.io.Serializable + import kotlin.experimental.ExperimentalTypeInference + + /** + * Set a typealias `Tuple2` for `Pair`. + */ + public typealias Tuple2 = Pair + + /** + * Set a typealias `Tuple3` for `Triple`. + */ + public typealias Tuple3 = Triple + """.trimIndent()) + + for (num in (4..maxTupleNumber)) { + generateTuple(writer, num) + } + + for (num in (2..maxTupleNumber)) { + generateTupleOf(writer, num) + } + + for (num in (4..maxTupleNumber)) { + generateToList(writer, num) + } + + for (num in (2..maxTupleNumber)) { + generateMapColumns(writer, num) + } + + for (num in (2..maxTupleNumber)) { + generateAggregateColumns(writer, num) + } + + for (num in (2..maxTupleNumber)) { + generateGroupingAggregateColumns(writer, num) + } + } + } +} + +sourceSets.main { + java.srcDirs(generatedSourceDir) +} + +tasks.getByName("compileKotlin").dependsOn(generateTuples) From 5ccdb1f7d5913e060651befdb369fb567fa8a7bc Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 15 May 2021 00:35:05 +0800 Subject: [PATCH 009/126] migrate generate tuples --- buildSrc/src/main/kotlin/generate-tuples.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/generate-tuples.gradle.kts b/buildSrc/src/main/kotlin/generate-tuples.gradle.kts index e7923283..c8b9c8bf 100644 --- a/buildSrc/src/main/kotlin/generate-tuples.gradle.kts +++ b/buildSrc/src/main/kotlin/generate-tuples.gradle.kts @@ -377,7 +377,8 @@ val generateTuples by tasks.registering { outputFile.parentFile.mkdirs() outputFile.bufferedWriter().use { writer -> - writer.write("""/* + writer.write(""" + /* * Copyright 2018-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); From a4e2729a74bfc19f1a1845ec8152f8be68a606ee Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 15 May 2021 14:55:19 +0800 Subject: [PATCH 010/126] migrate root project --- build.gradle | 24 ------------------- build.gradle.kts | 18 ++++++++++++++ check-source-header.gradle | 49 -------------------------------------- settings.gradle | 14 ----------- settings.gradle.kts | 14 +++++++++++ 5 files changed, 32 insertions(+), 87 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 check-source-header.gradle delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 5d9806ef..00000000 --- a/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ - -allprojects { - group = "org.ktorm" - version = "3.5.0-SNAPSHOT" -} - -subprojects { - apply plugin: "module-conventions" -} - -task printClasspath() { - doLast { - def jars = subprojects.collect { it.configurations.compileClasspath.getFiles() }.flatten().toSet() - jars.removeIf { it.name.contains("ktorm") } - - println("Project classpath: ") - jars.each { println(it.name) } - - def file = file("build/ktorm.classpath") - file.parentFile.mkdirs() - file.write(jars.collect { it.absolutePath }.join(File.pathSeparator)) - println("Classpath written to build/ktorm.classpath") - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..e6ec56af --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,18 @@ + +group = "org.ktorm" +version = "3.5.0-SNAPSHOT" + +task("printClasspath") { + doLast { + val jars = subprojects.flatMapTo(HashSet()) { it.configurations["compileClasspath"].files } + jars.removeIf { it.name.contains("ktorm") } + + println("Project classpath: ") + jars.forEach { println(it.name) } + + val file = file("build/ktorm.classpath") + file.parentFile.mkdirs() + file.writeText(jars.joinToString(File.pathSeparator) { it.absolutePath }) + println("Classpath written to build/ktorm.classpath") + } +} diff --git a/check-source-header.gradle b/check-source-header.gradle deleted file mode 100644 index 60d3fd8d..00000000 --- a/check-source-header.gradle +++ /dev/null @@ -1,49 +0,0 @@ - -project.ext.licenseHeaderText = """/* - * Copyright 2018-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -""" - -task checkCopyrightHeader { - doLast { - def headerLines = project.licenseHeaderText.readLines() - - sourceSets.main.kotlin.srcDirs.each { dir -> - def tree = fileTree(dir) - tree.include("**/*.kt") - - tree.visit { - if (!it.isDirectory()) { - def failed = false - - it.file.withReader { reader -> - for (line in headerLines) { - if (line != reader.readLine()) { - failed = true - break - } - } - } - - if (failed) { - throw new IllegalStateException("Copyright header not found in file: " + it.file) - } - } - } - } - } -} - -check.dependsOn(checkCopyrightHeader) \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 96b80edf..00000000 --- a/settings.gradle +++ /dev/null @@ -1,14 +0,0 @@ - -include "ktorm-core" -include "ktorm-global" -include "ktorm-jackson" -include "ktorm-support-mysql" -include "ktorm-support-oracle" -include "ktorm-support-postgresql" -include "ktorm-support-sqlite" -include "ktorm-support-sqlserver" - -rootProject.name = "ktorm" -rootProject.children.each { project -> - project.buildFileName = "${project.name}.gradle" -} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..7e735fc9 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,14 @@ + +include("ktorm-core") +include("ktorm-global") +include("ktorm-jackson") +include("ktorm-support-mysql") +include("ktorm-support-oracle") +include("ktorm-support-postgresql") +include("ktorm-support-sqlite") +include("ktorm-support-sqlserver") + +rootProject.name = "ktorm" +rootProject.children.forEach { project -> + project.buildFileName = "${project.name}.gradle.kts" +} From 0a311ef2b6eab01c620100419f21cabcd5e455f5 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 15 May 2021 18:48:55 +0800 Subject: [PATCH 011/126] migrate core module --- ...ts => ktorm.module-conventions.gradle.kts} | 4 +- ...s => ktorm.source-header-check.gradle.kts} | 0 ...kts => ktorm.tuples-generation.gradle.kts} | 0 ktorm-core/generate-tuples.gradle | 432 ------------------ ktorm-core/ktorm-core.gradle | 25 - ktorm-core/ktorm-core.gradle.kts | 30 ++ 6 files changed, 32 insertions(+), 459 deletions(-) rename buildSrc/src/main/kotlin/{module-conventions.gradle.kts => ktorm.module-conventions.gradle.kts} (98%) rename buildSrc/src/main/kotlin/{check-source-header.gradle.kts => ktorm.source-header-check.gradle.kts} (100%) rename buildSrc/src/main/kotlin/{generate-tuples.gradle.kts => ktorm.tuples-generation.gradle.kts} (100%) delete mode 100644 ktorm-core/generate-tuples.gradle delete mode 100644 ktorm-core/ktorm-core.gradle create mode 100644 ktorm-core/ktorm-core.gradle.kts diff --git a/buildSrc/src/main/kotlin/module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts similarity index 98% rename from buildSrc/src/main/kotlin/module-conventions.gradle.kts rename to buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index 45dfdfec..02c63e95 100644 --- a/buildSrc/src/main/kotlin/module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -4,7 +4,7 @@ plugins { id("signing") id("maven-publish") id("io.gitlab.arturbosch.detekt") - id("check-source-header") + id("ktorm.source-header-check") } repositories { @@ -32,7 +32,7 @@ tasks { register("generateSources") { archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) + from(sourceSets.main.map { it.allSource }) } register("generateJavadoc") { diff --git a/buildSrc/src/main/kotlin/check-source-header.gradle.kts b/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts similarity index 100% rename from buildSrc/src/main/kotlin/check-source-header.gradle.kts rename to buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts diff --git a/buildSrc/src/main/kotlin/generate-tuples.gradle.kts b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts similarity index 100% rename from buildSrc/src/main/kotlin/generate-tuples.gradle.kts rename to buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts diff --git a/ktorm-core/generate-tuples.gradle b/ktorm-core/generate-tuples.gradle deleted file mode 100644 index ce4d6ea2..00000000 --- a/ktorm-core/generate-tuples.gradle +++ /dev/null @@ -1,432 +0,0 @@ - -def generatedSourceDir = "${project.buildDir.absolutePath}/generated/source/main/kotlin" -def maxTupleNumber = 9 - -def generateTuple(Writer writer, int tupleNumber) { - def typeParams = (1..tupleNumber).collect { "out E$it" }.join(", ") - def propertyDefinitions = (1..tupleNumber).collect { "val element$it: E$it" }.join(",\n ") - def toStringTemplate = (1..tupleNumber).collect { "\$element$it" }.join(", ") - - writer.write(""" - /** - * Represents a tuple of $tupleNumber values. - * - * There is no meaning attached to values in this class, it can be used for any purpose. - * Two tuples are equal if all the components are equal. - */ - public data class Tuple$tupleNumber<$typeParams>( - $propertyDefinitions - ) : Serializable { - - override fun toString(): String { - return \"($toStringTemplate)\" - } - - private companion object { - private const val serialVersionUID = 1L - } - } - """.stripIndent()) -} - -def generateTupleOf(Writer writer, int tupleNumber) { - def typeParams = (1..tupleNumber).collect { "E$it" }.join(", ") - def params = (1..tupleNumber).collect { "element$it: E$it" }.join(",\n ") - def elements = (1..tupleNumber).collect { "element$it" }.join(", ") - - writer.write(""" - /** - * Create a tuple of $tupleNumber values. - * - * @since 2.7 - */ - public fun <$typeParams> tupleOf( - $params - ): Tuple$tupleNumber<$typeParams> { - return Tuple$tupleNumber($elements) - } - """.stripIndent()) -} - -def generateToList(Writer writer, int tupleNumber) { - def typeParams = (1..tupleNumber).collect { "E" }.join(", ") - def elements = (1..tupleNumber).collect { "element$it" }.join(", ") - - writer.write(""" - /** - * Convert this tuple into a list. - * - * @since 2.7 - */ - public fun Tuple$tupleNumber<$typeParams>.toList(): List { - return listOf($elements) - } - """.stripIndent()) -} - -def generateMapColumns(Writer writer, int tupleNumber) { - def typeParams = (1..tupleNumber).collect { "C$it : Any" }.join(", ") - def columnDeclarings = (1..tupleNumber).collect { "ColumnDeclaring" }.join(", ") - def resultTypes = (1..tupleNumber).collect { "C$it?" }.join(", ") - def variableNames = (1..tupleNumber).collect { "c$it" }.join(", ") - def resultExtractors = (1..tupleNumber).collect { "c${it}.sqlType.getResult(row, $it)" }.join(", ") - - writer.write(""" - /** - * Customize the selected columns of the internal query by the given [columnSelector] function, and return a [List] - * containing the query results. - * - * See [EntitySequence.mapColumns] for more details. - * - * The operation is terminal. - * - * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. - * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. - * @return a list of the query results. - */ - @Deprecated( - message = "This function will be removed in the future. Please use mapColumns { .. } instead.", - replaceWith = ReplaceWith("mapColumns(isDistinct, columnSelector)") - ) - public inline fun , $typeParams> EntitySequence.mapColumns$tupleNumber( - isDistinct: Boolean = false, - columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): List> { - return mapColumns(isDistinct, columnSelector) - } - - /** - * Customize the selected columns of the internal query by the given [columnSelector] function, and append the query - * results to the given [destination]. - * - * See [EntitySequence.mapColumnsTo] for more details. - * - * The operation is terminal. - * - * @param destination a [MutableCollection] used to store the results. - * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. - * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. - * @return the [destination] collection of the query results. - */ - @Deprecated( - message = "This function will be removed in the future. Please use mapColumnsTo(destination) { .. } instead.", - replaceWith = ReplaceWith("mapColumnsTo(destination, isDistinct, columnSelector)") - ) - public inline fun , $typeParams, R> EntitySequence.mapColumns${tupleNumber}To( - destination: R, - isDistinct: Boolean = false, - columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): R where R : MutableCollection> { - return mapColumnsTo(destination, isDistinct, columnSelector) - } - - /** - * Customize the selected columns of the internal query by the given [columnSelector] function, and return a [List] - * containing the query results. - * - * This function is similar to [EntitySequence.map], but the [columnSelector] closure accepts the current table - * object [T] as the parameter, so what we get in the closure by `it` is the table object instead of an entity - * element. Besides, the function’s return type is a tuple of `ColumnDeclaring`s, and we should return some - * columns or expressions to customize the `select` clause of the generated SQL. - * - * Ktorm supports selecting two or more columns, we just need to wrap our selected columns by [tupleOf] - * in the closure, then the function’s return type becomes `List>`. - * - * The operation is terminal. - * - * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. - * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. - * @return a list of the query results. - * @since 3.1.0 - */ - @JvmName("_mapColumns$tupleNumber") - @OptIn(ExperimentalTypeInference::class) - @OverloadResolutionByLambdaReturnType - public inline fun , $typeParams> EntitySequence.mapColumns( - isDistinct: Boolean = false, - columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): List> { - return mapColumnsTo(ArrayList(), isDistinct, columnSelector) - } - - /** - * Customize the selected columns of the internal query by the given [columnSelector] function, and append the query - * results to the given [destination]. - * - * This function is similar to [EntitySequence.mapTo], but the [columnSelector] closure accepts the current table - * object [T] as the parameter, so what we get in the closure by `it` is the table object instead of an entity - * element. Besides, the function’s return type is a tuple of `ColumnDeclaring`s, and we should return some - * columns or expressions to customize the `select` clause of the generated SQL. - * - * Ktorm supports selecting two or more columns, we just need to wrap our selected columns by [tupleOf] - * in the closure, then the function’s return type becomes `List>`. - * - * The operation is terminal. - * - * @param destination a [MutableCollection] used to store the results. - * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. - * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. - * @return the [destination] collection of the query results. - * @since 3.1.0 - */ - @JvmName("_mapColumns${tupleNumber}To") - @OptIn(ExperimentalTypeInference::class) - @OverloadResolutionByLambdaReturnType - public inline fun , $typeParams, R> EntitySequence.mapColumnsTo( - destination: R, - isDistinct: Boolean = false, - columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): R where R : MutableCollection> { - val ($variableNames) = columnSelector(sourceTable) - - val expr = expression.copy( - columns = listOf($variableNames).map { it.aliased(null) }, - isDistinct = isDistinct - ) - - return Query(database, expr).mapTo(destination) { row -> tupleOf($resultExtractors) } - } - """.stripIndent()) -} - -def generateAggregateColumns(Writer writer, int tupleNumber) { - def typeParams = (1..tupleNumber).collect { "C$it : Any" }.join(", ") - def columnDeclarings = (1..tupleNumber).collect { "ColumnDeclaring" }.join(", ") - def resultTypes = (1..tupleNumber).collect { "C$it?" }.join(", ") - def variableNames = (1..tupleNumber).collect { "c$it" }.join(", ") - def resultExtractors = (1..tupleNumber).collect { "c${it}.sqlType.getResult(rowSet, $it)" }.join(", ") - - writer.write(""" - /** - * Perform a tuple of aggregations given by [aggregationSelector] for all elements in the sequence, - * and return the aggregate results. - * - * The operation is terminal. - * - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return a tuple of the aggregate results. - */ - @Deprecated( - message = "This function will be removed in the future. Please use aggregateColumns { .. } instead.", - replaceWith = ReplaceWith("aggregateColumns(aggregationSelector)") - ) - public inline fun , $typeParams> EntitySequence.aggregateColumns$tupleNumber( - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): Tuple$tupleNumber<$resultTypes> { - return aggregateColumns(aggregationSelector) - } - - /** - * Perform a tuple of aggregations given by [aggregationSelector] for all elements in the sequence, - * and return the aggregate results. - * - * Ktorm supports aggregating two or more columns, we just need to wrap our aggregate expressions by - * [tupleOf] in the closure, then the function’s return type becomes `TupleN`. - * - * The operation is terminal. - * - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return a tuple of the aggregate results. - * @since 3.1.0 - */ - @JvmName("_aggregateColumns$tupleNumber") - @OptIn(ExperimentalTypeInference::class) - @OverloadResolutionByLambdaReturnType - public inline fun , $typeParams> EntitySequence.aggregateColumns( - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): Tuple$tupleNumber<$resultTypes> { - val ($variableNames) = aggregationSelector(sourceTable) - - val expr = expression.copy( - columns = listOf($variableNames).map { it.aliased(null) } - ) - - val rowSet = Query(database, expr).rowSet - - if (rowSet.size() == 1) { - check(rowSet.next()) - return tupleOf($resultExtractors) - } else { - val (sql, _) = database.formatExpression(expr, beautifySql = true) - throw IllegalStateException("Expected 1 row but \${rowSet.size()} returned from sql: \\n\\n\$sql") - } - } - """.stripIndent()) -} - -def generateGroupingAggregateColumns(Writer writer, int tupleNumber) { - def typeParams = (1..tupleNumber).collect { "C$it : Any" }.join(", ") - def columnDeclarings = (1..tupleNumber).collect { "ColumnDeclaring" }.join(", ") - def resultTypes = (1..tupleNumber).collect { "C$it?" }.join(", ") - def variableNames = (1..tupleNumber).collect { "c$it" }.join(", ") - def resultExtractors = (1..tupleNumber).collect { "c${it}.sqlType.getResult(row, ${it + 1})" }.join(", ") - - writer.write(""" - /** - * Group elements from the source sequence by key and perform the given aggregations for elements in each group, - * then store the results in a new [Map]. - * - * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: - * `select key, aggregation from source group by key`. - * - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return a [Map] associating the key of each group with the results of aggregations of the group elements. - */ - @Deprecated( - message = "This function will be removed in the future. Please use aggregateColumns { .. } instead.", - replaceWith = ReplaceWith("aggregateColumns(aggregationSelector)") - ) - public inline fun , K : Any, $typeParams> EntityGrouping.aggregateColumns$tupleNumber( - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): Map> { - return aggregateColumns(aggregationSelector) - } - - /** - * Group elements from the source sequence by key and perform the given aggregations for elements in each group, - * then store the results in the [destination] map. - * - * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: - * `select key, aggregation from source group by key`. - * - * @param destination a [MutableMap] used to store the results. - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return the [destination] map associating the key of each group with the result of aggregations of the group elements. - */ - @Deprecated( - message = "This function will be removed in the future. Please use aggregateColumns(destination) { .. } instead.", - replaceWith = ReplaceWith("aggregateColumns(destination, aggregationSelector)") - ) - public inline fun , K : Any, $typeParams, M> EntityGrouping.aggregateColumns${tupleNumber}To( - destination: M, - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): M where M : MutableMap> { - return aggregateColumnsTo(destination, aggregationSelector) - } - - /** - * Group elements from the source sequence by key and perform the given aggregations for elements in each group, - * then store the results in a new [Map]. - * - * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: - * `select key, aggregation from source group by key`. - * - * Ktorm supports aggregating two or more columns, we just need to wrap our aggregate expressions by [tupleOf] - * in the closure, then the function’s return type becomes `Map>`. - * - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return a [Map] associating the key of each group with the results of aggregations of the group elements. - * @since 3.1.0 - */ - @JvmName("_aggregateColumns$tupleNumber") - @OptIn(ExperimentalTypeInference::class) - @OverloadResolutionByLambdaReturnType - public inline fun , K : Any, $typeParams> EntityGrouping.aggregateColumns( - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): Map> { - return aggregateColumnsTo(LinkedHashMap(), aggregationSelector) - } - - /** - * Group elements from the source sequence by key and perform the given aggregations for elements in each group, - * then store the results in the [destination] map. - * - * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: - * `select key, aggregation from source group by key`. - * - * Ktorm supports aggregating two or more columns, we just need to wrap our aggregate expressions by [tupleOf] - * in the closure, then the function’s return type becomes `Map>`. - * - * @param destination a [MutableMap] used to store the results. - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return the [destination] map associating the key of each group with the result of aggregations of the group elements. - * @since 3.1.0 - */ - @JvmName("_aggregateColumns${tupleNumber}To") - @OptIn(ExperimentalTypeInference::class) - @OverloadResolutionByLambdaReturnType - public inline fun , K : Any, $typeParams, M> EntityGrouping.aggregateColumnsTo( - destination: M, - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): M where M : MutableMap> { - val keyColumn = keySelector(sequence.sourceTable) - val ($variableNames) = aggregationSelector(sequence.sourceTable) - - val expr = sequence.expression.copy( - columns = listOf(keyColumn, $variableNames).map { it.aliased(null) }, - groupBy = listOf(keyColumn.asExpression()) - ) - - for (row in Query(sequence.database, expr)) { - val key = keyColumn.sqlType.getResult(row, 1) - destination[key] = tupleOf($resultExtractors) - } - - return destination - } - """.stripIndent()) -} - -task generateTuples { - doLast { - def outputFile = file("$generatedSourceDir/org/ktorm/entity/Tuples.kt") - outputFile.parentFile.mkdirs() - - outputFile.withWriter { writer -> - writer.write(project.licenseHeaderText) - - writer.write(""" - // This file is auto-generated by generate-tuples.gradle, DO NOT EDIT! - - package org.ktorm.entity - - import org.ktorm.dsl.Query - import org.ktorm.dsl.mapTo - import org.ktorm.schema.ColumnDeclaring - import org.ktorm.schema.BaseTable - import java.io.Serializable - import kotlin.experimental.ExperimentalTypeInference - - /** - * Set a typealias `Tuple2` for `Pair`. - */ - public typealias Tuple2 = Pair - - /** - * Set a typealias `Tuple3` for `Triple`. - */ - public typealias Tuple3 = Triple - """.stripIndent()) - - (4..maxTupleNumber).each { num -> - generateTuple(writer, num) - } - - (2..maxTupleNumber).each { num -> - generateTupleOf(writer, num) - } - - (4..maxTupleNumber).each { num -> - generateToList(writer, num) - } - - (2..maxTupleNumber).each { num -> - generateMapColumns(writer, num) - } - - (2..maxTupleNumber).each { num -> - generateAggregateColumns(writer, num) - } - - (2..maxTupleNumber).each { num -> - generateGroupingAggregateColumns(writer, num) - } - } - } -} - -sourceSets { - main.kotlin.srcDirs += generatedSourceDir -} - -compileKotlin.dependsOn(generateTuples) diff --git a/ktorm-core/ktorm-core.gradle b/ktorm-core/ktorm-core.gradle deleted file mode 100644 index b8c815c4..00000000 --- a/ktorm-core/ktorm-core.gradle +++ /dev/null @@ -1,25 +0,0 @@ - -apply from: "generate-tuples.gradle" - -dependencies { - compileOnly "org.slf4j:slf4j-api:1.7.25" - compileOnly "commons-logging:commons-logging:1.2" - compileOnly "com.google.android:android:1.5_r4" - compileOnly "org.springframework:spring-jdbc:5.0.10.RELEASE" - compileOnly "org.springframework:spring-tx:5.0.10.RELEASE" - compileOnly "org.postgresql:postgresql:42.2.5" - testImplementation "com.h2database:h2:1.4.197" -} - -configurations { - testOutput.extendsFrom(testImplementation) -} - -task testJar(type: Jar, dependsOn: testClasses) { - from sourceSets.test.output - archiveClassifier = "test" -} - -artifacts { - testOutput testJar -} \ No newline at end of file diff --git a/ktorm-core/ktorm-core.gradle.kts b/ktorm-core/ktorm-core.gradle.kts new file mode 100644 index 00000000..01322bc0 --- /dev/null +++ b/ktorm-core/ktorm-core.gradle.kts @@ -0,0 +1,30 @@ + +plugins { + id("ktorm.module-conventions") + id("ktorm.tuples-generation") +} + +dependencies { + compileOnly("org.slf4j:slf4j-api:1.7.25") + compileOnly("commons-logging:commons-logging:1.2") + compileOnly("com.google.android:android:1.5_r4") + compileOnly("org.springframework:spring-jdbc:5.0.10.RELEASE") + compileOnly("org.springframework:spring-tx:5.0.10.RELEASE") + compileOnly("org.postgresql:postgresql:42.2.5") + testImplementation("com.h2database:h2:1.4.197") +} + +configurations { + val testOutput by creating + testOutput.extendsFrom(configurations["testImplementation"]) +} + +task("testJar") { + dependsOn("testClasses") + from(sourceSets.test.map { it.output }) + archiveClassifier.set("test") +} + +artifacts { + add("testOutput", "testJar") +} \ No newline at end of file From e3b6b945f02448566b934bbb9941b8b74fba22a8 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 15 May 2021 18:55:05 +0800 Subject: [PATCH 012/126] migrate global module --- ktorm-global/ktorm-global.gradle | 9 --------- ktorm-global/ktorm-global.gradle.kts | 13 +++++++++++++ 2 files changed, 13 insertions(+), 9 deletions(-) delete mode 100644 ktorm-global/ktorm-global.gradle create mode 100644 ktorm-global/ktorm-global.gradle.kts diff --git a/ktorm-global/ktorm-global.gradle b/ktorm-global/ktorm-global.gradle deleted file mode 100644 index a4aa7b55..00000000 --- a/ktorm-global/ktorm-global.gradle +++ /dev/null @@ -1,9 +0,0 @@ - -dependencies { - api project(":ktorm-core") - compileOnly "org.springframework:spring-jdbc:5.0.10.RELEASE" - compileOnly "org.springframework:spring-tx:5.0.10.RELEASE" - - testImplementation project(path: ":ktorm-core", configuration: "testOutput") - testImplementation "com.h2database:h2:1.4.197" -} \ No newline at end of file diff --git a/ktorm-global/ktorm-global.gradle.kts b/ktorm-global/ktorm-global.gradle.kts new file mode 100644 index 00000000..96300f73 --- /dev/null +++ b/ktorm-global/ktorm-global.gradle.kts @@ -0,0 +1,13 @@ + +plugins { + id("ktorm.module-conventions") +} + +dependencies { + api(project(":ktorm-core")) + compileOnly("org.springframework:spring-jdbc:5.0.10.RELEASE") + compileOnly("org.springframework:spring-tx:5.0.10.RELEASE") + + testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation("com.h2database:h2:1.4.197") +} \ No newline at end of file From df8e821268c4b7aa3600cfcfe8084636e87603eb Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 15 May 2021 21:37:52 +0800 Subject: [PATCH 013/126] migrate jackson module --- ktorm-core/ktorm-core.gradle.kts | 2 +- ktorm-global/ktorm-global.gradle.kts | 2 +- ktorm-jackson/ktorm-jackson.gradle | 24 ---------------------- ktorm-jackson/ktorm-jackson.gradle.kts | 28 ++++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 26 deletions(-) delete mode 100644 ktorm-jackson/ktorm-jackson.gradle create mode 100644 ktorm-jackson/ktorm-jackson.gradle.kts diff --git a/ktorm-core/ktorm-core.gradle.kts b/ktorm-core/ktorm-core.gradle.kts index 01322bc0..21f93915 100644 --- a/ktorm-core/ktorm-core.gradle.kts +++ b/ktorm-core/ktorm-core.gradle.kts @@ -27,4 +27,4 @@ task("testJar") { artifacts { add("testOutput", "testJar") -} \ No newline at end of file +} diff --git a/ktorm-global/ktorm-global.gradle.kts b/ktorm-global/ktorm-global.gradle.kts index 96300f73..484097dd 100644 --- a/ktorm-global/ktorm-global.gradle.kts +++ b/ktorm-global/ktorm-global.gradle.kts @@ -10,4 +10,4 @@ dependencies { testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) testImplementation("com.h2database:h2:1.4.197") -} \ No newline at end of file +} diff --git a/ktorm-jackson/ktorm-jackson.gradle b/ktorm-jackson/ktorm-jackson.gradle deleted file mode 100644 index 28f2a8b4..00000000 --- a/ktorm-jackson/ktorm-jackson.gradle +++ /dev/null @@ -1,24 +0,0 @@ - -def generatedSourceDir = "${project.buildDir.absolutePath}/generated/source/main/kotlin" - -task generatePackageVersion(type: Copy) { - from "src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl" - into "${generatedSourceDir}/org/ktorm/jackson" - - rename '(.+)\\.tmpl', '$1' - expand project.properties -} - -sourceSets { - main.kotlin.srcDirs += generatedSourceDir -} - -compileKotlin.dependsOn(generatePackageVersion) - -dependencies { - api project(":ktorm-core") - api "com.fasterxml.jackson.core:jackson-databind:2.12.3" - api "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3" - api "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3" - compileOnly "org.postgresql:postgresql:42.2.5" -} \ No newline at end of file diff --git a/ktorm-jackson/ktorm-jackson.gradle.kts b/ktorm-jackson/ktorm-jackson.gradle.kts new file mode 100644 index 00000000..3ced8b15 --- /dev/null +++ b/ktorm-jackson/ktorm-jackson.gradle.kts @@ -0,0 +1,28 @@ + +plugins { + id("ktorm.module-conventions") +} + +dependencies { + api(project(":ktorm-core")) + api("com.fasterxml.jackson.core:jackson-databind:2.12.3") + api("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3") + api("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3") + compileOnly("org.postgresql:postgresql:42.2.5") +} + +val generatedSourceDir = "${project.buildDir.absolutePath}/generated/source/main/kotlin" + +val generatePackageVersion by tasks.registering(Copy::class) { + from("src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl") + into("${generatedSourceDir}/org/ktorm/jackson") + + rename("(.+)\\.tmpl", "$1") + expand(project.properties) +} + +sourceSets.main { + java.srcDirs(generatedSourceDir) +} + +tasks.getByName("compileKotlin").dependsOn(generatePackageVersion) From 1558b71874019397a3c75756f6730c86500d20c3 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 15 May 2021 22:29:49 +0800 Subject: [PATCH 014/126] migrate dialect modules --- ktorm-support-mysql/ktorm-support-mysql.gradle | 9 --------- .../ktorm-support-mysql.gradle.kts | 13 +++++++++++++ .../ktorm-support-oracle.gradle | 8 -------- .../ktorm-support-oracle.gradle.kts | 12 ++++++++++++ .../ktorm-support-postgresql.gradle | 13 ------------- .../ktorm-support-postgresql.gradle.kts | 17 +++++++++++++++++ .../ktorm-support-sqlite.gradle | 7 ------- .../ktorm-support-sqlite.gradle.kts | 11 +++++++++++ .../ktorm-support-sqlserver.gradle | 8 -------- .../ktorm-support-sqlserver.gradle.kts | 12 ++++++++++++ 10 files changed, 65 insertions(+), 45 deletions(-) delete mode 100644 ktorm-support-mysql/ktorm-support-mysql.gradle create mode 100644 ktorm-support-mysql/ktorm-support-mysql.gradle.kts delete mode 100644 ktorm-support-oracle/ktorm-support-oracle.gradle create mode 100644 ktorm-support-oracle/ktorm-support-oracle.gradle.kts delete mode 100644 ktorm-support-postgresql/ktorm-support-postgresql.gradle create mode 100644 ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts delete mode 100644 ktorm-support-sqlite/ktorm-support-sqlite.gradle create mode 100644 ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts delete mode 100644 ktorm-support-sqlserver/ktorm-support-sqlserver.gradle create mode 100644 ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts diff --git a/ktorm-support-mysql/ktorm-support-mysql.gradle b/ktorm-support-mysql/ktorm-support-mysql.gradle deleted file mode 100644 index 6c5ad2b4..00000000 --- a/ktorm-support-mysql/ktorm-support-mysql.gradle +++ /dev/null @@ -1,9 +0,0 @@ - -dependencies { - api project(":ktorm-core") - - testImplementation project(path: ":ktorm-core", configuration: "testOutput") - testImplementation project(":ktorm-jackson") - testImplementation "mysql:mysql-connector-java:8.0.23" - testImplementation "org.testcontainers:mysql:1.15.1" -} \ No newline at end of file diff --git a/ktorm-support-mysql/ktorm-support-mysql.gradle.kts b/ktorm-support-mysql/ktorm-support-mysql.gradle.kts new file mode 100644 index 00000000..ef9ef3e1 --- /dev/null +++ b/ktorm-support-mysql/ktorm-support-mysql.gradle.kts @@ -0,0 +1,13 @@ + +plugins { + id("ktorm.module-conventions") +} + +dependencies { + api(project(":ktorm-core")) + + testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation(project(":ktorm-jackson")) + testImplementation("mysql:mysql-connector-java:8.0.23") + testImplementation("org.testcontainers:mysql:1.15.1") +} diff --git a/ktorm-support-oracle/ktorm-support-oracle.gradle b/ktorm-support-oracle/ktorm-support-oracle.gradle deleted file mode 100644 index 0c4bdc8c..00000000 --- a/ktorm-support-oracle/ktorm-support-oracle.gradle +++ /dev/null @@ -1,8 +0,0 @@ - -dependencies { - api project(":ktorm-core") - - testImplementation project(path: ":ktorm-core", configuration: "testOutput") - testImplementation fileTree(dir: "lib", includes: ["*.jar"]) - testImplementation "org.testcontainers:oracle-xe:1.15.1" -} \ No newline at end of file diff --git a/ktorm-support-oracle/ktorm-support-oracle.gradle.kts b/ktorm-support-oracle/ktorm-support-oracle.gradle.kts new file mode 100644 index 00000000..f95eed93 --- /dev/null +++ b/ktorm-support-oracle/ktorm-support-oracle.gradle.kts @@ -0,0 +1,12 @@ + +plugins { + id("ktorm.module-conventions") +} + +dependencies { + api(project(":ktorm-core")) + + testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation(fileTree("lib") { include("*.jar") }) + testImplementation("org.testcontainers:oracle-xe:1.15.1") +} diff --git a/ktorm-support-postgresql/ktorm-support-postgresql.gradle b/ktorm-support-postgresql/ktorm-support-postgresql.gradle deleted file mode 100644 index 15223d5a..00000000 --- a/ktorm-support-postgresql/ktorm-support-postgresql.gradle +++ /dev/null @@ -1,13 +0,0 @@ - -dependencies { - api project(":ktorm-core") - - testImplementation project(path: ":ktorm-core", configuration: "testOutput") - testImplementation project(":ktorm-jackson") - testImplementation "org.postgresql:postgresql:42.2.5" - testImplementation "org.testcontainers:postgresql:1.15.1" - testImplementation "com.zaxxer:HikariCP:4.0.3" - testImplementation "com.mchange:c3p0:0.9.5.5" - testImplementation "org.apache.commons:commons-dbcp2:2.8.0" - testImplementation "com.alibaba:druid:1.2.6" -} \ No newline at end of file diff --git a/ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts b/ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts new file mode 100644 index 00000000..e05422c2 --- /dev/null +++ b/ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts @@ -0,0 +1,17 @@ + +plugins { + id("ktorm.module-conventions") +} + +dependencies { + api(project(":ktorm-core")) + + testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation(project(":ktorm-jackson")) + testImplementation("org.postgresql:postgresql:42.2.5") + testImplementation("org.testcontainers:postgresql:1.15.1") + testImplementation("com.zaxxer:HikariCP:4.0.3") + testImplementation("com.mchange:c3p0:0.9.5.5") + testImplementation("org.apache.commons:commons-dbcp2:2.8.0") + testImplementation("com.alibaba:druid:1.2.6") +} diff --git a/ktorm-support-sqlite/ktorm-support-sqlite.gradle b/ktorm-support-sqlite/ktorm-support-sqlite.gradle deleted file mode 100644 index 9c274ae2..00000000 --- a/ktorm-support-sqlite/ktorm-support-sqlite.gradle +++ /dev/null @@ -1,7 +0,0 @@ - -dependencies { - api project(":ktorm-core") - - testImplementation project(path: ":ktorm-core", configuration: "testOutput") - testImplementation "org.xerial:sqlite-jdbc:3.34.0" -} diff --git a/ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts b/ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts new file mode 100644 index 00000000..e12fc384 --- /dev/null +++ b/ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts @@ -0,0 +1,11 @@ + +plugins { + id("ktorm.module-conventions") +} + +dependencies { + api(project(":ktorm-core")) + + testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation("org.xerial:sqlite-jdbc:3.34.0") +} diff --git a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle deleted file mode 100644 index bcdb531e..00000000 --- a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle +++ /dev/null @@ -1,8 +0,0 @@ - -dependencies { - api project(":ktorm-core") - api "com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8" - - testImplementation project(path: ":ktorm-core", configuration: "testOutput") - testImplementation "org.testcontainers:mssqlserver:1.15.1" -} diff --git a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts new file mode 100644 index 00000000..64653bcc --- /dev/null +++ b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts @@ -0,0 +1,12 @@ + +plugins { + id("ktorm.module-conventions") +} + +dependencies { + api(project(":ktorm-core")) + api("com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8") + + testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation("org.testcontainers:mssqlserver:1.15.1") +} From 30128757cd2d4c33713804c028122d798422e20f Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 16 May 2021 00:17:07 +0800 Subject: [PATCH 015/126] fix bugs --- .../kotlin/ktorm.module-conventions.gradle.kts | 15 +++++++++------ .../kotlin/ktorm.tuples-generation.gradle.kts | 3 ++- ktorm-core/ktorm-core.gradle.kts | 9 ++++----- ktorm-jackson/ktorm-jackson.gradle.kts | 3 ++- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index 02c63e95..4975b5de 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -7,6 +7,9 @@ plugins { id("ktorm.source-header-check") } +group = rootProject.group +version = rootProject.version + repositories { mavenCentral() } @@ -14,12 +17,12 @@ repositories { dependencies { api(kotlin("stdlib")) api(kotlin("reflect")) - testImplementation(kotlin("test")) + testImplementation(kotlin("test-junit")) detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.17.0-RC2") } tasks { - withType { + named("compileKotlin") { kotlinOptions { jvmTarget = "1.6" allWarningsAsErrors = true @@ -30,12 +33,12 @@ tasks { } } - register("generateSources") { + register("jarSources") { archiveClassifier.set("sources") from(sourceSets.main.map { it.allSource }) } - register("generateJavadoc") { + register("jarJavadoc") { archiveClassifier.set("javadoc") } } @@ -50,8 +53,8 @@ publishing { publications { create("dist") { from(components["java"]) - artifact("generateSources") - artifact("generateJavadoc") + artifact(tasks["jarSources"]) + artifact(tasks["jarJavadoc"]) groupId = project.group.toString() artifactId = project.name diff --git a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts index c8b9c8bf..04d250ae 100644 --- a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts @@ -447,4 +447,5 @@ sourceSets.main { java.srcDirs(generatedSourceDir) } -tasks.getByName("compileKotlin").dependsOn(generateTuples) +tasks["compileKotlin"].dependsOn(generateTuples) +tasks["jarSources"].dependsOn(generateTuples) diff --git a/ktorm-core/ktorm-core.gradle.kts b/ktorm-core/ktorm-core.gradle.kts index 21f93915..8b2df090 100644 --- a/ktorm-core/ktorm-core.gradle.kts +++ b/ktorm-core/ktorm-core.gradle.kts @@ -14,17 +14,16 @@ dependencies { testImplementation("com.h2database:h2:1.4.197") } -configurations { - val testOutput by creating - testOutput.extendsFrom(configurations["testImplementation"]) +val testOutput by configurations.creating { + extendsFrom(configurations["testImplementation"]) } -task("testJar") { +val testJar by tasks.registering(Jar::class) { dependsOn("testClasses") from(sourceSets.test.map { it.output }) archiveClassifier.set("test") } artifacts { - add("testOutput", "testJar") + add(testOutput.name, testJar) } diff --git a/ktorm-jackson/ktorm-jackson.gradle.kts b/ktorm-jackson/ktorm-jackson.gradle.kts index 3ced8b15..a39adcaa 100644 --- a/ktorm-jackson/ktorm-jackson.gradle.kts +++ b/ktorm-jackson/ktorm-jackson.gradle.kts @@ -25,4 +25,5 @@ sourceSets.main { java.srcDirs(generatedSourceDir) } -tasks.getByName("compileKotlin").dependsOn(generatePackageVersion) +tasks["compileKotlin"].dependsOn(generatePackageVersion) +tasks["jarSources"].dependsOn(generatePackageVersion) From 5ce69230080f3c3506eb4a93a9ed290b6dd5f591 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 16 May 2021 15:49:59 +0800 Subject: [PATCH 016/126] fix tuples generation --- .../kotlin/ktorm.tuples-generation.gradle.kts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts index 04d250ae..c18706de 100644 --- a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts @@ -12,6 +12,7 @@ fun generateTuple(writer: java.io.Writer, tupleNumber: Int) { val toStringTemplate = (1..tupleNumber).joinToString(separator = ", ") { "\$element$it" } writer.write(""" + /** * Represents a tuple of $tupleNumber values. * @@ -25,11 +26,12 @@ fun generateTuple(writer: java.io.Writer, tupleNumber: Int) { override fun toString(): String { return "($toStringTemplate)" } - + private companion object { private const val serialVersionUID = 1L } } + """.trimIndent()) } @@ -39,6 +41,7 @@ fun generateTupleOf(writer: java.io.Writer, tupleNumber: Int) { val elements = (1..tupleNumber).joinToString(separator = ", ") { "element$it" } writer.write(""" + /** * Create a tuple of $tupleNumber values. * @@ -49,6 +52,7 @@ fun generateTupleOf(writer: java.io.Writer, tupleNumber: Int) { ): Tuple$tupleNumber<$typeParams> { return Tuple$tupleNumber($elements) } + """.trimIndent()) } @@ -57,6 +61,7 @@ fun generateToList(writer: java.io.Writer, tupleNumber: Int) { val elements = (1..tupleNumber).joinToString(separator = ", ") { "element$it" } writer.write(""" + /** * Convert this tuple into a list. * @@ -65,6 +70,7 @@ fun generateToList(writer: java.io.Writer, tupleNumber: Int) { public fun Tuple$tupleNumber<$typeParams>.toList(): List { return listOf($elements) } + """.trimIndent()) } @@ -76,6 +82,7 @@ fun generateMapColumns(writer: java.io.Writer, tupleNumber: Int) { val resultExtractors = (1..tupleNumber).joinToString(separator = ", ") { "c${it}.sqlType.getResult(row, $it)" } writer.write(""" + /** * Customize the selected columns of the internal query by the given [columnSelector] function, and return a [List] * containing the query results. @@ -190,6 +197,7 @@ fun generateMapColumns(writer: java.io.Writer, tupleNumber: Int) { return Query(database, expr).mapTo(destination) { row -> tupleOf($resultExtractors) } } + """.trimIndent()) } @@ -201,6 +209,7 @@ fun generateAggregateColumns(writer: java.io.Writer, tupleNumber: Int) { val resultExtractors = (1..tupleNumber).joinToString(separator = ", ") { "c${it}.sqlType.getResult(rowSet, $it)" } writer.write(""" + /** * Perform a tuple of aggregations given by [aggregationSelector] for all elements in the sequence, * and return the aggregate results. @@ -255,6 +264,7 @@ fun generateAggregateColumns(writer: java.io.Writer, tupleNumber: Int) { throw IllegalStateException("Expected 1 row but ${'$'}{rowSet.size()} returned from sql: \n\n${'$'}sql") } } + """.trimIndent()) } @@ -266,6 +276,7 @@ fun generateGroupingAggregateColumns(writer: java.io.Writer, tupleNumber: Int) { val resultExtractors = (1..tupleNumber).joinToString(separator = ", ") { "c${it}.sqlType.getResult(row, ${it + 1})" } writer.write(""" + /** * Group elements from the source sequence by key and perform the given aggregations for elements in each group, * then store the results in a new [Map]. @@ -368,6 +379,7 @@ fun generateGroupingAggregateColumns(writer: java.io.Writer, tupleNumber: Int) { return destination } + """.trimIndent()) } @@ -393,7 +405,7 @@ val generateTuples by tasks.registering { * See the License for the specific language governing permissions and * limitations under the License. */ - + // This file is auto-generated by generate-tuples.gradle, DO NOT EDIT! package org.ktorm.entity @@ -414,6 +426,7 @@ val generateTuples by tasks.registering { * Set a typealias `Tuple3` for `Triple`. */ public typealias Tuple3 = Triple + """.trimIndent()) for (num in (4..maxTupleNumber)) { From 94c8818088c37b83efcb819f303ad52a1968dcae Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 16 May 2021 17:31:29 +0800 Subject: [PATCH 017/126] fix code style --- .../src/main/kotlin/ktorm.module-conventions.gradle.kts | 4 ++-- .../src/main/kotlin/ktorm.source-header-check.gradle.kts | 7 ++++--- .../src/main/kotlin/ktorm.tuples-generation.gradle.kts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index 4975b5de..dde88d1c 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -186,9 +186,9 @@ signing { val secretKey = System.getenv("GPG_SECRET_KEY") val password = System.getenv("GPG_PASSWORD") - setRequired({ + setRequired { !project.version.toString().endsWith("SNAPSHOT") - }) + } useInMemoryPgpKeys(keyId, secretKey, password) sign(publishing.publications["dist"]) diff --git a/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts b/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts index 8806a00f..87e167c1 100644 --- a/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts @@ -3,7 +3,8 @@ plugins { id("kotlin") } -val licenseHeaderText = """/* +val licenseHeaderText = """ +/* * Copyright 2018-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +19,7 @@ val licenseHeaderText = """/* * See the License for the specific language governing permissions and * limitations under the License. */ -""" +""".trimIndent() val checkSourceHeader by tasks.registering { doLast { @@ -37,4 +38,4 @@ val checkSourceHeader by tasks.registering { } } -tasks.getByName("check").dependsOn(checkSourceHeader) +tasks["check"].dependsOn(checkSourceHeader) diff --git a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts index c18706de..7f176a12 100644 --- a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts @@ -406,7 +406,7 @@ val generateTuples by tasks.registering { * limitations under the License. */ - // This file is auto-generated by generate-tuples.gradle, DO NOT EDIT! + // This file is auto-generated by ktorm.tuples-generation.gradle.kts, DO NOT EDIT! package org.ktorm.entity From 6ba7b363437c719fd1c2f96b68c95d57b4bc2955 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 16 May 2021 22:52:49 +0800 Subject: [PATCH 018/126] fix code style --- build.gradle.kts | 10 +++++----- .../src/main/kotlin/ktorm.tuples-generation.gradle.kts | 2 +- ktorm-global/ktorm-global.gradle.kts | 3 +-- ktorm-jackson/ktorm-jackson.gradle.kts | 1 - ktorm-support-mysql/ktorm-support-mysql.gradle.kts | 3 +-- ktorm-support-oracle/ktorm-support-oracle.gradle.kts | 5 ++--- .../ktorm-support-postgresql.gradle.kts | 3 +-- ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts | 3 +-- .../ktorm-support-sqlserver.gradle.kts | 3 +-- 9 files changed, 13 insertions(+), 20 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e6ec56af..3733e0cc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,11 +4,11 @@ version = "3.5.0-SNAPSHOT" task("printClasspath") { doLast { - val jars = subprojects.flatMapTo(HashSet()) { it.configurations["compileClasspath"].files } - jars.removeIf { it.name.contains("ktorm") } - - println("Project classpath: ") - jars.forEach { println(it.name) } + val jars = subprojects + .map { it.configurations["compileClasspath"] } + .flatMap { it.files } + .filterNotTo(HashSet()) { it.name.contains("ktorm") } + .onEach { println(it.name) } val file = file("build/ktorm.classpath") file.parentFile.mkdirs() diff --git a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts index 7f176a12..e3d571fb 100644 --- a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts @@ -406,7 +406,7 @@ val generateTuples by tasks.registering { * limitations under the License. */ - // This file is auto-generated by ktorm.tuples-generation.gradle.kts, DO NOT EDIT! + // Auto-generated by ktorm.tuples-generation.gradle.kts, DO NOT EDIT! package org.ktorm.entity diff --git a/ktorm-global/ktorm-global.gradle.kts b/ktorm-global/ktorm-global.gradle.kts index 484097dd..9a8afc18 100644 --- a/ktorm-global/ktorm-global.gradle.kts +++ b/ktorm-global/ktorm-global.gradle.kts @@ -7,7 +7,6 @@ dependencies { api(project(":ktorm-core")) compileOnly("org.springframework:spring-jdbc:5.0.10.RELEASE") compileOnly("org.springframework:spring-tx:5.0.10.RELEASE") - - testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation(project(":ktorm-core", configuration = "testOutput")) testImplementation("com.h2database:h2:1.4.197") } diff --git a/ktorm-jackson/ktorm-jackson.gradle.kts b/ktorm-jackson/ktorm-jackson.gradle.kts index a39adcaa..b82ac67b 100644 --- a/ktorm-jackson/ktorm-jackson.gradle.kts +++ b/ktorm-jackson/ktorm-jackson.gradle.kts @@ -16,7 +16,6 @@ val generatedSourceDir = "${project.buildDir.absolutePath}/generated/source/main val generatePackageVersion by tasks.registering(Copy::class) { from("src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl") into("${generatedSourceDir}/org/ktorm/jackson") - rename("(.+)\\.tmpl", "$1") expand(project.properties) } diff --git a/ktorm-support-mysql/ktorm-support-mysql.gradle.kts b/ktorm-support-mysql/ktorm-support-mysql.gradle.kts index ef9ef3e1..8ff16824 100644 --- a/ktorm-support-mysql/ktorm-support-mysql.gradle.kts +++ b/ktorm-support-mysql/ktorm-support-mysql.gradle.kts @@ -5,8 +5,7 @@ plugins { dependencies { api(project(":ktorm-core")) - - testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation(project(":ktorm-core", configuration = "testOutput")) testImplementation(project(":ktorm-jackson")) testImplementation("mysql:mysql-connector-java:8.0.23") testImplementation("org.testcontainers:mysql:1.15.1") diff --git a/ktorm-support-oracle/ktorm-support-oracle.gradle.kts b/ktorm-support-oracle/ktorm-support-oracle.gradle.kts index f95eed93..beb0c0b6 100644 --- a/ktorm-support-oracle/ktorm-support-oracle.gradle.kts +++ b/ktorm-support-oracle/ktorm-support-oracle.gradle.kts @@ -5,8 +5,7 @@ plugins { dependencies { api(project(":ktorm-core")) - - testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) - testImplementation(fileTree("lib") { include("*.jar") }) + testImplementation(project(":ktorm-core", configuration = "testOutput")) + testImplementation(files("lib/ojdbc6-11.2.0.3.jar")) testImplementation("org.testcontainers:oracle-xe:1.15.1") } diff --git a/ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts b/ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts index e05422c2..de769438 100644 --- a/ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts +++ b/ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts @@ -5,8 +5,7 @@ plugins { dependencies { api(project(":ktorm-core")) - - testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation(project(":ktorm-core", configuration = "testOutput")) testImplementation(project(":ktorm-jackson")) testImplementation("org.postgresql:postgresql:42.2.5") testImplementation("org.testcontainers:postgresql:1.15.1") diff --git a/ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts b/ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts index e12fc384..4c6c7732 100644 --- a/ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts +++ b/ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts @@ -5,7 +5,6 @@ plugins { dependencies { api(project(":ktorm-core")) - - testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation(project(":ktorm-core", configuration = "testOutput")) testImplementation("org.xerial:sqlite-jdbc:3.34.0") } diff --git a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts index 64653bcc..6eab5d79 100644 --- a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts +++ b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts @@ -5,8 +5,7 @@ plugins { dependencies { api(project(":ktorm-core")) - api("com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8") - + compileOnly("com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8") testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) testImplementation("org.testcontainers:mssqlserver:1.15.1") } From c484de55e1f13f074c0cc0c65ef9aac09d0b2887 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 16 May 2021 22:55:30 +0800 Subject: [PATCH 019/126] fix code style --- ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts index 6eab5d79..01941625 100644 --- a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts +++ b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts @@ -6,6 +6,6 @@ plugins { dependencies { api(project(":ktorm-core")) compileOnly("com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8") - testImplementation(project(path = ":ktorm-core", configuration = "testOutput")) + testImplementation(project(":ktorm-core", configuration = "testOutput")) testImplementation("org.testcontainers:mssqlserver:1.15.1") } From 2de50b2b8ea12cd02fcbfd2025451ccac8147487 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 17 May 2021 00:01:46 +0800 Subject: [PATCH 020/126] refactor publish script --- .../kotlin/ktorm.maven-publish.gradle.kts | 160 ++++++++++++++++ .../ktorm.module-conventions.gradle.kts | 181 ++---------------- .../ktorm-support-sqlserver.gradle.kts | 2 +- 3 files changed, 173 insertions(+), 170 deletions(-) create mode 100644 buildSrc/src/main/kotlin/ktorm.maven-publish.gradle.kts diff --git a/buildSrc/src/main/kotlin/ktorm.maven-publish.gradle.kts b/buildSrc/src/main/kotlin/ktorm.maven-publish.gradle.kts new file mode 100644 index 00000000..1e003a57 --- /dev/null +++ b/buildSrc/src/main/kotlin/ktorm.maven-publish.gradle.kts @@ -0,0 +1,160 @@ + +plugins { + id("kotlin") + id("signing") + id("maven-publish") +} + +val jarSources by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + from(sourceSets.main.map { it.allSource }) +} + +val jarJavadoc by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") +} + +publishing { + publications { + create("dist") { + from(components["java"]) + artifact(jarSources) + artifact(jarJavadoc) + + groupId = project.group.toString() + artifactId = project.name + version = project.version.toString() + + pom { + name.set("${project.group}:${project.name}") + description.set("A lightweight ORM Framework for Kotlin with strong typed SQL DSL and sequence APIs.") + url.set("https://www.ktorm.org") + licenses { + license { + name.set("The Apache Software License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + scm { + url.set("https://github.com/kotlin-orm/ktorm") + connection.set("scm:git:https://github.com/kotlin-orm/ktorm.git") + developerConnection.set("scm:git:ssh://git@github.com/kotlin-orm/ktorm.git") + } + developers { + developer { + id.set("vincentlauvlwj") + name.set("vince") + email.set("me@liuwj.me") + } + developer { + id.set("waluo") + name.set("waluo") + email.set("1b79349b@gmail.com") + } + developer { + id.set("clydebarrow") + name.set("Clyde") + email.set("clyde@control-j.com") + } + developer { + id.set("Ray-Eldath") + name.set("Ray Eldath") + email.set("ray.eldath@outlook.com") + } + developer { + id.set("hangingman") + name.set("hiroyuki.nagata") + email.set("idiotpanzer@gmail.com") + } + developer { + id.set("onXoot") + name.set("beetlerx") + email.set("beetlerx@gmail.com") + } + developer { + id.set("arustleund") + name.set("Andrew Rustleund") + email.set("andrew@rustleund.com") + } + developer { + id.set("afezeria") + name.set("afezeria") + email.set("zodal@outlook.com") + } + developer { + id.set("scorsi") + name.set("Sylvain Corsini") + email.set("sylvain.corsini@protonmail.com") + } + developer { + id.set("lyndsysimon") + name.set("Lyndsy Simon") + email.set("lyndsy@lyndsysimon.com") + } + developer { + id.set("antonydenyer") + name.set("Antony Denyer") + email.set("git@antonydenyer.co.uk") + } + developer { + id.set("mik629") + name.set("Mikhail Erkhov") + email.set("mikhail.erkhov@gmail.com") + } + developer { + id.set("sinzed") + name.set("Saeed Zahedi") + email.set("saeedzhd@gmail.com") + } + developer { + id.set("smn-dv") + name.set("Simon Schoof") + email.set("simon.schoof@hey.com") + } + developer { + id.set("pedrod") + name.set("Pedro Domingues") + email.set("pedro.domingues.pt@gmail.com") + } + developer { + id.set("efenderbosch") + name.set("Eric Fenderbosch") + email.set("eric@fender.net") + } + } + } + } + + repositories { + maven { + name = "central" + url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2") + credentials { + username = System.getenv("OSSRH_USER") + password = System.getenv("OSSRH_PASSWORD") + } + } + maven { + name = "snapshot" + url = uri("https://oss.sonatype.org/content/repositories/snapshots") + credentials { + username = System.getenv("OSSRH_USER") + password = System.getenv("OSSRH_PASSWORD") + } + } + } + } +} + +signing { + val keyId = System.getenv("GPG_KEY_ID") + val secretKey = System.getenv("GPG_SECRET_KEY") + val password = System.getenv("GPG_PASSWORD") + + setRequired { + !project.version.toString().endsWith("SNAPSHOT") + } + + useInMemoryPgpKeys(keyId, secretKey, password) + sign(publishing.publications["dist"]) +} diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index dde88d1c..8187905e 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -1,15 +1,14 @@ +group = rootProject.group +version = rootProject.version + plugins { id("kotlin") - id("signing") - id("maven-publish") id("io.gitlab.arturbosch.detekt") id("ktorm.source-header-check") + id("ktorm.maven-publish") } -group = rootProject.group -version = rootProject.version - repositories { mavenCentral() } @@ -21,175 +20,19 @@ dependencies { detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.17.0-RC2") } -tasks { - named("compileKotlin") { - kotlinOptions { - jvmTarget = "1.6" - allWarningsAsErrors = true - freeCompilerArgs = listOf( - "-Xexplicit-api=strict", - "-Xopt-in=kotlin.RequiresOptIn" - ) - } - } - - register("jarSources") { - archiveClassifier.set("sources") - from(sourceSets.main.map { it.allSource }) - } - - register("jarJavadoc") { - archiveClassifier.set("javadoc") - } -} - detekt { toolVersion = "1.17.0-RC2" input = files("src/main/kotlin") config = files("${project.rootDir}/detekt.yml") } -publishing { - publications { - create("dist") { - from(components["java"]) - artifact(tasks["jarSources"]) - artifact(tasks["jarJavadoc"]) - - groupId = project.group.toString() - artifactId = project.name - version = project.version.toString() - - pom { - name.set("${project.group}:${project.name}") - description.set("A lightweight ORM Framework for Kotlin with strong typed SQL DSL and sequence APIs.") - url.set("https://www.ktorm.org") - licenses { - license { - name.set("The Apache Software License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - scm { - url.set("https://github.com/kotlin-orm/ktorm") - connection.set("scm:git:https://github.com/kotlin-orm/ktorm.git") - developerConnection.set("scm:git:ssh://git@github.com/kotlin-orm/ktorm.git") - } - developers { - developer { - id.set("vincentlauvlwj") - name.set("vince") - email.set("me@liuwj.me") - } - developer { - id.set("waluo") - name.set("waluo") - email.set("1b79349b@gmail.com") - } - developer { - id.set("clydebarrow") - name.set("Clyde") - email.set("clyde@control-j.com") - } - developer { - id.set("Ray-Eldath") - name.set("Ray Eldath") - email.set("ray.eldath@outlook.com") - } - developer { - id.set("hangingman") - name.set("hiroyuki.nagata") - email.set("idiotpanzer@gmail.com") - } - developer { - id.set("onXoot") - name.set("beetlerx") - email.set("beetlerx@gmail.com") - } - developer { - id.set("arustleund") - name.set("Andrew Rustleund") - email.set("andrew@rustleund.com") - } - developer { - id.set("afezeria") - name.set("afezeria") - email.set("zodal@outlook.com") - } - developer { - id.set("scorsi") - name.set("Sylvain Corsini") - email.set("sylvain.corsini@protonmail.com") - } - developer { - id.set("lyndsysimon") - name.set("Lyndsy Simon") - email.set("lyndsy@lyndsysimon.com") - } - developer { - id.set("antonydenyer") - name.set("Antony Denyer") - email.set("git@antonydenyer.co.uk") - } - developer { - id.set("mik629") - name.set("Mikhail Erkhov") - email.set("mikhail.erkhov@gmail.com") - } - developer { - id.set("sinzed") - name.set("Saeed Zahedi") - email.set("saeedzhd@gmail.com") - } - developer { - id.set("smn-dv") - name.set("Simon Schoof") - email.set("simon.schoof@hey.com") - } - developer { - id.set("pedrod") - name.set("Pedro Domingues") - email.set("pedro.domingues.pt@gmail.com") - } - developer { - id.set("efenderbosch") - name.set("Eric Fenderbosch") - email.set("eric@fender.net") - } - } - } - } - - repositories { - maven { - name = "central" - url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2") - credentials { - username = System.getenv("OSSRH_USER") - password = System.getenv("OSSRH_PASSWORD") - } - } - maven { - name = "snapshot" - url = uri("https://oss.sonatype.org/content/repositories/snapshots") - credentials { - username = System.getenv("OSSRH_USER") - password = System.getenv("OSSRH_PASSWORD") - } - } - } - } -} - -signing { - val keyId = System.getenv("GPG_KEY_ID") - val secretKey = System.getenv("GPG_SECRET_KEY") - val password = System.getenv("GPG_PASSWORD") - - setRequired { - !project.version.toString().endsWith("SNAPSHOT") +tasks.named("compileKotlin") { + kotlinOptions { + jvmTarget = "1.6" + allWarningsAsErrors = true + freeCompilerArgs = listOf( + "-Xexplicit-api=strict", + "-Xopt-in=kotlin.RequiresOptIn" + ) } - - useInMemoryPgpKeys(keyId, secretKey, password) - sign(publishing.publications["dist"]) } diff --git a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts index 01941625..684640f0 100644 --- a/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts +++ b/ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts @@ -5,7 +5,7 @@ plugins { dependencies { api(project(":ktorm-core")) - compileOnly("com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8") + api("com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8") testImplementation(project(":ktorm-core", configuration = "testOutput")) testImplementation("org.testcontainers:mssqlserver:1.15.1") } From 09b77fff9bfc11b7551c2d8509d64a4f6843a157 Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 19 May 2021 00:24:33 +0800 Subject: [PATCH 021/126] update contributing guide --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 263ec503..50cf4f32 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,4 +9,4 @@ Pull requests are always welcome and can be a quick way to get your fix or impro - By contributing to Ktorm, you agree that your contributions will be licensed under [Apache License 2.0](LICENSE). - Coding Conventions are very important. Refer to the [Kotlin Style Guide](https://kotlinlang.org/docs/reference/coding-conventions.html) for the recommended coding standards of Ktorm. - If you've added code that should be tested, add tests and ensure they all pass. If you've changed APIs, update the documentation. -- If it's your first time contributing to Ktorm, please also update the `build.gradle` file, add your GitHub ID to the developers info, which will let more people know your contributions. +- If it's your first time contributing to Ktorm, please also update [this file](buildSrc/src/main/kotlin/ktorm.maven-publish.gradle.kts), add your GitHub ID to the developer's info, which will let more people know your contributions. From 741bdd551c514448d59d8fbf1bd5278d50ae8d67 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 23 May 2021 00:21:36 +0800 Subject: [PATCH 022/126] upgrade to kotlin 1.5 --- buildSrc/build.gradle.kts | 10 +++++++++- .../kotlin/ktorm.module-conventions.gradle.kts | 2 +- .../main/kotlin/org/ktorm/database/Database.kt | 2 +- .../org/ktorm/expression/SqlFormatter.kt | 18 +++++++++--------- .../src/test/kotlin/org/ktorm/BaseTest.kt | 4 ++-- .../org/ktorm/entity/EntitySequenceTest.kt | 4 ++-- .../ktorm/global/GlobalEntitySequenceTest.kt | 4 ++-- .../org/ktorm/support/oracle/OracleTest.kt | 2 +- 8 files changed, 27 insertions(+), 19 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 9eccb279..037e8700 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -9,6 +9,14 @@ repositories { } dependencies { - api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32") + api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.0") { + // TODO: Remove the exclusions when this issue fixed: https://youtrack.jetbrains.com/issue/KT-41142 + exclude("org.jetbrains.kotlin", "kotlin-stdlib") + exclude("org.jetbrains.kotlin", "kotlin-stdlib-common") + exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7") + exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8") + exclude("org.jetbrains.kotlin", "kotlin-reflect") + } + api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.17.0-RC2") } diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index 8187905e..a296b3a0 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -28,7 +28,7 @@ detekt { tasks.named("compileKotlin") { kotlinOptions { - jvmTarget = "1.6" + jvmTarget = "1.8" allWarningsAsErrors = true freeCompilerArgs = listOf( "-Xexplicit-api=strict", diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt index 0aad560a..711658e7 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt @@ -254,7 +254,7 @@ public class Database( name = url.substringAfterLast('/').substringBefore('?') productName = metadata.runCatching { databaseProductName }.orEmpty() productVersion = metadata.runCatching { databaseProductVersion }.orEmpty() - keywords = ANSI_SQL_2003_KEYWORDS + metadata.runCatching { sqlKeywords }.orEmpty().toUpperCase().split(',') + keywords = ANSI_SQL_2003_KEYWORDS + metadata.runCatching { sqlKeywords }.orEmpty().uppercase().split(',') identifierQuoteString = metadata.runCatching { identifierQuoteString }.orEmpty().trim() extraNameCharacters = metadata.runCatching { extraNameCharacters }.orEmpty() supportsMixedCaseIdentifiers = metadata.runCatching { supportsMixedCaseIdentifiers() }.orFalse() diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt index 67cd6ce2..d614c903 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt @@ -76,16 +76,16 @@ public abstract class SqlFormatter( protected fun writeKeyword(keyword: String) { when (database.generateSqlInUpperCase) { true -> { - _builder.append(keyword.toUpperCase()) + _builder.append(keyword.uppercase()) } false -> { - _builder.append(keyword.toLowerCase()) + _builder.append(keyword.lowercase()) } null -> { if (database.supportsMixedCaseIdentifiers || !database.storesLowerCaseIdentifiers) { - _builder.append(keyword.toUpperCase()) + _builder.append(keyword.uppercase()) } else { - _builder.append(keyword.toLowerCase()) + _builder.append(keyword.lowercase()) } } } @@ -100,7 +100,7 @@ public abstract class SqlFormatter( if (!identifier.isIdentifier) { return true } - if (identifier.toUpperCase() in database.keywords) { + if (identifier.uppercase() in database.keywords) { return true } if (identifier.isMixedCase @@ -118,10 +118,10 @@ public abstract class SqlFormatter( return "${database.identifierQuoteString}${this}${database.identifierQuoteString}" } else { if (database.storesUpperCaseQuotedIdentifiers) { - return "${database.identifierQuoteString}${this.toUpperCase()}${database.identifierQuoteString}" + return "${database.identifierQuoteString}${this.uppercase()}${database.identifierQuoteString}" } if (database.storesLowerCaseQuotedIdentifiers) { - return "${database.identifierQuoteString}${this.toLowerCase()}${database.identifierQuoteString}" + return "${database.identifierQuoteString}${this.lowercase()}${database.identifierQuoteString}" } if (database.storesMixedCaseQuotedIdentifiers) { return "${database.identifierQuoteString}${this}${database.identifierQuoteString}" @@ -134,10 +134,10 @@ public abstract class SqlFormatter( return this } else { if (database.storesUpperCaseIdentifiers) { - return this.toUpperCase() + return this.uppercase() } if (database.storesLowerCaseIdentifiers) { - return this.toLowerCase() + return this.lowercase() } if (database.storesMixedCaseIdentifiers) { return this diff --git a/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt index 232b2840..85ec5f19 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt @@ -72,8 +72,8 @@ open class BaseTest { var salary: Long var department: Department - val upperName get() = name.toUpperCase() - fun upperName() = name.toUpperCase() + val upperName get() = name.uppercase() + fun upperName() = name.uppercase() } interface Customer : Entity { diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt index e553b945..846d8e70 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt @@ -126,8 +126,8 @@ class EntitySequenceTest : BaseTest() { val employees = database.employees.groupBy { it.department.id } println(employees) assert(employees.size == 2) - assert(employees[1]!!.sumBy { it.salary.toInt() } == 150) - assert(employees[2]!!.sumBy { it.salary.toInt() } == 300) + assert(employees[1]!!.sumOf { it.salary.toInt() } == 150) + assert(employees[2]!!.sumOf { it.salary.toInt() } == 300) } @Test diff --git a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt index a96069c6..2b13ee0d 100644 --- a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt +++ b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt @@ -129,8 +129,8 @@ class GlobalEntitySequenceTest : BaseGlobalTest() { val employees = Employees.asSequence().groupBy { it.department.id } println(employees) assert(employees.size == 2) - assert(employees[1]!!.sumBy { it.salary.toInt() } == 150) - assert(employees[2]!!.sumBy { it.salary.toInt() } == 300) + assert(employees[1]!!.sumOf { it.salary.toInt() } == 150) + assert(employees[2]!!.sumOf { it.salary.toInt() } == 300) } @Test diff --git a/ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/OracleTest.kt b/ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/OracleTest.kt index f70789d6..b4ef1e8c 100644 --- a/ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/OracleTest.kt +++ b/ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/OracleTest.kt @@ -147,7 +147,7 @@ class OracleTest : BaseTest() { @Test fun testSchema() { - val t = object : Table("t_department", schema = oracle.username.toUpperCase()) { + val t = object : Table("t_department", schema = oracle.username.uppercase()) { val id = int("id").primaryKey().bindTo { it.id } val name = varchar("name").bindTo { it.name } } From 29c7746fc1c029f21833d2ff29b8f045e511c507 Mon Sep 17 00:00:00 2001 From: mroberts Date: Fri, 10 Sep 2021 10:25:46 -0400 Subject: [PATCH 023/126] don't cause stackoverflow in whereWithConditions --- .../main/kotlin/org/ktorm/dsl/Operators.kt | 26 ++++++++ .../src/main/kotlin/org/ktorm/dsl/Query.kt | 4 +- .../ktorm/expression/SqlExpressionVisitor.kt | 13 ++++ .../org/ktorm/expression/SqlExpressions.kt | 26 ++++++++ .../org/ktorm/expression/SqlFormatter.kt | 20 ++++++ .../test/kotlin/org/ktorm/dsl/QueryTest.kt | 65 ++++++++++++++++++- 6 files changed, 151 insertions(+), 3 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt index cd73a149..72de0781 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt @@ -224,6 +224,19 @@ public infix fun Boolean.and(expr: ColumnDeclaring): BinaryExpression, expr2: ColumnDeclaring, vararg exprs: ColumnDeclaring) : VarargsExpression = + and(listOf(expr1, expr2, *exprs)) + +/** + * And operator, translated to the `and` keyword in SQL. ANDs together all parameters. + * There must be at least two items in the collection. + */ +public fun and(exprs: Collection>) : VarargsExpression = + VarargsExpression(VarargsExpressionType.AND, exprs.map { it.asExpression() }, BooleanSqlType) + // --------- Or ---------- /** @@ -247,6 +260,19 @@ public infix fun Boolean.or(expr: ColumnDeclaring): BinaryExpression, expr2: ColumnDeclaring, vararg exprs: ColumnDeclaring) : VarargsExpression = + or(listOf(expr1, expr2, *exprs)) + +/** + * Or operator, translated to the `or` keyword in SQL. ORs together all parameters. + * There must be at least two items in the collection. + */ +public fun or(exprs: Collection>) : VarargsExpression = + VarargsExpression(VarargsExpressionType.OR, exprs.map { it.asExpression() }, BooleanSqlType) + // -------- Xor --------- /** diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt index 05dfa037..fc59772b 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt @@ -226,7 +226,7 @@ public inline fun Query.whereWithConditions(block: (MutableList a and b } } + return this.where { and(conditions) } } } @@ -242,7 +242,7 @@ public inline fun Query.whereWithOrConditions(block: (MutableList a or b } } + return this.where { or(conditions) } } } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt index 4f696b8e..dfc7e10d 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt @@ -16,6 +16,8 @@ package org.ktorm.expression +import kotlin.math.exp + /** * Base class designed to visit or modify SQL expression trees using visitor pattern. * @@ -68,6 +70,7 @@ public open class SqlExpressionVisitor { is BetweenExpression<*> -> visitBetween(expr) is ArgumentExpression -> visitArgument(expr) is FunctionExpression -> visitFunction(expr) + is VarargsExpression -> visitVarargs(expr) else -> visitUnknown(expr) } @@ -142,6 +145,16 @@ public open class SqlExpressionVisitor { } } + protected open fun visitVarargs(expr: VarargsExpression): VarargsExpression { + val scalars = expr.scalars.map { visitScalar(it) } + + return if (expr.scalars.zip(scalars).all { (orig, visited) -> orig === visited }) { + expr + } else { + expr.copy(scalars = scalars) + } + } + protected open fun visitTable(expr: TableExpression): TableExpression { return expr } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt index 2f6f8b5a..90abdb4c 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt @@ -290,6 +290,18 @@ public enum class BinaryExpressionType(private val value: String) { } } +public enum class VarargsExpressionType(private val value: String) { + /** + * And operator, translated to the `and` keyword in SQL. + */ + AND("and"), + + /** + * Or operator, translated to the `or` keyword in SQL. + */ + OR("or") +} + /** * Binary expression. * @@ -306,6 +318,20 @@ public data class BinaryExpression( override val extraProperties: Map = emptyMap() ) : ScalarExpression() +/** + * Varargs expression: change multiple expressions together. + * + * @property type the expression's type. + * @property scalars items in the expression. + */ +public data class VarargsExpression( + val type: VarargsExpressionType, + val scalars: Collection>, + override val sqlType: SqlType, + override val isLeafNode: Boolean = false, + override val extraProperties: Map = emptyMap() +) : ScalarExpression() + /** * Table expression. * diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt index 67cd6ce2..06deb150 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt @@ -260,6 +260,26 @@ public abstract class SqlFormatter( return expr } + override fun visitVarargs(expr: VarargsExpression): VarargsExpression { + write("((") + visit(expr.scalars.first()) + removeLastBlank() + write(")") + + expr.scalars.drop(1).forEach { scalar -> + write(" ") + writeKeyword("${expr.type} ") + write("(") + visit(scalar) + removeLastBlank() + write(")") + } + + removeLastBlank() + write(")") + return expr + } + override fun visitTable(expr: TableExpression): TableExpression { if (expr.catalog != null && expr.catalog.isNotBlank()) { write("${expr.catalog.quoted}.") diff --git a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt index 671f03ec..01b107fe 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt @@ -1,5 +1,6 @@ package org.ktorm.dsl +import org.junit.Assert.assertEquals import org.junit.Test import org.ktorm.BaseTest import org.ktorm.entity.filter @@ -11,6 +12,7 @@ import java.util.concurrent.ExecutionException import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException +import kotlin.random.Random /** * Created by vince on Dec 07, 2018. @@ -69,6 +71,67 @@ class QueryTest : BaseTest() { assert(name == "vince") } + @Test + fun testWhereWithConditionsSql() { + val t = Employees.aliased("t") + + val sql = database + .from(t) + .select(t.name) + .whereWithConditions { + it += t.managerId.isNull() + it += t.departmentId eq 1 + } + .sql + + val expected = """ + SELECT "t"."name" AS "t_name" + FROM "t_employee" "t" + WHERE ("t"."manager_id" IS NULL AND "t"."department_id" = ?) + """.trimIndent() + assertEquals(expected, sql) + } + + @Test + fun testWhereWithOrConditionsSql() { + val t = Employees.aliased("t") + + val sql = database + .from(t) + .select(t.name) + .whereWithOrConditions { + it += (t.id eq 1) and (t.departmentId eq 100) + it += (t.id eq 2) and (t.departmentId eq 200) + it += (t.id eq 3) and (t.departmentId eq 300) + } + .sql + + val expected = """ + SELECT "t"."name" AS "t_name" + FROM "t_employee" "t" + WHERE ((("t"."id" = ?) AND ("t"."department_id" = ?)) OR (("t"."id" = ?) AND ("t"."department_id" = ?)) OR (("t"."id" = ?) AND ("t"."department_id" = ?))) + """.trimIndent() + assertEquals(expected, sql) + } + + @Test + fun testWhereWithOrConditionsNoStackOverflow() { + val t = Employees.aliased("t") + + val sql = database + .from(t) + .select(t.name) + .whereWithOrConditions { where -> + repeat(100_000) { + where += (t.id eq Random.nextInt()) and (t.departmentId eq Random.nextInt()) + } + } + .sql + + // very large SQL doesn't cause stackoverflow + assert(true) + } + @Test fun testCombineConditions() { val t = Employees.aliased("t") @@ -300,4 +363,4 @@ class QueryTest : BaseTest() { assert(names[0] == "0:vince") assert(names[1] == "1:marry") } -} \ No newline at end of file +} From 0398a542cc3b3603c14791d165abfd3c47107724 Mon Sep 17 00:00:00 2001 From: mroberts Date: Fri, 10 Sep 2021 14:21:41 -0400 Subject: [PATCH 024/126] use suggestion --- .../main/kotlin/org/ktorm/dsl/Operators.kt | 26 ----------- .../src/main/kotlin/org/ktorm/dsl/Query.kt | 20 +++++++-- .../ktorm/expression/SqlExpressionVisitor.kt | 11 ----- .../org/ktorm/expression/SqlFormatter.kt | 20 --------- .../test/kotlin/org/ktorm/dsl/QueryTest.kt | 44 ------------------- 5 files changed, 16 insertions(+), 105 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt index 72de0781..cd73a149 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt @@ -224,19 +224,6 @@ public infix fun Boolean.and(expr: ColumnDeclaring): BinaryExpression, expr2: ColumnDeclaring, vararg exprs: ColumnDeclaring) : VarargsExpression = - and(listOf(expr1, expr2, *exprs)) - -/** - * And operator, translated to the `and` keyword in SQL. ANDs together all parameters. - * There must be at least two items in the collection. - */ -public fun and(exprs: Collection>) : VarargsExpression = - VarargsExpression(VarargsExpressionType.AND, exprs.map { it.asExpression() }, BooleanSqlType) - // --------- Or ---------- /** @@ -260,19 +247,6 @@ public infix fun Boolean.or(expr: ColumnDeclaring): BinaryExpression, expr2: ColumnDeclaring, vararg exprs: ColumnDeclaring) : VarargsExpression = - or(listOf(expr1, expr2, *exprs)) - -/** - * Or operator, translated to the `or` keyword in SQL. ORs together all parameters. - * There must be at least two items in the collection. - */ -public fun or(exprs: Collection>) : VarargsExpression = - VarargsExpression(VarargsExpressionType.OR, exprs.map { it.asExpression() }, BooleanSqlType) - // -------- Xor --------- /** diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt index fc59772b..fa6a416a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt @@ -221,12 +221,18 @@ public inline fun Query.where(condition: () -> ColumnDeclaring): Query * Note that if we don't add any conditions to the list, the `where` clause would not be set. */ public inline fun Query.whereWithConditions(block: (MutableList>) -> Unit): Query { - val conditions = ArrayList>().apply(block) + var conditions: List> = ArrayList>().apply(block) if (conditions.isEmpty()) { return this } else { - return this.where { and(conditions) } + while (conditions.size > 1) { + conditions = conditions.chunked(2) { chunk -> + if (chunk.size == 2) chunk[0] and chunk[1] else chunk[0] + } + } + + return this.where { conditions[0] } } } @@ -237,12 +243,18 @@ public inline fun Query.whereWithConditions(block: (MutableList>) -> Unit): Query { - val conditions = ArrayList>().apply(block) + var conditions: List> = ArrayList>().apply(block) if (conditions.isEmpty()) { return this } else { - return this.where { or(conditions) } + while (conditions.size > 1) { + conditions = conditions.chunked(2) { chunk -> + if (chunk.size == 2) chunk[0] or chunk[1] else chunk[0] + } + } + + return this.where { conditions[0] } } } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt index dfc7e10d..5009a88e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt @@ -70,7 +70,6 @@ public open class SqlExpressionVisitor { is BetweenExpression<*> -> visitBetween(expr) is ArgumentExpression -> visitArgument(expr) is FunctionExpression -> visitFunction(expr) - is VarargsExpression -> visitVarargs(expr) else -> visitUnknown(expr) } @@ -145,16 +144,6 @@ public open class SqlExpressionVisitor { } } - protected open fun visitVarargs(expr: VarargsExpression): VarargsExpression { - val scalars = expr.scalars.map { visitScalar(it) } - - return if (expr.scalars.zip(scalars).all { (orig, visited) -> orig === visited }) { - expr - } else { - expr.copy(scalars = scalars) - } - } - protected open fun visitTable(expr: TableExpression): TableExpression { return expr } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt index 06deb150..67cd6ce2 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt @@ -260,26 +260,6 @@ public abstract class SqlFormatter( return expr } - override fun visitVarargs(expr: VarargsExpression): VarargsExpression { - write("((") - visit(expr.scalars.first()) - removeLastBlank() - write(")") - - expr.scalars.drop(1).forEach { scalar -> - write(" ") - writeKeyword("${expr.type} ") - write("(") - visit(scalar) - removeLastBlank() - write(")") - } - - removeLastBlank() - write(")") - return expr - } - override fun visitTable(expr: TableExpression): TableExpression { if (expr.catalog != null && expr.catalog.isNotBlank()) { write("${expr.catalog.quoted}.") diff --git a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt index 01b107fe..ba97532b 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt @@ -1,6 +1,5 @@ package org.ktorm.dsl -import org.junit.Assert.assertEquals import org.junit.Test import org.ktorm.BaseTest import org.ktorm.entity.filter @@ -71,49 +70,6 @@ class QueryTest : BaseTest() { assert(name == "vince") } - @Test - fun testWhereWithConditionsSql() { - val t = Employees.aliased("t") - - val sql = database - .from(t) - .select(t.name) - .whereWithConditions { - it += t.managerId.isNull() - it += t.departmentId eq 1 - } - .sql - - val expected = """ - SELECT "t"."name" AS "t_name" - FROM "t_employee" "t" - WHERE ("t"."manager_id" IS NULL AND "t"."department_id" = ?) - """.trimIndent() - assertEquals(expected, sql) - } - - @Test - fun testWhereWithOrConditionsSql() { - val t = Employees.aliased("t") - - val sql = database - .from(t) - .select(t.name) - .whereWithOrConditions { - it += (t.id eq 1) and (t.departmentId eq 100) - it += (t.id eq 2) and (t.departmentId eq 200) - it += (t.id eq 3) and (t.departmentId eq 300) - } - .sql - - val expected = """ - SELECT "t"."name" AS "t_name" - FROM "t_employee" "t" - WHERE ((("t"."id" = ?) AND ("t"."department_id" = ?)) OR (("t"."id" = ?) AND ("t"."department_id" = ?)) OR (("t"."id" = ?) AND ("t"."department_id" = ?))) - """.trimIndent() - assertEquals(expected, sql) - } - @Test fun testWhereWithOrConditionsNoStackOverflow() { val t = Employees.aliased("t") From a7db1d854e42ee183666979c2bb19fd4efe6284e Mon Sep 17 00:00:00 2001 From: mroberts Date: Fri, 10 Sep 2021 14:23:26 -0400 Subject: [PATCH 025/126] cleanup --- .../org/ktorm/expression/SqlExpressionVisitor.kt | 2 -- .../kotlin/org/ktorm/expression/SqlExpressions.kt | 14 -------------- 2 files changed, 16 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt index 5009a88e..4f696b8e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt @@ -16,8 +16,6 @@ package org.ktorm.expression -import kotlin.math.exp - /** * Base class designed to visit or modify SQL expression trees using visitor pattern. * diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt index 90abdb4c..a008a929 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt @@ -318,20 +318,6 @@ public data class BinaryExpression( override val extraProperties: Map = emptyMap() ) : ScalarExpression() -/** - * Varargs expression: change multiple expressions together. - * - * @property type the expression's type. - * @property scalars items in the expression. - */ -public data class VarargsExpression( - val type: VarargsExpressionType, - val scalars: Collection>, - override val sqlType: SqlType, - override val isLeafNode: Boolean = false, - override val extraProperties: Map = emptyMap() -) : ScalarExpression() - /** * Table expression. * From df7b0ad9db2b510c0ed488dc83d7b415840d1667 Mon Sep 17 00:00:00 2001 From: mroberts Date: Fri, 10 Sep 2021 14:24:23 -0400 Subject: [PATCH 026/126] more cleanup --- .../kotlin/org/ktorm/expression/SqlExpressions.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt index a008a929..2f6f8b5a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt @@ -290,18 +290,6 @@ public enum class BinaryExpressionType(private val value: String) { } } -public enum class VarargsExpressionType(private val value: String) { - /** - * And operator, translated to the `and` keyword in SQL. - */ - AND("and"), - - /** - * Or operator, translated to the `or` keyword in SQL. - */ - OR("or") -} - /** * Binary expression. * From 93ae923bc4503b2f562b046cfdd4d4f7b4833ddb Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Sat, 15 Jan 2022 04:38:01 +0100 Subject: [PATCH 027/126] Add 'earth' and 'cube' Postgresql types --- .../ktorm-support-postgresql.gradle | 2 +- .../org/ktorm/support/postgresql/SqlTypes.kt | 86 +++++++++++++++++ .../support/postgresql/EarthdistanceTest.kt | 96 +++++++++++++++++++ .../test/resources/destroy-earthdistance.sql | 1 + .../src/test/resources/init-earthdistance.sql | 6 ++ 5 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt create mode 100644 ktorm-support-postgresql/src/test/resources/destroy-earthdistance.sql create mode 100644 ktorm-support-postgresql/src/test/resources/init-earthdistance.sql diff --git a/ktorm-support-postgresql/ktorm-support-postgresql.gradle b/ktorm-support-postgresql/ktorm-support-postgresql.gradle index 15223d5a..0683e19d 100644 --- a/ktorm-support-postgresql/ktorm-support-postgresql.gradle +++ b/ktorm-support-postgresql/ktorm-support-postgresql.gradle @@ -4,7 +4,7 @@ dependencies { testImplementation project(path: ":ktorm-core", configuration: "testOutput") testImplementation project(":ktorm-jackson") - testImplementation "org.postgresql:postgresql:42.2.5" + implementation "org.postgresql:postgresql:42.2.5" testImplementation "org.testcontainers:postgresql:1.15.1" testImplementation "com.zaxxer:HikariCP:4.0.3" testImplementation "com.mchange:c3p0:0.9.5.5" diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index 411a982e..e2562d65 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -19,6 +19,7 @@ package org.ktorm.support.postgresql import org.ktorm.schema.BaseTable import org.ktorm.schema.Column import org.ktorm.schema.SqlType +import org.postgresql.util.PGobject import java.sql.PreparedStatement import java.sql.ResultSet import java.sql.Types @@ -117,3 +118,88 @@ public class PgEnumType>(private val enumClass: Class) : SqlType< return rs.getString(index)?.takeIf { it.isNotBlank() }?.let { enumClass.cast(valueOf(null, it)) } } } + +/** + * Represents location of a point on the surface of the Earth. + * Part of PostgreSQL's `earthdistance` extension. + * https://www.postgresql.org/docs/12/earthdistance.html + */ +public typealias Earth = Triple + +public object PGEarthType : SqlType(Types.OTHER, "earth") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Earth) { + ps.setObject(index, parameter, Types.OTHER) + } + + override fun doGetResult(rs: ResultSet, index: Int): Earth? { + return rs.getObject(index)?.let { + (it as PGobject).value + .substring(1, it.value.length - 1) + .split(",") + .let { rawNumbers -> + Earth(rawNumbers[0].toDouble(), rawNumbers[1].toDouble(), rawNumbers[2].toDouble()) + } + } + } + +} + +public fun BaseTable<*>.earth(name: String): Column = registerColumn(name, PGEarthType) + +/** + * Represents a box suitable for an indexed search using the cube @> operator. + * Part of PostgreSQL's `cube` SQL extension. + * https://www.postgresql.org/docs/9.5/cube.html + */ +public class Cube( + public val first: Array, + public val second: Array +) { + init { + if (first.size != second.size) + throw IllegalArgumentException("Cube should be initialized with same size arrays") + } + + override fun toString(): String { + return "${first.contentToString()}, ${second.contentToString()}" + .replace('[', '(') + .replace(']', ')') + } + + override fun equals(other: Any?): Boolean { + if (other !is Cube) return false + if (!other.first.contentEquals(this.first)) return false + if (!other.second.contentEquals(this.second)) return false + return true + } + + override fun hashCode(): Int { + var result = first.contentHashCode() + result = 31 * result + second.contentHashCode() + return result + } +} + +public object PGCubeType : SqlType(Types.OTHER, "cube") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Cube) { + ps.setObject(index, parameter, Types.OTHER) + } + + override fun doGetResult(rs: ResultSet, index: Int): Cube? { + return rs.getObject(index)?.let { pgObj -> + (pgObj as PGobject).value // (-1.1, 2.2, 3.0), (1.1, -2.2, 0.3) + .replace("(", "") + .replace(")", "")// -1.1, 2.2, 3.0, 1.1, -2.2, 0.3 + .split(',') + .let { rawValues -> + Cube( + rawValues.take(rawValues.size/2).map { it.toDouble() }.toTypedArray(), + rawValues.takeLast(rawValues.size/2).map { it.toDouble() }.toTypedArray() + ) + } + } + } + +} + +public fun BaseTable<*>.cube(name: String): Column = registerColumn(name, PGCubeType) diff --git a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt new file mode 100644 index 00000000..2c1c6e2e --- /dev/null +++ b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt @@ -0,0 +1,96 @@ +package org.ktorm.support.postgresql + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.ClassRule +import org.junit.Test +import org.ktorm.BaseTest +import org.ktorm.database.Database +import org.ktorm.dsl.eq +import org.ktorm.dsl.from +import org.ktorm.dsl.insert +import org.ktorm.dsl.map +import org.ktorm.dsl.select +import org.ktorm.dsl.where +import org.ktorm.logging.ConsoleLogger +import org.ktorm.logging.LogLevel +import org.ktorm.schema.Table +import org.testcontainers.containers.PostgreSQLContainer + +/** + * Created by Kacper on 14/01/2022 + */ +class EarthdistanceTest : BaseTest() { + + companion object { + class KPostgreSqlContainer : PostgreSQLContainer("postgres:13-alpine") + + @ClassRule + @JvmField + val postgres = KPostgreSqlContainer() + } + + override fun init() { + database = Database.connect( + url = postgres.jdbcUrl, + driver = postgres.driverClassName, + user = postgres.username, + password = postgres.password, + logger = ConsoleLogger(threshold = LogLevel.TRACE) + ) + + execSqlScript("init-earthdistance.sql") + } + + override fun destroy() { + execSqlScript("destroy-earthdistance.sql") + } + + @Test + fun testEarthType() { + val table = object : Table("earthdistance_t") { + val e = earth("earth_field") + } + + val earthValue = Earth(-849959.4557142439, -5441944.3654614175, 3216183.683045588) + val inserted = database.insert(table) { + set(table.e, earthValue) + } + + val queried = database + .from(table) + .select() + .where(table.e eq earthValue) + .map { it[table.e] } + .firstOrNull() + + assertEquals(1, inserted) + assertEquals(earthValue, queried) + } + + @Test + fun testCubeType() { + val table = object : Table("earthdistance_t") { + val c = cube("cube_field") + } + + assertThrows(IllegalArgumentException::class.java) { + Cube(arrayOf(1.0), arrayOf(1.0, 2.0)) + } + + val cubeValue = Cube(arrayOf(-1.1, 2.2, 3.0), arrayOf(1.1, -2.2, 0.3)) + val inserted = database.insert(table) { + set(table.c, cubeValue) + } + + val queried = database + .from(table) + .select() + .where(table.c eq cubeValue) + .map { it[table.c] } + .firstOrNull() + + assertEquals(1, inserted) + assertEquals(cubeValue, queried) + } +} \ No newline at end of file diff --git a/ktorm-support-postgresql/src/test/resources/destroy-earthdistance.sql b/ktorm-support-postgresql/src/test/resources/destroy-earthdistance.sql new file mode 100644 index 00000000..e1772a43 --- /dev/null +++ b/ktorm-support-postgresql/src/test/resources/destroy-earthdistance.sql @@ -0,0 +1 @@ +drop table earthdistance_t; \ No newline at end of file diff --git a/ktorm-support-postgresql/src/test/resources/init-earthdistance.sql b/ktorm-support-postgresql/src/test/resources/init-earthdistance.sql new file mode 100644 index 00000000..dc992888 --- /dev/null +++ b/ktorm-support-postgresql/src/test/resources/init-earthdistance.sql @@ -0,0 +1,6 @@ +create extension if not exists earthdistance cascade; + +create table earthdistance_t( + earth_field earth, + cube_field cube +); From 5b8f20294e27e68d874e7e883410033d3f5f9355 Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Mon, 17 Jan 2022 10:32:01 +0100 Subject: [PATCH 028/126] Add CubeExpression and it's operators --- .../ktorm/support/postgresql/EarthDistance.kt | 85 +++++++++++++++++++ .../support/postgresql/PostgreSqlDialect.kt | 47 +++++++++- .../support/postgresql/EarthdistanceTest.kt | 68 +++++++++++---- 3 files changed, 181 insertions(+), 19 deletions(-) create mode 100644 ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt new file mode 100644 index 00000000..4223b5ac --- /dev/null +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -0,0 +1,85 @@ +package org.ktorm.support.postgresql + +import org.ktorm.expression.ScalarExpression +import org.ktorm.schema.BooleanSqlType +import org.ktorm.schema.ColumnDeclaring +import org.ktorm.schema.SqlType + +public enum class CubeExpressionType(private val value: String) { + /** + * Cube overlaps operator, translated to the && operator in PostgreSQL. + */ + OVERLAP("&&"), + + /** + * Cube contains operator, translated to the @> operator in PostgreSQL. + */ + CONTAINS("@>"), + + /** + * Cube contained in operator, translated to the <@ operator in PostgreSQL. + */ + CONTAINED_IN("<@"); + + override fun toString(): String { + return value + } +} + +/** + * Expression class represents PostgreSQL's `Cube` operations. + * + * @property type the expression's type. + * @property left the expression's left operand. + * @property right the expression's right operand. + */ +public data class CubeExpression( + val type: CubeExpressionType, + val left: ScalarExpression, + val right: ScalarExpression, + override val sqlType: SqlType, + override val isLeafNode: Boolean = false, + override val extraProperties: Map = emptyMap() +) : ScalarExpression() + +/** + * Cube contains operator, translated to the @> operator in PostgreSQL. + */ +public infix fun ColumnDeclaring.contains(argument: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.CONTAINS, asExpression(), argument.asExpression(), BooleanSqlType) +} + +/** + * Cube contains operator, translated to the @> operator in PostgreSQL. + */ +public infix fun ColumnDeclaring.contains(argument: Cube): CubeExpression { + return this.contains(wrapArgument(argument)) +} + +/** + * Cube contained in operator, translated to the <@ operator in PostgreSQL. + */ +public infix fun ColumnDeclaring.containedIn(argument: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.CONTAINED_IN, asExpression(), argument.asExpression(), BooleanSqlType) +} + +/** + * Cube contained in operator, translated to the <@ operator in PostgreSQL. + */ +public infix fun ColumnDeclaring.containedIn(argument: Cube): CubeExpression { + return this.containedIn(wrapArgument(argument)) +} + +/** + * Cube overlap operator, translated to the && operator in PostgreSQL. + */ +public infix fun ColumnDeclaring.overlaps(argument: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.OVERLAP, asExpression(), argument.asExpression(), BooleanSqlType) +} + +/** + * Cube overlap operator, translated to the && operator in PostgreSQL. + */ +public infix fun ColumnDeclaring.overlaps(argument: Cube): CubeExpression { + return this.overlaps(wrapArgument(argument)) +} \ No newline at end of file diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt index f5b7f523..0eea241c 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt @@ -18,7 +18,15 @@ package org.ktorm.support.postgresql import org.ktorm.database.Database import org.ktorm.database.SqlDialect -import org.ktorm.expression.* +import org.ktorm.expression.ArgumentExpression +import org.ktorm.expression.ColumnAssignmentExpression +import org.ktorm.expression.QueryExpression +import org.ktorm.expression.ScalarExpression +import org.ktorm.expression.SelectExpression +import org.ktorm.expression.SqlExpression +import org.ktorm.expression.SqlExpressionVisitor +import org.ktorm.expression.SqlFormatter +import org.ktorm.expression.TableExpression import org.ktorm.schema.IntSqlType /** @@ -60,6 +68,7 @@ public open class PostgreSqlFormatter( val result = when (expr) { is ILikeExpression -> visitILike(expr) is HStoreExpression -> visitHStore(expr) + is CubeExpression -> visitCube(expr) else -> super.visitScalar(expr) } @@ -185,6 +194,30 @@ public open class PostgreSqlFormatter( return expr } + protected open fun visitCube(expr: CubeExpression): CubeExpression { + if (expr.left.removeBrackets) { + visit(expr.left) + } else { + write("(") + visit(expr.left) + removeLastBlank() + write(") ") + } + + writeKeyword("${expr.type} ") + + if (expr.right.removeBrackets) { + visit(expr.right) + } else { + write("(") + visit(expr.right) + removeLastBlank() + write(") ") + } + + return expr + } + protected open fun visitInsertOrUpdate(expr: InsertOrUpdateExpression): InsertOrUpdateExpression { writeKeyword("insert into ") visitTable(expr.table) @@ -276,6 +309,7 @@ public open class PostgreSqlExpressionVisitor : SqlExpressionVisitor() { val result = when (expr) { is ILikeExpression -> visitILike(expr) is HStoreExpression -> visitHStore(expr) + is CubeExpression -> visitCube(expr) else -> super.visitScalar(expr) } @@ -305,6 +339,17 @@ public open class PostgreSqlExpressionVisitor : SqlExpressionVisitor() { } } + protected open fun visitCube(expr: CubeExpression): CubeExpression { + val left = visitScalar(expr.left) + val right = visitScalar(expr.right) + + if (left === expr.left && right === expr.right) { + return expr + } else { + return expr.copy(left = left, right = right) + } + } + protected open fun visitInsertOrUpdate(expr: InsertOrUpdateExpression): InsertOrUpdateExpression { val table = visitTable(expr.table) val assignments = visitColumnAssignments(expr.assignments) diff --git a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt index 2c1c6e2e..b4b7cbbe 100644 --- a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt +++ b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt @@ -12,6 +12,9 @@ import org.ktorm.dsl.insert import org.ktorm.dsl.map import org.ktorm.dsl.select import org.ktorm.dsl.where +import org.ktorm.entity.Entity +import org.ktorm.entity.add +import org.ktorm.entity.sequenceOf import org.ktorm.logging.ConsoleLogger import org.ktorm.logging.LogLevel import org.ktorm.schema.Table @@ -22,6 +25,17 @@ import org.testcontainers.containers.PostgreSQLContainer */ class EarthdistanceTest : BaseTest() { + object TestTable : Table("earthdistance_t") { + val e = earth("earth_field").bindTo { it.e } + val c = cube("cube_field").bindTo { it.c } + } + + interface TestRecord : Entity { + companion object : Entity.Factory() + val e: Earth + var c: Cube + } + companion object { class KPostgreSqlContainer : PostgreSQLContainer("postgres:13-alpine") @@ -48,20 +62,16 @@ class EarthdistanceTest : BaseTest() { @Test fun testEarthType() { - val table = object : Table("earthdistance_t") { - val e = earth("earth_field") - } - val earthValue = Earth(-849959.4557142439, -5441944.3654614175, 3216183.683045588) - val inserted = database.insert(table) { - set(table.e, earthValue) + val inserted = database.insert(TestTable) { + set(TestTable.e, earthValue) } val queried = database - .from(table) + .from(TestTable) .select() - .where(table.e eq earthValue) - .map { it[table.e] } + .where(TestTable.e eq earthValue) + .map { it[TestTable.e] } .firstOrNull() assertEquals(1, inserted) @@ -70,27 +80,49 @@ class EarthdistanceTest : BaseTest() { @Test fun testCubeType() { - val table = object : Table("earthdistance_t") { - val c = cube("cube_field") - } - assertThrows(IllegalArgumentException::class.java) { Cube(arrayOf(1.0), arrayOf(1.0, 2.0)) } val cubeValue = Cube(arrayOf(-1.1, 2.2, 3.0), arrayOf(1.1, -2.2, 0.3)) - val inserted = database.insert(table) { - set(table.c, cubeValue) + val inserted = database.insert(TestTable) { + set(TestTable.c, cubeValue) } val queried = database - .from(table) + .from(TestTable) .select() - .where(table.c eq cubeValue) - .map { it[table.c] } + .where(TestTable.c eq cubeValue) + .map { it[TestTable.c] } .firstOrNull() assertEquals(1, inserted) assertEquals(cubeValue, queried) } + + @Test + fun testCubeExpression() { + val cube1 = Cube(arrayOf(1.0, 1.0, 1.0), arrayOf(-1.0, -1.0, -1.0)) + val cube2 = Cube(arrayOf(0.0, 0.0, 0.0), arrayOf(2.0, 2.0, 2.0)) + val cube3 = Cube(arrayOf(0.5, 0.5, 0.5), arrayOf(-0.5, -0.5, -0.5)) + val record = TestRecord() + record.c = cube1 + + database.sequenceOf(TestTable).add(record) + + val t1 = (TestTable.c contains cube3).aliased("t1") // true + val t2 = (TestTable.c containedIn cube2).aliased("t2") // false + val t3 = (TestTable.c overlaps cube2).aliased("t3") // true + val t4 = (TestTable.c eq cube1).aliased("t4") // true + database.from(TestTable) + .select( + t1, t2, t3, t4 + ).where(TestTable.c eq cube1) + .map { row -> + assertEquals(true, row[t1]) + assertEquals(false, row[t2]) + assertEquals(true, row[t3]) + assertEquals(true, row[t4]) + } + } } \ No newline at end of file From 612eca4501e1be7c9f5dd44e1e98fab77009f190 Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Mon, 17 Jan 2022 12:54:26 +0100 Subject: [PATCH 029/126] Add functions from earthdistance extension --- .../ktorm/support/postgresql/EarthDistance.kt | 171 +++++++++++++++++- .../support/postgresql/EarthdistanceTest.kt | 37 ++++ 2 files changed, 205 insertions(+), 3 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt index 4223b5ac..914f2ccb 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -1,8 +1,11 @@ package org.ktorm.support.postgresql +import org.ktorm.expression.ArgumentExpression +import org.ktorm.expression.FunctionExpression import org.ktorm.expression.ScalarExpression import org.ktorm.schema.BooleanSqlType import org.ktorm.schema.ColumnDeclaring +import org.ktorm.schema.DoubleSqlType import org.ktorm.schema.SqlType public enum class CubeExpressionType(private val value: String) { @@ -35,8 +38,8 @@ public enum class CubeExpressionType(private val value: String) { */ public data class CubeExpression( val type: CubeExpressionType, - val left: ScalarExpression, - val right: ScalarExpression, + val left: ScalarExpression<*>, + val right: ScalarExpression<*>, override val sqlType: SqlType, override val isLeafNode: Boolean = false, override val extraProperties: Map = emptyMap() @@ -56,6 +59,22 @@ public infix fun ColumnDeclaring.contains(argument: Cube): CubeExpression< return this.contains(wrapArgument(argument)) } +/** + * Cube contains operator, translated to the @> operator in PostgreSQL. + */ +@JvmName("containsEarth") +public infix fun ColumnDeclaring.contains(argument: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.CONTAINS, asExpression(), argument.asExpression(), BooleanSqlType) +} + +/** + * Cube contains operator, translated to the @> operator in PostgreSQL. + */ +@JvmName("containsEarth") +public infix fun ColumnDeclaring.contains(argument: Earth): CubeExpression { + return this.contains(ArgumentExpression(argument, PGEarthType)) +} + /** * Cube contained in operator, translated to the <@ operator in PostgreSQL. */ @@ -70,6 +89,22 @@ public infix fun ColumnDeclaring.containedIn(argument: Cube): CubeExpressi return this.containedIn(wrapArgument(argument)) } +/** + * Cube contained in operator, translated to the <@ operator in PostgreSQL. + */ +@JvmName("earthContainedInCube") +public infix fun ColumnDeclaring.containedIn(argument: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.CONTAINED_IN, asExpression(), argument.asExpression(), BooleanSqlType) +} + +/** + * Cube contained in operator, translated to the <@ operator in PostgreSQL. + */ +@JvmName("earthContainedInCube") +public infix fun ColumnDeclaring.containedIn(argument: Cube): CubeExpression { + return this.containedIn(ArgumentExpression(argument, PGCubeType)) +} + /** * Cube overlap operator, translated to the && operator in PostgreSQL. */ @@ -82,4 +117,134 @@ public infix fun ColumnDeclaring.overlaps(argument: ColumnDeclaring) */ public infix fun ColumnDeclaring.overlaps(argument: Cube): CubeExpression { return this.overlaps(wrapArgument(argument)) -} \ No newline at end of file +} + +/** + * Returns the location of a point on the surface of the Earth + * given its latitude (argument 1) and longitude (argument 2) in degrees. + * + * Function from earthdistance extension + */ +public fun llToEarth( + lat: ColumnDeclaring, + lng: ColumnDeclaring +): FunctionExpression = + FunctionExpression( + functionName = "ll_to_earth", + arguments = listOf(lat.asExpression(), lng.asExpression()), + sqlType = PGEarthType + ) + +/** + * Returns the location of a point on the surface of the Earth + * given its latitude (argument 1) and longitude (argument 2) in degrees. + */ +public fun llToEarth( + lat: ColumnDeclaring, + lng: Double +): FunctionExpression = + llToEarth(lat, ArgumentExpression(lng, DoubleSqlType)) + +/** + * Returns the location of a point on the surface of the Earth + * given its latitude (argument 1) and longitude (argument 2) in degrees. + */ +public fun llToEarth( + lat: Double, + lng: ColumnDeclaring +): FunctionExpression = + llToEarth(ArgumentExpression(lat, DoubleSqlType), lng) + +/** + * Returns the location of a point on the surface of the Earth + * given its latitude (argument 1) and longitude (argument 2) in degrees. + */ +public fun llToEarth( + lat: Double, + lng: Double +): FunctionExpression = + llToEarth(ArgumentExpression(lat, DoubleSqlType), ArgumentExpression(lng, DoubleSqlType)) + +/** + * Get distance between 2 points on earth. + * + * Function from earthdistance extension, `earth_distance(point1, point2)` in SQL. + */ +public fun earthDistance( + point1: ColumnDeclaring, + point2: ColumnDeclaring +): FunctionExpression = + FunctionExpression( + functionName = "earth_distance", + arguments = listOf(point1.asExpression(), point2.asExpression()), + sqlType = DoubleSqlType + ) + +/** + * Get distance between 2 points on earth. +**/ +public fun earthDistance( + point1: ColumnDeclaring, + point2: Earth +): FunctionExpression = + earthDistance(point1, ArgumentExpression(point2, PGEarthType)) + +/** + * Get distance between 2 points on earth. + **/ +public fun earthDistance( + point1: Earth, + point2: ColumnDeclaring +): FunctionExpression = + earthDistance(ArgumentExpression(point1, PGEarthType), point2) + +/** + * Get distance between 2 points on earth. + **/ +public fun earthDistance( + point1: Earth, + point2: Earth +): FunctionExpression = + earthDistance(ArgumentExpression(point1, PGEarthType), ArgumentExpression(point2, PGEarthType)) + +/** + * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. + * + * Function from earthdistance extension, `earth_box(point, radius)` in SQL. + */ +public fun earthBox( + point: ColumnDeclaring, + radius: ColumnDeclaring +): FunctionExpression = + FunctionExpression( + functionName = "earth_box", + arguments = listOf(point.asExpression(), radius.asExpression()), + sqlType = PGCubeType + ) + +/** + * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. +**/ +public fun earthBox( + point: ColumnDeclaring, + radius: Double +): FunctionExpression = + earthBox(point, ArgumentExpression(radius, DoubleSqlType)) + +/** + * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. + **/ +public fun earthBox( + point: Earth, + radius: ColumnDeclaring +): FunctionExpression = + earthBox(ArgumentExpression(point, PGEarthType), radius) + +/** + * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. + **/ +public fun earthBox( + point: Earth, + radius: Double +): FunctionExpression = + earthBox(ArgumentExpression(point, PGEarthType), ArgumentExpression(radius, DoubleSqlType)) \ No newline at end of file diff --git a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt index b4b7cbbe..c0866e5a 100644 --- a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt +++ b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt @@ -1,7 +1,9 @@ package org.ktorm.support.postgresql import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue import org.junit.ClassRule import org.junit.Test import org.ktorm.BaseTest @@ -123,6 +125,41 @@ class EarthdistanceTest : BaseTest() { assertEquals(false, row[t2]) assertEquals(true, row[t3]) assertEquals(true, row[t4]) + return } + assertTrue(false) // Throws if above statement didn't return anything + } + + @Test + fun testEarthDistanceFunctions() { + val distance = earthDistance(llToEarth(0.0, 0.0), llToEarth(1.0, 0.0)).aliased("distance") + + val record = TestRecord() + record.c = Cube(arrayOf(0.0), arrayOf(0.0)) + database.sequenceOf(TestTable).add(record) + + database.from(TestTable) + .select(distance) + .map { row -> + assertTrue(row[distance]!! > 100000) + } + + val box = earthBox(llToEarth(0.0, 0.0), 10000.0) + val pointInBox = llToEarth(0.01, 0.01) + val pointOutsideBox = llToEarth(10.0, 10.0) + val check1 = (box contains pointInBox).aliased("c1") + val check1r = (pointInBox containedIn box).aliased("c1r") + val check2 = (box contains pointOutsideBox).aliased("c2") + val check2r = (pointOutsideBox containedIn box).aliased("c2r") + database.from(TestTable) + .select(check1, check2, check1r, check2r) + .map { row -> + assertTrue(row[check1]!!) + assertTrue(row[check1r]!!) + assertFalse(row[check2]!!) + assertFalse(row[check2r]!!) + return + } + assertTrue(false) } } \ No newline at end of file From 9766e9c9e89ce8445cb17def871b6bcdd302ae65 Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Mon, 17 Jan 2022 13:10:01 +0100 Subject: [PATCH 030/126] Add myself to contributors --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 67f86634..eff77e77 100644 --- a/build.gradle +++ b/build.gradle @@ -168,6 +168,11 @@ subprojects { project -> name = "Eric Fenderbosch" email = "eric@fender.net" } + developer { + id = "kocproz" + name = "Kacper Stasiuk" + email = "kocproz@pm.me" + } } } } From c2d36110745de018c59bdf0a39d1742ae569753c Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Mon, 17 Jan 2022 13:21:07 +0100 Subject: [PATCH 031/126] Add additional comments --- .../main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index e2562d65..f609962c 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -144,6 +144,9 @@ public object PGEarthType : SqlType(Types.OTHER, "earth") { } +/** + * Define a column typed [PGEarthType]. + */ public fun BaseTable<*>.earth(name: String): Column = registerColumn(name, PGEarthType) /** @@ -202,4 +205,7 @@ public object PGCubeType : SqlType(Types.OTHER, "cube") { } +/** + * Define a column typed [PGCubeType]. + */ public fun BaseTable<*>.cube(name: String): Column = registerColumn(name, PGCubeType) From 3eebd3d49cc725256d9a15aa41dd0546e69b4326 Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Mon, 17 Jan 2022 13:45:32 +0100 Subject: [PATCH 032/126] Replace double[] arrays with kotlin's DoubleArray --- .../kotlin/org/ktorm/support/postgresql/SqlTypes.kt | 8 ++++---- .../ktorm/support/postgresql/EarthdistanceTest.kt | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index f609962c..de624cda 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -155,8 +155,8 @@ public fun BaseTable<*>.earth(name: String): Column = registerColumn(name * https://www.postgresql.org/docs/9.5/cube.html */ public class Cube( - public val first: Array, - public val second: Array + public val first: DoubleArray, + public val second: DoubleArray ) { init { if (first.size != second.size) @@ -196,8 +196,8 @@ public object PGCubeType : SqlType(Types.OTHER, "cube") { .split(',') .let { rawValues -> Cube( - rawValues.take(rawValues.size/2).map { it.toDouble() }.toTypedArray(), - rawValues.takeLast(rawValues.size/2).map { it.toDouble() }.toTypedArray() + rawValues.take(rawValues.size/2).map { it.toDouble() }.toDoubleArray(), + rawValues.takeLast(rawValues.size/2).map { it.toDouble() }.toDoubleArray() ) } } diff --git a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt index c0866e5a..7e8f88e2 100644 --- a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt +++ b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt @@ -83,10 +83,10 @@ class EarthdistanceTest : BaseTest() { @Test fun testCubeType() { assertThrows(IllegalArgumentException::class.java) { - Cube(arrayOf(1.0), arrayOf(1.0, 2.0)) + Cube(doubleArrayOf(1.0), doubleArrayOf(1.0, 2.0)) } - val cubeValue = Cube(arrayOf(-1.1, 2.2, 3.0), arrayOf(1.1, -2.2, 0.3)) + val cubeValue = Cube(doubleArrayOf(-1.1, 2.2, 3.0), doubleArrayOf(1.1, -2.2, 0.3)) val inserted = database.insert(TestTable) { set(TestTable.c, cubeValue) } @@ -104,9 +104,9 @@ class EarthdistanceTest : BaseTest() { @Test fun testCubeExpression() { - val cube1 = Cube(arrayOf(1.0, 1.0, 1.0), arrayOf(-1.0, -1.0, -1.0)) - val cube2 = Cube(arrayOf(0.0, 0.0, 0.0), arrayOf(2.0, 2.0, 2.0)) - val cube3 = Cube(arrayOf(0.5, 0.5, 0.5), arrayOf(-0.5, -0.5, -0.5)) + val cube1 = Cube(doubleArrayOf(1.0, 1.0, 1.0), doubleArrayOf(-1.0, -1.0, -1.0)) + val cube2 = Cube(doubleArrayOf(0.0, 0.0, 0.0), doubleArrayOf(2.0, 2.0, 2.0)) + val cube3 = Cube(doubleArrayOf(0.5, 0.5, 0.5), doubleArrayOf(-0.5, -0.5, -0.5)) val record = TestRecord() record.c = cube1 @@ -135,7 +135,7 @@ class EarthdistanceTest : BaseTest() { val distance = earthDistance(llToEarth(0.0, 0.0), llToEarth(1.0, 0.0)).aliased("distance") val record = TestRecord() - record.c = Cube(arrayOf(0.0), arrayOf(0.0)) + record.c = Cube(doubleArrayOf(0.0), doubleArrayOf(0.0)) database.sequenceOf(TestTable).add(record) database.from(TestTable) From e71ccdb4af411ef58b6cebff7af00fe4e5fa5d85 Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Mon, 17 Jan 2022 13:50:28 +0100 Subject: [PATCH 033/126] Styling changes --- .../org/ktorm/support/postgresql/EarthDistance.kt | 2 +- .../kotlin/org/ktorm/support/postgresql/SqlTypes.kt | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt index 914f2ccb..094c5fd0 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -247,4 +247,4 @@ public fun earthBox( point: Earth, radius: Double ): FunctionExpression = - earthBox(ArgumentExpression(point, PGEarthType), ArgumentExpression(radius, DoubleSqlType)) \ No newline at end of file + earthBox(ArgumentExpression(point, PGEarthType), ArgumentExpression(radius, DoubleSqlType)) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index de624cda..2696f511 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -141,7 +141,6 @@ public object PGEarthType : SqlType(Types.OTHER, "earth") { } } } - } /** @@ -183,6 +182,11 @@ public class Cube( } } +/** + * Represents a Cube by storing 2 n-dimensional points + * Part of PostgreSQL's `cube` SQL extension. + * https://www.postgresql.org/docs/9.5/cube.html + */ public object PGCubeType : SqlType(Types.OTHER, "cube") { override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Cube) { ps.setObject(index, parameter, Types.OTHER) @@ -192,17 +196,16 @@ public object PGCubeType : SqlType(Types.OTHER, "cube") { return rs.getObject(index)?.let { pgObj -> (pgObj as PGobject).value // (-1.1, 2.2, 3.0), (1.1, -2.2, 0.3) .replace("(", "") - .replace(")", "")// -1.1, 2.2, 3.0, 1.1, -2.2, 0.3 + .replace(")", "") // -1.1, 2.2, 3.0, 1.1, -2.2, 0.3 .split(',') .let { rawValues -> Cube( - rawValues.take(rawValues.size/2).map { it.toDouble() }.toDoubleArray(), - rawValues.takeLast(rawValues.size/2).map { it.toDouble() }.toDoubleArray() + rawValues.take(rawValues.size / 2).map { it.toDouble() }.toDoubleArray(), + rawValues.takeLast(rawValues.size / 2).map { it.toDouble() }.toDoubleArray() ) } } } - } /** From 2f26e0c03c6165c7f15c8c95ebcdd4a52b593aaa Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Mon, 17 Jan 2022 13:59:36 +0100 Subject: [PATCH 034/126] More styling changes to address codacy --- .../org/ktorm/support/postgresql/EarthDistance.kt | 15 +++++++++------ .../org/ktorm/support/postgresql/SqlTypes.kt | 9 +++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt index 094c5fd0..02a51baf 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -8,6 +8,9 @@ import org.ktorm.schema.ColumnDeclaring import org.ktorm.schema.DoubleSqlType import org.ktorm.schema.SqlType +/** + * Enum for 'cube' and 'earthdistance' binary operators. + */ public enum class CubeExpressionType(private val value: String) { /** * Cube overlaps operator, translated to the && operator in PostgreSQL. @@ -182,7 +185,7 @@ public fun earthDistance( /** * Get distance between 2 points on earth. -**/ + */ public fun earthDistance( point1: ColumnDeclaring, point2: Earth @@ -191,7 +194,7 @@ public fun earthDistance( /** * Get distance between 2 points on earth. - **/ + */ public fun earthDistance( point1: Earth, point2: ColumnDeclaring @@ -200,7 +203,7 @@ public fun earthDistance( /** * Get distance between 2 points on earth. - **/ + */ public fun earthDistance( point1: Earth, point2: Earth @@ -224,7 +227,7 @@ public fun earthBox( /** * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. -**/ + */ public fun earthBox( point: ColumnDeclaring, radius: Double @@ -233,7 +236,7 @@ public fun earthBox( /** * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. - **/ + */ public fun earthBox( point: Earth, radius: ColumnDeclaring @@ -242,7 +245,7 @@ public fun earthBox( /** * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. - **/ + */ public fun earthBox( point: Earth, radius: Double diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index 2696f511..3d8e14d4 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -126,6 +126,10 @@ public class PgEnumType>(private val enumClass: Class) : SqlType< */ public typealias Earth = Triple +/** + * Represents a point on Earth's surface + * Part of PostgreSQL's `earthdistance` SQL extension. + */ public object PGEarthType : SqlType(Types.OTHER, "earth") { override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Earth) { ps.setObject(index, parameter, Types.OTHER) @@ -153,13 +157,14 @@ public fun BaseTable<*>.earth(name: String): Column = registerColumn(name * Part of PostgreSQL's `cube` SQL extension. * https://www.postgresql.org/docs/9.5/cube.html */ -public class Cube( +public data class Cube( public val first: DoubleArray, public val second: DoubleArray ) { init { - if (first.size != second.size) + if (first.size != second.size) { throw IllegalArgumentException("Cube should be initialized with same size arrays") + } } override fun toString(): String { From b97afeb90db0cd1fc239b272ef474944ef6a66f7 Mon Sep 17 00:00:00 2001 From: Kacper Stasiuk Date: Mon, 17 Jan 2022 15:30:15 +0100 Subject: [PATCH 035/126] Add copyright header --- .../ktorm/support/postgresql/EarthDistance.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt index 02a51baf..38bd9d25 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.ktorm.support.postgresql import org.ktorm.expression.ArgumentExpression From a26e3f61ad687342d37499c0e5ef3d126d3b5dd3 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Wed, 9 Feb 2022 11:40:24 +0800 Subject: [PATCH 036/126] feature(SQLite): InsertOrUpdate --- .../ktorm/support/sqlite/InsertOrUpdate.kt | 447 ++++++++++++++++++ .../org/ktorm/support/sqlite/SQLiteDialect.kt | 34 ++ 2 files changed, 481 insertions(+) create mode 100644 ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt new file mode 100644 index 00000000..6b9b0356 --- /dev/null +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt @@ -0,0 +1,447 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ktorm.support.sqlite + +import org.ktorm.database.CachedRowSet +import org.ktorm.database.Database +import org.ktorm.dsl.AssignmentsBuilder +import org.ktorm.dsl.KtormDsl +import org.ktorm.expression.ColumnAssignmentExpression +import org.ktorm.expression.ColumnExpression +import org.ktorm.expression.SqlExpression +import org.ktorm.expression.TableExpression +import org.ktorm.schema.BaseTable +import org.ktorm.schema.Column + +/** + * Insert or update expression, represents an insert statement with an + * `on conflict (key) do update set` clause in PostgreSQL. + * + * @property table the table to be inserted. + * @property assignments the inserted column assignments. + * @property conflictColumns the index columns on which the conflict may happen. + * @property updateAssignments the updated column assignments while any key conflict exists. + * @property returningColumns the returning columns. + */ +public data class InsertOrUpdateExpression( + val table: TableExpression, + val assignments: List>, + val conflictColumns: List> = emptyList(), + val updateAssignments: List> = emptyList(), + val returningColumns: List> = emptyList(), + override val isLeafNode: Boolean = false, + override val extraProperties: Map = emptyMap() +) : SqlExpression() + +/** + * Insert a record to the table, determining if there is a key conflict while it's being inserted, and automatically + * performs an update if any conflict exists. + * + * Usage: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = t_employee.salary + ? + * ``` + * + * @since 2.7 + * @param table the table to be inserted. + * @param block the DSL block used to construct the expression. + * @return the effected row count. + */ +public fun > Database.insertOrUpdate( + table: T, block: InsertOrUpdateStatementBuilder.(T) -> Unit +): Int { + val expression = buildInsertOrUpdateExpression(table, returning = emptyList(), block = block) + return executeUpdate(expression) +} + +/** + * Insert a record to the table, determining if there is a key conflict while it's being inserted, automatically + * performs an update if any conflict exists, and finally returns the specific columns. + * + * Usage: + * + * ```kotlin + * val (id, job) = database.insertOrUpdateReturning(Employees, Pair(Employees.id, Employees.job)) { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = t_employee.salary + ? + * returning id, job + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the columns to return + * @param block the DSL block used to construct the expression. + * @return the returning columns' values. + */ +public fun , C1 : Any, C2 : Any> Database.insertOrUpdateReturning( + table: T, returning: Pair, Column>, block: InsertOrUpdateStatementBuilder.(T) -> Unit +): Pair { + val (c1, c2) = returning + val row = insertOrUpdateReturningRow(table, listOf(c1, c2), block) + if (row == null) { + return Pair(null, null) + } else { + return Pair(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2)) + } +} + +/** + * Insert a record to the table, determining if there is a key conflict while it's being inserted, automatically + * performs an update if any conflict exists, and finally returns the specific columns. + * + * Usage: + * + * ```kotlin + * val (id, job, salary) = + * database.insertOrUpdateReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = t_employee.salary + ? + * returning id, job, salary + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the columns to return + * @param block the DSL block used to construct the expression. + * @return the returning columns' values. + */ +public fun , C1 : Any, C2 : Any, C3 : Any> Database.insertOrUpdateReturning( + table: T, returning: Triple, Column, Column>, block: InsertOrUpdateStatementBuilder.(T) -> Unit +): Triple { + val (c1, c2, c3) = returning + val row = insertOrUpdateReturningRow(table, listOf(c1, c2, c3), block) + if (row == null) { + return Triple(null, null, null) + } else { + return Triple(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2), c3.sqlType.getResult(row, 3)) + } +} + +/** + * Insert or update, returning one row. + */ +private fun > Database.insertOrUpdateReturningRow( + table: T, returning: List>, block: InsertOrUpdateStatementBuilder.(T) -> Unit +): CachedRowSet? { + val expression = buildInsertOrUpdateExpression(table, returning, block) + val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) + + if (rowSet.size() == 0) { + // Possible when using onConflict { doNothing() } + return null + } + + if (rowSet.size() == 1) { + check(rowSet.next()) + return rowSet + } else { + val (sql, _) = formatExpression(expression, beautifySql = true) + throw IllegalStateException("Expected 1 row but ${rowSet.size()} returned from sql: \n\n$sql") + } +} + +/** + * Build an insert or update expression. + */ +private fun > buildInsertOrUpdateExpression( + table: T, returning: List>, block: InsertOrUpdateStatementBuilder.(T) -> Unit +): InsertOrUpdateExpression { + val builder = InsertOrUpdateStatementBuilder().apply { block(table) } + + val conflictColumns = builder.conflictColumns.ifEmpty { table.primaryKeys } + if (conflictColumns.isEmpty()) { + val msg = "" + + "Table '$table' doesn't have a primary key, " + + "you must specify the conflict columns when calling onConflict(col) { .. }" + throw IllegalStateException(msg) + } + + if (!builder.doNothing && builder.updateAssignments.isEmpty()) { + val msg = "" + + "Cannot leave the onConflict clause empty! " + + "If you desire no update action at all please explicitly call `doNothing()`" + throw IllegalStateException(msg) + } + + return InsertOrUpdateExpression( + table = table.asExpression(), + assignments = builder.assignments, + conflictColumns = conflictColumns.map { it.asExpression() }, + updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments, + returningColumns = returning.map { it.asExpression() } + ) +} + +/** + * Insert a record to the table and return the specific column. + * + * Usage: + * + * ```kotlin + * val id = database.insertReturning(Employees, Employees.id) { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * returning id + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the column to return + * @param block the DSL block used to construct the expression. + * @return the returning column's value. + */ +public fun , C : Any> Database.insertReturning( + table: T, returning: Column, block: AssignmentsBuilder.(T) -> Unit +): C? { + val row = insertReturningRow(table, listOf(returning), block) + return returning.sqlType.getResult(row, 1) +} + +/** + * Insert a record to the table and return the specific columns. + * + * Usage: + * + * ```kotlin + * val (id, job) = database.insertReturning(Employees, Pair(Employees.id, Employees.job)) { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * returning id, job + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the columns to return + * @param block the DSL block used to construct the expression. + * @return the returning columns' values. + */ +public fun , C1 : Any, C2 : Any> Database.insertReturning( + table: T, returning: Pair, Column>, block: AssignmentsBuilder.(T) -> Unit +): Pair { + val (c1, c2) = returning + val row = insertReturningRow(table, listOf(c1, c2), block) + return Pair(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2)) +} + +/** + * Insert a record to the table and return the specific columns. + * + * Usage: + * + * ```kotlin + * val (id, job, salary) = + * database.insertReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * returning id, job, salary + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the columns to return + * @param block the DSL block used to construct the expression. + * @return the returning columns' values. + */ +public fun , C1 : Any, C2 : Any, C3 : Any> Database.insertReturning( + table: T, returning: Triple, Column, Column>, block: AssignmentsBuilder.(T) -> Unit +): Triple { + val (c1, c2, c3) = returning + val row = insertReturningRow(table, listOf(c1, c2, c3), block) + return Triple(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2), c3.sqlType.getResult(row, 3)) +} + +/** + * Insert and returning one row. + */ +private fun > Database.insertReturningRow( + table: T, returning: List>, block: AssignmentsBuilder.(T) -> Unit +): CachedRowSet { + val builder = PostgreSqlAssignmentsBuilder().apply { block(table) } + + val expression = InsertOrUpdateExpression( + table = table.asExpression(), + assignments = builder.assignments, + returningColumns = returning.map { it.asExpression() } + ) + + val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) + + if (rowSet.size() == 1) { + check(rowSet.next()) + return rowSet + } else { + val (sql, _) = formatExpression(expression, beautifySql = true) + throw IllegalStateException("Expected 1 row but ${rowSet.size()} returned from sql: \n\n$sql") + } +} + +/** + * Base class of PostgreSQL DSL builders, provide basic functions used to build assignments for insert or update DSL. + */ +@KtormDsl +public open class PostgreSqlAssignmentsBuilder : AssignmentsBuilder() { + + /** + * A getter that returns the readonly view of the built assignments list. + */ + internal val assignments: List> get() = _assignments +} + +/** + * DSL builder for insert or update statements. + */ +@KtormDsl +public class InsertOrUpdateStatementBuilder : PostgreSqlAssignmentsBuilder() { + internal val conflictColumns = ArrayList>() + internal val updateAssignments = ArrayList>() + internal var doNothing = false + + /** + * Specify the update assignments while any key conflict exists. + */ + @Deprecated( + message = "This function will be removed in the future, please use onConflict { } instead", + replaceWith = ReplaceWith("onConflict(columns, block)") + ) + public fun onDuplicateKey(vararg columns: Column<*>, block: AssignmentsBuilder.() -> Unit) { + onConflict(*columns, block = block) + } + + /** + * Specify the update assignments while any key conflict exists. + */ + public fun onConflict(vararg columns: Column<*>, block: InsertOrUpdateOnConflictClauseBuilder.() -> Unit) { + val builder = InsertOrUpdateOnConflictClauseBuilder().apply(block) + this.conflictColumns += columns + this.updateAssignments += builder.assignments + this.doNothing = builder.doNothing + } +} + +/** + * DSL builder for insert or update on conflict clause. + */ +@KtormDsl +public class InsertOrUpdateOnConflictClauseBuilder : PostgreSqlAssignmentsBuilder() { + internal var doNothing = false + + /** + * Explicitly tells ktorm to ignore any on-conflict errors and continue insertion. + */ + public fun doNothing() { + this.doNothing = true + } + + /** + * Reference the 'EXCLUDED' table in a ON CONFLICT clause. + */ + public fun excluded(column: Column): ColumnExpression { + // excluded.name + return ColumnExpression( + table = TableExpression(name = "excluded"), + name = column.name, + sqlType = column.sqlType + ) + } +} diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index d6d90cd9..62a65029 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -19,6 +19,7 @@ package org.ktorm.support.sqlite import org.ktorm.database.* import org.ktorm.expression.ArgumentExpression import org.ktorm.expression.QueryExpression +import org.ktorm.expression.SqlExpression import org.ktorm.expression.SqlFormatter import org.ktorm.schema.IntSqlType @@ -63,10 +64,43 @@ public open class SQLiteFormatter( database: Database, beautifySql: Boolean, indentSize: Int ) : SqlFormatter(database, beautifySql, indentSize) { + override fun visit(expr: SqlExpression): SqlExpression { + val result = when (expr) { + is InsertOrUpdateExpression -> visitInsertOrUpdate(expr) + else -> super.visit(expr) + } + + check(result === expr) { "SqlFormatter cannot modify the expression trees." } + return result + } + override fun writePagination(expr: QueryExpression) { newLine(Indentation.SAME) writeKeyword("limit ?, ? ") _parameters += ArgumentExpression(expr.offset ?: 0, IntSqlType) _parameters += ArgumentExpression(expr.limit ?: Int.MAX_VALUE, IntSqlType) } + + protected open fun visitInsertOrUpdate(expr: InsertOrUpdateExpression): InsertOrUpdateExpression { + writeKeyword("insert into ") + visitTable(expr.table) + writeInsertColumnNames(expr.assignments.map { it.column }) + writeKeyword("values ") + writeInsertValues(expr.assignments) + + if (expr.conflictColumns.isNotEmpty()) { + writeKeyword("on conflict ") + writeInsertColumnNames(expr.conflictColumns) + + if (expr.updateAssignments.isNotEmpty()) { + writeKeyword("do update set ") + visitColumnAssignments(expr.updateAssignments) + } else { + writeKeyword("do nothing ") + } + } + + return expr + } + } From 889d40f9ceabce889b4f67adf8a6ae391f89dba5 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Wed, 9 Feb 2022 14:24:01 +0800 Subject: [PATCH 037/126] =?UTF-8?q?feature(SQLite):=20InsertOrUpdate=20?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BA=20sqlite=20=E6=96=B9=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 参照 postgresql 添加 InsertOrUpdate 相关代码 参照 mysql 添加 AliasRemover 代码 --- .../ktorm/support/sqlite/InsertOrUpdate.kt | 308 +++--------------- .../org/ktorm/support/sqlite/SQLiteDialect.kt | 44 ++- .../org/ktorm/support/sqlite/SQLiteTest.kt | 29 ++ 3 files changed, 107 insertions(+), 274 deletions(-) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt index 6b9b0356..fe9ea996 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt @@ -16,7 +16,6 @@ package org.ktorm.support.sqlite -import org.ktorm.database.CachedRowSet import org.ktorm.database.Database import org.ktorm.dsl.AssignmentsBuilder import org.ktorm.dsl.KtormDsl @@ -29,20 +28,18 @@ import org.ktorm.schema.Column /** * Insert or update expression, represents an insert statement with an - * `on conflict (key) do update set` clause in PostgreSQL. + * `on conflict (key) do update set` clause in SQLite. * * @property table the table to be inserted. * @property assignments the inserted column assignments. * @property conflictColumns the index columns on which the conflict may happen. * @property updateAssignments the updated column assignments while any key conflict exists. - * @property returningColumns the returning columns. */ public data class InsertOrUpdateExpression( val table: TableExpression, val assignments: List>, val conflictColumns: List> = emptyList(), val updateAssignments: List> = emptyList(), - val returningColumns: List> = emptyList(), override val isLeafNode: Boolean = false, override val extraProperties: Map = emptyMap() ) : SqlExpression() @@ -61,7 +58,7 @@ public data class InsertOrUpdateExpression( * set(it.salary, 1000) * set(it.hireDate, LocalDate.now()) * set(it.departmentId, 1) - * onConflict { + * onConflict(it.id) { * set(it.salary, it.salary + 900) * } * } @@ -83,133 +80,15 @@ public data class InsertOrUpdateExpression( public fun > Database.insertOrUpdate( table: T, block: InsertOrUpdateStatementBuilder.(T) -> Unit ): Int { - val expression = buildInsertOrUpdateExpression(table, returning = emptyList(), block = block) + val expression = AliasRemover.visit(buildInsertOrUpdateExpression(table, block = block)) return executeUpdate(expression) } -/** - * Insert a record to the table, determining if there is a key conflict while it's being inserted, automatically - * performs an update if any conflict exists, and finally returns the specific columns. - * - * Usage: - * - * ```kotlin - * val (id, job) = database.insertOrUpdateReturning(Employees, Pair(Employees.id, Employees.job)) { - * set(it.id, 1) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * onConflict { - * set(it.salary, it.salary + 900) - * } - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) - * on conflict (id) do update set salary = t_employee.salary + ? - * returning id, job - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the columns to return - * @param block the DSL block used to construct the expression. - * @return the returning columns' values. - */ -public fun , C1 : Any, C2 : Any> Database.insertOrUpdateReturning( - table: T, returning: Pair, Column>, block: InsertOrUpdateStatementBuilder.(T) -> Unit -): Pair { - val (c1, c2) = returning - val row = insertOrUpdateReturningRow(table, listOf(c1, c2), block) - if (row == null) { - return Pair(null, null) - } else { - return Pair(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2)) - } -} - -/** - * Insert a record to the table, determining if there is a key conflict while it's being inserted, automatically - * performs an update if any conflict exists, and finally returns the specific columns. - * - * Usage: - * - * ```kotlin - * val (id, job, salary) = - * database.insertOrUpdateReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { - * set(it.id, 1) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * onConflict { - * set(it.salary, it.salary + 900) - * } - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) - * on conflict (id) do update set salary = t_employee.salary + ? - * returning id, job, salary - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the columns to return - * @param block the DSL block used to construct the expression. - * @return the returning columns' values. - */ -public fun , C1 : Any, C2 : Any, C3 : Any> Database.insertOrUpdateReturning( - table: T, returning: Triple, Column, Column>, block: InsertOrUpdateStatementBuilder.(T) -> Unit -): Triple { - val (c1, c2, c3) = returning - val row = insertOrUpdateReturningRow(table, listOf(c1, c2, c3), block) - if (row == null) { - return Triple(null, null, null) - } else { - return Triple(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2), c3.sqlType.getResult(row, 3)) - } -} - -/** - * Insert or update, returning one row. - */ -private fun > Database.insertOrUpdateReturningRow( - table: T, returning: List>, block: InsertOrUpdateStatementBuilder.(T) -> Unit -): CachedRowSet? { - val expression = buildInsertOrUpdateExpression(table, returning, block) - val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) - - if (rowSet.size() == 0) { - // Possible when using onConflict { doNothing() } - return null - } - - if (rowSet.size() == 1) { - check(rowSet.next()) - return rowSet - } else { - val (sql, _) = formatExpression(expression, beautifySql = true) - throw IllegalStateException("Expected 1 row but ${rowSet.size()} returned from sql: \n\n$sql") - } -} - /** * Build an insert or update expression. */ private fun > buildInsertOrUpdateExpression( - table: T, returning: List>, block: InsertOrUpdateStatementBuilder.(T) -> Unit + table: T, block: InsertOrUpdateStatementBuilder.(T) -> Unit ): InsertOrUpdateExpression { val builder = InsertOrUpdateStatementBuilder().apply { block(table) } @@ -232,155 +111,15 @@ private fun > buildInsertOrUpdateExpression( table = table.asExpression(), assignments = builder.assignments, conflictColumns = conflictColumns.map { it.asExpression() }, - updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments, - returningColumns = returning.map { it.asExpression() } + updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments ) } /** - * Insert a record to the table and return the specific column. - * - * Usage: - * - * ```kotlin - * val id = database.insertReturning(Employees, Employees.id) { - * set(it.id, 1) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) - * returning id - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the column to return - * @param block the DSL block used to construct the expression. - * @return the returning column's value. - */ -public fun , C : Any> Database.insertReturning( - table: T, returning: Column, block: AssignmentsBuilder.(T) -> Unit -): C? { - val row = insertReturningRow(table, listOf(returning), block) - return returning.sqlType.getResult(row, 1) -} - -/** - * Insert a record to the table and return the specific columns. - * - * Usage: - * - * ```kotlin - * val (id, job) = database.insertReturning(Employees, Pair(Employees.id, Employees.job)) { - * set(it.id, 1) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) - * returning id, job - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the columns to return - * @param block the DSL block used to construct the expression. - * @return the returning columns' values. - */ -public fun , C1 : Any, C2 : Any> Database.insertReturning( - table: T, returning: Pair, Column>, block: AssignmentsBuilder.(T) -> Unit -): Pair { - val (c1, c2) = returning - val row = insertReturningRow(table, listOf(c1, c2), block) - return Pair(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2)) -} - -/** - * Insert a record to the table and return the specific columns. - * - * Usage: - * - * ```kotlin - * val (id, job, salary) = - * database.insertReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { - * set(it.id, 1) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) - * returning id, job, salary - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the columns to return - * @param block the DSL block used to construct the expression. - * @return the returning columns' values. - */ -public fun , C1 : Any, C2 : Any, C3 : Any> Database.insertReturning( - table: T, returning: Triple, Column, Column>, block: AssignmentsBuilder.(T) -> Unit -): Triple { - val (c1, c2, c3) = returning - val row = insertReturningRow(table, listOf(c1, c2, c3), block) - return Triple(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2), c3.sqlType.getResult(row, 3)) -} - -/** - * Insert and returning one row. - */ -private fun > Database.insertReturningRow( - table: T, returning: List>, block: AssignmentsBuilder.(T) -> Unit -): CachedRowSet { - val builder = PostgreSqlAssignmentsBuilder().apply { block(table) } - - val expression = InsertOrUpdateExpression( - table = table.asExpression(), - assignments = builder.assignments, - returningColumns = returning.map { it.asExpression() } - ) - - val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) - - if (rowSet.size() == 1) { - check(rowSet.next()) - return rowSet - } else { - val (sql, _) = formatExpression(expression, beautifySql = true) - throw IllegalStateException("Expected 1 row but ${rowSet.size()} returned from sql: \n\n$sql") - } -} - -/** - * Base class of PostgreSQL DSL builders, provide basic functions used to build assignments for insert or update DSL. + * Base class of SQLite DSL builders, provide basic functions used to build assignments for insert or update DSL. */ @KtormDsl -public open class PostgreSqlAssignmentsBuilder : AssignmentsBuilder() { +public open class SQLiteAssignmentsBuilder : AssignmentsBuilder() { /** * A getter that returns the readonly view of the built assignments list. @@ -392,7 +131,7 @@ public open class PostgreSqlAssignmentsBuilder : AssignmentsBuilder() { * DSL builder for insert or update statements. */ @KtormDsl -public class InsertOrUpdateStatementBuilder : PostgreSqlAssignmentsBuilder() { +public class InsertOrUpdateStatementBuilder : SQLiteAssignmentsBuilder() { internal val conflictColumns = ArrayList>() internal val updateAssignments = ArrayList>() internal var doNothing = false @@ -423,7 +162,7 @@ public class InsertOrUpdateStatementBuilder : PostgreSqlAssignmentsBuilder() { * DSL builder for insert or update on conflict clause. */ @KtormDsl -public class InsertOrUpdateOnConflictClauseBuilder : PostgreSqlAssignmentsBuilder() { +public class InsertOrUpdateOnConflictClauseBuilder : SQLiteAssignmentsBuilder() { internal var doNothing = false /** @@ -444,4 +183,33 @@ public class InsertOrUpdateOnConflictClauseBuilder : PostgreSqlAssignmentsBuilde sqlType = column.sqlType ) } + + /** + * be equal to 'set(column, excluded(column))'. + */ + public fun setExcluded(column: Column) { + set(column, excluded(column)) + } +} + +/** + * [SQLiteExpressionVisitor] implementation used to removed table aliases, used by Ktorm internal. + */ +internal object AliasRemover : SQLiteExpressionVisitor() { + + override fun visitTable(expr: TableExpression): TableExpression { + if (expr.tableAlias == null) { + return expr + } else { + return expr.copy(tableAlias = null) + } + } + + override fun visitColumn(expr: ColumnExpression): ColumnExpression { + if (expr.table == null) { + return expr + } else { + return expr.copy(table = null) + } + } } diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index 62a65029..85f998b5 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -17,10 +17,7 @@ package org.ktorm.support.sqlite import org.ktorm.database.* -import org.ktorm.expression.ArgumentExpression -import org.ktorm.expression.QueryExpression -import org.ktorm.expression.SqlExpression -import org.ktorm.expression.SqlFormatter +import org.ktorm.expression.* import org.ktorm.schema.IntSqlType /** @@ -104,3 +101,42 @@ public open class SQLiteFormatter( } } + +/** + * Base class designed to visit or modify SQLite's expression trees using visitor pattern. + * + * For detailed documents, see [SqlExpressionVisitor]. + */ +public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { + + override fun visit(expr: SqlExpression): SqlExpression { + return when (expr) { + is InsertOrUpdateExpression -> visitInsertOrUpdate(expr) + else -> super.visit(expr) + } + } + + protected open fun visitInsertOrUpdate(expr: InsertOrUpdateExpression): InsertOrUpdateExpression { + val table = visitTable(expr.table) + val assignments = visitColumnAssignments(expr.assignments) + val conflictColumns = visitExpressionList(expr.conflictColumns) + val updateAssignments = visitColumnAssignments(expr.updateAssignments) + + @Suppress("ComplexCondition") + if (table === expr.table + && assignments === expr.assignments + && conflictColumns === expr.conflictColumns + && updateAssignments === expr.updateAssignments + ) { + return expr + } else { + return expr.copy( + table = table, + assignments = assignments, + conflictColumns = conflictColumns, + updateAssignments = updateAssignments + ) + } + } + +} diff --git a/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt b/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt index e4ccd062..75f14904 100644 --- a/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt +++ b/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt @@ -78,6 +78,35 @@ class SQLiteTest : BaseTest() { database.delete(configs) { it.key eq "test" } } + @Test + fun testInsertOrUpdate() { + database.insertOrUpdate(Employees) { + set(it.id, 1) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + onConflict { + set(it.salary, it.salary + 1000) + } + } + database.insertOrUpdate(Employees.aliased("t")) { + set(it.id, 5) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + onConflict(it.id) { + set(it.salary, it.salary + 1000) + } + } + + assert(database.employees.find { it.id eq 1 }!!.salary == 1100L) + assert(database.employees.find { it.id eq 5 }!!.salary == 1000L) + } + @Test fun testLimit() { val query = database.from(Employees).select().orderBy(Employees.id.desc()).limit(0, 2) From 8b0fdeddc1e3b73424e40409ecd57f652d30fff0 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Wed, 9 Feb 2022 15:58:47 +0800 Subject: [PATCH 038/126] fix: Codacy Static Code Analysis --- .../kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt | 8 ++++---- .../main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt index fe9ea996..e2828ca3 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt @@ -95,15 +95,15 @@ private fun > buildInsertOrUpdateExpression( val conflictColumns = builder.conflictColumns.ifEmpty { table.primaryKeys } if (conflictColumns.isEmpty()) { val msg = "" + - "Table '$table' doesn't have a primary key, " + - "you must specify the conflict columns when calling onConflict(col) { .. }" + "Table '$table' doesn't have a primary key, " + + "you must specify the conflict columns when calling onConflict(col) { .. }" throw IllegalStateException(msg) } if (!builder.doNothing && builder.updateAssignments.isEmpty()) { val msg = "" + - "Cannot leave the onConflict clause empty! " + - "If you desire no update action at all please explicitly call `doNothing()`" + "Cannot leave the onConflict clause empty! " + + "If you desire no update action at all please explicitly call `doNothing()`" throw IllegalStateException(msg) } diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index 85f998b5..44959d22 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -99,7 +99,6 @@ public open class SQLiteFormatter( return expr } - } /** From 261b5ee53ec479516648159f123dc39e94de9223 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Wed, 9 Feb 2022 16:03:24 +0800 Subject: [PATCH 039/126] fix: Codacy Static Code Analysis --- .../src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index 44959d22..102f0221 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -137,5 +137,4 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { ) } } - } From 9c6ddf7c68c0ac3257a52cdb5ed35b2e1e69bcf7 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Thu, 10 Feb 2022 08:36:06 +0800 Subject: [PATCH 040/126] feature(SQLite): Adding Functions --- .../org/ktorm/support/sqlite/Functions.kt | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/Functions.kt diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/Functions.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/Functions.kt new file mode 100644 index 00000000..90ac8d26 --- /dev/null +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/Functions.kt @@ -0,0 +1,238 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ktorm.support.sqlite + +import org.ktorm.expression.ArgumentExpression +import org.ktorm.expression.FunctionExpression +import org.ktorm.schema.* +import java.math.BigDecimal +import java.sql.Date +import java.sql.Time +import java.sql.Timestamp +import java.time.* +import java.util.* + +@PublishedApi +internal inline fun sqlTypeOf(): SqlType? { + val sqlType = when (T::class) { + Boolean::class -> BooleanSqlType + Int::class -> IntSqlType + Short::class -> ShortSqlType + Long::class -> LongSqlType + Float::class -> FloatSqlType + Double::class -> DoubleSqlType + BigDecimal::class -> DecimalSqlType + String::class -> VarcharSqlType + ByteArray::class -> BytesSqlType + Timestamp::class -> TimestampSqlType + Date::class -> DateSqlType + Time::class -> TimeSqlType + Instant::class -> InstantSqlType + LocalDateTime::class -> LocalDateTimeSqlType + LocalDate::class -> LocalDateSqlType + LocalTime::class -> LocalTimeSqlType + MonthDay::class -> MonthDaySqlType + YearMonth::class -> YearMonthSqlType + Year::class -> YearSqlType + UUID::class -> UuidSqlType + else -> null + } + + @Suppress("UNCHECKED_CAST") + return sqlType as SqlType? +} + +// region SQLite: The JSON1 Extension + +/** + * SQLite json_extract function, translated to `json_extract(column, path)`. + */ +public inline fun ColumnDeclaring<*>.jsonExtract( + path: String, + sqlType: SqlType = sqlTypeOf() ?: error("Cannot detect the result's SqlType, please specify manually.") +): FunctionExpression { + // json_extract(column, path) + return FunctionExpression( + functionName = "json_extract", + arguments = listOf(asExpression(), ArgumentExpression(path, VarcharSqlType)), + sqlType = sqlType + ) +} + +/** + * SQLite json_patch function, translated to `json_patch(left, right)`. + */ +public fun ColumnDeclaring<*>.jsonPatch(right: ColumnDeclaring<*>): FunctionExpression { + // json_patch(left, right) + return FunctionExpression( + functionName = "json_patch", + arguments = listOf(this, right).map { it.asExpression() }, + sqlType = VarcharSqlType + ) +} + +/** + * SQLite json_remove function, translated to `json_remove(column, path)`. + */ +public fun ColumnDeclaring<*>.jsonRemove(path: String): FunctionExpression { + // json_remove(column, path) + return FunctionExpression( + functionName = "json_remove", + arguments = listOf(asExpression(), ArgumentExpression(path, VarcharSqlType)), + sqlType = VarcharSqlType + ) +} + +/** + * SQLite json_valid function, translated to `json_valid(column)`. + */ +public fun ColumnDeclaring<*>.jsonValid(): FunctionExpression { + // json_valid(column) + return FunctionExpression( + functionName = "json_valid", + arguments = listOf(asExpression()), + sqlType = BooleanSqlType + ) +} + +// endregion + +// region SQLite: Built-In Scalar SQL Functions + +/** + * SQLite random function, translated to `random()`. + */ +public fun random(): FunctionExpression { + return FunctionExpression(functionName = "random", arguments = emptyList(), sqlType = LongSqlType) +} + +/** + * SQLite ifnull function, translated to `ifnull(left, right)`. + */ +public fun ColumnDeclaring.ifNull(right: ColumnDeclaring): FunctionExpression { + // ifnull(left, right) + return FunctionExpression( + functionName = "ifnull", + arguments = listOf(this, right).map { it.asExpression() }, + sqlType = sqlType + ) +} + +/** + * SQLite ifnull function, translated to `ifnull(left, right)`. + */ +public fun ColumnDeclaring.ifNull(right: T?): FunctionExpression { + return this.ifNull(wrapArgument(right)) +} + +/** + * SQLite iif function, translated to `iif(condition, then, otherwise)`. + */ +public fun iif( + condition: ColumnDeclaring, + then: ColumnDeclaring, + otherwise: ColumnDeclaring +): FunctionExpression { + // iif(condition, then, otherwise) + return FunctionExpression( + functionName = "iif", + arguments = listOf(condition, then, otherwise).map { it.asExpression() }, + sqlType = then.sqlType + ) +} + +/** + * SQLite iif function, translated to `iif(condition, then, otherwise)`. + */ +public inline fun iif( + condition: ColumnDeclaring, + then: T, + otherwise: T, + sqlType: SqlType = sqlTypeOf() ?: error("Cannot detect the param's SqlType, please specify manually.") +): FunctionExpression { + // iif(condition, then, otherwise) + return FunctionExpression( + functionName = "iif", + arguments = listOf( + condition.asExpression(), + ArgumentExpression(then, sqlType), + ArgumentExpression(otherwise, sqlType) + ), + sqlType = sqlType + ) +} + +/** + * SQLite instr function, translated to `instr(left, right)`. + */ +public fun ColumnDeclaring.instr(right: ColumnDeclaring): FunctionExpression { + // instr(left, right) + return FunctionExpression( + functionName = "instr", + arguments = listOf(this, right).map { it.asExpression() }, + sqlType = IntSqlType + ) +} + +/** + * SQLite instr function, translated to `instr(left, right)`. + */ +public fun ColumnDeclaring.instr(right: String): FunctionExpression { + return instr(wrapArgument(right)) +} + +/** + * SQLite replace function, translated to `replace(str, oldValue, newValue)`. + */ +public fun ColumnDeclaring.replace(oldValue: String, newValue: String): FunctionExpression { + // replace(str, oldValue, newValue) + return FunctionExpression( + functionName = "replace", + arguments = listOf( + this.asExpression(), + ArgumentExpression(oldValue, VarcharSqlType), + ArgumentExpression(newValue, VarcharSqlType) + ), + sqlType = VarcharSqlType + ) +} + +/** + * SQLite lower function, translated to `lower(str). ` + */ +public fun ColumnDeclaring.toLowerCase(): FunctionExpression { + // lower(str) + return FunctionExpression( + functionName = "lower", + arguments = listOf(this.asExpression()), + sqlType = VarcharSqlType + ) +} + +/** + * SQLite upper function, translated to `upper(str). ` + */ +public fun ColumnDeclaring.toUpperCase(): FunctionExpression { + // upper(str) + return FunctionExpression( + functionName = "upper", + arguments = listOf(this.asExpression()), + sqlType = VarcharSqlType + ) +} + +// endregion From 87c498bded995a3f855f3d200df472024e459c86 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Thu, 10 Feb 2022 08:51:16 +0800 Subject: [PATCH 041/126] feature(SQLite): BulkInsert.kt copy from postgresql --- .../org/ktorm/support/sqlite/BulkInsert.kt | 556 ++++++++++++++++++ .../org/ktorm/support/sqlite/SQLiteDialect.kt | 75 +++ 2 files changed, 631 insertions(+) create mode 100644 ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt new file mode 100644 index 00000000..c6e72e12 --- /dev/null +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt @@ -0,0 +1,556 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ktorm.support.sqlite + +import org.ktorm.database.CachedRowSet +import org.ktorm.database.Database +import org.ktorm.database.asIterable +import org.ktorm.dsl.AssignmentsBuilder +import org.ktorm.dsl.KtormDsl +import org.ktorm.dsl.batchInsert +import org.ktorm.expression.ColumnAssignmentExpression +import org.ktorm.expression.ColumnExpression +import org.ktorm.expression.SqlExpression +import org.ktorm.expression.TableExpression +import org.ktorm.schema.BaseTable +import org.ktorm.schema.Column + +/** + * Bulk insert expression, represents a bulk insert statement in SQLite. + * + * For example: + * + * ```sql + * insert into table (column1, column2) + * values (?, ?), (?, ?), (?, ?)... + * on conflict (...) do update set ...` + * ``` + * + * @property table the table to be inserted. + * @property assignments column assignments of the bulk insert statement. + * @property conflictColumns the index columns on which the conflict may happen. + * @property updateAssignments the updated column assignments while key conflict exists. + * @property returningColumns the returning columns. + */ +public data class BulkInsertExpression( + val table: TableExpression, + val assignments: List>>, + val conflictColumns: List> = emptyList(), + val updateAssignments: List> = emptyList(), + val returningColumns: List> = emptyList(), + override val isLeafNode: Boolean = false, + override val extraProperties: Map = emptyMap() +) : SqlExpression() + +/** + * Bulk insert records to the table and return the effected row count. + * + * The usage is almost the same as [batchInsert], but this function is implemented by generating a special SQL + * using SQLite's bulk insert syntax, instead of based on JDBC batch operations. For this reason, its performance + * is much better than [batchInsert]. + * + * The generated SQL is like: `insert into table (column1, column2) values (?, ?), (?, ?), (?, ?)...`. + * + * Usage: + * + * ```kotlin + * database.bulkInsert(Employees) { + * item { + * set(it.name, "jerry") + * set(it.job, "trainee") + * set(it.managerId, 1) + * set(it.hireDate, LocalDate.now()) + * set(it.salary, 50) + * set(it.departmentId, 1) + * } + * item { + * set(it.name, "linda") + * set(it.job, "assistant") + * set(it.managerId, 3) + * set(it.hireDate, LocalDate.now()) + * set(it.salary, 100) + * set(it.departmentId, 2) + * } + * } + * ``` + * + * @since 3.3.0 + * @param table the table to be inserted. + * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. + * @return the effected row count. + * @see batchInsert + */ +public fun > Database.bulkInsert( + table: T, block: BulkInsertStatementBuilder.(T) -> Unit +): Int { + val builder = BulkInsertStatementBuilder(table).apply { block(table) } + val expression = BulkInsertExpression(table.asExpression(), builder.assignments) + return executeUpdate(expression) +} + +/** + * Bulk insert records to the table and return the specific column's values. + * + * Usage: + * + * ```kotlin + * database.bulkInsertReturning(Employees, Employees.id) { + * item { + * set(it.name, "jerry") + * set(it.job, "trainee") + * set(it.managerId, 1) + * set(it.hireDate, LocalDate.now()) + * set(it.salary, 50) + * set(it.departmentId, 1) + * } + * item { + * set(it.name, "linda") + * set(it.job, "assistant") + * set(it.managerId, 3) + * set(it.hireDate, LocalDate.now()) + * set(it.salary, 100) + * set(it.departmentId, 2) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into table (name, job, manager_id, hire_date, salary, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)... + * returning id + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the column to return + * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. + * @return the returning column's values. + */ +public fun , C : Any> Database.bulkInsertReturning( + table: T, returning: Column, block: BulkInsertStatementBuilder.(T) -> Unit +): List { + val rowSet = bulkInsertReturningRowSet(table, listOf(returning), block) + return rowSet.asIterable().map { row -> returning.sqlType.getResult(row, 1) } +} + +/** + * Bulk insert records to the table and return the specific columns' values. + * + * Usage: + * + * ```kotlin + * database.bulkInsertReturning(Employees, Pair(Employees.id, Employees.job)) { + * item { + * set(it.name, "jerry") + * set(it.job, "trainee") + * set(it.managerId, 1) + * set(it.hireDate, LocalDate.now()) + * set(it.salary, 50) + * set(it.departmentId, 1) + * } + * item { + * set(it.name, "linda") + * set(it.job, "assistant") + * set(it.managerId, 3) + * set(it.hireDate, LocalDate.now()) + * set(it.salary, 100) + * set(it.departmentId, 2) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into table (name, job, manager_id, hire_date, salary, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)... + * returning id, job + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the columns to return + * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. + * @return the returning columns' values. + */ +public fun , C1 : Any, C2 : Any> Database.bulkInsertReturning( + table: T, returning: Pair, Column>, block: BulkInsertStatementBuilder.(T) -> Unit +): List> { + val (c1, c2) = returning + val rowSet = bulkInsertReturningRowSet(table, listOf(c1, c2), block) + return rowSet.asIterable().map { row -> Pair(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2)) } +} + +/** + * Bulk insert records to the table and return the specific columns' values. + * + * Usage: + * + * ```kotlin + * database.bulkInsertReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { + * item { + * set(it.name, "jerry") + * set(it.job, "trainee") + * set(it.managerId, 1) + * set(it.hireDate, LocalDate.now()) + * set(it.salary, 50) + * set(it.departmentId, 1) + * } + * item { + * set(it.name, "linda") + * set(it.job, "assistant") + * set(it.managerId, 3) + * set(it.hireDate, LocalDate.now()) + * set(it.salary, 100) + * set(it.departmentId, 2) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into table (name, job, manager_id, hire_date, salary, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)... + * returning id, job, salary + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the columns to return + * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. + * @return the returning columns' values. + */ +public fun , C1 : Any, C2 : Any, C3 : Any> Database.bulkInsertReturning( + table: T, returning: Triple, Column, Column>, block: BulkInsertStatementBuilder.(T) -> Unit +): List> { + val (c1, c2, c3) = returning + val rowSet = bulkInsertReturningRowSet(table, listOf(c1, c2, c3), block) + return rowSet.asIterable().map { row -> + Triple(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2), c3.sqlType.getResult(row, 3)) + } +} + +/** + * Bulk insert records to the table, returning row set. + */ +private fun > Database.bulkInsertReturningRowSet( + table: T, returning: List>, block: BulkInsertStatementBuilder.(T) -> Unit +): CachedRowSet { + val builder = BulkInsertStatementBuilder(table).apply { block(table) } + + val expression = BulkInsertExpression( + table = table.asExpression(), + assignments = builder.assignments, + returningColumns = returning.map { it.asExpression() } + ) + + val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) + return rowSet +} + +/** + * Bulk insert records to the table, determining if there is a key conflict while inserting each of them, + * and automatically performs updates if any conflict exists. + * + * Usage: + * + * ```kotlin + * database.bulkInsertOrUpdate(Employees) { + * item { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * item { + * set(it.id, 5) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = t_employee.salary + ? + * ``` + * + * @since 3.3.0 + * @param table the table to be inserted. + * @param block the DSL block used to construct the expression. + * @return the effected row count. + */ +public fun > Database.bulkInsertOrUpdate( + table: T, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit +): Int { + val expression = buildBulkInsertOrUpdateExpression(table, returning = emptyList(), block = block) + return executeUpdate(expression) +} + +/** + * Bulk insert records to the table, determining if there is a key conflict while inserting each of them, + * automatically performs updates if any conflict exists, and finally returns the specific column. + * + * Usage: + * + * ```kotlin + * database.bulkInsertOrUpdateReturning(Employees, Employees.id) { + * item { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * item { + * set(it.id, 5) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = t_employee.salary + ? + * returning id + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the column to return + * @param block the DSL block used to construct the expression. + * @return the returning column's values. + */ +public fun , C : Any> Database.bulkInsertOrUpdateReturning( + table: T, returning: Column, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit +): List { + val expression = buildBulkInsertOrUpdateExpression(table, listOf(returning), block) + val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) + return rowSet.asIterable().map { row -> returning.sqlType.getResult(row, 1) } +} + +/** + * Bulk insert records to the table, determining if there is a key conflict while inserting each of them, + * automatically performs updates if any conflict exists, and finally returns the specific columns. + * + * Usage: + * + * ```kotlin + * database.bulkInsertOrUpdateReturning(Employees, Pair(Employees.id, Employees.job)) { + * item { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * item { + * set(it.id, 5) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = t_employee.salary + ? + * returning id, job + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the columns to return + * @param block the DSL block used to construct the expression. + * @return the returning columns' values. + */ +public fun , C1 : Any, C2 : Any> Database.bulkInsertOrUpdateReturning( + table: T, returning: Pair, Column>, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit +): List> { + val (c1, c2) = returning + val expression = buildBulkInsertOrUpdateExpression(table, listOf(c1, c2), block) + val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) + return rowSet.asIterable().map { row -> Pair(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2)) } +} + +/** + * Bulk insert records to the table, determining if there is a key conflict while inserting each of them, + * automatically performs updates if any conflict exists, and finally returns the specific columns. + * + * Usage: + * + * ```kotlin + * database.bulkInsertOrUpdateReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { + * item { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * item { + * set(it.id, 5) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = t_employee.salary + ? + * returning id, job, salary + * ``` + * + * @since 3.4.0 + * @param table the table to be inserted. + * @param returning the columns to return + * @param block the DSL block used to construct the expression. + * @return the returning columns' values. + */ +public fun , C1 : Any, C2 : Any, C3 : Any> Database.bulkInsertOrUpdateReturning( + table: T, + returning: Triple, Column, Column>, + block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit +): List> { + val (c1, c2, c3) = returning + val expression = buildBulkInsertOrUpdateExpression(table, listOf(c1, c2, c3), block) + val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) + return rowSet.asIterable().map { row -> + Triple(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2), c3.sqlType.getResult(row, 3)) + } +} + +/** + * Build a bulk insert or update expression. + */ +private fun > buildBulkInsertOrUpdateExpression( + table: T, returning: List>, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit +): BulkInsertExpression { + val builder = BulkInsertOrUpdateStatementBuilder(table).apply { block(table) } + + val conflictColumns = builder.conflictColumns.ifEmpty { table.primaryKeys } + if (conflictColumns.isEmpty()) { + val msg = "" + + "Table '$table' doesn't have a primary key, " + + "you must specify the conflict columns when calling onConflict(col) { .. }" + throw IllegalStateException(msg) + } + + if (!builder.doNothing && builder.updateAssignments.isEmpty()) { + val msg = "" + + "Cannot leave the onConflict clause empty! " + + "If you desire no update action at all please explicitly call `doNothing()`" + throw IllegalStateException(msg) + } + + return BulkInsertExpression( + table = table.asExpression(), + assignments = builder.assignments, + conflictColumns = conflictColumns.map { it.asExpression() }, + updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments, + returningColumns = returning.map { it.asExpression() } + ) +} + +/** + * DSL builder for bulk insert statements. + */ +@KtormDsl +public open class BulkInsertStatementBuilder>(internal val table: T) { + internal val assignments = ArrayList>>() + + /** + * Add the assignments of a new row to the bulk insert. + */ + public fun item(block: AssignmentsBuilder.() -> Unit) { + val builder = SQLiteAssignmentsBuilder().apply(block) + + if (assignments.isEmpty() + || assignments[0].map { it.column.name } == builder.assignments.map { it.column.name } + ) { + assignments += builder.assignments + } else { + throw IllegalArgumentException("Every item in a batch operation must be the same.") + } + } +} + +/** + * DSL builder for bulk insert or update statements. + */ +@KtormDsl +public class BulkInsertOrUpdateStatementBuilder>(table: T) : BulkInsertStatementBuilder(table) { + internal val conflictColumns = ArrayList>() + internal val updateAssignments = ArrayList>() + internal var doNothing: Boolean = false + + /** + * Specify the update assignments while any key conflict exists. + */ + public fun onConflict(vararg columns: Column<*>, block: InsertOrUpdateOnConflictClauseBuilder.() -> Unit) { + val builder = InsertOrUpdateOnConflictClauseBuilder().apply(block) + this.conflictColumns += columns + this.updateAssignments += builder.assignments + this.doNothing = builder.doNothing + } +} diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index 102f0221..36e3584a 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -64,6 +64,7 @@ public open class SQLiteFormatter( override fun visit(expr: SqlExpression): SqlExpression { val result = when (expr) { is InsertOrUpdateExpression -> visitInsertOrUpdate(expr) + is BulkInsertExpression -> visitBulkInsert(expr) else -> super.visit(expr) } @@ -99,6 +100,35 @@ public open class SQLiteFormatter( return expr } + + protected open fun visitBulkInsert(expr: BulkInsertExpression): BulkInsertExpression { + writeKeyword("insert into ") + visitTable(expr.table) + writeInsertColumnNames(expr.assignments[0].map { it.column }) + writeKeyword("values ") + + for ((i, assignments) in expr.assignments.withIndex()) { + if (i > 0) { + removeLastBlank() + write(", ") + } + writeInsertValues(assignments) + } + + if (expr.conflictColumns.isNotEmpty()) { + writeKeyword("on conflict ") + writeInsertColumnNames(expr.conflictColumns) + + if (expr.updateAssignments.isNotEmpty()) { + writeKeyword("do update set ") + visitColumnAssignments(expr.updateAssignments) + } else { + writeKeyword("do nothing ") + } + } + + return expr + } } /** @@ -111,6 +141,7 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { override fun visit(expr: SqlExpression): SqlExpression { return when (expr) { is InsertOrUpdateExpression -> visitInsertOrUpdate(expr) + is BulkInsertExpression -> visitBulkInsert(expr) else -> super.visit(expr) } } @@ -137,4 +168,48 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { ) } } + + protected open fun visitBulkInsert(expr: BulkInsertExpression): BulkInsertExpression { + val table = expr.table + val assignments = visitBulkInsertAssignments(expr.assignments) + val conflictColumns = visitExpressionList(expr.conflictColumns) + val updateAssignments = visitColumnAssignments(expr.updateAssignments) + val returningColumns = visitExpressionList(expr.returningColumns) + + @Suppress("ComplexCondition") + if (table === expr.table + && assignments === expr.assignments + && conflictColumns === expr.conflictColumns + && updateAssignments === expr.updateAssignments + && returningColumns === expr.returningColumns + ) { + return expr + } else { + return expr.copy( + table = table, + assignments = assignments, + conflictColumns = conflictColumns, + updateAssignments = updateAssignments, + returningColumns = returningColumns + ) + } + } + + protected open fun visitBulkInsertAssignments( + assignments: List>> + ): List>> { + val result = ArrayList>>() + var changed = false + + for (row in assignments) { + val visited = visitColumnAssignments(row) + result += visited + + if (visited !== row) { + changed = true + } + } + + return if (changed) result else assignments + } } From edafba5ffb0fc99e0116b8eb0aefdd52e5911553 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Thu, 10 Feb 2022 09:35:02 +0800 Subject: [PATCH 042/126] fix(SQLite): no need to remove 'table-name' when visitColumn --- .../kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt index e2828ca3..0e720e08 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt @@ -204,12 +204,4 @@ internal object AliasRemover : SQLiteExpressionVisitor() { return expr.copy(tableAlias = null) } } - - override fun visitColumn(expr: ColumnExpression): ColumnExpression { - if (expr.table == null) { - return expr - } else { - return expr.copy(table = null) - } - } } From 6488c8ca5580e1e9c859edd6ce57ed4e62e5ef96 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Thu, 10 Feb 2022 09:48:45 +0800 Subject: [PATCH 043/126] feature(SQLite): BulkInsert.kt change to SQLite dialect --- .../org/ktorm/support/sqlite/BulkInsert.kt | 344 +----------------- .../org/ktorm/support/sqlite/SQLiteDialect.kt | 7 +- .../org/ktorm/support/sqlite/SQLiteTest.kt | 129 +++++++ 3 files changed, 140 insertions(+), 340 deletions(-) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt index c6e72e12..67d0f609 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt @@ -16,9 +16,7 @@ package org.ktorm.support.sqlite -import org.ktorm.database.CachedRowSet import org.ktorm.database.Database -import org.ktorm.database.asIterable import org.ktorm.dsl.AssignmentsBuilder import org.ktorm.dsl.KtormDsl import org.ktorm.dsl.batchInsert @@ -44,14 +42,12 @@ import org.ktorm.schema.Column * @property assignments column assignments of the bulk insert statement. * @property conflictColumns the index columns on which the conflict may happen. * @property updateAssignments the updated column assignments while key conflict exists. - * @property returningColumns the returning columns. */ public data class BulkInsertExpression( val table: TableExpression, val assignments: List>>, val conflictColumns: List> = emptyList(), val updateAssignments: List> = emptyList(), - val returningColumns: List> = emptyList(), override val isLeafNode: Boolean = false, override val extraProperties: Map = emptyMap() ) : SqlExpression() @@ -98,171 +94,12 @@ public fun > Database.bulkInsert( table: T, block: BulkInsertStatementBuilder.(T) -> Unit ): Int { val builder = BulkInsertStatementBuilder(table).apply { block(table) } - val expression = BulkInsertExpression(table.asExpression(), builder.assignments) - return executeUpdate(expression) -} - -/** - * Bulk insert records to the table and return the specific column's values. - * - * Usage: - * - * ```kotlin - * database.bulkInsertReturning(Employees, Employees.id) { - * item { - * set(it.name, "jerry") - * set(it.job, "trainee") - * set(it.managerId, 1) - * set(it.hireDate, LocalDate.now()) - * set(it.salary, 50) - * set(it.departmentId, 1) - * } - * item { - * set(it.name, "linda") - * set(it.job, "assistant") - * set(it.managerId, 3) - * set(it.hireDate, LocalDate.now()) - * set(it.salary, 100) - * set(it.departmentId, 2) - * } - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into table (name, job, manager_id, hire_date, salary, department_id) - * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)... - * returning id - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the column to return - * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. - * @return the returning column's values. - */ -public fun , C : Any> Database.bulkInsertReturning( - table: T, returning: Column, block: BulkInsertStatementBuilder.(T) -> Unit -): List { - val rowSet = bulkInsertReturningRowSet(table, listOf(returning), block) - return rowSet.asIterable().map { row -> returning.sqlType.getResult(row, 1) } -} - -/** - * Bulk insert records to the table and return the specific columns' values. - * - * Usage: - * - * ```kotlin - * database.bulkInsertReturning(Employees, Pair(Employees.id, Employees.job)) { - * item { - * set(it.name, "jerry") - * set(it.job, "trainee") - * set(it.managerId, 1) - * set(it.hireDate, LocalDate.now()) - * set(it.salary, 50) - * set(it.departmentId, 1) - * } - * item { - * set(it.name, "linda") - * set(it.job, "assistant") - * set(it.managerId, 3) - * set(it.hireDate, LocalDate.now()) - * set(it.salary, 100) - * set(it.departmentId, 2) - * } - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into table (name, job, manager_id, hire_date, salary, department_id) - * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)... - * returning id, job - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the columns to return - * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. - * @return the returning columns' values. - */ -public fun , C1 : Any, C2 : Any> Database.bulkInsertReturning( - table: T, returning: Pair, Column>, block: BulkInsertStatementBuilder.(T) -> Unit -): List> { - val (c1, c2) = returning - val rowSet = bulkInsertReturningRowSet(table, listOf(c1, c2), block) - return rowSet.asIterable().map { row -> Pair(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2)) } -} - -/** - * Bulk insert records to the table and return the specific columns' values. - * - * Usage: - * - * ```kotlin - * database.bulkInsertReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { - * item { - * set(it.name, "jerry") - * set(it.job, "trainee") - * set(it.managerId, 1) - * set(it.hireDate, LocalDate.now()) - * set(it.salary, 50) - * set(it.departmentId, 1) - * } - * item { - * set(it.name, "linda") - * set(it.job, "assistant") - * set(it.managerId, 3) - * set(it.hireDate, LocalDate.now()) - * set(it.salary, 100) - * set(it.departmentId, 2) - * } - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into table (name, job, manager_id, hire_date, salary, department_id) - * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)... - * returning id, job, salary - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the columns to return - * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. - * @return the returning columns' values. - */ -public fun , C1 : Any, C2 : Any, C3 : Any> Database.bulkInsertReturning( - table: T, returning: Triple, Column, Column>, block: BulkInsertStatementBuilder.(T) -> Unit -): List> { - val (c1, c2, c3) = returning - val rowSet = bulkInsertReturningRowSet(table, listOf(c1, c2, c3), block) - return rowSet.asIterable().map { row -> - Triple(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2), c3.sqlType.getResult(row, 3)) - } -} -/** - * Bulk insert records to the table, returning row set. - */ -private fun > Database.bulkInsertReturningRowSet( - table: T, returning: List>, block: BulkInsertStatementBuilder.(T) -> Unit -): CachedRowSet { - val builder = BulkInsertStatementBuilder(table).apply { block(table) } - - val expression = BulkInsertExpression( - table = table.asExpression(), - assignments = builder.assignments, - returningColumns = returning.map { it.asExpression() } + val expression = AliasRemover.visit( + BulkInsertExpression(table.asExpression(), builder.assignments) ) - val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) - return rowSet + return executeUpdate(expression) } /** @@ -311,180 +148,18 @@ private fun > Database.bulkInsertReturningRowSet( public fun > Database.bulkInsertOrUpdate( table: T, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit ): Int { - val expression = buildBulkInsertOrUpdateExpression(table, returning = emptyList(), block = block) - return executeUpdate(expression) -} - -/** - * Bulk insert records to the table, determining if there is a key conflict while inserting each of them, - * automatically performs updates if any conflict exists, and finally returns the specific column. - * - * Usage: - * - * ```kotlin - * database.bulkInsertOrUpdateReturning(Employees, Employees.id) { - * item { - * set(it.id, 1) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * item { - * set(it.id, 5) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * onConflict { - * set(it.salary, it.salary + 900) - * } - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) - * on conflict (id) do update set salary = t_employee.salary + ? - * returning id - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the column to return - * @param block the DSL block used to construct the expression. - * @return the returning column's values. - */ -public fun , C : Any> Database.bulkInsertOrUpdateReturning( - table: T, returning: Column, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit -): List { - val expression = buildBulkInsertOrUpdateExpression(table, listOf(returning), block) - val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) - return rowSet.asIterable().map { row -> returning.sqlType.getResult(row, 1) } -} - -/** - * Bulk insert records to the table, determining if there is a key conflict while inserting each of them, - * automatically performs updates if any conflict exists, and finally returns the specific columns. - * - * Usage: - * - * ```kotlin - * database.bulkInsertOrUpdateReturning(Employees, Pair(Employees.id, Employees.job)) { - * item { - * set(it.id, 1) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * item { - * set(it.id, 5) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * onConflict { - * set(it.salary, it.salary + 900) - * } - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) - * on conflict (id) do update set salary = t_employee.salary + ? - * returning id, job - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the columns to return - * @param block the DSL block used to construct the expression. - * @return the returning columns' values. - */ -public fun , C1 : Any, C2 : Any> Database.bulkInsertOrUpdateReturning( - table: T, returning: Pair, Column>, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit -): List> { - val (c1, c2) = returning - val expression = buildBulkInsertOrUpdateExpression(table, listOf(c1, c2), block) - val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) - return rowSet.asIterable().map { row -> Pair(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2)) } -} + val expression = AliasRemover.visit( + buildBulkInsertOrUpdateExpression(table, block = block) + ) -/** - * Bulk insert records to the table, determining if there is a key conflict while inserting each of them, - * automatically performs updates if any conflict exists, and finally returns the specific columns. - * - * Usage: - * - * ```kotlin - * database.bulkInsertOrUpdateReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { - * item { - * set(it.id, 1) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * item { - * set(it.id, 5) - * set(it.name, "vince") - * set(it.job, "engineer") - * set(it.salary, 1000) - * set(it.hireDate, LocalDate.now()) - * set(it.departmentId, 1) - * } - * onConflict { - * set(it.salary, it.salary + 900) - * } - * } - * ``` - * - * Generated SQL: - * - * ```sql - * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) - * on conflict (id) do update set salary = t_employee.salary + ? - * returning id, job, salary - * ``` - * - * @since 3.4.0 - * @param table the table to be inserted. - * @param returning the columns to return - * @param block the DSL block used to construct the expression. - * @return the returning columns' values. - */ -public fun , C1 : Any, C2 : Any, C3 : Any> Database.bulkInsertOrUpdateReturning( - table: T, - returning: Triple, Column, Column>, - block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit -): List> { - val (c1, c2, c3) = returning - val expression = buildBulkInsertOrUpdateExpression(table, listOf(c1, c2, c3), block) - val (_, rowSet) = executeUpdateAndRetrieveKeys(expression) - return rowSet.asIterable().map { row -> - Triple(c1.sqlType.getResult(row, 1), c2.sqlType.getResult(row, 2), c3.sqlType.getResult(row, 3)) - } + return executeUpdate(expression) } /** * Build a bulk insert or update expression. */ private fun > buildBulkInsertOrUpdateExpression( - table: T, returning: List>, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit + table: T, block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit ): BulkInsertExpression { val builder = BulkInsertOrUpdateStatementBuilder(table).apply { block(table) } @@ -507,8 +182,7 @@ private fun > buildBulkInsertOrUpdateExpression( table = table.asExpression(), assignments = builder.assignments, conflictColumns = conflictColumns.map { it.asExpression() }, - updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments, - returningColumns = returning.map { it.asExpression() } + updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments ) } diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index 36e3584a..87b03b85 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -170,18 +170,16 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { } protected open fun visitBulkInsert(expr: BulkInsertExpression): BulkInsertExpression { - val table = expr.table + val table = visitTable(expr.table) val assignments = visitBulkInsertAssignments(expr.assignments) val conflictColumns = visitExpressionList(expr.conflictColumns) val updateAssignments = visitColumnAssignments(expr.updateAssignments) - val returningColumns = visitExpressionList(expr.returningColumns) @Suppress("ComplexCondition") if (table === expr.table && assignments === expr.assignments && conflictColumns === expr.conflictColumns && updateAssignments === expr.updateAssignments - && returningColumns === expr.returningColumns ) { return expr } else { @@ -189,8 +187,7 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { table = table, assignments = assignments, conflictColumns = conflictColumns, - updateAssignments = updateAssignments, - returningColumns = returningColumns + updateAssignments = updateAssignments ) } } diff --git a/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt b/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt index 75f14904..b4d83ee4 100644 --- a/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt +++ b/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt @@ -107,6 +107,135 @@ class SQLiteTest : BaseTest() { assert(database.employees.find { it.id eq 5 }!!.salary == 1000L) } + @Test + fun testInsertOrUpdate1() { + database.insertOrUpdate(Employees) { + set(it.id, 1) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + onConflict { + setExcluded(it.salary) + } + } + database.insertOrUpdate(Employees.aliased("t")) { + set(it.id, 5) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + onConflict(it.id) { + set(it.salary, it.salary + 1000) + } + } + + assert(database.employees.find { it.id eq 1 }!!.salary == 1000L) + assert(database.employees.find { it.id eq 5 }!!.salary == 1000L) + } + + @Test + fun testBulkInsert() { + database.bulkInsert(Employees.aliased("t")) { + item { + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + } + item { + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + } + } + + assert(database.employees.count() == 6) + } + + @Test + fun testBulkInsertOrUpdate() { + database.bulkInsertOrUpdate(Employees.aliased("t")) { + item { + set(it.id, 1) + set(it.name, "vince") + set(it.job, "trainee") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 2) + } + item { + set(it.id, 5) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 2) + } + onConflict(it.id) { + set(it.job, it.job) + set(it.departmentId, excluded(it.departmentId)) + set(it.salary, it.salary + 1000) + } + } + + database.employees.find { it.id eq 1 }!!.let { + assert(it.job == "engineer") + assert(it.department.id == 2) + assert(it.salary == 1100L) + } + + database.employees.find { it.id eq 5 }!!.let { + assert(it.job == "engineer") + assert(it.department.id == 2) + assert(it.salary == 1000L) + } + } + + @Test + fun testBulkInsertOrUpdate1() { + val bulkInsertWithUpdate = { ignoreErrors: Boolean -> + database.bulkInsertOrUpdate(Employees.aliased("t")) { + item { + set(it.id, 5) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + } + item { + set(it.id, 6) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + } + onConflict { + if (ignoreErrors) doNothing() else set(it.salary, it.salary + 900) + } + } + } + + bulkInsertWithUpdate(false) + assert(database.employees.find { it.id eq 5 }!!.salary == 1000L) + assert(database.employees.find { it.id eq 6 }!!.salary == 1000L) + + bulkInsertWithUpdate(false) + assert(database.employees.find { it.id eq 5 }!!.salary == 1900L) + assert(database.employees.find { it.id eq 6 }!!.salary == 1900L) + + bulkInsertWithUpdate(true) + assert(database.employees.find { it.id eq 5 }!!.salary == 1900L) + assert(database.employees.find { it.id eq 6 }!!.salary == 1900L) + } + @Test fun testLimit() { val query = database.from(Employees).select().orderBy(Employees.id.desc()).limit(0, 2) From edf0f296f428eafcf2d10d190702841d1c9f3e9b Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Thu, 10 Feb 2022 10:18:50 +0800 Subject: [PATCH 044/126] feature(SQLite): WHERE clause at the end of the DO UPDATE --- .../org/ktorm/support/sqlite/BulkInsert.kt | 12 ++-- .../ktorm/support/sqlite/InsertOrUpdate.kt | 15 +++-- .../org/ktorm/support/sqlite/SQLiteDialect.kt | 20 +++++- .../org/ktorm/support/sqlite/SQLiteTest.kt | 66 +++++++++++++++++++ 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt index 67d0f609..bf4c6ceb 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt @@ -20,12 +20,10 @@ import org.ktorm.database.Database import org.ktorm.dsl.AssignmentsBuilder import org.ktorm.dsl.KtormDsl import org.ktorm.dsl.batchInsert -import org.ktorm.expression.ColumnAssignmentExpression -import org.ktorm.expression.ColumnExpression -import org.ktorm.expression.SqlExpression -import org.ktorm.expression.TableExpression +import org.ktorm.expression.* import org.ktorm.schema.BaseTable import org.ktorm.schema.Column +import org.ktorm.schema.ColumnDeclaring /** * Bulk insert expression, represents a bulk insert statement in SQLite. @@ -48,6 +46,7 @@ public data class BulkInsertExpression( val assignments: List>>, val conflictColumns: List> = emptyList(), val updateAssignments: List> = emptyList(), + val where: ScalarExpression? = null, override val isLeafNode: Boolean = false, override val extraProperties: Map = emptyMap() ) : SqlExpression() @@ -182,7 +181,8 @@ private fun > buildBulkInsertOrUpdateExpression( table = table.asExpression(), assignments = builder.assignments, conflictColumns = conflictColumns.map { it.asExpression() }, - updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments + updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments, + where = builder.where?.asExpression() ) } @@ -216,6 +216,7 @@ public open class BulkInsertStatementBuilder>(internal val tabl public class BulkInsertOrUpdateStatementBuilder>(table: T) : BulkInsertStatementBuilder(table) { internal val conflictColumns = ArrayList>() internal val updateAssignments = ArrayList>() + internal var where: ColumnDeclaring? = null internal var doNothing: Boolean = false /** @@ -225,6 +226,7 @@ public class BulkInsertOrUpdateStatementBuilder>(table: T) : Bu val builder = InsertOrUpdateOnConflictClauseBuilder().apply(block) this.conflictColumns += columns this.updateAssignments += builder.assignments + this.where = builder.where this.doNothing = builder.doNothing } } diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt index 0e720e08..68859d86 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt @@ -19,12 +19,10 @@ package org.ktorm.support.sqlite import org.ktorm.database.Database import org.ktorm.dsl.AssignmentsBuilder import org.ktorm.dsl.KtormDsl -import org.ktorm.expression.ColumnAssignmentExpression -import org.ktorm.expression.ColumnExpression -import org.ktorm.expression.SqlExpression -import org.ktorm.expression.TableExpression +import org.ktorm.expression.* import org.ktorm.schema.BaseTable import org.ktorm.schema.Column +import org.ktorm.schema.ColumnDeclaring /** * Insert or update expression, represents an insert statement with an @@ -40,6 +38,7 @@ public data class InsertOrUpdateExpression( val assignments: List>, val conflictColumns: List> = emptyList(), val updateAssignments: List> = emptyList(), + val where: ScalarExpression? = null, override val isLeafNode: Boolean = false, override val extraProperties: Map = emptyMap() ) : SqlExpression() @@ -111,6 +110,7 @@ private fun > buildInsertOrUpdateExpression( table = table.asExpression(), assignments = builder.assignments, conflictColumns = conflictColumns.map { it.asExpression() }, + where = builder.where?.asExpression(), updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments ) } @@ -134,6 +134,7 @@ public open class SQLiteAssignmentsBuilder : AssignmentsBuilder() { public class InsertOrUpdateStatementBuilder : SQLiteAssignmentsBuilder() { internal val conflictColumns = ArrayList>() internal val updateAssignments = ArrayList>() + internal var where: ColumnDeclaring? = null internal var doNothing = false /** @@ -154,6 +155,7 @@ public class InsertOrUpdateStatementBuilder : SQLiteAssignmentsBuilder() { val builder = InsertOrUpdateOnConflictClauseBuilder().apply(block) this.conflictColumns += columns this.updateAssignments += builder.assignments + this.where = builder.where this.doNothing = builder.doNothing } } @@ -163,8 +165,13 @@ public class InsertOrUpdateStatementBuilder : SQLiteAssignmentsBuilder() { */ @KtormDsl public class InsertOrUpdateOnConflictClauseBuilder : SQLiteAssignmentsBuilder() { + internal var where: ColumnDeclaring? = null internal var doNothing = false + public fun where(block: () -> ColumnDeclaring) { + this.where = block() + } + /** * Explicitly tells ktorm to ignore any on-conflict errors and continue insertion. */ diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index 87b03b85..1dd41999 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -93,6 +93,11 @@ public open class SQLiteFormatter( if (expr.updateAssignments.isNotEmpty()) { writeKeyword("do update set ") visitColumnAssignments(expr.updateAssignments) + + if (expr.where != null) { + writeKeyword("where ") + visit(expr.where) + } } else { writeKeyword("do nothing ") } @@ -122,6 +127,11 @@ public open class SQLiteFormatter( if (expr.updateAssignments.isNotEmpty()) { writeKeyword("do update set ") visitColumnAssignments(expr.updateAssignments) + + if (expr.where != null) { + writeKeyword("where ") + visit(expr.where) + } } else { writeKeyword("do nothing ") } @@ -151,12 +161,14 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { val assignments = visitColumnAssignments(expr.assignments) val conflictColumns = visitExpressionList(expr.conflictColumns) val updateAssignments = visitColumnAssignments(expr.updateAssignments) + val where = expr.where?.let { visitScalar(it) } @Suppress("ComplexCondition") if (table === expr.table && assignments === expr.assignments && conflictColumns === expr.conflictColumns && updateAssignments === expr.updateAssignments + && where === expr.where ) { return expr } else { @@ -164,7 +176,8 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { table = table, assignments = assignments, conflictColumns = conflictColumns, - updateAssignments = updateAssignments + updateAssignments = updateAssignments, + where = where ) } } @@ -174,12 +187,14 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { val assignments = visitBulkInsertAssignments(expr.assignments) val conflictColumns = visitExpressionList(expr.conflictColumns) val updateAssignments = visitColumnAssignments(expr.updateAssignments) + val where = expr.where?.let { visitScalar(it) } @Suppress("ComplexCondition") if (table === expr.table && assignments === expr.assignments && conflictColumns === expr.conflictColumns && updateAssignments === expr.updateAssignments + && where === expr.where ) { return expr } else { @@ -187,7 +202,8 @@ public open class SQLiteExpressionVisitor : SqlExpressionVisitor() { table = table, assignments = assignments, conflictColumns = conflictColumns, - updateAssignments = updateAssignments + updateAssignments = updateAssignments, + where = where ) } } diff --git a/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt b/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt index b4d83ee4..d15f10fe 100644 --- a/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt +++ b/ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/SQLiteTest.kt @@ -136,6 +136,41 @@ class SQLiteTest : BaseTest() { assert(database.employees.find { it.id eq 5 }!!.salary == 1000L) } + @Test + fun testInsertOrUpdateOnConflictWhere() { + database.insertOrUpdate(Employees.aliased("t")) { + set(it.id, 1) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + onConflict { + set(it.salary, it.salary + excluded(it.salary)) + where { + it.salary less 1000 + } + } + } + assert(database.employees.find { it.id eq 1 }!!.salary == 1100L) + database.insertOrUpdate(Employees.aliased("t")) { + set(it.id, 1) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + onConflict(it.id) { + set(it.salary, it.salary + excluded(it.salary)) + where { + it.salary less 1000 + } + } + } + + assert(database.employees.find { it.id eq 1 }!!.salary == 1100L) + } + @Test fun testBulkInsert() { database.bulkInsert(Employees.aliased("t")) { @@ -197,6 +232,37 @@ class SQLiteTest : BaseTest() { } } + @Test + fun testBulkInsertOrUpdateOnConflictWhere() { + database.bulkInsertOrUpdate(Employees.aliased("t")) { + item { + set(it.id, 1) + set(it.name, "vince") + set(it.job, "engineer") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + } + item { + set(it.id, 2) + set(it.name, "marry") + set(it.job, "trainee") + set(it.salary, 1000) + set(it.hireDate, LocalDate.now()) + set(it.departmentId, 1) + } + onConflict(it.id) { + set(it.salary, it.salary + 1000) + where { + it.job eq "engineer" + } + } + } + + assert(database.employees.find { it.id eq 1 }!!.salary == 1100L) + assert(database.employees.find { it.id eq 2 }!!.salary == 50L) + } + @Test fun testBulkInsertOrUpdate1() { val bulkInsertWithUpdate = { ignoreErrors: Boolean -> From 169bb6f6506c9d7e2457611cd766895b6c021e99 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Thu, 10 Feb 2022 10:35:54 +0800 Subject: [PATCH 045/126] feature(SQLite): Adding Global.kt Adding Global.kt remove '@since' notes --- .../org/ktorm/support/sqlite/BulkInsert.kt | 2 - .../kotlin/org/ktorm/support/sqlite/Global.kt | 159 ++++++++++++++++++ .../ktorm/support/sqlite/InsertOrUpdate.kt | 1 - 3 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/Global.kt diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt index bf4c6ceb..16b88da4 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt @@ -83,7 +83,6 @@ public data class BulkInsertExpression( * } * ``` * - * @since 3.3.0 * @param table the table to be inserted. * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. * @return the effected row count. @@ -139,7 +138,6 @@ public fun > Database.bulkInsert( * on conflict (id) do update set salary = t_employee.salary + ? * ``` * - * @since 3.3.0 * @param table the table to be inserted. * @param block the DSL block used to construct the expression. * @return the effected row count. diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/Global.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/Global.kt new file mode 100644 index 00000000..a1f601d7 --- /dev/null +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/Global.kt @@ -0,0 +1,159 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ktorm.support.sqlite + +import org.ktorm.database.Database +import org.ktorm.dsl.batchInsert +import org.ktorm.schema.BaseTable +import java.lang.reflect.InvocationTargetException + +/** + * Obtain the global database instance via reflection, throwing an exception if ktorm-global is not + * available in the classpath. + */ +@Suppress("SwallowedException") +internal val Database.Companion.global: Database get() { + try { + val cls = Class.forName("org.ktorm.global.GlobalKt") + val method = cls.getMethod("getGlobal", Database.Companion::class.java) + return method.invoke(null, Database.Companion) as Database + } catch (e: ClassNotFoundException) { + throw IllegalStateException("Cannot detect the global database object, please add ktorm-global to classpath", e) + } catch (e: InvocationTargetException) { + throw e.targetException + } +} + +/** + * Insert a record to the table, determining if there is a key conflict while it's being inserted, and automatically + * performs an update if any conflict exists. + * + * Usage: + * + * ```kotlin + * Employees.insertOrUpdate { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) values (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = salary + ? + * ``` + * + * @param block the DSL block used to construct the expression. + * @return the effected row count. + */ +public fun > T.insertOrUpdate(block: InsertOrUpdateStatementBuilder.(T) -> Unit): Int { + return Database.global.insertOrUpdate(this, block) +} + +/** + * Construct a bulk insert expression in the given closure, then execute it and return the effected row count. + * + * The usage is almost the same as [batchInsert], but this function is implemented by generating a special SQL + * using SQLite's bulk insert syntax, instead of based on JDBC batch operations. For this reason, its performance + * is much better than [batchInsert]. + * + * The generated SQL is like: `insert into table (column1, column2) values (?, ?), (?, ?), (?, ?)...`. + * + * Usage: + * + * ```kotlin + * Employees.bulkInsert { + * item { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * item { + * set(it.id, 5) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * } + * ``` + * + * @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression. + * @return the effected row count. + * @see batchInsert + */ +public fun > T.bulkInsert(block: BulkInsertStatementBuilder.(T) -> Unit): Int { + return Database.global.bulkInsert(this, block) +} + +/** + * Bulk insert records to the table, determining if there is a key conflict while inserting each of them, + * and automatically performs updates if any conflict exists. + * + * Usage: + * + * ```kotlin + * Employees.bulkInsertOrUpdate { + * item { + * set(it.id, 1) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * item { + * set(it.id, 5) + * set(it.name, "vince") + * set(it.job, "engineer") + * set(it.salary, 1000) + * set(it.hireDate, LocalDate.now()) + * set(it.departmentId, 1) + * } + * onConflict { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (id) do update set salary = salary + ? + * ``` + * + * @param block the DSL block used to construct the expression. + * @return the effected row count. + * @see bulkInsert + */ +public fun > T.bulkInsertOrUpdate(block: BulkInsertOrUpdateStatementBuilder.(T) -> Unit): Int { + return Database.global.bulkInsertOrUpdate(this, block) +} diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt index 68859d86..d981c344 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt @@ -71,7 +71,6 @@ public data class InsertOrUpdateExpression( * on conflict (id) do update set salary = t_employee.salary + ? * ``` * - * @since 2.7 * @param table the table to be inserted. * @param block the DSL block used to construct the expression. * @return the effected row count. From b5e849554de01a8bbaa5374554d6d67f23f1a6d0 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Thu, 10 Feb 2022 10:41:06 +0800 Subject: [PATCH 046/126] docs(SQLite): add docs on function 'where' --- .../src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt index d981c344..98660b4d 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt @@ -167,6 +167,9 @@ public class InsertOrUpdateOnConflictClauseBuilder : SQLiteAssignmentsBuilder() internal var where: ColumnDeclaring? = null internal var doNothing = false + /** + * Specify the where clause for this update statement. + */ public fun where(block: () -> ColumnDeclaring) { this.where = block() } From 6d1423893d67c9e35e541166567c6967a0f656ee Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Thu, 10 Feb 2022 17:32:07 +0800 Subject: [PATCH 047/126] build(build.gradle): Add name to list of developers --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 67f86634..40f32d31 100644 --- a/build.gradle +++ b/build.gradle @@ -168,6 +168,11 @@ subprojects { project -> name = "Eric Fenderbosch" email = "eric@fender.net" } + developer { + id = "2938137849" + name = "ccr" + email = "2938137849@qq.com" + } } } } From 7190535a07607dc01f013d0dc5863133b6ddd136 Mon Sep 17 00:00:00 2001 From: 2938137849 <2938137849@qq.com> Date: Tue, 15 Feb 2022 16:20:13 +0800 Subject: [PATCH 048/126] fix(SQLite): UNION clause can only use `visitQuery` --- .../org/ktorm/support/sqlite/SQLiteDialect.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index 1dd41999..5f6174cd 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -79,6 +79,34 @@ public open class SQLiteFormatter( _parameters += ArgumentExpression(expr.limit ?: Int.MAX_VALUE, IntSqlType) } + override fun visitUnion(expr: UnionExpression): UnionExpression { + when (expr.left) { + is SelectExpression -> visitQuery(expr.left) + is UnionExpression -> visitUnion(expr.left as UnionExpression) + } + + if (expr.isUnionAll) { + writeKeyword("union all ") + } else { + writeKeyword("union ") + } + + when (expr.right) { + is SelectExpression -> visitQuery(expr.right) + is UnionExpression -> visitUnion(expr.right as UnionExpression) + } + + if (expr.orderBy.isNotEmpty()) { + newLine(Indentation.SAME) + writeKeyword("order by ") + visitOrderByList(expr.orderBy) + } + if (expr.offset != null || expr.limit != null) { + writePagination(expr) + } + return expr + } + protected open fun visitInsertOrUpdate(expr: InsertOrUpdateExpression): InsertOrUpdateExpression { writeKeyword("insert into ") visitTable(expr.table) From eccb2951224ffdff956eb2a1afa9489bbb89bc40 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 6 May 2022 21:55:57 +0800 Subject: [PATCH 049/126] fix code style --- ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt index fa6a416a..e9924191 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt @@ -227,9 +227,7 @@ public inline fun Query.whereWithConditions(block: (MutableList 1) { - conditions = conditions.chunked(2) { chunk -> - if (chunk.size == 2) chunk[0] and chunk[1] else chunk[0] - } + conditions = conditions.chunked(2) { chunk -> if (chunk.size == 2) chunk[0] and chunk[1] else chunk[0] } } return this.where { conditions[0] } From 1c516112eb9b45aa681ca6321d743ef060d1ba55 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 6 May 2022 22:16:43 +0800 Subject: [PATCH 050/126] fix code style --- ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt index e9924191..19c56397 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt @@ -247,9 +247,7 @@ public inline fun Query.whereWithOrConditions(block: (MutableList 1) { - conditions = conditions.chunked(2) { chunk -> - if (chunk.size == 2) chunk[0] or chunk[1] else chunk[0] - } + conditions = conditions.chunked(2) { chunk -> if (chunk.size == 2) chunk[0] or chunk[1] else chunk[0] } } return this.where { conditions[0] } From 65501c4248cd4630a9a01375e2daea2c11964502 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 6 May 2022 22:54:47 +0800 Subject: [PATCH 051/126] rm codacy badge --- README.md | 3 --- README_cn.md | 3 --- README_jp.md | 3 --- 3 files changed, 9 deletions(-) diff --git a/README.md b/README.md index 661b2488..284783ca 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,6 @@ Apache License 2 - - Codacy Badge - Awesome Kotlin Badge diff --git a/README_cn.md b/README_cn.md index 5b78d1fd..9dceca56 100644 --- a/README_cn.md +++ b/README_cn.md @@ -11,9 +11,6 @@ Apache License 2 - - Codacy Badge - Awesome Kotlin Badge diff --git a/README_jp.md b/README_jp.md index 5e82e6e4..034c7b6b 100644 --- a/README_jp.md +++ b/README_jp.md @@ -11,9 +11,6 @@ Apache License 2 - - Codacy Badge - Awesome Kotlin Badge From 03f970249da75d2994a74c70d3d891032c77f501 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 13 May 2022 22:29:49 +0800 Subject: [PATCH 052/126] config github actions build --- .github/workflows/build.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..574d358d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: Build Ktorm + +on: + - push + - pull_request + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: 8 + + - name: Assemble the Project + uses: gradle/gradle-build-action@v2 + with: + arguments: assemble + + - name: Run Tests + uses: gradle/gradle-build-action@v2 + with: + arguments: check \ No newline at end of file From 1f7027f148f38313e091d55d6c931670b725067d Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 13 May 2022 23:09:47 +0800 Subject: [PATCH 053/126] rm travis config --- .github/workflows/build.yml | 2 -- .travis.yml | 33 --------------------------------- 2 files changed, 35 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 574d358d..1376c6cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,3 @@ -name: Build Ktorm - on: - push - pull_request diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index dedbda68..00000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ - -language: java - -env: - - GRADLE_OPTS="-Xms2048m -Xmx2048m" - -#services: -# - mysql -# - postgresql -# -#before_install: -# - mysql -e "create database ktorm;" -# - psql -c "create database ktorm;" -U postgres - -after_success: - - ./gradlew publishDistPublicationToSnapshotRepository - -before_cache: - - rm -f "${HOME}/.gradle/caches/modules-2/modules-2.lock" - - rm -rf "${HOME}/.gradle/caches/*/plugin-resolution/" - - rm -rf "${HOME}/.gradle/caches/*/fileHashes/" - -cache: - directories: - - "${HOME}/.gradle/caches/" - - "${HOME}/.gradle/wrapper/" - -notifications: - email: - recipients: - - me@liuwj.me - on_success: change - on_failure: always From 5c113d183f9589337442318b1045fdf535811ef5 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 11:01:58 +0800 Subject: [PATCH 054/126] test publish --- .github/workflows/build.yml | 37 +++++++++++++++++++++++++++---------- build.gradle.kts | 2 +- ktorm.version | 1 + 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 ktorm.version diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1376c6cb..b7babfc5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,11 @@ +name: build + on: - - push - - pull_request + push: + pull_request: + types: [opened, synchronize, reopened] + release: + types: [published] jobs: build: @@ -15,12 +20,24 @@ jobs: distribution: temurin java-version: 8 - - name: Assemble the Project - uses: gradle/gradle-build-action@v2 - with: - arguments: assemble +# - name: Assemble the Project +# uses: gradle/gradle-build-action@v2 +# with: +# arguments: assemble +# +# - name: Run Tests +# uses: gradle/gradle-build-action@v2 +# with: +# arguments: check - - name: Run Tests - uses: gradle/gradle-build-action@v2 - with: - arguments: check \ No newline at end of file + - name: Publish Artifacts + run: | + if [ cat("ktorm.version") =~ "SNAPSHOT" ] ; then + echo "Publish snapshot artifacts...' + else + if [ $GITHUB_EVENT_NAME == "release" ] ; then + echo "Publish release artifacts...' + else + echo "Skip release publication because this is not a release event" + fi + fi \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 3733e0cc..6813856e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ group = "org.ktorm" -version = "3.5.0-SNAPSHOT" +version = file("ktorm.version").readText() task("printClasspath") { doLast { diff --git a/ktorm.version b/ktorm.version new file mode 100644 index 00000000..b9821b82 --- /dev/null +++ b/ktorm.version @@ -0,0 +1 @@ +3.5.0-SNAPSHOT \ No newline at end of file From 745202723d72947d9544d66dd5026f62dc82c22e Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 11:05:20 +0800 Subject: [PATCH 055/126] fix shell syntax error --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7babfc5..1850beec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: - name: Publish Artifacts run: | - if [ cat("ktorm.version") =~ "SNAPSHOT" ] ; then + if [ $(cat "ktorm.version") =~ "SNAPSHOT" ] ; then echo "Publish snapshot artifacts...' else if [ $GITHUB_EVENT_NAME == "release" ] ; then From c8b6093d415c9ef73ffe2397976d2f29e04d55d4 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 11:16:08 +0800 Subject: [PATCH 056/126] fix shell syntax error --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1850beec..01ee817a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,10 +33,10 @@ jobs: - name: Publish Artifacts run: | if [ $(cat "ktorm.version") =~ "SNAPSHOT" ] ; then - echo "Publish snapshot artifacts...' + echo "Publish snapshot artifacts..." else if [ $GITHUB_EVENT_NAME == "release" ] ; then - echo "Publish release artifacts...' + echo "Publish release artifacts..." else echo "Skip release publication because this is not a release event" fi From ad1f85d96fa747376bd3a66b1ff8381fce13799e Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 11:22:54 +0800 Subject: [PATCH 057/126] test --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01ee817a..5a3e1683 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,6 +32,9 @@ jobs: - name: Publish Artifacts run: | + pwd + ls + cat "ktorm.version" if [ $(cat "ktorm.version") =~ "SNAPSHOT" ] ; then echo "Publish snapshot artifacts..." else From f56249f1f74dc50e7c0d475d1864b2d1d7c619b7 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 11:48:02 +0800 Subject: [PATCH 058/126] fix shell syntax error --- .github/workflows/build.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a3e1683..f3e615b0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,13 +32,10 @@ jobs: - name: Publish Artifacts run: | - pwd - ls - cat "ktorm.version" - if [ $(cat "ktorm.version") =~ "SNAPSHOT" ] ; then + if [[ $(cat "ktorm.version") =~ "SNAPSHOT" ]] ; then echo "Publish snapshot artifacts..." else - if [ $GITHUB_EVENT_NAME == "release" ] ; then + if [[ $GITHUB_EVENT_NAME == "release" ]] ; then echo "Publish release artifacts..." else echo "Skip release publication because this is not a release event" From 66a4e6b5b11ecb2c49de255e984bc4ba482a405f Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 11:51:21 +0800 Subject: [PATCH 059/126] test --- ktorm.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm.version b/ktorm.version index b9821b82..e5b82034 100644 --- a/ktorm.version +++ b/ktorm.version @@ -1 +1 @@ -3.5.0-SNAPSHOT \ No newline at end of file +3.5.0 \ No newline at end of file From a6c50e334fb7a6e217a0f147ed44a5539aabb13f Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 12:36:17 +0800 Subject: [PATCH 060/126] test --- ktorm.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm.version b/ktorm.version index e5b82034..b9821b82 100644 --- a/ktorm.version +++ b/ktorm.version @@ -1 +1 @@ -3.5.0 \ No newline at end of file +3.5.0-SNAPSHOT \ No newline at end of file From bb139bc82b40f87b02124c6a917d4a5bfd020eaa Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 14:54:39 +0800 Subject: [PATCH 061/126] setup github actions build --- .github/workflows/build.yml | 29 +++++++++++++++++------------ README.md | 4 ++-- README_cn.md | 4 ++-- README_jp.md | 4 ++-- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3e615b0..2a863618 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,24 +20,29 @@ jobs: distribution: temurin java-version: 8 -# - name: Assemble the Project -# uses: gradle/gradle-build-action@v2 -# with: -# arguments: assemble -# -# - name: Run Tests -# uses: gradle/gradle-build-action@v2 -# with: -# arguments: check + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Assemble the Project + run: ./gradlew assemble + + - name: Run Tests + run: ./gradlew check - name: Publish Artifacts run: | if [[ $(cat "ktorm.version") =~ "SNAPSHOT" ]] ; then - echo "Publish snapshot artifacts..." + ./gradlew publishDistPublicationToSnapshotRepository else if [[ $GITHUB_EVENT_NAME == "release" ]] ; then - echo "Publish release artifacts..." + ./gradlew publishDistPublicationToCentralRepository else echo "Skip release publication because this is not a release event" fi - fi \ No newline at end of file + fi + env: + OSSRH_USER: ${{secrets.OSSRH_USER}} + OSSRH_PASSWORD: ${{secrets.OSSRH_PASSWORD}} + GPG_KEY_ID: ${{secrets.GPG_KEY_ID}} + GPG_PASSWORD: ${{secrets.GPG_PASSWORD}} + GPG_SECRET_KEY: ${{secrets.GPG_SECRET_KEY}} \ No newline at end of file diff --git a/README.md b/README.md index 284783ca..9fff659e 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Ktorm

- - Build Status + + Build Status Maven Central diff --git a/README_cn.md b/README_cn.md index 9dceca56..bf60be3b 100644 --- a/README_cn.md +++ b/README_cn.md @@ -2,8 +2,8 @@ Ktorm

- - Build Status + + Build Status Maven Central diff --git a/README_jp.md b/README_jp.md index 034c7b6b..5db37668 100644 --- a/README_jp.md +++ b/README_jp.md @@ -2,8 +2,8 @@ Ktorm

- - Build Status + + Build Status Maven Central From 2da4d0e25947abe70b74deacee20e9f66badd321 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 16:12:32 +0800 Subject: [PATCH 062/126] upgrade gradle & kotlin version --- buildSrc/build.gradle.kts | 12 ++---------- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 037e8700..69c282ec 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("org.gradle.kotlin.kotlin-dsl") version "2.1.4" + id("org.gradle.kotlin.kotlin-dsl") version "2.1.7" } repositories { @@ -9,14 +9,6 @@ repositories { } dependencies { - api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.0") { - // TODO: Remove the exclusions when this issue fixed: https://youtrack.jetbrains.com/issue/KT-41142 - exclude("org.jetbrains.kotlin", "kotlin-stdlib") - exclude("org.jetbrains.kotlin", "kotlin-stdlib-common") - exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7") - exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8") - exclude("org.jetbrains.kotlin", "kotlin-reflect") - } - + api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.32") api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.17.0-RC2") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d435ce29..92f06b50 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 63c1b9658c1b0f45886141c129e3083b852c8029 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 18:50:23 +0800 Subject: [PATCH 063/126] upgrade detekt version --- buildSrc/build.gradle.kts | 2 +- .../src/main/kotlin/ktorm.module-conventions.gradle.kts | 5 ++--- detekt.yml | 8 ++++---- .../main/kotlin/org/ktorm/expression/SqlExpressions.kt | 1 + ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt | 2 +- ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt | 4 ++-- .../test/kotlin/org/ktorm/support/oracle/OracleTest.kt | 3 +-- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 69c282ec..3404fc04 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,5 +10,5 @@ repositories { dependencies { api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.32") - api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.17.0-RC2") + api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.20.0") } diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index a296b3a0..231885c4 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -17,12 +17,11 @@ dependencies { api(kotlin("stdlib")) api(kotlin("reflect")) testImplementation(kotlin("test-junit")) - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.17.0-RC2") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${detekt.toolVersion}") } detekt { - toolVersion = "1.17.0-RC2" - input = files("src/main/kotlin") + source = files("src/main/kotlin") config = files("${project.rootDir}/detekt.yml") } diff --git a/detekt.yml b/detekt.yml index 4b32c185..05aa15b5 100644 --- a/detekt.yml +++ b/detekt.yml @@ -361,7 +361,7 @@ potential-bugs: active: true LateinitUsage: active: false - excludeAnnotatedProperties: "" + ignoreAnnotated: [] ignoreOnClassesPattern: "" UnconditionalJumpStatementInLoop: active: true @@ -466,10 +466,10 @@ style: active: true UnderscoresInNumericLiterals: active: true - acceptableDecimalLength: 5 + acceptableLength: 5 UnnecessaryAbstractClass: active: true - excludeAnnotatedClasses: "dagger.Module" + ignoreAnnotated: ["dagger.Module"] UnnecessaryApply: active: true UnnecessaryInheritance: @@ -489,7 +489,7 @@ style: allowedNames: "(_|ignored|expected|serialVersionUID)" UseDataClass: active: true - excludeAnnotatedClasses: "" + ignoreAnnotated: [] UtilityClassWithPublicConstructor: active: true VarCouldBeVal: diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt index 2f6f8b5a..6e77a848 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt @@ -88,6 +88,7 @@ public data class CastingExpression( /** * Query source expression, used in the `from` clause of a [SelectExpression]. */ +@Suppress("UnnecessaryAbstractClass") public abstract class QuerySourceExpression : SqlExpression() /** diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt index d4a66fec..a8d5ae5d 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt @@ -48,7 +48,7 @@ import kotlin.reflect.jvm.jvmErasure */ @Suppress("UNCHECKED_CAST") public open class Table>( - tableName: String, + tableName: String, alias: String? = null, catalog: String? = null, schema: String? = null, diff --git a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt index ba97532b..0c6e3759 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt @@ -74,7 +74,7 @@ class QueryTest : BaseTest() { fun testWhereWithOrConditionsNoStackOverflow() { val t = Employees.aliased("t") - val sql = database + val query = database .from(t) .select(t.name) .whereWithOrConditions { where -> @@ -82,9 +82,9 @@ class QueryTest : BaseTest() { where += (t.id eq Random.nextInt()) and (t.departmentId eq Random.nextInt()) } } - .sql // very large SQL doesn't cause stackoverflow + println(query.sql) assert(true) } diff --git a/ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/OracleTest.kt b/ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/OracleTest.kt index b4ef1e8c..407838d4 100644 --- a/ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/OracleTest.kt +++ b/ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/OracleTest.kt @@ -21,7 +21,6 @@ import org.testcontainers.containers.OracleContainer * Created by vince at Aug 01, 2020. */ class OracleTest : BaseTest() { - companion object { const val TOTAL_RECORDS = 4 const val MINUS_ONE = -1 @@ -36,7 +35,7 @@ class OracleTest : BaseTest() { @ClassRule @JvmField val oracle: OracleContainer = OracleContainer("zerda/oracle-database:11.2.0.2-xe") - .withCreateContainerCmdModifier { cmd -> cmd.hostConfig?.withShmSize(1 * 1024 * 1024 * 1024) } + .withCreateContainerCmdModifier { cmd -> cmd.hostConfig?.withShmSize((1 * 1024 * 1024 * 1024).toLong()) } } override fun init() { From 4cfee8d52dc575788ef1a47bb8937600651be78e Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 20:14:42 +0800 Subject: [PATCH 064/126] fix code style --- ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt index a8d5ae5d..d4a66fec 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt @@ -48,7 +48,7 @@ import kotlin.reflect.jvm.jvmErasure */ @Suppress("UNCHECKED_CAST") public open class Table>( - tableName: String, + tableName: String, alias: String? = null, catalog: String? = null, schema: String? = null, From b4f7666038171f2e6534a743c17400fb43f099fa Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 14 May 2022 21:12:12 +0800 Subject: [PATCH 065/126] update source header --- buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts | 2 +- buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts | 2 +- ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt | 2 +- .../src/main/kotlin/org/ktorm/database/CachedRowSetMetadata.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/database/JdbcExtensions.kt | 2 +- .../main/kotlin/org/ktorm/database/JdbcTransactionManager.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/database/Keywords.kt | 2 +- .../org/ktorm/database/SpringManagedTransactionManager.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/database/SqlDialect.kt | 2 +- .../src/main/kotlin/org/ktorm/database/TransactionManager.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/dsl/Aggregation.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/dsl/CountExpression.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/dsl/QueryRowSet.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/dsl/QuerySource.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/entity/EntityGrouping.kt | 2 +- .../src/main/kotlin/org/ktorm/entity/EntityImplementation.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt | 2 +- .../main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt | 2 +- .../src/main/kotlin/org/ktorm/expression/SqlExpressions.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt | 2 +- .../src/main/kotlin/org/ktorm/logging/AndroidLoggerAdapter.kt | 2 +- .../src/main/kotlin/org/ktorm/logging/CommonsLoggerAdapter.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/logging/ConsoleLogger.kt | 2 +- .../src/main/kotlin/org/ktorm/logging/JdkLoggerAdapter.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/logging/Logger.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/logging/NoOpLogger.kt | 2 +- .../src/main/kotlin/org/ktorm/logging/Slf4jLoggerAdapter.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt | 2 +- .../src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/schema/RefCounter.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/schema/SqlType.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/schema/TypeReference.kt | 2 +- ktorm-global/src/main/kotlin/org/ktorm/global/Aggregations.kt | 2 +- ktorm-global/src/main/kotlin/org/ktorm/global/Dml.kt | 2 +- ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt | 2 +- ktorm-global/src/main/kotlin/org/ktorm/global/Global.kt | 2 +- ktorm-global/src/main/kotlin/org/ktorm/global/Query.kt | 2 +- .../src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt | 2 +- .../src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt | 2 +- .../main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt | 2 +- .../src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt | 2 +- ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt | 2 +- ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt | 2 +- .../src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl | 2 +- .../src/test/kotlin/org/ktorm/jackson/JacksonAnnotationTest.kt | 2 +- .../src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt | 2 +- .../src/main/kotlin/org/ktorm/support/mysql/Functions.kt | 2 +- .../src/main/kotlin/org/ktorm/support/mysql/Global.kt | 2 +- .../src/main/kotlin/org/ktorm/support/mysql/InsertOrUpdate.kt | 2 +- .../src/main/kotlin/org/ktorm/support/mysql/Lock.kt | 2 +- .../src/main/kotlin/org/ktorm/support/mysql/MatchAgainst.kt | 2 +- .../src/main/kotlin/org/ktorm/support/mysql/MySqlDialect.kt | 2 +- .../src/main/kotlin/org/ktorm/support/mysql/NaturalJoin.kt | 2 +- .../src/main/kotlin/org/ktorm/support/oracle/OracleDialect.kt | 2 +- .../src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt | 2 +- .../src/main/kotlin/org/ktorm/support/postgresql/Global.kt | 2 +- .../src/main/kotlin/org/ktorm/support/postgresql/HStore.kt | 2 +- .../src/main/kotlin/org/ktorm/support/postgresql/ILike.kt | 2 +- .../main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt | 2 +- .../src/main/kotlin/org/ktorm/support/postgresql/Lock.kt | 2 +- .../kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt | 2 +- .../src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt | 2 +- .../src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt | 2 +- .../main/kotlin/org/ktorm/support/sqlserver/SqlServerDialect.kt | 2 +- .../src/main/kotlin/org/ktorm/support/sqlserver/SqlTypes.kt | 2 +- 75 files changed, 75 insertions(+), 75 deletions(-) diff --git a/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts b/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts index 87e167c1..71ec9a4b 100644 --- a/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts @@ -5,7 +5,7 @@ plugins { val licenseHeaderText = """ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts index e3d571fb..cedb7e1b 100644 --- a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts @@ -391,7 +391,7 @@ val generateTuples by tasks.registering { outputFile.bufferedWriter().use { writer -> writer.write(""" /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt index 917fd088..ee4f9bff 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSetMetadata.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSetMetadata.kt index fa5acba2..caa657a2 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSetMetadata.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSetMetadata.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt index 711658e7..19d1772f 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/JdbcExtensions.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/JdbcExtensions.kt index 57ca6bc4..4edb1f05 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/JdbcExtensions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/JdbcExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/JdbcTransactionManager.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/JdbcTransactionManager.kt index c18be82c..b888a37a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/JdbcTransactionManager.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/JdbcTransactionManager.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/Keywords.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/Keywords.kt index d173d63c..51387c47 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/Keywords.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/Keywords.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/SpringManagedTransactionManager.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/SpringManagedTransactionManager.kt index 59eda87d..ea074889 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/SpringManagedTransactionManager.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/SpringManagedTransactionManager.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/SqlDialect.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/SqlDialect.kt index 45684ed3..38bfb0a8 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/SqlDialect.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/SqlDialect.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/TransactionManager.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/TransactionManager.kt index df82c5c5..14d28b97 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/TransactionManager.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/TransactionManager.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Aggregation.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Aggregation.kt index f18ac1c9..f11c7cf0 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Aggregation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Aggregation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/CountExpression.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/CountExpression.kt index 22d4200f..eb5bdd8a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/CountExpression.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/CountExpression.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt index ad226c97..c5dd6468 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt index cd73a149..3f7f7cc1 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt index 19c56397..0ffc67da 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/QueryRowSet.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/QueryRowSet.kt index 9346bdbf..e9f4fd99 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/QueryRowSet.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/QueryRowSet.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/QuerySource.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/QuerySource.kt index f268149d..e676137a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/QuerySource.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/QuerySource.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt index 137743d8..45ca8cbb 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt index 467157a1..816c8046 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt index 9088e377..2a5a7a02 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityGrouping.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityGrouping.kt index 34b349bb..9f7b7a92 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityGrouping.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityGrouping.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index 1c167d59..2bc00caf 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt index 3c6f0fe3..240a0425 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt index 4f696b8e..f22ebbdb 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitor.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt index 6e77a848..d5b12905 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt index d614c903..d9643d99 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/logging/AndroidLoggerAdapter.kt b/ktorm-core/src/main/kotlin/org/ktorm/logging/AndroidLoggerAdapter.kt index 51c9ad21..05ec9dd8 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/logging/AndroidLoggerAdapter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/logging/AndroidLoggerAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/logging/CommonsLoggerAdapter.kt b/ktorm-core/src/main/kotlin/org/ktorm/logging/CommonsLoggerAdapter.kt index ed7e6cbe..ef96e75b 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/logging/CommonsLoggerAdapter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/logging/CommonsLoggerAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/logging/ConsoleLogger.kt b/ktorm-core/src/main/kotlin/org/ktorm/logging/ConsoleLogger.kt index 5c83b6ab..629f4778 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/logging/ConsoleLogger.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/logging/ConsoleLogger.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/logging/JdkLoggerAdapter.kt b/ktorm-core/src/main/kotlin/org/ktorm/logging/JdkLoggerAdapter.kt index 33f8e5f8..55d43b66 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/logging/JdkLoggerAdapter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/logging/JdkLoggerAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/logging/Logger.kt b/ktorm-core/src/main/kotlin/org/ktorm/logging/Logger.kt index 770e7ff5..c04ccb0a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/logging/Logger.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/logging/Logger.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/logging/NoOpLogger.kt b/ktorm-core/src/main/kotlin/org/ktorm/logging/NoOpLogger.kt index 6e273ec0..32d018b4 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/logging/NoOpLogger.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/logging/NoOpLogger.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/logging/Slf4jLoggerAdapter.kt b/ktorm-core/src/main/kotlin/org/ktorm/logging/Slf4jLoggerAdapter.kt index b7d9d6a3..b7e0390f 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/logging/Slf4jLoggerAdapter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/logging/Slf4jLoggerAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt index 6fecd1d4..aa0b9f37 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt index 30736cb4..edf8ea02 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt index d7d1fb07..f982b5b1 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/RefCounter.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/RefCounter.kt index 122e4639..a58350b7 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/RefCounter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/RefCounter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlType.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlType.kt index 2c5b35ac..34759f30 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlType.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlType.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt index 999412b6..f2e89f24 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt index d4a66fec..1c725e1c 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/TypeReference.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/TypeReference.kt index 8cdf2912..9363c85e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/TypeReference.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/TypeReference.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-global/src/main/kotlin/org/ktorm/global/Aggregations.kt b/ktorm-global/src/main/kotlin/org/ktorm/global/Aggregations.kt index 7a4f7564..34192ce2 100644 --- a/ktorm-global/src/main/kotlin/org/ktorm/global/Aggregations.kt +++ b/ktorm-global/src/main/kotlin/org/ktorm/global/Aggregations.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-global/src/main/kotlin/org/ktorm/global/Dml.kt b/ktorm-global/src/main/kotlin/org/ktorm/global/Dml.kt index 1f059838..f9be9a99 100644 --- a/ktorm-global/src/main/kotlin/org/ktorm/global/Dml.kt +++ b/ktorm-global/src/main/kotlin/org/ktorm/global/Dml.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt b/ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt index 019c49e6..46052375 100644 --- a/ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt +++ b/ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-global/src/main/kotlin/org/ktorm/global/Global.kt b/ktorm-global/src/main/kotlin/org/ktorm/global/Global.kt index 0f73dc08..1f15a782 100644 --- a/ktorm-global/src/main/kotlin/org/ktorm/global/Global.kt +++ b/ktorm-global/src/main/kotlin/org/ktorm/global/Global.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-global/src/main/kotlin/org/ktorm/global/Query.kt b/ktorm-global/src/main/kotlin/org/ktorm/global/Query.kt index 5c981295..12de2e7b 100644 --- a/ktorm-global/src/main/kotlin/org/ktorm/global/Query.kt +++ b/ktorm-global/src/main/kotlin/org/ktorm/global/Query.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt index 5ab6bd9d..4a57dda0 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt index a44ed933..4d0f312e 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt index 782a002d..bfbcb692 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt index 8f66bb08..56326195 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt index 4d503717..e950e97c 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt index d13c5e65..00396215 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl index 15585fe7..32ce0c18 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-jackson/src/test/kotlin/org/ktorm/jackson/JacksonAnnotationTest.kt b/ktorm-jackson/src/test/kotlin/org/ktorm/jackson/JacksonAnnotationTest.kt index b60f6549..aee0a571 100644 --- a/ktorm-jackson/src/test/kotlin/org/ktorm/jackson/JacksonAnnotationTest.kt +++ b/ktorm-jackson/src/test/kotlin/org/ktorm/jackson/JacksonAnnotationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt index 4dc68e65..9f2ae290 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Functions.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Functions.kt index 7aa9c85a..ca060490 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Functions.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Functions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Global.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Global.kt index 7c29c404..7db4d32b 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Global.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Global.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/InsertOrUpdate.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/InsertOrUpdate.kt index 135010b3..c3583b30 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/InsertOrUpdate.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/InsertOrUpdate.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Lock.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Lock.kt index 8edc356e..3d1e1e6b 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Lock.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/Lock.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MatchAgainst.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MatchAgainst.kt index fcc5dda5..fd8a67c8 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MatchAgainst.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MatchAgainst.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MySqlDialect.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MySqlDialect.kt index 6baf51af..c36388dc 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MySqlDialect.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MySqlDialect.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/NaturalJoin.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/NaturalJoin.kt index 72d8cf48..f73a10a9 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/NaturalJoin.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/NaturalJoin.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-oracle/src/main/kotlin/org/ktorm/support/oracle/OracleDialect.kt b/ktorm-support-oracle/src/main/kotlin/org/ktorm/support/oracle/OracleDialect.kt index ab959bfa..79193ed3 100644 --- a/ktorm-support-oracle/src/main/kotlin/org/ktorm/support/oracle/OracleDialect.kt +++ b/ktorm-support-oracle/src/main/kotlin/org/ktorm/support/oracle/OracleDialect.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt index e8d1f641..9dd160a6 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Global.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Global.kt index ef4aa85d..10d9ea74 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Global.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Global.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/HStore.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/HStore.kt index 03612d2b..691b5419 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/HStore.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/HStore.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/ILike.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/ILike.kt index fa385700..e13cd281 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/ILike.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/ILike.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt index adbc67bf..f2f2d383 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Lock.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Lock.kt index 246d01ab..e5a889db 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Lock.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Lock.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt index f5b7f523..4226b54d 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index 411a982e..135e481a 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt index d6d90cd9..35570c04 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlServerDialect.kt b/ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlServerDialect.kt index cfe4e4da..6d70e811 100644 --- a/ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlServerDialect.kt +++ b/ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlServerDialect.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlTypes.kt b/ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlTypes.kt index dbee32ec..a83cdb69 100644 --- a/ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlTypes.kt +++ b/ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlTypes.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From d2b80335f10eb40ef427bc2a1c2d585ffced4eb5 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 15 May 2022 08:52:04 +0800 Subject: [PATCH 066/126] test inline class --- .../kotlin/org/ktorm/database/DatabaseTest.kt | 92 ++---------------- .../org/ktorm/entity/InlineClassTest.kt | 97 +++++++++++++++++++ 2 files changed, 105 insertions(+), 84 deletions(-) create mode 100644 ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt diff --git a/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt index 27d373da..09be3461 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt @@ -2,12 +2,14 @@ package org.ktorm.database import org.junit.Test import org.ktorm.BaseTest -import org.ktorm.dsl.* -import org.ktorm.entity.* -import org.ktorm.schema.* -import java.sql.PreparedStatement -import java.sql.ResultSet -import java.sql.Types +import org.ktorm.dsl.delete +import org.ktorm.dsl.eq +import org.ktorm.dsl.insert +import org.ktorm.entity.count +import org.ktorm.entity.sequenceOf +import org.ktorm.schema.Table +import org.ktorm.schema.defaultValue +import org.ktorm.schema.varchar /** * Created by vince on Dec 02, 2018. @@ -93,84 +95,6 @@ class DatabaseTest : BaseTest() { assert(names[1] == "marry") } - fun BaseTable<*>.ulong(name: String): Column { - return registerColumn(name, object : SqlType(Types.BIGINT, "bigint unsigned") { - override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: ULong) { - ps.setLong(index, parameter.toLong()) - } - - override fun doGetResult(rs: ResultSet, index: Int): ULong? { - return rs.getLong(index).toULong() - } - }) - } - - interface TestUnsigned : Entity { - companion object : Entity.Factory() - var id: ULong - } - - @Test - fun testUnsigned() { - val t = object : Table("T_TEST_UNSIGNED") { - val id = ulong("ID").primaryKey().bindTo { it.id } - } - - database.useConnection { conn -> - conn.createStatement().use { statement -> - val sql = """CREATE TABLE T_TEST_UNSIGNED(ID BIGINT UNSIGNED NOT NULL PRIMARY KEY)""" - statement.executeUpdate(sql) - } - } - - val unsigned = TestUnsigned { id = 5UL } - assert(unsigned.id == 5UL) - database.sequenceOf(t).add(unsigned) - - val ids = database.sequenceOf(t).toList().map { it.id } - println(ids) - assert(ids == listOf(5UL)) - - database.insert(t) { - set(it.id, 6UL) - } - - val ids2 = database.from(t).select(t.id).map { row -> row[t.id] } - println(ids2) - assert(ids2 == listOf(5UL, 6UL)) - - assert(TestUnsigned().id == 0UL) - } - - interface TestUnsignedNullable : Entity { - companion object : Entity.Factory() - var id: ULong? - } - - @Test - fun testUnsignedNullable() { - val t = object : Table("T_TEST_UNSIGNED_NULLABLE") { - val id = ulong("ID").primaryKey().bindTo { it.id } - } - - database.useConnection { conn -> - conn.createStatement().use { statement -> - val sql = """CREATE TABLE T_TEST_UNSIGNED_NULLABLE(ID BIGINT UNSIGNED NOT NULL PRIMARY KEY)""" - statement.executeUpdate(sql) - } - } - - val unsigned = TestUnsignedNullable { id = 5UL } - assert(unsigned.id == 5UL) - database.sequenceOf(t).add(unsigned) - - val ids = database.sequenceOf(t).toList().map { it.id } - println(ids) - assert(ids == listOf(5UL)) - - assert(TestUnsignedNullable().id == null) - } - @Test fun testDefaultValueReferenceEquality() { assert(Boolean::class.javaPrimitiveType!!.defaultValue === Boolean::class.javaPrimitiveType!!.defaultValue) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt new file mode 100644 index 00000000..9b2b8e5e --- /dev/null +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt @@ -0,0 +1,97 @@ +package org.ktorm.entity + +import org.junit.Test +import org.ktorm.BaseTest +import org.ktorm.database.use +import org.ktorm.dsl.from +import org.ktorm.dsl.insert +import org.ktorm.dsl.map +import org.ktorm.dsl.select +import org.ktorm.schema.BaseTable +import org.ktorm.schema.Column +import org.ktorm.schema.SqlType +import org.ktorm.schema.Table +import java.sql.PreparedStatement +import java.sql.ResultSet +import java.sql.Types + +class InlineClassTest : BaseTest() { + + fun BaseTable<*>.ulong(name: String): Column { + return registerColumn(name, object : SqlType(Types.BIGINT, "bigint unsigned") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: ULong) { + ps.setLong(index, parameter.toLong()) + } + + override fun doGetResult(rs: ResultSet, index: Int): ULong? { + return rs.getLong(index).toULong() + } + }) + } + + interface TestUnsigned : Entity { + companion object : Entity.Factory() + var id: ULong + } + + @Test + fun testUnsigned() { + val t = object : Table("T_TEST_UNSIGNED") { + val id = ulong("ID").primaryKey().bindTo { it.id } + } + + database.useConnection { conn -> + conn.createStatement().use { statement -> + val sql = """CREATE TABLE T_TEST_UNSIGNED(ID BIGINT UNSIGNED NOT NULL PRIMARY KEY)""" + statement.executeUpdate(sql) + } + } + + val unsigned = TestUnsigned { id = 5UL } + assert(unsigned.id == 5UL) + database.sequenceOf(t).add(unsigned) + + val ids = database.sequenceOf(t).toList().map { it.id } + println(ids) + assert(ids == listOf(5UL)) + + database.insert(t) { + set(it.id, 6UL) + } + + val ids2 = database.from(t).select(t.id).map { row -> row[t.id] } + println(ids2) + assert(ids2 == listOf(5UL, 6UL)) + + assert(TestUnsigned().id == 0UL) + } + + interface TestUnsignedNullable : Entity { + companion object : Entity.Factory() + var id: ULong? + } + + @Test + fun testUnsignedNullable() { + val t = object : Table("T_TEST_UNSIGNED_NULLABLE") { + val id = ulong("ID").primaryKey().bindTo { it.id } + } + + database.useConnection { conn -> + conn.createStatement().use { statement -> + val sql = """CREATE TABLE T_TEST_UNSIGNED_NULLABLE(ID BIGINT UNSIGNED NOT NULL PRIMARY KEY)""" + statement.executeUpdate(sql) + } + } + + val unsigned = TestUnsignedNullable { id = 5UL } + assert(unsigned.id == 5UL) + database.sequenceOf(t).add(unsigned) + + val ids = database.sequenceOf(t).toList().map { it.id } + println(ids) + assert(ids == listOf(5UL)) + + assert(TestUnsignedNullable().id == null) + } +} \ No newline at end of file From c3c828bf7e14645b00d088642f2a6a23f19aa797 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 16 May 2022 00:06:32 +0800 Subject: [PATCH 067/126] test unbox value --- .../kotlin/org/ktorm/entity/Reflections.kt | 26 ++++++ .../org/ktorm/entity/InlineClassTest.kt | 92 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt new file mode 100644 index 00000000..18a98e8f --- /dev/null +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt @@ -0,0 +1,26 @@ +package org.ktorm.entity + +import java.lang.reflect.InvocationTargetException +import kotlin.reflect.KClass +import kotlin.reflect.full.hasAnnotation + +/** + * Check if this class is an inline class. + */ +internal val KClass<*>.isInline: Boolean get() = this.isValue && this.hasAnnotation() + +/** + * Unbox the inline class value to the target type. + */ +internal fun Any.unboxTo(targetClass: Class<*>): Any? { + var curr: Any? = this + while (curr != null && curr::class.isInline && curr.javaClass != targetClass) { + try { + curr = curr.javaClass.getMethod("unbox-impl").invoke(curr) + } catch (e: InvocationTargetException) { + throw e.targetException + } + } + + return curr +} diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt index 9b2b8e5e..4b9af5ad 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt @@ -14,6 +14,7 @@ import org.ktorm.schema.Table import java.sql.PreparedStatement import java.sql.ResultSet import java.sql.Types +import kotlin.test.assertEquals class InlineClassTest : BaseTest() { @@ -36,6 +37,10 @@ class InlineClassTest : BaseTest() { @Test fun testUnsigned() { + for (method in TestUnsigned::class.java.methods) { + println(method) + } + val t = object : Table("T_TEST_UNSIGNED") { val id = ulong("ID").primaryKey().bindTo { it.id } } @@ -94,4 +99,91 @@ class InlineClassTest : BaseTest() { assert(TestUnsignedNullable().id == null) } + + interface Case1 { + @JvmInline + value class ICPrimitive(val x: Int) + } + + interface Case2 { + @JvmInline + value class ICReference(val s: String) + } + + interface Case3 { + @JvmInline + value class ICNullable(val s: String?) + } + + interface Case4 { + @JvmInline + value class IC1(val s: String) + @JvmInline + value class IC2(val ic1: IC1?) + @JvmInline + value class IC3(val ic2: IC2) + } + + interface Case5 { + @JvmInline + value class IC1(val s: String?) + @JvmInline + value class IC2(val ic1: IC1?) + @JvmInline + value class IC3(val ic2: IC2) + } + + @Test + fun testUnboxCase1() { + val c = Case1.ICPrimitive(1) + assertEquals(c.unboxTo(Int::class.java), 1) + assertEquals(c.unboxTo(Case1.ICPrimitive::class.java), Case1.ICPrimitive(1)) + } + + @Test + fun testUnboxCase2() { + val c = Case2.ICReference("hello") + assertEquals(c.unboxTo(String::class.java), "hello") + assertEquals(c.unboxTo(Case2.ICReference::class.java), Case2.ICReference("hello")) + } + + @Test + fun testUnboxCase3() { + val c1 = Case3.ICNullable("hello") + assertEquals(c1.unboxTo(String::class.java), "hello") + assertEquals(c1.unboxTo(Case3.ICNullable::class.java), Case3.ICNullable("hello")) + + val c2 = Case3.ICNullable(null) + assertEquals(c2.unboxTo(String::class.java), null) + assertEquals(c2.unboxTo(Case3.ICNullable::class.java), Case3.ICNullable(null)) + } + + @Test + fun testUnboxCase4() { + val c1 = Case4.IC3(Case4.IC2(Case4.IC1("hello"))) + assertEquals(c1.unboxTo(String::class.java), "hello") + assertEquals(c1.unboxTo(Case4.IC3::class.java), Case4.IC3(Case4.IC2(Case4.IC1("hello")))) + + val c2 = Case4.IC3(Case4.IC2(null)) + assertEquals(c2.unboxTo(String::class.java), null) + assertEquals(c2.unboxTo(Case4.IC3::class.java), Case4.IC3(Case4.IC2(null))) + } + + @Test + fun testUnboxCase5() { + val c1 = Case5.IC3(Case5.IC2(Case5.IC1("hello"))) + assertEquals(c1.unboxTo(String::class.java), "hello") + assertEquals(c1.unboxTo(Case5.IC1::class.java), Case5.IC1("hello")) + assertEquals(c1.unboxTo(Case5.IC3::class.java), Case5.IC3(Case5.IC2(Case5.IC1("hello")))) + + val c2 = Case5.IC3(Case5.IC2(null)) + assertEquals(c2.unboxTo(String::class.java), null) + assertEquals(c2.unboxTo(Case5.IC1::class.java), null) + assertEquals(c2.unboxTo(Case5.IC3::class.java), Case5.IC3(Case5.IC2(null))) + + val c3 = Case5.IC3(Case5.IC2(Case5.IC1(null))) + assertEquals(c3.unboxTo(String::class.java), null) + assertEquals(c3.unboxTo(Case5.IC1::class.java), Case5.IC1(null)) + assertEquals(c3.unboxTo(Case5.IC3::class.java), Case5.IC3(Case5.IC2(Case5.IC1(null)))) + } } \ No newline at end of file From aa5a3eca29f5a69bdcd170b2eb4a84ccc61bb81a Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 16 May 2022 23:10:18 +0800 Subject: [PATCH 068/126] add source header --- .../main/kotlin/org/ktorm/entity/Reflections.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt index 18a98e8f..aed7872d 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2018-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.ktorm.entity import java.lang.reflect.InvocationTargetException From 98487d3a0c872fa63fd6d57355bee156828aa2db Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 18 May 2022 01:29:19 +0800 Subject: [PATCH 069/126] test box value --- .../org/ktorm/entity/EntityImplementation.kt | 16 +--- .../kotlin/org/ktorm/entity/Reflections.kt | 47 +++++++++- .../org/ktorm/entity/InlineClassTest.kt | 93 +++++++++++++------ 3 files changed, 110 insertions(+), 46 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index 2bc00caf..480c0502 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -22,11 +22,8 @@ import org.ktorm.schema.defaultValue import org.ktorm.schema.kotlinProperty import java.io.* import java.lang.reflect.InvocationHandler -import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.util.* -import kotlin.collections.LinkedHashMap -import kotlin.collections.LinkedHashSet import kotlin.reflect.KClass import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaGetter @@ -135,21 +132,16 @@ internal class EntityImplementation( setProperty(prop, value) } - @Suppress("SwallowedException") private fun callDefaultImpl(proxy: Any, method: Method, args: Array?): Any? { val impl = defaultImplsCache.computeIfAbsent(method) { val cls = Class.forName(method.declaringClass.name + "\$DefaultImpls") cls.getMethod(method.name, method.declaringClass, *method.parameterTypes) } - try { - if (args == null) { - return impl.invoke(null, proxy) - } else { - return impl.invoke(null, proxy, *args) - } - } catch (e: InvocationTargetException) { - throw e.targetException + if (args == null) { + return impl.invoke0(null, proxy) + } else { + return impl.invoke0(null, proxy, *args) } } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt index aed7872d..bf2ad6bc 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt @@ -17,7 +17,9 @@ package org.ktorm.entity import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method import kotlin.reflect.KClass +import kotlin.reflect.KType import kotlin.reflect.full.hasAnnotation /** @@ -31,12 +33,47 @@ internal val KClass<*>.isInline: Boolean get() = this.isValue && this.hasAnnotat internal fun Any.unboxTo(targetClass: Class<*>): Any? { var curr: Any? = this while (curr != null && curr::class.isInline && curr.javaClass != targetClass) { - try { - curr = curr.javaClass.getMethod("unbox-impl").invoke(curr) - } catch (e: InvocationTargetException) { - throw e.targetException - } + curr = curr.javaClass.getMethod("unbox-impl").invoke0(curr) } return curr } + +/** + * Box the underlying value to an inline class value. + */ +internal fun KType.boxFrom(value: Any?): Any? { + if (value == null && this.isMarkedNullable) { + return null + } else { + return (this.classifier as KClass<*>).boxFrom(value) + } +} + +/** + * Box the underlying value to an inline class value + */ +internal fun KClass<*>.boxFrom(value: Any?): Any? { + if (!this.isInline || this.java == value?.javaClass) { + return value + } + + val method = this.java.methods.single { it.name == "box-impl" } + if (value == null || method.parameterTypes[0].kotlin.isInstance(value)) { + return method.invoke0(null, value) + } else { + return method.invoke0(null, method.parameterTypes[0].kotlin.boxFrom(value)) + } +} + +/** + * Call the [Method.invoke] method, catching [InvocationTargetException], and rethrowing the target exception. + */ +@Suppress("SwallowedException") +internal fun Method.invoke0(obj: Any?, vararg args: Any?): Any? { + try { + return this.invoke(obj, *args) + } catch (e: InvocationTargetException) { + throw e.targetException + } +} diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt index 4b9af5ad..b63125d9 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt @@ -24,7 +24,7 @@ class InlineClassTest : BaseTest() { ps.setLong(index, parameter.toLong()) } - override fun doGetResult(rs: ResultSet, index: Int): ULong? { + override fun doGetResult(rs: ResultSet, index: Int): ULong { return rs.getLong(index).toULong() } }) @@ -105,34 +105,6 @@ class InlineClassTest : BaseTest() { value class ICPrimitive(val x: Int) } - interface Case2 { - @JvmInline - value class ICReference(val s: String) - } - - interface Case3 { - @JvmInline - value class ICNullable(val s: String?) - } - - interface Case4 { - @JvmInline - value class IC1(val s: String) - @JvmInline - value class IC2(val ic1: IC1?) - @JvmInline - value class IC3(val ic2: IC2) - } - - interface Case5 { - @JvmInline - value class IC1(val s: String?) - @JvmInline - value class IC2(val ic1: IC1?) - @JvmInline - value class IC3(val ic2: IC2) - } - @Test fun testUnboxCase1() { val c = Case1.ICPrimitive(1) @@ -140,6 +112,17 @@ class InlineClassTest : BaseTest() { assertEquals(c.unboxTo(Case1.ICPrimitive::class.java), Case1.ICPrimitive(1)) } + @Test + fun testBoxCase1() { + assertEquals(Case1.ICPrimitive::class.boxFrom(1), Case1.ICPrimitive(1)) + assertEquals(Case1.ICPrimitive::class.boxFrom(Case1.ICPrimitive(1)), Case1.ICPrimitive(1)) + } + + interface Case2 { + @JvmInline + value class ICReference(val s: String) + } + @Test fun testUnboxCase2() { val c = Case2.ICReference("hello") @@ -147,6 +130,17 @@ class InlineClassTest : BaseTest() { assertEquals(c.unboxTo(Case2.ICReference::class.java), Case2.ICReference("hello")) } + @Test + fun testBoxCase2() { + assertEquals(Case2.ICReference::class.boxFrom("hello"), Case2.ICReference("hello")) + assertEquals(Case2.ICReference::class.boxFrom(Case2.ICReference("hello")), Case2.ICReference("hello")) + } + + interface Case3 { + @JvmInline + value class ICNullable(val s: String?) + } + @Test fun testUnboxCase3() { val c1 = Case3.ICNullable("hello") @@ -158,6 +152,22 @@ class InlineClassTest : BaseTest() { assertEquals(c2.unboxTo(Case3.ICNullable::class.java), Case3.ICNullable(null)) } + @Test + fun testBoxCase3() { + assertEquals(Case3.ICNullable::class.boxFrom(null), Case3.ICNullable(null)) + assertEquals(Case3.ICNullable::class.boxFrom("hello"), Case3.ICNullable("hello")) + assertEquals(Case3.ICNullable::class.boxFrom(Case3.ICNullable("hello")), Case3.ICNullable("hello")) + } + + interface Case4 { + @JvmInline + value class IC1(val s: String) + @JvmInline + value class IC2(val ic1: IC1?) + @JvmInline + value class IC3(val ic2: IC2) + } + @Test fun testUnboxCase4() { val c1 = Case4.IC3(Case4.IC2(Case4.IC1("hello"))) @@ -169,6 +179,22 @@ class InlineClassTest : BaseTest() { assertEquals(c2.unboxTo(Case4.IC3::class.java), Case4.IC3(Case4.IC2(null))) } + @Test + fun testBoxCase4() { + assertEquals(Case4.IC3::class.boxFrom(null), Case4.IC3(Case4.IC2(null))) + assertEquals(Case4.IC3::class.boxFrom("hello"), Case4.IC3(Case4.IC2(Case4.IC1("hello")))) + assertEquals(Case4.IC3::class.boxFrom(Case4.IC3(Case4.IC2(Case4.IC1("hello")))), Case4.IC3(Case4.IC2(Case4.IC1("hello")))) + } + + interface Case5 { + @JvmInline + value class IC1(val s: String?) + @JvmInline + value class IC2(val ic1: IC1?) + @JvmInline + value class IC3(val ic2: IC2) + } + @Test fun testUnboxCase5() { val c1 = Case5.IC3(Case5.IC2(Case5.IC1("hello"))) @@ -186,4 +212,13 @@ class InlineClassTest : BaseTest() { assertEquals(c3.unboxTo(Case5.IC1::class.java), Case5.IC1(null)) assertEquals(c3.unboxTo(Case5.IC3::class.java), Case5.IC3(Case5.IC2(Case5.IC1(null)))) } + + @Test + fun testBoxCase5() { + assertEquals(Case5.IC3::class.boxFrom(null), Case5.IC3(Case5.IC2(null))) + assertEquals(Case5.IC3::class.boxFrom(Case5.IC1(null)), Case5.IC3(Case5.IC2(Case5.IC1(null)))) + assertEquals(Case5.IC3::class.boxFrom("hello"), Case5.IC3(Case5.IC2(Case5.IC1("hello")))) + assertEquals(Case5.IC3::class.boxFrom(Case5.IC1("hello")), Case5.IC3(Case5.IC2(Case5.IC1("hello")))) + assertEquals(Case5.IC3::class.boxFrom(Case5.IC3(Case5.IC2(Case5.IC1("hello")))), Case5.IC3(Case5.IC2(Case5.IC1("hello")))) + } } \ No newline at end of file From 22efee1eca444875262b78006dc4c7cae9cab553 Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 18 May 2022 01:36:37 +0800 Subject: [PATCH 070/126] fix kdoc --- ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt index bf2ad6bc..58a13c8e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt @@ -51,7 +51,7 @@ internal fun KType.boxFrom(value: Any?): Any? { } /** - * Box the underlying value to an inline class value + * Box the underlying value to an inline class value. */ internal fun KClass<*>.boxFrom(value: Any?): Any? { if (!this.isInline || this.java == value?.javaClass) { From d866a18316772a8b1272626a9463c3021fb4f844 Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 18 May 2022 23:21:38 +0800 Subject: [PATCH 071/126] support inline class properties --- .../org/ktorm/entity/EntityImplementation.kt | 44 ++----------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index 480c0502..bd2e3839 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -27,7 +27,6 @@ import java.util.* import kotlin.reflect.KClass import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaGetter -import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.jvm.jvmName import kotlin.reflect.jvm.kotlinFunction @@ -149,51 +148,16 @@ internal class EntityImplementation( return prop.name in values } - @OptIn(ExperimentalUnsignedTypes::class) fun getProperty(prop: KProperty1<*, *>, unboxInlineValues: Boolean = false): Any? { - if (!unboxInlineValues) { + if (unboxInlineValues) { + return values[prop.name]?.unboxTo(prop.javaGetter!!.returnType) + } else { return values[prop.name] } - - val returnType = prop.javaGetter!!.returnType - val value = values[prop.name] - - // Unbox inline class values if necessary. - // In principle, we need to check for all inline classes, but kotlin-reflect is still unable to determine - // whether a class is inline, so as a workaround, we have to enumerate some common-used types here. - return when { - value is UByte && returnType == Byte::class.javaPrimitiveType -> value.toByte() - value is UShort && returnType == Short::class.javaPrimitiveType -> value.toShort() - value is UInt && returnType == Int::class.javaPrimitiveType -> value.toInt() - value is ULong && returnType == Long::class.javaPrimitiveType -> value.toLong() - value is UByteArray && returnType == ByteArray::class.java -> value.toByteArray() - value is UShortArray && returnType == ShortArray::class.java -> value.toShortArray() - value is UIntArray && returnType == IntArray::class.java -> value.toIntArray() - value is ULongArray && returnType == LongArray::class.java -> value.toLongArray() - else -> value - } } - @OptIn(ExperimentalUnsignedTypes::class) fun setProperty(prop: KProperty1<*, *>, value: Any?, forceSet: Boolean = false) { - val propType = prop.returnType.jvmErasure - - // For inline classes, always box the underlying values as wrapper types. - // In principle, we need to check for all inline classes, but kotlin-reflect is still unable to determine - // whether a class is inline, so as a workaround, we have to enumerate some common-used types here. - val boxedValue = when { - propType == UByte::class && value is Byte -> value.toUByte() - propType == UShort::class && value is Short -> value.toUShort() - propType == UInt::class && value is Int -> value.toUInt() - propType == ULong::class && value is Long -> value.toULong() - propType == UByteArray::class && value is ByteArray -> value.toUByteArray() - propType == UShortArray::class && value is ShortArray -> value.toUShortArray() - propType == UIntArray::class && value is IntArray -> value.toUIntArray() - propType == ULongArray::class && value is LongArray -> value.toULongArray() - else -> value - } - - doSetProperty(prop.name, boxedValue, forceSet) + doSetProperty(prop.name, prop.returnType.boxFrom(value), forceSet) } private fun doSetProperty(name: String, value: Any?, forceSet: Boolean = false) { From bbb994f08855148aa4bd48646a1add9ef902be65 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 19 May 2022 00:28:15 +0800 Subject: [PATCH 072/126] add test --- .../org/ktorm/entity/InlineClassTest.kt | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt index b63125d9..ec635762 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/InlineClassTest.kt @@ -7,10 +7,7 @@ import org.ktorm.dsl.from import org.ktorm.dsl.insert import org.ktorm.dsl.map import org.ktorm.dsl.select -import org.ktorm.schema.BaseTable -import org.ktorm.schema.Column -import org.ktorm.schema.SqlType -import org.ktorm.schema.Table +import org.ktorm.schema.* import java.sql.PreparedStatement import java.sql.ResultSet import java.sql.Types @@ -78,6 +75,9 @@ class InlineClassTest : BaseTest() { @Test fun testUnsignedNullable() { + val unsigned2 = TestUnsignedNullable { id = null } + assert(unsigned2.id == null) + val t = object : Table("T_TEST_UNSIGNED_NULLABLE") { val id = ulong("ID").primaryKey().bindTo { it.id } } @@ -193,6 +193,28 @@ class InlineClassTest : BaseTest() { value class IC2(val ic1: IC1?) @JvmInline value class IC3(val ic2: IC2) + + interface TestEntity : Entity { + companion object : Entity.Factory() + var id: IC3 + } + } + + fun BaseTable<*>.case5(name: String): Column { + return registerColumn(name, object : SqlType(Types.VARCHAR, "varchar") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Case5.IC3) { + val s = parameter.ic2.ic1?.s + if (s == null) { + ps.setNull(index, typeCode) + } else { + ps.setString(index, s) + } + } + + override fun doGetResult(rs: ResultSet, index: Int): Case5.IC3 { + return Case5.IC3(Case5.IC2(Case5.IC1(rs.getString(index)))) + } + }) } @Test @@ -221,4 +243,30 @@ class InlineClassTest : BaseTest() { assertEquals(Case5.IC3::class.boxFrom(Case5.IC1("hello")), Case5.IC3(Case5.IC2(Case5.IC1("hello")))) assertEquals(Case5.IC3::class.boxFrom(Case5.IC3(Case5.IC2(Case5.IC1("hello")))), Case5.IC3(Case5.IC2(Case5.IC1("hello")))) } + + @Test + fun testNestedInlineClassCase5() { + for (method in Case5.TestEntity::class.java.methods) { + println(method) + } + + val t = object : Table("T_TEST_NESTED_INLINE_CLASS_CASE5") { + val id = case5("ID").primaryKey().bindTo { it.id } + } + + database.useConnection { conn -> + conn.createStatement().use { statement -> + val sql = """CREATE TABLE T_TEST_NESTED_INLINE_CLASS_CASE5(ID VARCHAR NOT NULL PRIMARY KEY)""" + statement.executeUpdate(sql) + } + } + + val entity = Case5.TestEntity { id = Case5.IC3(Case5.IC2(Case5.IC1("hello"))) } + assert(entity.id == Case5.IC3(Case5.IC2(Case5.IC1("hello")))) + database.sequenceOf(t).add(entity) + + val ids = database.sequenceOf(t).toList().map { it.id } + println(ids) + assert(ids == listOf(Case5.IC3(Case5.IC2(Case5.IC1("hello"))))) + } } \ No newline at end of file From 80c67f1d2768c4511c9dcf6f7a7060592ce7971e Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 19 May 2022 00:58:29 +0800 Subject: [PATCH 073/126] rm unsigned numbers opt-inn --- .../main/kotlin/org/ktorm/entity/Reflections.kt | 3 ++- .../org/ktorm/schema/ColumnBindingHandler.kt | 5 ----- .../kotlin/org/ktorm/database/DatabaseTest.kt | 5 ----- .../org/ktorm/jackson/JacksonExtensions.kt | 17 ++++++----------- .../kotlin/org/ktorm/jackson/JacksonTest.kt | 1 - 5 files changed, 8 insertions(+), 23 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt index 58a13c8e..7fa62e33 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt @@ -21,6 +21,7 @@ import java.lang.reflect.Method import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.jvm.jvmErasure /** * Check if this class is an inline class. @@ -46,7 +47,7 @@ internal fun KType.boxFrom(value: Any?): Any? { if (value == null && this.isMarkedNullable) { return null } else { - return (this.classifier as KClass<*>).boxFrom(value) + return this.jvmErasure.boxFrom(value) } } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt index f982b5b1..83d689ba 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt @@ -84,7 +84,6 @@ internal val Method.kotlinProperty: Pair, Boolean>? get() { return null } -@OptIn(ExperimentalUnsignedTypes::class) internal val Class<*>.defaultValue: Any get() { val value = when { this == Boolean::class.javaPrimitiveType -> false @@ -100,10 +99,6 @@ internal val Class<*>.defaultValue: Any get() { this == UShort::class.java -> 0.toUShort() this == UInt::class.java -> 0U this == ULong::class.java -> 0UL - this == UByteArray::class.java -> ubyteArrayOf() - this == UShortArray::class.java -> ushortArrayOf() - this == UIntArray::class.java -> uintArrayOf() - this == ULongArray::class.java -> ulongArrayOf() this == Set::class.java -> LinkedHashSet() this == List::class.java -> ArrayList() this == Collection::class.java -> ArrayList() diff --git a/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt index 09be3461..da9ecaa5 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt @@ -14,7 +14,6 @@ import org.ktorm.schema.varchar /** * Created by vince on Dec 02, 2018. */ -@ExperimentalUnsignedTypes class DatabaseTest : BaseTest() { @Test @@ -110,9 +109,5 @@ class DatabaseTest : BaseTest() { assert(UShort::class.java.defaultValue !== UShort::class.java.defaultValue) assert(UInt::class.java.defaultValue !== UInt::class.java.defaultValue) assert(ULong::class.java.defaultValue !== ULong::class.java.defaultValue) - assert(UByteArray::class.java.defaultValue !== UByteArray::class.java.defaultValue) - assert(UShortArray::class.java.defaultValue !== UShortArray::class.java.defaultValue) - assert(UIntArray::class.java.defaultValue !== UIntArray::class.java.defaultValue) - assert(ULongArray::class.java.defaultValue !== ULongArray::class.java.defaultValue) } } \ No newline at end of file diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt index 56326195..3cc2cf65 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JacksonExtensions.kt @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.introspect.AnnotatedMethod import kotlin.reflect.KMutableProperty import kotlin.reflect.KProperty1 +import kotlin.reflect.full.hasAnnotation import kotlin.reflect.jvm.* internal fun JsonGenerator.configureIndentOutputIfEnabled() { @@ -111,17 +112,11 @@ internal inline fun KProperty1<*, *>.findAnnotationForD return annotation } -@OptIn(ExperimentalUnsignedTypes::class) internal fun KProperty1<*, *>.getPropertyType(): java.lang.reflect.Type { - return when (returnType.jvmErasure) { - UByte::class -> UByte::class.java - UShort::class -> UShort::class.java - UInt::class -> UInt::class.java - ULong::class -> ULong::class.java - UByteArray::class -> UByteArray::class.java - UShortArray::class -> UShortArray::class.java - UIntArray::class -> UIntArray::class.java - ULongArray::class -> ULongArray::class.java - else -> returnType.javaType + val cls = returnType.jvmErasure + if (cls.isValue && cls.hasAnnotation()) { + return cls.java + } else { + return returnType.javaType } } diff --git a/ktorm-jackson/src/test/kotlin/org/ktorm/jackson/JacksonTest.kt b/ktorm-jackson/src/test/kotlin/org/ktorm/jackson/JacksonTest.kt index c5b97a83..86633d0c 100644 --- a/ktorm-jackson/src/test/kotlin/org/ktorm/jackson/JacksonTest.kt +++ b/ktorm-jackson/src/test/kotlin/org/ktorm/jackson/JacksonTest.kt @@ -11,7 +11,6 @@ import java.math.BigInteger /** * Created by vince on Dec 09, 2018. */ -@ExperimentalUnsignedTypes class JacksonTest { private val objectMapper = ObjectMapper() From d4531425f9d23189b7faf2d03fce1b76ae982a70 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 19 May 2022 01:22:18 +0800 Subject: [PATCH 074/126] refactor reflect functions --- .../org/ktorm/entity/EntityImplementation.kt | 2 - .../kotlin/org/ktorm/entity/Reflections.kt | 63 +++++++++++++++++++ .../org/ktorm/schema/ColumnBindingHandler.kt | 56 +---------------- 3 files changed, 65 insertions(+), 56 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index bd2e3839..5b20d008 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -18,8 +18,6 @@ package org.ktorm.entity import org.ktorm.database.Database import org.ktorm.schema.Table -import org.ktorm.schema.defaultValue -import org.ktorm.schema.kotlinProperty import java.io.* import java.lang.reflect.InvocationHandler import java.lang.reflect.Method diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt index 7fa62e33..aaa884f7 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt @@ -18,11 +18,74 @@ package org.ktorm.entity import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method +import java.util.* import kotlin.reflect.KClass +import kotlin.reflect.KMutableProperty +import kotlin.reflect.KProperty1 import kotlin.reflect.KType +import kotlin.reflect.full.createInstance +import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.jvm.javaGetter +import kotlin.reflect.jvm.javaSetter import kotlin.reflect.jvm.jvmErasure +/** + * Return the corresponding Kotlin property of this method if exists and a flag indicates whether + * it's a getter (true) or setter (false). + */ +internal val Method.kotlinProperty: Pair, Boolean>? get() { + for (prop in declaringClass.kotlin.declaredMemberProperties) { + if (prop.javaGetter == this) { + return Pair(prop, true) + } + if (prop is KMutableProperty<*> && prop.javaSetter == this) { + return Pair(prop, false) + } + } + return null +} + +/** + * Return a default value for the class. + */ +internal val Class<*>.defaultValue: Any get() { + val value = when { + this == Boolean::class.javaPrimitiveType -> false + this == Char::class.javaPrimitiveType -> 0.toChar() + this == Byte::class.javaPrimitiveType -> 0.toByte() + this == Short::class.javaPrimitiveType -> 0.toShort() + this == Int::class.javaPrimitiveType -> 0 + this == Long::class.javaPrimitiveType -> 0L + this == Float::class.javaPrimitiveType -> 0.0F + this == Double::class.javaPrimitiveType -> 0.0 + this == String::class.java -> "" + this == UByte::class.java -> 0.toUByte() + this == UShort::class.java -> 0.toUShort() + this == UInt::class.java -> 0U + this == ULong::class.java -> 0UL + this == Set::class.java -> LinkedHashSet() + this == List::class.java -> ArrayList() + this == Collection::class.java -> ArrayList() + this == Map::class.java -> LinkedHashMap() + this == Queue::class.java || this == Deque::class.java -> LinkedList() + this == SortedSet::class.java || this == NavigableSet::class.java -> TreeSet() + this == SortedMap::class.java || this == NavigableMap::class.java -> TreeMap() + this.isEnum -> this.enumConstants[0] + this.isArray -> java.lang.reflect.Array.newInstance(this.componentType, 0) + this.kotlin.isSubclassOf(Entity::class) -> Entity.create(this.kotlin) + else -> this.kotlin.createInstance() + } + + if (this.kotlin.isInstance(value)) { + return value + } else { + // never happens... + throw AssertionError("$value must be instance of $this") + } +} + /** * Check if this class is an inline class. */ diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt index 83d689ba..b3d6ecdd 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt @@ -17,18 +17,14 @@ package org.ktorm.schema import org.ktorm.entity.Entity +import org.ktorm.entity.defaultValue +import org.ktorm.entity.kotlinProperty import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy -import java.util.* import kotlin.reflect.KClass -import kotlin.reflect.KMutableProperty import kotlin.reflect.KProperty1 -import kotlin.reflect.full.createInstance -import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.isSubclassOf -import kotlin.reflect.jvm.javaGetter -import kotlin.reflect.jvm.javaSetter @PublishedApi internal class ColumnBindingHandler(val properties: MutableList>) : InvocationHandler { @@ -71,51 +67,3 @@ internal class ColumnBindingHandler(val properties: MutableList } } } - -internal val Method.kotlinProperty: Pair, Boolean>? get() { - for (prop in declaringClass.kotlin.declaredMemberProperties) { - if (prop.javaGetter == this) { - return Pair(prop, true) - } - if (prop is KMutableProperty<*> && prop.javaSetter == this) { - return Pair(prop, false) - } - } - return null -} - -internal val Class<*>.defaultValue: Any get() { - val value = when { - this == Boolean::class.javaPrimitiveType -> false - this == Char::class.javaPrimitiveType -> 0.toChar() - this == Byte::class.javaPrimitiveType -> 0.toByte() - this == Short::class.javaPrimitiveType -> 0.toShort() - this == Int::class.javaPrimitiveType -> 0 - this == Long::class.javaPrimitiveType -> 0L - this == Float::class.javaPrimitiveType -> 0.0F - this == Double::class.javaPrimitiveType -> 0.0 - this == String::class.java -> "" - this == UByte::class.java -> 0.toUByte() - this == UShort::class.java -> 0.toUShort() - this == UInt::class.java -> 0U - this == ULong::class.java -> 0UL - this == Set::class.java -> LinkedHashSet() - this == List::class.java -> ArrayList() - this == Collection::class.java -> ArrayList() - this == Map::class.java -> LinkedHashMap() - this == Queue::class.java || this == Deque::class.java -> LinkedList() - this == SortedSet::class.java || this == NavigableSet::class.java -> TreeSet() - this == SortedMap::class.java || this == NavigableMap::class.java -> TreeMap() - this.isEnum -> this.enumConstants[0] - this.isArray -> java.lang.reflect.Array.newInstance(this.componentType, 0) - this.kotlin.isSubclassOf(Entity::class) -> Entity.create(this.kotlin) - else -> this.kotlin.createInstance() - } - - if (this.kotlin.isInstance(value)) { - return value - } else { - // never happens... - throw AssertionError("$value must be instance of $this") - } -} From 420fa61d7600787759e778fcd0b1976876171f70 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 19 May 2022 01:27:08 +0800 Subject: [PATCH 075/126] fix compile error --- ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt index c5dd6468..1ac3af85 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt @@ -17,11 +17,11 @@ package org.ktorm.dsl import org.ktorm.database.Database +import org.ktorm.entity.defaultValue import org.ktorm.expression.* import org.ktorm.schema.BaseTable import org.ktorm.schema.Column import org.ktorm.schema.ColumnDeclaring -import org.ktorm.schema.defaultValue import java.lang.reflect.InvocationHandler import java.lang.reflect.Proxy import java.sql.PreparedStatement From 4279c67cc81229ac94c33ed99e2933ce1fcc7a37 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 19 May 2022 01:28:19 +0800 Subject: [PATCH 076/126] fix compile error --- ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt index da9ecaa5..a0a8b2e9 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/database/DatabaseTest.kt @@ -6,9 +6,9 @@ import org.ktorm.dsl.delete import org.ktorm.dsl.eq import org.ktorm.dsl.insert import org.ktorm.entity.count +import org.ktorm.entity.defaultValue import org.ktorm.entity.sequenceOf import org.ktorm.schema.Table -import org.ktorm.schema.defaultValue import org.ktorm.schema.varchar /** From 9161c61362ded640ec46d716393c344917c42704 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 19 May 2022 01:39:55 +0800 Subject: [PATCH 077/126] fix code style --- .../src/main/kotlin/org/ktorm/entity/EntityExtensions.kt | 8 ++++---- .../src/main/kotlin/org/ktorm/entity/Reflections.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt index 2a5a7a02..4b9d4ca9 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt @@ -41,9 +41,9 @@ internal fun EntityImplementation.hasColumnValue(binding: ColumnBinding): Boolea if (child == null) { // null is also a legal column value. return true + } else { + return child.implementation.hasPrimaryKeyValue(binding.referenceTable as Table<*>) } - - return child.implementation.hasPrimaryKeyValue(binding.referenceTable as Table<*>) } is NestedBinding -> { var curr: EntityImplementation = this @@ -58,9 +58,9 @@ internal fun EntityImplementation.hasColumnValue(binding: ColumnBinding): Boolea if (child == null) { // null is also a legal column value. return true + } else { + curr = child.implementation } - - curr = child.implementation } } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt index aaa884f7..aed23f51 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt @@ -48,7 +48,7 @@ internal val Method.kotlinProperty: Pair, Boolean>? get() { } /** - * Return a default value for the class. + * Return a default value for the class. */ internal val Class<*>.defaultValue: Any get() { val value = when { From 74d18d24dd7b6a660090057762128b6040283f23 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 20 May 2022 00:11:30 +0800 Subject: [PATCH 078/126] test jvm-default compatibility --- .../src/main/kotlin/ktorm.module-conventions.gradle.kts | 8 ++++++++ ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt | 3 +++ ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index 231885c4..d188702a 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -35,3 +35,11 @@ tasks.named("compileKotlin") { ) } } + +tasks.named("compileTestKotlin") { + kotlinOptions { + freeCompilerArgs = listOf( + "-Xjvm-default=enable" + ) + } +} diff --git a/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt index 85ec5f19..60ee3de7 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt @@ -73,6 +73,9 @@ open class BaseTest { var department: Department val upperName get() = name.uppercase() + + @JvmDefault + @Suppress("DEPRECATION") fun upperName() = name.uppercase() } diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index 552850aa..ebcf69f9 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -35,6 +35,10 @@ class EntityTest : BaseTest() { @Test fun testEntityProperties() { + for (method in Employee::class.java.methods) { + println(method) + } + val employee = Employee { name = "vince" } From 4c3fd344200635ad77c1c3a43b5749ea2058e792 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 21 May 2022 19:17:45 +0800 Subject: [PATCH 079/126] default method handler --- .../org/ktorm/entity/DefaultMethodHandler.kt | 78 +++++++++++++++++++ .../org/ktorm/entity/EntityImplementation.kt | 20 +---- 2 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt new file mode 100644 index 00000000..d719dffc --- /dev/null +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2018-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ktorm.entity + +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import java.lang.reflect.Method +import java.util.* + +internal class DefaultMethodHandler( + private val kotlinDefaultImplMethod: Method? = null, + private val javaDefaultMethodHandle: MethodHandle? = null +) { + + fun invoke(proxy: Any, args: Array?): Any? { + if (kotlinDefaultImplMethod != null) { + if (args == null) { + return kotlinDefaultImplMethod.invoke0(null, proxy) + } else { + return kotlinDefaultImplMethod.invoke0(null, proxy, *args) + } + } + + if (javaDefaultMethodHandle != null) { + if (args == null) { + return javaDefaultMethodHandle.bindTo(proxy).invokeWithArguments() + } else { + return javaDefaultMethodHandle.bindTo(proxy).invokeWithArguments(*args) + } + } + + throw AssertionError("Non-abstract method in an interface must be a JVM default method or have DefaultImpls") + } + + companion object { + private val handlersCache: MutableMap = Collections.synchronizedMap(WeakHashMap()) + + fun forMethod(method: Method): DefaultMethodHandler { + return handlersCache.computeIfAbsent(method) { + val defaultImpl = getKotlinDefaultImplMethod(method) + if (defaultImpl != null) { + DefaultMethodHandler(kotlinDefaultImplMethod = defaultImpl) + } else { + DefaultMethodHandler(javaDefaultMethodHandle = getJavaDefaultMethodHandle(method)) + } + } + } + + private fun getKotlinDefaultImplMethod(method: Method): Method? { + try { + val cls = Class.forName(method.declaringClass.name + "\$DefaultImpls") + return cls.getMethod(method.name, method.declaringClass, *method.parameterTypes) + } catch (e: ClassNotFoundException) { + return null + } catch (e: NoSuchMethodException) { + return null + } + } + + private fun getJavaDefaultMethodHandle(method: Method): MethodHandle? { + return MethodHandles.lookup().unreflectSpecial(method, method.declaringClass) + } + } +} \ No newline at end of file diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index 5b20d008..e1095b7f 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -40,7 +40,6 @@ internal class EntityImplementation( companion object { private const val serialVersionUID = 1L - private val defaultImplsCache: MutableMap = Collections.synchronizedMap(WeakHashMap()) } override fun invoke(proxy: Any, method: Method, args: Array?): Any? { @@ -89,14 +88,14 @@ internal class EntityImplementation( return null } } else { - return callDefaultImpl(proxy, method, args) + return DefaultMethodHandler.forMethod(method).invoke(proxy, args) } } else { val func = method.kotlinFunction if (func != null && !func.isAbstract) { - return callDefaultImpl(proxy, method, args) + return DefaultMethodHandler.forMethod(method).invoke(proxy, args) } else { - throw IllegalStateException("Unrecognized method: $method") + throw IllegalStateException("Unsupported method invocation: $method") } } } @@ -129,19 +128,6 @@ internal class EntityImplementation( setProperty(prop, value) } - private fun callDefaultImpl(proxy: Any, method: Method, args: Array?): Any? { - val impl = defaultImplsCache.computeIfAbsent(method) { - val cls = Class.forName(method.declaringClass.name + "\$DefaultImpls") - cls.getMethod(method.name, method.declaringClass, *method.parameterTypes) - } - - if (args == null) { - return impl.invoke0(null, proxy) - } else { - return impl.invoke0(null, proxy, *args) - } - } - fun hasProperty(prop: KProperty1<*, *>): Boolean { return prop.name in values } From e806b75558eea8ab6cc68a1b4bc251668b439015 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 21 May 2022 22:57:42 +0800 Subject: [PATCH 080/126] fix lookup illegal access exception --- .../org/ktorm/entity/DefaultMethodHandler.kt | 32 +++++++++++++++++-- .../main/kotlin/org/ktorm/entity/EntityDml.kt | 2 +- .../org/ktorm/entity/EntityImplementation.kt | 2 +- .../src/test/kotlin/org/ktorm/BaseTest.kt | 7 +++- .../kotlin/org/ktorm/entity/EntityTest.kt | 24 ++++++++------ 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt index d719dffc..24724a1c 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt @@ -18,6 +18,9 @@ package org.ktorm.entity import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodHandles.Lookup.* +import java.lang.reflect.Constructor +import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.util.* @@ -47,7 +50,8 @@ internal class DefaultMethodHandler( } companion object { - private val handlersCache: MutableMap = Collections.synchronizedMap(WeakHashMap()) + private val handlersCache = Collections.synchronizedMap(WeakHashMap()) + private val lookupConstructor = initLookupConstructor() fun forMethod(method: Method): DefaultMethodHandler { return handlersCache.computeIfAbsent(method) { @@ -60,6 +64,7 @@ internal class DefaultMethodHandler( } } + @Suppress("SwallowedException") private fun getKotlinDefaultImplMethod(method: Method): Method? { try { val cls = Class.forName(method.declaringClass.name + "\$DefaultImpls") @@ -71,8 +76,29 @@ internal class DefaultMethodHandler( } } + @Suppress("SwallowedException") private fun getJavaDefaultMethodHandle(method: Method): MethodHandle? { - return MethodHandles.lookup().unreflectSpecial(method, method.declaringClass) + try { + val allModes = PUBLIC or PRIVATE or PROTECTED or PACKAGE + val lookup = lookupConstructor.newInstance(method.declaringClass, allModes) + return lookup.unreflectSpecial(method, method.declaringClass) + } catch (e: InvocationTargetException) { + throw e.targetException + } + } + + private fun initLookupConstructor(): Constructor { + try { + val constructor = MethodHandles.Lookup::class.java + .getDeclaredConstructor(Class::class.java, Int::class.javaPrimitiveType) + constructor.isAccessible = true + return constructor + } catch (e: NoSuchMethodException) { + val msg = "" + + "Cannot find constructor MethodHandles.Lookup(Class, int), " + + "please ensure you are using JDK 8 or above." + throw IllegalStateException(msg) + } } } -} \ No newline at end of file +} diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt index 816c8046..541ff0e6 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt @@ -201,7 +201,7 @@ private fun EntitySequence<*, *>.checkIfSequenceModified() { if (isModified) { throw UnsupportedOperationException( "Entity manipulation functions are not supported by this sequence object. " + - "Please call on the origin sequence returned from database.sequenceOf(table)" + "Please call on the origin sequence returned from database.sequenceOf(table)" ) } } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index e1095b7f..b9f02437 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -95,7 +95,7 @@ internal class EntityImplementation( if (func != null && !func.isAbstract) { return DefaultMethodHandler.forMethod(method).invoke(proxy, args) } else { - throw IllegalStateException("Unsupported method invocation: $method") + throw IllegalStateException("Cannot invoke entity abstract method: $method") } } } diff --git a/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt index 60ee3de7..6181e402 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/BaseTest.kt @@ -62,6 +62,7 @@ open class BaseTest { var mixedCase: String? } + @Suppress("DEPRECATION") interface Employee : Entity { companion object : Entity.Factory() var id: Int @@ -75,8 +76,12 @@ open class BaseTest { val upperName get() = name.uppercase() @JvmDefault - @Suppress("DEPRECATION") fun upperName() = name.uppercase() + + fun nameWithPrefix(prefix: String) = prefix + name + + @JvmDefault + fun nameWithSuffix(suffix: String) = name + suffix } interface Customer : Entity { diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index ebcf69f9..ff899f56 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -35,23 +35,29 @@ class EntityTest : BaseTest() { @Test fun testEntityProperties() { + val employee = Employee { name = "vince" } + println(employee) + + assert(employee["name"] == "vince") + assert(employee.name == "vince") + + assert(employee["job"] == null) + assert(employee.job == "") + } + + @Test + fun testDefaultMethod() { for (method in Employee::class.java.methods) { println(method) } - val employee = Employee { - name = "vince" - } - + val employee = Employee { name = "vince" } println(employee) - assert(employee["name"] == "vince") - assert(employee.name == "vince") assert(employee.upperName == "VINCE") assert(employee.upperName() == "VINCE") - - assert(employee["job"] == null) - assert(employee.job == "") + assert(employee.nameWithPrefix(":") == ":vince") + assert(employee.nameWithSuffix(":") == "vince:") } @Test From 919d109ad8064b05cb6bc6aad16576a88c427a05 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 22 May 2022 00:29:30 +0800 Subject: [PATCH 081/126] refactor default method handler --- .../org/ktorm/entity/DefaultMethodHandler.kt | 42 +++++++------------ .../main/kotlin/org/ktorm/entity/EntityDml.kt | 4 +- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt index 24724a1c..66e8a4dd 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt @@ -25,24 +25,24 @@ import java.lang.reflect.Method import java.util.* internal class DefaultMethodHandler( - private val kotlinDefaultImplMethod: Method? = null, - private val javaDefaultMethodHandle: MethodHandle? = null + private val javaDefaultMethodHandle: MethodHandle? = null, + private val kotlinDefaultImplMethod: Method? = null ) { fun invoke(proxy: Any, args: Array?): Any? { - if (kotlinDefaultImplMethod != null) { + if (javaDefaultMethodHandle != null) { if (args == null) { - return kotlinDefaultImplMethod.invoke0(null, proxy) + return javaDefaultMethodHandle.bindTo(proxy).invokeWithArguments() } else { - return kotlinDefaultImplMethod.invoke0(null, proxy, *args) + return javaDefaultMethodHandle.bindTo(proxy).invokeWithArguments(*args) } } - if (javaDefaultMethodHandle != null) { + if (kotlinDefaultImplMethod != null) { if (args == null) { - return javaDefaultMethodHandle.bindTo(proxy).invokeWithArguments() + return kotlinDefaultImplMethod.invoke0(null, proxy) } else { - return javaDefaultMethodHandle.bindTo(proxy).invokeWithArguments(*args) + return kotlinDefaultImplMethod.invoke0(null, proxy, *args) } } @@ -55,29 +55,19 @@ internal class DefaultMethodHandler( fun forMethod(method: Method): DefaultMethodHandler { return handlersCache.computeIfAbsent(method) { - val defaultImpl = getKotlinDefaultImplMethod(method) - if (defaultImpl != null) { - DefaultMethodHandler(kotlinDefaultImplMethod = defaultImpl) + if (method.isDefault) { + val handle = unreflectSpecial(method) + DefaultMethodHandler(javaDefaultMethodHandle = handle) } else { - DefaultMethodHandler(javaDefaultMethodHandle = getJavaDefaultMethodHandle(method)) + val cls = Class.forName(method.declaringClass.name + "\$DefaultImpls") + val impl = cls.getMethod(method.name, method.declaringClass, *method.parameterTypes) + DefaultMethodHandler(kotlinDefaultImplMethod = impl) } } } @Suppress("SwallowedException") - private fun getKotlinDefaultImplMethod(method: Method): Method? { - try { - val cls = Class.forName(method.declaringClass.name + "\$DefaultImpls") - return cls.getMethod(method.name, method.declaringClass, *method.parameterTypes) - } catch (e: ClassNotFoundException) { - return null - } catch (e: NoSuchMethodException) { - return null - } - } - - @Suppress("SwallowedException") - private fun getJavaDefaultMethodHandle(method: Method): MethodHandle? { + private fun unreflectSpecial(method: Method): MethodHandle { try { val allModes = PUBLIC or PRIVATE or PROTECTED or PACKAGE val lookup = lookupConstructor.newInstance(method.declaringClass, allModes) @@ -97,7 +87,7 @@ internal class DefaultMethodHandler( val msg = "" + "Cannot find constructor MethodHandles.Lookup(Class, int), " + "please ensure you are using JDK 8 or above." - throw IllegalStateException(msg) + throw IllegalStateException(msg, e) } } } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt index 541ff0e6..de461411 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt @@ -199,10 +199,10 @@ private fun EntitySequence<*, *>.checkIfSequenceModified() { || expression.limit != null if (isModified) { - throw UnsupportedOperationException( + val msg = "" + "Entity manipulation functions are not supported by this sequence object. " + "Please call on the origin sequence returned from database.sequenceOf(table)" - ) + throw UnsupportedOperationException(msg) } } From a1567c21c10fbb81cc7b5e9c988d195b1e3ccc49 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 22 May 2022 11:58:02 +0800 Subject: [PATCH 082/126] default method handle for jdk 9 --- .../ktorm.module-conventions.gradle.kts | 3 +- .../org/ktorm/entity/DefaultMethodHandler.kt | 80 +++++++++++++------ 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index d188702a..ec79501e 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -27,7 +27,7 @@ detekt { tasks.named("compileKotlin") { kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "9" allWarningsAsErrors = true freeCompilerArgs = listOf( "-Xexplicit-api=strict", @@ -38,6 +38,7 @@ tasks.named("compileKotlin") { tasks.named("compileTestKotlin") { kotlinOptions { + jvmTarget = "9" freeCompilerArgs = listOf( "-Xjvm-default=enable" ) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt index 66e8a4dd..c176cd24 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt @@ -18,6 +18,7 @@ package org.ktorm.entity import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodHandles.Lookup import java.lang.invoke.MethodHandles.Lookup.* import java.lang.reflect.Constructor import java.lang.reflect.InvocationTargetException @@ -51,44 +52,73 @@ internal class DefaultMethodHandler( companion object { private val handlersCache = Collections.synchronizedMap(WeakHashMap()) - private val lookupConstructor = initLookupConstructor() + private val privateLookupIn: Method? + private val lookupConstructor: Constructor? - fun forMethod(method: Method): DefaultMethodHandler { - return handlersCache.computeIfAbsent(method) { - if (method.isDefault) { - val handle = unreflectSpecial(method) - DefaultMethodHandler(javaDefaultMethodHandle = handle) - } else { - val cls = Class.forName(method.declaringClass.name + "\$DefaultImpls") - val impl = cls.getMethod(method.name, method.declaringClass, *method.parameterTypes) - DefaultMethodHandler(kotlinDefaultImplMethod = impl) - } - } + init { + privateLookupIn = initPrivateLookupInMethod() + lookupConstructor = if (privateLookupIn == null) initLookupConstructor() else null } - @Suppress("SwallowedException") - private fun unreflectSpecial(method: Method): MethodHandle { + private fun initPrivateLookupInMethod(): Method? { try { - val allModes = PUBLIC or PRIVATE or PROTECTED or PACKAGE - val lookup = lookupConstructor.newInstance(method.declaringClass, allModes) - return lookup.unreflectSpecial(method, method.declaringClass) - } catch (e: InvocationTargetException) { - throw e.targetException + return MethodHandles::class.java.getMethod("privateLookupIn", Class::class.java, Lookup::class.java) + } catch (e: NoSuchMethodException) { + // MethodHandles.privateLookupIn(Class, MethodHandles.Lookup) doesn't exist in JDK 1.8 + return null } } - private fun initLookupConstructor(): Constructor { + private fun initLookupConstructor(): Constructor? { try { - val constructor = MethodHandles.Lookup::class.java - .getDeclaredConstructor(Class::class.java, Int::class.javaPrimitiveType) - constructor.isAccessible = true - return constructor + val c = Lookup::class.java.getDeclaredConstructor(Class::class.java, Int::class.javaPrimitiveType) + c.isAccessible = true + return c } catch (e: NoSuchMethodException) { val msg = "" + "Cannot find constructor MethodHandles.Lookup(Class, int), " + - "please ensure you are using JDK 8 or above." + "please ensure you are using JDK 1.8 or above." throw IllegalStateException(msg, e) } } + + @Suppress("SwallowedException") + private fun unreflectSpecial(method: Method): MethodHandle { + // For JDK 9 or above + if (privateLookupIn != null) { + val lookup = privateLookupIn.invoke0(null, method.declaringClass, MethodHandles.lookup()) as Lookup + return lookup.unreflectSpecial(method, method.declaringClass) + } + + // For JDK 1.8 + if (lookupConstructor != null) { + try { + val allModes = PUBLIC or PRIVATE or PROTECTED or PACKAGE + val lookup = lookupConstructor.newInstance(method.declaringClass, allModes) + return lookup.unreflectSpecial(method, method.declaringClass) + } catch (e: InvocationTargetException) { + throw e.targetException + } + } + + // Throws error for JDK version lower than 1.8. + val msg = "" + + "Cannot find constructor MethodHandles.Lookup(Class, int), " + + "please ensure you are using JDK 1.8 or above." + throw AssertionError(msg) + } + + fun forMethod(method: Method): DefaultMethodHandler { + return handlersCache.computeIfAbsent(method) { + if (method.isDefault) { + val handle = unreflectSpecial(method) + DefaultMethodHandler(javaDefaultMethodHandle = handle) + } else { + val cls = Class.forName(method.declaringClass.name + "\$DefaultImpls") + val impl = cls.getMethod(method.name, method.declaringClass, *method.parameterTypes) + DefaultMethodHandler(kotlinDefaultImplMethod = impl) + } + } + } } } From 009ea16b408b7616f758813ed81b31906a07b78d Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 22 May 2022 18:23:35 +0800 Subject: [PATCH 083/126] refactor --- .../org/ktorm/entity/DefaultMethodHandler.kt | 7 ++-- .../main/kotlin/org/ktorm/entity/EntityDml.kt | 2 +- .../org/ktorm/entity/EntityImplementation.kt | 32 +++++++++++-------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt index c176cd24..1e53f9fa 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/DefaultMethodHandler.kt @@ -60,11 +60,12 @@ internal class DefaultMethodHandler( lookupConstructor = if (privateLookupIn == null) initLookupConstructor() else null } + @Suppress("SwallowedException") private fun initPrivateLookupInMethod(): Method? { try { return MethodHandles::class.java.getMethod("privateLookupIn", Class::class.java, Lookup::class.java) } catch (e: NoSuchMethodException) { - // MethodHandles.privateLookupIn(Class, MethodHandles.Lookup) doesn't exist in JDK 1.8 + // MethodHandles.privateLookupIn(Class, MethodHandles.Lookup) doesn't exist in JDK 1.8. return null } } @@ -84,13 +85,13 @@ internal class DefaultMethodHandler( @Suppress("SwallowedException") private fun unreflectSpecial(method: Method): MethodHandle { - // For JDK 9 or above + // For JDK 9 or above. if (privateLookupIn != null) { val lookup = privateLookupIn.invoke0(null, method.declaringClass, MethodHandles.lookup()) as Lookup return lookup.unreflectSpecial(method, method.declaringClass) } - // For JDK 1.8 + // For JDK 1.8. if (lookupConstructor != null) { try { val allModes = PUBLIC or PRIVATE or PROTECTED or PACKAGE diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt index de461411..f352378a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt @@ -137,7 +137,7 @@ public fun > EntitySequence.removeIf( } /** - * Remove all of the elements of this sequence. The sequence will be empty after this function returns. + * Remove all the elements of this sequence. The sequence will be empty after this function returns. * * @since 2.7 */ diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index b9f02437..be441784 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -29,19 +29,16 @@ import kotlin.reflect.jvm.jvmName import kotlin.reflect.jvm.kotlinFunction internal class EntityImplementation( - var entityClass: KClass<*>, - @Transient var fromDatabase: Database?, - @Transient var fromTable: Table<*>?, - @Transient var parent: EntityImplementation? + _entityClass: KClass<*>, _fromDatabase: Database?, _fromTable: Table<*>?, _parent: EntityImplementation? ) : InvocationHandler, Serializable { + var entityClass: KClass<*> = _entityClass var values = LinkedHashMap() + @Transient var fromDatabase: Database? = _fromDatabase + @Transient var fromTable: Table<*>? = _fromTable + @Transient var parent: EntityImplementation? = _parent @Transient var changedProperties = LinkedHashSet() - companion object { - private const val serialVersionUID = 1L - } - override fun invoke(proxy: Any, method: Method, args: Array?): Any? { return when (method.declaringClass.kotlin) { Any::class -> { @@ -115,7 +112,8 @@ internal class EntityImplementation( private fun cacheDefaultValue(prop: KProperty1<*, *>, value: Any) { val type = prop.javaGetter!!.returnType - // Skip for primitive types, enums and string, because their default values always share the same instance. + // No need to cache primitive types, enums and string, + // because their default values always share the same instance. if (type == Boolean::class.javaPrimitiveType) return if (type == Char::class.javaPrimitiveType) return if (type == Byte::class.javaPrimitiveType) return @@ -125,6 +123,7 @@ internal class EntityImplementation( if (type == String::class.java) return if (type.isEnum) return + // Cache the default value to avoid the weird case that entity.prop !== entity.prop setProperty(prop, value) } @@ -160,14 +159,14 @@ internal class EntityImplementation( for ((name, value) in values) { if (value is Entity<*>) { - val valueCopy = value.copy() + val copied = value.copy() // Keep the parent relationship. - if (valueCopy.implementation.parent == this) { - valueCopy.implementation.parent = entity.implementation + if (copied.implementation.parent == this) { + copied.implementation.parent = entity.implementation } - entity.implementation.values[name] = valueCopy + entity.implementation.values[name] = copied } else { entity.implementation.values[name] = value?.let { deserialize(serialize(it)) } } @@ -201,7 +200,8 @@ internal class EntityImplementation( @Suppress("UNCHECKED_CAST") private fun readObject(input: ObjectInputStream) { - entityClass = Class.forName(input.readUTF()).kotlin + val javaClass = Class.forName(input.readUTF()) + entityClass = javaClass.kotlin values = input.readObject() as LinkedHashMap changedProperties = LinkedHashSet() } @@ -226,4 +226,8 @@ internal class EntityImplementation( override fun toString(): String { return entityClass.simpleName + values } + + companion object { + private const val serialVersionUID = 1L + } } From da44cfc5081145d4ea059b07bd30ad06feed70e7 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 22 May 2022 18:33:23 +0800 Subject: [PATCH 084/126] fix parameter naming --- .../kotlin/org/ktorm/entity/EntityImplementation.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index be441784..19978c6e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -28,15 +28,16 @@ import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.jvmName import kotlin.reflect.jvm.kotlinFunction +@Suppress("CanBePrimaryConstructorProperty") internal class EntityImplementation( - _entityClass: KClass<*>, _fromDatabase: Database?, _fromTable: Table<*>?, _parent: EntityImplementation? + entityClass: KClass<*>, fromDatabase: Database?, fromTable: Table<*>?, parent: EntityImplementation? ) : InvocationHandler, Serializable { - var entityClass: KClass<*> = _entityClass + var entityClass: KClass<*> = entityClass var values = LinkedHashMap() - @Transient var fromDatabase: Database? = _fromDatabase - @Transient var fromTable: Table<*>? = _fromTable - @Transient var parent: EntityImplementation? = _parent + @Transient var fromDatabase: Database? = fromDatabase + @Transient var fromTable: Table<*>? = fromTable + @Transient var parent: EntityImplementation? = parent @Transient var changedProperties = LinkedHashSet() override fun invoke(proxy: Any, method: Method, args: Array?): Any? { From 1c43f3452e4f0e1f2b33357a792986e3745bc18f Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 22 May 2022 22:33:13 +0800 Subject: [PATCH 085/126] refactor jackson entity type resolver builder --- .../jackson/EntityTypeResolverBuilder.kt | 129 +++++++++--------- .../kotlin/org/ktorm/jackson/KtormModule.kt | 5 +- 2 files changed, 69 insertions(+), 65 deletions(-) diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt index bfbcb692..b5e9128b 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityTypeResolverBuilder.kt @@ -17,93 +17,96 @@ package org.ktorm.jackson import com.fasterxml.jackson.annotation.JsonTypeInfo -import com.fasterxml.jackson.databind.DeserializationConfig import com.fasterxml.jackson.databind.JavaType -import com.fasterxml.jackson.databind.SerializationConfig -import com.fasterxml.jackson.databind.jsontype.NamedType -import com.fasterxml.jackson.databind.jsontype.TypeDeserializer +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator import com.fasterxml.jackson.databind.jsontype.TypeIdResolver -import com.fasterxml.jackson.databind.jsontype.TypeSerializer import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder import org.ktorm.entity.Entity /** * Created by vince on Aug 13, 2018. */ -internal class EntityTypeResolverBuilder(val delegate: StdTypeResolverBuilder) : StdTypeResolverBuilder() { +internal class EntityTypeResolverBuilder( + src: DefaultTypeResolverBuilder +) : DefaultTypeResolverBuilder(src._appliesFor, src._subtypeValidator) { + companion object { - private const val serialVersionUID = 1L + private const val serialVersionUID = 2L } init { - this._idType = delegate["_idType"] as JsonTypeInfo.Id? - this._includeAs = delegate["_includeAs"] as JsonTypeInfo.As? - this._typeProperty = delegate["_typeProperty"] as String? - this._typeIdVisible = delegate["_typeIdVisible"] as Boolean - this._defaultImpl = delegate["_defaultImpl"] as Class<*>? - this._customIdResolver = delegate["_customIdResolver"] as TypeIdResolver? - } - - override fun getDefaultImpl(): Class<*>? { - return _defaultImpl + this._idType = src._idType + this._includeAs = src._includeAs + this._typeProperty = src._typeProperty + this._typeIdVisible = src._typeIdVisible + this._defaultImpl = src._defaultImpl + this._customIdResolver = src._customIdResolver } - override fun buildTypeSerializer( - config: SerializationConfig, - baseType: JavaType, - subtypes: MutableCollection? - ): TypeSerializer? { - - if (baseType.isTypeOrSubTypeOf(Entity::class.java)) { + override fun useForType(t: JavaType): Boolean { + if (t.isTypeOrSubTypeOf(Entity::class.java)) { // Always use type serialization for entity types... - return super.buildTypeSerializer(config, baseType, subtypes) + return true } else { - return delegate.buildTypeSerializer(config, baseType, subtypes) + return super.useForType(t) } } +} - override fun buildTypeDeserializer( - config: DeserializationConfig, - baseType: JavaType, - subtypes: MutableCollection? - ): TypeDeserializer? { +@Suppress("ObjectPropertyName") +private val StdTypeResolverBuilder._idType: JsonTypeInfo.Id? get() { + val field = StdTypeResolverBuilder::class.java.getDeclaredField("_idType") + field.isAccessible = true + return field.get(this) as JsonTypeInfo.Id? +} - if (baseType.isTypeOrSubTypeOf(Entity::class.java)) { - // Always use type serialization for entity types... - return super.buildTypeDeserializer(config, baseType, subtypes) - } else { - return delegate.buildTypeDeserializer(config, baseType, subtypes) - } - } +@Suppress("ObjectPropertyName") +private val StdTypeResolverBuilder._includeAs: JsonTypeInfo.As? get() { + val field = StdTypeResolverBuilder::class.java.getDeclaredField("_includeAs") + field.isAccessible = true + return field.get(this) as JsonTypeInfo.As? +} - override fun init(idType: JsonTypeInfo.Id, res: TypeIdResolver?): StdTypeResolverBuilder { - delegate.init(idType, res) - return super.init(idType, res) - } +@Suppress("ObjectPropertyName") +private val StdTypeResolverBuilder._typeProperty: String? get() { + val field = StdTypeResolverBuilder::class.java.getDeclaredField("_typeProperty") + field.isAccessible = true + return field.get(this) as String? +} - override fun inclusion(includeAs: JsonTypeInfo.As): StdTypeResolverBuilder { - delegate.inclusion(includeAs) - return super.inclusion(includeAs) - } +@Suppress("ObjectPropertyName") +private val StdTypeResolverBuilder._typeIdVisible: Boolean get() { + val field = StdTypeResolverBuilder::class.java.getDeclaredField("_typeIdVisible") + field.isAccessible = true + return field.get(this) as Boolean +} - override fun typeProperty(propName: String?): StdTypeResolverBuilder { - delegate.typeProperty(propName) - return super.typeProperty(propName) - } +@Suppress("ObjectPropertyName") +private val StdTypeResolverBuilder._defaultImpl: Class<*>? get() { + val field = StdTypeResolverBuilder::class.java.getDeclaredField("_defaultImpl") + field.isAccessible = true + return field.get(this) as Class<*>? +} - override fun defaultImpl(defaultImpl: Class<*>?): StdTypeResolverBuilder { - delegate.defaultImpl(defaultImpl) - return super.defaultImpl(defaultImpl) - } +@Suppress("ObjectPropertyName") +private val StdTypeResolverBuilder._customIdResolver: TypeIdResolver? get() { + val field = StdTypeResolverBuilder::class.java.getDeclaredField("_customIdResolver") + field.isAccessible = true + return field.get(this) as TypeIdResolver? +} - override fun typeIdVisibility(isVisible: Boolean): StdTypeResolverBuilder { - delegate.typeIdVisibility(isVisible) - return super.typeIdVisibility(isVisible) - } +@Suppress("ObjectPropertyName") +private val DefaultTypeResolverBuilder._appliesFor: ObjectMapper.DefaultTyping get() { + val field = DefaultTypeResolverBuilder::class.java.getDeclaredField("_appliesFor") + field.isAccessible = true + return field.get(this) as ObjectMapper.DefaultTyping +} - private operator fun StdTypeResolverBuilder.get(fieldName: String): Any? { - val field = StdTypeResolverBuilder::class.java.getDeclaredField(fieldName) - field.isAccessible = true - return field.get(this) - } +@Suppress("ObjectPropertyName") +private val DefaultTypeResolverBuilder._subtypeValidator: PolymorphicTypeValidator get() { + val field = DefaultTypeResolverBuilder::class.java.getDeclaredField("_subtypeValidator") + field.isAccessible = true + return field.get(this) as PolymorphicTypeValidator } diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt index 00396215..69fc95f4 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.ObjectCodec import com.fasterxml.jackson.core.Version import com.fasterxml.jackson.databind.Module import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder import java.util.ServiceLoader import org.ktorm.entity.Entity @@ -61,12 +62,12 @@ public class KtormModule : Module() { val objectType = codec.constructType(Any::class.java) val serializerTyper = codec.serializationConfig.getDefaultTyper(objectType) - if (serializerTyper != null && serializerTyper is StdTypeResolverBuilder) { + if (serializerTyper != null && serializerTyper is DefaultTypeResolverBuilder) { codec.setConfig(codec.serializationConfig.with(EntityTypeResolverBuilder(serializerTyper))) } val deserializerTyper = codec.deserializationConfig.getDefaultTyper(objectType) - if (deserializerTyper != null && deserializerTyper is StdTypeResolverBuilder) { + if (deserializerTyper != null && deserializerTyper is DefaultTypeResolverBuilder) { codec.setConfig(codec.deserializationConfig.with(EntityTypeResolverBuilder(deserializerTyper))) } } From 45e92950b5ed7452a95dd6df6b4d266439ff427b Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 22 May 2022 22:34:09 +0800 Subject: [PATCH 086/126] rm unused import --- ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt index 69fc95f4..4379a053 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/KtormModule.kt @@ -21,9 +21,8 @@ import com.fasterxml.jackson.core.Version import com.fasterxml.jackson.databind.Module import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder -import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder -import java.util.ServiceLoader import org.ktorm.entity.Entity +import java.util.* /** * Jackson [Module] implementation that supports serializing Ktorm's entity objects in JSON format. From d9b5ecc869ecbca4222ecbddd877425e6a5a91f9 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 23 May 2022 00:19:50 +0800 Subject: [PATCH 087/126] test default method override --- .../kotlin/org/ktorm/entity/EntityTest.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index ff899f56..771b46b8 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -60,6 +60,28 @@ class EntityTest : BaseTest() { assert(employee.nameWithSuffix(":") == "vince:") } + @Suppress("DEPRECATION") + interface Animal> : Entity { + fun say1() = "animal1" + fun say2() = "animal2" + @JvmDefault fun say3() = "animal3" + } + + @Suppress("DEPRECATION") + interface Dog : Animal { + override fun say1() = "${super.say1()} --> dog1" + @JvmDefault override fun say2() = "${super.say2()} --> dog2" + @JvmDefault override fun say3() = "${super.say3()} --> dog3" + } + + @Test + fun testDefaultMethodOverride() { + val dog = Entity.create() + assert(dog.say1() == "animal1 --> dog1") + assert(dog.say2() == "animal2 --> dog2") + assert(dog.say3() == "animal3 --> dog3") + } + @Test fun testSerialize() { val employee = Employee { From b3f086aac0819c4dc687959c0bbcd1b5001a721d Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 23 May 2022 00:28:37 +0800 Subject: [PATCH 088/126] update jvm target --- buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index ec79501e..a388135f 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -27,7 +27,7 @@ detekt { tasks.named("compileKotlin") { kotlinOptions { - jvmTarget = "9" + jvmTarget = "1.8" allWarningsAsErrors = true freeCompilerArgs = listOf( "-Xexplicit-api=strict", @@ -38,7 +38,7 @@ tasks.named("compileKotlin") { tasks.named("compileTestKotlin") { kotlinOptions { - jvmTarget = "9" + jvmTarget = "1.8" freeCompilerArgs = listOf( "-Xjvm-default=enable" ) From 10375b2bac5237536e80f45595f4c4ce81e97d5b Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 24 May 2022 23:07:06 +0800 Subject: [PATCH 089/126] gt operator --- .../main/kotlin/org/ktorm/dsl/Operators.kt | 21 +++++++++++++++++++ .../kotlin/org/ktorm/dsl/AggregationTest.kt | 6 +++--- .../test/kotlin/org/ktorm/dsl/QueryTest.kt | 6 +++--- .../org/ktorm/entity/EntitySequenceTest.kt | 2 +- .../org/ktorm/global/GlobalAggregationTest.kt | 6 +++--- .../ktorm/global/GlobalEntitySequenceTest.kt | 2 +- .../org/ktorm/global/GlobalQueryTest.kt | 6 +++--- 7 files changed, 35 insertions(+), 14 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt index 3f7f7cc1..3facc3e8 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt @@ -344,6 +344,27 @@ public infix fun > T.greater(expr: ColumnDeclaring): Binary return expr.wrapArgument(this) greater expr } +/** + * Greater operator, translated to `>` in SQL. + */ +public infix fun > ColumnDeclaring.gt(expr: ColumnDeclaring): BinaryExpression { + return BinaryExpression(BinaryExpressionType.GREATER_THAN, asExpression(), expr.asExpression(), BooleanSqlType) +} + +/** + * Greater operator, translated to `>` in SQL. + */ +public infix fun > ColumnDeclaring.gt(value: T): BinaryExpression { + return this gt wrapArgument(value) +} + +/** + * Greater operator, translated to `>` in SQL. + */ +public infix fun > T.gt(expr: ColumnDeclaring): BinaryExpression { + return expr.wrapArgument(this) gt expr +} + // -------- GreaterEq --------- /** diff --git a/ktorm-core/src/test/kotlin/org/ktorm/dsl/AggregationTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/dsl/AggregationTest.kt index 8f384c84..f3170f2b 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/dsl/AggregationTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/dsl/AggregationTest.kt @@ -47,17 +47,17 @@ class AggregationTest : BaseTest() { @Test fun testNone() { - assert(database.employees.none { it.salary greater 200L }) + assert(database.employees.none { it.salary gt 200L }) } @Test fun testAny() { - assert(!database.employees.any { it.salary greater 200L }) + assert(!database.employees.any { it.salary gt 200L }) } @Test fun testAll() { - assert(database.employees.all { it.salary greater 0L }) + assert(database.employees.all { it.salary gt 0L }) } @Test diff --git a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt index 0c6e3759..0bae2d93 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt @@ -141,7 +141,7 @@ class QueryTest : BaseTest() { .from(t) .select(t.departmentId, avg(t.salary)) .groupBy(t.departmentId) - .having(avg(t.salary).greater(100.0)) + .having(avg(t.salary).gt(100.0)) .associate { it.getInt(1) to it.getDouble(2) } println(salaries) @@ -158,7 +158,7 @@ class QueryTest : BaseTest() { .from(Employees) .select(deptId, salaryAvg) .groupBy(deptId) - .having { salaryAvg greater 100.0 } + .having { salaryAvg gt 100.0 } .associate { row -> row[deptId] to row[salaryAvg] } @@ -176,7 +176,7 @@ class QueryTest : BaseTest() { val salaries = database .from(Employees) .select(salary) - .where { salary greater 200L } + .where { salary gt 200L } .map { it.getLong(1) } println(salaries) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt index 846d8e70..1a50f0b6 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt @@ -60,7 +60,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testAll() { - assert(database.employees.filter { it.departmentId eq 1 }.all { it.salary greater 49L }) + assert(database.employees.filter { it.departmentId eq 1 }.all { it.salary gt 49L }) } @Test diff --git a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalAggregationTest.kt b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalAggregationTest.kt index 9a0e8f86..48a84f45 100644 --- a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalAggregationTest.kt +++ b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalAggregationTest.kt @@ -49,17 +49,17 @@ class GlobalAggregationTest : BaseGlobalTest() { @Test fun testNone() { - assert(Employees.none { it.salary greater 200L }) + assert(Employees.none { it.salary gt 200L }) } @Test fun testAny() { - assert(!Employees.any { it.salary greater 200L }) + assert(!Employees.any { it.salary gt 200L }) } @Test fun testAll() { - assert(Employees.all { it.salary greater 0L }) + assert(Employees.all { it.salary gt 0L }) } @Test diff --git a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt index 2b13ee0d..19a6c44a 100644 --- a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt +++ b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt @@ -62,7 +62,7 @@ class GlobalEntitySequenceTest : BaseGlobalTest() { @Test fun testAll() { - assert(Employees.asSequence().filter { it.departmentId eq 1 }.all { it.salary greater 49L }) + assert(Employees.asSequence().filter { it.departmentId eq 1 }.all { it.salary gt 49L }) } @Test diff --git a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalQueryTest.kt b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalQueryTest.kt index 289bcb8c..f6245295 100644 --- a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalQueryTest.kt +++ b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalQueryTest.kt @@ -111,7 +111,7 @@ class GlobalQueryTest : BaseGlobalTest() { val salaries = t .select(t.departmentId, avg(t.salary)) .groupBy(t.departmentId) - .having { avg(t.salary) greater 100.0 } + .having { avg(t.salary) gt 100.0 } .associate { it.getInt(1) to it.getDouble(2) } println(salaries) @@ -127,7 +127,7 @@ class GlobalQueryTest : BaseGlobalTest() { val salaries = Employees .select(deptId, salaryAvg) .groupBy(deptId) - .having { salaryAvg greater 100.0 } + .having { salaryAvg gt 100.0 } .associate { row -> row[deptId] to row[salaryAvg] } @@ -144,7 +144,7 @@ class GlobalQueryTest : BaseGlobalTest() { val salaries = Employees .select(salary) - .where { salary greater 200L } + .where { salary gt 200L } .map { it.getLong(1) } println(salaries) From b52318c5dd5ce233f0e5e195c93b2fcb4f868c44 Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 24 May 2022 23:45:12 +0800 Subject: [PATCH 090/126] gte operator --- .../main/kotlin/org/ktorm/dsl/Operators.kt | 26 +++++++++++++++++++ .../org/ktorm/support/mysql/MySqlTest.kt | 4 +-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt index 3facc3e8..62f6c104 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt @@ -393,6 +393,32 @@ public infix fun > T.greaterEq(expr: ColumnDeclaring): Bina return expr.wrapArgument(this) greaterEq expr } +/** + * Greater-eq operator, translated to `>=` in SQL. + */ +public infix fun > ColumnDeclaring.gte(expr: ColumnDeclaring): BinaryExpression { + return BinaryExpression( + type = BinaryExpressionType.GREATER_THAN_OR_EQUAL, + left = asExpression(), + right = expr.asExpression(), + sqlType = BooleanSqlType + ) +} + +/** + * Greater-eq operator, translated to `>=` in SQL. + */ +public infix fun > ColumnDeclaring.gte(value: T): BinaryExpression { + return this gte wrapArgument(value) +} + +/** + * Greater-eq operator, translated to `>=` in SQL. + */ +public infix fun > T.gte(expr: ColumnDeclaring): BinaryExpression { + return expr.wrapArgument(this) gte expr +} + // -------- Eq --------- /** diff --git a/ktorm-support-mysql/src/test/kotlin/org/ktorm/support/mysql/MySqlTest.kt b/ktorm-support-mysql/src/test/kotlin/org/ktorm/support/mysql/MySqlTest.kt index 00df266a..b4875b3a 100644 --- a/ktorm-support-mysql/src/test/kotlin/org/ktorm/support/mysql/MySqlTest.kt +++ b/ktorm-support-mysql/src/test/kotlin/org/ktorm/support/mysql/MySqlTest.kt @@ -355,7 +355,7 @@ class MySqlTest : BaseTest() { fun testIf() { val countRich = database .from(Employees) - .select(sum(IF(Employees.salary greaterEq 100L, 1, 0))) + .select(sum(IF(Employees.salary gte 100L, 1, 0))) .map { row -> row.getInt(1) } assert(countRich.size == 1) @@ -366,7 +366,7 @@ class MySqlTest : BaseTest() { fun testSum() { val countRich = database .from(Employees) - .select(sum(Employees.salary.greaterEq(100L).toInt())) + .select(sum(Employees.salary.gte(100L).toInt())) .map { row -> row.getInt(1) } assert(countRich.size == 1) From e4428bc5c093cc627ccd95b0480815d5ffad3653 Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 24 May 2022 23:48:49 +0800 Subject: [PATCH 091/126] lt operator --- .../main/kotlin/org/ktorm/dsl/Operators.kt | 21 +++++++++++++++++++ .../kotlin/org/ktorm/entity/DataClassTest.kt | 2 +- .../org/ktorm/entity/EntitySequenceTest.kt | 2 +- .../ktorm/global/GlobalEntitySequenceTest.kt | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt index 62f6c104..9b73d7ba 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt @@ -293,6 +293,27 @@ public infix fun > T.less(expr: ColumnDeclaring): BinaryExp return expr.wrapArgument(this) less expr } +/** + * Less operator, translated to `<` in SQL. + */ +public infix fun > ColumnDeclaring.lt(expr: ColumnDeclaring): BinaryExpression { + return BinaryExpression(BinaryExpressionType.LESS_THAN, asExpression(), expr.asExpression(), BooleanSqlType) +} + +/** + * Less operator, translated to `<` in SQL. + */ +public infix fun > ColumnDeclaring.lt(value: T): BinaryExpression { + return this lt wrapArgument(value) +} + +/** + * Less operator, translated to `<` in SQL. + */ +public infix fun > T.lt(expr: ColumnDeclaring): BinaryExpression { + return expr.wrapArgument(this) lt expr +} + // ------- LessEq --------- /** diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/DataClassTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/DataClassTest.kt index 0a4e4fc5..4e173d22 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/DataClassTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/DataClassTest.kt @@ -141,7 +141,7 @@ class DataClassTest : BaseTest() { @Test fun testEachCount() { val counts = database.staffs - .filter { it.salary less 100000L } + .filter { it.salary lt 100000L } .groupingBy { it.sectionId } .eachCount() diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt index 1a50f0b6..d710d239 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt @@ -147,7 +147,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testEachCount() { val counts = database.employees - .filter { it.salary less 100000L } + .filter { it.salary lt 100000L } .groupingBy { it.departmentId } .eachCount() diff --git a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt index 19a6c44a..b6dd4ac0 100644 --- a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt +++ b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt @@ -152,7 +152,7 @@ class GlobalEntitySequenceTest : BaseGlobalTest() { fun testEachCount() { val counts = Employees .asSequence() - .filter { it.salary less 100000L } + .filter { it.salary lt 100000L } .groupingBy { it.departmentId } .eachCount() From 659e3781815b88e524620b110174d70511f94fce Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 24 May 2022 23:50:24 +0800 Subject: [PATCH 092/126] lte operator --- .../main/kotlin/org/ktorm/dsl/Operators.kt | 26 +++++++++++++++++++ .../org/ktorm/entity/EntitySequenceTest.kt | 2 +- .../ktorm/global/GlobalEntitySequenceTest.kt | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt index 9b73d7ba..84f8f08a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt @@ -342,6 +342,32 @@ public infix fun > T.lessEq(expr: ColumnDeclaring): BinaryE return expr.wrapArgument(this) lessEq expr } +/** + * Less-eq operator, translated to `<=` in SQL. + */ +public infix fun > ColumnDeclaring.lte(expr: ColumnDeclaring): BinaryExpression { + return BinaryExpression( + type = BinaryExpressionType.LESS_THAN_OR_EQUAL, + left = asExpression(), + right = expr.asExpression(), + sqlType = BooleanSqlType + ) +} + +/** + * Less-eq operator, translated to `<=` in SQL. + */ +public infix fun > ColumnDeclaring.lte(value: T): BinaryExpression { + return this lte wrapArgument(value) +} + +/** + * Less-eq operator, translated to `<=` in SQL. + */ +public infix fun > T.lte(expr: ColumnDeclaring): BinaryExpression { + return expr.wrapArgument(this) lte expr +} + // ------- Greater --------- /** diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt index d710d239..4c7943ff 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntitySequenceTest.kt @@ -160,7 +160,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testEachSum() { val sums = database.employees - .filter { it.salary lessEq 100000L } + .filter { it.salary lte 100000L } .groupingBy { it.departmentId } .eachSumBy { it.salary } diff --git a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt index b6dd4ac0..96a9c75c 100644 --- a/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt +++ b/ktorm-global/src/test/kotlin/org/ktorm/global/GlobalEntitySequenceTest.kt @@ -166,7 +166,7 @@ class GlobalEntitySequenceTest : BaseGlobalTest() { fun testEachSum() { val sums = Employees .asSequence() - .filter { it.salary lessEq 100000L } + .filter { it.salary lte 100000L } .groupingBy { it.departmentId } .eachSumBy { it.salary } From 37bedfe36703f43d4f3a0f47ab23b24453941f0f Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 24 May 2022 23:57:03 +0800 Subject: [PATCH 093/126] neq operator --- .../main/kotlin/org/ktorm/dsl/Operators.kt | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt index 84f8f08a..94646696 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Operators.kt @@ -482,9 +482,12 @@ public infix fun ColumnDeclaring.eq(value: T): BinaryExpression T.eq(expr: ColumnDeclaring): BinaryExpression { -// return expr.wrapArgument(this) eq expr -// } +/** + * Equal operator, translated to `=` in SQL. + */ +public infix fun T.eq(expr: ColumnDeclaring): BinaryExpression { + return expr.wrapArgument(this) eq expr +} // ------- NotEq ------- @@ -502,9 +505,33 @@ public infix fun ColumnDeclaring.notEq(value: T): BinaryExpression< return this notEq wrapArgument(value) } -// infix fun T.notEq(expr: ColumnDeclaring): BinaryExpression { -// return expr.wrapArgument(this) notEq expr -// } +/** + * Not-equal operator, translated to `<>` in SQL. + */ +public infix fun T.notEq(expr: ColumnDeclaring): BinaryExpression { + return expr.wrapArgument(this) notEq expr +} + +/** + * Not-equal operator, translated to `<>` in SQL. + */ +public infix fun ColumnDeclaring.neq(expr: ColumnDeclaring): BinaryExpression { + return BinaryExpression(BinaryExpressionType.NOT_EQUAL, asExpression(), expr.asExpression(), BooleanSqlType) +} + +/** + * Not-equal operator, translated to `<>` in SQL. + */ +public infix fun ColumnDeclaring.neq(value: T): BinaryExpression { + return this neq wrapArgument(value) +} + +/** + * Not-equal operator, translated to `<>` in SQL. + */ +public infix fun T.neq(expr: ColumnDeclaring): BinaryExpression { + return expr.wrapArgument(this) neq expr +} // ---- Between ---- From 450b1cebcec9efda453af2da8ce8bd98e9a179a5 Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 25 May 2022 00:44:26 +0800 Subject: [PATCH 094/126] use kotlin-stdlib-jdk8 --- buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts index a388135f..55d2f67b 100644 --- a/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.module-conventions.gradle.kts @@ -14,7 +14,7 @@ repositories { } dependencies { - api(kotlin("stdlib")) + api(kotlin("stdlib-jdk8")) api(kotlin("reflect")) testImplementation(kotlin("test-junit")) detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${detekt.toolVersion}") From 473dc7e1f1af424490083c5df1f0396fc94d8617 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 00:23:32 +0800 Subject: [PATCH 095/126] rm deprecated api --- .../src/main/kotlin/org/ktorm/dsl/Dml.kt | 81 ------------------- 1 file changed, 81 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt index 1ac3af85..6fac6edc 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt @@ -17,16 +17,11 @@ package org.ktorm.dsl import org.ktorm.database.Database -import org.ktorm.entity.defaultValue import org.ktorm.expression.* import org.ktorm.schema.BaseTable import org.ktorm.schema.Column import org.ktorm.schema.ColumnDeclaring -import java.lang.reflect.InvocationHandler -import java.lang.reflect.Proxy -import java.sql.PreparedStatement import java.sql.Statement -import kotlin.collections.ArrayList /** * Construct an update expression in the given closure, then execute it and return the effected row count. @@ -288,82 +283,6 @@ public open class AssignmentsBuilder { public fun set(column: Column, value: C?) { _assignments += ColumnAssignmentExpression(column.asExpression(), column.wrapArgument(value)) } - - /** - * Assign the specific column to a value. - * - * @since 3.1.0 - */ - @JvmName("setAny") - @Suppress("UNCHECKED_CAST") - @Deprecated("This function will be removed in the future. Please use the generic version instead.") - public fun set(column: Column<*>, value: Any?) { - (column as Column).checkAssignableFrom(value) - _assignments += ColumnAssignmentExpression(column.asExpression(), column.wrapArgument(value)) - } - - /** - * Assign the current column to another column or an expression's result. - */ - @Deprecated( - message = "This function will be removed in the future. Please use set(column, expr) instead.", - replaceWith = ReplaceWith("set(this, expr)") - ) - public infix fun Column.to(expr: ColumnDeclaring) { - _assignments += ColumnAssignmentExpression(asExpression(), expr.asExpression()) - } - - /** - * Assign the current column to a specific value. - */ - @Deprecated( - message = "This function will be removed in the future. Please use set(column, value) instead.", - replaceWith = ReplaceWith("set(this, value)") - ) - public infix fun Column.to(value: C?) { - _assignments += ColumnAssignmentExpression(asExpression(), wrapArgument(value)) - } - - /** - * Assign the current column to a specific value. - * - * Note that this function accepts an argument type `Any?`, that's because it is designed to avoid - * applications call [kotlin.to] unexpectedly in the DSL closures. An exception will be thrown - * by this function if the argument type doesn't match the column's type. - */ - @JvmName("toAny") - @Suppress("UNCHECKED_CAST") - @Deprecated( - message = "This function will be removed in the future. Please use set(column, value) instead.", - replaceWith = ReplaceWith("set(this, value)") - ) - public infix fun Column<*>.to(value: Any?) { - this as Column - checkAssignableFrom(value) - _assignments += ColumnAssignmentExpression(asExpression(), wrapArgument(value)) - } - - private fun Column.checkAssignableFrom(value: Any?) { - if (value == null) return - - val handler = InvocationHandler { _, method, _ -> - // Do nothing... - @Suppress("ForbiddenVoid") - if (method.returnType == Void.TYPE || !method.returnType.isPrimitive) { - null - } else { - method.returnType.defaultValue - } - } - - val proxy = Proxy.newProxyInstance(javaClass.classLoader, arrayOf(PreparedStatement::class.java), handler) - - try { - sqlType.setParameter(proxy as PreparedStatement, 1, value) - } catch (e: ClassCastException) { - throw IllegalArgumentException("Argument type doesn't match the column's type, column: $this", e) - } - } } /** From dbd3fe0841eb4f0b9cfb2a03b473ad399d63958c Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 00:33:44 +0800 Subject: [PATCH 096/126] rm deprecated api --- ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt index 0ffc67da..04987b96 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/dsl/Query.kt @@ -769,18 +769,3 @@ public fun Query.joinToString( ): String { return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() } - -/** - * Indicate that this query should acquire the record-lock, the generated SQL would be `select ... for update`. - * - * @since 3.1.0 - */ -@Deprecated("Will remove in the future, locking clause should be implemented in dialects respectively.") -public fun Query.forUpdate(): Query { - val expr = when (expression) { - is SelectExpression -> expression.copy(forUpdate = true) - is UnionExpression -> throw IllegalStateException("SELECT FOR UPDATE is not supported in a union expression.") - } - - return this.withExpression(expr) -} From ecb43caa9f89f13c31617aa2b74fa61786c5b01b Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 00:37:20 +0800 Subject: [PATCH 097/126] rm deprecated api --- .../src/main/kotlin/org/ktorm/entity/EntitySequence.kt | 10 ---------- .../main/kotlin/org/ktorm/expression/SqlExpressions.kt | 3 --- .../main/kotlin/org/ktorm/expression/SqlFormatter.kt | 4 ---- 3 files changed, 17 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt index 240a0425..2073203d 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt @@ -1503,13 +1503,3 @@ public fun EntitySequence.joinToString( ): String { return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() } - -/** - * Indicate that this query should acquire the record-lock, the generated SQL would be `select ... for update`. - * - * @since 3.1.0 - */ -@Deprecated("Will remove in the future, locking clause should be implemented in dialects respectively.") -public fun > EntitySequence.forUpdate(): EntitySequence { - return this.withExpression(expression.copy(forUpdate = true)) -} diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt index d5b12905..2f06186f 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt @@ -116,7 +116,6 @@ public sealed class QueryExpression : QuerySourceExpression() { * @property groupBy the grouping conditions, represents the `group by` clause of SQL. * @property having the having condition, represents the `having` clause of SQL. * @property isDistinct mark if this query is distinct, true means the SQL is `select distinct ...`. - * @property forUpdate mark if this query should acquire the record-lock, true means the SQL is `select ... for update`. */ public data class SelectExpression( val columns: List> = emptyList(), @@ -125,8 +124,6 @@ public data class SelectExpression( val groupBy: List> = emptyList(), val having: ScalarExpression? = null, val isDistinct: Boolean = false, - @Deprecated("Will remove in the future, locking clause should be implemented in dialects respectively.") - val forUpdate: Boolean = false, override val orderBy: List = emptyList(), override val offset: Int? = null, override val limit: Int? = null, diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt index d9643d99..9166ae5d 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlFormatter.kt @@ -390,10 +390,6 @@ public abstract class SqlFormatter( if (expr.offset != null || expr.limit != null) { writePagination(expr) } - @Suppress("DEPRECATION") - if (expr.forUpdate) { - writeKeyword("for update ") - } return expr } From 3a338556060524208a10aaa5e92ef4738eb4ba3f Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 00:55:28 +0800 Subject: [PATCH 098/126] rm deprecated api --- .../kotlin/ktorm.tuples-generation.gradle.kts | 113 +----------------- 1 file changed, 2 insertions(+), 111 deletions(-) diff --git a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts index cedb7e1b..d26ff953 100644 --- a/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.tuples-generation.gradle.kts @@ -83,61 +83,13 @@ fun generateMapColumns(writer: java.io.Writer, tupleNumber: Int) { writer.write(""" - /** - * Customize the selected columns of the internal query by the given [columnSelector] function, and return a [List] - * containing the query results. - * - * See [EntitySequence.mapColumns] for more details. - * - * The operation is terminal. - * - * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. - * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. - * @return a list of the query results. - */ - @Deprecated( - message = "This function will be removed in the future. Please use mapColumns { .. } instead.", - replaceWith = ReplaceWith("mapColumns(isDistinct, columnSelector)") - ) - public inline fun , $typeParams> EntitySequence.mapColumns$tupleNumber( - isDistinct: Boolean = false, - columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): List> { - return mapColumns(isDistinct, columnSelector) - } - - /** - * Customize the selected columns of the internal query by the given [columnSelector] function, and append the query - * results to the given [destination]. - * - * See [EntitySequence.mapColumnsTo] for more details. - * - * The operation is terminal. - * - * @param destination a [MutableCollection] used to store the results. - * @param isDistinct specify if the query is distinct, the generated SQL becomes `select distinct` if it's set to true. - * @param columnSelector a function in which we should return a tuple of columns or expressions to be selected. - * @return the [destination] collection of the query results. - */ - @Deprecated( - message = "This function will be removed in the future. Please use mapColumnsTo(destination) { .. } instead.", - replaceWith = ReplaceWith("mapColumnsTo(destination, isDistinct, columnSelector)") - ) - public inline fun , $typeParams, R> EntitySequence.mapColumns${tupleNumber}To( - destination: R, - isDistinct: Boolean = false, - columnSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): R where R : MutableCollection> { - return mapColumnsTo(destination, isDistinct, columnSelector) - } - /** * Customize the selected columns of the internal query by the given [columnSelector] function, and return a [List] * containing the query results. * * This function is similar to [EntitySequence.map], but the [columnSelector] closure accepts the current table * object [T] as the parameter, so what we get in the closure by `it` is the table object instead of an entity - * element. Besides, the function’s return type is a tuple of `ColumnDeclaring`s, and we should return some + * element. Besides, the closure’s return type is a tuple of `ColumnDeclaring`s, and we should return some * columns or expressions to customize the `select` clause of the generated SQL. * * Ktorm supports selecting two or more columns, we just need to wrap our selected columns by [tupleOf] @@ -166,7 +118,7 @@ fun generateMapColumns(writer: java.io.Writer, tupleNumber: Int) { * * This function is similar to [EntitySequence.mapTo], but the [columnSelector] closure accepts the current table * object [T] as the parameter, so what we get in the closure by `it` is the table object instead of an entity - * element. Besides, the function’s return type is a tuple of `ColumnDeclaring`s, and we should return some + * element. Besides, the closure’s return type is a tuple of `ColumnDeclaring`s, and we should return some * columns or expressions to customize the `select` clause of the generated SQL. * * Ktorm supports selecting two or more columns, we just need to wrap our selected columns by [tupleOf] @@ -210,25 +162,6 @@ fun generateAggregateColumns(writer: java.io.Writer, tupleNumber: Int) { writer.write(""" - /** - * Perform a tuple of aggregations given by [aggregationSelector] for all elements in the sequence, - * and return the aggregate results. - * - * The operation is terminal. - * - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return a tuple of the aggregate results. - */ - @Deprecated( - message = "This function will be removed in the future. Please use aggregateColumns { .. } instead.", - replaceWith = ReplaceWith("aggregateColumns(aggregationSelector)") - ) - public inline fun , $typeParams> EntitySequence.aggregateColumns$tupleNumber( - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): Tuple$tupleNumber<$resultTypes> { - return aggregateColumns(aggregationSelector) - } - /** * Perform a tuple of aggregations given by [aggregationSelector] for all elements in the sequence, * and return the aggregate results. @@ -277,48 +210,6 @@ fun generateGroupingAggregateColumns(writer: java.io.Writer, tupleNumber: Int) { writer.write(""" - /** - * Group elements from the source sequence by key and perform the given aggregations for elements in each group, - * then store the results in a new [Map]. - * - * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: - * `select key, aggregation from source group by key`. - * - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return a [Map] associating the key of each group with the results of aggregations of the group elements. - */ - @Deprecated( - message = "This function will be removed in the future. Please use aggregateColumns { .. } instead.", - replaceWith = ReplaceWith("aggregateColumns(aggregationSelector)") - ) - public inline fun , K : Any, $typeParams> EntityGrouping.aggregateColumns$tupleNumber( - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): Map> { - return aggregateColumns(aggregationSelector) - } - - /** - * Group elements from the source sequence by key and perform the given aggregations for elements in each group, - * then store the results in the [destination] map. - * - * The key for each group is provided by the [EntityGrouping.keySelector] function, and the generated SQL is like: - * `select key, aggregation from source group by key`. - * - * @param destination a [MutableMap] used to store the results. - * @param aggregationSelector a function that accepts the source table and returns a tuple of aggregate expressions. - * @return the [destination] map associating the key of each group with the result of aggregations of the group elements. - */ - @Deprecated( - message = "This function will be removed in the future. Please use aggregateColumns(destination) { .. } instead.", - replaceWith = ReplaceWith("aggregateColumns(destination, aggregationSelector)") - ) - public inline fun , K : Any, $typeParams, M> EntityGrouping.aggregateColumns${tupleNumber}To( - destination: M, - aggregationSelector: (T) -> Tuple$tupleNumber<$columnDeclarings> - ): M where M : MutableMap> { - return aggregateColumnsTo(destination, aggregationSelector) - } - /** * Group elements from the source sequence by key and perform the given aggregations for elements in each group, * then store the results in a new [Map]. From ed3e8c41e689098e03e779126f68994256712a45 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 00:58:54 +0800 Subject: [PATCH 099/126] rm deprecated api --- .../kotlin/org/ktorm/entity/EntitySequence.kt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt index 2073203d..ccb53822 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntitySequence.kt @@ -598,21 +598,6 @@ public inline fun , C, R> EntitySequence.mapColu return Query(database, expr).mapNotNullTo(destination) { row -> column.sqlType.getResult(row, 1) } } -/** - * Return a sequence customizing the `order by` clause of the internal query. - * - * The operation is intermediate. - */ -@Deprecated( - message = "This function is deprecated, use sortedBy({ it.col1.asc() }, { it.col2.desc() }) instead.", - replaceWith = ReplaceWith("sortedBy") -) -public inline fun > EntitySequence.sorted( - selector: (T) -> List -): EntitySequence { - return this.withExpression(expression.copy(orderBy = selector(sourceTable))) -} - /** * Return a sequence sorting elements by multiple columns, in ascending or descending order. For example, * `sortedBy({ it.col1.asc() }, { it.col2.desc() })`. From e9aa05736f491e5450e492690d50349d9a7d4d47 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 01:01:03 +0800 Subject: [PATCH 100/126] rm deprecated api --- .../main/kotlin/org/ktorm/schema/SqlTypes.kt | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt index f2e89f24..ddd44209 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt @@ -42,7 +42,7 @@ public object BooleanSqlType : SqlType(Types.BOOLEAN, "boolean") { ps.setBoolean(index, parameter) } - override fun doGetResult(rs: ResultSet, index: Int): Boolean? { + override fun doGetResult(rs: ResultSet, index: Int): Boolean { return rs.getBoolean(index) } } @@ -62,7 +62,7 @@ public object IntSqlType : SqlType(Types.INTEGER, "int") { ps.setInt(index, parameter) } - override fun doGetResult(rs: ResultSet, index: Int): Int? { + override fun doGetResult(rs: ResultSet, index: Int): Int { return rs.getInt(index) } } @@ -86,7 +86,7 @@ public object ShortSqlType : SqlType(Types.SMALLINT, "smallint") { ps.setShort(index, parameter) } - override fun doGetResult(rs: ResultSet, index: Int): Short? { + override fun doGetResult(rs: ResultSet, index: Int): Short { return rs.getShort(index) } } @@ -106,7 +106,7 @@ public object LongSqlType : SqlType(Types.BIGINT, "bigint") { ps.setLong(index, parameter) } - override fun doGetResult(rs: ResultSet, index: Int): Long? { + override fun doGetResult(rs: ResultSet, index: Int): Long { return rs.getLong(index) } } @@ -126,7 +126,7 @@ public object FloatSqlType : SqlType(Types.FLOAT, "float") { ps.setFloat(index, parameter) } - override fun doGetResult(rs: ResultSet, index: Int): Float? { + override fun doGetResult(rs: ResultSet, index: Int): Float { return rs.getFloat(index) } } @@ -146,7 +146,7 @@ public object DoubleSqlType : SqlType(Types.DOUBLE, "double") { ps.setDouble(index, parameter) } - override fun doGetResult(rs: ResultSet, index: Int): Double? { + override fun doGetResult(rs: ResultSet, index: Int): Double { return rs.getDouble(index) } } @@ -470,22 +470,6 @@ public object YearSqlType : SqlType(Types.INTEGER, "int") { } } -/** - * Define a column typed of [EnumSqlType]. - * - * @param name the column's name. - * @param typeRef the generic type information of this column, generally created by [org.ktorm.schema.typeRef]. - * @return the registered column. - */ -@Suppress("UNCHECKED_CAST") -@Deprecated( - message = "This function will be removed in the future. Please use enum(name) instead.", - replaceWith = ReplaceWith("enum(name)") -) -public fun > BaseTable<*>.enum(name: String, typeRef: TypeReference): Column { - return registerColumn(name, EnumSqlType(typeRef.referencedType as Class)) -} - /** * Define a column typed of [EnumSqlType]. * From 3a9b5b2e93f46320a08d63dfd7db0a1378d8a62c Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 01:02:51 +0800 Subject: [PATCH 101/126] rm deprecated api --- .../kotlin/org/ktorm/global/EntitySequence.kt | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt b/ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt index 46052375..1d4ba250 100644 --- a/ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt +++ b/ktorm-global/src/main/kotlin/org/ktorm/global/EntitySequence.kt @@ -29,28 +29,6 @@ public fun > T.asSequence(withReferences: Boolean = tr return Database.global.sequenceOf(this, withReferences) } -/** - * Insert the given entity into this sequence and return the affected record number. - * - * If we use an auto-increment key in our table, we need to tell Ktorm which is the primary key by calling - * [Table.primaryKey] while registering columns, then this function will obtain the generated key from the - * database and fill it into the corresponding property after the insertion completes. But this requires us - * not to set the primary key’s value beforehand, otherwise, if you do that, the given value will be inserted - * into the database, and no keys generated. - * - * Note that after calling this function, the [entity] will be ATTACHED to the current database. - * - * @see Entity.flushChanges - * @see Entity.delete - */ -@Deprecated( - message = "This function will be removed in the future. Please use addEntity(entity) instead.", - replaceWith = ReplaceWith("addEntity(entity)") -) -public fun > Table.add(entity: E): Int { - return Database.global.sequenceOf(this).add(entity) -} - /** * Insert the given entity into this sequence and return the affected record number. * From 416d06b923e8949b3b2e22e2a7d93aae2c45537f Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 01:04:28 +0800 Subject: [PATCH 102/126] rm deprecated api --- .../kotlin/org/ktorm/jackson/JsonSqlType.kt | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt index e950e97c..622b3038 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt @@ -35,26 +35,6 @@ public val sharedObjectMapper: ObjectMapper = ObjectMapper() .registerModule(KotlinModule()) .registerModule(JavaTimeModule()) -/** - * Define a column typed of [JsonSqlType]. - * - * @param name the column's name. - * @param typeRef the generic type information of this column, generally created by [org.ktorm.schema.typeRef]. - * @param mapper the object mapper used to serialize column values to JSON strings and deserialize them. - * @return the registered column. - */ -@Deprecated( - message = "This function will be removed in the future. Please use json(name, mapper) instead.", - replaceWith = ReplaceWith("json(name, mapper)") -) -public fun BaseTable<*>.json( - name: String, - typeRef: TypeReference, - mapper: ObjectMapper = sharedObjectMapper -): Column { - return registerColumn(name, JsonSqlType(mapper, mapper.constructType(typeRef.referencedType))) -} - /** * Define a column typed of [JsonSqlType]. * From b5385ed94428b24dde397098c671a10073a5adb4 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 01:19:27 +0800 Subject: [PATCH 103/126] rm deprecated api --- .../org/ktorm/support/mysql/BulkInsert.kt | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt index 9f2ae290..fb1a07a5 100644 --- a/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt +++ b/ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/BulkInsert.kt @@ -93,7 +93,7 @@ public fun > Database.bulkInsert( val builder = BulkInsertStatementBuilder(table).apply(block) val expression = AliasRemover.visit( - BulkInsertExpression(table.asExpression(), builder.assignments, builder.updateAssignments) + BulkInsertExpression(table.asExpression(), builder.assignments) ) return executeUpdate(expression) @@ -159,21 +159,8 @@ public fun > Database.bulkInsertOrUpdate( * DSL builder for bulk insert statements. */ @KtormDsl -public class BulkInsertStatementBuilder>(table: T) : BulkInsertOrUpdateStatementBuilder(table) { - - @Deprecated("This function will be removed in the future, please use bulkInsertOrUpdate instead of bulkInsert.") - override fun onDuplicateKey(block: BulkInsertOrUpdateOnDuplicateKeyClauseBuilder.(T) -> Unit) { - super.onDuplicateKey(block) - } -} - -/** - * DSL builder for bulk insert or update statements. - */ -@KtormDsl -public open class BulkInsertOrUpdateStatementBuilder>(internal val table: T) { +public open class BulkInsertStatementBuilder>(internal val table: T) { internal val assignments = ArrayList>>() - internal val updateAssignments = ArrayList>() /** * Add the assignments of a new row to the bulk insert. @@ -190,11 +177,19 @@ public open class BulkInsertOrUpdateStatementBuilder>(internal throw IllegalArgumentException("Every item in a batch operation must be the same.") } } +} + +/** + * DSL builder for bulk insert or update statements. + */ +@KtormDsl +public class BulkInsertOrUpdateStatementBuilder>(table: T) : BulkInsertStatementBuilder(table) { + internal val updateAssignments = ArrayList>() /** * Specify the update assignments while any key conflict exists. */ - public open fun onDuplicateKey(block: BulkInsertOrUpdateOnDuplicateKeyClauseBuilder.(T) -> Unit) { + public fun onDuplicateKey(block: BulkInsertOrUpdateOnDuplicateKeyClauseBuilder.(T) -> Unit) { val builder = BulkInsertOrUpdateOnDuplicateKeyClauseBuilder() builder.block(table) updateAssignments += builder.assignments From 863696e4c24d18db4f7f1c83b93e740261c1a5a7 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 01:22:43 +0800 Subject: [PATCH 104/126] rm deprecated api --- .../org/ktorm/support/postgresql/InsertOrUpdate.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt index f2f2d383..87f3f121 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt @@ -443,17 +443,6 @@ public class InsertOrUpdateStatementBuilder : PostgreSqlAssignmentsBuilder() { internal val updateAssignments = ArrayList>() internal var doNothing = false - /** - * Specify the update assignments while any key conflict exists. - */ - @Deprecated( - message = "This function will be removed in the future, please use onConflict { } instead", - replaceWith = ReplaceWith("onConflict(columns, block)") - ) - public fun onDuplicateKey(vararg columns: Column<*>, block: AssignmentsBuilder.() -> Unit) { - onConflict(*columns, block = block) - } - /** * Specify the update assignments while any key conflict exists. */ From 11291e222c159e4cb39a12180b008a1184738ebb Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 01:25:04 +0800 Subject: [PATCH 105/126] rm deprecated api --- .../org/ktorm/support/postgresql/SqlTypes.kt | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index 135e481a..f0894b68 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -80,40 +80,3 @@ public object TextArraySqlType : SqlType(Types.ARRAY, "text[]") { } } } - -/** - * Define a column typed of [PgEnumType]. - * !Note Enums are case sensitive and must match what is in the db - * - * @param The Java enum type to use - * @param name the column's name. - * @return the registered column. - */ -@Suppress("DEPRECATION") -@Deprecated( - message = "Will remove in the future, please use `enum` instead", - replaceWith = ReplaceWith(expression = "enum(name)", imports = ["org.ktorm.schema.enum"]) -) -public inline fun > BaseTable<*>.pgEnum(name: String): Column { - return registerColumn(name, PgEnumType(C::class.java)) -} - -/** - * [SqlType] implementation represents PostgreSQL `enum` type. - * @see datatype-enum - */ -@Deprecated( - message = "Will remove in the future, please use `EnumSqlType` instead", - replaceWith = ReplaceWith(expression = "EnumSqlType", imports = ["org.ktorm.schema.EnumSqlType"]) -) -public class PgEnumType>(private val enumClass: Class) : SqlType(Types.OTHER, enumClass.name) { - private val valueOf = enumClass.getDeclaredMethod("valueOf", String::class.java) - - override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: C) { - ps.setObject(index, parameter.name, Types.OTHER) - } - - override fun doGetResult(rs: ResultSet, index: Int): C? { - return rs.getString(index)?.takeIf { it.isNotBlank() }?.let { enumClass.cast(valueOf(null, it)) } - } -} From 1b60a01519bd455ac7635be376a868b0bb549487 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 26 May 2022 01:34:18 +0800 Subject: [PATCH 106/126] fix compile error --- .../test/kotlin/org/ktorm/dsl/QueryTest.kt | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt index 0bae2d93..b7af2f0d 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/dsl/QueryTest.kt @@ -2,15 +2,7 @@ package org.ktorm.dsl import org.junit.Test import org.ktorm.BaseTest -import org.ktorm.entity.filter -import org.ktorm.entity.first -import org.ktorm.entity.forUpdate -import org.ktorm.entity.sequenceOf import org.ktorm.expression.ScalarExpression -import java.util.concurrent.ExecutionException -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException import kotlin.random.Random /** @@ -278,34 +270,6 @@ class QueryTest : BaseTest() { println(query.sql) } - @Test - @Suppress("DEPRECATION") - fun testSelectForUpdate() { - database.useTransaction { - val employee = database - .sequenceOf(Employees, withReferences = false) - .filter { it.id eq 1 } - .forUpdate() - .first() - - val future = Executors.newSingleThreadExecutor().submit { - employee.name = "vince" - employee.flushChanges() - } - - try { - future.get(5, TimeUnit.SECONDS) - throw AssertionError() - } catch (e: ExecutionException) { - // Expected, the record is locked. - e.printStackTrace() - } catch (e: TimeoutException) { - // Expected, the record is locked. - e.printStackTrace() - } - } - } - @Test fun testFlatMap() { val names = database From 16a576dda7595af35f8eb0503301be46b8c6d8ae Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 28 May 2022 01:33:08 +0800 Subject: [PATCH 107/126] refactor cube sql type --- .../ktorm/support/postgresql/EarthDistance.kt | 4 +- .../org/ktorm/support/postgresql/SqlTypes.kt | 67 +++++++++---------- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt index f1d52b1e..c1496e4d 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -121,7 +121,7 @@ public infix fun ColumnDeclaring.containedIn(argument: ColumnDeclaring.containedIn(argument: Cube): CubeExpression { - return this.containedIn(ArgumentExpression(argument, PGCubeType)) + return this.containedIn(ArgumentExpression(argument, CubeSqlType)) } /** @@ -238,7 +238,7 @@ public fun earthBox( FunctionExpression( functionName = "earth_box", arguments = listOf(point.asExpression(), radius.asExpression()), - sqlType = PGCubeType + sqlType = CubeSqlType ) /** diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index 8b8c7677..74e0c627 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -37,7 +37,7 @@ public typealias TextArray = Array /** * Define a column typed [HStoreSqlType]. */ -public fun BaseTable.hstore(name: String): Column { +public fun BaseTable<*>.hstore(name: String): Column { return registerColumn(name, HStoreSqlType) } @@ -58,7 +58,7 @@ public object HStoreSqlType : SqlType(Types.OTHER, "hstore") { /** * Define a column typed [TextArraySqlType]. */ -public fun BaseTable.textArray(name: String): Column { +public fun BaseTable<*>.textArray(name: String): Column { return registerColumn(name, TextArraySqlType) } @@ -120,34 +120,34 @@ public fun BaseTable<*>.earth(name: String): Column = registerColumn(name * Part of PostgreSQL's `cube` SQL extension. * https://www.postgresql.org/docs/9.5/cube.html */ -public data class Cube( - public val first: DoubleArray, - public val second: DoubleArray -) { +public data class Cube(val x: DoubleArray, val y: DoubleArray) { init { - if (first.size != second.size) { - throw IllegalArgumentException("Cube should be initialized with same size arrays") + if (x.size != y.size) { + throw IllegalArgumentException("x and y should have same dimensions.") } } - override fun toString(): String { - return "${first.contentToString()}, ${second.contentToString()}" - .replace('[', '(') - .replace(']', ')') - } - override fun equals(other: Any?): Boolean { - if (other !is Cube) return false - if (!other.first.contentEquals(this.first)) return false - if (!other.second.contentEquals(this.second)) return false - return true + return other is Cube && x.contentEquals(other.x) && y.contentEquals(other.y) } override fun hashCode(): Int { - var result = first.contentHashCode() - result = 31 * result + second.contentHashCode() + var result = 1 + result = 31 * result + x.contentHashCode() + result = 31 * result + y.contentHashCode() return result } + + override fun toString(): String { + return "(${x.joinToString(", ")}), (${y.joinToString(", ")})" + } +} + +/** + * Define a column typed [CubeSqlType]. + */ +public fun BaseTable<*>.cube(name: String): Column { + return registerColumn(name, CubeSqlType) } /** @@ -155,28 +155,21 @@ public data class Cube( * Part of PostgreSQL's `cube` SQL extension. * https://www.postgresql.org/docs/9.5/cube.html */ -public object PGCubeType : SqlType(Types.OTHER, "cube") { +public object CubeSqlType : SqlType(Types.OTHER, "cube") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Cube) { ps.setObject(index, parameter, Types.OTHER) } override fun doGetResult(rs: ResultSet, index: Int): Cube? { - return rs.getObject(index)?.let { pgObj -> - (pgObj as PGobject).value // (-1.1, 2.2, 3.0), (1.1, -2.2, 0.3) - .replace("(", "") - .replace(")", "") // -1.1, 2.2, 3.0, 1.1, -2.2, 0.3 - .split(',') - .let { rawValues -> - Cube( - rawValues.take(rawValues.size / 2).map { it.toDouble() }.toDoubleArray(), - rawValues.takeLast(rawValues.size / 2).map { it.toDouble() }.toDoubleArray() - ) - } + val obj = rs.getObject(index) as PGobject? + if (obj == null) { + return null + } else { + // (1, 2, 3), (4, 5, 6) + val numbers = obj.value.replace("(", "").replace(")", "").split(",").map { it.trim().toDouble() } + val (x, y) = numbers.chunked(numbers.size / 2).map { it.toDoubleArray() } + return Cube(x, y) } } } - -/** - * Define a column typed [PGCubeType]. - */ -public fun BaseTable<*>.cube(name: String): Column = registerColumn(name, PGCubeType) From 14ecb4dac4170248d8100e1fc919599d5d9fd419 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 28 May 2022 21:05:37 +0800 Subject: [PATCH 108/126] refactor earth sql type --- .../ktorm/support/postgresql/EarthDistance.kt | 14 ++-- .../org/ktorm/support/postgresql/SqlTypes.kt | 69 ++++++++++--------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt index c1496e4d..65c110d9 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -91,7 +91,7 @@ public infix fun ColumnDeclaring.contains(argument: ColumnDeclaring */ @JvmName("containsEarth") public infix fun ColumnDeclaring.contains(argument: Earth): CubeExpression { - return this.contains(ArgumentExpression(argument, PGEarthType)) + return this.contains(ArgumentExpression(argument, EarthSqlType)) } /** @@ -151,7 +151,7 @@ public fun llToEarth( FunctionExpression( functionName = "ll_to_earth", arguments = listOf(lat.asExpression(), lng.asExpression()), - sqlType = PGEarthType + sqlType = EarthSqlType ) /** @@ -206,7 +206,7 @@ public fun earthDistance( point1: ColumnDeclaring, point2: Earth ): FunctionExpression = - earthDistance(point1, ArgumentExpression(point2, PGEarthType)) + earthDistance(point1, ArgumentExpression(point2, EarthSqlType)) /** * Get distance between 2 points on earth. @@ -215,7 +215,7 @@ public fun earthDistance( point1: Earth, point2: ColumnDeclaring ): FunctionExpression = - earthDistance(ArgumentExpression(point1, PGEarthType), point2) + earthDistance(ArgumentExpression(point1, EarthSqlType), point2) /** * Get distance between 2 points on earth. @@ -224,7 +224,7 @@ public fun earthDistance( point1: Earth, point2: Earth ): FunctionExpression = - earthDistance(ArgumentExpression(point1, PGEarthType), ArgumentExpression(point2, PGEarthType)) + earthDistance(ArgumentExpression(point1, EarthSqlType), ArgumentExpression(point2, EarthSqlType)) /** * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. @@ -257,7 +257,7 @@ public fun earthBox( point: Earth, radius: ColumnDeclaring ): FunctionExpression = - earthBox(ArgumentExpression(point, PGEarthType), radius) + earthBox(ArgumentExpression(point, EarthSqlType), radius) /** * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. @@ -266,4 +266,4 @@ public fun earthBox( point: Earth, radius: Double ): FunctionExpression = - earthBox(ArgumentExpression(point, PGEarthType), ArgumentExpression(radius, DoubleSqlType)) + earthBox(ArgumentExpression(point, EarthSqlType), ArgumentExpression(radius, DoubleSqlType)) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt index 74e0c627..d0ec99aa 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/SqlTypes.kt @@ -82,39 +82,6 @@ public object TextArraySqlType : SqlType(Types.ARRAY, "text[]") { } } -/** - * Represents location of a point on the surface of the Earth. - * Part of PostgreSQL's `earthdistance` extension. - * https://www.postgresql.org/docs/12/earthdistance.html - */ -public typealias Earth = Triple - -/** - * Represents a point on Earth's surface - * Part of PostgreSQL's `earthdistance` SQL extension. - */ -public object PGEarthType : SqlType(Types.OTHER, "earth") { - override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Earth) { - ps.setObject(index, parameter, Types.OTHER) - } - - override fun doGetResult(rs: ResultSet, index: Int): Earth? { - return rs.getObject(index)?.let { - (it as PGobject).value - .substring(1, it.value.length - 1) - .split(",") - .let { rawNumbers -> - Earth(rawNumbers[0].toDouble(), rawNumbers[1].toDouble(), rawNumbers[2].toDouble()) - } - } - } -} - -/** - * Define a column typed [PGEarthType]. - */ -public fun BaseTable<*>.earth(name: String): Column = registerColumn(name, PGEarthType) - /** * Represents a box suitable for an indexed search using the cube @> operator. * Part of PostgreSQL's `cube` SQL extension. @@ -173,3 +140,39 @@ public object CubeSqlType : SqlType(Types.OTHER, "cube") { } } } + +/** + * Cube-based earth abstraction, using 3 coordinates representing the x, y, and z distance from the center of the Earth. + * Part of PostgreSQL's `earthdistance` extension. + * https://www.postgresql.org/docs/12/earthdistance.html + */ +public typealias Earth = Triple + +/** + * Define a column typed [EarthSqlType]. + */ +public fun BaseTable<*>.earth(name: String): Column { + return registerColumn(name, EarthSqlType) +} + +/** + * Cube-based earth abstraction, using 3 coordinates representing the x, y, and z distance from the center of the Earth. + * Part of PostgreSQL's `earthdistance` SQL extension. + */ +public object EarthSqlType : SqlType(Types.OTHER, "earth") { + + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Earth) { + ps.setObject(index, parameter, Types.OTHER) + } + + override fun doGetResult(rs: ResultSet, index: Int): Earth? { + val obj = rs.getObject(index) as PGobject? + if (obj == null) { + return null + } else { + // (1, 2, 3) + val (x, y, z) = obj.value.removeSurrounding("(", ")").split(",").map { it.trim().toDouble() } + return Earth(x, y, z) + } + } +} From 8c652eaa3a78111db078a5e4070e9f9b396ba126 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 28 May 2022 21:39:07 +0800 Subject: [PATCH 109/126] rm infix keyword for cube operators --- .../ktorm/support/postgresql/EarthDistance.kt | 35 ++++++++++--------- .../support/postgresql/EarthdistanceTest.kt | 16 ++++----- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt index 65c110d9..1d3a30c4 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -28,6 +28,7 @@ import org.ktorm.schema.SqlType * Enum for 'cube' and 'earthdistance' binary operators. */ public enum class CubeExpressionType(private val value: String) { + /** * Cube overlaps operator, translated to the && operator in PostgreSQL. */ @@ -67,14 +68,14 @@ public data class CubeExpression( /** * Cube contains operator, translated to the @> operator in PostgreSQL. */ -public infix fun ColumnDeclaring.contains(argument: ColumnDeclaring): CubeExpression { - return CubeExpression(CubeExpressionType.CONTAINS, asExpression(), argument.asExpression(), BooleanSqlType) +public fun ColumnDeclaring.contains(expr: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.CONTAINS, asExpression(), expr.asExpression(), BooleanSqlType) } /** * Cube contains operator, translated to the @> operator in PostgreSQL. */ -public infix fun ColumnDeclaring.contains(argument: Cube): CubeExpression { +public fun ColumnDeclaring.contains(argument: Cube): CubeExpression { return this.contains(wrapArgument(argument)) } @@ -82,59 +83,59 @@ public infix fun ColumnDeclaring.contains(argument: Cube): CubeExpression< * Cube contains operator, translated to the @> operator in PostgreSQL. */ @JvmName("containsEarth") -public infix fun ColumnDeclaring.contains(argument: ColumnDeclaring): CubeExpression { - return CubeExpression(CubeExpressionType.CONTAINS, asExpression(), argument.asExpression(), BooleanSqlType) +public fun ColumnDeclaring.contains(expr: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.CONTAINS, asExpression(), expr.asExpression(), BooleanSqlType) } /** * Cube contains operator, translated to the @> operator in PostgreSQL. */ @JvmName("containsEarth") -public infix fun ColumnDeclaring.contains(argument: Earth): CubeExpression { +public fun ColumnDeclaring.contains(argument: Earth): CubeExpression { return this.contains(ArgumentExpression(argument, EarthSqlType)) } /** * Cube contained in operator, translated to the <@ operator in PostgreSQL. */ -public infix fun ColumnDeclaring.containedIn(argument: ColumnDeclaring): CubeExpression { - return CubeExpression(CubeExpressionType.CONTAINED_IN, asExpression(), argument.asExpression(), BooleanSqlType) +public fun ColumnDeclaring.containedIn(expr: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.CONTAINED_IN, asExpression(), expr.asExpression(), BooleanSqlType) } /** * Cube contained in operator, translated to the <@ operator in PostgreSQL. */ -public infix fun ColumnDeclaring.containedIn(argument: Cube): CubeExpression { +public fun ColumnDeclaring.containedIn(argument: Cube): CubeExpression { return this.containedIn(wrapArgument(argument)) } /** * Cube contained in operator, translated to the <@ operator in PostgreSQL. */ -@JvmName("earthContainedInCube") -public infix fun ColumnDeclaring.containedIn(argument: ColumnDeclaring): CubeExpression { - return CubeExpression(CubeExpressionType.CONTAINED_IN, asExpression(), argument.asExpression(), BooleanSqlType) +@JvmName("earthContainedIn") +public fun ColumnDeclaring.containedIn(expr: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.CONTAINED_IN, asExpression(), expr.asExpression(), BooleanSqlType) } /** * Cube contained in operator, translated to the <@ operator in PostgreSQL. */ -@JvmName("earthContainedInCube") -public infix fun ColumnDeclaring.containedIn(argument: Cube): CubeExpression { +@JvmName("earthContainedIn") +public fun ColumnDeclaring.containedIn(argument: Cube): CubeExpression { return this.containedIn(ArgumentExpression(argument, CubeSqlType)) } /** * Cube overlap operator, translated to the && operator in PostgreSQL. */ -public infix fun ColumnDeclaring.overlaps(argument: ColumnDeclaring): CubeExpression { - return CubeExpression(CubeExpressionType.OVERLAP, asExpression(), argument.asExpression(), BooleanSqlType) +public fun ColumnDeclaring.overlaps(expr: ColumnDeclaring): CubeExpression { + return CubeExpression(CubeExpressionType.OVERLAP, asExpression(), expr.asExpression(), BooleanSqlType) } /** * Cube overlap operator, translated to the && operator in PostgreSQL. */ -public infix fun ColumnDeclaring.overlaps(argument: Cube): CubeExpression { +public fun ColumnDeclaring.overlaps(argument: Cube): CubeExpression { return this.overlaps(wrapArgument(argument)) } diff --git a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt index 7e8f88e2..48eac081 100644 --- a/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt +++ b/ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/EarthdistanceTest.kt @@ -112,10 +112,10 @@ class EarthdistanceTest : BaseTest() { database.sequenceOf(TestTable).add(record) - val t1 = (TestTable.c contains cube3).aliased("t1") // true - val t2 = (TestTable.c containedIn cube2).aliased("t2") // false - val t3 = (TestTable.c overlaps cube2).aliased("t3") // true - val t4 = (TestTable.c eq cube1).aliased("t4") // true + val t1 = TestTable.c.contains(cube3).aliased("t1") // true + val t2 = TestTable.c.containedIn(cube2).aliased("t2") // false + val t3 = TestTable.c.overlaps(cube2).aliased("t3") // true + val t4 = TestTable.c.eq(cube1).aliased("t4") // true database.from(TestTable) .select( t1, t2, t3, t4 @@ -147,10 +147,10 @@ class EarthdistanceTest : BaseTest() { val box = earthBox(llToEarth(0.0, 0.0), 10000.0) val pointInBox = llToEarth(0.01, 0.01) val pointOutsideBox = llToEarth(10.0, 10.0) - val check1 = (box contains pointInBox).aliased("c1") - val check1r = (pointInBox containedIn box).aliased("c1r") - val check2 = (box contains pointOutsideBox).aliased("c2") - val check2r = (pointOutsideBox containedIn box).aliased("c2r") + val check1 = box.contains(pointInBox).aliased("c1") + val check1r = pointInBox.containedIn(box).aliased("c1r") + val check2 = box.contains(pointOutsideBox).aliased("c2") + val check2r = pointOutsideBox.containedIn(box).aliased("c2r") database.from(TestTable) .select(check1, check2, check1r, check2r) .map { row -> From 3b74617fffda234871ad7cd847b720d7f15740bd Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 28 May 2022 22:12:27 +0800 Subject: [PATCH 110/126] refactor earth functions --- .../ktorm/support/postgresql/EarthDistance.kt | 145 +++++++++--------- 1 file changed, 75 insertions(+), 70 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt index 1d3a30c4..ee04ce58 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/EarthDistance.kt @@ -145,126 +145,131 @@ public fun ColumnDeclaring.overlaps(argument: Cube): CubeExpression, - lng: ColumnDeclaring -): FunctionExpression = - FunctionExpression( +public fun llToEarth(lat: ColumnDeclaring, lng: ColumnDeclaring): FunctionExpression { + // ll_to_earth(lat, lng) + return FunctionExpression( functionName = "ll_to_earth", arguments = listOf(lat.asExpression(), lng.asExpression()), sqlType = EarthSqlType ) +} /** * Returns the location of a point on the surface of the Earth * given its latitude (argument 1) and longitude (argument 2) in degrees. + * + * Function from earthdistance extension */ -public fun llToEarth( - lat: ColumnDeclaring, - lng: Double -): FunctionExpression = - llToEarth(lat, ArgumentExpression(lng, DoubleSqlType)) +public fun llToEarth(lat: ColumnDeclaring, lng: Double): FunctionExpression { + return llToEarth(lat, ArgumentExpression(lng, DoubleSqlType)) +} /** * Returns the location of a point on the surface of the Earth * given its latitude (argument 1) and longitude (argument 2) in degrees. + * + * Function from earthdistance extension */ -public fun llToEarth( - lat: Double, - lng: ColumnDeclaring -): FunctionExpression = - llToEarth(ArgumentExpression(lat, DoubleSqlType), lng) +public fun llToEarth(lat: Double, lng: ColumnDeclaring): FunctionExpression { + return llToEarth(ArgumentExpression(lat, DoubleSqlType), lng) +} /** * Returns the location of a point on the surface of the Earth * given its latitude (argument 1) and longitude (argument 2) in degrees. + * + * Function from earthdistance extension */ -public fun llToEarth( - lat: Double, - lng: Double -): FunctionExpression = - llToEarth(ArgumentExpression(lat, DoubleSqlType), ArgumentExpression(lng, DoubleSqlType)) +public fun llToEarth(lat: Double, lng: Double): FunctionExpression { + return llToEarth(ArgumentExpression(lat, DoubleSqlType), ArgumentExpression(lng, DoubleSqlType)) +} /** - * Get distance between 2 points on earth. + * Returns the great circle distance between two points on the surface of the Earth. * - * Function from earthdistance extension, `earth_distance(point1, point2)` in SQL. + * Function from earthdistance extension, `earth_distance(p1, p2)` in SQL. */ -public fun earthDistance( - point1: ColumnDeclaring, - point2: ColumnDeclaring -): FunctionExpression = - FunctionExpression( +public fun earthDistance(p1: ColumnDeclaring, p2: ColumnDeclaring): FunctionExpression { + // earth_distance(p1, p2) + return FunctionExpression( functionName = "earth_distance", - arguments = listOf(point1.asExpression(), point2.asExpression()), + arguments = listOf(p1.asExpression(), p2.asExpression()), sqlType = DoubleSqlType ) +} /** - * Get distance between 2 points on earth. + * Returns the great circle distance between two points on the surface of the Earth. + * + * Function from earthdistance extension, `earth_distance(p1, p2)` in SQL. */ -public fun earthDistance( - point1: ColumnDeclaring, - point2: Earth -): FunctionExpression = - earthDistance(point1, ArgumentExpression(point2, EarthSqlType)) +public fun earthDistance(p1: ColumnDeclaring, p2: Earth): FunctionExpression { + return earthDistance(p1, ArgumentExpression(p2, EarthSqlType)) +} /** - * Get distance between 2 points on earth. + * Returns the great circle distance between two points on the surface of the Earth. + * + * Function from earthdistance extension, `earth_distance(p1, p2)` in SQL. */ -public fun earthDistance( - point1: Earth, - point2: ColumnDeclaring -): FunctionExpression = - earthDistance(ArgumentExpression(point1, EarthSqlType), point2) +public fun earthDistance(p1: Earth, p2: ColumnDeclaring): FunctionExpression { + return earthDistance(ArgumentExpression(p1, EarthSqlType), p2) +} /** - * Get distance between 2 points on earth. + * Returns the great circle distance between two points on the surface of the Earth. + * + * Function from earthdistance extension, `earth_distance(p1, p2)` in SQL. */ -public fun earthDistance( - point1: Earth, - point2: Earth -): FunctionExpression = - earthDistance(ArgumentExpression(point1, EarthSqlType), ArgumentExpression(point2, EarthSqlType)) +public fun earthDistance(p1: Earth, p2: Earth): FunctionExpression { + return earthDistance(ArgumentExpression(p1, EarthSqlType), ArgumentExpression(p2, EarthSqlType)) +} /** - * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. + * Returns a box suitable for an indexed search using the cube @> operator for points within a given great circle + * distance of a location. Some points in this box are further than the specified great circle distance from the + * location, so a second check using earth_distance should be included in the query. * * Function from earthdistance extension, `earth_box(point, radius)` in SQL. */ -public fun earthBox( - point: ColumnDeclaring, - radius: ColumnDeclaring -): FunctionExpression = - FunctionExpression( +public fun earthBox(point: ColumnDeclaring, radius: ColumnDeclaring): FunctionExpression { + // earth_box(point, radius) + return FunctionExpression( functionName = "earth_box", arguments = listOf(point.asExpression(), radius.asExpression()), sqlType = CubeSqlType ) +} /** - * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. + * Returns a box suitable for an indexed search using the cube @> operator for points within a given great circle + * distance of a location. Some points in this box are further than the specified great circle distance from the + * location, so a second check using earth_distance should be included in the query. + * + * Function from earthdistance extension, `earth_box(point, radius)` in SQL. */ -public fun earthBox( - point: ColumnDeclaring, - radius: Double -): FunctionExpression = - earthBox(point, ArgumentExpression(radius, DoubleSqlType)) +public fun earthBox(point: ColumnDeclaring, radius: Double): FunctionExpression { + return earthBox(point, ArgumentExpression(radius, DoubleSqlType)) +} /** - * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. + * Returns a box suitable for an indexed search using the cube @> operator for points within a given great circle + * distance of a location. Some points in this box are further than the specified great circle distance from the + * location, so a second check using earth_distance should be included in the query. + * + * Function from earthdistance extension, `earth_box(point, radius)` in SQL. */ -public fun earthBox( - point: Earth, - radius: ColumnDeclaring -): FunctionExpression = - earthBox(ArgumentExpression(point, EarthSqlType), radius) +public fun earthBox(point: Earth, radius: ColumnDeclaring): FunctionExpression { + return earthBox(ArgumentExpression(point, EarthSqlType), radius) +} /** - * Creates a bounding cube, sized to contain all the points that are not farther than radius meters from a given point. + * Returns a box suitable for an indexed search using the cube @> operator for points within a given great circle + * distance of a location. Some points in this box are further than the specified great circle distance from the + * location, so a second check using earth_distance should be included in the query. + * + * Function from earthdistance extension, `earth_box(point, radius)` in SQL. */ -public fun earthBox( - point: Earth, - radius: Double -): FunctionExpression = - earthBox(ArgumentExpression(point, EarthSqlType), ArgumentExpression(radius, DoubleSqlType)) +public fun earthBox(point: Earth, radius: Double): FunctionExpression { + return earthBox(ArgumentExpression(point, EarthSqlType), ArgumentExpression(radius, DoubleSqlType)) +} From 285cc7dd4658544dc8142570ca77d352faebf4c8 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 29 May 2022 00:02:48 +0800 Subject: [PATCH 111/126] refactor sql types --- .../main/kotlin/org/ktorm/schema/SqlTypes.kt | 21 +++++++- .../org/ktorm/support/postgresql/SqlTypes.kt | 54 ++++++++++--------- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt index ddd44209..1bf06705 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/SqlTypes.kt @@ -38,6 +38,7 @@ public fun BaseTable<*>.boolean(name: String): Column { * [SqlType] implementation represents `boolean` SQL type. */ public object BooleanSqlType : SqlType(Types.BOOLEAN, "boolean") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Boolean) { ps.setBoolean(index, parameter) } @@ -58,6 +59,7 @@ public fun BaseTable<*>.int(name: String): Column { * [SqlType] implementation represents `int` SQL type. */ public object IntSqlType : SqlType(Types.INTEGER, "int") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Int) { ps.setInt(index, parameter) } @@ -82,6 +84,7 @@ public fun BaseTable<*>.short(name: String): Column { * @since 3.1.0 */ public object ShortSqlType : SqlType(Types.SMALLINT, "smallint") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Short) { ps.setShort(index, parameter) } @@ -102,6 +105,7 @@ public fun BaseTable<*>.long(name: String): Column { * [SqlType] implementation represents `long` SQL type. */ public object LongSqlType : SqlType(Types.BIGINT, "bigint") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Long) { ps.setLong(index, parameter) } @@ -122,6 +126,7 @@ public fun BaseTable<*>.float(name: String): Column { * [SqlType] implementation represents `float` SQL type. */ public object FloatSqlType : SqlType(Types.FLOAT, "float") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Float) { ps.setFloat(index, parameter) } @@ -142,6 +147,7 @@ public fun BaseTable<*>.double(name: String): Column { * [SqlType] implementation represents `double` SQL type. */ public object DoubleSqlType : SqlType(Types.DOUBLE, "double") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Double) { ps.setDouble(index, parameter) } @@ -162,6 +168,7 @@ public fun BaseTable<*>.decimal(name: String): Column { * [SqlType] implementation represents `decimal` SQL type. */ public object DecimalSqlType : SqlType(Types.DECIMAL, "decimal") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: BigDecimal) { ps.setBigDecimal(index, parameter) } @@ -182,6 +189,7 @@ public fun BaseTable<*>.varchar(name: String): Column { * [SqlType] implementation represents `varchar` SQL type. */ public object VarcharSqlType : SqlType(Types.VARCHAR, "varchar") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: String) { ps.setString(index, parameter) } @@ -202,6 +210,7 @@ public fun BaseTable<*>.text(name: String): Column { * [SqlType] implementation represents `text` SQL type. */ public object TextSqlType : SqlType(Types.LONGVARCHAR, "text") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: String) { ps.setString(index, parameter) } @@ -222,6 +231,7 @@ public fun BaseTable<*>.blob(name: String): Column { * [SqlType] implementation represents `blob` SQL type. */ public object BlobSqlType : SqlType(Types.BLOB, "blob") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: ByteArray) { ps.setBlob(index, SerialBlob(parameter)) } @@ -248,6 +258,7 @@ public fun BaseTable<*>.bytes(name: String): Column { * [SqlType] implementation represents `bytes` SQL type. */ public object BytesSqlType : SqlType(Types.BINARY, "bytes") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: ByteArray) { ps.setBytes(index, parameter) } @@ -268,6 +279,7 @@ public fun BaseTable<*>.jdbcTimestamp(name: String): Column { * [SqlType] implementation represents `timestamp` SQL type. */ public object TimestampSqlType : SqlType(Types.TIMESTAMP, "timestamp") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Timestamp) { ps.setTimestamp(index, parameter) } @@ -288,6 +300,7 @@ public fun BaseTable<*>.jdbcDate(name: String): Column { * [SqlType] implementation represents `date` SQL type. */ public object DateSqlType : SqlType(Types.DATE, "date") { + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: Date) { ps.setDate(index, parameter) } @@ -308,6 +321,7 @@ public fun BaseTable<*>.jdbcTime(name: String): Column