Initial Development (#1)
This project now contains: - A generic JVM document library (with Kotlin extensions on the JDBC `Connection` object) - A Groovy library which adds extension methods to the `Connection` object - A Scala library, which uses native Scala collections and adds Scala-style extension methods to the `Connection` object - A Kotlin library which uses `kotlinx.serialization` (no reflection, reified generic types) along with `Connection` extensions Reviewed-on: #1
This commit is contained in:
parent
995f565255
commit
d202c002b5
6
.gitignore
vendored
6
.gitignore
vendored
@ -26,3 +26,9 @@ replay_pid*
|
||||
|
||||
# Kotlin Gradle plugin data, see https://kotlinlang.org/docs/whatsnew20.html#new-directory-for-kotlin-data-in-gradle-projects
|
||||
.kotlin/
|
||||
|
||||
# Temporary output directories
|
||||
**/target
|
||||
|
||||
# Maven Central Repo settings
|
||||
settings.xml
|
||||
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
7
.idea/codeStyles/Project.xml
generated
Normal file
7
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<ScalaCodeStyleSettings>
|
||||
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
|
||||
</ScalaCodeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
25
.idea/compiler.xml
generated
Normal file
25
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="kotlin" />
|
||||
<module name="core" />
|
||||
<module name="groovy" />
|
||||
<module name="scala" />
|
||||
<module name="kotlinx" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel>
|
||||
<module name="common" target="1.8" />
|
||||
<module name="core8" target="1.8" />
|
||||
<module name="documents (2)" target="1.5" />
|
||||
<module name="java" target="17" />
|
||||
<module name="jvm" target="11" />
|
||||
<module name="sqlite" target="1.8" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
33
.idea/encodings.xml
generated
Normal file
33
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/core/src/main/kotlin" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/core/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/core/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/core/src/main/kotlin" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/core/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/groovy/src/main/groovy" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/groovy/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/groovy/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/java/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/java/src/main/kotlin" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/java/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/jvm/src/main/kotlin" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/jvm/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/kotlin/src/main/kotlin" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/kotlin/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/kotlinx/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/kotlinx/src/main/kotlin" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/kotlinx/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/kotlin" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/scala/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/scala/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/scala/src/main/scala" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/sqlite/src/main/kotlin" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/sqlite/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
25
.idea/jarRepositories.xml
generated
Normal file
25
.idea/jarRepositories.xml
generated
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="mavenCentral" />
|
||||
<option name="name" value="mavenCentral" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
16
.idea/kotlinc.xml
generated
Normal file
16
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JsCompilerArguments">
|
||||
<option name="moduleKind" value="plain" />
|
||||
</component>
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="17" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.1" />
|
||||
<option name="languageVersion" value="2.1" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.1.20" />
|
||||
</component>
|
||||
</project>
|
23
.idea/libraries/KotlinJavaRuntime.xml
generated
Normal file
23
.idea/libraries/KotlinJavaRuntime.xml
generated
Normal file
@ -0,0 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
26
.idea/libraries/Maven__scala_sdk_3_0_0.xml
generated
Normal file
26
.idea/libraries/Maven__scala_sdk_3_0_0.xml
generated
Normal file
@ -0,0 +1,26 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: scala-sdk-3.0.0" type="Scala">
|
||||
<properties>
|
||||
<language-level>Scala_3_0</language-level>
|
||||
<compiler-classpath>
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-compiler_3/3.0.0/scala3-compiler_3-3.0.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-interfaces/3.0.0/scala3-interfaces-3.0.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-library_3/3.0.0/scala3-library_3-3.0.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala-library/2.13.5/scala-library-2.13.5.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/tasty-core_3/3.0.0/tasty-core_3-3.0.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/modules/scala-asm/9.1.0-scala-1/scala-asm-9.1.0-scala-1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/compiler-interface/1.3.5/compiler-interface-1.3.5.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/com/google/protobuf/protobuf-java/3.7.0/protobuf-java-3.7.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/util-interface/1.3.0/util-interface-1.3.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-reader/3.19.0/jline-reader-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.19.0/jline-terminal-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.19.0/jline-terminal-jna-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.3.1/jna-5.3.1.jar" />
|
||||
</compiler-classpath>
|
||||
<compiler-bridge-binary-jar>file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.0.0/scala3-sbt-bridge-3.0.0.jar</compiler-bridge-binary-jar>
|
||||
</properties>
|
||||
<CLASSES />
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
26
.idea/libraries/Maven__scala_sdk_3_1_3.xml
generated
Normal file
26
.idea/libraries/Maven__scala_sdk_3_1_3.xml
generated
Normal file
@ -0,0 +1,26 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: scala-sdk-3.1.3" type="Scala">
|
||||
<properties>
|
||||
<language-level>Scala_3_1</language-level>
|
||||
<compiler-classpath>
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-compiler_3/3.1.3/scala3-compiler_3-3.1.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-interfaces/3.1.3/scala3-interfaces-3.1.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-library_3/3.1.3/scala3-library_3-3.1.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala-library/2.13.8/scala-library-2.13.8.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/tasty-core_3/3.1.3/tasty-core_3-3.1.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/modules/scala-asm/9.2.0-scala-1/scala-asm-9.2.0-scala-1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/compiler-interface/1.3.5/compiler-interface-1.3.5.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/com/google/protobuf/protobuf-java/3.7.0/protobuf-java-3.7.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/util-interface/1.3.0/util-interface-1.3.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-reader/3.19.0/jline-reader-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.19.0/jline-terminal-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.19.0/jline-terminal-jna-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.3.1/jna-5.3.1.jar" />
|
||||
</compiler-classpath>
|
||||
<compiler-bridge-binary-jar>file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.1.3/scala3-sbt-bridge-3.1.3.jar</compiler-bridge-binary-jar>
|
||||
</properties>
|
||||
<CLASSES />
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
25
.idea/libraries/Maven__scala_sdk_3_3_3.xml
generated
Normal file
25
.idea/libraries/Maven__scala_sdk_3_3_3.xml
generated
Normal file
@ -0,0 +1,25 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: scala-sdk-3.3.3" type="Scala">
|
||||
<properties>
|
||||
<language-level>Scala_3_3</language-level>
|
||||
<compiler-classpath>
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-compiler_3/3.3.3/scala3-compiler_3-3.3.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-interfaces/3.3.3/scala3-interfaces-3.3.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-library_3/3.3.3/scala3-library_3-3.3.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala-library/2.13.12/scala-library-2.13.12.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/tasty-core_3/3.3.3/tasty-core_3-3.3.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/modules/scala-asm/9.5.0-scala-1/scala-asm-9.5.0-scala-1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/compiler-interface/1.9.3/compiler-interface-1.9.3.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/util-interface/1.9.2/util-interface-1.9.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-reader/3.19.0/jline-reader-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.19.0/jline-terminal-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.19.0/jline-terminal-jna-3.19.0.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.3.1/jna-5.3.1.jar" />
|
||||
</compiler-classpath>
|
||||
<compiler-bridge-binary-jar>file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.3.3/scala3-sbt-bridge-3.3.3.jar</compiler-bridge-binary-jar>
|
||||
</properties>
|
||||
<CLASSES />
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
26
.idea/libraries/Maven__scala_sdk_3_5_2.xml
generated
Normal file
26
.idea/libraries/Maven__scala_sdk_3_5_2.xml
generated
Normal file
@ -0,0 +1,26 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: scala-sdk-3.5.2" type="Scala">
|
||||
<properties>
|
||||
<language-level>Scala_3_5</language-level>
|
||||
<compiler-classpath>
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-compiler_3/3.5.2/scala3-compiler_3-3.5.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-interfaces/3.5.2/scala3-interfaces-3.5.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-library_3/3.5.2/scala3-library_3-3.5.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala-library/2.13.14/scala-library-2.13.14.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/tasty-core_3/3.5.2/tasty-core_3-3.5.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/modules/scala-asm/9.7.0-scala-2/scala-asm-9.7.0-scala-2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/compiler-interface/1.9.6/compiler-interface-1.9.6.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/util-interface/1.9.8/util-interface-1.9.8.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-reader/3.25.1/jline-reader-3.25.1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.25.1/jline-terminal-3.25.1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-native/3.25.1/jline-native-3.25.1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.25.1/jline-terminal-jna-3.25.1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar" />
|
||||
</compiler-classpath>
|
||||
<compiler-bridge-binary-jar>file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.5.2/scala3-sbt-bridge-3.5.2.jar</compiler-bridge-binary-jar>
|
||||
</properties>
|
||||
<CLASSES />
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
23
.idea/misc.xml
generated
Normal file
23
.idea/misc.xml
generated
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/src/common/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/src/sqlite/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/old-pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="ignoredFiles">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$/src/common/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/src/sqlite/pom.xml" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
7
.idea/scala_compiler.xml
generated
Normal file
7
.idea/scala_compiler.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ScalaCompilerConfiguration">
|
||||
<option name="incrementalityType" value="IDEA" />
|
||||
<profile name="Maven 1" modules="core,jvm,scala" />
|
||||
</component>
|
||||
</project>
|
6
.idea/scala_settings.xml
generated
Normal file
6
.idea/scala_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ScalaProjectSettings">
|
||||
<option name="scala3DisclaimerShown" value="true" />
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
52
README.md
52
README.md
@ -1,3 +1,53 @@
|
||||
# solutions.bitbadger.documents
|
||||
|
||||
Treat PostgreSQL and SQLite as document stores from Java and Kotlin
|
||||
Treat PostgreSQL and SQLite as document stores from Java, Kotlin, Scala, and Groovy
|
||||
|
||||
## Examples
|
||||
|
||||
```java
|
||||
// Retrieve (find) all orders (Java)
|
||||
public List<Order> findOrders(Connection conn) {
|
||||
Find.all(/*table name*/ "order", /*type*/ Order.class, conn);
|
||||
}
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// Mark an order as fulfilled (Kotlin)
|
||||
fun markFulfilled(orderId: Long, conn: Connection) =
|
||||
conn.patchById(
|
||||
/*table name*/ "order",
|
||||
/*document ID*/ orderId,
|
||||
/*patch object*/ mapOf("fulfilled" to true)
|
||||
)
|
||||
```
|
||||
|
||||
```scala
|
||||
// Delete orders marked as obsolete (Scala)
|
||||
def deleteObsolete(Connection conn):
|
||||
conn.deleteByFields(/*table name*/ "order",
|
||||
/*field criteria*/ Field.equal("obsolete", true) :: Nil)
|
||||
```
|
||||
|
||||
```groovy
|
||||
// Remove the pending status from multiple orders (Groovy)
|
||||
void clearPending(List<Long> orderIds, Connection conn) {
|
||||
conn.removeFieldsByFields(/*table name*/ "order",
|
||||
/*field criteria*/ List.of(Field.any("id", orderIds)),
|
||||
/*fields to remove*/ List.of("pending"))
|
||||
}
|
||||
```
|
||||
|
||||
## Packages / Modules
|
||||
|
||||
* The `core` module provides the base implementation and can be used from any JVM language.
|
||||
* The `solutions.bitbadger.documents` package contains support types like `Configuration` and `Field`.
|
||||
* The `solutions.bitbadger.documents.java` package contains document access functions and serialization config.
|
||||
* The `solutions.bitbadger.documents.java.extensions` package contains extensions on the JDBC `Connection` object, callable as extension methods from Kotlin or as static functions from other languages.
|
||||
|
||||
* The `groovy` module packages the extension methods so that Groovy can access them. No other packages will need to be imported; they will show up on any `Connection` instance.
|
||||
|
||||
* The `kotlinx` module utilizes the kotlinx-serialization project for its JSON serialization, which requires a different serializer and different function/method signatures (`inline fun <reified T> ...` vs. `fun <T> ...`).
|
||||
* `solutions.bitbadger.documents.kotlinx` and `solutions.bitbadger.documents.kotlinx.extensions` packages expose a similar API to their `java` counterparts, but one designed to be consumed from Kotlin. Generally, document retrieval functions will require a generic parameter instead of a `Class<T>` parameter.
|
||||
|
||||
* The `scala` module extends `core` by utilizing Scala's implicit `ClassTag`s to remove the `Class[T]` parameter.
|
||||
* `solutions.bitbadger.documents.scala` and `solutions.bitbadger.documents.scala.extensions` packages expose the same API as their `java` counterparts, utilizing Scala collections and `Option`s instead of Java collections and `Optional`s.
|
||||
|
117
pom.xml
Normal file
117
pom.xml
Normal file
@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>solutions.bitbadger</groupId>
|
||||
<artifactId>documents</artifactId>
|
||||
<version>1.0.0-RC1</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.groupId}:${project.artifactId}</name>
|
||||
<description>Expose a document store interface for PostgreSQL and SQLite</description>
|
||||
<url>https://relationaldocs.bitbadger.solutions/jvm/</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>https://www.opensource.org/licenses/mit-license.php</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<name>Daniel J. Summers</name>
|
||||
<email>daniel@bitbadger.solutions</email>
|
||||
<organization>Bit Badger Solutions</organization>
|
||||
<organizationUrl>https://bitbadger.solutions</organizationUrl>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git</connection>
|
||||
<developerConnection>scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git</developerConnection>
|
||||
<url>https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents</url>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<kotlin.code.style>official</kotlin.code.style>
|
||||
<java.version>17</java.version>
|
||||
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
|
||||
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
|
||||
<kotlin.version>2.1.20</kotlin.version>
|
||||
<serialization.version>1.8.0</serialization.version>
|
||||
<scala.version>3.5.2</scala.version>
|
||||
<groovy.version>4.0.26</groovy.version>
|
||||
<surefire.version>3.5.2</surefire.version>
|
||||
<failsafe.version>3.5.2</failsafe.version>
|
||||
<jackson.version>2.18.2</jackson.version>
|
||||
<sqlite.version>3.46.1.2</sqlite.version>
|
||||
<postgresql.version>42.7.5</postgresql.version>
|
||||
<buildHelperPlugin.version>3.6.0</buildHelperPlugin.version>
|
||||
<sourcePlugin.version>3.3.1</sourcePlugin.version>
|
||||
<javaDocPlugin.version>3.11.2</javaDocPlugin.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>./src/core</module>
|
||||
<module>./src/groovy</module>
|
||||
<module>./src/kotlinx</module>
|
||||
<module>./src/scala</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.2.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.10.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test-junit5</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>${sqlite.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>2.0.16</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
8
solutions.bitbadger.documents.iml
Normal file
8
solutions.bitbadger.documents.iml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="GENERAL_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
src/core/core.iml
Normal file
8
src/core/core.iml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$" dumb="true">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
182
src/core/pom.xml
Normal file
182
src/core/pom.xml
Normal file
@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>solutions.bitbadger</groupId>
|
||||
<artifactId>documents</artifactId>
|
||||
<version>1.0.0-RC1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>solutions.bitbadger.documents</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
|
||||
<name>${project.groupId}:${project.artifactId}</name>
|
||||
<description>Expose a document store interface for PostgreSQL and SQLite (Core Library)</description>
|
||||
<url>https://relationaldocs.bitbadger.solutions/jvm/</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>https://www.opensource.org/licenses/mit-license.php</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<name>Daniel J. Summers</name>
|
||||
<email>daniel@bitbadger.solutions</email>
|
||||
<organization>Bit Badger Solutions</organization>
|
||||
<organizationUrl>https://bitbadger.solutions</organizationUrl>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git</connection>
|
||||
<developerConnection>scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git</developerConnection>
|
||||
<url>https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents</url>
|
||||
</scm>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sourceDirs>
|
||||
<sourceDir>${project.basedir}/src/main/java</sourceDir>
|
||||
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
|
||||
</sourceDirs>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>process-test-sources</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sourceDirs>
|
||||
<sourceDir>${project.basedir}/src/test/java</sourceDir>
|
||||
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
|
||||
</sourceDirs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${surefire.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>${failsafe.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>${buildHelperPlugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/main/kotlin</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${sourcePlugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.dokka</groupId>
|
||||
<artifactId>dokka-maven-plugin</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<configuration>
|
||||
<reportUndocumented>true</reportUndocumented>
|
||||
<includes>${project.basedir}/src/main/module-info.md</includes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>javadocJar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.7.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<deploymentName>Deployment-core-${project.version}</deploymentName>
|
||||
<publishingServerId>central</publishingServerId>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
9
src/core/src/main/java/module-info.java
Normal file
9
src/core/src/main/java/module-info.java
Normal file
@ -0,0 +1,9 @@
|
||||
module solutions.bitbadger.documents.core {
|
||||
requires kotlin.stdlib;
|
||||
requires kotlin.reflect;
|
||||
requires java.sql;
|
||||
exports solutions.bitbadger.documents;
|
||||
exports solutions.bitbadger.documents.java;
|
||||
exports solutions.bitbadger.documents.java.extensions;
|
||||
exports solutions.bitbadger.documents.query;
|
||||
}
|
85
src/core/src/main/kotlin/AutoId.kt
Normal file
85
src/core/src/main/kotlin/AutoId.kt
Normal file
@ -0,0 +1,85 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
import kotlin.jvm.Throws
|
||||
import kotlin.reflect.full.*
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
|
||||
/**
|
||||
* Strategies for automatic document IDs
|
||||
*/
|
||||
enum class AutoId {
|
||||
/** No automatic IDs will be generated */
|
||||
DISABLED,
|
||||
|
||||
/** Generate a `MAX`-plus-1 numeric ID */
|
||||
NUMBER,
|
||||
|
||||
/** Generate a `UUID` string ID */
|
||||
UUID,
|
||||
|
||||
/** Generate a random hex character string ID */
|
||||
RANDOM_STRING;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Generate a `UUID` string
|
||||
*
|
||||
* @return A `UUID` string
|
||||
*/
|
||||
@JvmStatic
|
||||
fun generateUUID(): String =
|
||||
java.util.UUID.randomUUID().toString().replace("-", "")
|
||||
|
||||
/**
|
||||
* Generate a string of random hex characters
|
||||
*
|
||||
* @param length The length of the string (optional; defaults to configured length)
|
||||
* @return A string of random hex characters of the requested length
|
||||
*/
|
||||
@JvmStatic
|
||||
fun generateRandomString(length: Int? = null): String =
|
||||
(length ?: Configuration.idStringLength).let { len ->
|
||||
kotlin.random.Random.nextBytes((len + 2) / 2)
|
||||
.joinToString("") { String.format("%02x", it) }
|
||||
.substring(0, len)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a document needs an automatic ID applied
|
||||
*
|
||||
* @param strategy The auto ID strategy for which the document is evaluated
|
||||
* @param document The document whose need of an automatic ID should be determined
|
||||
* @param idProp The name of the document property containing the ID
|
||||
* @return `true` if the document needs an automatic ID, `false` if not
|
||||
* @throws DocumentException If bad input prevents the determination
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <T> needsAutoId(strategy: AutoId, document: T, idProp: String): Boolean {
|
||||
if (document == null) throw DocumentException("document cannot be null")
|
||||
|
||||
if (strategy == DISABLED) return false
|
||||
|
||||
val id = document!!::class.memberProperties.find { it.name == idProp }?.apply { isAccessible = true }
|
||||
if (id == null) throw DocumentException("$idProp not found in document")
|
||||
|
||||
if (strategy == NUMBER) {
|
||||
return when (id.returnType) {
|
||||
Byte::class.createType() -> id.call(document) == 0.toByte()
|
||||
Short::class.createType() -> id.call(document) == 0.toShort()
|
||||
Int::class.createType() -> id.call(document) == 0
|
||||
Long::class.createType() -> id.call(document) == 0.toLong()
|
||||
else -> throw DocumentException("$idProp was not a number; cannot auto-generate number ID")
|
||||
}
|
||||
}
|
||||
|
||||
val typ = id.returnType.toString()
|
||||
if (typ.endsWith("String") || typ.endsWith("String!")) {
|
||||
return id.call(document) == ""
|
||||
}
|
||||
|
||||
throw DocumentException("$idProp was not a string ($typ); cannot auto-generate UUID or random string")
|
||||
}
|
||||
}
|
||||
}
|
68
src/core/src/main/kotlin/Comparison.kt
Normal file
68
src/core/src/main/kotlin/Comparison.kt
Normal file
@ -0,0 +1,68 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* Information required to generate a JSON field comparison
|
||||
*/
|
||||
interface Comparison<T> {
|
||||
|
||||
/** The operation for the field comparison */
|
||||
val op: Op
|
||||
|
||||
/** The value against which the comparison will be made */
|
||||
val value: T
|
||||
|
||||
/** Whether the value should be considered numeric */
|
||||
val isNumeric: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to determine if a value is numeric
|
||||
*
|
||||
* @param it The value in question
|
||||
* @return True if it is a numeric type, false if not
|
||||
*/
|
||||
private fun <T> isNumeric(it: T) =
|
||||
it is Byte || it is Short || it is Int || it is Long
|
||||
|
||||
/**
|
||||
* A single-value comparison against a field in a JSON document
|
||||
*/
|
||||
class ComparisonSingle<T>(override val op: Op, override val value: T) : Comparison<T> {
|
||||
|
||||
init {
|
||||
when (op) {
|
||||
Op.BETWEEN, Op.IN, Op.IN_ARRAY ->
|
||||
throw DocumentException("Cannot use single comparison for multiple values")
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
override val isNumeric = isNumeric(value)
|
||||
|
||||
override fun toString() =
|
||||
"$op $value"
|
||||
}
|
||||
|
||||
/**
|
||||
* A range comparison against a field in a JSON document
|
||||
*/
|
||||
class ComparisonBetween<T>(override val value: Pair<T, T>) : Comparison<Pair<T, T>> {
|
||||
override val op = Op.BETWEEN
|
||||
override val isNumeric = isNumeric(value.first)
|
||||
}
|
||||
|
||||
/**
|
||||
* A check within a collection of values
|
||||
*/
|
||||
class ComparisonIn<T>(override val value: Collection<T>) : Comparison<Collection<T>> {
|
||||
override val op = Op.IN
|
||||
override val isNumeric = !value.isEmpty() && isNumeric(value.elementAt(0))
|
||||
}
|
||||
|
||||
/**
|
||||
* A check within a collection of values against an array in a document
|
||||
*/
|
||||
class ComparisonInArray<T>(override val value: Pair<String, Collection<T>>) : Comparison<Pair<String, Collection<T>>> {
|
||||
override val op = Op.IN_ARRAY
|
||||
override val isNumeric = false
|
||||
}
|
67
src/core/src/main/kotlin/Configuration.kt
Normal file
67
src/core/src/main/kotlin/Configuration.kt
Normal file
@ -0,0 +1,67 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
|
||||
/**
|
||||
* Configuration for the document library
|
||||
*/
|
||||
object Configuration {
|
||||
|
||||
/** The field in which a document's ID is stored */
|
||||
@JvmField
|
||||
var idField = "id"
|
||||
|
||||
/** The automatic ID strategy to use */
|
||||
@JvmField
|
||||
var autoIdStrategy = AutoId.DISABLED
|
||||
|
||||
/** The length of automatic random hex character string */
|
||||
@JvmField
|
||||
var idStringLength = 16
|
||||
|
||||
/** The derived dialect value from the connection string */
|
||||
private var dialectValue: Dialect? = null
|
||||
|
||||
/** The connection string for the JDBC connection */
|
||||
@JvmStatic
|
||||
var connectionString: String? = null
|
||||
/**
|
||||
* Set a value for the connection string
|
||||
* @param value The connection string to set
|
||||
*/
|
||||
set(value) {
|
||||
field = value
|
||||
dialectValue = if (value.isNullOrBlank()) null else Dialect.deriveFromConnectionString(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a new connection to the configured database
|
||||
*
|
||||
* @return A new connection to the configured database
|
||||
* @throws DocumentException If the connection string is not set before calling this
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun dbConn(): Connection {
|
||||
if (connectionString == null) {
|
||||
throw DocumentException("Please provide a connection string before attempting data access")
|
||||
}
|
||||
return DriverManager.getConnection(connectionString)
|
||||
}
|
||||
|
||||
/**
|
||||
* The dialect in use
|
||||
*
|
||||
* @param process The process being attempted
|
||||
* @return The dialect for the current connection
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun dialect(process: String? = null): Dialect =
|
||||
dialectValue ?: throw DocumentException(
|
||||
"Database mode not set" + if (process == null) "" else "; cannot $process"
|
||||
)
|
||||
}
|
33
src/core/src/main/kotlin/Dialect.kt
Normal file
33
src/core/src/main/kotlin/Dialect.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* The SQL dialect to use when building queries
|
||||
*/
|
||||
enum class Dialect {
|
||||
/** PostgreSQL */
|
||||
POSTGRESQL,
|
||||
|
||||
/** SQLite */
|
||||
SQLITE;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Derive the dialect from the given connection string
|
||||
*
|
||||
* @param connectionString The connection string from which the dialect will be derived
|
||||
* @return The dialect for the connection string
|
||||
* @throws DocumentException If the dialect cannot be determined
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun deriveFromConnectionString(connectionString: String): Dialect =
|
||||
when {
|
||||
connectionString.contains(":sqlite:") -> SQLITE
|
||||
connectionString.contains(":postgresql:") -> POSTGRESQL
|
||||
else -> throw DocumentException("Cannot determine dialect from [$connectionString]")
|
||||
}
|
||||
}
|
||||
}
|
9
src/core/src/main/kotlin/DocumentException.kt
Normal file
9
src/core/src/main/kotlin/DocumentException.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* An exception caused by invalid operations in the document library
|
||||
*
|
||||
* @param message The message for the exception
|
||||
* @param cause The underlying exception (optional)
|
||||
*/
|
||||
class DocumentException @JvmOverloads constructor(message: String, cause: Throwable? = null) : Exception(message, cause)
|
13
src/core/src/main/kotlin/DocumentIndex.kt
Normal file
13
src/core/src/main/kotlin/DocumentIndex.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* The type of index to generate for the document
|
||||
*/
|
||||
enum class DocumentIndex(val sql: String) {
|
||||
|
||||
/** A GIN index with standard operations (all operators supported) */
|
||||
FULL(""),
|
||||
|
||||
/** A GIN index with JSONPath operations (optimized for @>, @?, @@ operators) */
|
||||
OPTIMIZED(" jsonb_path_ops")
|
||||
}
|
24
src/core/src/main/kotlin/DocumentSerializer.kt
Normal file
24
src/core/src/main/kotlin/DocumentSerializer.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* The interface for a document serializer/deserializer
|
||||
*/
|
||||
interface DocumentSerializer {
|
||||
|
||||
/**
|
||||
* Serialize a document to its JSON representation
|
||||
*
|
||||
* @param document The document to be serialized
|
||||
* @return The JSON representation of the document
|
||||
*/
|
||||
fun <TDoc> serialize(document: TDoc): String
|
||||
|
||||
/**
|
||||
* Deserialize a document from its JSON representation
|
||||
*
|
||||
* @param json The JSON representation of the document
|
||||
* @param clazz The class of the document to be deserialized
|
||||
* @return The document instance represented by the given JSON string
|
||||
*/
|
||||
fun <TDoc> deserialize(json: String, clazz: Class<TDoc>): TDoc
|
||||
}
|
320
src/core/src/main/kotlin/Field.kt
Normal file
320
src/core/src/main/kotlin/Field.kt
Normal file
@ -0,0 +1,320 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* A field and its comparison
|
||||
*
|
||||
* @property name The name of the field in the JSON document
|
||||
* @property comparison The comparison to apply against the field
|
||||
* @property parameterName The name of the parameter to use in the query (optional, generated if missing)
|
||||
* @property qualifier A table qualifier to use to address the `data` field (useful for multi-table queries)
|
||||
*/
|
||||
class Field<T> private constructor(
|
||||
val name: String,
|
||||
val comparison: Comparison<T>,
|
||||
val parameterName: String? = null,
|
||||
val qualifier: String? = null) {
|
||||
|
||||
init {
|
||||
if (parameterName != null && !parameterName.startsWith(':') && !parameterName.startsWith('@'))
|
||||
throw DocumentException("Parameter Name must start with : or @ ($name)")
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the parameter name for the field
|
||||
*
|
||||
* @param paramName The parameter name to use for this field
|
||||
* @return A new `Field` with the parameter name specified
|
||||
*/
|
||||
fun withParameterName(paramName: String) =
|
||||
Field(name, comparison, paramName, qualifier)
|
||||
|
||||
/**
|
||||
* Specify a qualifier (alias) for the document table
|
||||
*
|
||||
* @param alias The table alias for this field
|
||||
* @return A new `Field` with the table qualifier specified
|
||||
*/
|
||||
fun withQualifier(alias: String) =
|
||||
Field(name, comparison, parameterName, alias)
|
||||
|
||||
/**
|
||||
* Get the path for this field
|
||||
*
|
||||
* @param dialect The SQL dialect to use for the path to the JSON field
|
||||
* @param format Whether the value should be retrieved as JSON or SQL (optional, default SQL)
|
||||
* @return The path for the field
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun path(dialect: Dialect, format: FieldFormat = FieldFormat.SQL): String =
|
||||
(if (qualifier == null) "" else "${qualifier}.") + nameToPath(name, dialect, format)
|
||||
|
||||
/** Parameters to bind each value of `IN` and `IN_ARRAY` operations */
|
||||
private val inParameterNames: String
|
||||
get() {
|
||||
val values = if (comparison.op == Op.IN) {
|
||||
comparison.value as Collection<*>
|
||||
} else {
|
||||
val parts = comparison.value as Pair<*, *>
|
||||
parts.second as Collection<*>
|
||||
}
|
||||
return List(values.size) { idx -> "${parameterName}_$idx" }.joinToString(", ")
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `WHERE` clause fragment for this field
|
||||
*
|
||||
* @return The `WHERE` clause for this field
|
||||
* @throws DocumentException If the field has no parameter name or the database dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun toWhere(): String {
|
||||
if (parameterName == null && !listOf(Op.EXISTS, Op.NOT_EXISTS).contains(comparison.op))
|
||||
throw DocumentException("Parameter for $name must be specified")
|
||||
|
||||
val dialect = Configuration.dialect("make field WHERE clause")
|
||||
val fieldName = path(dialect, if (comparison.op == Op.IN_ARRAY) FieldFormat.JSON else FieldFormat.SQL)
|
||||
val fieldPath = when (dialect) {
|
||||
Dialect.POSTGRESQL -> if (comparison.isNumeric) "($fieldName)::numeric" else fieldName
|
||||
Dialect.SQLITE -> fieldName
|
||||
}
|
||||
val criteria = when (comparison.op) {
|
||||
in listOf(Op.EXISTS, Op.NOT_EXISTS) -> ""
|
||||
Op.BETWEEN -> " ${parameterName}min AND ${parameterName}max"
|
||||
Op.IN -> " ($inParameterNames)"
|
||||
Op.IN_ARRAY -> if (dialect == Dialect.POSTGRESQL) " ARRAY[$inParameterNames]" else ""
|
||||
else -> " $parameterName"
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return if (dialect == Dialect.SQLITE && comparison.op == Op.IN_ARRAY) {
|
||||
val (table, _) = comparison.value as? Pair<String, *> ?: throw DocumentException("InArray field invalid")
|
||||
"EXISTS (SELECT 1 FROM json_each($table.data, '$.$name') WHERE value IN ($inParameterNames))"
|
||||
} else {
|
||||
"$fieldPath ${comparison.op.sql}$criteria"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the parameters required for this field
|
||||
*
|
||||
* @param existing The existing parameters
|
||||
* @return The collection with the necessary parameters appended
|
||||
*/
|
||||
fun appendParameter(existing: MutableCollection<Parameter<*>>): MutableCollection<Parameter<*>> {
|
||||
val typ = if (comparison.isNumeric) ParameterType.NUMBER else ParameterType.STRING
|
||||
when (comparison) {
|
||||
is ComparisonBetween<*> -> {
|
||||
existing.add(Parameter("${parameterName}min", typ, comparison.value.first))
|
||||
existing.add(Parameter("${parameterName}max", typ, comparison.value.second))
|
||||
}
|
||||
|
||||
is ComparisonIn<*> -> {
|
||||
comparison.value.forEachIndexed { index, item ->
|
||||
existing.add(Parameter("${parameterName}_$index", typ, item))
|
||||
}
|
||||
}
|
||||
|
||||
is ComparisonInArray<*> -> {
|
||||
val mkString = Configuration.dialect("append parameters for InArray") == Dialect.POSTGRESQL
|
||||
comparison.value.second.forEachIndexed { index, item ->
|
||||
if (mkString) {
|
||||
existing.add(Parameter("${parameterName}_$index", ParameterType.STRING, "$item"))
|
||||
} else {
|
||||
existing.add(Parameter("${parameterName}_$index", typ, item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (comparison.op != Op.EXISTS && comparison.op != Op.NOT_EXISTS) {
|
||||
existing.add(Parameter(parameterName!!, typ, comparison.value))
|
||||
}
|
||||
}
|
||||
}
|
||||
return existing
|
||||
}
|
||||
|
||||
override fun toString() =
|
||||
"Field ${parameterName ?: "<unnamed>"} $comparison${qualifier?.let { " (qualifier $it)" } ?: ""}"
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Create a field equality comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> equal(name: String, value: T, paramName: String? = null) =
|
||||
Field(name, ComparisonSingle(Op.EQUAL, value), paramName)
|
||||
|
||||
/**
|
||||
* Create a field greater-than comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> greater(name: String, value: T, paramName: String? = null) =
|
||||
Field(name, ComparisonSingle(Op.GREATER, value), paramName)
|
||||
|
||||
/**
|
||||
* Create a field greater-than-or-equal-to comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> greaterOrEqual(name: String, value: T, paramName: String? = null) =
|
||||
Field(name, ComparisonSingle(Op.GREATER_OR_EQUAL, value), paramName)
|
||||
|
||||
/**
|
||||
* Create a field less-than comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> less(name: String, value: T, paramName: String? = null) =
|
||||
Field(name, ComparisonSingle(Op.LESS, value), paramName)
|
||||
|
||||
/**
|
||||
* Create a field less-than-or-equal-to comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> lessOrEqual(name: String, value: T, paramName: String? = null) =
|
||||
Field(name, ComparisonSingle(Op.LESS_OR_EQUAL, value), paramName)
|
||||
|
||||
/**
|
||||
* Create a field inequality comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> notEqual(name: String, value: T, paramName: String? = null) =
|
||||
Field(name, ComparisonSingle(Op.NOT_EQUAL, value), paramName)
|
||||
|
||||
/**
|
||||
* Create a field range comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param minValue The lower value for the comparison
|
||||
* @param maxValue The upper value for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> between(name: String, minValue: T, maxValue: T, paramName: String? = null) =
|
||||
Field(name, ComparisonBetween(Pair(minValue, maxValue)), paramName)
|
||||
|
||||
/**
|
||||
* Create a field where any values match (SQL `IN`)
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param values The values for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> any(name: String, values: Collection<T>, paramName: String? = null) =
|
||||
Field(name, ComparisonIn(values), paramName)
|
||||
|
||||
/**
|
||||
* Create a field where values should exist in a document's array
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param tableName The name of the document table
|
||||
* @param values The values for the comparison
|
||||
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <T> inArray(name: String, tableName: String, values: Collection<T>, paramName: String? = null) =
|
||||
Field(name, ComparisonInArray(Pair(tableName, values)), paramName)
|
||||
|
||||
/**
|
||||
* Create a field where a document field should exist
|
||||
*
|
||||
* @param name The name of the field whose existence should be checked
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
fun exists(name: String) =
|
||||
Field(name, ComparisonSingle(Op.EXISTS, ""))
|
||||
|
||||
/**
|
||||
* Create a field where a document field should not exist
|
||||
*
|
||||
* @param name The name of the field whose existence should be checked
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
@JvmStatic
|
||||
fun notExists(name: String) =
|
||||
Field(name, ComparisonSingle(Op.NOT_EXISTS, ""))
|
||||
|
||||
/**
|
||||
* Create a field with a given named comparison (useful for ordering fields)
|
||||
*
|
||||
* @param name The name of the field
|
||||
* @return A `Field` with the given name (comparison equal to an empty string)
|
||||
*/
|
||||
@JvmStatic
|
||||
fun named(name: String) =
|
||||
Field(name, ComparisonSingle(Op.EQUAL, ""))
|
||||
|
||||
/**
|
||||
* Convert a name to the SQL path for the given dialect
|
||||
*
|
||||
* @param name The field name to be translated
|
||||
* @param dialect The database for which the path should be created
|
||||
* @param format Whether the field should be retrieved as a JSON value or a SQL value
|
||||
* @return The path to the JSON field
|
||||
*/
|
||||
@JvmStatic
|
||||
fun nameToPath(name: String, dialect: Dialect, format: FieldFormat): String {
|
||||
val path = StringBuilder("data")
|
||||
val extra = if (format == FieldFormat.SQL) ">" else ""
|
||||
if (name.indexOf('.') > -1) {
|
||||
if (dialect == Dialect.POSTGRESQL) {
|
||||
path.append("#>", extra, "'{", name.replace('.', ','), "}'")
|
||||
} else {
|
||||
val names = name.split('.').toMutableList()
|
||||
val last = names.removeLast()
|
||||
names.forEach { path.append("->'", it, "'") }
|
||||
path.append("->", extra, "'", last, "'")
|
||||
}
|
||||
} else {
|
||||
path.append("->", extra, "'", name, "'")
|
||||
}
|
||||
return path.toString()
|
||||
}
|
||||
}
|
||||
}
|
12
src/core/src/main/kotlin/FieldFormat.kt
Normal file
12
src/core/src/main/kotlin/FieldFormat.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* The data format for a document field retrieval
|
||||
*/
|
||||
enum class FieldFormat {
|
||||
/** Retrieve the field as a SQL value (string in PostgreSQL, best guess in SQLite */
|
||||
SQL,
|
||||
|
||||
/** Retrieve the field as a JSON value */
|
||||
JSON
|
||||
}
|
12
src/core/src/main/kotlin/FieldMatch.kt
Normal file
12
src/core/src/main/kotlin/FieldMatch.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* How fields should be matched in by-field queries
|
||||
*/
|
||||
enum class FieldMatch(val sql: String) {
|
||||
/** Match any of the field criteria (`OR`) */
|
||||
ANY("OR"),
|
||||
|
||||
/** Match all the field criteria (`AND`) */
|
||||
ALL("AND"),
|
||||
}
|
39
src/core/src/main/kotlin/Op.kt
Normal file
39
src/core/src/main/kotlin/Op.kt
Normal file
@ -0,0 +1,39 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* A comparison operator used for fields
|
||||
*/
|
||||
enum class Op(val sql: String) {
|
||||
/** Compare using equality */
|
||||
EQUAL("="),
|
||||
|
||||
/** Compare using greater-than */
|
||||
GREATER(">"),
|
||||
|
||||
/** Compare using greater-than-or-equal-to */
|
||||
GREATER_OR_EQUAL(">="),
|
||||
|
||||
/** Compare using less-than */
|
||||
LESS("<"),
|
||||
|
||||
/** Compare using less-than-or-equal-to */
|
||||
LESS_OR_EQUAL("<="),
|
||||
|
||||
/** Compare using inequality */
|
||||
NOT_EQUAL("<>"),
|
||||
|
||||
/** Compare between two values */
|
||||
BETWEEN("BETWEEN"),
|
||||
|
||||
/** Compare existence in a list of values */
|
||||
IN("IN"),
|
||||
|
||||
/** Compare overlap between an array and a list of values */
|
||||
IN_ARRAY("??|"),
|
||||
|
||||
/** Compare existence */
|
||||
EXISTS("IS NOT NULL"),
|
||||
|
||||
/** Compare nonexistence */
|
||||
NOT_EXISTS("IS NULL")
|
||||
}
|
58
src/core/src/main/kotlin/Parameter.kt
Normal file
58
src/core/src/main/kotlin/Parameter.kt
Normal file
@ -0,0 +1,58 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.Types
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* A parameter to use for a query
|
||||
*
|
||||
* @property name The name of the parameter (prefixed with a colon)
|
||||
* @property type The type of this parameter
|
||||
* @property value The value of the parameter
|
||||
*/
|
||||
class Parameter<T>(val name: String, val type: ParameterType, val value: T) {
|
||||
|
||||
init {
|
||||
if (!name.startsWith(':') && !name.startsWith('@'))
|
||||
throw DocumentException("Name must start with : or @ ($name)")
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind this parameter to a prepared statement at the given index
|
||||
*
|
||||
* @param stmt The prepared statement to which this parameter should be bound
|
||||
* @param index The index (1-based) to which the parameter should be bound
|
||||
* @throws DocumentException If a number parameter is given a non-numeric value
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun bind(stmt: PreparedStatement, index: Int) {
|
||||
when (type) {
|
||||
ParameterType.NUMBER -> {
|
||||
when (value) {
|
||||
null -> stmt.setNull(index, Types.NULL)
|
||||
is Byte -> stmt.setByte(index, value)
|
||||
is Short -> stmt.setShort(index, value)
|
||||
is Int -> stmt.setInt(index, value)
|
||||
is Long -> stmt.setLong(index, value)
|
||||
else -> throw DocumentException(
|
||||
"Number parameter must be Byte, Short, Int, or Long (${value::class.simpleName})"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ParameterType.STRING -> {
|
||||
when (value) {
|
||||
null -> stmt.setNull(index, Types.NULL)
|
||||
is String -> stmt.setString(index, value)
|
||||
else -> stmt.setString(index, value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
ParameterType.JSON -> stmt.setObject(index, value as String, Types.OTHER)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() =
|
||||
"$type[$name] = $value"
|
||||
}
|
18
src/core/src/main/kotlin/ParameterName.kt
Normal file
18
src/core/src/main/kotlin/ParameterName.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* Derive parameter names; each instance wraps a counter to provide names for anonymous fields
|
||||
*/
|
||||
class ParameterName {
|
||||
|
||||
private var currentIdx = 0
|
||||
|
||||
/**
|
||||
* Derive the parameter name from the current possibly-null string
|
||||
*
|
||||
* @param paramName The name of the parameter as specified by the field
|
||||
* @return The name from the field, if present, or a derived name if missing
|
||||
*/
|
||||
fun derive(paramName: String?): String =
|
||||
paramName ?: ":field${currentIdx++}"
|
||||
}
|
15
src/core/src/main/kotlin/ParameterType.kt
Normal file
15
src/core/src/main/kotlin/ParameterType.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package solutions.bitbadger.documents
|
||||
|
||||
/**
|
||||
* The types of parameters supported by the document library
|
||||
*/
|
||||
enum class ParameterType {
|
||||
/** The parameter value is some sort of number (`Byte`, `Short`, `Int`, or `Long`) */
|
||||
NUMBER,
|
||||
|
||||
/** The parameter value is a string */
|
||||
STRING,
|
||||
|
||||
/** The parameter should be JSON-encoded */
|
||||
JSON,
|
||||
}
|
147
src/core/src/main/kotlin/java/Count.kt
Normal file
147
src/core/src/main/kotlin/java/Count.kt
Normal file
@ -0,0 +1,147 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.query.CountQuery
|
||||
import java.sql.Connection
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to count documents
|
||||
*/
|
||||
object Count {
|
||||
|
||||
/**
|
||||
* Count all documents in the table
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param conn The connection over which documents should be counted
|
||||
* @return A count of the documents in the table
|
||||
* @throws DocumentException If any dependent process does
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun all(tableName: String, conn: Connection) =
|
||||
Custom.scalar(CountQuery.all(tableName), listOf(), Long::class.java, conn, Results::toCount)
|
||||
|
||||
/**
|
||||
* Count all documents in the table
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @return A count of the documents in the table
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun all(tableName: String) =
|
||||
Configuration.dbConn().use { all(tableName, it) }
|
||||
|
||||
/**
|
||||
* Count documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param conn The connection on which the deletion should be executed
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
): Long {
|
||||
val named = Parameters.nameFields(fields)
|
||||
return Custom.scalar(
|
||||
CountQuery.byFields(tableName, named, howMatched),
|
||||
Parameters.addFields(named),
|
||||
Long::class.java,
|
||||
conn,
|
||||
Results::toCount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Count documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
|
||||
|
||||
/**
|
||||
* Count documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param conn The connection on which the count should be executed
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
|
||||
Custom.scalar(
|
||||
CountQuery.byContains(tableName),
|
||||
listOf(Parameters.json(":criteria", criteria)),
|
||||
Long::class.java,
|
||||
conn,
|
||||
Results::toCount
|
||||
)
|
||||
|
||||
/**
|
||||
* Count documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains) =
|
||||
Configuration.dbConn().use { byContains(tableName, criteria, it) }
|
||||
|
||||
/**
|
||||
* Count documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param path The JSON path comparison to match
|
||||
* @param conn The connection on which the count should be executed
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String, conn: Connection) =
|
||||
Custom.scalar(
|
||||
CountQuery.byJsonPath(tableName),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path)),
|
||||
Long::class.java,
|
||||
conn,
|
||||
Results::toCount
|
||||
)
|
||||
|
||||
/**
|
||||
* Count documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param path The JSON path comparison to match
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String) =
|
||||
Configuration.dbConn().use { byJsonPath(tableName, path, it) }
|
||||
}
|
281
src/core/src/main/kotlin/java/Custom.kt
Normal file
281
src/core/src/main/kotlin/java/Custom.kt
Normal file
@ -0,0 +1,281 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.Configuration
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import solutions.bitbadger.documents.Parameter
|
||||
import java.io.PrintWriter
|
||||
import java.sql.Connection
|
||||
import java.sql.ResultSet
|
||||
import java.sql.SQLException
|
||||
import java.util.*
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to run custom queries
|
||||
*/
|
||||
object Custom {
|
||||
|
||||
/**
|
||||
* Execute a query that returns a list of results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param conn The connection over which the query should be executed
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return A list of results for the given query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> list(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<TDoc>,
|
||||
conn: Connection,
|
||||
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
|
||||
) = Parameters.apply(conn, query, parameters).use { Results.toCustomList(it, clazz, mapFunc) }
|
||||
|
||||
/**
|
||||
* Execute a query that returns a list of results (creates connection)
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return A list of results for the given query
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> list(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<TDoc>,
|
||||
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
|
||||
) = Configuration.dbConn().use { list(query, parameters, clazz, it, mapFunc) }
|
||||
|
||||
/**
|
||||
* Execute a query that returns a JSON array of results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param conn The connection over which the query should be executed
|
||||
* @param mapFunc The mapping function to extract the JSON from the query
|
||||
* @return A JSON array of results for the given query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun jsonArray(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
conn: Connection,
|
||||
mapFunc: (ResultSet) -> String
|
||||
) = Parameters.apply(conn, query, parameters).use { Results.toJsonArray(it, mapFunc) }
|
||||
|
||||
/**
|
||||
* Execute a query that returns a JSON array of results (creates connection)
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param mapFunc The mapping function to extract the JSON from the query
|
||||
* @return A JSON array of results for the given query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun jsonArray(query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> String) =
|
||||
Configuration.dbConn().use { jsonArray(query, parameters, it, mapFunc) }
|
||||
|
||||
/**
|
||||
* Execute a query, writing its JSON array of results to the given `PrintWriter`
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param writer The writer to which the results should be written
|
||||
* @param conn The connection over which the query should be executed
|
||||
* @param mapFunc The mapping function to extract the JSON from the query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeJsonArray(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
writer: PrintWriter,
|
||||
conn: Connection,
|
||||
mapFunc: (ResultSet) -> String
|
||||
) = Parameters.apply(conn, query, parameters).use { Results.writeJsonArray(writer, it, mapFunc) }
|
||||
|
||||
/**
|
||||
* Execute a query, writing its JSON array of results to the given `PrintWriter` (creates connection)
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param writer The writer to which the results should be written
|
||||
* @param mapFunc The mapping function to extract the JSON from the query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeJsonArray(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
writer: PrintWriter,
|
||||
mapFunc: (ResultSet) -> String
|
||||
) = Configuration.dbConn().use { writeJsonArray(query, parameters, writer, it, mapFunc) }
|
||||
|
||||
/**
|
||||
* Execute a query that returns one or no results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param conn The connection over which the query should be executed
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return An `Optional` value, with the document if one matches the query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> single(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<TDoc>,
|
||||
conn: Connection,
|
||||
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
|
||||
) = Optional.ofNullable(list("$query LIMIT 1", parameters, clazz, conn, mapFunc).singleOrNull())
|
||||
|
||||
/**
|
||||
* Execute a query that returns one or no results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return The document if one matches the query, `null` otherwise
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> single(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<TDoc>,
|
||||
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
|
||||
) = Configuration.dbConn().use { single(query, parameters, clazz, it, mapFunc) }
|
||||
|
||||
/**
|
||||
* Execute a query that returns JSON for one or no documents
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param conn The connection over which the query should be executed
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return The JSON for the document if found, an empty object (`{}`) if not
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun jsonSingle(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
conn: Connection,
|
||||
mapFunc: (ResultSet) -> String
|
||||
) = jsonArray("$query LIMIT 1", parameters, conn, mapFunc).let {
|
||||
if (it == "[]") "{}" else it.substring(1, it.length - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query that returns JSON for one or no documents (creates connection)
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return The JSON for the document if found, an empty object (`{}`) if not
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun jsonSingle(query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> String) =
|
||||
Configuration.dbConn().use { jsonSingle(query, parameters, it, mapFunc) }
|
||||
|
||||
/**
|
||||
* Execute a query that returns no results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param conn The connection over which the query should be executed
|
||||
* @param parameters Parameters to use for the query
|
||||
* @throws DocumentException If parameters are invalid or if the query fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection) {
|
||||
try {
|
||||
Parameters.apply(conn, query, parameters).use { it.executeUpdate() }
|
||||
} catch (ex: SQLException) {
|
||||
throw DocumentException("Unable to execute non-query: ${ex.message}", ex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query that returns no results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @throws DocumentException If no connection string has been set, if parameters are invalid, or if the query fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf()) =
|
||||
Configuration.dbConn().use { nonQuery(query, parameters, it) }
|
||||
|
||||
/**
|
||||
* Execute a query that returns a scalar result
|
||||
*
|
||||
* @param query The query to retrieve the result
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param conn The connection over which the query should be executed
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return The scalar value from the query
|
||||
* @throws DocumentException If parameters are invalid or if the query fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <T : Any> scalar(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<T>,
|
||||
conn: Connection,
|
||||
mapFunc: (ResultSet, Class<T>) -> T
|
||||
) = Parameters.apply(conn, query, parameters).use { stmt ->
|
||||
try {
|
||||
stmt.executeQuery().use { rs ->
|
||||
rs.next()
|
||||
mapFunc(rs, clazz)
|
||||
}
|
||||
} catch (ex: SQLException) {
|
||||
throw DocumentException("Unable to retrieve scalar value: ${ex.message}", ex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query that returns a scalar result
|
||||
*
|
||||
* @param query The query to retrieve the result
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return The scalar value from the query
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <T : Any> scalar(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<T>,
|
||||
mapFunc: (ResultSet, Class<T>) -> T
|
||||
) = Configuration.dbConn().use { scalar(query, parameters, clazz, it, mapFunc) }
|
||||
}
|
92
src/core/src/main/kotlin/java/Definition.kt
Normal file
92
src/core/src/main/kotlin/java/Definition.kt
Normal file
@ -0,0 +1,92 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.Configuration
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import solutions.bitbadger.documents.DocumentIndex
|
||||
import solutions.bitbadger.documents.query.DefinitionQuery
|
||||
import java.sql.Connection
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to define tables and indexes
|
||||
*/
|
||||
object Definition {
|
||||
|
||||
/**
|
||||
* Create a document table if necessary
|
||||
*
|
||||
* @param tableName The table whose existence should be ensured (may include schema)
|
||||
* @param conn The connection on which the query should be executed
|
||||
* @throws DocumentException If the dialect is not configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun ensureTable(tableName: String, conn: Connection) =
|
||||
Configuration.dialect("ensure $tableName exists").let {
|
||||
Custom.nonQuery(DefinitionQuery.ensureTable(tableName, it), conn = conn)
|
||||
Custom.nonQuery(DefinitionQuery.ensureKey(tableName, it), conn = conn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a document table if necessary
|
||||
*
|
||||
* @param tableName The table whose existence should be ensured (may include schema)
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun ensureTable(tableName: String) =
|
||||
Configuration.dbConn().use { ensureTable(tableName, it) }
|
||||
|
||||
/**
|
||||
* Create an index on field(s) within documents in the specified table if necessary
|
||||
*
|
||||
* @param tableName The table to be indexed (may include schema)
|
||||
* @param indexName The name of the index to create
|
||||
* @param fields One or more fields to be indexed
|
||||
* @param conn The connection on which the query should be executed
|
||||
* @throws DocumentException If any dependent process does
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>, conn: Connection) =
|
||||
Custom.nonQuery(DefinitionQuery.ensureIndexOn(tableName, indexName, fields), conn = conn)
|
||||
|
||||
/**
|
||||
* Create an index on field(s) within documents in the specified table if necessary
|
||||
*
|
||||
* @param tableName The table to be indexed (may include schema)
|
||||
* @param indexName The name of the index to create
|
||||
* @param fields One or more fields to be indexed
|
||||
* @throws DocumentException If no connection string has been set, or if any dependent process does
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>) =
|
||||
Configuration.dbConn().use { ensureFieldIndex(tableName, indexName, fields, it) }
|
||||
|
||||
/**
|
||||
* Create a document index on a table (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table to be indexed (may include schema)
|
||||
* @param indexType The type of index to ensure
|
||||
* @param conn The connection on which the query should be executed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex, conn: Connection) =
|
||||
Custom.nonQuery(DefinitionQuery.ensureDocumentIndexOn(tableName, indexType), conn = conn)
|
||||
|
||||
/**
|
||||
* Create a document index on a table (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table to be indexed (may include schema)
|
||||
* @param indexType The type of index to ensure
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex) =
|
||||
Configuration.dbConn().use { ensureDocumentIndex(tableName, indexType, it) }
|
||||
}
|
122
src/core/src/main/kotlin/java/Delete.kt
Normal file
122
src/core/src/main/kotlin/java/Delete.kt
Normal file
@ -0,0 +1,122 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.query.DeleteQuery
|
||||
import java.sql.Connection
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to delete documents
|
||||
*/
|
||||
object Delete {
|
||||
|
||||
/**
|
||||
* Delete a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param docId The ID of the document to be deleted
|
||||
* @param conn The connection on which the deletion should be executed
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> byId(tableName: String, docId: TKey, conn: Connection) =
|
||||
Custom.nonQuery(
|
||||
DeleteQuery.byId(tableName, docId),
|
||||
Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
|
||||
conn
|
||||
)
|
||||
|
||||
/**
|
||||
* Delete a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param docId The ID of the document to be deleted
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> byId(tableName: String, docId: TKey) =
|
||||
Configuration.dbConn().use { byId(tableName, docId, it) }
|
||||
|
||||
/**
|
||||
* Delete documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param conn The connection on which the deletion should be executed
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null, conn: Connection) {
|
||||
val named = Parameters.nameFields(fields)
|
||||
Custom.nonQuery(DeleteQuery.byFields(tableName, named, howMatched), Parameters.addFields(named), conn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
|
||||
|
||||
/**
|
||||
* Delete documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param conn The connection on which the deletion should be executed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
|
||||
Custom.nonQuery(DeleteQuery.byContains(tableName), listOf(Parameters.json(":criteria", criteria)), conn)
|
||||
|
||||
/**
|
||||
* Delete documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains) =
|
||||
Configuration.dbConn().use { byContains(tableName, criteria, it) }
|
||||
|
||||
/**
|
||||
* Delete documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param path The JSON path comparison to match
|
||||
* @param conn The connection on which the deletion should be executed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String, conn: Connection) =
|
||||
Custom.nonQuery(DeleteQuery.byJsonPath(tableName), listOf(Parameter(":path", ParameterType.STRING, path)), conn)
|
||||
|
||||
/**
|
||||
* Delete documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param path The JSON path comparison to match
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String) =
|
||||
Configuration.dbConn().use { byJsonPath(tableName, path, it) }
|
||||
}
|
109
src/core/src/main/kotlin/java/Document.kt
Normal file
109
src/core/src/main/kotlin/java/Document.kt
Normal file
@ -0,0 +1,109 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.AutoId
|
||||
import solutions.bitbadger.documents.Configuration
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import solutions.bitbadger.documents.Field
|
||||
import solutions.bitbadger.documents.query.DocumentQuery
|
||||
import solutions.bitbadger.documents.query.Where
|
||||
import solutions.bitbadger.documents.query.statementWhere
|
||||
import java.sql.Connection
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions for manipulating documents
|
||||
*/
|
||||
object Document {
|
||||
|
||||
/**
|
||||
* Insert a new document
|
||||
*
|
||||
* @param tableName The table into which the document should be inserted (may include schema)
|
||||
* @param document The document to be inserted
|
||||
* @param conn The connection on which the query should be executed
|
||||
* @throws DocumentException If IDs are misconfigured, or if the database command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> insert(tableName: String, document: TDoc, conn: Connection) {
|
||||
val strategy = Configuration.autoIdStrategy
|
||||
val query = if (strategy == AutoId.DISABLED || !AutoId.needsAutoId(strategy, document, Configuration.idField)) {
|
||||
DocumentQuery.insert(tableName)
|
||||
} else {
|
||||
DocumentQuery.insert(tableName, strategy)
|
||||
}
|
||||
Custom.nonQuery(query, listOf(Parameters.json(":data", document)), conn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new document
|
||||
*
|
||||
* @param tableName The table into which the document should be inserted (may include schema)
|
||||
* @param document The document to be inserted
|
||||
* @throws DocumentException If no connection string has been set; if IDs are misconfigured; or if the database
|
||||
* command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> insert(tableName: String, document: TDoc) =
|
||||
Configuration.dbConn().use { insert(tableName, document, it) }
|
||||
|
||||
/**
|
||||
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||
*
|
||||
* @param tableName The table in which the document should be saved (may include schema)
|
||||
* @param document The document to be saved
|
||||
* @param conn The connection on which the query should be executed
|
||||
* @throws DocumentException If the database command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> save(tableName: String, document: TDoc, conn: Connection) =
|
||||
Custom.nonQuery(DocumentQuery.save(tableName), listOf(Parameters.json(":data", document)), conn)
|
||||
|
||||
/**
|
||||
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||
*
|
||||
* @param tableName The table in which the document should be saved (may include schema)
|
||||
* @param document The document to be saved
|
||||
* @throws DocumentException If no connection string has been set, or if the database command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> save(tableName: String, document: TDoc) =
|
||||
Configuration.dbConn().use { save(tableName, document, it) }
|
||||
|
||||
/**
|
||||
* Update (replace) a document by its ID
|
||||
*
|
||||
* @param tableName The table in which the document should be replaced (may include schema)
|
||||
* @param docId The ID of the document to be replaced
|
||||
* @param document The document to be replaced
|
||||
* @param conn The connection on which the query should be executed
|
||||
* @throws DocumentException If no dialect has been configured, or if the database command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey, TDoc> update(tableName: String, docId: TKey, document: TDoc, conn: Connection) =
|
||||
Custom.nonQuery(
|
||||
statementWhere(DocumentQuery.update(tableName), Where.byId(":id", docId)),
|
||||
Parameters.addFields(
|
||||
listOf(Field.equal(Configuration.idField, docId, ":id")),
|
||||
mutableListOf(Parameters.json(":data", document))
|
||||
),
|
||||
conn
|
||||
)
|
||||
|
||||
/**
|
||||
* Update (replace) a document by its ID
|
||||
*
|
||||
* @param tableName The table in which the document should be replaced (may include schema)
|
||||
* @param docId The ID of the document to be replaced
|
||||
* @param document The document to be replaced
|
||||
* @throws DocumentException If no connection string has been set, or if the database command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey, TDoc> update(tableName: String, docId: TKey, document: TDoc) =
|
||||
Configuration.dbConn().use { update(tableName, docId, document, it) }
|
||||
}
|
15
src/core/src/main/kotlin/java/DocumentConfig.kt
Normal file
15
src/core/src/main/kotlin/java/DocumentConfig.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.DocumentSerializer
|
||||
|
||||
/**
|
||||
* Configuration for document serialization
|
||||
*/
|
||||
object DocumentConfig {
|
||||
|
||||
/**
|
||||
* The serializer to use for documents
|
||||
*/
|
||||
@JvmStatic
|
||||
var serializer: DocumentSerializer = NullDocumentSerializer()
|
||||
}
|
155
src/core/src/main/kotlin/java/Exists.kt
Normal file
155
src/core/src/main/kotlin/java/Exists.kt
Normal file
@ -0,0 +1,155 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.query.ExistsQuery
|
||||
import java.sql.Connection
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to determine whether documents exist
|
||||
*/
|
||||
object Exists {
|
||||
|
||||
/**
|
||||
* Determine a document's existence by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param docId The ID of the document to be checked
|
||||
* @param conn The connection on which the existence check should be executed
|
||||
* @return True if the document exists, false if not
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> byId(tableName: String, docId: TKey, conn: Connection) =
|
||||
Custom.scalar(
|
||||
ExistsQuery.byId(tableName, docId),
|
||||
Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
|
||||
Boolean::class.java,
|
||||
conn,
|
||||
Results::toExists
|
||||
)
|
||||
|
||||
/**
|
||||
* Determine a document's existence by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param docId The ID of the document to be checked
|
||||
* @return True if the document exists, false if not
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> byId(tableName: String, docId: TKey) =
|
||||
Configuration.dbConn().use { byId(tableName, docId, it) }
|
||||
|
||||
/**
|
||||
* Determine document existence using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param conn The connection on which the existence check should be executed
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
): Boolean {
|
||||
val named = Parameters.nameFields(fields)
|
||||
return Custom.scalar(
|
||||
ExistsQuery.byFields(tableName, named, howMatched),
|
||||
Parameters.addFields(named),
|
||||
Boolean::class.java,
|
||||
conn,
|
||||
Results::toExists
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine document existence using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
|
||||
|
||||
/**
|
||||
* Determine document existence using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param conn The connection on which the existence check should be executed
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
|
||||
Custom.scalar(
|
||||
ExistsQuery.byContains(tableName),
|
||||
listOf(Parameters.json(":criteria", criteria)),
|
||||
Boolean::class.java,
|
||||
conn,
|
||||
Results::toExists
|
||||
)
|
||||
|
||||
/**
|
||||
* Determine document existence using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains) =
|
||||
Configuration.dbConn().use { byContains(tableName, criteria, it) }
|
||||
|
||||
/**
|
||||
* Determine document existence using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param path The JSON path comparison to match
|
||||
* @param conn The connection on which the existence check should be executed
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String, conn: Connection) =
|
||||
Custom.scalar(
|
||||
ExistsQuery.byJsonPath(tableName),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path)),
|
||||
Boolean::class.java,
|
||||
conn,
|
||||
Results::toExists
|
||||
)
|
||||
|
||||
/**
|
||||
* Determine document existence using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param path The JSON path comparison to match
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String) =
|
||||
Configuration.dbConn().use { byJsonPath(tableName, path, it) }
|
||||
}
|
502
src/core/src/main/kotlin/java/Find.kt
Normal file
502
src/core/src/main/kotlin/java/Find.kt
Normal file
@ -0,0 +1,502 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.query.FindQuery
|
||||
import solutions.bitbadger.documents.query.orderBy
|
||||
import java.sql.Connection
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to find and retrieve documents
|
||||
*/
|
||||
object Find {
|
||||
|
||||
/**
|
||||
* Retrieve all documents in the given table, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A list of documents from the given table
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> all(tableName: String, clazz: Class<TDoc>, orderBy: Collection<Field<*>>? = null, conn: Connection) =
|
||||
Custom.list(FindQuery.all(tableName) + (orderBy?.let(::orderBy) ?: ""), listOf(), clazz, conn, Results::fromData)
|
||||
|
||||
/**
|
||||
* Retrieve all documents in the given table
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A list of documents from the given table
|
||||
* @throws DocumentException If no connection string has been set, or if query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TDoc> all(tableName: String, clazz: Class<TDoc>, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { all(tableName, clazz, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve all documents in the given table
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A list of documents from the given table
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> all(tableName: String, clazz: Class<TDoc>, conn: Connection) =
|
||||
all(tableName, clazz, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve a document by its ID
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return An `Optional` item with the document if it is found
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey, TDoc> byId(tableName: String, docId: TKey, clazz: Class<TDoc>, conn: Connection) =
|
||||
Custom.single(
|
||||
FindQuery.byId(tableName, docId),
|
||||
Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
|
||||
clazz,
|
||||
conn,
|
||||
Results::fromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve a document by its ID
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @param clazz The class of the document to be returned
|
||||
* @return An `Optional` item with the document if it is found
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey, TDoc> byId(tableName: String, docId: TKey, clazz: Class<TDoc>) =
|
||||
Configuration.dbConn().use { byId(tableName, docId, clazz, it) }
|
||||
|
||||
/**
|
||||
* Retrieve documents using a field comparison, ordering results by the given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A list of documents matching the field comparison
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
clazz: Class<TDoc>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
): List<TDoc> {
|
||||
val named = Parameters.nameFields(fields)
|
||||
return Custom.list(
|
||||
FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
|
||||
Parameters.addFields(named),
|
||||
clazz,
|
||||
conn,
|
||||
Results::fromData
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents using a field comparison, ordering results by the given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A list of documents matching the field comparison
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TDoc> byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
clazz: Class<TDoc>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) =
|
||||
Configuration.dbConn().use { byFields(tableName, fields, clazz, howMatched, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve documents using a field comparison
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A list of documents matching the field comparison
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
clazz: Class<TDoc>,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
) =
|
||||
byFields(tableName, fields, clazz, howMatched, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A list of documents matching the JSON containment query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc, TContains> byContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) =
|
||||
Custom.list(
|
||||
FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameters.json(":criteria", criteria)),
|
||||
clazz,
|
||||
conn,
|
||||
Results::fromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A list of documents matching the JSON containment query
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TDoc, TContains> byContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) =
|
||||
Configuration.dbConn().use { byContains(tableName, criteria, clazz, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A list of documents matching the JSON containment query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc, TContains> byContains(tableName: String, criteria: TContains, clazz: Class<TDoc>, conn: Connection) =
|
||||
byContains(tableName, criteria, clazz, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A list of documents matching the JSON Path match query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> byJsonPath(
|
||||
tableName: String,
|
||||
path: String,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) =
|
||||
Custom.list(
|
||||
FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path)),
|
||||
clazz,
|
||||
conn,
|
||||
Results::fromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A list of documents matching the JSON Path match query
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TDoc> byJsonPath(tableName: String, path: String, clazz: Class<TDoc>, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { byJsonPath(tableName, path, clazz, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A list of documents matching the JSON Path match query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> byJsonPath(tableName: String, path: String, clazz: Class<TDoc>, conn: Connection) =
|
||||
byJsonPath(tableName, path, clazz, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a field comparison and optional ordering fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return An `Optional` item, with the first document matching the field comparison if found
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> firstByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
clazz: Class<TDoc>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
): Optional<TDoc & Any> {
|
||||
val named = Parameters.nameFields(fields)
|
||||
return Custom.single(
|
||||
FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
|
||||
Parameters.addFields(named),
|
||||
clazz,
|
||||
conn,
|
||||
Results::fromData
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a field comparison and optional ordering fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return An `Optional` item, with the first document matching the field comparison if found
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TDoc> firstByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
clazz: Class<TDoc>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) =
|
||||
Configuration.dbConn().use { firstByFields(tableName, fields, clazz, howMatched, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a field comparison
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return An `Optional` item, with the first document matching the field comparison if found
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> firstByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
clazz: Class<TDoc>,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
) =
|
||||
firstByFields(tableName, fields, clazz, howMatched, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return An `Optional` item, with the first document matching the JSON containment query if found
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc, TContains> firstByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) =
|
||||
Custom.single(
|
||||
FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameters.json(":criteria", criteria)),
|
||||
clazz,
|
||||
conn,
|
||||
Results::fromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return An `Optional` item, with the first document matching the JSON containment query if found
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc, TContains> firstByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
clazz: Class<TDoc>,
|
||||
conn: Connection
|
||||
) =
|
||||
firstByContains(tableName, criteria, clazz, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return An `Optional` item, with the first document matching the JSON containment query if found
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TDoc, TContains> firstByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) =
|
||||
Configuration.dbConn().use { firstByContains(tableName, criteria, clazz, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return An `Optional` item, with the first document matching the JSON Path match query if found
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> firstByJsonPath(
|
||||
tableName: String,
|
||||
path: String,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) =
|
||||
Custom.single(
|
||||
FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path)),
|
||||
clazz,
|
||||
conn,
|
||||
Results::fromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return An `Optional` item, with the first document matching the JSON Path match query if found
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TDoc> firstByJsonPath(tableName: String, path: String, clazz: Class<TDoc>, conn: Connection) =
|
||||
firstByJsonPath(tableName, path, clazz, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return An `Optional` item, with the first document matching the JSON Path match query if found
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TDoc> firstByJsonPath(
|
||||
tableName: String,
|
||||
path: String,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) =
|
||||
Configuration.dbConn().use { firstByJsonPath(tableName, path, clazz, orderBy, it) }
|
||||
}
|
877
src/core/src/main/kotlin/java/Json.kt
Normal file
877
src/core/src/main/kotlin/java/Json.kt
Normal file
@ -0,0 +1,877 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.query.FindQuery
|
||||
import solutions.bitbadger.documents.query.orderBy
|
||||
import java.io.PrintWriter
|
||||
import java.sql.Connection
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to find and retrieve documents, returning them as JSON strings
|
||||
*/
|
||||
object Json {
|
||||
|
||||
/**
|
||||
* Retrieve all documents in the given table, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON array of documents from the given table
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun all(tableName: String, orderBy: Collection<Field<*>>? = null, conn: Connection) =
|
||||
Custom.jsonArray(
|
||||
FindQuery.all(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(),
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve all documents in the given table (creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A JSON array of documents from the given table
|
||||
* @throws DocumentException If no connection string has been set, or if query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun all(tableName: String, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { all(tableName, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve all documents in the given table
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON array of documents from the given table
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun all(tableName: String, conn: Connection) =
|
||||
all(tableName, null, conn)
|
||||
|
||||
/**
|
||||
* Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeAll(tableName: String, writer: PrintWriter, orderBy: Collection<Field<*>>? = null, conn: Connection) =
|
||||
Custom.writeJsonArray(
|
||||
FindQuery.all(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(),
|
||||
writer,
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields
|
||||
* (creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun writeAll(tableName: String, writer: PrintWriter, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { writeAll(tableName, writer, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Write all documents in the given table to the given `PrintWriter`
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeAll(tableName: String, writer: PrintWriter, conn: Connection) =
|
||||
writeAll(tableName, writer, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve a document by its ID
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON document if found, an empty JSON object if not found
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> byId(tableName: String, docId: TKey, conn: Connection) =
|
||||
Custom.jsonSingle(
|
||||
FindQuery.byId(tableName, docId),
|
||||
Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve a document by its ID (creates connection)
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @return A JSON document if found, an empty JSON object if not found
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> byId(tableName: String, docId: TKey) =
|
||||
Configuration.dbConn().use { byId(tableName, docId, it) }
|
||||
|
||||
/**
|
||||
* Write a document to the given `PrintWriter` by its ID (writes empty object if not found)
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> writeById(tableName: String, writer: PrintWriter, docId: TKey, conn: Connection) =
|
||||
writer.write(byId(tableName, docId, conn))
|
||||
|
||||
/**
|
||||
* Write a document to the given `PrintWriter` by its ID (writes empty object if not found; creates connection)
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> writeById(tableName: String, writer: PrintWriter, docId: TKey) =
|
||||
Configuration.dbConn().use { writeById(tableName, writer, docId, it) }
|
||||
|
||||
/**
|
||||
* Retrieve documents using a field comparison, ordering results by the given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON array of documents matching the field comparison
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
): String {
|
||||
val named = Parameters.nameFields(fields)
|
||||
return Custom.jsonArray(
|
||||
FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
|
||||
Parameters.addFields(named),
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents using a field comparison, ordering results by the given fields (creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A JSON array of documents matching the field comparison
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Configuration.dbConn().use { byFields(tableName, fields, howMatched, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve documents using a field comparison
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON array of documents matching the field comparison
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null, conn: Connection) =
|
||||
byFields(tableName, fields, howMatched, null, conn)
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeByFields(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) {
|
||||
val named = Parameters.nameFields(fields)
|
||||
Custom.writeJsonArray(
|
||||
FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
|
||||
Parameters.addFields(named),
|
||||
writer,
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields
|
||||
* (creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun writeByFields(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Configuration.dbConn().use { writeByFields(tableName, writer, fields, howMatched, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a field comparison
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeByFields(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
) = writeByFields(tableName, writer, fields, howMatched, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON array of documents matching the JSON containment query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) = Custom.jsonArray(
|
||||
FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameters.json(":criteria", criteria)),
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only; creates
|
||||
* connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A JSON array of documents matching the JSON containment query
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { byContains(tableName, criteria, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON array of documents matching the JSON containment query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
|
||||
byContains(tableName, criteria, null, conn)
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a JSON containment query, ordering results by the given fields
|
||||
* (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> writeByContains(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) = Custom.writeJsonArray(
|
||||
FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameters.json(":criteria", criteria)),
|
||||
writer,
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a JSON containment query, ordering results by the given fields
|
||||
* (PostgreSQL only; creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TContains> writeByContains(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Configuration.dbConn().use { writeByContains(tableName, writer, criteria, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> writeByContains(tableName: String, writer: PrintWriter, criteria: TContains, conn: Connection) =
|
||||
writeByContains(tableName, writer, criteria, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON array of documents matching the JSON Path match query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String, orderBy: Collection<Field<*>>? = null, conn: Connection) =
|
||||
Custom.jsonArray(
|
||||
FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path)),
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only; creates
|
||||
* connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A JSON array of documents matching the JSON Path match query
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byJsonPath(tableName: String, path: String, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { byJsonPath(tableName, path, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return A JSON array of documents matching the JSON Path match query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String, conn: Connection) =
|
||||
byJsonPath(tableName, path, null, conn)
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a JSON Path match query, ordering results by the given fields
|
||||
* (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeByJsonPath(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
path: String,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) = Custom.writeJsonArray(
|
||||
FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path)),
|
||||
writer,
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a JSON Path match query, ordering results by the given fields
|
||||
* (PostgreSQL only; creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun writeByJsonPath(tableName: String, writer: PrintWriter, path: String, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { writeByJsonPath(tableName, writer, path, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param path The JSON path comparison to match
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeByJsonPath(tableName: String, writer: PrintWriter, path: String, conn: Connection) =
|
||||
writeByJsonPath(tableName, writer, path, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a field comparison and optional ordering fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return The first JSON document matching the field comparison if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun firstByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
): String {
|
||||
val named = Parameters.nameFields(fields)
|
||||
return Custom.jsonSingle(
|
||||
FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
|
||||
Parameters.addFields(named),
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a field comparison and optional ordering fields (creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return The first JSON document matching the field comparison if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun firstByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Configuration.dbConn().use { firstByFields(tableName, fields, howMatched, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a field comparison
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return The first JSON document matching the field comparison if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun firstByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
) = firstByFields(tableName, fields, howMatched, null, conn)
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a field comparison and optional ordering fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeFirstByFields(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) = writer.write(firstByFields(tableName, fields, howMatched, orderBy, conn))
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a field comparison and optional ordering fields
|
||||
* (creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun writeFirstByFields(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Configuration.dbConn().use { writeFirstByFields(tableName, writer, fields, howMatched, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a field comparison
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeFirstByFields(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
) = writeFirstByFields(tableName, writer, fields, howMatched, null, conn)
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> firstByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) = Custom.jsonSingle(
|
||||
FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameters.json(":criteria", criteria)),
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> firstByContains(tableName: String, criteria: TContains, conn: Connection) =
|
||||
firstByContains(tableName, criteria, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only; creates
|
||||
* connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TContains> firstByContains(tableName: String, criteria: TContains, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { firstByContains(tableName, criteria, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a JSON containment query and optional ordering fields
|
||||
* (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> writeFirstByContains(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) = writer.write(firstByContains(tableName, criteria, orderBy, conn))
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> writeFirstByContains(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
criteria: TContains,
|
||||
conn: Connection
|
||||
) = writeFirstByContains(tableName, writer, criteria, null, conn)
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a JSON containment query and optional ordering fields
|
||||
* (PostgreSQL only; creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TContains> writeFirstByContains(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Configuration.dbConn().use { writeFirstByContains(tableName, writer, criteria, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun firstByJsonPath(tableName: String, path: String, orderBy: Collection<Field<*>>? = null, conn: Connection) =
|
||||
Custom.jsonSingle(
|
||||
FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path)),
|
||||
conn,
|
||||
Results::jsonFromData
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun firstByJsonPath(tableName: String, path: String, conn: Connection) =
|
||||
firstByJsonPath(tableName, path, null, conn)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only; creates
|
||||
* connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun firstByJsonPath(tableName: String, path: String, orderBy: Collection<Field<*>>? = null) =
|
||||
Configuration.dbConn().use { firstByJsonPath(tableName, path, orderBy, it) }
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a JSON Path match query and optional ordering fields
|
||||
* (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeFirstByJsonPath(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
path: String,
|
||||
orderBy: Collection<Field<*>>? = null,
|
||||
conn: Connection
|
||||
) = writer.write(firstByJsonPath(tableName, path, orderBy, conn))
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param path The JSON path comparison to match
|
||||
* @param conn The connection over which documents should be retrieved
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun writeFirstByJsonPath(tableName: String, writer: PrintWriter, path: String, conn: Connection) =
|
||||
writeFirstByJsonPath(tableName, writer, path, null, conn)
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a JSON Path match query and optional ordering fields
|
||||
* (PostgreSQL only; creates connection)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun writeFirstByJsonPath(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
path: String,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Configuration.dbConn().use { writeFirstByJsonPath(tableName, writer, path, orderBy, it) }
|
||||
}
|
21
src/core/src/main/kotlin/java/NullDocumentSerializer.kt
Normal file
21
src/core/src/main/kotlin/java/NullDocumentSerializer.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.DocumentSerializer
|
||||
|
||||
/**
|
||||
* A serializer that tells the user to implement another one
|
||||
*
|
||||
* This is the default serializer, so the library itself does not have any firm dependency on any JSON serialization
|
||||
* library. The tests for this library (will) have an example Jackson-based serializer.
|
||||
*/
|
||||
class NullDocumentSerializer : DocumentSerializer {
|
||||
|
||||
override fun <TDoc> serialize(document: TDoc): String {
|
||||
TODO("Replace this serializer in DocumentConfig.serializer")
|
||||
}
|
||||
|
||||
override fun <TDoc> deserialize(json: String, clazz: Class<TDoc>): TDoc {
|
||||
TODO("Replace this serializer in DocumentConfig.serializer")
|
||||
}
|
||||
|
||||
}
|
131
src/core/src/main/kotlin/java/Parameters.kt
Normal file
131
src/core/src/main/kotlin/java/Parameters.kt
Normal file
@ -0,0 +1,131 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.ParameterName
|
||||
import java.sql.Connection
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.SQLException
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to assist with the creation and implementation of parameters for SQL queries
|
||||
*
|
||||
* @author Daniel J. Summers <daniel@bitbadger.solutions>
|
||||
*/
|
||||
object Parameters {
|
||||
|
||||
/**
|
||||
* Assign parameter names to any fields that do not have them assigned
|
||||
*
|
||||
* @param fields The collection of fields to be named
|
||||
* @return The collection of fields with parameter names assigned
|
||||
*/
|
||||
@JvmStatic
|
||||
fun nameFields(fields: Collection<Field<*>>): Collection<Field<*>> {
|
||||
val name = ParameterName()
|
||||
return fields.map {
|
||||
if (it.parameterName.isNullOrEmpty() && !listOf(Op.EXISTS, Op.NOT_EXISTS).contains(it.comparison.op)) {
|
||||
it.withParameterName(name.derive(null))
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a parameter by encoding a JSON object
|
||||
*
|
||||
* @param name The parameter name
|
||||
* @param value The object to be encoded as JSON
|
||||
* @return A parameter with the value encoded
|
||||
*/
|
||||
@JvmStatic
|
||||
fun <T> json(name: String, value: T) =
|
||||
Parameter(name, ParameterType.JSON, DocumentConfig.serializer.serialize(value))
|
||||
|
||||
/**
|
||||
* Add field parameters to the given set of parameters
|
||||
*
|
||||
* @param fields The fields being compared in the query
|
||||
* @param existing Any existing parameters for the query (optional, defaults to empty collection)
|
||||
* @return A collection of parameters for the query
|
||||
*/
|
||||
@JvmStatic
|
||||
fun addFields(fields: Collection<Field<*>>, existing: MutableCollection<Parameter<*>> = mutableListOf()) =
|
||||
fields.fold(existing) { acc, field -> field.appendParameter(acc) }
|
||||
|
||||
/**
|
||||
* Replace the parameter names in the query with question marks
|
||||
*
|
||||
* @param query The query with named placeholders
|
||||
* @param parameters The parameters for the query
|
||||
* @return The query, with name parameters changed to `?`s
|
||||
*/
|
||||
@JvmStatic
|
||||
fun replaceNamesInQuery(query: String, parameters: Collection<Parameter<*>>) =
|
||||
parameters.sortedByDescending { it.name.length }.fold(query) { acc, param -> acc.replace(param.name, "?") }
|
||||
|
||||
/**
|
||||
* Apply the given parameters to the given query, returning a prepared statement
|
||||
*
|
||||
* @param conn The active JDBC connection
|
||||
* @param query The query
|
||||
* @param parameters The parameters for the query
|
||||
* @return A `PreparedStatement` with the parameter names replaced with `?` and parameter values bound
|
||||
* @throws DocumentException If parameter names are invalid or number value types are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun apply(conn: Connection, query: String, parameters: Collection<Parameter<*>>): PreparedStatement {
|
||||
|
||||
if (parameters.isEmpty()) return try {
|
||||
conn.prepareStatement(query)
|
||||
} catch (ex: SQLException) {
|
||||
throw DocumentException("Error preparing no-parameter query: ${ex.message}", ex)
|
||||
}
|
||||
|
||||
val replacements = mutableListOf<Pair<Int, Parameter<*>>>()
|
||||
parameters.sortedByDescending { it.name.length }.forEach {
|
||||
var startPos = query.indexOf(it.name)
|
||||
while (startPos > -1) {
|
||||
replacements.add(Pair(startPos, it))
|
||||
startPos = query.indexOf(it.name, startPos + it.name.length + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return try {
|
||||
replaceNamesInQuery(query, parameters)
|
||||
//.also(::println)
|
||||
.let { conn.prepareStatement(it) }
|
||||
.also { stmt ->
|
||||
replacements.sortedBy { it.first }
|
||||
.map { it.second }
|
||||
.forEachIndexed { index, param -> param.bind(stmt, index + 1) }
|
||||
}
|
||||
} catch (ex: SQLException) {
|
||||
throw DocumentException("Error creating query / binding parameters: ${ex.message}", ex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create parameters for field names to be removed from a document
|
||||
*
|
||||
* @param names The names of the fields to be removed
|
||||
* @param parameterName The parameter name to use for the query
|
||||
* @return A list of parameters to use for building the query
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun fieldNames(names: Collection<String>, parameterName: String = ":name"): MutableCollection<Parameter<*>> =
|
||||
when (Configuration.dialect("generate field name parameters")) {
|
||||
Dialect.POSTGRESQL -> mutableListOf(
|
||||
Parameter(parameterName, ParameterType.STRING, names.joinToString(",").let { "{$it}" })
|
||||
)
|
||||
|
||||
Dialect.SQLITE -> names.mapIndexed { index, name ->
|
||||
Parameter("$parameterName$index", ParameterType.STRING, name)
|
||||
}.toMutableList()
|
||||
}
|
||||
}
|
155
src/core/src/main/kotlin/java/Patch.kt
Normal file
155
src/core/src/main/kotlin/java/Patch.kt
Normal file
@ -0,0 +1,155 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.query.PatchQuery
|
||||
import java.sql.Connection
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to patch (partially update) documents
|
||||
*/
|
||||
object Patch {
|
||||
|
||||
/**
|
||||
* Patch a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which a document should be patched
|
||||
* @param docId The ID of the document to be patched
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @param conn The connection on which the update should be executed
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey, TPatch> byId(tableName: String, docId: TKey, patch: TPatch, conn: Connection) =
|
||||
Custom.nonQuery(
|
||||
PatchQuery.byId(tableName, docId),
|
||||
Parameters.addFields(
|
||||
listOf(Field.equal(Configuration.idField, docId, ":id")),
|
||||
mutableListOf(Parameters.json(":data", patch))
|
||||
),
|
||||
conn
|
||||
)
|
||||
|
||||
/**
|
||||
* Patch a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which a document should be patched
|
||||
* @param docId The ID of the document to be patched
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey, TPatch> byId(tableName: String, docId: TKey, patch: TPatch) =
|
||||
Configuration.dbConn().use { byId(tableName, docId, patch, it) }
|
||||
|
||||
/**
|
||||
* Patch documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param fields The fields which should be compared
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param conn The connection on which the update should be executed
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TPatch> byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
patch: TPatch,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
) {
|
||||
val named = Parameters.nameFields(fields)
|
||||
Custom.nonQuery(
|
||||
PatchQuery.byFields(tableName, named, howMatched),
|
||||
Parameters.addFields(named, mutableListOf(Parameters.json(":data", patch))),
|
||||
conn
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param fields The fields which should be compared
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @param howMatched How the fields should be matched
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TPatch> byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
patch: TPatch,
|
||||
howMatched: FieldMatch? = null
|
||||
) =
|
||||
Configuration.dbConn().use { byFields(tableName, fields, patch, howMatched, it) }
|
||||
|
||||
/**
|
||||
* Patch documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param criteria The object against which JSON containment should be checked
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @param conn The connection on which the update should be executed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains, TPatch> byContains(tableName: String, criteria: TContains, patch: TPatch, conn: Connection) =
|
||||
Custom.nonQuery(
|
||||
PatchQuery.byContains(tableName),
|
||||
listOf(Parameters.json(":criteria", criteria), Parameters.json(":data", patch)),
|
||||
conn
|
||||
)
|
||||
|
||||
/**
|
||||
* Patch documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param criteria The object against which JSON containment should be checked
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains, TPatch> byContains(tableName: String, criteria: TContains, patch: TPatch) =
|
||||
Configuration.dbConn().use { byContains(tableName, criteria, patch, it) }
|
||||
|
||||
/**
|
||||
* Patch documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param path The JSON path comparison to match
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @param conn The connection on which the update should be executed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TPatch> byJsonPath(tableName: String, path: String, patch: TPatch, conn: Connection) =
|
||||
Custom.nonQuery(
|
||||
PatchQuery.byJsonPath(tableName),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path), Parameters.json(":data", patch)),
|
||||
conn
|
||||
)
|
||||
|
||||
/**
|
||||
* Patch documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param path The JSON path comparison to match
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TPatch> byJsonPath(tableName: String, path: String, patch: TPatch) =
|
||||
Configuration.dbConn().use { byJsonPath(tableName, path, patch, it) }
|
||||
}
|
178
src/core/src/main/kotlin/java/RemoveFields.kt
Normal file
178
src/core/src/main/kotlin/java/RemoveFields.kt
Normal file
@ -0,0 +1,178 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.query.RemoveFieldsQuery
|
||||
import java.sql.Connection
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to remove fields from documents
|
||||
*/
|
||||
object RemoveFields {
|
||||
|
||||
/**
|
||||
* Translate field paths to JSON paths for SQLite queries
|
||||
*
|
||||
* @param parameters The parameters for the specified fields
|
||||
* @return The parameters for the specified fields, translated if used for SQLite
|
||||
*/
|
||||
private fun translatePath(parameters: MutableCollection<Parameter<*>>): MutableCollection<Parameter<*>> {
|
||||
val dialect = Configuration.dialect("remove fields")
|
||||
return when (dialect) {
|
||||
Dialect.POSTGRESQL -> parameters
|
||||
Dialect.SQLITE -> parameters.map { Parameter(it.name, it.type, "$.${it.value}") }.toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove fields from a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which the document's fields should be removed
|
||||
* @param docId The ID of the document to have fields removed
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @param conn The connection on which the update should be executed
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> byId(tableName: String, docId: TKey, toRemove: Collection<String>, conn: Connection) {
|
||||
val nameParams = Parameters.fieldNames(toRemove)
|
||||
Custom.nonQuery(
|
||||
RemoveFieldsQuery.byId(tableName, nameParams, docId),
|
||||
Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id")), translatePath(nameParams)),
|
||||
conn
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove fields from a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which the document's fields should be removed
|
||||
* @param docId The ID of the document to have fields removed
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @throws DocumentException If no connection string has been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TKey> byId(tableName: String, docId: TKey, toRemove: Collection<String>) =
|
||||
Configuration.dbConn().use { byId(tableName, docId, toRemove, it) }
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param fields The fields which should be compared
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param conn The connection on which the update should be executed
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
toRemove: Collection<String>,
|
||||
howMatched: FieldMatch? = null,
|
||||
conn: Connection
|
||||
) {
|
||||
val named = Parameters.nameFields(fields)
|
||||
val nameParams = Parameters.fieldNames(toRemove)
|
||||
Custom.nonQuery(
|
||||
RemoveFieldsQuery.byFields(tableName, nameParams, named, howMatched),
|
||||
Parameters.addFields(named, translatePath(nameParams)),
|
||||
conn
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param fields The fields which should be compared
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @param howMatched How the fields should be matched
|
||||
* @throws DocumentException If no connection string has been set, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
toRemove: Collection<String>,
|
||||
howMatched: FieldMatch? = null
|
||||
) =
|
||||
Configuration.dbConn().use { byFields(tableName, fields, toRemove, howMatched, it) }
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param criteria The object against which JSON containment should be checked
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @param conn The connection on which the update should be executed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
toRemove: Collection<String>,
|
||||
conn: Connection
|
||||
) {
|
||||
val nameParams = Parameters.fieldNames(toRemove)
|
||||
Custom.nonQuery(
|
||||
RemoveFieldsQuery.byContains(tableName, nameParams),
|
||||
listOf(Parameters.json(":criteria", criteria), *nameParams.toTypedArray()),
|
||||
conn
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param criteria The object against which JSON containment should be checked
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun <TContains> byContains(tableName: String, criteria: TContains, toRemove: Collection<String>) =
|
||||
Configuration.dbConn().use { byContains(tableName, criteria, toRemove, it) }
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param path The JSON path comparison to match
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @param conn The connection on which the update should be executed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String, toRemove: Collection<String>, conn: Connection) {
|
||||
val nameParams = Parameters.fieldNames(toRemove)
|
||||
Custom.nonQuery(
|
||||
RemoveFieldsQuery.byJsonPath(tableName, nameParams),
|
||||
listOf(Parameter(":path", ParameterType.STRING, path), *nameParams.toTypedArray()),
|
||||
conn
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param path The JSON path comparison to match
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, path: String, toRemove: Collection<String>) =
|
||||
Configuration.dbConn().use { byJsonPath(tableName, path, toRemove, it) }
|
||||
}
|
167
src/core/src/main/kotlin/java/Results.kt
Normal file
167
src/core/src/main/kotlin/java/Results.kt
Normal file
@ -0,0 +1,167 @@
|
||||
package solutions.bitbadger.documents.java
|
||||
|
||||
import solutions.bitbadger.documents.Configuration
|
||||
import solutions.bitbadger.documents.Dialect
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import java.io.PrintWriter
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.ResultSet
|
||||
import java.sql.SQLException
|
||||
|
||||
/**
|
||||
* Functions to create results from queries
|
||||
*/
|
||||
object Results {
|
||||
|
||||
/**
|
||||
* Create a domain item from a document, specifying the field in which the document is found
|
||||
*
|
||||
* @param field The field name containing the JSON document
|
||||
* @param rs A `ResultSet` set to the row with the document to be constructed
|
||||
* @param clazz The class of the document to be returned
|
||||
* @return The constructed domain item
|
||||
*/
|
||||
@JvmStatic
|
||||
fun <TDoc> fromDocument(field: String, rs: ResultSet, clazz: Class<TDoc>) =
|
||||
DocumentConfig.serializer.deserialize(rs.getString(field), clazz)
|
||||
|
||||
/**
|
||||
* Create a domain item from a document
|
||||
*
|
||||
* @param rs A `ResultSet` set to the row with the document to be constructed<
|
||||
* @param clazz The class of the document to be returned
|
||||
* @return The constructed domain item
|
||||
*/
|
||||
@JvmStatic
|
||||
fun <TDoc> fromData(rs: ResultSet, clazz: Class<TDoc>) =
|
||||
fromDocument("data", rs, clazz)
|
||||
|
||||
/**
|
||||
* Create a list of items for the results of the given command, using the specified mapping function
|
||||
*
|
||||
* @param stmt The prepared statement to execute
|
||||
* @param mapFunc The mapping function from data reader to domain class instance
|
||||
* @param clazz The class of the document to be returned
|
||||
* @return A list of items from the query's result
|
||||
* @throws DocumentException If there is a problem executing the query (unchecked)
|
||||
*/
|
||||
@JvmStatic
|
||||
fun <TDoc> toCustomList(
|
||||
stmt: PreparedStatement, clazz: Class<TDoc>, mapFunc: (ResultSet, Class<TDoc>) -> TDoc
|
||||
) =
|
||||
try {
|
||||
stmt.executeQuery().use {
|
||||
val results = mutableListOf<TDoc>()
|
||||
while (it.next()) {
|
||||
results.add(mapFunc(it, clazz))
|
||||
}
|
||||
results.toList()
|
||||
}
|
||||
} catch (ex: SQLException) {
|
||||
throw DocumentException("Error retrieving documents from query: ${ex.message}", ex)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a count from the first column
|
||||
*
|
||||
* @param rs A `ResultSet` set to the row with the count to retrieve
|
||||
* @param clazz The type parameter (ignored; this always returns `Long`)
|
||||
* @return The count from the row
|
||||
* @throws DocumentException If the dialect has not been set (unchecked)
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toCount(rs: ResultSet, clazz: Class<*>) =
|
||||
when (Configuration.dialect()) {
|
||||
Dialect.POSTGRESQL -> rs.getInt("it").toLong()
|
||||
Dialect.SQLITE -> rs.getLong("it")
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a true/false value from the first column
|
||||
*
|
||||
* @param rs A `ResultSet` set to the row with the true/false value to retrieve
|
||||
* @param clazz The type parameter (ignored; this always returns `Boolean`)
|
||||
* @return The true/false value from the row
|
||||
* @throws DocumentException If the dialect has not been set (unchecked)
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toExists(rs: ResultSet, clazz: Class<*>) =
|
||||
when (Configuration.dialect()) {
|
||||
Dialect.POSTGRESQL -> rs.getBoolean("it")
|
||||
Dialect.SQLITE -> toCount(rs, Long::class.java) > 0L
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the JSON text of a document, specifying the field in which the document is found
|
||||
*
|
||||
* @param field The field name containing the JSON document
|
||||
* @param rs A `ResultSet` set to the row with the document to be constructed
|
||||
* @return The JSON text of the document
|
||||
*/
|
||||
@JvmStatic
|
||||
fun jsonFromDocument(field: String, rs: ResultSet) =
|
||||
rs.getString(field) ?: "{}"
|
||||
|
||||
/**
|
||||
* Retrieve the JSON text of a document, specifying the field in which the document is found
|
||||
*
|
||||
* @param rs A `ResultSet` set to the row with the document to be constructed
|
||||
* @return The JSON text of the document
|
||||
*/
|
||||
@JvmStatic
|
||||
fun jsonFromData(rs: ResultSet) =
|
||||
jsonFromDocument("data", rs)
|
||||
|
||||
/**
|
||||
* Create a JSON array of items for the results of the given command, using the specified mapping function
|
||||
*
|
||||
* @param stmt The prepared statement to execute
|
||||
* @param mapFunc The mapping function from data reader to JSON text
|
||||
* @return A string with a JSON array of documents from the query's result
|
||||
* @throws DocumentException If there is a problem executing the query (unchecked)
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toJsonArray(stmt: PreparedStatement, mapFunc: (ResultSet) -> String): String =
|
||||
try {
|
||||
val results = StringBuilder("[")
|
||||
stmt.executeQuery().use {
|
||||
while (it.next()) {
|
||||
if (results.length > 2) results.append(",")
|
||||
results.append(mapFunc(it))
|
||||
}
|
||||
}
|
||||
results.append("]").toString()
|
||||
} catch (ex: SQLException) {
|
||||
throw DocumentException("Error retrieving documents from query: ${ex.message}", ex)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a JSON array of items for the results of the given command to the given `PrintWriter`, using the specified
|
||||
* mapping function
|
||||
*
|
||||
* @param writer The writer for the results of the query
|
||||
* @param stmt The prepared statement to execute
|
||||
* @param mapFunc The mapping function from data reader to JSON text
|
||||
* @return A string with a JSON array of documents from the query's result
|
||||
* @throws DocumentException If there is a problem executing the query (unchecked)
|
||||
*/
|
||||
@JvmStatic
|
||||
fun writeJsonArray(writer: PrintWriter, stmt: PreparedStatement, mapFunc: (ResultSet) -> String) =
|
||||
try {
|
||||
writer.write("[")
|
||||
stmt.executeQuery().use {
|
||||
var isFirst = true
|
||||
while (it.next()) {
|
||||
if (isFirst) {
|
||||
isFirst = false
|
||||
} else {
|
||||
writer.write(",")
|
||||
}
|
||||
writer.write(mapFunc(it))
|
||||
}
|
||||
}
|
||||
writer.write("]")
|
||||
} catch (ex: SQLException) {
|
||||
throw DocumentException("Error writing documents from query: ${ex.message}", ex)
|
||||
}
|
||||
}
|
885
src/core/src/main/kotlin/java/extensions/Connection.kt
Normal file
885
src/core/src/main/kotlin/java/extensions/Connection.kt
Normal file
@ -0,0 +1,885 @@
|
||||
@file:JvmName("ConnExt")
|
||||
|
||||
package solutions.bitbadger.documents.java.extensions
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import solutions.bitbadger.documents.java.*
|
||||
import java.io.PrintWriter
|
||||
import java.sql.Connection
|
||||
import java.sql.ResultSet
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
// ~~~ CUSTOM QUERIES ~~~
|
||||
|
||||
/**
|
||||
* Execute a query that returns a list of results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return A list of results for the given query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TDoc> Connection.customList(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<TDoc>,
|
||||
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
|
||||
) = Custom.list(query, parameters, clazz, this, mapFunc)
|
||||
|
||||
/**
|
||||
* Execute a query that returns a JSON array of results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param mapFunc The mapping function to extract the JSON from the query
|
||||
* @return A JSON array of results for the given query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.customJsonArray(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
mapFunc: (ResultSet) -> String
|
||||
) = Custom.jsonArray(query, parameters, this, mapFunc)
|
||||
|
||||
/**
|
||||
* Execute a query, writing its JSON array of results to the given `PrintWriter` (creates connection)
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param writer The writer to which the results should be written
|
||||
* @param mapFunc The mapping function to extract the JSON from the query
|
||||
* @return A JSON array of results for the given query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.writeCustomJsonArray(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
writer: PrintWriter,
|
||||
mapFunc: (ResultSet) -> String
|
||||
) = Custom.writeJsonArray(query, parameters, writer, this, mapFunc)
|
||||
|
||||
/**
|
||||
* Execute a query that returns one or no results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return The document if one matches the query, `null` otherwise
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TDoc> Connection.customSingle(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<TDoc>,
|
||||
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
|
||||
) = Custom.single(query, parameters, clazz, this, mapFunc)
|
||||
|
||||
/**
|
||||
* Execute a query that returns JSON for one or no documents
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return The JSON for the document if found, an empty object (`{}`) if not
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.customJsonSingle(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
mapFunc: (ResultSet) -> String
|
||||
) = Custom.jsonSingle(query, parameters, this, mapFunc)
|
||||
|
||||
/**
|
||||
* Execute a query that returns no results
|
||||
*
|
||||
* @param query The query to retrieve the results
|
||||
* @param parameters Parameters to use for the query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.customNonQuery(query: String, parameters: Collection<Parameter<*>> = listOf()) =
|
||||
Custom.nonQuery(query, parameters, this)
|
||||
|
||||
/**
|
||||
* Execute a query that returns a scalar result
|
||||
*
|
||||
* @param query The query to retrieve the result
|
||||
* @param parameters Parameters to use for the query
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param mapFunc The mapping function between the document and the domain item
|
||||
* @return The scalar value from the query
|
||||
* @throws DocumentException If parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <T : Any> Connection.customScalar(
|
||||
query: String,
|
||||
parameters: Collection<Parameter<*>> = listOf(),
|
||||
clazz: Class<T>,
|
||||
mapFunc: (ResultSet, Class<T>) -> T
|
||||
) = Custom.scalar(query, parameters, clazz, this, mapFunc)
|
||||
|
||||
// ~~~ DEFINITION QUERIES ~~~
|
||||
|
||||
/**
|
||||
* Create a document table if necessary
|
||||
*
|
||||
* @param tableName The table whose existence should be ensured (may include schema)
|
||||
* @throws DocumentException If the dialect is not configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.ensureTable(tableName: String) =
|
||||
Definition.ensureTable(tableName, this)
|
||||
|
||||
/**
|
||||
* Create an index on field(s) within documents in the specified table if necessary
|
||||
*
|
||||
* @param tableName The table to be indexed (may include schema)
|
||||
* @param indexName The name of the index to create
|
||||
* @param fields One or more fields to be indexed<
|
||||
* @throws DocumentException If any dependent process does
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>) =
|
||||
Definition.ensureFieldIndex(tableName, indexName, fields, this)
|
||||
|
||||
/**
|
||||
* Create a document index on a table (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table to be indexed (may include schema)
|
||||
* @param indexType The type of index to ensure
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.ensureDocumentIndex(tableName: String, indexType: DocumentIndex) =
|
||||
Definition.ensureDocumentIndex(tableName, indexType, this)
|
||||
|
||||
// ~~~ DOCUMENT MANIPULATION QUERIES ~~~
|
||||
|
||||
/**
|
||||
* Insert a new document
|
||||
*
|
||||
* @param tableName The table into which the document should be inserted (may include schema)
|
||||
* @param document The document to be inserted
|
||||
* @throws DocumentException If IDs are misconfigured, or if the database command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TDoc> Connection.insert(tableName: String, document: TDoc) =
|
||||
Document.insert(tableName, document, this)
|
||||
|
||||
/**
|
||||
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||
*
|
||||
* @param tableName The table in which the document should be saved (may include schema)
|
||||
* @param document The document to be saved
|
||||
* @throws DocumentException If the database command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TDoc> Connection.save(tableName: String, document: TDoc) =
|
||||
Document.save(tableName, document, this)
|
||||
|
||||
/**
|
||||
* Update (replace) a document by its ID
|
||||
*
|
||||
* @param tableName The table in which the document should be replaced (may include schema)
|
||||
* @param docId The ID of the document to be replaced
|
||||
* @param document The document to be replaced
|
||||
* @throws DocumentException If no dialect has been configured, or if the database command fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey, TDoc> Connection.update(tableName: String, docId: TKey, document: TDoc) =
|
||||
Document.update(tableName, docId, document, this)
|
||||
|
||||
// ~~~ DOCUMENT COUNT QUERIES ~~~
|
||||
|
||||
/**
|
||||
* Count all documents in the table
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @return A count of the documents in the table
|
||||
* @throws DocumentException If any dependent process does
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.countAll(tableName: String) =
|
||||
Count.all(tableName, this)
|
||||
|
||||
/**
|
||||
* Count documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If the dialect has not been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.countByFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
Count.byFields(tableName, fields, howMatched, this)
|
||||
|
||||
/**
|
||||
* Count documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TContains> Connection.countByContains(tableName: String, criteria: TContains) =
|
||||
Count.byContains(tableName, criteria, this)
|
||||
|
||||
/**
|
||||
* Count documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be counted
|
||||
* @param path The JSON path comparison to match
|
||||
* @return A count of the matching documents in the table
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.countByJsonPath(tableName: String, path: String) =
|
||||
Count.byJsonPath(tableName, path, this)
|
||||
|
||||
// ~~~ DOCUMENT EXISTENCE QUERIES ~~~
|
||||
|
||||
/**
|
||||
* Determine a document's existence by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param docId The ID of the document to be checked
|
||||
* @return True if the document exists, false if not
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey> Connection.existsById(tableName: String, docId: TKey) =
|
||||
Exists.byId(tableName, docId, this)
|
||||
|
||||
/**
|
||||
* Determine document existence using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.existsByFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
Exists.byFields(tableName, fields, howMatched, this)
|
||||
|
||||
/**
|
||||
* Determine document existence using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TContains> Connection.existsByContains(tableName: String, criteria: TContains) =
|
||||
Exists.byContains(tableName, criteria, this)
|
||||
|
||||
/**
|
||||
* Determine document existence using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param path The JSON path comparison to match
|
||||
* @return True if any matching documents exist, false if not
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.existsByJsonPath(tableName: String, path: String) =
|
||||
Exists.byJsonPath(tableName, path, this)
|
||||
|
||||
// ~~~ DOCUMENT RETRIEVAL QUERIES (Domain Objects) ~~~
|
||||
|
||||
/**
|
||||
* Retrieve all documents in the given table, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A list of documents from the given table
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TDoc> Connection.findAll(tableName: String, clazz: Class<TDoc>, orderBy: Collection<Field<*>>? = null) =
|
||||
Find.all(tableName, clazz, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve a document by its ID
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @param clazz The class of the document to be returned
|
||||
* @return An `Optional` item with the document if it is found
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey, TDoc> Connection.findById(tableName: String, docId: TKey, clazz: Class<TDoc>) =
|
||||
Find.byId(tableName, docId, clazz, this)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a field comparison, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A list of documents matching the field comparison
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TDoc> Connection.findByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
clazz: Class<TDoc>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Find.byFields(tableName, fields, clazz, howMatched, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON containment query, ordering results by the optional given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A list of documents matching the JSON containment query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TDoc, TContains> Connection.findByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Find.byContains(tableName, criteria, clazz, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON Path match query, ordering results by the optional given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A list of documents matching the JSON Path match query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TDoc> Connection.findByJsonPath(
|
||||
tableName: String,
|
||||
path: String,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Find.byJsonPath(tableName, path, clazz, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a field comparison and optional ordering fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return An `Optional` item, with the first document matching the field comparison if found
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TDoc> Connection.findFirstByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
clazz: Class<TDoc>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Find.firstByFields(tableName, fields, clazz, howMatched, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return An `Optional` item, with the first document matching the JSON containment query if found
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TDoc, TContains> Connection.findFirstByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Find.firstByContains(tableName, criteria, clazz, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param clazz The class of the document to be returned
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return An `Optional` item, with the first document matching the JSON Path match query if found
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TDoc> Connection.findFirstByJsonPath(
|
||||
tableName: String,
|
||||
path: String,
|
||||
clazz: Class<TDoc>,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Find.firstByJsonPath(tableName, path, clazz, orderBy, this)
|
||||
|
||||
// ~~~ DOCUMENT RETRIEVAL QUERIES (Raw JSON) ~~~
|
||||
|
||||
/**
|
||||
* Retrieve all documents in the given table, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A JSON array of documents from the given table
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.jsonAll(tableName: String, orderBy: Collection<Field<*>>? = null) =
|
||||
Json.all(tableName, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve a document by its ID
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @return A JSON document if found, an empty JSON object if not found
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey> Connection.jsonById(tableName: String, docId: TKey) =
|
||||
Json.byId(tableName, docId, this)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a field comparison, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A JSON array of documents matching the field comparison
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.jsonByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.byFields(tableName, fields, howMatched, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON containment query, ordering results by the optional given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A JSON array of documents matching the JSON containment query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TContains> Connection.jsonByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.byContains(tableName, criteria, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve documents using a JSON Path match query, ordering results by the optional given fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return A JSON array of documents matching the JSON Path match query
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.jsonByJsonPath(tableName: String, path: String, orderBy: Collection<Field<*>>? = null) =
|
||||
Json.byJsonPath(tableName, path, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a field comparison and optional ordering fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return The first JSON document matching the field comparison if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.jsonFirstByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.firstByFields(tableName, fields, howMatched, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TContains> Connection.jsonFirstByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.firstByContains(tableName, criteria, orderBy, this)
|
||||
|
||||
/**
|
||||
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.jsonFirstByJsonPath(tableName: String, path: String, orderBy: Collection<Field<*>>? = null) =
|
||||
Json.firstByJsonPath(tableName, path, orderBy, this)
|
||||
|
||||
// ~~~ DOCUMENT RETRIEVAL QUERIES (Write raw JSON to output) ~~~
|
||||
|
||||
/**
|
||||
* Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If query execution fails
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.writeJsonAll(tableName: String, writer: PrintWriter, orderBy: Collection<Field<*>>? = null) =
|
||||
Json.writeAll(tableName, writer, orderBy, this)
|
||||
|
||||
/**
|
||||
* Write a document to the given `PrintWriter` by its ID
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param docId The ID of the document to retrieve
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey> Connection.writeJsonById(tableName: String, writer: PrintWriter, docId: TKey) =
|
||||
Json.writeById(tableName, writer, docId, this)
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a field comparison, ordering results by the optional given fields
|
||||
*
|
||||
* @param tableName The table from which the document should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.writeJsonByFields(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.writeByFields(tableName, writer, fields, howMatched, orderBy, this)
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a JSON containment query, ordering results by the optional given
|
||||
* fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document existence should be checked
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TContains> Connection.writeJsonByContains(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.writeByContains(tableName, writer, criteria, orderBy, this)
|
||||
|
||||
/**
|
||||
* Write documents to the given `PrintWriter` using a JSON Path match query, ordering results by the optional given
|
||||
* fields (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.writeJsonByJsonPath(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
path: String,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.writeByJsonPath(tableName, writer, path, orderBy, this)
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a field comparison and optional ordering fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.writeJsonFirstByFields(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.writeFirstByFields(tableName, writer, fields, howMatched, orderBy, this)
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a JSON containment query and optional ordering fields
|
||||
* (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TContains> Connection.writeJsonFirstByContains(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
criteria: TContains,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.writeFirstByContains(tableName, writer, criteria, orderBy, this)
|
||||
|
||||
/**
|
||||
* Write the first document to the given `PrintWriter` using a JSON Path match query and optional ordering fields
|
||||
* (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved
|
||||
* @param writer The `PrintWriter` to which the results should be written
|
||||
* @param path The JSON path comparison to match
|
||||
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.writeJsonFirstByJsonPath(
|
||||
tableName: String,
|
||||
writer: PrintWriter,
|
||||
path: String,
|
||||
orderBy: Collection<Field<*>>? = null
|
||||
) = Json.writeFirstByJsonPath(tableName, writer, path, orderBy, this)
|
||||
|
||||
// ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~
|
||||
|
||||
/**
|
||||
* Patch a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which a document should be patched
|
||||
* @param docId The ID of the document to be patched
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey, TPatch> Connection.patchById(tableName: String, docId: TKey, patch: TPatch) =
|
||||
Patch.byId(tableName, docId, patch, this)
|
||||
|
||||
/**
|
||||
* Patch documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param fields The fields which should be compared
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @param howMatched How the fields should be matched
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun <TPatch> Connection.patchByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
patch: TPatch,
|
||||
howMatched: FieldMatch? = null
|
||||
) = Patch.byFields(tableName, fields, patch, howMatched, this)
|
||||
|
||||
/**
|
||||
* Patch documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param criteria The object against which JSON containment should be checked
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TContains, TPatch> Connection.patchByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
patch: TPatch
|
||||
) = Patch.byContains(tableName, criteria, patch, this)
|
||||
|
||||
/**
|
||||
* Patch documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which documents should be patched
|
||||
* @param path The JSON path comparison to match
|
||||
* @param patch The object whose properties should be replaced in the document
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TPatch> Connection.patchByJsonPath(tableName: String, path: String, patch: TPatch) =
|
||||
Patch.byJsonPath(tableName, path, patch, this)
|
||||
|
||||
// ~~~ DOCUMENT FIELD REMOVAL QUERIES ~~~
|
||||
|
||||
/**
|
||||
* Remove fields from a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table in which the document's fields should be removed
|
||||
* @param docId The ID of the document to have fields removed
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey> Connection.removeFieldsById(tableName: String, docId: TKey, toRemove: Collection<String>) =
|
||||
RemoveFields.byId(tableName, docId, toRemove, this)
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param fields The fields which should be compared
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @param howMatched How the fields should be matched
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.removeFieldsByFields(
|
||||
tableName: String,
|
||||
fields: Collection<Field<*>>,
|
||||
toRemove: Collection<String>,
|
||||
howMatched: FieldMatch? = null
|
||||
) = RemoveFields.byFields(tableName, fields, toRemove, howMatched, this)
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param criteria The object against which JSON containment should be checked
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TContains> Connection.removeFieldsByContains(
|
||||
tableName: String,
|
||||
criteria: TContains,
|
||||
toRemove: Collection<String>
|
||||
) = RemoveFields.byContains(tableName, criteria, toRemove, this)
|
||||
|
||||
/**
|
||||
* Remove fields from documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table in which document fields should be removed
|
||||
* @param path The JSON path comparison to match
|
||||
* @param toRemove The names of the fields to be removed
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.removeFieldsByJsonPath(tableName: String, path: String, toRemove: Collection<String>) =
|
||||
RemoveFields.byJsonPath(tableName, path, toRemove, this)
|
||||
|
||||
// ~~~ DOCUMENT DELETION QUERIES ~~~
|
||||
|
||||
/**
|
||||
* Delete a document by its ID
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param docId The ID of the document to be deleted
|
||||
* @throws DocumentException If no dialect has been configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey> Connection.deleteById(tableName: String, docId: TKey) =
|
||||
Delete.byId(tableName, docId, this)
|
||||
|
||||
/**
|
||||
* Delete documents using a field comparison
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param fields The fields which should be compared
|
||||
* @param howMatched How the fields should be matched
|
||||
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun Connection.deleteByFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
Delete.byFields(tableName, fields, howMatched, this)
|
||||
|
||||
/**
|
||||
* Delete documents using a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param criteria The object for which JSON containment should be checked
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TContains> Connection.deleteByContains(tableName: String, criteria: TContains) =
|
||||
Delete.byContains(tableName, criteria, this)
|
||||
|
||||
/**
|
||||
* Delete documents using a JSON Path match query (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table from which documents should be deleted
|
||||
* @param path The JSON path comparison to match
|
||||
* @throws DocumentException If called on a SQLite connection
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun Connection.deleteByJsonPath(tableName: String, path: String) =
|
||||
Delete.byJsonPath(tableName, path, this)
|
62
src/core/src/main/kotlin/query/CountQuery.kt
Normal file
62
src/core/src/main/kotlin/query/CountQuery.kt
Normal file
@ -0,0 +1,62 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import solutions.bitbadger.documents.Field
|
||||
import solutions.bitbadger.documents.FieldMatch
|
||||
import kotlin.jvm.Throws
|
||||
import solutions.bitbadger.documents.query.byFields as byFieldsBase
|
||||
|
||||
/**
|
||||
* Functions to count documents
|
||||
*/
|
||||
object CountQuery {
|
||||
|
||||
/**
|
||||
* Query to count all documents in a table
|
||||
*
|
||||
* @param tableName The table in which to count documents (may include schema)
|
||||
* @return A query to count documents
|
||||
*/
|
||||
@JvmStatic
|
||||
fun all(tableName: String) =
|
||||
"SELECT COUNT(*) AS it FROM $tableName"
|
||||
|
||||
/**
|
||||
* Query to count documents matching the given fields
|
||||
*
|
||||
* @param tableName The table in which to count documents (may include schema)
|
||||
* @param fields The field comparisons for the count
|
||||
* @param howMatched How fields should be compared (optional, defaults to ALL)
|
||||
* @return A query to count documents matching the given fields
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
byFieldsBase(all(tableName), fields, howMatched)
|
||||
|
||||
/**
|
||||
* Query to count documents via JSON containment (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table in which to count documents (may include schema)
|
||||
* @return A query to count documents via JSON containment
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byContains(tableName: String) =
|
||||
statementWhere(all(tableName), Where.jsonContains())
|
||||
|
||||
/**
|
||||
* Query to count documents via a JSON path match (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table in which to count documents (may include schema)
|
||||
* @return A query to count documents via a JSON path match
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String) =
|
||||
statementWhere(all(tableName), Where.jsonPathMatches())
|
||||
}
|
108
src/core/src/main/kotlin/query/DefinitionQuery.kt
Normal file
108
src/core/src/main/kotlin/query/DefinitionQuery.kt
Normal file
@ -0,0 +1,108 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to create queries to define tables and indexes
|
||||
*/
|
||||
object DefinitionQuery {
|
||||
|
||||
/**
|
||||
* SQL statement to create a document table
|
||||
*
|
||||
* @param tableName The name of the table to create (may include schema)
|
||||
* @param dataType The type of data for the column (`JSON`, `JSONB`, etc.)
|
||||
* @return A query to create a document table
|
||||
*/
|
||||
@JvmStatic
|
||||
fun ensureTableFor(tableName: String, dataType: String) =
|
||||
"CREATE TABLE IF NOT EXISTS $tableName (data $dataType NOT NULL)"
|
||||
|
||||
/**
|
||||
* SQL statement to create a document table in the current dialect
|
||||
*
|
||||
* @param tableName The name of the table to create (may include schema)
|
||||
* @param dialect The dialect to generate (optional, used in place of current)
|
||||
* @return A query to create a document table
|
||||
* @throws DocumentException If the dialect is neither provided nor configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun ensureTable(tableName: String, dialect: Dialect? = null) =
|
||||
when (dialect ?: Configuration.dialect("create table creation query")) {
|
||||
Dialect.POSTGRESQL -> ensureTableFor(tableName, "JSONB")
|
||||
Dialect.SQLITE -> ensureTableFor(tableName, "TEXT")
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a schema and table name
|
||||
*
|
||||
* @param tableName The name of the table, possibly with a schema
|
||||
* @return A pair with the first item as the schema and the second as the table name
|
||||
*/
|
||||
private fun splitSchemaAndTable(tableName: String) =
|
||||
tableName.split('.').let { if (it.size == 1) Pair("", tableName) else Pair(it[0], it[1]) }
|
||||
|
||||
/**
|
||||
* SQL statement to create an index on one or more fields in a JSON document
|
||||
*
|
||||
* @param tableName The table on which an index should be created (may include schema)
|
||||
* @param indexName The name of the index to be created
|
||||
* @param fields One or more fields to include in the index
|
||||
* @param dialect The SQL dialect to use when creating this index (optional, used in place of current)
|
||||
* @return A query to create the field index
|
||||
* @throws DocumentException If the dialect is neither provided nor configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun ensureIndexOn(
|
||||
tableName: String,
|
||||
indexName: String,
|
||||
fields: Collection<String>,
|
||||
dialect: Dialect? = null
|
||||
): String {
|
||||
val (_, tbl) = splitSchemaAndTable(tableName)
|
||||
val mode = dialect ?: Configuration.dialect("create index $tbl.$indexName")
|
||||
val jsonFields = fields.joinToString(", ") {
|
||||
val parts = it.split(' ')
|
||||
val direction = if (parts.size > 1) " ${parts[1]}" else ""
|
||||
"(" + Field.nameToPath(parts[0], mode, FieldFormat.SQL) + ")$direction"
|
||||
}
|
||||
return "CREATE INDEX IF NOT EXISTS idx_${tbl}_$indexName ON $tableName ($jsonFields)"
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL statement to create a key index for a document table
|
||||
*
|
||||
* @param tableName The table on which a key index should be created (may include schema)
|
||||
* @param dialect The SQL dialect to use when creating this index (optional, used in place of current)
|
||||
* @return A query to create the key index
|
||||
* @throws DocumentException If the dialect is neither provided nor configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun ensureKey(tableName: String, dialect: Dialect? = null) =
|
||||
ensureIndexOn(tableName, "key", listOf(Configuration.idField), dialect).replace("INDEX", "UNIQUE INDEX")
|
||||
|
||||
/**
|
||||
* Create a document-wide index on a table (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table on which the document index should be created
|
||||
* @param indexType The type of index to be created
|
||||
* @return The SQL statement to create an index on JSON documents in the specified table
|
||||
* @throws DocumentException If the database mode is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun ensureDocumentIndexOn(tableName: String, indexType: DocumentIndex): String {
|
||||
if (Configuration.dialect("create document index query") != Dialect.POSTGRESQL) {
|
||||
throw DocumentException("'Document indexes are only supported on PostgreSQL")
|
||||
}
|
||||
val (_, tbl) = splitSchemaAndTable(tableName)
|
||||
return "CREATE INDEX IF NOT EXISTS idx_${tbl}_document ON $tableName USING GIN (data${indexType.sql})"
|
||||
}
|
||||
}
|
76
src/core/src/main/kotlin/query/DeleteQuery.kt
Normal file
76
src/core/src/main/kotlin/query/DeleteQuery.kt
Normal file
@ -0,0 +1,76 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import solutions.bitbadger.documents.Field
|
||||
import solutions.bitbadger.documents.FieldMatch
|
||||
import kotlin.jvm.Throws
|
||||
import solutions.bitbadger.documents.query.byFields as byFieldsBase
|
||||
import solutions.bitbadger.documents.query.byId as byIdBase
|
||||
|
||||
/**
|
||||
* Functions to delete documents
|
||||
*/
|
||||
object DeleteQuery {
|
||||
|
||||
/**
|
||||
* Query to delete documents from a table
|
||||
*
|
||||
* @param tableName The table in which documents should be deleted (may include schema)
|
||||
* @return A query to delete documents
|
||||
*/
|
||||
private fun delete(tableName: String) =
|
||||
"DELETE FROM $tableName"
|
||||
|
||||
/**
|
||||
* Query to delete a document by its ID
|
||||
*
|
||||
* @param tableName The table from which documents should be deleted (may include schema)
|
||||
* @param docId The ID of the document (optional, used for type checking)
|
||||
* @return A query to delete a document by its ID
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TKey> byId(tableName: String, docId: TKey? = null) =
|
||||
byIdBase(delete(tableName), docId)
|
||||
|
||||
/**
|
||||
* Query to delete documents matching the given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be deleted (may include schema)
|
||||
* @param fields The field comparisons for documents to be deleted
|
||||
* @param howMatched How fields should be compared (optional, defaults to ALL)
|
||||
* @return A query to delete documents matching for the given fields
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
byFieldsBase(delete(tableName), fields, howMatched)
|
||||
|
||||
/**
|
||||
* Query to delete documents via JSON containment (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be deleted (may include schema)
|
||||
* @return A query to delete documents via JSON containment
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byContains(tableName: String) =
|
||||
statementWhere(delete(tableName), Where.jsonContains())
|
||||
|
||||
/**
|
||||
* Query to delete documents via a JSON path match (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be deleted (may include schema)
|
||||
* @return A query to delete documents via a JSON path match
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String) =
|
||||
statementWhere(delete(tableName), Where.jsonPathMatches())
|
||||
}
|
68
src/core/src/main/kotlin/query/DocumentQuery.kt
Normal file
68
src/core/src/main/kotlin/query/DocumentQuery.kt
Normal file
@ -0,0 +1,68 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.AutoId
|
||||
import solutions.bitbadger.documents.Configuration
|
||||
import solutions.bitbadger.documents.Dialect
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions for document-level operations
|
||||
*/
|
||||
object DocumentQuery {
|
||||
|
||||
/**
|
||||
* Query to insert a document
|
||||
*
|
||||
* @param tableName The table into which to insert (may include schema)
|
||||
* @return A query to insert a document
|
||||
* @throws DocumentException If the dialect is not configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun insert(tableName: String, autoId: AutoId? = null): String {
|
||||
val id = Configuration.idField
|
||||
val values = when (Configuration.dialect("create INSERT statement")) {
|
||||
Dialect.POSTGRESQL -> when (autoId ?: AutoId.DISABLED) {
|
||||
AutoId.DISABLED -> ":data"
|
||||
AutoId.NUMBER -> ":data::jsonb || ('{\"$id\":' || " +
|
||||
"(SELECT COALESCE(MAX((data->>'$id')::numeric), 0) + 1 " +
|
||||
"FROM $tableName) || '}')::jsonb"
|
||||
AutoId.UUID -> ":data::jsonb || '{\"$id\":\"${AutoId.generateUUID()}\"}'"
|
||||
AutoId.RANDOM_STRING -> ":data::jsonb || '{\"$id\":\"${AutoId.generateRandomString()}\"}'"
|
||||
}
|
||||
Dialect.SQLITE -> when (autoId ?: AutoId.DISABLED) {
|
||||
AutoId.DISABLED -> ":data"
|
||||
AutoId.NUMBER -> "json_set(:data, '$.$id', " +
|
||||
"(SELECT coalesce(max(data->>'$id'), 0) + 1 FROM $tableName))"
|
||||
AutoId.UUID -> "json_set(:data, '$.$id', '${AutoId.generateUUID()}')"
|
||||
AutoId.RANDOM_STRING -> "json_set(:data, '$.$id', '${AutoId.generateRandomString()}')"
|
||||
}
|
||||
}
|
||||
return "INSERT INTO $tableName VALUES ($values)"
|
||||
}
|
||||
|
||||
/**
|
||||
* Query to save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||
*
|
||||
* @param tableName The table into which to save (may include schema)
|
||||
* @return A query to save a document
|
||||
* @throws DocumentException If the dialect is not configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun save(tableName: String) =
|
||||
insert(tableName, AutoId.DISABLED) +
|
||||
" ON CONFLICT ((data->>'${Configuration.idField}')) DO UPDATE SET data = EXCLUDED.data"
|
||||
|
||||
/**
|
||||
* Query to update (replace) a document (this query has no `WHERE` clause)
|
||||
*
|
||||
* @param tableName The table in which documents should be replaced (may include schema)
|
||||
* @return A query to update documents
|
||||
*/
|
||||
@JvmStatic
|
||||
fun update(tableName: String) =
|
||||
"UPDATE $tableName SET data = :data"
|
||||
}
|
75
src/core/src/main/kotlin/query/ExistsQuery.kt
Normal file
75
src/core/src/main/kotlin/query/ExistsQuery.kt
Normal file
@ -0,0 +1,75 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import solutions.bitbadger.documents.Field
|
||||
import solutions.bitbadger.documents.FieldMatch
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to check for document existence
|
||||
*/
|
||||
object ExistsQuery {
|
||||
|
||||
/**
|
||||
* Query to check for document existence in a table
|
||||
*
|
||||
* @param tableName The table in which existence should be checked (may include schema)
|
||||
* @param where The `WHERE` clause with the existence criteria
|
||||
* @return A query to check document existence
|
||||
*/
|
||||
private fun exists(tableName: String, where: String) =
|
||||
"SELECT EXISTS (SELECT 1 FROM $tableName WHERE $where) AS it"
|
||||
|
||||
/**
|
||||
* Query to check for document existence by ID
|
||||
*
|
||||
* @param tableName The table in which existence should be checked (may include schema)
|
||||
* @param docId The ID of the document (optional, used for type checking)
|
||||
* @return A query to determine document existence by ID
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TKey> byId(tableName: String, docId: TKey? = null) =
|
||||
exists(tableName, Where.byId(docId = docId))
|
||||
|
||||
/**
|
||||
* Query to check for document existence matching the given fields
|
||||
*
|
||||
* @param tableName The table in which existence should be checked (may include schema)
|
||||
* @param fields The field comparisons for the existence check
|
||||
* @param howMatched How fields should be compared (optional, defaults to ALL)
|
||||
* @return A query to determine document existence for the given fields
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
exists(tableName, Where.byFields(fields, howMatched))
|
||||
|
||||
/**
|
||||
* Query to check for document existence via JSON containment (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table in which existence should be checked (may include schema)
|
||||
* @return A query to determine document existence via JSON containment
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byContains(tableName: String) =
|
||||
exists(tableName, Where.jsonContains())
|
||||
|
||||
/**
|
||||
* Query to check for document existence via a JSON path match (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table in which existence should be checked (may include schema)
|
||||
* @return A query to determine document existence via a JSON path match
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String) =
|
||||
exists(tableName, Where.jsonPathMatches())
|
||||
}
|
77
src/core/src/main/kotlin/query/FindQuery.kt
Normal file
77
src/core/src/main/kotlin/query/FindQuery.kt
Normal file
@ -0,0 +1,77 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import solutions.bitbadger.documents.Field
|
||||
import solutions.bitbadger.documents.FieldMatch
|
||||
import kotlin.jvm.Throws
|
||||
import solutions.bitbadger.documents.query.byId as byIdBase
|
||||
import solutions.bitbadger.documents.query.byFields as byFieldsBase
|
||||
|
||||
/**
|
||||
* Functions to retrieve documents
|
||||
*/
|
||||
object FindQuery {
|
||||
|
||||
/**
|
||||
* Query to retrieve all documents from a table
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved (may include schema)
|
||||
* @return A query to retrieve documents
|
||||
*/
|
||||
@JvmStatic
|
||||
fun all(tableName: String) =
|
||||
"SELECT data FROM $tableName"
|
||||
|
||||
/**
|
||||
* Query to retrieve a document by its ID
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved (may include schema)
|
||||
* @param docId The ID of the document (optional, used for type checking)
|
||||
* @return A query to retrieve a document by its ID
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TKey> byId(tableName: String, docId: TKey? = null) =
|
||||
byIdBase(all(tableName), docId)
|
||||
|
||||
/**
|
||||
* Query to retrieve documents matching the given fields
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved (may include schema)
|
||||
* @param fields The field comparisons for matching documents to retrieve
|
||||
* @param howMatched How fields should be compared (optional, defaults to ALL)
|
||||
* @return A query to retrieve documents matching the given fields
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
byFieldsBase(all(tableName), fields, howMatched)
|
||||
|
||||
/**
|
||||
* Query to retrieve documents via JSON containment (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved (may include schema)
|
||||
* @return A query to retrieve documents via JSON containment
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byContains(tableName: String) =
|
||||
statementWhere(all(tableName), Where.jsonContains())
|
||||
|
||||
/**
|
||||
* Query to retrieve documents via a JSON path match (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The table from which documents should be retrieved (may include schema)
|
||||
* @return A query to retrieve documents via a JSON path match
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String) =
|
||||
statementWhere(all(tableName), Where.jsonPathMatches())
|
||||
}
|
77
src/core/src/main/kotlin/query/PatchQuery.kt
Normal file
77
src/core/src/main/kotlin/query/PatchQuery.kt
Normal file
@ -0,0 +1,77 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import kotlin.jvm.Throws
|
||||
import solutions.bitbadger.documents.query.byFields as byFieldsBase
|
||||
import solutions.bitbadger.documents.query.byId as byIdBase
|
||||
|
||||
/**
|
||||
* Functions to create queries to patch (partially update) JSON documents
|
||||
*/
|
||||
object PatchQuery {
|
||||
|
||||
/**
|
||||
* Create an `UPDATE` statement to patch documents
|
||||
*
|
||||
* @param tableName The table to be updated
|
||||
* @return A query to patch documents
|
||||
*/
|
||||
private fun patch(tableName: String) =
|
||||
when (Configuration.dialect("create patch query")) {
|
||||
Dialect.POSTGRESQL -> "data || :data"
|
||||
Dialect.SQLITE -> "json_patch(data, json(:data))"
|
||||
}.let { "UPDATE $tableName SET data = $it" }
|
||||
|
||||
/**
|
||||
* A query to patch (partially update) a JSON document by its ID
|
||||
*
|
||||
* @param tableName The name of the table where the document is stored
|
||||
* @param docId The ID of the document to be updated (optional, used for type checking)
|
||||
* @return A query to patch a JSON document by its ID
|
||||
* @throws DocumentException If the dialect is not configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TKey> byId(tableName: String, docId: TKey? = null) =
|
||||
byIdBase(patch(tableName), docId)
|
||||
|
||||
/**
|
||||
* A query to patch (partially update) a JSON document using field match criteria
|
||||
*
|
||||
* @param tableName The name of the table where the documents are stored
|
||||
* @param fields The field criteria
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `ALL`)
|
||||
* @return A query to patch JSON documents by field match criteria
|
||||
* @throws DocumentException If the dialect is not configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
byFieldsBase(patch(tableName), fields, howMatched)
|
||||
|
||||
/**
|
||||
* A query to patch (partially update) a JSON document by JSON containment (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table where the document is stored
|
||||
* @return A query to patch JSON documents by JSON containment
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byContains(tableName: String) =
|
||||
statementWhere(patch(tableName), Where.jsonContains())
|
||||
|
||||
/**
|
||||
* A query to patch (partially update) a JSON document by JSON path match (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table where the document is stored
|
||||
* @return A query to patch JSON documents by JSON path match
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String) =
|
||||
statementWhere(patch(tableName), Where.jsonPathMatches())
|
||||
}
|
82
src/core/src/main/kotlin/query/Query.kt
Normal file
82
src/core/src/main/kotlin/query/Query.kt
Normal file
@ -0,0 +1,82 @@
|
||||
@file:JvmName("QueryUtils")
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
// ~~~ TOP-LEVEL FUNCTIONS FOR THE QUERY PACKAGE ~~~
|
||||
|
||||
/**
|
||||
* Combine a query (`SELECT`, `UPDATE`, etc.) and a `WHERE` clause
|
||||
*
|
||||
* @param statement The first part of the statement
|
||||
* @param where The `WHERE` clause for the statement
|
||||
* @return The two parts of the query combined with `WHERE`
|
||||
*/
|
||||
fun statementWhere(statement: String, where: String) =
|
||||
"$statement WHERE $where"
|
||||
|
||||
/**
|
||||
* Create a query by a document's ID
|
||||
*
|
||||
* @param statement The SQL statement to be run against a document by its ID
|
||||
* @param docId The ID of the document targeted
|
||||
* @returns A query addressing a document by its ID
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
fun <TKey> byId(statement: String, docId: TKey) =
|
||||
statementWhere(statement, Where.byId(docId = docId))
|
||||
|
||||
/**
|
||||
* Create a query on JSON fields
|
||||
*
|
||||
* @param statement The SQL statement to be run against matching fields
|
||||
* @param fields The field conditions to be matched
|
||||
* @param howMatched Whether to match any or all of the field conditions (optional; default ALL)
|
||||
* @return A query addressing documents by field matching conditions
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun byFields(statement: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
statementWhere(statement, Where.byFields(fields, howMatched))
|
||||
|
||||
/**
|
||||
* Create an `ORDER BY` clause for the given fields
|
||||
*
|
||||
* @param fields One or more fields by which to order
|
||||
* @param dialect The SQL dialect for the generated clause
|
||||
* @return An `ORDER BY` clause for the given fields
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmOverloads
|
||||
fun orderBy(fields: Collection<Field<*>>, dialect: Dialect? = null): String {
|
||||
val mode = dialect ?: Configuration.dialect("generate ORDER BY clause")
|
||||
if (fields.isEmpty()) return ""
|
||||
val orderFields = fields.joinToString(", ") {
|
||||
val (field, direction) =
|
||||
if (it.name.indexOf(' ') > -1) {
|
||||
val parts = it.name.split(' ')
|
||||
Pair(Field.named(parts[0]), " " + parts.drop(1).joinToString(" "))
|
||||
} else {
|
||||
Pair<Field<*>, String?>(it, null)
|
||||
}
|
||||
val path = when {
|
||||
field.name.startsWith("n:") -> Field.named(field.name.substring(2)).let { fld ->
|
||||
when (mode) {
|
||||
Dialect.POSTGRESQL -> "(${fld.path(mode)})::numeric"
|
||||
Dialect.SQLITE -> fld.path(mode)
|
||||
}
|
||||
}
|
||||
field.name.startsWith("i:") -> Field.named(field.name.substring(2)).path(mode).let { p ->
|
||||
when (mode) {
|
||||
Dialect.POSTGRESQL -> "LOWER($p)"
|
||||
Dialect.SQLITE -> "$p COLLATE NOCASE"
|
||||
}
|
||||
}
|
||||
else -> field.path(mode)
|
||||
}
|
||||
"$path${direction ?: ""}"
|
||||
}
|
||||
return " ORDER BY $orderFields"
|
||||
}
|
89
src/core/src/main/kotlin/query/RemoveFieldsQuery.kt
Normal file
89
src/core/src/main/kotlin/query/RemoveFieldsQuery.kt
Normal file
@ -0,0 +1,89 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.*
|
||||
import kotlin.jvm.Throws
|
||||
import solutions.bitbadger.documents.query.byFields as byFieldsBase
|
||||
import solutions.bitbadger.documents.query.byId as byIdBase
|
||||
|
||||
/**
|
||||
* Functions to create queries to remove fields from documents
|
||||
*/
|
||||
object RemoveFieldsQuery {
|
||||
|
||||
/**
|
||||
* Create a query to remove fields based on the given parameters
|
||||
*
|
||||
* @param tableName The name of the table in which documents should have fields removed
|
||||
* @param toRemove The parameters for the fields to be removed
|
||||
* @return A query to remove fields from documents in the given table
|
||||
*/
|
||||
private fun removeFields(tableName: String, toRemove: Collection<Parameter<*>>) =
|
||||
when (Configuration.dialect("generate field removal query")) {
|
||||
Dialect.POSTGRESQL -> "UPDATE $tableName SET data = data - ${toRemove.elementAt(0).name}::text[]"
|
||||
Dialect.SQLITE -> toRemove.joinToString(", ") { it.name }.let {
|
||||
"UPDATE $tableName SET data = json_remove(data, $it)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A query to patch (partially update) a JSON document by its ID
|
||||
*
|
||||
* @param tableName The name of the table where the document is stored
|
||||
* @param toRemove The parameters for the fields to be removed
|
||||
* @param docId The ID of the document to be updated (optional, used for type checking)
|
||||
* @return A query to patch a JSON document by its ID
|
||||
* @throws DocumentException If the dialect is not configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TKey> byId(tableName: String, toRemove: Collection<Parameter<*>>, docId: TKey? = null) =
|
||||
byIdBase(removeFields(tableName, toRemove), docId)
|
||||
|
||||
/**
|
||||
* A query to patch (partially update) a JSON document using field match criteria
|
||||
*
|
||||
* @param tableName The name of the table where the documents are stored
|
||||
* @param toRemove The parameters for the fields to be removed
|
||||
* @param fields The field criteria
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `ALL`)
|
||||
* @return A query to patch JSON documents by field match criteria
|
||||
* @throws DocumentException If the dialect is not configured
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(
|
||||
tableName: String,
|
||||
toRemove: Collection<Parameter<*>>,
|
||||
fields: Collection<Field<*>>,
|
||||
howMatched: FieldMatch? = null
|
||||
) =
|
||||
byFieldsBase(removeFields(tableName, toRemove), fields, howMatched)
|
||||
|
||||
/**
|
||||
* A query to patch (partially update) a JSON document by JSON containment (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table where the document is stored
|
||||
* @param toRemove The parameters for the fields to be removed
|
||||
* @return A query to patch JSON documents by JSON containment
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byContains(tableName: String, toRemove: Collection<Parameter<*>>) =
|
||||
statementWhere(removeFields(tableName, toRemove), Where.jsonContains())
|
||||
|
||||
/**
|
||||
* A query to patch (partially update) a JSON document by JSON path match (PostgreSQL only)
|
||||
*
|
||||
* @param tableName The name of the table where the document is stored
|
||||
* @param toRemove The parameters for the fields to be removed
|
||||
* @return A query to patch JSON documents by JSON path match
|
||||
* @throws DocumentException If the database dialect is not PostgreSQL
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
fun byJsonPath(tableName: String, toRemove: Collection<Parameter<*>>) =
|
||||
statementWhere(removeFields(tableName, toRemove), Where.jsonPathMatches())
|
||||
}
|
74
src/core/src/main/kotlin/query/Where.kt
Normal file
74
src/core/src/main/kotlin/query/Where.kt
Normal file
@ -0,0 +1,74 @@
|
||||
package solutions.bitbadger.documents.query
|
||||
|
||||
import solutions.bitbadger.documents.Configuration
|
||||
import solutions.bitbadger.documents.Dialect
|
||||
import solutions.bitbadger.documents.DocumentException
|
||||
import solutions.bitbadger.documents.Field
|
||||
import solutions.bitbadger.documents.FieldMatch
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Functions to create `WHERE` clause fragments
|
||||
*/
|
||||
object Where {
|
||||
|
||||
/**
|
||||
* Create a `WHERE` clause fragment to query by one or more fields
|
||||
*
|
||||
* @param fields The fields to be queried
|
||||
* @param howMatched How the fields should be matched (optional, defaults to `ALL`)
|
||||
* @return A `WHERE` clause fragment to match the given fields
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun byFields(fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
|
||||
fields.joinToString(" ${(howMatched ?: FieldMatch.ALL).sql} ") { it.toWhere() }
|
||||
|
||||
/**
|
||||
* Create a `WHERE` clause fragment to retrieve a document by its ID
|
||||
*
|
||||
* @param parameterName The parameter name to use for the ID placeholder (optional, defaults to ":id")
|
||||
* @param docId The ID value (optional; used for type determinations, string assumed if not provided)
|
||||
* @return A `WHERE` clause fragment to match the document's ID
|
||||
* @throws DocumentException If the dialect has not been set
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <TKey> byId(parameterName: String = ":id", docId: TKey? = null) =
|
||||
byFields(listOf(Field.equal(Configuration.idField, docId ?: "", parameterName)))
|
||||
|
||||
/**
|
||||
* Create a `WHERE` clause fragment to implement a JSON containment query (PostgreSQL only)
|
||||
*
|
||||
* @param parameterName The parameter name to use for the JSON placeholder (optional, defaults to ":criteria")
|
||||
* @return A `WHERE` clause fragment to implement a JSON containment criterion
|
||||
* @throws DocumentException If called against a SQLite database
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun jsonContains(parameterName: String = ":criteria") =
|
||||
when (Configuration.dialect("create containment WHERE clause")) {
|
||||
Dialect.POSTGRESQL -> "data @> $parameterName"
|
||||
Dialect.SQLITE -> throw DocumentException("JSON containment is not supported")
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `WHERE` clause fragment to implement a JSON path match query (PostgreSQL only)
|
||||
*
|
||||
* @param parameterName The parameter name to use for the placeholder (optional, defaults to ":path")
|
||||
* @return A `WHERE` clause fragment to implement a JSON path match criterion
|
||||
* @throws DocumentException If called against a SQLite database
|
||||
*/
|
||||
@Throws(DocumentException::class)
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun jsonPathMatches(parameterName: String = ":path") =
|
||||
when (Configuration.dialect("create JSON path match WHERE clause")) {
|
||||
Dialect.POSTGRESQL -> "jsonb_path_exists(data, $parameterName::jsonpath)"
|
||||
Dialect.SQLITE -> throw DocumentException("JSON path match is not supported")
|
||||
}
|
||||
}
|
19
src/core/src/main/module-info.md
Normal file
19
src/core/src/main/module-info.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Module core
|
||||
|
||||
This module contains configuration and support files for the document store API, as well as an implementation suitable for any JVM language.
|
||||
|
||||
# Package solutions.bitbadger.documents
|
||||
|
||||
Configuration and other items to support the document store API
|
||||
|
||||
# Package solutions.bitbadger.documents.query
|
||||
|
||||
Functions to create document manipulation queries
|
||||
|
||||
# Package solutions.bitbadger.documents.java
|
||||
|
||||
A Java-focused implementation of the document store API
|
||||
|
||||
# Package solutions.bitbadger.documents.java.extensions
|
||||
|
||||
Extensions on the Java `Connection` object for document manipulation
|
20
src/core/src/test/java/module-info.java
Normal file
20
src/core/src/test/java/module-info.java
Normal file
@ -0,0 +1,20 @@
|
||||
module solutions.bitbadger.documents.core.tests {
|
||||
requires solutions.bitbadger.documents.core;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires java.sql;
|
||||
requires kotlin.stdlib;
|
||||
requires kotlin.test.junit5;
|
||||
requires org.junit.jupiter.api;
|
||||
requires org.slf4j;
|
||||
requires annotations;
|
||||
//requires org.checkerframework.checker.qual;
|
||||
|
||||
exports solutions.bitbadger.documents.core.tests;
|
||||
exports solutions.bitbadger.documents.core.tests.integration;
|
||||
exports solutions.bitbadger.documents.core.tests.java;
|
||||
exports solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
opens solutions.bitbadger.documents.core.tests;
|
||||
opens solutions.bitbadger.documents.core.tests.java;
|
||||
opens solutions.bitbadger.documents.core.tests.java.integration;
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.AutoId;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for the `AutoId` enum
|
||||
*/
|
||||
@DisplayName("Core | Java | AutoId")
|
||||
final public class AutoIdTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Generates a UUID string")
|
||||
public void generateUUID() {
|
||||
assertEquals(32, AutoId.generateUUID().length(), "The UUID should have been a 32-character string");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Generates a random hex character string of an even length")
|
||||
public void generateRandomStringEven() {
|
||||
final String result = AutoId.generateRandomString(8);
|
||||
assertEquals(8, result.length(), "There should have been 8 characters in " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Generates a random hex character string of an odd length")
|
||||
public void generateRandomStringOdd() {
|
||||
final String result = AutoId.generateRandomString(11);
|
||||
assertEquals(11, result.length(), "There should have been 11 characters in " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Generates different random hex character strings")
|
||||
public void generateRandomStringIsRandom() {
|
||||
final String result1 = AutoId.generateRandomString(16);
|
||||
final String result2 = AutoId.generateRandomString(16);
|
||||
assertNotEquals(result1, result2, "There should have been 2 different strings generated");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId fails for null document")
|
||||
public void needsAutoIdFailsForNullDocument() {
|
||||
assertThrows(DocumentException.class, () -> AutoId.needsAutoId(AutoId.DISABLED, null, "id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId fails for missing ID property")
|
||||
public void needsAutoIdFailsForMissingId() {
|
||||
assertThrows(DocumentException.class, () -> AutoId.needsAutoId(AutoId.UUID, new IntIdClass(0), "Id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns false if disabled")
|
||||
public void needsAutoIdFalseIfDisabled() {
|
||||
try {
|
||||
assertFalse(AutoId.needsAutoId(AutoId.DISABLED, "", ""), "Disabled Auto ID should always return false");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns true for Number strategy and byte ID of 0")
|
||||
public void needsAutoIdTrueForByteWithZero() {
|
||||
try {
|
||||
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, new ByteIdClass((byte) 0), "id"),
|
||||
"Number Auto ID with 0 should return true");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns false for Number strategy and byte ID of non-0")
|
||||
public void needsAutoIdFalseForByteWithNonZero() {
|
||||
try {
|
||||
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, new ByteIdClass((byte) 77), "id"),
|
||||
"Number Auto ID with 77 should return false");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns true for Number strategy and short ID of 0")
|
||||
public void needsAutoIdTrueForShortWithZero() {
|
||||
try {
|
||||
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, new ShortIdClass((short) 0), "id"),
|
||||
"Number Auto ID with 0 should return true");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns false for Number strategy and short ID of non-0")
|
||||
public void needsAutoIdFalseForShortWithNonZero() {
|
||||
try {
|
||||
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, new ShortIdClass((short) 31), "id"),
|
||||
"Number Auto ID with 31 should return false");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns true for Number strategy and int ID of 0")
|
||||
public void needsAutoIdTrueForIntWithZero() {
|
||||
try {
|
||||
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, new IntIdClass(0), "id"),
|
||||
"Number Auto ID with 0 should return true");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns false for Number strategy and int ID of non-0")
|
||||
public void needsAutoIdFalseForIntWithNonZero() {
|
||||
try {
|
||||
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, new IntIdClass(6), "id"),
|
||||
"Number Auto ID with 6 should return false");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns true for Number strategy and long ID of 0")
|
||||
public void needsAutoIdTrueForLongWithZero() {
|
||||
try {
|
||||
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, new LongIdClass(0L), "id"),
|
||||
"Number Auto ID with 0 should return true");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns false for Number strategy and long ID of non-0")
|
||||
public void needsAutoIdFalseForLongWithNonZero() {
|
||||
try {
|
||||
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, new LongIdClass(2L), "id"),
|
||||
"Number Auto ID with 2 should return false");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId fails for Number strategy and non-number ID")
|
||||
public void needsAutoIdFailsForNumberWithStringId() {
|
||||
assertThrows(DocumentException.class, () -> AutoId.needsAutoId(AutoId.NUMBER, new StringIdClass(""), "id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns true for UUID strategy and blank ID")
|
||||
public void needsAutoIdTrueForUUIDWithBlank() {
|
||||
try {
|
||||
assertTrue(AutoId.needsAutoId(AutoId.UUID, new StringIdClass(""), "id"),
|
||||
"UUID Auto ID with blank should return true");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns false for UUID strategy and non-blank ID")
|
||||
public void needsAutoIdFalseForUUIDNotBlank() {
|
||||
try {
|
||||
assertFalse(AutoId.needsAutoId(AutoId.UUID, new StringIdClass("howdy"), "id"),
|
||||
"UUID Auto ID with non-blank should return false");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId fails for UUID strategy and non-string ID")
|
||||
public void needsAutoIdFailsForUUIDNonString() {
|
||||
assertThrows(DocumentException.class, () -> AutoId.needsAutoId(AutoId.UUID, new IntIdClass(5), "id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns true for Random String strategy and blank ID")
|
||||
public void needsAutoIdTrueForRandomWithBlank() {
|
||||
try {
|
||||
assertTrue(AutoId.needsAutoId(AutoId.RANDOM_STRING, new StringIdClass(""), "id"),
|
||||
"Random String Auto ID with blank should return true");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId returns false for Random String strategy and non-blank ID")
|
||||
public void needsAutoIdFalseForRandomNotBlank() {
|
||||
try {
|
||||
assertFalse(AutoId.needsAutoId(AutoId.RANDOM_STRING, new StringIdClass("full"), "id"),
|
||||
"Random String Auto ID with non-blank should return false");
|
||||
} catch (DocumentException ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("needsAutoId fails for Random String strategy and non-string ID")
|
||||
public void needsAutoIdFailsForRandomNonString() {
|
||||
assertThrows(DocumentException.class,
|
||||
() -> AutoId.needsAutoId(AutoId.RANDOM_STRING, new ShortIdClass((short) 55), "id"));
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
public class ByteIdClass {
|
||||
|
||||
private byte id;
|
||||
|
||||
public byte getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ByteIdClass(byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.AutoId;
|
||||
import solutions.bitbadger.documents.Configuration;
|
||||
import solutions.bitbadger.documents.Dialect;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Configuration` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Configuration")
|
||||
final public class ConfigurationTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Default ID field is `id`")
|
||||
public void defaultIdField() {
|
||||
assertEquals("id", Configuration.idField, "Default ID field incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Default Auto ID strategy is `DISABLED`")
|
||||
public void defaultAutoId() {
|
||||
assertEquals(AutoId.DISABLED, Configuration.autoIdStrategy, "Default Auto ID strategy should be `disabled`");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Default ID string length should be 16")
|
||||
public void defaultIdStringLength() {
|
||||
assertEquals(16, Configuration.idStringLength, "Default ID string length should be 16");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Dialect is derived from connection string")
|
||||
public void dialectIsDerived() throws DocumentException {
|
||||
try {
|
||||
assertThrows(DocumentException.class, Configuration::dialect);
|
||||
Configuration.setConnectionString("jdbc:postgresql:db");
|
||||
assertEquals(Dialect.POSTGRESQL, Configuration.dialect());
|
||||
} finally {
|
||||
Configuration.setConnectionString(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.Field;
|
||||
import solutions.bitbadger.documents.query.CountQuery;
|
||||
import solutions.bitbadger.documents.core.tests.ForceDialect;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Count` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | CountQuery")
|
||||
final public class CountQueryTest {
|
||||
|
||||
/**
|
||||
* Clear the connection string (resets Dialect)
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("all generates correctly")
|
||||
public void all() {
|
||||
assertEquals(String.format("SELECT COUNT(*) AS it FROM %s", TEST_TABLE), CountQuery.all(TEST_TABLE),
|
||||
"Count query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | PostgreSQL")
|
||||
public void byFieldsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("SELECT COUNT(*) AS it FROM %s WHERE data->>'test' = :field0", TEST_TABLE),
|
||||
CountQuery.byFields(TEST_TABLE, List.of(Field.equal("test", "", ":field0"))),
|
||||
"Count query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | SQLite")
|
||||
public void byFieldsSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("SELECT COUNT(*) AS it FROM %s WHERE data->>'test' = :field0", TEST_TABLE),
|
||||
CountQuery.byFields(TEST_TABLE, List.of(Field.equal("test", "", ":field0"))),
|
||||
"Count query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains generates correctly | PostgreSQL")
|
||||
public void byContainsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("SELECT COUNT(*) AS it FROM %s WHERE data @> :criteria", TEST_TABLE),
|
||||
CountQuery.byContains(TEST_TABLE), "Count query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains fails | SQLite")
|
||||
public void byContainsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> CountQuery.byContains(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath generates correctly | PostgreSQL")
|
||||
public void byJsonPathPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(
|
||||
String.format("SELECT COUNT(*) AS it FROM %s WHERE jsonb_path_exists(data, :path::jsonpath)",
|
||||
TEST_TABLE),
|
||||
CountQuery.byJsonPath(TEST_TABLE), "Count query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath fails | SQLite")
|
||||
public void byJsonPathSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> CountQuery.byJsonPath(TEST_TABLE));
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.Dialect;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.DocumentIndex;
|
||||
import solutions.bitbadger.documents.query.DefinitionQuery;
|
||||
import solutions.bitbadger.documents.core.tests.ForceDialect;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Definition` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | DefinitionQuery")
|
||||
final public class DefinitionQueryTest {
|
||||
|
||||
/**
|
||||
* Clear the connection string (resets Dialect)
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureTableFor generates correctly")
|
||||
public void ensureTableFor() {
|
||||
assertEquals("CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
|
||||
DefinitionQuery.ensureTableFor("my.table", "JSONB"),
|
||||
"CREATE TABLE statement not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureTable generates correctly | PostgreSQL")
|
||||
public void ensureTablePostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("CREATE TABLE IF NOT EXISTS %s (data JSONB NOT NULL)", TEST_TABLE),
|
||||
DefinitionQuery.ensureTable(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureTable generates correctly | SQLite")
|
||||
public void ensureTableSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("CREATE TABLE IF NOT EXISTS %s (data TEXT NOT NULL)", TEST_TABLE),
|
||||
DefinitionQuery.ensureTable(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureTable fails when no dialect is set")
|
||||
public void ensureTableFailsUnknown() {
|
||||
assertThrows(DocumentException.class, () -> DefinitionQuery.ensureTable(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureKey generates correctly with schema")
|
||||
public void ensureKeyWithSchema() throws DocumentException {
|
||||
assertEquals("CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'id'))",
|
||||
DefinitionQuery.ensureKey("test.table", Dialect.POSTGRESQL),
|
||||
"CREATE INDEX for key statement with schema not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureKey generates correctly without schema")
|
||||
public void ensureKeyWithoutSchema() throws DocumentException {
|
||||
assertEquals(
|
||||
String.format("CREATE UNIQUE INDEX IF NOT EXISTS idx_%1$s_key ON %1$s ((data->>'id'))", TEST_TABLE),
|
||||
DefinitionQuery.ensureKey(TEST_TABLE, Dialect.SQLITE),
|
||||
"CREATE INDEX for key statement without schema not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureIndexOn generates multiple fields and directions")
|
||||
public void ensureIndexOnMultipleFields() throws DocumentException {
|
||||
assertEquals(
|
||||
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table ((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
|
||||
DefinitionQuery.ensureIndexOn("test.table", "gibberish", List.of("taco", "guac DESC", "salsa ASC"),
|
||||
Dialect.POSTGRESQL),
|
||||
"CREATE INDEX for multiple field statement not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureIndexOn generates nested field | PostgreSQL")
|
||||
public void ensureIndexOnNestedPostgres() throws DocumentException {
|
||||
assertEquals(String.format("CREATE INDEX IF NOT EXISTS idx_%1$s_nest ON %1$s ((data#>>'{a,b,c}'))", TEST_TABLE),
|
||||
DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List.of("a.b.c"), Dialect.POSTGRESQL),
|
||||
"CREATE INDEX for nested PostgreSQL field incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureIndexOn generates nested field | SQLite")
|
||||
public void ensureIndexOnNestedSQLite() throws DocumentException {
|
||||
assertEquals(
|
||||
String.format("CREATE INDEX IF NOT EXISTS idx_%1$s_nest ON %1$s ((data->'a'->'b'->>'c'))", TEST_TABLE),
|
||||
DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List.of("a.b.c"), Dialect.SQLITE),
|
||||
"CREATE INDEX for nested SQLite field incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureDocumentIndexOn generates Full | PostgreSQL")
|
||||
public void ensureDocumentIndexOnFullPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("CREATE INDEX IF NOT EXISTS idx_%1$s_document ON %1$s USING GIN (data)", TEST_TABLE),
|
||||
DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL),
|
||||
"CREATE INDEX for full document index incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureDocumentIndexOn generates Optimized | PostgreSQL")
|
||||
public void ensureDocumentIndexOnOptimizedPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(
|
||||
String.format("CREATE INDEX IF NOT EXISTS idx_%1$s_document ON %1$s USING GIN (data jsonb_path_ops)",
|
||||
TEST_TABLE),
|
||||
DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.OPTIMIZED),
|
||||
"CREATE INDEX for optimized document index incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ensureDocumentIndexOn fails | SQLite")
|
||||
public void ensureDocumentIndexOnFailsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class,
|
||||
() -> DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL));
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.Field;
|
||||
import solutions.bitbadger.documents.query.DeleteQuery;
|
||||
import solutions.bitbadger.documents.core.tests.ForceDialect;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Delete` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | DeleteQuery")
|
||||
final public class DeleteQueryTest {
|
||||
|
||||
/**
|
||||
* Clear the connection string (resets Dialect)
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | PostgreSQL")
|
||||
public void byIdPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("DELETE FROM %s WHERE data->>'id' = :id", TEST_TABLE), DeleteQuery.byId(TEST_TABLE),
|
||||
"Delete query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | SQLite")
|
||||
public void byIdSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("DELETE FROM %s WHERE data->>'id' = :id", TEST_TABLE), DeleteQuery.byId(TEST_TABLE),
|
||||
"Delete query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | PostgreSQL")
|
||||
public void byFieldsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("DELETE FROM %s WHERE data->>'a' = :b", TEST_TABLE),
|
||||
DeleteQuery.byFields(TEST_TABLE, List.of(Field.equal("a", "", ":b"))),
|
||||
"Delete query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | SQLite")
|
||||
public void byFieldsSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("DELETE FROM %s WHERE data->>'a' = :b", TEST_TABLE),
|
||||
DeleteQuery.byFields(TEST_TABLE, List.of(Field.equal("a", "", ":b"))),
|
||||
"Delete query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains generates correctly | PostgreSQL")
|
||||
public void byContainsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("DELETE FROM %s WHERE data @> :criteria", TEST_TABLE),
|
||||
DeleteQuery.byContains(TEST_TABLE), "Delete query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains fails | SQLite")
|
||||
public void byContainsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> DeleteQuery.byContains(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath generates correctly | PostgreSQL")
|
||||
public void byJsonPathPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("DELETE FROM %s WHERE jsonb_path_exists(data, :path::jsonpath)", TEST_TABLE),
|
||||
DeleteQuery.byJsonPath(TEST_TABLE), "Delete query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath fails | SQLite")
|
||||
public void byJsonPathSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> DeleteQuery.byJsonPath(TEST_TABLE));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.Dialect;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Dialect` enum
|
||||
*/
|
||||
@DisplayName("Core | Java | Dialect")
|
||||
final public class DialectTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("deriveFromConnectionString derives PostgreSQL correctly")
|
||||
public void derivesPostgres() throws DocumentException {
|
||||
assertEquals(Dialect.POSTGRESQL, Dialect.deriveFromConnectionString("jdbc:postgresql:db"),
|
||||
"Dialect should have been PostgreSQL");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("deriveFromConnectionString derives SQLite correctly")
|
||||
public void derivesSQLite() throws DocumentException {
|
||||
assertEquals(
|
||||
Dialect.SQLITE, Dialect.deriveFromConnectionString("jdbc:sqlite:memory"),
|
||||
"Dialect should have been SQLite");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("deriveFromConnectionString fails when the connection string is unknown")
|
||||
public void deriveFailsWhenUnknown() {
|
||||
try {
|
||||
Dialect.deriveFromConnectionString("SQL Server");
|
||||
fail("Dialect derivation should have failed");
|
||||
} catch (DocumentException ex) {
|
||||
assertNotNull(ex.getMessage(), "The exception message should not have been null");
|
||||
assertTrue(ex.getMessage().contains("[SQL Server]"),
|
||||
"The connection string should have been in the exception message");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.DocumentIndex;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for the `DocumentIndex` enum
|
||||
*/
|
||||
@DisplayName("Core | Java | DocumentIndex")
|
||||
final public class DocumentIndexTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("FULL uses proper SQL")
|
||||
public void fullSQL() {
|
||||
assertEquals("", DocumentIndex.FULL.getSql(), "The SQL for Full is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("OPTIMIZED uses proper SQL")
|
||||
public void optimizedSQL() {
|
||||
assertEquals(" jsonb_path_ops", DocumentIndex.OPTIMIZED.getSql(), "The SQL for Optimized is incorrect");
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.AutoId;
|
||||
import solutions.bitbadger.documents.Configuration;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.query.DocumentQuery;
|
||||
import solutions.bitbadger.documents.core.tests.ForceDialect;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Document` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | DocumentQuery")
|
||||
final public class DocumentQueryTest {
|
||||
|
||||
/**
|
||||
* Clear the connection string (resets Dialect)
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates no auto ID | PostgreSQL")
|
||||
public void insertNoAutoPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("INSERT INTO %s VALUES (:data)", TEST_TABLE), DocumentQuery.insert(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates no auto ID | SQLite")
|
||||
public void insertNoAutoSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("INSERT INTO %s VALUES (:data)", TEST_TABLE), DocumentQuery.insert(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates auto number | PostgreSQL")
|
||||
public void insertAutoNumberPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("INSERT INTO %1$s VALUES (:data::jsonb || ('{\"id\":' "
|
||||
+ "|| (SELECT COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM %1$s) || '}')::jsonb)",
|
||||
TEST_TABLE),
|
||||
DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates auto number | SQLite")
|
||||
public void insertAutoNumberSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("INSERT INTO %1$s VALUES (json_set(:data, '$.id', "
|
||||
+ "(SELECT coalesce(max(data->>'id'), 0) + 1 FROM %1$s)))", TEST_TABLE),
|
||||
DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates auto UUID | PostgreSQL")
|
||||
public void insertAutoUUIDPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
final String query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID);
|
||||
assertTrue(query.startsWith(String.format("INSERT INTO %s VALUES (:data::jsonb || '{\"id\":\"", TEST_TABLE)),
|
||||
String.format("Query start not correct (actual: %s)", query));
|
||||
assertTrue(query.endsWith("\"}')"), "Query end not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates auto UUID | SQLite")
|
||||
public void insertAutoUUIDSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
final String query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID);
|
||||
assertTrue(query.startsWith(String.format("INSERT INTO %s VALUES (json_set(:data, '$.id', '", TEST_TABLE)),
|
||||
String.format("Query start not correct (actual: %s)", query));
|
||||
assertTrue(query.endsWith("'))"), "Query end not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates auto random string | PostgreSQL")
|
||||
public void insertAutoRandomPostgres() throws DocumentException {
|
||||
try {
|
||||
ForceDialect.postgres();
|
||||
Configuration.idStringLength = 8;
|
||||
final String query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING);
|
||||
final String start = String.format("INSERT INTO %s VALUES (:data::jsonb || '{\"id\":\"", TEST_TABLE);
|
||||
final String end = "\"}')";
|
||||
assertTrue(query.startsWith(start), String.format("Query start not correct (actual: %s)", query));
|
||||
assertTrue(query.endsWith(end), "Query end not correct");
|
||||
assertEquals(8, query.replace(start, "").replace(end, "").length(), "Random string length incorrect");
|
||||
} finally {
|
||||
Configuration.idStringLength = 16;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates auto random string | SQLite")
|
||||
public void insertAutoRandomSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
final String query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING);
|
||||
final String start = String.format("INSERT INTO %s VALUES (json_set(:data, '$.id', '", TEST_TABLE);
|
||||
final String end = "'))";
|
||||
assertTrue(query.startsWith(start), String.format("Query start not correct (actual: %s)", query));
|
||||
assertTrue(query.endsWith(end), "Query end not correct");
|
||||
assertEquals(Configuration.idStringLength, query.replace(start, "").replace(end, "").length(),
|
||||
"Random string length incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert fails when no dialect is set")
|
||||
public void insertFailsUnknown() {
|
||||
assertThrows(DocumentException.class, () |