Initial Development #1
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, () -> DocumentQuery.insert(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("save generates correctly")
|
||||
public void save() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format(
|
||||
"INSERT INTO %s VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data",
|
||||
TEST_TABLE),
|
||||
DocumentQuery.save(TEST_TABLE), "INSERT ON CONFLICT UPDATE statement not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update generates successfully")
|
||||
public void update() {
|
||||
assertEquals(String.format("UPDATE %s SET data = :data", TEST_TABLE), DocumentQuery.update(TEST_TABLE),
|
||||
"Update query not constructed correctly");
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
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.ExistsQuery;
|
||||
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 `Exists` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | ExistsQuery")
|
||||
final public class ExistsQueryTest {
|
||||
|
||||
/**
|
||||
* 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("SELECT EXISTS (SELECT 1 FROM %s WHERE data->>'id' = :id) AS it", TEST_TABLE),
|
||||
ExistsQuery.byId(TEST_TABLE), "Exists query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | SQLite")
|
||||
public void byIdSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("SELECT EXISTS (SELECT 1 FROM %s WHERE data->>'id' = :id) AS it", TEST_TABLE),
|
||||
ExistsQuery.byId(TEST_TABLE), "Exists query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | PostgreSQL")
|
||||
public void byFieldsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format(
|
||||
"SELECT EXISTS (SELECT 1 FROM %s WHERE (data->>'it')::numeric = :test) AS it", TEST_TABLE),
|
||||
ExistsQuery.byFields(TEST_TABLE, List.of(Field.equal("it", 7, ":test"))),
|
||||
"Exists query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | SQLite")
|
||||
public void byFieldsSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("SELECT EXISTS (SELECT 1 FROM %s WHERE data->>'it' = :test) AS it", TEST_TABLE),
|
||||
ExistsQuery.byFields(TEST_TABLE, List.of(Field.equal("it", 7, ":test"))),
|
||||
"Exists query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains generates correctly | PostgreSQL")
|
||||
public void byContainsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("SELECT EXISTS (SELECT 1 FROM %s WHERE data @> :criteria) AS it", TEST_TABLE),
|
||||
ExistsQuery.byContains(TEST_TABLE), "Exists query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains fails | SQLite")
|
||||
public void byContainsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> ExistsQuery.byContains(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath generates correctly | PostgreSQL")
|
||||
public void byJsonPathPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format(
|
||||
"SELECT EXISTS (SELECT 1 FROM %s WHERE jsonb_path_exists(data, :path::jsonpath)) AS it",
|
||||
TEST_TABLE),
|
||||
ExistsQuery.byJsonPath(TEST_TABLE), "Exists query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath fails | SQLite")
|
||||
public void byJsonPathSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> ExistsQuery.byJsonPath(TEST_TABLE));
|
||||
}
|
||||
}
|
@ -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.FieldMatch;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for the `FieldMatch` enum
|
||||
*/
|
||||
@DisplayName("Core | Java | FieldMatch")
|
||||
final public class FieldMatchTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("ANY uses proper SQL")
|
||||
public void any() {
|
||||
assertEquals("OR", FieldMatch.ANY.getSql(), "ANY should use OR");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ALL uses proper SQL")
|
||||
public void all() {
|
||||
assertEquals("AND", FieldMatch.ALL.getSql(), "ALL should use AND");
|
||||
}
|
||||
}
|
@ -0,0 +1,636 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import kotlin.Pair;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.*;
|
||||
import solutions.bitbadger.documents.core.tests.ForceDialect;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Field` class
|
||||
*/
|
||||
@DisplayName("Core | Java | Field")
|
||||
final public class FieldTest {
|
||||
|
||||
/**
|
||||
* Clear the connection string (resets Dialect)
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
// ~~~ INSTANCE METHODS ~~~
|
||||
|
||||
@Test
|
||||
@DisplayName("withParameterName fails for invalid name")
|
||||
public void withParamNameFails() {
|
||||
assertThrows(DocumentException.class, () -> Field.equal("it", "").withParameterName("2424"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("withParameterName works with colon prefix")
|
||||
public void withParamNameColon() {
|
||||
Field<String> field = Field.equal("abc", "22").withQualifier("me");
|
||||
Field<String> withParam = field.withParameterName(":test");
|
||||
assertNotSame(field, withParam, "A new Field instance should have been created");
|
||||
assertEquals(field.getName(), withParam.getName(), "Name should have been preserved");
|
||||
assertEquals(field.getComparison(), withParam.getComparison(), "Comparison should have been preserved");
|
||||
assertEquals(":test", withParam.getParameterName(), "Parameter name not set correctly");
|
||||
assertEquals(field.getQualifier(), withParam.getQualifier(), "Qualifier should have been preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("withParameterName works with at-sign prefix")
|
||||
public void withParamNameAtSign() {
|
||||
Field<String> field = Field.equal("def", "44");
|
||||
Field<String> withParam = field.withParameterName("@unit");
|
||||
assertNotSame(field, withParam, "A new Field instance should have been created");
|
||||
assertEquals(field.getName(), withParam.getName(), "Name should have been preserved");
|
||||
assertEquals(field.getComparison(), withParam.getComparison(), "Comparison should have been preserved");
|
||||
assertEquals("@unit", withParam.getParameterName(), "Parameter name not set correctly");
|
||||
assertEquals(field.getQualifier(), withParam.getQualifier(), "Qualifier should have been preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("withQualifier sets qualifier correctly")
|
||||
public void withQualifier() {
|
||||
Field<String> field = Field.equal("j", "k");
|
||||
Field<String> withQual = field.withQualifier("test");
|
||||
assertNotSame(field, withQual, "A new Field instance should have been created");
|
||||
assertEquals(field.getName(), withQual.getName(), "Name should have been preserved");
|
||||
assertEquals(field.getComparison(), withQual.getComparison(), "Comparison should have been preserved");
|
||||
assertEquals(field.getParameterName(), withQual.getParameterName(),
|
||||
"Parameter Name should have been preserved");
|
||||
assertEquals("test", withQual.getQualifier(), "Qualifier not set correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("path generates for simple unqualified field | PostgreSQL")
|
||||
public void pathPostgresSimpleUnqualified() {
|
||||
assertEquals("data->>'SomethingCool'",
|
||||
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.POSTGRESQL, FieldFormat.SQL),
|
||||
"Path not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("path generates for simple qualified field | PostgreSQL")
|
||||
public void pathPostgresSimpleQualified() {
|
||||
assertEquals("this.data->>'SomethingElse'",
|
||||
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.POSTGRESQL, FieldFormat.SQL),
|
||||
"Path not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("path generates for nested unqualified field | PostgreSQL")
|
||||
public void pathPostgresNestedUnqualified() {
|
||||
assertEquals("data#>>'{My,Nested,Field}'",
|
||||
Field.equal("My.Nested.Field", "howdy").path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("path generates for nested qualified field | PostgreSQL")
|
||||
public void pathPostgresNestedQualified() {
|
||||
assertEquals("bird.data#>>'{Nest,Away}'",
|
||||
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.POSTGRESQL, FieldFormat.SQL),
|
||||
"Path not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("path generates for simple unqualified field | SQLite")
|
||||
public void pathSQLiteSimpleUnqualified() {
|
||||
assertEquals("data->>'SomethingCool'",
|
||||
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("path generates for simple qualified field | SQLite")
|
||||
public void pathSQLiteSimpleQualified() {
|
||||
assertEquals("this.data->>'SomethingElse'",
|
||||
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.SQLITE, FieldFormat.SQL),
|
||||
"Path not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("path generates for nested unqualified field | SQLite")
|
||||
public void pathSQLiteNestedUnqualified() {
|
||||
assertEquals("data->'My'->'Nested'->>'Field'",
|
||||
Field.equal("My.Nested.Field", "howdy").path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("path generates for nested qualified field | SQLite")
|
||||
public void pathSQLiteNestedQualified() {
|
||||
assertEquals("bird.data->'Nest'->>'Away'",
|
||||
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.SQLITE, FieldFormat.SQL),
|
||||
"Path not correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for exists w/o qualifier | PostgreSQL")
|
||||
public void toWhereExistsNoQualPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for exists w/o qualifier | SQLite")
|
||||
public void toWhereExistsNoQualSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for not-exists w/o qualifier | PostgreSQL")
|
||||
public void toWhereNotExistsNoQualPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for not-exists w/o qualifier | SQLite")
|
||||
public void toWhereNotExistsNoQualSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for BETWEEN w/o qualifier, numeric range | PostgreSQL")
|
||||
public void toWhereBetweenNoQualNumericPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("(data->>'age')::numeric BETWEEN @agemin AND @agemax",
|
||||
Field.between("age", 13, 17, "@age").toWhere(), "Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for BETWEEN w/o qualifier, alphanumeric range | PostgreSQL")
|
||||
public void toWhereBetweenNoQualAlphaPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'city' BETWEEN :citymin AND :citymax",
|
||||
Field.between("city", "Atlanta", "Chicago", ":city").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for BETWEEN w/o qualifier | SQLite")
|
||||
public void toWhereBetweenNoQualSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'age' BETWEEN @agemin AND @agemax", Field.between("age", 13, 17, "@age").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for BETWEEN w/ qualifier, numeric range | PostgreSQL")
|
||||
public void toWhereBetweenQualNumericPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("(test.data->>'age')::numeric BETWEEN @agemin AND @agemax",
|
||||
Field.between("age", 13, 17, "@age").withQualifier("test").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for BETWEEN w/ qualifier, alphanumeric range | PostgreSQL")
|
||||
public void toWhereBetweenQualAlphaPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("unit.data->>'city' BETWEEN :citymin AND :citymax",
|
||||
Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for BETWEEN w/ qualifier | SQLite")
|
||||
public void toWhereBetweenQualSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("my.data->>'age' BETWEEN @agemin AND @agemax",
|
||||
Field.between("age", 13, 17, "@age").withQualifier("my").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for IN/any, numeric values | PostgreSQL")
|
||||
public void toWhereAnyNumericPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)",
|
||||
Field.any("even", List.of(2, 4, 6), ":nbr").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for IN/any, alphanumeric values | PostgreSQL")
|
||||
public void toWhereAnyAlphaPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'test' IN (:city_0, :city_1)",
|
||||
Field.any("test", List.of("Atlanta", "Chicago"), ":city").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for IN/any | SQLite")
|
||||
public void toWhereAnySQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'test' IN (:city_0, :city_1)",
|
||||
Field.any("test", List.of("Atlanta", "Chicago"), ":city").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for inArray | PostgreSQL")
|
||||
public void toWhereInArrayPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]",
|
||||
Field.inArray("even", "tbl", List.of(2, 4, 6, 8), ":it").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for inArray | SQLite")
|
||||
public void toWhereInArraySQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))",
|
||||
Field.inArray("test", "tbl", List.of("Atlanta", "Chicago"), ":city").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for others w/o qualifier | PostgreSQL")
|
||||
public void toWhereOtherNoQualPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates for others w/o qualifier | SQLite")
|
||||
public void toWhereOtherNoQualSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates no-parameter w/ qualifier | PostgreSQL")
|
||||
public void toWhereNoParamWithQualPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates no-parameter w/ qualifier | SQLite")
|
||||
public void toWhereNoParamWithQualSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates parameter w/ qualifier | PostgreSQL")
|
||||
public void toWhereParamWithQualPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("(q.data->>'le_field')::numeric <= :it",
|
||||
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere generates parameter w/ qualifier | SQLite")
|
||||
public void toWhereParamWithQualSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("q.data->>'le_field' <= :it",
|
||||
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere(),
|
||||
"Field WHERE clause not generated correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toWhere fails when dialect is not set")
|
||||
public void toWhereFailsNoDialect() {
|
||||
assertThrows(DocumentException.class, () -> Field.equal("a", "oops").toWhere());
|
||||
}
|
||||
|
||||
// ~~~ STATIC TESTS ~~~
|
||||
|
||||
@Test
|
||||
@DisplayName("equal constructs a field w/o parameter name")
|
||||
public void equalCtor() {
|
||||
final Field<Integer> field = Field.equal("Test", 14);
|
||||
assertEquals("Test", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.EQUAL, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals(14, field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equal constructs a field w/ parameter name")
|
||||
public void equalParameterCtor() {
|
||||
final Field<Integer> field = Field.equal("Test", 14, ":w");
|
||||
assertEquals("Test", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.EQUAL, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals(14, field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertEquals(":w", field.getParameterName(), "Field parameter name not filled correctly");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("greater constructs a field w/o parameter name")
|
||||
public void greaterCtor() {
|
||||
final Field<String> field = Field.greater("Great", "night");
|
||||
assertEquals("Great", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.GREATER, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("night", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("greater constructs a field w/ parameter name")
|
||||
public void greaterParameterCtor() {
|
||||
final Field<String> field = Field.greater("Great", "night", ":yeah");
|
||||
assertEquals("Great", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.GREATER, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("night", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertEquals(":yeah", field.getParameterName(), "Field parameter name not filled correctly");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("greaterOrEqual constructs a field w/o parameter name")
|
||||
public void greaterOrEqualCtor() {
|
||||
final Field<Long> field = Field.greaterOrEqual("Nice", 88L);
|
||||
assertEquals("Nice", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.GREATER_OR_EQUAL, field.getComparison().getOp(),
|
||||
"Field comparison operation not filled correctly");
|
||||
assertEquals(88L, field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("greaterOrEqual constructs a field w/ parameter name")
|
||||
public void greaterOrEqualParameterCtor() {
|
||||
final Field<Long> field = Field.greaterOrEqual("Nice", 88L, ":nice");
|
||||
assertEquals("Nice", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.GREATER_OR_EQUAL, field.getComparison().getOp(),
|
||||
"Field comparison operation not filled correctly");
|
||||
assertEquals(88L, field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertEquals(":nice", field.getParameterName(), "Field parameter name not filled correctly");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("less constructs a field w/o parameter name")
|
||||
public void lessCtor() {
|
||||
final Field<String> field = Field.less("Lesser", "seven");
|
||||
assertEquals("Lesser", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.LESS, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("seven", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("less constructs a field w/ parameter name")
|
||||
public void lessParameterCtor() {
|
||||
final Field<String> field = Field.less("Lesser", "seven", ":max");
|
||||
assertEquals("Lesser", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.LESS, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("seven", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertEquals(":max", field.getParameterName(), "Field parameter name not filled correctly");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("lessOrEqual constructs a field w/o parameter name")
|
||||
public void lessOrEqualCtor() {
|
||||
final Field<String> field = Field.lessOrEqual("Nobody", "KNOWS");
|
||||
assertEquals("Nobody", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.LESS_OR_EQUAL, field.getComparison().getOp(),
|
||||
"Field comparison operation not filled correctly");
|
||||
assertEquals("KNOWS", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("lessOrEqual constructs a field w/ parameter name")
|
||||
public void lessOrEqualParameterCtor() {
|
||||
final Field<String> field = Field.lessOrEqual("Nobody", "KNOWS", ":nope");
|
||||
assertEquals("Nobody", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.LESS_OR_EQUAL, field.getComparison().getOp(),
|
||||
"Field comparison operation not filled correctly");
|
||||
assertEquals("KNOWS", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertEquals(":nope", field.getParameterName(), "Field parameter name not filled correctly");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("notEqual constructs a field w/o parameter name")
|
||||
public void notEqualCtor() {
|
||||
final Field<String> field = Field.notEqual("Park", "here");
|
||||
assertEquals("Park", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.NOT_EQUAL, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("here", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("notEqual constructs a field w/ parameter name")
|
||||
public void notEqualParameterCtor() {
|
||||
final Field<String> field = Field.notEqual("Park", "here", ":now");
|
||||
assertEquals("Park", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.NOT_EQUAL, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("here", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertEquals(":now", field.getParameterName(), "Field parameter name not filled correctly");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("between constructs a field w/o parameter name")
|
||||
public void betweenCtor() {
|
||||
final Field<Pair<Integer, Integer>> field = Field.between("Age", 18, 49);
|
||||
assertEquals("Age", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.BETWEEN, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals(18, field.getComparison().getValue().getFirst(),
|
||||
"Field comparison min value not filled correctly");
|
||||
assertEquals(49, field.getComparison().getValue().getSecond(),
|
||||
"Field comparison max value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("between constructs a field w/ parameter name")
|
||||
public void betweenParameterCtor() {
|
||||
final Field<Pair<Integer, Integer>> field = Field.between("Age", 18, 49, ":limit");
|
||||
assertEquals("Age", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.BETWEEN, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals(18, field.getComparison().getValue().getFirst(),
|
||||
"Field comparison min value not filled correctly");
|
||||
assertEquals(49, field.getComparison().getValue().getSecond(),
|
||||
"Field comparison max value not filled correctly");
|
||||
assertEquals(":limit", field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("any constructs a field w/o parameter name")
|
||||
public void anyCtor() {
|
||||
final Field<Collection<Integer>> field = Field.any("Here", List.of(8, 16, 32));
|
||||
assertEquals("Here", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.IN, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals(List.of(8, 16, 32), field.getComparison().getValue(),
|
||||
"Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("any constructs a field w/ parameter name")
|
||||
public void anyParameterCtor() {
|
||||
final Field<Collection<Integer>> field = Field.any("Here", List.of(8, 16, 32), ":list");
|
||||
assertEquals("Here", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.IN, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals(List.of(8, 16, 32), field.getComparison().getValue(),
|
||||
"Field comparison value not filled correctly");
|
||||
assertEquals(":list", field.getParameterName(), "Field parameter name not filled correctly");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("inArray constructs a field w/o parameter name")
|
||||
public void inArrayCtor() {
|
||||
final Field<Pair<String, Collection<String>>> field = Field.inArray("ArrayField", "table", List.of("z"));
|
||||
assertEquals("ArrayField", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.IN_ARRAY, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("table", field.getComparison().getValue().getFirst(),
|
||||
"Field comparison table not filled correctly");
|
||||
assertEquals(List.of("z"), field.getComparison().getValue().getSecond(),
|
||||
"Field comparison values not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("inArray constructs a field w/ parameter name")
|
||||
public void inArrayParameterCtor() {
|
||||
final Field<Pair<String, Collection<String>>> field = Field.inArray("ArrayField", "table", List.of("z"), ":a");
|
||||
assertEquals("ArrayField", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.IN_ARRAY, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("table", field.getComparison().getValue().getFirst(),
|
||||
"Field comparison table not filled correctly");
|
||||
assertEquals(List.of("z"), field.getComparison().getValue().getSecond(),
|
||||
"Field comparison values not filled correctly");
|
||||
assertEquals(":a", field.getParameterName(), "Field parameter name not filled correctly");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("exists constructs a field")
|
||||
public void existsCtor() {
|
||||
final Field<String> field = Field.exists("Groovy");
|
||||
assertEquals("Groovy", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.EXISTS, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("notExists constructs a field")
|
||||
public void notExistsCtor() {
|
||||
final Field<String> field = Field.notExists("Groovy");
|
||||
assertEquals("Groovy", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.NOT_EXISTS, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("named constructs a field")
|
||||
public void namedCtor() {
|
||||
final Field<String> field = Field.named("Tacos");
|
||||
assertEquals("Tacos", field.getName(), "Field name not filled correctly");
|
||||
assertEquals(Op.EQUAL, field.getComparison().getOp(), "Field comparison operation not filled correctly");
|
||||
assertEquals("", field.getComparison().getValue(), "Field comparison value not filled correctly");
|
||||
assertNull(field.getParameterName(), "The parameter name should have been null");
|
||||
assertNull(field.getQualifier(), "The qualifier should have been null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("static constructors fail for invalid parameter name")
|
||||
public void staticCtorsFailOnParamName() {
|
||||
assertThrows(DocumentException.class, () -> Field.equal("a", "b", "that ain't it, Jack..."));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameToPath creates a simple PostgreSQL SQL name")
|
||||
public void nameToPathPostgresSimpleSQL() {
|
||||
assertEquals("data->>'Simple'", Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.SQL),
|
||||
"Path not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameToPath creates a simple SQLite SQL name")
|
||||
public void nameToPathSQLiteSimpleSQL() {
|
||||
assertEquals("data->>'Simple'", Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.SQL),
|
||||
"Path not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameToPath creates a nested PostgreSQL SQL name")
|
||||
public void nameToPathPostgresNestedSQL() {
|
||||
assertEquals("data#>>'{A,Long,Path,to,the,Property}'",
|
||||
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.SQL),
|
||||
"Path not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameToPath creates a nested SQLite SQL name")
|
||||
public void nameToPathSQLiteNestedSQL() {
|
||||
assertEquals("data->'A'->'Long'->'Path'->'to'->'the'->>'Property'",
|
||||
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.SQL),
|
||||
"Path not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameToPath creates a simple PostgreSQL JSON name")
|
||||
public void nameToPathPostgresSimpleJSON() {
|
||||
assertEquals("data->'Simple'", Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.JSON),
|
||||
"Path not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameToPath creates a simple SQLite JSON name")
|
||||
public void nameToPathSQLiteSimpleJSON() {
|
||||
assertEquals("data->'Simple'", Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.JSON),
|
||||
"Path not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameToPath creates a nested PostgreSQL JSON name")
|
||||
public void nameToPathPostgresNestedJSON() {
|
||||
assertEquals("data#>'{A,Long,Path,to,the,Property}'",
|
||||
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.JSON),
|
||||
"Path not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameToPath creates a nested SQLite JSON name")
|
||||
public void nameToPathSQLiteNestedJSON() {
|
||||
assertEquals("data->'A'->'Long'->'Path'->'to'->'the'->'Property'",
|
||||
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.JSON),
|
||||
"Path not constructed correctly");
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
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.FindQuery;
|
||||
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 `Find` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | FindQuery")
|
||||
final public class FindQueryTest {
|
||||
|
||||
/**
|
||||
* Clear the connection string (resets Dialect)
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("all generates correctly")
|
||||
public void all() {
|
||||
assertEquals(String.format("SELECT data FROM %s", TEST_TABLE), FindQuery.all(TEST_TABLE),
|
||||
"Find query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | PostgreSQL")
|
||||
public void byIdPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("SELECT data FROM %s WHERE data->>'id' = :id", TEST_TABLE),
|
||||
FindQuery.byId(TEST_TABLE), "Find query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | SQLite")
|
||||
public void byIdSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("SELECT data FROM %s WHERE data->>'id' = :id", TEST_TABLE),
|
||||
FindQuery.byId(TEST_TABLE), "Find query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | PostgreSQL")
|
||||
public void byFieldsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(
|
||||
String.format("SELECT data FROM %s WHERE data->>'a' = :b AND (data->>'c')::numeric < :d", TEST_TABLE),
|
||||
FindQuery.byFields(TEST_TABLE, List.of(Field.equal("a", "", ":b"), Field.less("c", 14, ":d"))),
|
||||
"Find query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | SQLite")
|
||||
public void byFieldsSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("SELECT data FROM %s WHERE data->>'a' = :b AND data->>'c' < :d", TEST_TABLE),
|
||||
FindQuery.byFields(TEST_TABLE, List.of(Field.equal("a", "", ":b"), Field.less("c", 14, ":d"))),
|
||||
"Find query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains generates correctly | PostgreSQL")
|
||||
public void byContainsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("SELECT data FROM %s WHERE data @> :criteria", TEST_TABLE),
|
||||
FindQuery.byContains(TEST_TABLE), "Find query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains fails | SQLite")
|
||||
public void byContainsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> FindQuery.byContains(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath generates correctly | PostgreSQL")
|
||||
public void byJsonPathPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("SELECT data FROM %s WHERE jsonb_path_exists(data, :path::jsonpath)", TEST_TABLE),
|
||||
FindQuery.byJsonPath(TEST_TABLE), "Find query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath fails | SQLite")
|
||||
public void byJsonPathSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> FindQuery.byJsonPath(TEST_TABLE));
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
public class IntIdClass {
|
||||
|
||||
private int id;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public IntIdClass(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
public class LongIdClass {
|
||||
|
||||
private long id;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LongIdClass(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.Op;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Op` enum
|
||||
*/
|
||||
@DisplayName("Core | Java | Op")
|
||||
final public class OpTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("EQUAL uses proper SQL")
|
||||
public void equalSQL() {
|
||||
assertEquals("=", Op.EQUAL.getSql(), "The SQL for equal is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GREATER uses proper SQL")
|
||||
public void greaterSQL() {
|
||||
assertEquals(">", Op.GREATER.getSql(), "The SQL for greater is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GREATER_OR_EQUAL uses proper SQL")
|
||||
public void greaterOrEqualSQL() {
|
||||
assertEquals(">=", Op.GREATER_OR_EQUAL.getSql(), "The SQL for greater-or-equal is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("LESS uses proper SQL")
|
||||
public void lessSQL() {
|
||||
assertEquals("<", Op.LESS.getSql(), "The SQL for less is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("LESS_OR_EQUAL uses proper SQL")
|
||||
public void lessOrEqualSQL() {
|
||||
assertEquals("<=", Op.LESS_OR_EQUAL.getSql(), "The SQL for less-or-equal is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("NOT_EQUAL uses proper SQL")
|
||||
public void notEqualSQL() {
|
||||
assertEquals("<>", Op.NOT_EQUAL.getSql(), "The SQL for not-equal is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("BETWEEN uses proper SQL")
|
||||
public void betweenSQL() {
|
||||
assertEquals("BETWEEN", Op.BETWEEN.getSql(), "The SQL for between is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("IN uses proper SQL")
|
||||
public void inSQL() {
|
||||
assertEquals("IN", Op.IN.getSql(), "The SQL for in is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("IN_ARRAY uses proper SQL")
|
||||
public void inArraySQL() {
|
||||
assertEquals("??|", Op.IN_ARRAY.getSql(), "The SQL for in-array is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("EXISTS uses proper SQL")
|
||||
public void existsSQL() {
|
||||
assertEquals("IS NOT NULL", Op.EXISTS.getSql(), "The SQL for exists is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("NOT_EXISTS uses proper SQL")
|
||||
public void notExistsSQL() {
|
||||
assertEquals("IS NULL", Op.NOT_EXISTS.getSql(), "The SQL for not-exists is incorrect");
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.ParameterName;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for the `ParameterName` class
|
||||
*/
|
||||
@DisplayName("Core | Java | ParameterName")
|
||||
final public class ParameterNameTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("derive works when given existing names")
|
||||
public void withExisting() {
|
||||
ParameterName names = new ParameterName();
|
||||
assertEquals(":taco", names.derive(":taco"), "Name should have been :taco");
|
||||
assertEquals(":field0", names.derive(null), "Counter should not have advanced for named field");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("derive works when given all anonymous fields")
|
||||
public void allAnonymous() {
|
||||
ParameterName names = new ParameterName();
|
||||
assertEquals(":field0", names.derive(null), "Anonymous field name should have been returned");
|
||||
assertEquals(":field1", names.derive(null), "Counter should have advanced from previous call");
|
||||
assertEquals(":field2", names.derive(null), "Counter should have advanced from previous call");
|
||||
assertEquals(":field3", names.derive(null), "Counter should have advanced from previous call");
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.Parameter;
|
||||
import solutions.bitbadger.documents.ParameterType;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Parameter` class
|
||||
*/
|
||||
@DisplayName("Core | Java | Parameter")
|
||||
final public class ParameterTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Construction with colon-prefixed name")
|
||||
public void ctorWithColon() {
|
||||
Parameter<String> p = new Parameter<>(":test", ParameterType.STRING, "ABC");
|
||||
assertEquals(":test", p.getName(), "Parameter name was incorrect");
|
||||
assertEquals(ParameterType.STRING, p.getType(), "Parameter type was incorrect");
|
||||
assertEquals("ABC", p.getValue(), "Parameter value was incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Construction with at-sign-prefixed name")
|
||||
public void ctorWithAtSign() {
|
||||
Parameter<String> p = new Parameter<>("@yo", ParameterType.NUMBER, null);
|
||||
assertEquals("@yo", p.getName(), "Parameter name was incorrect");
|
||||
assertEquals(ParameterType.NUMBER, p.getType(), "Parameter type was incorrect");
|
||||
assertNull(p.getValue(), "Parameter value was incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Construction fails with incorrect prefix")
|
||||
public void ctorFailsForPrefix() {
|
||||
assertThrows(DocumentException.class, () -> new Parameter<>("it", ParameterType.JSON, ""));
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
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.*;
|
||||
import solutions.bitbadger.documents.java.Parameters;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Parameters` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Parameters")
|
||||
final public class ParametersTest {
|
||||
|
||||
/**
|
||||
* Reset the dialect
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
Configuration.setConnectionString(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameFields works with no changes")
|
||||
public void nameFieldsNoChange() {
|
||||
List<Field<?>> fields = List.of(Field.equal("a", "", ":test"), Field.exists("q"), Field.equal("b", "", ":me"));
|
||||
Field<?>[] named = Parameters.nameFields(fields).toArray(new Field<?>[] { });
|
||||
assertEquals(fields.size(), named.length, "There should have been 3 fields in the list");
|
||||
assertSame(fields.get(0), named[0], "The first field should be the same");
|
||||
assertSame(fields.get(1), named[1], "The second field should be the same");
|
||||
assertSame(fields.get(2), named[2], "The third field should be the same");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameFields works when changing fields")
|
||||
public void nameFieldsChange() {
|
||||
List<Field<?>> fields = List.of(
|
||||
Field.equal("a", ""), Field.equal("e", "", ":hi"), Field.equal("b", ""), Field.notExists("z"));
|
||||
Field<?>[] named = Parameters.nameFields(fields).toArray(new Field<?>[] { });
|
||||
assertEquals(fields.size(), named.length, "There should have been 4 fields in the list");
|
||||
assertNotSame(fields.get(0), named[0], "The first field should not be the same");
|
||||
assertEquals(":field0", named[0].getParameterName(), "First parameter name incorrect");
|
||||
assertSame(fields.get(1), named[1], "The second field should be the same");
|
||||
assertNotSame(fields.get(2), named[2], "The third field should not be the same");
|
||||
assertEquals(":field1", named[2].getParameterName(), "Third parameter name incorrect");
|
||||
assertSame(fields.get(3), named[3], "The fourth field should be the same");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("replaceNamesInQuery replaces successfully")
|
||||
public void replaceNamesInQuery() {
|
||||
List<Parameter<?>> parameters = List.of(new Parameter<>(":data", ParameterType.JSON, "{}"),
|
||||
new Parameter<>(":data_ext", ParameterType.STRING, ""));
|
||||
String query =
|
||||
"SELECT data, data_ext FROM tbl WHERE data = :data AND data_ext = :data_ext AND more_data = :data";
|
||||
assertEquals("SELECT data, data_ext FROM tbl WHERE data = ? AND data_ext = ? AND more_data = ?",
|
||||
Parameters.replaceNamesInQuery(query, parameters), "Parameters not replaced correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fieldNames generates a single parameter (PostgreSQL)")
|
||||
public void fieldNamesSinglePostgres() throws DocumentException {
|
||||
Configuration.setConnectionString(":postgresql:");
|
||||
Parameter<?>[] nameParams = Parameters.fieldNames(List.of("test")).toArray(new Parameter<?>[]{});
|
||||
assertEquals(1, nameParams.length, "There should be one name parameter");
|
||||
assertEquals(":name", nameParams[0].getName(), "The parameter name is incorrect");
|
||||
assertEquals(ParameterType.STRING, nameParams[0].getType(), "The parameter type is incorrect");
|
||||
assertEquals("{test}", nameParams[0].getValue(), "The parameter value is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fieldNames generates multiple parameters (PostgreSQL)")
|
||||
public void fieldNamesMultiplePostgres() throws DocumentException {
|
||||
Configuration.setConnectionString(":postgresql:");
|
||||
Parameter<?>[] nameParams = Parameters.fieldNames(List.of("test", "this", "today"))
|
||||
.toArray(new Parameter<?>[]{});
|
||||
assertEquals(1, nameParams.length, "There should be one name parameter");
|
||||
assertEquals(":name", nameParams[0].getName(), "The parameter name is incorrect");
|
||||
assertEquals(ParameterType.STRING, nameParams[0].getType(), "The parameter type is incorrect");
|
||||
assertEquals("{test,this,today}", nameParams[0].getValue(), "The parameter value is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fieldNames generates a single parameter (SQLite)")
|
||||
public void fieldNamesSingleSQLite() throws DocumentException {
|
||||
Configuration.setConnectionString(":sqlite:");
|
||||
Parameter<?>[] nameParams = Parameters.fieldNames(List.of("test")).toArray(new Parameter<?>[]{});
|
||||
assertEquals(1, nameParams.length, "There should be one name parameter");
|
||||
assertEquals(":name0", nameParams[0].getName(), "The parameter name is incorrect");
|
||||
assertEquals(ParameterType.STRING, nameParams[0].getType(), "The parameter type is incorrect");
|
||||
assertEquals("test", nameParams[0].getValue(), "The parameter value is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fieldNames generates multiple parameters (SQLite)")
|
||||
public void fieldNamesMultipleSQLite() throws DocumentException {
|
||||
Configuration.setConnectionString(":sqlite:");
|
||||
Parameter<?>[] nameParams = Parameters.fieldNames(List.of("test", "this", "today"))
|
||||
.toArray(new Parameter<?>[]{});
|
||||
assertEquals(3, nameParams.length, "There should be one name parameter");
|
||||
assertEquals(":name0", nameParams[0].getName(), "The first parameter name is incorrect");
|
||||
assertEquals(ParameterType.STRING, nameParams[0].getType(), "The first parameter type is incorrect");
|
||||
assertEquals("test", nameParams[0].getValue(), "The first parameter value is incorrect");
|
||||
assertEquals(":name1", nameParams[1].getName(), "The second parameter name is incorrect");
|
||||
assertEquals(ParameterType.STRING, nameParams[1].getType(), "The second parameter type is incorrect");
|
||||
assertEquals("this", nameParams[1].getValue(), "The second parameter value is incorrect");
|
||||
assertEquals(":name2", nameParams[2].getName(), "The third parameter name is incorrect");
|
||||
assertEquals(ParameterType.STRING, nameParams[2].getType(), "The third parameter type is incorrect");
|
||||
assertEquals("today", nameParams[2].getValue(), "The third parameter value is incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fieldNames fails if dialect not set")
|
||||
public void fieldNamesFails() {
|
||||
assertThrows(DocumentException.class, () -> Parameters.fieldNames(List.of()));
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
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.PatchQuery;
|
||||
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 `Patch` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | PatchQuery")
|
||||
final public class PatchQueryTest {
|
||||
|
||||
/**
|
||||
* Reset the dialect
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | PostgreSQL")
|
||||
public void byIdPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("UPDATE %s SET data = data || :data WHERE data->>'id' = :id", TEST_TABLE),
|
||||
PatchQuery.byId(TEST_TABLE), "Patch query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | SQLite")
|
||||
public void byIdSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format(
|
||||
"UPDATE %s SET data = json_patch(data, json(:data)) WHERE data->>'id' = :id", TEST_TABLE),
|
||||
PatchQuery.byId(TEST_TABLE), "Patch query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | PostgreSQL")
|
||||
public void byFieldsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("UPDATE %s SET data = data || :data WHERE data->>'z' = :y", TEST_TABLE),
|
||||
PatchQuery.byFields(TEST_TABLE, List.of(Field.equal("z", "", ":y"))),
|
||||
"Patch query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | SQLite")
|
||||
public void byFieldsSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format(
|
||||
"UPDATE %s SET data = json_patch(data, json(:data)) WHERE data->>'z' = :y", TEST_TABLE),
|
||||
PatchQuery.byFields(TEST_TABLE, List.of(Field.equal("z", "", ":y"))),
|
||||
"Patch query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains generates correctly | PostgreSQL")
|
||||
public void byContainsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("UPDATE %s SET data = data || :data WHERE data @> :criteria", TEST_TABLE),
|
||||
PatchQuery.byContains(TEST_TABLE), "Patch query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains fails | SQLite")
|
||||
public void byContainsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> PatchQuery.byContains(TEST_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath generates correctly | PostgreSQL")
|
||||
public void byJsonPathPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("UPDATE %s SET data = data || :data WHERE jsonb_path_exists(data, :path::jsonpath)",
|
||||
TEST_TABLE),
|
||||
PatchQuery.byJsonPath(TEST_TABLE), "Patch query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath fails | SQLite")
|
||||
public void byJsonPathSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> PatchQuery.byJsonPath(TEST_TABLE));
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
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.Field;
|
||||
import solutions.bitbadger.documents.FieldMatch;
|
||||
import solutions.bitbadger.documents.query.QueryUtils;
|
||||
import solutions.bitbadger.documents.core.tests.ForceDialect;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for the package-level query functions, presented as `QueryUtils` for Java-compatible use
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | QueryUtils")
|
||||
final public class QueryUtilsTest {
|
||||
|
||||
/**
|
||||
* Clear the connection string (resets Dialect)
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("statementWhere generates correctly")
|
||||
public void statementWhere() {
|
||||
assertEquals("x WHERE y", QueryUtils.statementWhere("x", "y"), "Statements not combined correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates a numeric ID query | PostgreSQL")
|
||||
public void byIdNumericPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("test WHERE (data->>'id')::numeric = :id", QueryUtils.byId("test", 9));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates an alphanumeric ID query | PostgreSQL")
|
||||
public void byIdAlphaPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("unit WHERE data->>'id' = :id", QueryUtils.byId("unit", "18"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates ID query | SQLite")
|
||||
public void byIdSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("yo WHERE data->>'id' = :id", QueryUtils.byId("yo", 27));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates default field query | PostgreSQL")
|
||||
public void byFieldsMultipleDefaultPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value",
|
||||
QueryUtils.byFields("this", List.of(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates default field query | SQLite")
|
||||
public void byFieldsMultipleDefaultSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("this WHERE data->>'a' = :the_a AND data->>'b' = :b_value",
|
||||
QueryUtils.byFields("this", List.of(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates ANY field query | PostgreSQL")
|
||||
public void byFieldsMultipleAnyPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value",
|
||||
QueryUtils.byFields("that", List.of(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")),
|
||||
FieldMatch.ANY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates ANY field query | SQLite")
|
||||
public void byFieldsMultipleAnySQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("that WHERE data->>'a' = :the_a OR data->>'b' = :b_value",
|
||||
QueryUtils.byFields("that", List.of(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")),
|
||||
FieldMatch.ANY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates for no fields")
|
||||
public void orderByNone() throws DocumentException {
|
||||
assertEquals("", QueryUtils.orderBy(List.of(), Dialect.POSTGRESQL),
|
||||
"ORDER BY should have been blank (PostgreSQL)");
|
||||
assertEquals("", QueryUtils.orderBy(List.of(), Dialect.SQLITE), "ORDER BY should have been blank (SQLite)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates single, no direction | PostgreSQL")
|
||||
public void orderBySinglePostgres() throws DocumentException {
|
||||
assertEquals(" ORDER BY data->>'TestField'",
|
||||
QueryUtils.orderBy(List.of(Field.named("TestField")), Dialect.POSTGRESQL),
|
||||
"ORDER BY not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates single, no direction | SQLite")
|
||||
public void orderBySingleSQLite() throws DocumentException {
|
||||
assertEquals(" ORDER BY data->>'TestField'",
|
||||
QueryUtils.orderBy(List.of(Field.named("TestField")), Dialect.SQLITE),
|
||||
"ORDER BY not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates multiple with direction | PostgreSQL")
|
||||
public void orderByMultiplePostgres() throws DocumentException {
|
||||
assertEquals(" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||
QueryUtils.orderBy(List.of(
|
||||
Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")),
|
||||
Dialect.POSTGRESQL),
|
||||
"ORDER BY not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates multiple with direction | SQLite")
|
||||
public void orderByMultipleSQLite() throws DocumentException {
|
||||
assertEquals(" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||
QueryUtils.orderBy(List.of(
|
||||
Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")),
|
||||
Dialect.SQLITE),
|
||||
"ORDER BY not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates numeric ordering | PostgreSQL")
|
||||
public void orderByNumericPostgres() throws DocumentException {
|
||||
assertEquals(" ORDER BY (data->>'Test')::numeric",
|
||||
QueryUtils.orderBy(List.of(Field.named("n:Test")), Dialect.POSTGRESQL),
|
||||
"ORDER BY not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates numeric ordering | SQLite")
|
||||
public void orderByNumericSQLite() throws DocumentException {
|
||||
assertEquals(" ORDER BY data->>'Test'", QueryUtils.orderBy(List.of(Field.named("n:Test")), Dialect.SQLITE),
|
||||
"ORDER BY not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates case-insensitive ordering | PostgreSQL")
|
||||
public void orderByCIPostgres() throws DocumentException {
|
||||
assertEquals(" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST",
|
||||
QueryUtils.orderBy(List.of(Field.named("i:Test.Field DESC NULLS FIRST")), Dialect.POSTGRESQL),
|
||||
"ORDER BY not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates case-insensitive ordering | SQLite")
|
||||
public void orderByCISQLite() throws DocumentException {
|
||||
assertEquals(" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST",
|
||||
QueryUtils.orderBy(List.of(Field.named("i:Test.Field ASC NULLS LAST")), Dialect.SQLITE),
|
||||
"ORDER BY not constructed correctly");
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
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.Parameter;
|
||||
import solutions.bitbadger.documents.ParameterType;
|
||||
import solutions.bitbadger.documents.query.RemoveFieldsQuery;
|
||||
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 `RemoveFields` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | RemoveFieldsQuery")
|
||||
final public class RemoveFieldsQueryTest {
|
||||
|
||||
/**
|
||||
* Reset the dialect
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | PostgreSQL")
|
||||
public void byIdPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("UPDATE %s SET data = data - :name::text[] WHERE data->>'id' = :id", TEST_TABLE),
|
||||
RemoveFieldsQuery.byId(TEST_TABLE, List.of(new Parameter<>(":name", ParameterType.STRING, "{a,z}"))),
|
||||
"Remove Fields query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates correctly | SQLite")
|
||||
public void byIdSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("UPDATE %s SET data = json_remove(data, :name0, :name1) WHERE data->>'id' = :id",
|
||||
TEST_TABLE),
|
||||
RemoveFieldsQuery.byId(TEST_TABLE, List.of(new Parameter<>(":name0", ParameterType.STRING, "a"),
|
||||
new Parameter<>(":name1", ParameterType.STRING, "z"))),
|
||||
"Remove Field query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | PostgreSQL")
|
||||
public void byFieldsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("UPDATE %s SET data = data - :name::text[] WHERE data->>'f' > :g", TEST_TABLE),
|
||||
RemoveFieldsQuery.byFields(TEST_TABLE, List.of(new Parameter<>(":name", ParameterType.STRING, "{b,c}")),
|
||||
List.of(Field.greater("f", "", ":g"))),
|
||||
"Remove Field query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates correctly | SQLite")
|
||||
public void byFieldsSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals(String.format("UPDATE %s SET data = json_remove(data, :name0, :name1) WHERE data->>'f' > :g",
|
||||
TEST_TABLE),
|
||||
RemoveFieldsQuery.byFields(TEST_TABLE, List.of(new Parameter<>(":name0", ParameterType.STRING, "b"),
|
||||
new Parameter<>(":name1", ParameterType.STRING, "c")),
|
||||
List.of(Field.greater("f", "", ":g"))),
|
||||
"Remove Field query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains generates correctly | PostgreSQL")
|
||||
public void byContainsPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format("UPDATE %s SET data = data - :name::text[] WHERE data @> :criteria", TEST_TABLE),
|
||||
RemoveFieldsQuery.byContains(TEST_TABLE,
|
||||
List.of(new Parameter<>(":name", ParameterType.STRING, "{m,n}"))),
|
||||
"Remove Field query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byContains fails | SQLite")
|
||||
public void byContainsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> RemoveFieldsQuery.byContains(TEST_TABLE, List.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath generates correctly | PostgreSQL")
|
||||
public void byJsonPathPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals(String.format(
|
||||
"UPDATE %s SET data = data - :name::text[] WHERE jsonb_path_exists(data, :path::jsonpath)",
|
||||
TEST_TABLE),
|
||||
RemoveFieldsQuery.byJsonPath(TEST_TABLE,
|
||||
List.of(new Parameter<>(":name", ParameterType.STRING, "{o,p}"))),
|
||||
"Remove Field query not constructed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byJsonPath fails | SQLite")
|
||||
public void byJsonPathSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, () -> RemoveFieldsQuery.byJsonPath(TEST_TABLE, List.of()));
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
public class ShortIdClass {
|
||||
|
||||
private short id;
|
||||
|
||||
public short getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(short id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ShortIdClass(short id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package solutions.bitbadger.documents.core.tests.java;
|
||||
|
||||
public class StringIdClass {
|
||||
|
||||
private String id;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public StringIdClass(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
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.FieldMatch;
|
||||
import solutions.bitbadger.documents.query.Where;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Unit tests for the `Where` object
|
||||
*/
|
||||
@DisplayName("Core | Java | Query | Where")
|
||||
final public class WhereTest {
|
||||
|
||||
/**
|
||||
* Clear the connection string (resets Dialect)
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
ForceDialect.none();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields is blank when given no fields")
|
||||
public void byFieldsBlankIfEmpty() throws DocumentException {
|
||||
assertEquals("", Where.byFields(List.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates one numeric field | PostgreSQL")
|
||||
public void byFieldsOneFieldPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("(data->>'it')::numeric = :that", Where.byFields(List.of(Field.equal("it", 9, ":that"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates one alphanumeric field | PostgreSQL")
|
||||
public void byFieldsOneAlphaFieldPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'it' = :that", Where.byFields(List.of(Field.equal("it", "", ":that"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates one field | SQLite")
|
||||
public void byFieldsOneFieldSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'it' = :that", Where.byFields(List.of(Field.equal("it", "", ":that"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates multiple fields w/ default match | PostgreSQL")
|
||||
public void byFieldsMultipleDefaultPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three",
|
||||
Where.byFields(List.of(
|
||||
Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates multiple fields w/ default match | SQLite")
|
||||
public void byFieldsMultipleDefaultSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three",
|
||||
Where.byFields(List.of(
|
||||
Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates multiple fields w/ ANY match | PostgreSQL")
|
||||
public void byFieldsMultipleAnyPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three",
|
||||
Where.byFields(List.of(
|
||||
Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")),
|
||||
FieldMatch.ANY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byFields generates multiple fields w/ ANY match | SQLite")
|
||||
public void byFieldsMultipleAnySQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three",
|
||||
Where.byFields(List.of(
|
||||
Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")),
|
||||
FieldMatch.ANY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates defaults for alphanumeric key | PostgreSQL")
|
||||
public void byIdDefaultAlphaPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'id' = :id", Where.byId(":id", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates defaults for numeric key | PostgreSQL")
|
||||
public void byIdDefaultNumericPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("(data->>'id')::numeric = :id", Where.byId(":id", 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates defaults | SQLite")
|
||||
public void byIdDefaultSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'id' = :id", Where.byId(":id", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates named ID | PostgreSQL")
|
||||
public void byIdDefaultNamedPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data->>'id' = :key", Where.byId(":key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("byId generates named ID | SQLite")
|
||||
public void byIdDefaultNamedSQLite() throws DocumentException {
|
||||
ForceDialect.sqlite();
|
||||
assertEquals("data->>'id' = :key", Where.byId(":key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("jsonContains generates defaults | PostgreSQL")
|
||||
public void jsonContainsDefaultPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data @> :criteria", Where.jsonContains());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("jsonContains generates named parameter | PostgreSQL")
|
||||
public void jsonContainsNamedPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("data @> :it", Where.jsonContains(":it"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("jsonContains fails | SQLite")
|
||||
public void jsonContainsFailsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, Where::jsonContains);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("jsonPathMatches generates defaults | PostgreSQL")
|
||||
public void jsonPathMatchDefaultPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("jsonb_path_exists(data, :path::jsonpath)", Where.jsonPathMatches());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("jsonPathMatches generates named parameter | PostgreSQL")
|
||||
public void jsonPathMatchNamedPostgres() throws DocumentException {
|
||||
ForceDialect.postgres();
|
||||
assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Where.jsonPathMatches(":jp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("jsonPathMatches fails | SQLite")
|
||||
public void jsonPathFailsSQLite() {
|
||||
ForceDialect.sqlite();
|
||||
assertThrows(DocumentException.class, Where::jsonPathMatches);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ArrayDocument {
|
||||
|
||||
private String id;
|
||||
private List<String> values;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<String> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public void setValues(List<String> values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public ArrayDocument(String id, List<String> values) {
|
||||
this.id = id;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public ArrayDocument() {
|
||||
this("", List.of());
|
||||
}
|
||||
|
||||
public static List<ArrayDocument> testDocuments =
|
||||
List.of(
|
||||
new ArrayDocument("first", List.of("a", "b", "c")),
|
||||
new ArrayDocument("second", List.of("c", "d", "e")),
|
||||
new ArrayDocument("third", List.of("x", "y", "z")));
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.Field;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
|
||||
|
||||
/**
|
||||
* Integration tests for the `Count` object
|
||||
*/
|
||||
final public class CountFunctions {
|
||||
|
||||
public static void all(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should have been 5 documents in the table");
|
||||
}
|
||||
|
||||
public static void byFieldsNumeric(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(3L, countByFields(db.getConn(), TEST_TABLE, List.of(Field.between("numValue", 10, 20))),
|
||||
"There should have been 3 matching documents");
|
||||
}
|
||||
|
||||
public static void byFieldsAlpha(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(1L, countByFields(db.getConn(), TEST_TABLE, List.of(Field.between("value", "aardvark", "apple"))),
|
||||
"There should have been 1 matching document");
|
||||
}
|
||||
|
||||
public static void byContainsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(2L, countByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple")),
|
||||
"There should have been 2 matching documents");
|
||||
}
|
||||
|
||||
public static void byContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(0L, countByContains(db.getConn(), TEST_TABLE, Map.of("value", "magenta")),
|
||||
"There should have been no matching documents");
|
||||
}
|
||||
|
||||
public static void byJsonPathMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(2L, countByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ < 5)"),
|
||||
"There should have been 2 matching documents");
|
||||
}
|
||||
|
||||
public static void byJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(0L, countByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 100)"),
|
||||
"There should have been no matching documents");
|
||||
}
|
||||
|
||||
private CountFunctions() {
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.*;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
import solutions.bitbadger.documents.java.Results;
|
||||
import solutions.bitbadger.documents.query.CountQuery;
|
||||
import solutions.bitbadger.documents.query.DeleteQuery;
|
||||
import solutions.bitbadger.documents.query.FindQuery;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
|
||||
import static solutions.bitbadger.documents.query.QueryUtils.orderBy;
|
||||
|
||||
final public class CustomFunctions {
|
||||
|
||||
public static void listEmpty(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
deleteByFields(db.getConn(), TEST_TABLE, List.of(Field.exists(Configuration.idField)));
|
||||
Collection<JsonDocument> result =
|
||||
customList(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), JsonDocument.class, Results::fromData);
|
||||
assertEquals(0, result.size(), "There should have been no results");
|
||||
}
|
||||
|
||||
public static void listAll(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
Collection<JsonDocument> result =
|
||||
customList(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), JsonDocument.class, Results::fromData);
|
||||
assertEquals(5, result.size(), "There should have been 5 results");
|
||||
}
|
||||
|
||||
public static void jsonArrayEmpty(ThrowawayDatabase db) throws DocumentException {
|
||||
assertEquals(0L, countAll(db.getConn(), TEST_TABLE), "The test table should be empty");
|
||||
assertEquals("[]", customJsonArray(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
|
||||
"An empty list was not represented correctly");
|
||||
}
|
||||
|
||||
public static void jsonArraySingle(ThrowawayDatabase db) throws DocumentException {
|
||||
insert(db.getConn(), TEST_TABLE, new ArrayDocument("one", List.of("2", "3")));
|
||||
assertEquals(JsonFunctions.maybeJsonB("[{\"id\":\"one\",\"values\":[\"2\",\"3\"]}]"),
|
||||
customJsonArray(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
|
||||
"A single document list was not represented correctly");
|
||||
}
|
||||
|
||||
public static void jsonArrayMany(ThrowawayDatabase db) throws DocumentException {
|
||||
for (ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
|
||||
assertEquals(JsonFunctions.maybeJsonB("[{\"id\":\"first\",\"values\":[\"a\",\"b\",\"c\"]},"
|
||||
+ "{\"id\":\"second\",\"values\":[\"c\",\"d\",\"e\"]},"
|
||||
+ "{\"id\":\"third\",\"values\":[\"x\",\"y\",\"z\"]}]"),
|
||||
customJsonArray(db.getConn(), FindQuery.all(TEST_TABLE) + orderBy(List.of(Field.named("id"))),
|
||||
List.of(), Results::jsonFromData),
|
||||
"A multiple document list was not represented correctly");
|
||||
}
|
||||
|
||||
public static void writeJsonArrayEmpty(ThrowawayDatabase db) throws DocumentException {
|
||||
assertEquals(0L, countAll(db.getConn(), TEST_TABLE), "The test table should be empty");
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeCustomJsonArray(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), writer, Results::jsonFromData);
|
||||
assertEquals("[]", output.toString(), "An empty list was not represented correctly");
|
||||
}
|
||||
|
||||
public static void writeJsonArraySingle(ThrowawayDatabase db) throws DocumentException {
|
||||
insert(db.getConn(), TEST_TABLE, new ArrayDocument("one", List.of("2", "3")));
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeCustomJsonArray(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), writer, Results::jsonFromData);
|
||||
assertEquals(JsonFunctions.maybeJsonB("[{\"id\":\"one\",\"values\":[\"2\",\"3\"]}]"), output.toString(),
|
||||
"A single document list was not represented correctly");
|
||||
}
|
||||
|
||||
public static void writeJsonArrayMany(ThrowawayDatabase db) throws DocumentException {
|
||||
for (ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeCustomJsonArray(db.getConn(), FindQuery.all(TEST_TABLE) + orderBy(List.of(Field.named("id"))), List.of(),
|
||||
writer, Results::jsonFromData);
|
||||
assertEquals(JsonFunctions.maybeJsonB("[{\"id\":\"first\",\"values\":[\"a\",\"b\",\"c\"]},"
|
||||
+ "{\"id\":\"second\",\"values\":[\"c\",\"d\",\"e\"]},"
|
||||
+ "{\"id\":\"third\",\"values\":[\"x\",\"y\",\"z\"]}]"),
|
||||
output.toString(), "A multiple document list was not represented correctly");
|
||||
}
|
||||
|
||||
public static void singleNone(ThrowawayDatabase db) throws DocumentException {
|
||||
assertFalse(
|
||||
customSingle(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), JsonDocument.class, Results::fromData)
|
||||
.isPresent(),
|
||||
"There should not have been a document returned");
|
||||
}
|
||||
|
||||
public static void singleOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertTrue(
|
||||
customSingle(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), JsonDocument.class, Results::fromData)
|
||||
.isPresent(),
|
||||
"There should have been a document returned");
|
||||
}
|
||||
|
||||
public static void jsonSingleNone(ThrowawayDatabase db) throws DocumentException {
|
||||
assertEquals("{}", customJsonSingle(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
|
||||
"An empty document was not represented correctly");
|
||||
}
|
||||
|
||||
public static void jsonSingleOne(ThrowawayDatabase db) throws DocumentException {
|
||||
insert(db.getConn(), TEST_TABLE, new ArrayDocument("me", List.of("myself", "i")));
|
||||
assertEquals(JsonFunctions.maybeJsonB("{\"id\":\"me\",\"values\":[\"myself\",\"i\"]}"),
|
||||
customJsonSingle(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
|
||||
"A single document was not represented correctly");
|
||||
}
|
||||
|
||||
public static void nonQueryChanges(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L,
|
||||
customScalar(db.getConn(), CountQuery.all(TEST_TABLE), List.of(), Long.class, Results::toCount),
|
||||
"There should have been 5 documents in the table");
|
||||
customNonQuery(db.getConn(), String.format("DELETE FROM %s", TEST_TABLE));
|
||||
assertEquals(0L,
|
||||
customScalar(db.getConn(), CountQuery.all(TEST_TABLE), List.of(), Long.class, Results::toCount),
|
||||
"There should have been no documents in the table");
|
||||
}
|
||||
|
||||
public static void nonQueryNoChanges(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L,
|
||||
customScalar(db.getConn(), CountQuery.all(TEST_TABLE), List.of(), Long.class, Results::toCount),
|
||||
"There should have been 5 documents in the table");
|
||||
customNonQuery(db.getConn(), DeleteQuery.byId(TEST_TABLE, "eighty-two"),
|
||||
List.of(new Parameter<>(":id", ParameterType.STRING, "eighty-two")));
|
||||
assertEquals(5L,
|
||||
customScalar(db.getConn(), CountQuery.all(TEST_TABLE), List.of(), Long.class, Results::toCount),
|
||||
"There should still have been 5 documents in the table");
|
||||
}
|
||||
|
||||
public static void scalar(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(3L,
|
||||
customScalar(db.getConn(), String.format("SELECT 3 AS it FROM %s LIMIT 1", TEST_TABLE), List.of(),
|
||||
Long.class, Results::toCount),
|
||||
"The number 3 should have been returned");
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.DocumentIndex;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
|
||||
|
||||
final public class DefinitionFunctions {
|
||||
|
||||
public static void ensuresATable(ThrowawayDatabase db) throws DocumentException {
|
||||
assertFalse(db.dbObjectExists("ensured"), "The 'ensured' table should not exist");
|
||||
assertFalse(db.dbObjectExists("idx_ensured_key"), "The PK index for the 'ensured' table should not exist");
|
||||
ensureTable(db.getConn(), "ensured");
|
||||
assertTrue(db.dbObjectExists("ensured"), "The 'ensured' table should exist");
|
||||
assertTrue(db.dbObjectExists("idx_ensured_key"), "The PK index for the 'ensured' table should now exist");
|
||||
}
|
||||
|
||||
public static void ensuresAFieldIndex(ThrowawayDatabase db) throws DocumentException {
|
||||
assertFalse(db.dbObjectExists(String.format("idx_%s_test", TEST_TABLE)), "The test index should not exist");
|
||||
ensureFieldIndex(db.getConn(), TEST_TABLE, "test", List.of("id", "category"));
|
||||
assertTrue(db.dbObjectExists(String.format("idx_%s_test", TEST_TABLE)), "The test index should now exist");
|
||||
}
|
||||
|
||||
public static void ensureDocumentIndexFull(ThrowawayDatabase db) throws DocumentException {
|
||||
assertFalse(db.dbObjectExists("doc_table"), "The 'doc_table' table should not exist");
|
||||
ensureTable(db.getConn(), "doc_table");
|
||||
assertTrue(db.dbObjectExists("doc_table"), "The 'doc_table' table should exist");
|
||||
assertFalse(db.dbObjectExists("idx_doc_table_document"), "The document index should not exist");
|
||||
ensureDocumentIndex(db.getConn(), "doc_table", DocumentIndex.FULL);
|
||||
assertTrue(db.dbObjectExists("idx_doc_table_document"), "The document index should exist");
|
||||
}
|
||||
|
||||
public static void ensureDocumentIndexOptimized(ThrowawayDatabase db) throws DocumentException {
|
||||
assertFalse(db.dbObjectExists("doc_table"), "The 'doc_table' table should not exist");
|
||||
ensureTable(db.getConn(), "doc_table");
|
||||
assertTrue(db.dbObjectExists("doc_table"), "The 'doc_table' table should exist");
|
||||
assertFalse(db.dbObjectExists("idx_doc_table_document"), "The document index should not exist");
|
||||
ensureDocumentIndex(db.getConn(), "doc_table", DocumentIndex.OPTIMIZED);
|
||||
assertTrue(db.dbObjectExists("idx_doc_table_document"), "The document index should exist");
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.Field;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
|
||||
|
||||
final public class DeleteFunctions {
|
||||
|
||||
public static void byIdMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should be 5 documents in the table");
|
||||
deleteById(db.getConn(), TEST_TABLE, "four");
|
||||
assertEquals(4L, countAll(db.getConn(), TEST_TABLE), "There should now be 4 documents in the table");
|
||||
}
|
||||
|
||||
public static void byIdNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should be 5 documents in the table");
|
||||
deleteById(db.getConn(), TEST_TABLE, "negative four");
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should still be 5 documents in the table");
|
||||
}
|
||||
|
||||
public static void byFieldsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should be 5 documents in the table");
|
||||
deleteByFields( db.getConn(), TEST_TABLE, List.of(Field.notEqual("value", "purple")));
|
||||
assertEquals(2L, countAll(db.getConn(), TEST_TABLE), "There should now be 2 documents in the table");
|
||||
}
|
||||
|
||||
public static void byFieldsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should be 5 documents in the table");
|
||||
deleteByFields(db.getConn(), TEST_TABLE, List.of(Field.equal("value", "crimson")));
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should still be 5 documents in the table");
|
||||
}
|
||||
|
||||
public static void byContainsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should be 5 documents in the table");
|
||||
deleteByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple"));
|
||||
assertEquals(3L, countAll(db.getConn(), TEST_TABLE), "There should now be 3 documents in the table");
|
||||
}
|
||||
|
||||
public static void byContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should be 5 documents in the table");
|
||||
deleteByContains(db.getConn(), TEST_TABLE, Map.of("target", "acquired"));
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should still be 5 documents in the table");
|
||||
}
|
||||
|
||||
public static void byJsonPathMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should be 5 documents in the table");
|
||||
deleteByJsonPath(db.getConn(), TEST_TABLE, "$.value ? (@ == \"purple\")");
|
||||
assertEquals(3L, countAll(db.getConn(), TEST_TABLE), "There should now be 3 documents in the table");
|
||||
}
|
||||
|
||||
public static void byJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should be 5 documents in the table");
|
||||
deleteByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 100)");
|
||||
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should still be 5 documents in the table");
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.AutoId;
|
||||
import solutions.bitbadger.documents.Configuration;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.Field;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
|
||||
|
||||
final public class DocumentFunctions {
|
||||
|
||||
public static void insertDefault(ThrowawayDatabase db) throws DocumentException {
|
||||
assertEquals(0L, countAll(db.getConn(), TEST_TABLE), "There should be no documents in the table");
|
||||
insert(db.getConn(), TEST_TABLE, new JsonDocument("turkey", "", 0, new SubDocument("gobble", "gobble!")));
|
||||
final List<JsonDocument> after = findAll(db.getConn(), TEST_TABLE, JsonDocument.class);
|
||||
assertEquals(1, after.size(), "There should be one document in the table");
|
||||
final JsonDocument doc = after.get(0);
|
||||
assertEquals("turkey", doc.getId(), "The inserted document's ID is incorrect");
|
||||
assertEquals("", doc.getValue(), "The inserted document's value is incorrect");
|
||||
assertEquals(0, doc.getNumValue(), "The document's numeric value is incorrect");
|
||||
assertNotNull(doc.getSub(), "The inserted document's subdocument was not created");
|
||||
assertEquals("gobble", doc.getSub().getFoo(), "The subdocument's \"foo\" property is incorrect");
|
||||
assertEquals("gobble!", doc.getSub().getBar(), "The subdocument's \"bar\" property is incorrect");
|
||||
}
|
||||
|
||||
public static void insertDupe(ThrowawayDatabase db) throws DocumentException {
|
||||
insert(db.getConn(), TEST_TABLE, new JsonDocument("a", "", 0));
|
||||
assertThrows(DocumentException.class, () -> insert(db.getConn(), TEST_TABLE, new JsonDocument("a", "b", 22)),
|
||||
"Inserting a document with a duplicate key should have thrown an exception");
|
||||
}
|
||||
|
||||
public static void insertNumAutoId(ThrowawayDatabase db) throws DocumentException {
|
||||
try {
|
||||
Configuration.autoIdStrategy = AutoId.NUMBER;
|
||||
Configuration.idField = "key";
|
||||
assertEquals(0L, countAll(db.getConn(), TEST_TABLE), "There should be no documents in the table");
|
||||
|
||||
insert(db.getConn(), TEST_TABLE, new NumIdDocument(0, "one"));
|
||||
insert(db.getConn(), TEST_TABLE, new NumIdDocument(0, "two"));
|
||||
insert(db.getConn(), TEST_TABLE, new NumIdDocument(77, "three"));
|
||||
insert(db.getConn(), TEST_TABLE, new NumIdDocument(0, "four"));
|
||||
|
||||
final List<NumIdDocument> after = findAll(db.getConn(), TEST_TABLE, NumIdDocument.class,
|
||||
List.of(Field.named("key")));
|
||||
assertEquals(4, after.size(), "There should have been 4 documents returned");
|
||||
assertEquals("1|2|77|78",
|
||||
after.stream().map(doc -> String.valueOf(doc.getKey()))
|
||||
.reduce((acc, item) -> String.format("%s|%s", acc, item)).get(),
|
||||
"The IDs were not generated correctly");
|
||||
} finally {
|
||||
Configuration.autoIdStrategy = AutoId.DISABLED;
|
||||
Configuration.idField = "id";
|
||||
}
|
||||
}
|
||||
|
||||
public static void insertUUIDAutoId(ThrowawayDatabase db) throws DocumentException {
|
||||
try {
|
||||
Configuration.autoIdStrategy = AutoId.UUID;
|
||||
assertEquals(0L, countAll(db.getConn(), TEST_TABLE), "There should be no documents in the table");
|
||||
|
||||
insert(db.getConn(), TEST_TABLE, new JsonDocument(""));
|
||||
|
||||
final List<JsonDocument> after = findAll(db.getConn(), TEST_TABLE, JsonDocument.class);
|
||||
assertEquals(1, after.size(), "There should have been 1 document returned");
|
||||
assertEquals(32, after.get(0).getId().length(), "The ID was not generated correctly");
|
||||
} finally {
|
||||
Configuration.autoIdStrategy = AutoId.DISABLED;
|
||||
}
|
||||
}
|
||||
|
||||
public static void insertStringAutoId(ThrowawayDatabase db) throws DocumentException {
|
||||
try {
|
||||
Configuration.autoIdStrategy = AutoId.RANDOM_STRING;
|
||||
assertEquals(0L, countAll(db.getConn(), TEST_TABLE), "There should be no documents in the table");
|
||||
|
||||
insert(db.getConn(), TEST_TABLE, new JsonDocument(""));
|
||||
|
||||
Configuration.idStringLength = 21;
|
||||
insert(db.getConn(), TEST_TABLE, new JsonDocument(""));
|
||||
|
||||
final List<JsonDocument> after = findAll(db.getConn(), TEST_TABLE, JsonDocument.class);
|
||||
assertEquals(2, after.size(), "There should have been 2 documents returned");
|
||||
assertEquals(16, after.get(0).getId().length(), "The first document's ID was not generated correctly");
|
||||
assertEquals(21, after.get(1).getId().length(), "The second document's ID was not generated correctly");
|
||||
} finally {
|
||||
Configuration.autoIdStrategy = AutoId.DISABLED;
|
||||
Configuration.idStringLength = 16;
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
save(db.getConn(), TEST_TABLE, new JsonDocument("two", "", 44));
|
||||
final Optional<JsonDocument> tryDoc = findById(db.getConn(), TEST_TABLE, "two", JsonDocument.class);
|
||||
assertTrue(tryDoc.isPresent(), "There should have been a document returned");
|
||||
final JsonDocument doc = tryDoc.get();
|
||||
assertEquals("two", doc.getId(), "An incorrect document was returned");
|
||||
assertEquals("", doc.getValue(), "The \"value\" field was not updated");
|
||||
assertEquals(44, doc.getNumValue(), "The \"numValue\" field was not updated");
|
||||
assertNull(doc.getSub(), "The \"sub\" field was not updated");
|
||||
}
|
||||
|
||||
public static void saveNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final JsonDocument toSave = new JsonDocument("test");
|
||||
toSave.setSub(new SubDocument("a", "b"));
|
||||
save(db.getConn(), TEST_TABLE, toSave);
|
||||
assertTrue(findById(db.getConn(), TEST_TABLE, "test", JsonDocument.class).isPresent(),
|
||||
"The test document should have been saved");
|
||||
}
|
||||
|
||||
public static void updateMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
update(db.getConn(), TEST_TABLE, "one", new JsonDocument("one", "howdy", 8, new SubDocument("y", "z")));
|
||||
final Optional<JsonDocument> tryDoc = findById(db.getConn(), TEST_TABLE, "one", JsonDocument.class);
|
||||
assertTrue(tryDoc.isPresent(), "There should have been a document returned");
|
||||
final JsonDocument doc = tryDoc.get();
|
||||
assertEquals("one", doc.getId(), "An incorrect document was returned");
|
||||
assertEquals("howdy", doc.getValue(), "The \"value\" field was not updated");
|
||||
assertEquals(8, doc.getNumValue(), "The \"numValue\" field was not updated");
|
||||
assertNotNull(doc.getSub(), "The sub-document should not be null");
|
||||
assertEquals("y", doc.getSub().getFoo(), "The sub-document \"foo\" field was not updated");
|
||||
assertEquals("z", doc.getSub().getBar(), "The sub-document \"bar\" field was not updated");
|
||||
}
|
||||
|
||||
public static void updateNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(existsById(db.getConn(), TEST_TABLE, "two-hundred"));
|
||||
update(db.getConn(), TEST_TABLE, "two-hundred", new JsonDocument("two-hundred", "", 200));
|
||||
assertFalse(existsById(db.getConn(), TEST_TABLE, "two-hundred"));
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.Field;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
|
||||
|
||||
final public class ExistsFunctions {
|
||||
|
||||
public static void byIdMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertTrue(existsById(db.getConn(), TEST_TABLE, "three"), "The document with ID \"three\" should exist");
|
||||
}
|
||||
|
||||
public static void byIdNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(existsById(db.getConn(), TEST_TABLE, "seven"), "The document with ID \"seven\" should not exist");
|
||||
}
|
||||
|
||||
public static void byFieldsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertTrue(existsByFields(db.getConn(), TEST_TABLE, List.of(Field.equal("numValue", 10))),
|
||||
"Matching documents should have been found");
|
||||
}
|
||||
|
||||
public static void byFieldsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(existsByFields(db.getConn(), TEST_TABLE, List.of(Field.equal("nothing", "none"))),
|
||||
"No matching documents should have been found");
|
||||
}
|
||||
|
||||
public static void byContainsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertTrue(existsByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple")),
|
||||
"Matching documents should have been found");
|
||||
}
|
||||
|
||||
public static void byContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(existsByContains(db.getConn(), TEST_TABLE, Map.of("value", "violet")),
|
||||
"Matching documents should not have been found");
|
||||
}
|
||||
|
||||
public static void byJsonPathMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertTrue(existsByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ == 10)"),
|
||||
"Matching documents should have been found");
|
||||
}
|
||||
|
||||
public static void byJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(existsByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ == 10.1)"),
|
||||
"Matching documents should not have been found");
|
||||
}
|
||||
}
|
@ -0,0 +1,279 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.Configuration;
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.Field;
|
||||
import solutions.bitbadger.documents.FieldMatch;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
|
||||
|
||||
final public class FindFunctions {
|
||||
|
||||
private static String docIds(List<JsonDocument> docs) {
|
||||
return docs.stream().map(JsonDocument::getId).reduce((acc, docId) -> String.format("%s|%s", acc, docId)).get();
|
||||
}
|
||||
|
||||
public static void allDefault(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(5, findAll(db.getConn(), TEST_TABLE, JsonDocument.class).size(),
|
||||
"There should have been 5 documents returned");
|
||||
}
|
||||
|
||||
public static void allAscending(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findAll(db.getConn(), TEST_TABLE, JsonDocument.class,
|
||||
List.of(Field.named("id")));
|
||||
assertEquals(5, docs.size(), "There should have been 5 documents returned");
|
||||
assertEquals("five|four|one|three|two", docIds(docs), "The documents were not ordered correctly");
|
||||
}
|
||||
|
||||
public static void allDescending(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findAll(db.getConn(), TEST_TABLE, JsonDocument.class,
|
||||
List.of(Field.named("id DESC")));
|
||||
assertEquals(5, docs.size(), "There should have been 5 documents returned");
|
||||
assertEquals("two|three|one|four|five", docIds(docs), "The documents were not ordered correctly");
|
||||
}
|
||||
|
||||
public static void allNumOrder(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findAll(db.getConn(), TEST_TABLE, JsonDocument.class,
|
||||
List.of(Field.named("sub.foo NULLS LAST"), Field.named("n:numValue")));
|
||||
assertEquals(5, docs.size(), "There should have been 5 documents returned");
|
||||
assertEquals("two|four|one|three|five", docIds(docs), "The documents were not ordered correctly");
|
||||
}
|
||||
|
||||
public static void allEmpty(ThrowawayDatabase db) throws DocumentException {
|
||||
assertEquals(0, findAll(db.getConn(), TEST_TABLE, JsonDocument.class).size(),
|
||||
"There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void byIdString(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findById(db.getConn(), TEST_TABLE, "two", JsonDocument.class);
|
||||
assertTrue(doc.isPresent(), "The document should have been returned");
|
||||
assertEquals("two", doc.get().getId(), "An incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void byIdNumber(ThrowawayDatabase db) throws DocumentException {
|
||||
Configuration.idField = "key";
|
||||
try {
|
||||
insert(db.getConn(), TEST_TABLE, new NumIdDocument(18, "howdy"));
|
||||
final Optional<NumIdDocument> doc = findById(db.getConn(), TEST_TABLE, 18, NumIdDocument.class);
|
||||
assertTrue(doc.isPresent(), "The document should have been returned");
|
||||
} finally {
|
||||
Configuration.idField = "id";
|
||||
}
|
||||
}
|
||||
|
||||
public static void byIdNotFound(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(findById(db.getConn(), TEST_TABLE, "x", JsonDocument.class).isPresent(),
|
||||
"There should have been no document returned");
|
||||
}
|
||||
|
||||
public static void byFieldsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.any("value", List.of("blue", "purple")), Field.exists("sub")), JsonDocument.class,
|
||||
FieldMatch.ALL);
|
||||
assertEquals(1, docs.size(), "There should have been a document returned");
|
||||
assertEquals("four", docs.get(0).getId(), "The incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void byFieldsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findByFields(db.getConn(), TEST_TABLE, List.of(Field.equal("value", "purple")),
|
||||
JsonDocument.class, null, List.of(Field.named("id")));
|
||||
assertEquals(2, docs.size(), "There should have been 2 documents returned");
|
||||
assertEquals("five|four", docIds(docs), "The documents were not ordered correctly");
|
||||
}
|
||||
|
||||
public static void byFieldsMatchNumIn(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.any("numValue", List.of(2, 4, 6, 8))), JsonDocument.class);
|
||||
assertEquals(1, docs.size(), "There should have been a document returned");
|
||||
assertEquals("three", docs.get(0).getId(), "The incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void byFieldsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(0,
|
||||
findByFields(db.getConn(), TEST_TABLE, List.of(Field.greater("numValue", 100)), JsonDocument.class)
|
||||
.size(),
|
||||
"There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void byFieldsMatchInArray(ThrowawayDatabase db) throws DocumentException {
|
||||
for (final ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
|
||||
final List<ArrayDocument> docs = findByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.inArray("values", TEST_TABLE, List.of("c"))), ArrayDocument.class);
|
||||
assertEquals(2, docs.size(), "There should have been two documents returned");
|
||||
assertTrue(List.of("first", "second").contains(docs.get(0).getId()),
|
||||
String.format("An incorrect document was returned (%s)", docs.get(0).getId()));
|
||||
assertTrue(List.of("first", "second").contains(docs.get(1).getId()),
|
||||
String.format("An incorrect document was returned (%s)", docs.get(1).getId()));
|
||||
}
|
||||
|
||||
public static void byFieldsNoMatchInArray(ThrowawayDatabase db) throws DocumentException {
|
||||
for (final ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
|
||||
assertEquals(0,
|
||||
findByFields(db.getConn(), TEST_TABLE, List.of(Field.inArray("values", TEST_TABLE, List.of("j"))),
|
||||
ArrayDocument.class).size(),
|
||||
"There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void byContainsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple"),
|
||||
JsonDocument.class);
|
||||
assertEquals(2, docs.size(), "There should have been 2 documents returned");
|
||||
assertTrue(List.of("four", "five").contains(docs.get(0).getId()),
|
||||
String.format("An incorrect document was returned (%s)", docs.get(0).getId()));
|
||||
assertTrue(List.of("four", "five").contains(docs.get(1).getId()),
|
||||
String.format("An incorrect document was returned (%s)", docs.get(1).getId()));
|
||||
}
|
||||
|
||||
public static void byContainsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findByContains(db.getConn(), TEST_TABLE, Map.of("sub", Map.of("foo", "green")),
|
||||
JsonDocument.class, List.of(Field.named("value")));
|
||||
assertEquals(2, docs.size(), "There should have been 2 documents returned");
|
||||
assertEquals("two|four", docIds(docs), "The documents were not ordered correctly");
|
||||
}
|
||||
|
||||
public static void byContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(0, findByContains(db.getConn(), TEST_TABLE, Map.of("value", "indigo"), JsonDocument.class).size(),
|
||||
"There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void byJsonPathMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 10)",
|
||||
JsonDocument.class);
|
||||
assertEquals(2, docs.size(), "There should have been 2 documents returned");
|
||||
assertTrue(List.of("four", "five").contains(docs.get(0).getId()),
|
||||
String.format("An incorrect document was returned (%s)", docs.get(0).getId()));
|
||||
assertTrue(List.of("four", "five").contains(docs.get(1).getId()),
|
||||
String.format("An incorrect document was returned (%s)", docs.get(1).getId())); }
|
||||
|
||||
public static void byJsonPathMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final List<JsonDocument> docs = findByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 10)",
|
||||
JsonDocument.class, List.of(Field.named("id")));
|
||||
assertEquals(2, docs.size(), "There should have been 2 documents returned");
|
||||
assertEquals("five|four", docIds(docs), "The documents were not ordered correctly");
|
||||
}
|
||||
|
||||
public static void byJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertEquals(0, findByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 100)", JsonDocument.class).size(),
|
||||
"There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void firstByFieldsMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.equal("value", "another")), JsonDocument.class);
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertEquals("two", doc.get().getId(), "The incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void firstByFieldsMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.equal("sub.foo", "green")), JsonDocument.class);
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertTrue(List.of("two", "four").contains(doc.get().getId()),
|
||||
String.format("An incorrect document was returned (%s)", doc.get().getId()));
|
||||
}
|
||||
|
||||
public static void firstByFieldsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.equal("sub.foo", "green")), JsonDocument.class, null,
|
||||
List.of(Field.named("n:numValue DESC")));
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertEquals("four", doc.get().getId(), "An incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void firstByFieldsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(findFirstByFields(db.getConn(), TEST_TABLE, List.of(Field.equal("value", "absent")),
|
||||
JsonDocument.class).isPresent(),
|
||||
"There should have been no document returned");
|
||||
}
|
||||
|
||||
public static void firstByContainsMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByContains(db.getConn(), TEST_TABLE, Map.of("value", "FIRST!"),
|
||||
JsonDocument.class);
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertEquals("one", doc.get().getId(), "An incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void firstByContainsMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple"),
|
||||
JsonDocument.class);
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertTrue(List.of("four", "five").contains(doc.get().getId()),
|
||||
String.format("An incorrect document was returned (%s)", doc.get().getId()));
|
||||
}
|
||||
|
||||
public static void firstByContainsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple"),
|
||||
JsonDocument.class, List.of(Field.named("sub.bar NULLS FIRST")));
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertEquals("five", doc.get().getId(), "An incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void firstByContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(findFirstByContains(db.getConn(), TEST_TABLE, Map.of("value", "indigo"), JsonDocument.class)
|
||||
.isPresent(),
|
||||
"There should have been no document returned");
|
||||
}
|
||||
|
||||
public static void firstByJsonPathMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ == 10)",
|
||||
JsonDocument.class);
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertEquals("two", doc.get().getId(), "An incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void firstByJsonPathMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 10)",
|
||||
JsonDocument.class);
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertTrue(List.of("four", "five").contains(doc.get().getId()),
|
||||
String.format("An incorrect document was returned (%s)", doc.get().getId()));
|
||||
}
|
||||
|
||||
public static void firstByJsonPathMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final Optional<JsonDocument> doc = findFirstByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 10)",
|
||||
JsonDocument.class, List.of(Field.named("id DESC")));
|
||||
assertTrue(doc.isPresent(), "There should have been a document returned");
|
||||
assertEquals("four", doc.get().getId(), "An incorrect document was returned");
|
||||
}
|
||||
|
||||
public static void firstByJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
assertFalse(findFirstByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 100)", JsonDocument.class)
|
||||
.isPresent(),
|
||||
"There should have been no document returned");
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.DocumentException;
|
||||
import solutions.bitbadger.documents.java.Document;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
|
||||
public class JsonDocument {
|
||||
|
||||
private String id;
|
||||
private String value;
|
||||
private int numValue;
|
||||
private SubDocument sub;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getNumValue() {
|
||||
return numValue;
|
||||
}
|
||||
|
||||
public void setNumValue(int numValue) {
|
||||
this.numValue = numValue;
|
||||
}
|
||||
|
||||
public SubDocument getSub() {
|
||||
return sub;
|
||||
}
|
||||
|
||||
public void setSub(SubDocument sub) {
|
||||
this.sub = sub;
|
||||
}
|
||||
|
||||
public JsonDocument(String id, String value, int numValue, SubDocument sub) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
this.numValue = numValue;
|
||||
this.sub = sub;
|
||||
}
|
||||
|
||||
public JsonDocument(String id, String value, int numValue) {
|
||||
this(id, value, numValue, null);
|
||||
}
|
||||
|
||||
public JsonDocument(String id) {
|
||||
this(id, "", 0, null);
|
||||
}
|
||||
|
||||
public JsonDocument() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
private static final List<JsonDocument> testDocuments = List.of(
|
||||
new JsonDocument("one", "FIRST!", 0),
|
||||
new JsonDocument("two", "another", 10, new SubDocument("green", "blue")),
|
||||
new JsonDocument("three", "", 4),
|
||||
new JsonDocument("four", "purple", 17, new SubDocument("green", "red")),
|
||||
new JsonDocument("five", "purple", 18));
|
||||
|
||||
public static void load(ThrowawayDatabase db, String tableName) {
|
||||
try {
|
||||
for (JsonDocument doc : testDocuments) {
|
||||
Document.insert(tableName, doc, db.getConn());
|
||||
}
|
||||
} catch (DocumentException ex) {
|
||||
fail("Could not load test documents", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void load(ThrowawayDatabase db) {
|
||||
load(db, TEST_TABLE);
|
||||
}
|
||||
|
||||
/** Document ID <code>one</code> as a JSON string */
|
||||
public static String one = "{\"id\":\"one\",\"value\":\"FIRST!\",\"numValue\":0,\"sub\":null}";
|
||||
|
||||
/** Document ID <code>two</code> as a JSON string */
|
||||
public static String two = "{\"id\":\"two\",\"value\":\"another\",\"numValue\":10,"
|
||||
+ "\"sub\":{\"foo\":\"green\",\"bar\":\"blue\"}}";
|
||||
|
||||
/** Document ID <code>three</code> as a JSON string */
|
||||
public static String three = "{\"id\":\"three\",\"value\":\"\",\"numValue\":4,\"sub\":null}";
|
||||
|
||||
/** Document ID <code>four</code> as a JSON string */
|
||||
public static String four = "{\"id\":\"four\",\"value\":\"purple\",\"numValue\":17,"
|
||||
+ "\"sub\":{\"foo\":\"green\",\"bar\":\"red\"}}";
|
||||
|
||||
/** Document ID <code>five</code> as a JSON string */
|
||||
public static String five = "{\"id\":\"five\",\"value\":\"purple\",\"numValue\":18,\"sub\":null}";
|
||||
}
|
@ -0,0 +1,761 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
import solutions.bitbadger.documents.*;
|
||||
import solutions.bitbadger.documents.core.tests.integration.ThrowawayDatabase;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
|
||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
|
||||
|
||||
/**
|
||||
* Tests for the JSON-returning functions
|
||||
* <p>
|
||||
* NOTE: PostgreSQL JSONB columns do not preserve the original JSON with which a document was stored. These tests are
|
||||
* the most complex within the library, as they have split testing based on the backing data store. The PostgreSQL tests
|
||||
* check IDs (and, in the case of ordered queries, which ones occur before which others) vs. the entire JSON string.
|
||||
* Meanwhile, SQLite stores JSON as text, and will return exactly the JSON it was given when it was originally written.
|
||||
* These tests can ensure the expected round-trip of the entire JSON string.
|
||||
*/
|
||||
final public class JsonFunctions {
|
||||
|
||||
/**
|
||||
* PostgreSQL, when returning JSONB as a string, has spaces after commas and colons delineating fields and values.
|
||||
* This function will do a crude string replacement to match the target string based on the dialect being tested.
|
||||
*
|
||||
* @param json The JSON which should be returned
|
||||
* @return The actual expected JSON based on the database being tested
|
||||
*/
|
||||
public static String maybeJsonB(String json) throws DocumentException {
|
||||
return switch (Configuration.dialect()) {
|
||||
case SQLITE -> json;
|
||||
case POSTGRESQL -> json.replace("\":", "\": ").replace(",\"", ", \"");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a snippet of JSON to find a document ID
|
||||
*
|
||||
* @param id The ID of the document
|
||||
* @return A connection-aware ID to check for presence and positioning
|
||||
*/
|
||||
private static String docId(String id) throws DocumentException {
|
||||
return maybeJsonB(String.format("{\"id\":\"%s\"", id));
|
||||
}
|
||||
|
||||
private static void checkAllDefault(String json) throws DocumentException {
|
||||
assertTrue(json.startsWith("["), "JSON should start with '[' ($json)");
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertTrue(json.contains(JsonDocument.one),
|
||||
String.format("Document 'one' not found in JSON (%s)", json));
|
||||
assertTrue(json.contains(JsonDocument.two),
|
||||
String.format("Document 'two' not found in JSON (%s)", json));
|
||||
assertTrue(json.contains(JsonDocument.three),
|
||||
String.format("Document 'three' not found in JSON (%s)", json));
|
||||
assertTrue(json.contains(JsonDocument.four),
|
||||
String.format("Document 'four' not found in JSON (%s)", json));
|
||||
assertTrue(json.contains(JsonDocument.five),
|
||||
String.format("Document 'five' not found in JSON (%s)", json));
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("one")), String.format("Document 'one' not found in JSON (%s)", json));
|
||||
assertTrue(json.contains(docId("two")), String.format("Document 'two' not found in JSON (%s)", json));
|
||||
assertTrue(json.contains(docId("three")),
|
||||
String.format("Document 'three' not found in JSON (%s)", json));
|
||||
assertTrue(json.contains(docId("four")), String.format("Document 'four' not found in JSON (%s)", json));
|
||||
assertTrue(json.contains(docId("five")), String.format("Document 'five' not found in JSON (%s)", json));
|
||||
break;
|
||||
}
|
||||
assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)");
|
||||
}
|
||||
|
||||
public static void allDefault(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkAllDefault(jsonAll(db.getConn(), TEST_TABLE));
|
||||
}
|
||||
|
||||
public static void writeAllDefault(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonAll(db.getConn(), TEST_TABLE, writer);
|
||||
checkAllDefault(output.toString());
|
||||
}
|
||||
|
||||
private static void checkAllEmpty(String json) {
|
||||
assertEquals("[]", json, "There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void allEmpty(ThrowawayDatabase db) throws DocumentException {
|
||||
checkAllEmpty(jsonAll(db.getConn(), TEST_TABLE));
|
||||
}
|
||||
|
||||
public static void writeAllEmpty(ThrowawayDatabase db) throws DocumentException {
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonAll(db.getConn(), TEST_TABLE, writer);
|
||||
checkAllEmpty(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByIdString(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(JsonDocument.two, json, "An incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("two")), String.format("An incorrect document was returned (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void byIdString(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByIdString(jsonById(db.getConn(), TEST_TABLE, "two"));
|
||||
}
|
||||
|
||||
public static void writeByIdString(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonById(db.getConn(), TEST_TABLE, writer, "two");
|
||||
checkByIdString(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByIdNumber(String json) throws DocumentException {
|
||||
assertEquals(maybeJsonB("{\"key\":18,\"text\":\"howdy\"}"), json,
|
||||
"The document should have been found by numeric ID");
|
||||
}
|
||||
|
||||
public static void byIdNumber(ThrowawayDatabase db) throws DocumentException {
|
||||
Configuration.idField = "key";
|
||||
try {
|
||||
insert(db.getConn(), TEST_TABLE, new NumIdDocument(18, "howdy"));
|
||||
checkByIdNumber(jsonById(db.getConn(), TEST_TABLE, 18));
|
||||
} finally {
|
||||
Configuration.idField = "id";
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeByIdNumber(ThrowawayDatabase db) throws DocumentException {
|
||||
Configuration.idField = "key";
|
||||
try {
|
||||
insert(db.getConn(), TEST_TABLE, new NumIdDocument(18, "howdy"));
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonById(db.getConn(), TEST_TABLE, writer, 18);
|
||||
checkByIdNumber(output.toString());
|
||||
} finally {
|
||||
Configuration.idField = "id";
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkByIdNotFound(String json) {
|
||||
assertEquals("{}", json, "There should have been no document returned");
|
||||
}
|
||||
|
||||
public static void byIdNotFound(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByIdNotFound(jsonById(db.getConn(), TEST_TABLE, "x"));
|
||||
}
|
||||
|
||||
public static void writeByIdNotFound(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonById(db.getConn(), TEST_TABLE, writer, "x");
|
||||
checkByIdNotFound(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByFieldsMatch(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(String.format("[%s]", JsonDocument.four), json, "The incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.startsWith("["), String.format("JSON should start with '[' (%s)", json));
|
||||
assertTrue(json.contains(docId("four")),
|
||||
String.format("The incorrect document was returned (%s)", json));
|
||||
assertTrue(json.endsWith("]"), String.format("JSON should end with ']' (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void byFieldsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByFieldsMatch(jsonByFields(db.getConn(), TEST_TABLE, List.of(Field.any("value", List.of("blue", "purple")),
|
||||
Field.exists("sub")), FieldMatch.ALL));
|
||||
}
|
||||
|
||||
public static void writeByFieldsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.any("value", List.of("blue", "purple")),
|
||||
Field.exists("sub")), FieldMatch.ALL);
|
||||
checkByFieldsMatch(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByFieldsMatchOrdered(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(String.format("[%s,%s]", JsonDocument.five, JsonDocument.four), json,
|
||||
"The documents were not ordered correctly");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
final int fiveIdx = json.indexOf(docId("five"));
|
||||
final int fourIdx = json.indexOf(docId("four"));
|
||||
assertTrue(json.startsWith("["), String.format("JSON should start with '[' (%s)", json));
|
||||
assertTrue(fiveIdx >= 0, String.format("Document 'five' not found (%s)", json));
|
||||
assertTrue(fourIdx >= 0, String.format("Document 'four' not found (%s)", json));
|
||||
assertTrue(fiveIdx < fourIdx,
|
||||
String.format("Document 'five' should have been before 'four' (%s)", json));
|
||||
assertTrue(json.endsWith("]"), String.format("JSON should end with ']' (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void byFieldsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByFieldsMatchOrdered(jsonByFields(db.getConn(), TEST_TABLE, List.of(Field.equal("value", "purple")), null,
|
||||
List.of(Field.named("id"))));
|
||||
}
|
||||
|
||||
public static void writeByFieldsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.equal("value", "purple")), null,
|
||||
List.of(Field.named("id")));
|
||||
checkByFieldsMatchOrdered(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByFieldsMatchNumIn(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(String.format("[%s]", JsonDocument.three), json, "The incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.startsWith("["), String.format("JSON should start with '[' (%s)", json));
|
||||
assertTrue(json.contains(docId("three")),
|
||||
String.format("The incorrect document was returned (%s)", json));
|
||||
assertTrue(json.endsWith("]"), String.format("JSON should end with ']' (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void byFieldsMatchNumIn(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByFieldsMatchNumIn(jsonByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.any("numValue", List.of(2, 4, 6, 8)))));
|
||||
}
|
||||
|
||||
public static void writeByFieldsMatchNumIn(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.any("numValue", List.of(2, 4, 6, 8))));
|
||||
checkByFieldsMatchNumIn(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByFieldsNoMatch(String json) {
|
||||
assertEquals("[]", json, "There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void byFieldsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByFieldsNoMatch(jsonByFields(db.getConn(), TEST_TABLE, List.of(Field.greater("numValue", 100))));
|
||||
}
|
||||
|
||||
public static void writeByFieldsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.greater("numValue", 100)));
|
||||
checkByFieldsNoMatch(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByFieldsMatchInArray(String json) throws DocumentException {
|
||||
assertTrue(json.startsWith("["), String.format("JSON should start with '[' (%s)", json));
|
||||
assertTrue(json.contains(docId("first")), String.format("The 'first' document was not found (%s)", json));
|
||||
assertTrue(json.contains(docId("second")), String.format("The 'second' document was not found (%s)", json));
|
||||
assertTrue(json.endsWith("]"), String.format("JSON should end with ']' (%s)", json));
|
||||
}
|
||||
|
||||
public static void byFieldsMatchInArray(ThrowawayDatabase db) throws DocumentException {
|
||||
for (ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
|
||||
checkByFieldsMatchInArray(jsonByFields(db.getConn(), TEST_TABLE, List.of(Field.inArray("values", TEST_TABLE,
|
||||
List.of("c")))));
|
||||
}
|
||||
|
||||
public static void writeByFieldsMatchInArray(ThrowawayDatabase db) throws DocumentException {
|
||||
for (ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.inArray("values", TEST_TABLE, List.of("c"))));
|
||||
checkByFieldsMatchInArray(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByFieldsNoMatchInArray(String json) {
|
||||
assertEquals("[]", json, "There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void byFieldsNoMatchInArray(ThrowawayDatabase db) throws DocumentException {
|
||||
for (ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
|
||||
checkByFieldsNoMatchInArray(jsonByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.inArray("values", TEST_TABLE, List.of("j")))));
|
||||
}
|
||||
|
||||
public static void writeByFieldsNoMatchInArray(ThrowawayDatabase db) throws DocumentException {
|
||||
for (ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.inArray("values", TEST_TABLE, List.of("j"))));
|
||||
checkByFieldsNoMatchInArray(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByContainsMatch(String json) throws DocumentException {
|
||||
assertTrue(json.startsWith("["), String.format("JSON should start with '[' (%s)", json));
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertTrue(json.contains(JsonDocument.four), String.format("Document 'four' not found (%s)", json));
|
||||
assertTrue(json.contains(JsonDocument.five), String.format("Document 'five' not found (%s)", json));
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("four")), String.format("Document 'four' not found (%s)", json));
|
||||
assertTrue(json.contains(docId("five")), String.format("Document 'five' not found (%s)", json));
|
||||
break;
|
||||
}
|
||||
assertTrue(json.endsWith("]"), String.format("JSON should end with ']' (%s)", json));
|
||||
}
|
||||
|
||||
public static void byContainsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByContainsMatch(jsonByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple")));
|
||||
}
|
||||
|
||||
public static void writeByContainsMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByContains(db.getConn(), TEST_TABLE, writer, Map.of("value", "purple"));
|
||||
checkByContainsMatch(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByContainsMatchOrdered(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(String.format("[%s,%s]", JsonDocument.two, JsonDocument.four), json,
|
||||
"The documents were not ordered correctly");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
final int twoIdx = json.indexOf(docId("two"));
|
||||
final int fourIdx = json.indexOf(docId("four"));
|
||||
assertTrue(json.startsWith("["), String.format("JSON should start with '[' (%s)", json));
|
||||
assertTrue(twoIdx >= 0, String.format("Document 'two' not found (%s)", json));
|
||||
assertTrue(fourIdx >= 0, String.format("Document 'four' not found (%s)", json));
|
||||
assertTrue(twoIdx < fourIdx, String.format("Document 'two' should have been before 'four' (%s)", json));
|
||||
assertTrue(json.endsWith("]"), String.format("JSON should end with ']' (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void byContainsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByContainsMatchOrdered(jsonByContains(db.getConn(), TEST_TABLE, Map.of("sub", Map.of("foo", "green")),
|
||||
List.of(Field.named("value"))));
|
||||
}
|
||||
|
||||
public static void writeByContainsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByContains(db.getConn(), TEST_TABLE, writer, Map.of("sub", Map.of("foo", "green")),
|
||||
List.of(Field.named("value")));
|
||||
checkByContainsMatchOrdered(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByContainsNoMatch(String json) {
|
||||
assertEquals("[]", json, "There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void byContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByContainsNoMatch(jsonByContains(db.getConn(), TEST_TABLE, Map.of("value", "indigo")));
|
||||
}
|
||||
|
||||
public static void writeByContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByContains(db.getConn(), TEST_TABLE, writer, Map.of("value", "indigo"));
|
||||
checkByContainsNoMatch(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByJsonPathMatch(String json) throws DocumentException {
|
||||
assertTrue(json.startsWith("["), String.format("JSON should start with '[' (%s)", json));
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertTrue(json.contains(JsonDocument.four), String.format("Document 'four' not found (%s)", json));
|
||||
assertTrue(json.contains(JsonDocument.five), String.format("Document 'five' not found (%s)", json));
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("four")), String.format("Document 'four' not found (%s)", json));
|
||||
assertTrue(json.contains(docId("five")), String.format("Document 'five' not found (%s)", json));
|
||||
break;
|
||||
}
|
||||
assertTrue(json.endsWith("]"), String.format("JSON should end with ']' (%s)", json));
|
||||
}
|
||||
|
||||
public static void byJsonPathMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByJsonPathMatch(jsonByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 10)"));
|
||||
}
|
||||
|
||||
public static void writeByJsonPathMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByJsonPath(db.getConn(), TEST_TABLE, writer, "$.numValue ? (@ > 10)");
|
||||
checkByJsonPathMatch(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByJsonPathMatchOrdered(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(String.format("[%s,%s]", JsonDocument.five, JsonDocument.four), json,
|
||||
"The documents were not ordered correctly");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
final int fiveIdx = json.indexOf(docId("five"));
|
||||
final int fourIdx = json.indexOf(docId("four"));
|
||||
assertTrue(json.startsWith("["), String.format("JSON should start with '[' (%s)", json));
|
||||
assertTrue(fiveIdx >= 0, String.format("Document 'five' not found (%s)", json));
|
||||
assertTrue(fourIdx >= 0, String.format("Document 'four' not found (%s)", json));
|
||||
assertTrue(fiveIdx < fourIdx,
|
||||
String.format("Document 'five' should have been before 'four' (%s)", json));
|
||||
assertTrue(json.endsWith("]"), String.format("JSON should end with ']' (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void byJsonPathMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByJsonPathMatchOrdered(jsonByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 10)",
|
||||
List.of(Field.named("id"))));
|
||||
}
|
||||
|
||||
public static void writeByJsonPathMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByJsonPath(db.getConn(), TEST_TABLE, writer, "$.numValue ? (@ > 10)", List.of(Field.named("id")));
|
||||
checkByJsonPathMatchOrdered(output.toString());
|
||||
}
|
||||
|
||||
private static void checkByJsonPathNoMatch(String json) {
|
||||
assertEquals("[]", json, "There should have been no documents returned");
|
||||
}
|
||||
|
||||
public static void byJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkByJsonPathNoMatch(jsonByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 100)"));
|
||||
}
|
||||
|
||||
public static void writeByJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonByJsonPath(db.getConn(), TEST_TABLE, writer, "$.numValue ? (@ > 100)");
|
||||
checkByJsonPathNoMatch(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByFieldsMatchOne(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(JsonDocument.two, json, "The incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("two")),
|
||||
String.format("The incorrect document was returned (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByFieldsMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByFieldsMatchOne(jsonFirstByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.equal("value", "another"))));
|
||||
}
|
||||
|
||||
public static void writeFirstByFieldsMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.equal("value", "another")));
|
||||
checkFirstByFieldsMatchOne(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByFieldsMatchMany(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertTrue(json.contains(JsonDocument.two) || json.contains(JsonDocument.four),
|
||||
String.format("Expected document 'two' or 'four' (%s)", json));
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("two")) || json.contains(docId("four")),
|
||||
String.format("Expected document 'two' or 'four' (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByFieldsMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByFieldsMatchMany(jsonFirstByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.equal("sub.foo", "green"))));
|
||||
}
|
||||
|
||||
public static void writeFirstByFieldsMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.equal("sub.foo", "green")));
|
||||
checkFirstByFieldsMatchMany(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByFieldsMatchOrdered(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(JsonDocument.four, json, "An incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("four")),
|
||||
String.format("An incorrect document was returned (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByFieldsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByFieldsMatchOrdered(jsonFirstByFields(db.getConn(), TEST_TABLE,
|
||||
List.of(Field.equal("sub.foo", "green")), null, List.of(Field.named("n:numValue DESC"))));
|
||||
}
|
||||
|
||||
public static void writeFirstByFieldsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.equal("sub.foo", "green")), null,
|
||||
List.of(Field.named("n:numValue DESC")));
|
||||
checkFirstByFieldsMatchOrdered(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByFieldsNoMatch(String json) {
|
||||
assertEquals("{}", json, "There should have been no document returned");
|
||||
}
|
||||
|
||||
public static void firstByFieldsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByFieldsNoMatch(jsonFirstByFields(db.getConn(), TEST_TABLE, List.of(Field.equal("value", "absent"))));
|
||||
}
|
||||
|
||||
public static void writeFirstByFieldsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByFields(db.getConn(), TEST_TABLE, writer, List.of(Field.equal("value", "absent")));
|
||||
checkFirstByFieldsNoMatch(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByContainsMatchOne(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(JsonDocument.one, json, "An incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("one")), String.format("An incorrect document was returned (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByContainsMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByContainsMatchOne(jsonFirstByContains(db.getConn(), TEST_TABLE, Map.of("value", "FIRST!")));
|
||||
}
|
||||
|
||||
public static void writeFirstByContainsMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByContains(db.getConn(), TEST_TABLE, writer, Map.of("value", "FIRST!"));
|
||||
checkFirstByContainsMatchOne(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByContainsMatchMany(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertTrue(json.contains(JsonDocument.four) || json.contains(JsonDocument.five),
|
||||
String.format("Expected document 'four' or 'five' (%s)", json));
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("four")) || json.contains(docId("five")),
|
||||
String.format("Expected document 'four' or 'five' (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByContainsMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByContainsMatchMany(jsonFirstByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple")));
|
||||
}
|
||||
|
||||
public static void writeFirstByContainsMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByContains(db.getConn(), TEST_TABLE, writer, Map.of("value", "purple"));
|
||||
checkFirstByContainsMatchMany(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByContainsMatchOrdered(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(JsonDocument.five, json, "An incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("five")),
|
||||
String.format("An incorrect document was returned (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByContainsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByContainsMatchOrdered(jsonFirstByContains(db.getConn(), TEST_TABLE, Map.of("value", "purple"),
|
||||
List.of(Field.named("sub.bar NULLS FIRST"))));
|
||||
}
|
||||
|
||||
public static void writeFirstByContainsMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByContains(db.getConn(), TEST_TABLE, writer, Map.of("value", "purple"),
|
||||
List.of(Field.named("sub.bar NULLS FIRST")));
|
||||
checkFirstByContainsMatchOrdered(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByContainsNoMatch(String json) {
|
||||
assertEquals("{}", json, "There should have been no document returned");
|
||||
}
|
||||
|
||||
public static void firstByContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByContainsNoMatch(jsonFirstByContains(db.getConn(), TEST_TABLE, Map.of("value", "indigo")));
|
||||
}
|
||||
|
||||
public static void writeFirstByContainsNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByContains(db.getConn(), TEST_TABLE, writer, Map.of("value", "indigo"));
|
||||
checkFirstByContainsNoMatch(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByJsonPathMatchOne(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(JsonDocument.two, json, "An incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("two")), String.format("An incorrect document was returned (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByJsonPathMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByJsonPathMatchOne(jsonFirstByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ == 10)"));
|
||||
}
|
||||
|
||||
public static void writeFirstByJsonPathMatchOne(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByJsonPath(db.getConn(), TEST_TABLE, writer, "$.numValue ? (@ == 10)");
|
||||
checkFirstByJsonPathMatchOne(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByJsonPathMatchMany(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertTrue(json.contains(JsonDocument.four) || json.contains(JsonDocument.five),
|
||||
String.format("Expected document 'four' or 'five' (%s)", json));
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("four")) || json.contains(docId("five")),
|
||||
String.format("Expected document 'four' or 'five' (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByJsonPathMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByJsonPathMatchMany(jsonFirstByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 10)"));
|
||||
}
|
||||
|
||||
public static void writeFirstByJsonPathMatchMany(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByJsonPath(db.getConn(), TEST_TABLE, writer, "$.numValue ? (@ > 10)");
|
||||
checkFirstByJsonPathMatchMany(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByJsonPathMatchOrdered(String json) throws DocumentException {
|
||||
switch (Configuration.dialect()) {
|
||||
case SQLITE:
|
||||
assertEquals(JsonDocument.four, json, "An incorrect document was returned");
|
||||
break;
|
||||
case POSTGRESQL:
|
||||
assertTrue(json.contains(docId("four")),
|
||||
String.format("An incorrect document was returned (%s)", json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void firstByJsonPathMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByJsonPathMatchOrdered(jsonFirstByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 10)",
|
||||
List.of(Field.named("id DESC"))));
|
||||
}
|
||||
|
||||
public static void writeFirstByJsonPathMatchOrdered(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByJsonPath(db.getConn(), TEST_TABLE, writer, "$.numValue ? (@ > 10)",
|
||||
List.of(Field.named("id DESC")));
|
||||
checkFirstByJsonPathMatchOrdered(output.toString());
|
||||
}
|
||||
|
||||
private static void checkFirstByJsonPathNoMatch(String json) {
|
||||
assertEquals("{}", json, "There should have been no document returned");
|
||||
}
|
||||
|
||||
public static void firstByJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
checkFirstByJsonPathNoMatch(jsonFirstByJsonPath(db.getConn(), TEST_TABLE, "$.numValue ? (@ > 100)"));
|
||||
}
|
||||
|
||||
public static void writeFirstByJsonPathNoMatch(ThrowawayDatabase db) throws DocumentException {
|
||||
JsonDocument.load(db);
|
||||
final StringWriter output = new StringWriter();
|
||||
final PrintWriter writer = new PrintWriter(output);
|
||||
writeJsonFirstByJsonPath(db.getConn(), TEST_TABLE, writer, "$.numValue ? (@ > 100)");
|
||||
checkFirstByJsonPathNoMatch(output.toString());
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package solutions.bitbadger.documents.core.tests.java.integration;
|
||||
|
||||
public class NumIdDocument {
|
||||
|
||||
private int key;
|
||||
private String text;
|
||||
|
||||
public int getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(int key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public NumIdDocument(int key, String text) {
|
||||
this.key = key;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public NumIdDocument() {
|
||||
this(0, "");
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user