├── .gitattributes ├── .github └── workflows │ └── cicd.yml ├── .gitignore ├── .java-version ├── LICENSE ├── README.md ├── build.gradle.kts ├── example ├── .gitattributes ├── .gitignore ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ └── java │ └── com │ └── github │ └── nwillc │ └── funjdbc │ └── example │ ├── Example.java │ ├── database │ ├── Database.java │ └── PersonTable.java │ └── model │ └── Person.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── java │ └── com │ │ └── github │ │ └── nwillc │ │ └── funjdbc │ │ ├── DbAccessor.java │ │ ├── ResultSetIterator.java │ │ ├── SqlStatement.java │ │ ├── UncheckedSQLException.java │ │ ├── functions │ │ ├── ConnectionProvider.java │ │ ├── Enricher.java │ │ ├── Extractor.java │ │ ├── ThrowingBiConsumer.java │ │ ├── ThrowingBiFunction.java │ │ ├── ThrowingFunction.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── utils │ │ ├── Closer.java │ │ ├── EFactory.java │ │ ├── ResultSetStream.java │ │ ├── Throwables.java │ │ └── package-info.java └── pages │ ├── README.md │ ├── docs │ ├── LICENSE.md │ └── example.md │ ├── index.html │ └── sidebar.md └── test └── java └── com └── github └── nwillc └── funjdbc ├── DbAccessorQueryCloseTest.java ├── DbAccessorTest.java ├── ResultSetIteratorTest.java ├── SqlStatementTest.java ├── TestDbInitialization.java ├── UncheckedSQLExceptionTest.java ├── functions ├── EnricherTest.java └── ThrowingBiConsumerTest.java ├── performance └── PerfTest.java └── utils ├── CloserTest.java ├── EFactoryTest.java ├── ResultSetStreamTest.java └── ThrowablesTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | gradlew text eol=lf 2 | *.sh text eol=lf 3 | *.bat eol=crlf 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/cicd.yml: -------------------------------------------------------------------------------- 1 | 2 | name: CICD 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-java@v1 15 | with: 16 | java-version: 1.8 17 | 18 | - name: Assemble 19 | run: ./gradlew assemble 20 | 21 | - name: Check 22 | run: ./gradlew check -x pmdTest 23 | 24 | - name: Upload coverage to Codecov 25 | uses: codecov/codecov-action@v1 26 | 27 | - name: Publish Releases 28 | env: 29 | BINTRAY_USER: ${{ secrets.BINTRAY_USER }} 30 | BINTRAY_API_KEY: ${{ secrets.BINTRAY_API_KEY }} 31 | run: ./gradlew bintrayUpload 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | .gradle 4 | *.i?? 5 | out 6 | .settings 7 | .project 8 | .classpath 9 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, nwillc@gmail.com 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fun-jdbc 2 | 3 | Java JDBC utility code employing JDK 8+ features (streams, lambdas, unchecked exceptions, etc) to reduce JDBC boilerplate code. 4 | 5 | ## Home 6 | 7 | Fun JDBC's [landing page](http://nwillc.github.io/fun-jdbc/) is the best place to go for more information. 8 | 9 | ------ 10 | [![Coverage](https://codecov.io/gh/nwillc/fun-jdbc/branch/master/graphs/badge.svg?branch=master)](https://codecov.io/gh/nwillc/fun-jdbc) 11 | [![license](https://img.shields.io/github/license/nwillc/fun-jdbc.svg)](https://tldrlegal.com/license/-isc-license) 12 | [![Build Status](https://github.com/nwillc/fun-jdbc/workflows/CICD/badge.svg)](https://github.com/nwillc/fun-jdbc/actions?query=workflow%3ACICD) 13 | [![Download](https://api.bintray.com/packages/nwillc/maven/fun-jdbc/images/download.svg)](https://bintray.com/nwillc/maven/fun-jdbc/_latestVersion) 14 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | import com.jfrog.bintray.gradle.tasks.BintrayUploadTask 18 | import com.jfrog.bintray.gradle.BintrayExtension.PackageConfig 19 | 20 | plugins { 21 | java 22 | idea 23 | `maven-publish` 24 | jacoco 25 | pmd 26 | id("com.github.nwillc.vplugin") version "3.0.5" 27 | id("com.jfrog.bintray") version "1.8.5" 28 | id("org.ajoberstar.git-publish") version "3.0.0-rc.1" 29 | } 30 | 31 | group = "com.github.nwillc" 32 | version = "1.0.1-SNAPSHOT" 33 | 34 | val archivesBaseName = "fun-jdbc" 35 | val almost_functional_version = "1.9.7" 36 | val assertj_version = "3.9.1" 37 | val embedded_db_junit_version = "2.0.0" 38 | val jdk_contract_version = "1.9.2" 39 | val jmockit_version = "1.39" 40 | val junit_version = "5.5.0-RC2" 41 | val tinylog_version = "1.3.6" 42 | val funkjdbc_test_version = "0.12.0" 43 | val publication_name = "maven" 44 | 45 | repositories { 46 | jcenter() 47 | } 48 | 49 | dependencies { 50 | implementation("com.github.nwillc:almost-functional:$almost_functional_version") 51 | implementation("org.tinylog:tinylog:$tinylog_version") 52 | 53 | testImplementation("junit:junit:4.13") 54 | testImplementation("org.assertj:assertj-core:$assertj_version") 55 | testImplementation("org.jmockit:jmockit:$jmockit_version") 56 | testImplementation("org.zapodot:embedded-db-junit:$embedded_db_junit_version") 57 | 58 | testImplementation("com.github.nwillc:jdk_contract_tests:$jdk_contract_version") { 59 | exclude(module = "junit-jupiter-api") 60 | } 61 | } 62 | 63 | val sourceJar by tasks.registering(Jar::class) { 64 | archiveClassifier.set("sources") 65 | from(sourceSets.main.get().allSource) 66 | } 67 | 68 | val javadocJar by tasks.registering(Jar::class) { 69 | dependsOn("javadoc") 70 | archiveClassifier.set("javadoc") 71 | from("build/docs/javadoc") 72 | } 73 | 74 | publishing { 75 | publications { 76 | create(publication_name) { 77 | groupId = "${project.group}" 78 | artifactId = project.name 79 | version = "${project.version}" 80 | 81 | artifact(sourceJar.get()) 82 | artifact(javadocJar.get()) 83 | from(components["java"]) 84 | } 85 | } 86 | } 87 | 88 | bintray { 89 | user = System.getenv("BINTRAY_USER") 90 | key = System.getenv("BINTRAY_API_KEY") 91 | dryRun = false 92 | publish = true 93 | setPublications(publication_name) 94 | pkg( 95 | delegateClosureOf { 96 | repo = publication_name 97 | name = project.name 98 | desc = "Java 8 functional JDBC utility code, applying some of Java\\'s newer features to reduce JDBC boilerplate code" 99 | websiteUrl = "https://github.com/nwillc/fun-jdbc" 100 | issueTrackerUrl = "https://github.com/nwillc/fun-jdbc/issues" 101 | vcsUrl = "https://github.com/nwillc/fun-jdbc.git" 102 | version.vcsTag = "v${project.version}" 103 | setLicenses("ISC") 104 | setLabels("jdk8", "JDBC") 105 | publicDownloadNumbers = true 106 | } 107 | ) 108 | } 109 | 110 | gitPublish { 111 | repoUri.set("git@github.com:nwillc/fun-jdbc.git") 112 | branch.set("gh-pages") 113 | repoDir.set(File("$buildDir/somewhereelse")) 114 | contents { 115 | from("src/main/pages") 116 | from("build/docs/javadoc") { 117 | into("javadoc") 118 | } 119 | } 120 | } 121 | 122 | tasks { 123 | withType { 124 | useJUnit() 125 | testLogging { 126 | showStandardStreams = true 127 | events("passed", "failed", "skipped") 128 | } 129 | } 130 | withType { 131 | dependsOn("test") 132 | reports { 133 | xml.isEnabled = true 134 | html.isEnabled = true 135 | } 136 | } 137 | withType { 138 | onlyIf { 139 | if (project.version.toString().contains('-')) { 140 | logger.lifecycle("Version ${project.version} is not a release version - skipping upload.") 141 | false 142 | } else { 143 | true 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /example/.gitattributes: -------------------------------------------------------------------------------- 1 | gradlew text eol=lf 2 | *.sh text eol=lf 3 | *.bat eol=crlf 4 | 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | .gradle 4 | *.i?? 5 | 6 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | To build and run simply: 4 | 5 | $ ./gradlew clean run 6 | 7 | ## What is Shown 8 | 9 | The basic example is in `example/src/main/java/com/github/nwillc/funjdbc/example/Example.java` and shows: 10 | 11 | - Basic DbAccessor 12 | - Extractor use via the factory 13 | - Enricher use 14 | - Migrations 15 | - Query to Stream 16 | - Finding a single entity 17 | - Updating 18 | 19 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'java' 3 | apply plugin: 'application' 4 | 5 | repositories { 6 | mavenLocal() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | compile 'org.slf4j:slf4j-api:1.7.21', 12 | 'com.h2database:h2:1.4.192', 13 | 'org.apache.commons:commons-dbcp2:2.1.1', 14 | 'com.github.nwillc:fun-jdbc:+' 15 | 16 | } 17 | 18 | mainClassName = 'com.github.nwillc.funjdbc.example.Example' -------------------------------------------------------------------------------- /example/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwillc/fun-jdbc/fd5318a04b8999a03dbede10a8c5562d34d0f83e/example/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 30 11:19:05 EDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip 7 | -------------------------------------------------------------------------------- /example/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /example/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /example/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This settings file was auto generated by the Gradle buildInit task 3 | * by 'nchristopher1' at '9/30/16 11:19 AM' with Gradle 3.1 4 | * 5 | * The settings file is used to specify which projects to include in your build. 6 | * In a single project build this file can be empty or even removed. 7 | * 8 | * Detailed information about configuring a multi-project build in Gradle can be found 9 | * in the user guide at https://docs.gradle.org/3.1/userguide/multi_project_builds.html 10 | */ 11 | 12 | /* 13 | // To declare projects as part of a multi-project build use the 'include' method 14 | include 'shared' 15 | include 'api' 16 | include 'services:webservice' 17 | */ 18 | 19 | rootProject.name = 'example' 20 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/nwillc/funjdbc/example/Example.java: -------------------------------------------------------------------------------- 1 | package com.github.nwillc.funjdbc.example; 2 | 3 | import com.github.nwillc.funjdbc.DbAccessor; 4 | import com.github.nwillc.funjdbc.example.database.Database; 5 | import com.github.nwillc.funjdbc.example.database.PersonTable; 6 | import com.github.nwillc.funjdbc.example.model.Person; 7 | import com.github.nwillc.funjdbc.functions.Enricher; 8 | import com.github.nwillc.funjdbc.functions.Extractor; 9 | import com.github.nwillc.funjdbc.migrate.Manager; 10 | import com.github.nwillc.funjdbc.utils.EFactory; 11 | 12 | import java.sql.Connection; 13 | import java.sql.ResultSet; 14 | import java.sql.SQLException; 15 | import java.util.Map; 16 | import java.util.Optional; 17 | import java.util.logging.Logger; 18 | import java.util.stream.Collectors; 19 | import static com.github.nwillc.funjdbc.SqlStatement.sql; 20 | 21 | class Example implements DbAccessor { 22 | private static final Logger LOGGER = Logger.getLogger(Example.class.getSimpleName()); 23 | private final Database database; 24 | 25 | private Extractor personExtractor = new EFactory() 26 | .factory(Person::new) 27 | .add(Person::setPersonId, ResultSet::getLong, "PERSON_ID") 28 | .add(Person::setGivenName, ResultSet::getString, "GIVEN_NAME") 29 | .add(Person::setFamilyName, ResultSet::getString, "FAMILY_NAME") 30 | .getExtractor(); 31 | 32 | private Enricher ageEnricher = new EFactory() 33 | .add(Person::setAge, ResultSet::getInt, "AGE") 34 | .getEnricher(); 35 | 36 | public static void main(String[] args) throws Exception { 37 | LOGGER.info("Start"); 38 | Example example = new Example(); 39 | LOGGER.info("Find persion 1"); 40 | System.out.println(example.find(1)); 41 | 42 | Map personMap; 43 | LOGGER.info("Get all people"); 44 | personMap = example.query(); 45 | personMap.values().forEach(System.out::println); 46 | LOGGER.info("Enrich with ages"); 47 | example.enrich(personMap); 48 | personMap.values().forEach(System.out::println); 49 | LOGGER.info("Wait Elmer bust be like 60..."); 50 | final Optional elmer = personMap.values().stream().filter(p -> p.getGivenName().equals("Elmer")).findFirst(); 51 | if (!elmer.isPresent()) { 52 | throw new NullPointerException("Couldn't find Elmer"); 53 | } 54 | example.setAge(elmer.get().getPersonId(), 60); 55 | example.enrich(personMap); 56 | personMap.values().forEach(System.out::println); 57 | LOGGER.info("end"); 58 | } 59 | 60 | private Example() throws Exception { 61 | database = new Database(); 62 | LOGGER.info("Create example schema using migration manager"); 63 | Manager manager = Manager.getInstance(); 64 | manager.setConnectionProvider(database); 65 | manager.enableMigrations(); 66 | manager.add(new PersonTable()); 67 | manager.doMigrations(); 68 | LOGGER.info("Schema created"); 69 | } 70 | 71 | @Override 72 | public Connection getConnection() throws SQLException { 73 | return database.getConnection(); 74 | } 75 | 76 | private Optional find(int id) throws SQLException { 77 | return dbFind(personExtractor, sql("SELECT * FROM PERSON WHERE PERSON_ID = %d", id)); 78 | } 79 | 80 | private Map query() throws SQLException { 81 | return dbQuery(personExtractor, sql("SELECT * FROM PERSON")).collect(Collectors.toMap(Person::getPersonId, person -> person)); 82 | } 83 | 84 | private void enrich(Map personMap) throws SQLException { 85 | dbEnrich(personMap, rs -> rs.getLong("PERSON_ID"), ageEnricher, sql("SELECT PERSON_ID, AGE FROM PERSON")); 86 | } 87 | 88 | private void setAge(long id, int age) throws SQLException { 89 | dbUpdate(sql("UPDATE PERSON SET AGE = %d WHERE PERSON_ID = %d", age, id)); 90 | } 91 | } -------------------------------------------------------------------------------- /example/src/main/java/com/github/nwillc/funjdbc/example/database/Database.java: -------------------------------------------------------------------------------- 1 | package com.github.nwillc.funjdbc.example.database; 2 | 3 | import com.github.nwillc.funjdbc.functions.ConnectionProvider; 4 | import org.apache.commons.dbcp2.*; 5 | import org.apache.commons.pool2.ObjectPool; 6 | import org.apache.commons.pool2.impl.GenericObjectPool; 7 | 8 | import javax.sql.DataSource; 9 | import java.sql.Connection; 10 | import java.sql.SQLException; 11 | 12 | public class Database implements ConnectionProvider { 13 | private final static String DRIVER = "org.h2.Driver"; 14 | private final static String URL = "jdbc:h2:mem:sample"; 15 | private final DataSource dataSource; 16 | 17 | public Database() throws ClassNotFoundException { 18 | Class.forName(DRIVER); 19 | dataSource = setupDataSource(URL); 20 | } 21 | 22 | @Override 23 | public Connection getConnection() throws SQLException { 24 | return dataSource.getConnection(); 25 | } 26 | 27 | private static DataSource setupDataSource(String connectURI) { 28 | ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(connectURI, null); 29 | PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null); 30 | ObjectPool connectionPool = new GenericObjectPool<>(poolableConnectionFactory); 31 | poolableConnectionFactory.setPool(connectionPool); 32 | return new PoolingDataSource<>(connectionPool); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/nwillc/funjdbc/example/database/PersonTable.java: -------------------------------------------------------------------------------- 1 | package com.github.nwillc.funjdbc.example.database; 2 | 3 | import com.github.nwillc.funjdbc.migrate.MigrationBase; 4 | import static com.github.nwillc.funjdbc.SqlStatement.sql; 5 | 6 | public class PersonTable extends MigrationBase { 7 | private static final String CREATE = "CREATE TABLE PERSON (\n" + 8 | "PERSON_ID IDENTITY,\n" + 9 | "GIVEN_NAME CHAR(25),\n" + 10 | "FAMILY_NAME CHAR(50),\n" + 11 | "AGE INTEGER\n" + 12 | ")\n"; 13 | private static final String INSERT = "INSERT INTO PERSON (GIVEN_NAME,FAMILY_NAME, AGE) VALUES ('%s','%s', %d)"; 14 | 15 | @Override 16 | public String getDescription() { 17 | return "Table representing a person"; 18 | } 19 | 20 | @Override 21 | public String getIdentifier() { 22 | return "0"; 23 | } 24 | 25 | @Override 26 | public void perform() throws Exception { 27 | dbUpdate(sql(CREATE)); 28 | dbUpdate(sql(INSERT, "Wile", "Coyote", 25)); 29 | dbUpdate(sql(INSERT, "Elmer", "Fud", 50)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/nwillc/funjdbc/example/model/Person.java: -------------------------------------------------------------------------------- 1 | package com.github.nwillc.funjdbc.example.model; 2 | 3 | public class Person { 4 | private long personId; 5 | private String givenName; 6 | private String familyName; 7 | private int age; 8 | 9 | public String getGivenName() { 10 | return givenName; 11 | } 12 | 13 | public void setGivenName(String givenName) { 14 | this.givenName = givenName; 15 | } 16 | 17 | public String getFamilyName() { 18 | return familyName; 19 | } 20 | 21 | public void setFamilyName(String familyName) { 22 | this.familyName = familyName; 23 | } 24 | 25 | public int getAge() { 26 | return age; 27 | } 28 | 29 | public void setAge(int age) { 30 | this.age = age; 31 | } 32 | 33 | public long getPersonId() { 34 | return personId; 35 | } 36 | 37 | public void setPersonId(long personId) { 38 | this.personId = personId; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "Person{" + 44 | "personId=" + personId + 45 | ", givenName='" + givenName + '\'' + 46 | ", familyName='" + familyName + '\'' + 47 | ((age == 0) ? "" : ", age=" + age) + 48 | '}'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwillc/fun-jdbc/fd5318a04b8999a03dbede10a8c5562d34d0f83e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "fun-jdbc" 2 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/DbAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc; 19 | 20 | import com.github.nwillc.funjdbc.functions.ConnectionProvider; 21 | import com.github.nwillc.funjdbc.functions.Enricher; 22 | import com.github.nwillc.funjdbc.functions.Extractor; 23 | import com.github.nwillc.funjdbc.functions.ThrowingFunction; 24 | import com.github.nwillc.funjdbc.utils.ResultSetStream; 25 | 26 | import java.sql.Connection; 27 | import java.sql.PreparedStatement; 28 | import java.sql.ResultSet; 29 | import java.sql.SQLException; 30 | import java.sql.Statement; 31 | import java.util.Map; 32 | import java.util.Optional; 33 | import java.util.stream.Stream; 34 | 35 | import static com.github.nwillc.funjdbc.utils.Closer.close; 36 | 37 | 38 | /** 39 | * Interface, with default methods providing JDBC database access functionality. 40 | */ 41 | @SuppressWarnings("PMD.DataflowAnomalyAnalysis") 42 | public interface DbAccessor extends ConnectionProvider { 43 | 44 | /** 45 | * Extract results from a SQL query designed to return multiple results. The SQL, and it's optional args 46 | * are formatted with String.format(String, Object ...) method. Note, Streams are Closeable, and the 47 | * resultant Stream should be closed when completed to insure database resources involved in the stream are freed. 48 | * 49 | * @param Type extracted and returned in the stream 50 | * @param sqlStatement The SQL statement 51 | * @param extractor The extractor to process the ResultSet with 52 | * @return a stream of the extracted elements 53 | * @throws SQLException if the query or an extraction fails 54 | */ 55 | default Stream dbQuery(final SqlStatement sqlStatement, final Extractor extractor) throws SQLException { 56 | return stream(extractor, 57 | c -> c.prepareStatement(sqlStatement.toString()), 58 | PreparedStatement::executeQuery); 59 | } 60 | 61 | /** 62 | * Given a map of entities, and a query that extracts details about them, then execute that query 63 | * and enrich the entities with the results. 64 | * 65 | * @param the key type 66 | * @param the entity type 67 | * @param sqlStatement The SQL statement 68 | * @param keyExtractor an Extractor to get the entity key from the detail records 69 | * @param map A map of entities the enrich 70 | * @param enricher a function to enrich an entity from the detail record 71 | * @throws SQLException may result from the query or enrichments 72 | */ 73 | default void dbEnrich(final SqlStatement sqlStatement, final Extractor keyExtractor, Map map, 74 | final Enricher enricher) throws SQLException { 75 | try (Connection connection = getConnection(); 76 | Statement statement = connection.createStatement(); 77 | ResultSet resultSet = statement.executeQuery(sqlStatement.toString())) { 78 | while (resultSet.next()) { 79 | K key = keyExtractor.extract(resultSet); 80 | if (key != null) { 81 | V value = map.get(key); 82 | if (value != null) { 83 | enricher.accept(value, resultSet); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * Extract the result from a SQL query which returns at most one result. The SQL, and it's optional args 92 | * are formatted with String.format(String, Object ...) method. 93 | * 94 | * @param Type extracted and optionally returned 95 | * @param sqlStatement the SQL statement 96 | * @param extractor the extractor to extract teh result 97 | * @return an Optional of the data 98 | * @throws SQLException if the query or extraction fails, or if multiple rows returned 99 | */ 100 | default Optional dbFind(final SqlStatement sqlStatement, final Extractor extractor) throws SQLException { 101 | try (Connection connection = getConnection(); 102 | Statement statement = connection.createStatement(); 103 | ResultSet resultSet = statement.executeQuery(sqlStatement.toString())) { 104 | 105 | if (!resultSet.next()) { 106 | return Optional.empty(); 107 | } 108 | 109 | final T result = extractor.extract(resultSet); 110 | 111 | if (resultSet.next()) { 112 | throw new SQLException("Query to find single row returned multiple."); 113 | } 114 | 115 | return Optional.of(result); 116 | } 117 | } 118 | 119 | /** 120 | * Execute a SQL update or delete. The SQL, and it's optional args 121 | * are formatted with String.format(String, Object ...) method. 122 | * 123 | * @param sqlStatement The SQL statement 124 | * @return the count of rows updated. 125 | * @throws SQLException if the update fails 126 | */ 127 | default int dbUpdate(SqlStatement sqlStatement) throws SQLException { 128 | try (Connection connection = getConnection(); 129 | Statement statement = connection.createStatement()) { 130 | return statement.executeUpdate(sqlStatement.toString()); 131 | } 132 | } 133 | 134 | /** 135 | * Execute an insert into a table with an auto incremented key, returning a stream of the keys. 136 | * 137 | * @param key type of generated keys for each tuple. 138 | * @param sqlStatement The SQL statement 139 | * @param keyExtractor The key extractor 140 | * @param keys The keys 141 | * @return the stream of keys generated 142 | * @throws SQLException if the insert failed 143 | * @since 0.13.0 144 | */ 145 | default Stream dbInsertGetGeneratedKeys(SqlStatement sqlStatement, Extractor keyExtractor, String[] keys) throws SQLException { 146 | return stream(keyExtractor, 147 | c -> c.prepareStatement(sqlStatement.toString(), keys), 148 | s -> { 149 | s.execute(); 150 | return s.getGeneratedKeys(); 151 | }); 152 | } 153 | 154 | /** 155 | * Execute a SQL statement. 156 | * 157 | * @param sqlStatement The SQL statement 158 | * @return true if the first result is a ResultSet 159 | * object; false if it is an update count or there are 160 | * no results 161 | * @throws SQLException if the SQL is invalid 162 | * @since 0.9.3 163 | */ 164 | default boolean dbExecute(SqlStatement sqlStatement) throws SQLException { 165 | try (Connection connection = getConnection(); 166 | Statement statement = connection.createStatement()) { 167 | return statement.execute(sqlStatement.toString()); 168 | } 169 | } 170 | 171 | /** 172 | * Return the results of a sql execution as a stream of an extracted type. 173 | * 174 | * @param extractor Function to extract data from the ResultSet 175 | * @param execution Given a Statement, execute it returning a ResultSet 176 | * @param Type of the elements in the resultant Stream 177 | * @param Type of the Statement 178 | * @param createStetement The create statement 179 | * @return A Stream of type T 180 | * @throws SQLException Should the execution have issues. 181 | * @since 0.13.1 182 | */ 183 | @SuppressWarnings("PMD.CloseResource") 184 | default Stream stream(final Extractor extractor, ThrowingFunction createStetement, ThrowingFunction execution) throws SQLException { 185 | Connection connection = null; 186 | S statement = null; 187 | ResultSet resultSet = null; 188 | 189 | // Can not try-with-resources here because if we return the stream we don't want to close things until stream done 190 | try { 191 | final Connection c = getConnection(); 192 | connection = c; 193 | final S s = createStetement.apply(c); 194 | statement = s; 195 | resultSet = execution.apply(s); 196 | return ResultSetStream.stream(resultSet, extractor) 197 | .onClose(() -> { 198 | close(s); 199 | close(c); 200 | }); 201 | } catch (Exception e) { 202 | close(statement); 203 | close(connection); 204 | close(resultSet); 205 | throw new SQLException("Query failed", e); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/ResultSetIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc; 19 | 20 | import com.github.nwillc.funjdbc.functions.Extractor; 21 | 22 | import java.sql.ResultSet; 23 | import java.sql.SQLException; 24 | import java.util.Iterator; 25 | import java.util.NoSuchElementException; 26 | import java.util.Objects; 27 | 28 | import static com.github.nwillc.funjdbc.utils.Throwables.propagate; 29 | 30 | /** 31 | * This is an Iterator that traverses a ResultSet returning elements via an Extractor. Additionally this 32 | * implements AutoCloseable, and support for adding other onClose Runnables. 33 | * 34 | * @param The type of elements being extracted 35 | */ 36 | @SuppressWarnings("PMD.BeanMembersShouldSerialize") 37 | public final class ResultSetIterator implements Iterator, AutoCloseable { 38 | private final ResultSet resultSet; 39 | private final Extractor extractor; 40 | private Runnable closers = () -> { }; 41 | private Boolean nextAvailable = null; 42 | 43 | /** 44 | * Create an instance with with a ResultSet to iterate over, and an Extractor to apply to 45 | * each row. 46 | * 47 | * @param resultSet the ResultSet to iterate over 48 | * @param extractor the Extractor to apply to each row 49 | */ 50 | public ResultSetIterator(final ResultSet resultSet, final Extractor extractor) { 51 | Objects.requireNonNull(resultSet, "A non null result set is required."); 52 | Objects.requireNonNull(extractor, "A non null extractor is required"); 53 | this.extractor = extractor; 54 | this.resultSet = resultSet; 55 | } 56 | 57 | @Override 58 | public boolean hasNext() { 59 | if (nextAvailable == null) { 60 | try { 61 | nextAvailable = resultSet.next(); 62 | } catch (Exception e) { 63 | throw propagate(e); 64 | } 65 | } 66 | return nextAvailable; 67 | } 68 | 69 | @SuppressWarnings("PMD.NullAssignment") 70 | @Override 71 | public T next() { 72 | if (!hasNext()) { 73 | throw new NoSuchElementException(); 74 | } 75 | nextAvailable = null; 76 | try { 77 | return extractor.extract(resultSet); 78 | } catch (Exception e) { 79 | throw propagate(e); 80 | } 81 | } 82 | 83 | /** 84 | * Add a Runnable to be invoked when this instance is closed. Runnables will be invoked in the order they are added. 85 | * 86 | * @param runnable a runnable 87 | * @return this instance 88 | */ 89 | public ResultSetIterator onClose(final Runnable runnable) { 90 | Runnable previous = closers; 91 | closers = () -> { 92 | previous.run(); 93 | runnable.run(); 94 | }; 95 | return this; 96 | } 97 | 98 | @Override 99 | public void close() throws SQLException { 100 | resultSet.close(); 101 | closers.run(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/SqlStatement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc; 19 | 20 | 21 | import org.pmw.tinylog.Logger; 22 | 23 | /** 24 | * A SQL statement comprised of a template SQL string, which is a {@link java.util.Formatter} string, and 25 | * the arguments to pass to it. 26 | * 27 | * @since 0.9.0 28 | */ 29 | @SuppressWarnings("PMD.BeanMembersShouldSerialize") 30 | public final class SqlStatement { 31 | private final String sqlString; 32 | private Object[] args; 33 | 34 | public SqlStatement(String sqlString, Object... args) { 35 | this.sqlString = sqlString; 36 | setArgs(args); 37 | } 38 | 39 | public void setArgs(Object... args) { 40 | this.args = args; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | final String formatted = (args == null || args.length == 0) ? sqlString : String.format(sqlString, args); 46 | Logger.debug("Formatted SQL: {}", formatted); 47 | return formatted; 48 | } 49 | 50 | public static SqlStatement sql(String sql, Object... args) { 51 | return new SqlStatement(sql, args); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/UncheckedSQLException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc; 19 | 20 | import java.sql.SQLException; 21 | import java.util.Optional; 22 | 23 | /** 24 | * An unchecked version of SQLException. Maintains the errorCode and sqlState of the SQLException. 25 | */ 26 | public class UncheckedSQLException extends RuntimeException { 27 | static final long serialVersionUID = 1L; 28 | 29 | public UncheckedSQLException(Throwable cause) { 30 | super(cause.getMessage(), cause); 31 | } 32 | 33 | public UncheckedSQLException(String message, Throwable cause) { 34 | super(message, cause); 35 | } 36 | 37 | /** 38 | * If the underlying cause provides an error code, provides it. 39 | * 40 | * @return an optional error code, empty if none available. 41 | */ 42 | public Optional getErrorCode() { 43 | final Throwable cause = getCause(); 44 | if (cause != null) { 45 | if (cause instanceof SQLException) { 46 | return Optional.of(((SQLException) cause).getErrorCode()); 47 | } 48 | if (cause instanceof UncheckedSQLException) { 49 | return ((UncheckedSQLException) cause).getErrorCode(); 50 | } 51 | } 52 | 53 | return Optional.empty(); 54 | } 55 | 56 | 57 | /** 58 | * If underlying cause provides a SQL state, provide it. 59 | * 60 | * @return an optional SQL state, empty if none available. 61 | */ 62 | public Optional getSqlState() { 63 | final Throwable cause = getCause(); 64 | if (cause != null) { 65 | if (cause instanceof SQLException) { 66 | return Optional.ofNullable(((SQLException) cause).getSQLState()); 67 | } 68 | if (cause instanceof UncheckedSQLException) { 69 | return ((UncheckedSQLException) cause).getSqlState(); 70 | } 71 | } 72 | 73 | return Optional.empty(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/functions/ConnectionProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.functions; 19 | 20 | import java.sql.Connection; 21 | import java.sql.SQLException; 22 | 23 | /** 24 | * An interface to indicate that an object can provide JDBC connections. 25 | */ 26 | @FunctionalInterface 27 | public interface ConnectionProvider { 28 | /** 29 | * Get a JDBC database connection. 30 | * 31 | * @return A valid database connection 32 | * @throws SQLException if a connection can not be returned 33 | */ 34 | Connection getConnection() throws SQLException; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/functions/Enricher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.functions; 19 | 20 | import com.github.nwillc.funjdbc.utils.EFactory; 21 | 22 | import java.sql.ResultSet; 23 | import java.sql.SQLException; 24 | import java.util.Objects; 25 | 26 | /** 27 | * Enrich an object with values from the current row of a result set. 28 | * 29 | * @since 0.8.0 30 | */ 31 | @FunctionalInterface 32 | public interface Enricher extends ThrowingBiConsumer { 33 | void acceptThrows(B bean, ResultSet rs) throws SQLException; 34 | 35 | /** 36 | * Chains another enricher to this one to be called upon completion. 37 | * 38 | * @param after another enricher 39 | * @return the enriched object 40 | * @see EFactory 41 | * @since 0.8.5 42 | */ 43 | default Enricher andThen(Enricher after) { 44 | Objects.requireNonNull(after); 45 | 46 | return (l, r) -> { 47 | accept(l, r); 48 | after.accept(l, r); 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/functions/Extractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.functions; 19 | 20 | import com.github.nwillc.funjdbc.utils.EFactory; 21 | 22 | import java.sql.ResultSet; 23 | import java.sql.SQLException; 24 | 25 | /** 26 | * A functional interface designed to extract a given type from a single row of a ResultSet. 27 | * 28 | * @param type to extract 29 | */ 30 | @FunctionalInterface 31 | public interface Extractor { 32 | /** 33 | * Extract type T from the current position in the ResultSet. 34 | * 35 | * @param rs the ResultSet to extract from 36 | * @return the type T extracted 37 | * @throws SQLException should the extraction fail 38 | * @see EFactory 39 | */ 40 | T extract(ResultSet rs) throws SQLException; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/functions/ThrowingBiConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.functions; 19 | 20 | import java.util.function.BiConsumer; 21 | 22 | import static com.github.nwillc.funjdbc.utils.Throwables.propagate; 23 | 24 | /** 25 | * A BiConsumer that allows for Exceptions which will be converted to appropriate RuntimeExceptions. 26 | * 27 | * @param type of the first argument to operations 28 | * @param type of second argument to operations 29 | * @since 0.8.5 30 | */ 31 | @FunctionalInterface 32 | public interface ThrowingBiConsumer extends BiConsumer { 33 | 34 | /** 35 | * The default accept allowing assignment as a normal BiConsumer. This method 36 | * will invoke the acceptThrows and correctly propagate any exception. 37 | * 38 | * @param t type of the first argument to operation 39 | * @param u type of second argument to operation 40 | */ 41 | @Override 42 | default void accept(T t, U u) { 43 | try { 44 | acceptThrows(t, u); 45 | } catch (Exception e) { 46 | throw propagate(e); 47 | } 48 | } 49 | 50 | /** 51 | * A method that accepts two arguments and allows for an Exception to be throws. 52 | * 53 | * @param t type of the first argument to operation 54 | * @param u type of second argument to operation 55 | * @throws Exception this function can throw exceptions 56 | */ 57 | @SuppressWarnings({"RedundantThrows", "EmptyMethod"}) 58 | void acceptThrows(T t, U u) throws Exception; 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/functions/ThrowingBiFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.functions; 19 | 20 | import java.util.function.BiFunction; 21 | 22 | import static com.github.nwillc.funjdbc.utils.Throwables.propagate; 23 | 24 | /** 25 | * A BiFunction that allows for exceptions, rethrowing them as an appropriate 26 | * type of RuntimeException. 27 | * 28 | * @param type of first argument to apply 29 | * @param type of second argument to apply 30 | * @param type of return value from apply 31 | * @since 0.8.4 32 | */ 33 | @FunctionalInterface 34 | public interface ThrowingBiFunction extends BiFunction { 35 | 36 | @Override 37 | default R apply(T t, U u) { 38 | try { 39 | return applyThrows(t, u); 40 | } catch (Exception e) { 41 | throw propagate(e); 42 | } 43 | } 44 | 45 | @SuppressWarnings("RedundantThrows") 46 | R applyThrows(T t, U u) throws Exception; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/functions/ThrowingFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.functions; 19 | 20 | import java.util.function.Function; 21 | 22 | import static com.github.nwillc.funjdbc.utils.Throwables.propagate; 23 | 24 | /** 25 | * A Function that allows for exceptions, rethrowing them as an appropriate 26 | * type of RuntimeException. 27 | * 28 | * @param The type of the argument 29 | * @param The type of the result 30 | * @since 0.13.1 31 | */ 32 | @FunctionalInterface 33 | public interface ThrowingFunction extends Function { 34 | @Override 35 | default R apply(T t) { 36 | try { 37 | return applyThrows(t); 38 | } catch (Exception e) { 39 | throw propagate(e); 40 | } 41 | } 42 | R applyThrows(T t) throws Exception; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/functions/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | /** 19 | * Functional interface definitions. 20 | */ 21 | package com.github.nwillc.funjdbc.functions; -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | /** 19 | * Java JDBC utility code employing JDK 8+ features (streams, lambdas, unchecked exceptions, etc) to reduce JDBC boilerplate code. 20 | * 21 | * @since 0.4 22 | */ 23 | package com.github.nwillc.funjdbc; 24 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/utils/Closer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.utils; 19 | 20 | import almost.functional.utils.LogFactory; 21 | 22 | import java.util.logging.Logger; 23 | 24 | /** 25 | * Utility class for closing an AutoCloseable without throwing an Exceptions. 26 | */ 27 | public final class Closer { 28 | private static final Logger LOGGER = LogFactory.getLogger(); 29 | 30 | private Closer() { 31 | } 32 | 33 | /** 34 | * Close an AutoCloseable without exception. 35 | * 36 | * @param autoCloseable resource to close. 37 | */ 38 | static public void close(AutoCloseable autoCloseable) { 39 | if (autoCloseable == null) { 40 | return; 41 | } 42 | 43 | try { 44 | autoCloseable.close(); 45 | } catch (Exception e) { 46 | LOGGER.info("Failed to close autoclosable " + e.getClass().getCanonicalName() + ": " + e.getMessage()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/utils/EFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.utils; 19 | 20 | import com.github.nwillc.funjdbc.functions.Enricher; 21 | import com.github.nwillc.funjdbc.functions.Extractor; 22 | import com.github.nwillc.funjdbc.functions.ThrowingBiFunction; 23 | 24 | import java.sql.ResultSet; 25 | import java.util.Objects; 26 | import java.util.function.BiConsumer; 27 | import java.util.function.BiFunction; 28 | import java.util.function.Supplier; 29 | 30 | /** 31 | * Create a basic Enricher or Extractor from a series of setter/getter/index tuples. 32 | * 33 | * @param the type of the Bean 34 | * @since 0.8.3+ 35 | */ 36 | @SuppressWarnings("PMD.BeanMembersShouldSerialize") 37 | public final class EFactory { 38 | private Enricher consumer = null; 39 | private Supplier factory = null; 40 | 41 | /** 42 | * Provide a factor this Extractor will use to create the object it will extract data 43 | * from the result set into. 44 | * 45 | * @param factory a factory instance 46 | * @return the extractor factory. 47 | */ 48 | public EFactory withFactory(Supplier factory) { 49 | this.factory = factory; 50 | return this; 51 | } 52 | 53 | /** 54 | * Add an extraction used by the Extractor. An extraction pulls a know type from an indexed column of ResultSet 55 | * and calls a setter with it. 56 | * 57 | * @param setter a BiConsumer that will set the value extracted 58 | * @param getter a BiFunction that will extract the value from the ResultSet 59 | * @param index the column index 60 | * @param the type 61 | * @return the factory 62 | */ 63 | public EFactory add(BiConsumer setter, ThrowingBiFunction getter, Integer index) { 64 | Objects.requireNonNull(setter); 65 | Objects.requireNonNull(getter); 66 | Objects.requireNonNull(index); 67 | final SingleEnricher singleEnricher = new SingleEnricher<>(setter, getter, index); 68 | consumer = (consumer == null) ? singleEnricher : consumer.andThen(singleEnricher); 69 | return this; 70 | } 71 | 72 | /** 73 | * Add an extraction used by the Extractor. An extraction pulls a know type from a named column of the ResultSet 74 | * and calls a setter with it. 75 | * 76 | * @param setter a BiConsumer that will set the value extracted 77 | * @param getter a BiFunction that will extract the value from the ResultSet 78 | * @param column the name of the column 79 | * @param the type 80 | * @return the factory 81 | */ 82 | public EFactory add(BiConsumer setter, ThrowingBiFunction getter, String column) { 83 | Objects.requireNonNull(setter); 84 | Objects.requireNonNull(getter); 85 | Objects.requireNonNull(column); 86 | final SingleEnricher singleEnricher = new SingleEnricher<>(setter, getter, column); 87 | consumer = (consumer == null) ? singleEnricher : consumer.andThen(singleEnricher); 88 | return this; 89 | } 90 | 91 | /** 92 | * Get the Enricher that results from the added setter, getter, pairings add. 93 | * 94 | * @return the generated enricher 95 | * @since 0.8.7 96 | */ 97 | public Enricher getEnricher() { 98 | Objects.requireNonNull(consumer, "A consumer(s) are required"); 99 | return consumer; 100 | } 101 | 102 | /** 103 | * Create the Extractor based on the factory and extractions added. 104 | * 105 | * @return the generated extractor 106 | */ 107 | public Extractor getExtractor() { 108 | Objects.requireNonNull(factory, "A non null factory is required"); 109 | Objects.requireNonNull(consumer, "A consumer(s) are required"); 110 | return new GeneratedExtractor<>(factory, consumer); 111 | } 112 | 113 | @SuppressWarnings("PMD.BeanMembersShouldSerialize") 114 | private final static class GeneratedExtractor implements Extractor { 115 | private final Supplier factory; 116 | private final BiConsumer consumer; 117 | 118 | GeneratedExtractor(Supplier factory, BiConsumer consumer) { 119 | this.factory = factory; 120 | this.consumer = consumer; 121 | } 122 | 123 | @Override 124 | public B extract(ResultSet rs) { 125 | final B bean = factory.get(); 126 | consumer.accept(bean, rs); 127 | return bean; 128 | } 129 | } 130 | 131 | @SuppressWarnings("PMD.BeanMembersShouldSerialize") 132 | private final static class SingleEnricher implements Enricher { 133 | final BiConsumer setter; 134 | final BiFunction getter; 135 | final C column; 136 | 137 | SingleEnricher(BiConsumer setter, BiFunction getter, C column) { 138 | this.setter = setter; 139 | this.getter = getter; 140 | this.column = column; 141 | } 142 | 143 | @Override 144 | public void acceptThrows(B bean, ResultSet resultSet) { 145 | setter.accept(bean, getter.apply(resultSet, column)); 146 | } 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/utils/ResultSetStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.utils; 19 | 20 | import com.github.nwillc.funjdbc.ResultSetIterator; 21 | import com.github.nwillc.funjdbc.functions.Extractor; 22 | 23 | import java.sql.ResultSet; 24 | import java.util.Spliterator; 25 | import java.util.Spliterators; 26 | import java.util.stream.Stream; 27 | import java.util.stream.StreamSupport; 28 | 29 | import static com.github.nwillc.funjdbc.utils.Closer.close; 30 | 31 | public final class ResultSetStream { 32 | private ResultSetStream() { 33 | } 34 | 35 | public static Stream stream(ResultSet resultSet, Extractor extractor) { 36 | final ResultSetIterator iterator = new ResultSetIterator<>(resultSet, extractor); 37 | return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.NONNULL), false) 38 | .onClose(() -> close(resultSet)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/utils/Throwables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.utils; 19 | 20 | import com.github.nwillc.funjdbc.UncheckedSQLException; 21 | 22 | import java.io.IOException; 23 | import java.io.UncheckedIOException; 24 | import java.sql.SQLException; 25 | 26 | /** 27 | * Utility methods for throwables. 28 | */ 29 | public final class Throwables { 30 | private Throwables() { 31 | } 32 | 33 | /** 34 | * Propagate a Throwable as a RuntimeException. The Runtime exception bases it's message on the message of the Throwable, 35 | * and the Throwable is set as it's cause. This can be used to deal with exceptions in lambdas etc. Special logic for SQLException, to 36 | * throw an {@link UncheckedSQLException}. 37 | * 38 | * @param exception the Exception to repropagate as a RuntimeException. 39 | * @return a RuntimeException 40 | */ 41 | public static RuntimeException propagate(final Exception exception) { 42 | if (SQLException.class.isAssignableFrom(exception.getClass())) { 43 | return new UncheckedSQLException("Repropagated " + exception.getMessage(), exception); 44 | } 45 | 46 | if (IOException.class.isAssignableFrom(exception.getClass())) { 47 | return new UncheckedIOException((IOException) exception); 48 | } 49 | 50 | return almost.functional.utils.Throwables.propagate(exception); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/nwillc/funjdbc/utils/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | /** 19 | * Utility classes to support java functional features in JDBC. 20 | * 21 | * @since 0.4 22 | */ 23 | package com.github.nwillc.funjdbc.utils; 24 | -------------------------------------------------------------------------------- /src/main/pages/README.md: -------------------------------------------------------------------------------- 1 | # fun-jdbc 2 | 3 | Java 8 functional JDBC utility code, applying some of Java's newer features to reduce JDBC boilerplate code. As trivial as this 4 | framework is, you get a lot of bang for about a 20k jar. 5 | 6 | ## Features 7 | - default method interface providing query, find, update etc. 8 | - ResultSet traversal as a Stream 9 | - Efficient functional approach to copying ResultSet values to model objects 10 | - Minimalist schema migration mechanism 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/pages/docs/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, nwillc@gmail.com 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /src/main/pages/docs/example.md: -------------------------------------------------------------------------------- 1 | # Example 2 | A cpmplete example can be found in the [example](https://github.com/nwillc/fun-jdbc/tree/master/example) directory of the repository. 3 | But the following is a contrived example just to give you an idea of this codes basic use. 4 | 5 | ## A Word List 6 | You're storing the words from a document in an in memory database for word count analysis. The table has the following schema: 7 | 8 | CREATE TABLE WORDS ( 9 | WORD CHAR(80) NOT NULL, 10 | COUNT INT, 11 | UNIQUE(WORD) 12 | ); 13 | 14 | You're writing a Java data access object (DAO) to allow you to do some basic work with the schema using fun-jdbc. 15 | 16 | public class Dao implements DbAccessor { 17 | private final static String DRIVER = "org.h2.Driver"; 18 | private final static String URL = "jdbc:h2:mem:"; 19 | 20 | public InMemWordsDatabase() throws ClassNotFoundException { 21 | Class.forName(DRIVER); 22 | } 23 | 24 | @Override 25 | public Connection getConnection() throws SQLException { 26 | return DriverManager.getConnection(URL); 27 | } 28 | } 29 | 30 | The above gets you most of the basics in one go. With that Dao you can do things like: 31 | 32 | void printAllWords(Dao dao) { 33 | dao.dbQuery(rs -> rs.getString("WORD"), "SELECT WORD FROM WORDS") 34 | .forEach(w -> System.out.println(w)); 35 | } 36 | 37 | Or to see if a given word appears: 38 | 39 | void boolean hasWord(Dao dao, String word) { 40 | return dao.dbFind(rs -> rs.getString("WORD"), 41 | "SELECT * FROM WORDS WHERE WORD = '%s'", word) 42 | .isPresent(); 43 | } 44 | 45 | And of course your Extractor can be more complex: 46 | 47 | public class Pair { 48 | String word; 49 | int count; 50 | 51 | public Pair() {}; 52 | 53 | public Pair(String word, int count) { 54 | this.word = word; 55 | this.count = count; 56 | } 57 | 58 | public void setWord(String str) { 59 | word = str; 60 | } 61 | 62 | public void setCount(int c) { 63 | count = c; 64 | } 65 | } 66 | 67 | void printCounts(Dao dao) { 68 | dao.dbQuery(rs -> new Pair(rs.getString("WORD"), rs.getInt("COUNT")), "SELECT WORD, COUNT FROM WORDS") 69 | .forEach(p -> System.out.println(p.word + ": " + p.count)); 70 | } 71 | 72 | Or you could use the EFactory to create an extractor: 73 | 74 | void printCounts(Dao dao) { 75 | ExtractorFactory factory = new ExtractorFactory<>(); 76 | Extractor extractor = factory 77 | .add(Pair::setWord, ResultSet::getString, "WORD") 78 | .add(Pair::setCount, ResultSet::getInt, 2) 79 | .factory(Pair::new) 80 | .getExtractor(); 81 | dao.dbQuery(extractor, "SELECT WORD, COUNT FROM WORDS") 82 | .forEach(p -> System.out.println(p.word + ": " + p.count)); 83 | } 84 | 85 | Read my blog posts for more info. 86 | -------------------------------------------------------------------------------- /src/main/pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | fun-jdbc 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/pages/sidebar.md: -------------------------------------------------------------------------------- 1 | ## FUN JDBC 2 | 3 | - [Home]() 4 | - [Example](#docs/example) 5 | - [Javadoc](javadoc) 6 | 7 | ## Links 8 | 9 | - [Issue Tracking](https://github.com/nwillc/fun-jdbc/issues) 10 | - [Blog Post 1](http://nwillc.wordpress.com/2014/09/27/a-little-java-8-goodness-in-jdbc) 11 | - [Blog Post 2](https://nwillc.wordpress.com/2016/08/28/functional-jdbc-part-2) 12 | - [Blog Post 3](https://nwillc.wordpress.com/2016/09/22/java-8-functional-jdbc-part-3) 13 | 14 | ------- 15 | [![Coverage](https://codecov.io/gh/nwillc/fun-jdbc/branch/master/graphs/badge.svg?branch=master)](https://codecov.io/gh/nwillc/fun-jdbc) 16 | [![license](https://img.shields.io/github/license/nwillc/fun-jdbc.svg)](https://tldrlegal.com/license/-isc-license) 17 | [![Build Status](https://github.com/nwillc/fun-jdbc/workflows/CICD/badge.svg)](https://github.com/nwillc/fun-jdbc/actions?query=workflow%3ACICD) 18 | [![Bintray](https://img.shields.io/bintray/v/nwillc/maven/fun-jdbc.svg)](https://bintray.com/nwillc/maven/fun-jdbc) 19 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/DbAccessorQueryCloseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc; 19 | 20 | import com.github.nwillc.funjdbc.utils.Closer; 21 | import mockit.Expectations; 22 | import mockit.integration.junit4.JMockit; 23 | import org.junit.Rule; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.zapodot.junit.db.EmbeddedDatabaseRule; 27 | 28 | import java.sql.Connection; 29 | import java.sql.SQLException; 30 | 31 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 32 | 33 | @RunWith(JMockit.class) 34 | public class DbAccessorQueryCloseTest implements DbAccessor { 35 | @Rule 36 | public final EmbeddedDatabaseRule embeddedDb = EmbeddedDatabaseRule 37 | .builder() 38 | .initializedByPlugin(new TestDbInitialization()) 39 | .build(); 40 | 41 | @Test 42 | public void testExceptionCloser() { 43 | new Expectations(Closer.class){{ 44 | Closer.close((AutoCloseable)any); times = 3; 45 | }}; 46 | assertThatThrownBy(() -> dbQuery(null, null)).isInstanceOf(SQLException.class); 47 | } 48 | 49 | @Override 50 | public Connection getConnection() { 51 | return embeddedDb.getConnection(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/DbAccessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 5 | * 6 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 7 | */ 8 | 9 | package com.github.nwillc.funjdbc; 10 | 11 | import com.github.nwillc.funjdbc.functions.Extractor; 12 | import com.github.nwillc.funjdbc.utils.EFactory; 13 | import org.junit.Before; 14 | import org.junit.Rule; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | import org.junit.runners.Parameterized; 18 | import org.junit.runners.Parameterized.Parameters; 19 | import org.zapodot.junit.db.EmbeddedDatabaseRule; 20 | 21 | import java.sql.Connection; 22 | import java.sql.ResultSet; 23 | import java.sql.SQLException; 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.Optional; 30 | import java.util.stream.Stream; 31 | 32 | import static com.github.nwillc.funjdbc.SqlStatement.sql; 33 | import static org.assertj.core.api.Assertions.assertThat; 34 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 35 | 36 | @RunWith(Parameterized.class) 37 | public class DbAccessorTest implements DbAccessor { 38 | private final static Extractor WORD_COUNT_EXTRACTOR = rs -> new WordCount(rs.getString(1)); 39 | private final Extractor wordExtractor; 40 | private boolean connectionFailed; 41 | 42 | @Rule 43 | public final EmbeddedDatabaseRule embeddedDb = EmbeddedDatabaseRule 44 | .builder() 45 | .initializedByPlugin(new TestDbInitialization()) 46 | .build(); 47 | 48 | public DbAccessorTest(Extractor extractor) { 49 | wordExtractor = extractor; 50 | } 51 | 52 | @Before 53 | public void setUp() { 54 | connectionFailed = false; 55 | } 56 | 57 | @SuppressWarnings("unchecked") 58 | @Parameters 59 | public static Collection getExtractors() { 60 | List[]> extractors = new ArrayList<>(); 61 | 62 | extractors.add(new Extractor[]{rs -> new Word(rs.getString(1))}); 63 | final Extractor extractor = new EFactory().withFactory(Word::new) 64 | .add(Word::setWord, ResultSet::getString, 1) 65 | .getExtractor(); 66 | extractors.add(new Extractor[]{extractor}); 67 | return extractors; 68 | } 69 | 70 | 71 | @Test 72 | public void testGetConnection() throws Exception { 73 | assertThat(getConnection()).isNotNull(); 74 | } 75 | 76 | @Test 77 | public void testQuery() throws Exception { 78 | Stream words = dbQuery(sql("SELECT * FROM WORDS"), wordExtractor); 79 | assertThat(words.count()).isEqualTo(3); 80 | } 81 | 82 | @Test 83 | public void testQueryWithArgs() throws Exception { 84 | Stream words = dbQuery(sql("SELECT * FROM WORDS WHERE WORD = '%s'", "a"), wordExtractor); 85 | assertThat(words.count()).isEqualTo(2); 86 | } 87 | 88 | @Test 89 | public void testFind() throws Exception { 90 | Optional word = dbFind(sql("SELECT * FROM WORDS WHERE WORD = 'b'"), wordExtractor); 91 | assertThat(word).isNotNull(); 92 | assertThat(word.isPresent()).isTrue(); 93 | assertThat(word.get().word).isEqualTo("b"); 94 | } 95 | 96 | @Test 97 | public void testFindWithArgs() throws Exception { 98 | Optional word = dbFind(sql("SELECT * FROM WORDS WHERE WORD = '%s'", "b"), wordExtractor); 99 | assertThat(word).isNotNull(); 100 | assertThat(word.isPresent()).isTrue(); 101 | assertThat(word.get().word).isEqualTo("b"); 102 | } 103 | 104 | 105 | @Test 106 | public void testNotFound() throws Exception { 107 | Optional word = dbFind(sql("SELECT * FROM WORDS WHERE WORD = 'c'"), wordExtractor); 108 | assertThat(word).isNotNull(); 109 | assertThat(word.isPresent()).isFalse(); 110 | } 111 | 112 | @Test 113 | public void testStream() throws Exception { 114 | try (Stream stream = dbQuery(sql("SELECT * FROM WORDS"), wordExtractor)) { 115 | assertThat(stream.count()).isEqualTo(3); 116 | } 117 | } 118 | 119 | @Test 120 | public void testUpdate() throws Exception { 121 | final int count = 2; 122 | final SqlStatement sql = sql("UPDATE WORDS set WORD = 'c' WHERE WORD = 'a'"); 123 | final int dbUpdated = dbUpdate(sql); 124 | assertThat(dbUpdated).isEqualTo(count); 125 | Stream words = dbQuery(sql("SELECT * FROM WORDS WHERE WORD = '%s'", "c"), wordExtractor); 126 | assertThat(words.count()).isEqualTo(count); 127 | } 128 | 129 | @Test 130 | public void testUpdateWithBadSqlException() { 131 | final SqlStatement sql = sql("blah blah"); 132 | assertThatThrownBy(() -> dbUpdate(sql)).isInstanceOf(SQLException.class); 133 | } 134 | 135 | @Test 136 | public void testUpdateWithBadConnectionException() { 137 | connectionFailed = true; 138 | final SqlStatement sql = sql("SELECT 1"); 139 | assertThatThrownBy(() -> dbUpdate(sql)).isInstanceOf(SQLException.class); 140 | } 141 | 142 | @Test(expected = SQLException.class) 143 | public void testFindFails() throws Exception { 144 | dbFind(sql("SELECT * FROM WORDS WHERE WORD = 'a'"), wordExtractor); 145 | } 146 | 147 | @Test 148 | public void shouldDbEnrich() throws Exception { 149 | Map counts = new HashMap<>(); 150 | 151 | dbQuery(sql("SELECT DISTINCT WORD FROM WORDS"), WORD_COUNT_EXTRACTOR).forEach(c -> counts.put(c.word, c)); 152 | 153 | assertThat(counts.size()).isEqualTo(2); 154 | dbEnrich(sql("SELECT WORD, COUNT(*) FROM WORDS GROUP BY WORD"), rs -> rs.getString(1), counts, 155 | (e, rs) -> e.count = rs.getInt(2) 156 | ); 157 | assertThat(counts.get("b").count).isEqualTo(1); 158 | assertThat(counts.get("a").count).isEqualTo(2); 159 | } 160 | 161 | @Test 162 | public void shouldDbEnrichNoKey() throws Exception { 163 | Map counts = new HashMap<>(); 164 | 165 | dbQuery(sql("SELECT DISTINCT WORD FROM WORDS"), WORD_COUNT_EXTRACTOR).forEach(c -> counts.put(c.word, c)); 166 | 167 | assertThat(counts.size()).isEqualTo(2); 168 | dbEnrich(sql("SELECT COUNT(*) FROM WORDS"), rs -> rs.getString(1), counts, 169 | (e, rs) -> e.count = rs.getInt(2) 170 | ); 171 | assertThat(counts.size()).isEqualTo(2); 172 | counts.values().forEach(wordCount -> assertThat(wordCount.count).isEqualTo(0)); 173 | } 174 | 175 | @Test 176 | public void shouldDbExecute() throws Exception { 177 | final SqlStatement sqlStatement = sql("SELECT 1"); 178 | assertThat(dbExecute(sqlStatement)).isTrue(); 179 | } 180 | 181 | @Test 182 | public void shouldBadSqlExecute() { 183 | final SqlStatement sqlStatement = sql("blah"); 184 | assertThatThrownBy(() -> dbExecute(sqlStatement)).isInstanceOf(SQLException.class); 185 | } 186 | 187 | @Test 188 | public void shouldDbBadConnectionExecute() { 189 | connectionFailed = true; 190 | final SqlStatement sqlStatement = sql("SELECT 1"); 191 | assertThatThrownBy(() -> dbExecute(sqlStatement)).isInstanceOf(SQLException.class); 192 | } 193 | 194 | @Test 195 | public void shouldDbEnrichNoValue() throws Exception { 196 | Map counts = new HashMap<>(); 197 | 198 | dbEnrich(sql("SELECT WORD, COUNT(*) FROM WORDS GROUP BY WORD"), rs -> rs.getString(1), counts, 199 | (e, rs) -> e.count = rs.getInt(2) 200 | ); 201 | assertThat(counts).hasSize(0); 202 | } 203 | 204 | @Test 205 | public void testGeneratedKeys() throws Exception { 206 | final SqlStatement sql = sql("INSERT INTO KEYED(WORD) VALUES('bar')"); 207 | Extractor longExtractor = rs -> rs.getLong(1); 208 | final String[] keys = {"ID"}; 209 | try (Stream keyStream = dbInsertGetGeneratedKeys(sql, longExtractor, keys)) { 210 | assertThat(keyStream.count()).isEqualTo(1L); 211 | } 212 | } 213 | 214 | @Override 215 | public Connection getConnection() throws SQLException { 216 | if (connectionFailed) { 217 | throw new SQLException("testing failed connection"); 218 | } 219 | return embeddedDb.getConnection(); 220 | } 221 | 222 | private static class Word { 223 | String word; 224 | 225 | Word() { 226 | } 227 | 228 | Word(String word) { 229 | this.word = word; 230 | } 231 | 232 | void setWord(String word) { 233 | this.word = word; 234 | } 235 | } 236 | 237 | private static class WordCount { 238 | final String word; 239 | int count; 240 | 241 | private WordCount(String word) { 242 | this.word = word; 243 | this.count = 0; 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/ResultSetIteratorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc; 19 | 20 | import com.github.nwillc.contracts.IteratorContract; 21 | import com.github.nwillc.funjdbc.functions.Extractor; 22 | import com.github.nwillc.funjdbc.utils.Closer; 23 | import mockit.Expectations; 24 | import mockit.Mocked; 25 | import mockit.integration.junit4.JMockit; 26 | import org.junit.After; 27 | import org.junit.Before; 28 | import org.junit.Rule; 29 | import org.junit.Test; 30 | import org.junit.runner.RunWith; 31 | import org.zapodot.junit.db.EmbeddedDatabaseRule; 32 | 33 | import java.sql.Connection; 34 | import java.sql.ResultSet; 35 | import java.sql.SQLException; 36 | import java.sql.Statement; 37 | import java.util.ArrayList; 38 | import java.util.Iterator; 39 | import java.util.List; 40 | import java.util.concurrent.atomic.AtomicBoolean; 41 | 42 | import static com.github.nwillc.funjdbc.utils.Closer.close; 43 | import static org.assertj.core.api.Assertions.assertThat; 44 | import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; 45 | 46 | @RunWith(JMockit.class) 47 | public class ResultSetIteratorTest extends IteratorContract implements DbAccessor { 48 | private final static Extractor wordExtractor = rs -> rs.getString("WORD"); 49 | private List> iterators; 50 | 51 | @Rule 52 | public final EmbeddedDatabaseRule embeddedDb = EmbeddedDatabaseRule 53 | .builder() 54 | .initializedByPlugin(new TestDbInitialization()) 55 | .build(); 56 | 57 | @Mocked 58 | ResultSet mockResultSet; 59 | @Mocked 60 | Extractor mockExtractor; 61 | 62 | @Before 63 | public void setUp() { 64 | iterators = new ArrayList<>(); 65 | } 66 | 67 | @After 68 | public void tearDown() { 69 | iterators.forEach(Closer::close); 70 | iterators = null; 71 | } 72 | 73 | @Override 74 | protected Iterator getNonEmptyIterator() { 75 | try { 76 | Connection connection = getConnection(); 77 | Statement statement = connection.createStatement(); 78 | ResultSet resultSet = statement.executeQuery("SELECT * FROM WORDS"); 79 | ResultSetIterator iterator = new ResultSetIterator<>(resultSet, wordExtractor).onClose(() -> { 80 | close(statement); 81 | close(connection); 82 | }); 83 | iterators.add(iterator); 84 | return iterator; 85 | } catch (SQLException e) { 86 | return null; 87 | } 88 | } 89 | 90 | @SuppressWarnings("unchecked") 91 | @Test 92 | public void testValidConstructorArgs() throws Exception { 93 | try { 94 | new ResultSetIterator(null, wordExtractor); 95 | failBecauseExceptionWasNotThrown(IllegalArgumentException.class); 96 | } catch (NullPointerException ignored) { 97 | } 98 | 99 | try (Connection connection = getConnection(); 100 | Statement statement = connection.createStatement(); 101 | ResultSet resultSet = statement.executeQuery("SELECT * FROM WORDS")) { 102 | new ResultSetIterator(resultSet, null); 103 | failBecauseExceptionWasNotThrown(IllegalArgumentException.class); 104 | } catch (NullPointerException ignored) { 105 | } 106 | } 107 | 108 | @SuppressWarnings("unchecked") 109 | @Test 110 | public void testExtractorSQLException() throws Exception { 111 | new Expectations() {{ 112 | mockResultSet.next(); 113 | result = true; 114 | mockExtractor.extract((ResultSet) any); 115 | result = new SQLException(); 116 | }}; 117 | 118 | ResultSetIterator resultSetIterator = new ResultSetIterator(mockResultSet, mockExtractor); 119 | try { 120 | resultSetIterator.next(); 121 | failBecauseExceptionWasNotThrown(RuntimeException.class); 122 | } catch (RuntimeException ignored) { 123 | } 124 | } 125 | 126 | @SuppressWarnings("unchecked") 127 | @Test 128 | public void shouldHandleResultSetExceptions() throws Exception { 129 | new Expectations() {{ 130 | mockResultSet.next(); 131 | result = new SQLException(); 132 | }}; 133 | 134 | ResultSetIterator resultSetIterator = new ResultSetIterator(mockResultSet, wordExtractor); 135 | 136 | try { 137 | resultSetIterator.hasNext(); 138 | failBecauseExceptionWasNotThrown(RuntimeException.class); 139 | } catch (RuntimeException ignored) { 140 | } 141 | } 142 | 143 | @Test 144 | public void testOnClose() throws Exception { 145 | final AtomicBoolean tattleTale = new AtomicBoolean(false); 146 | ResultSet resultSet; 147 | try (Connection connection = getConnection(); 148 | Statement statement = connection.createStatement()) { 149 | resultSet = statement.executeQuery("SELECT * FROM WORDS"); 150 | @SuppressWarnings("unchecked") ResultSetIterator resultSetIterator = new ResultSetIterator(resultSet, wordExtractor).onClose(() -> tattleTale.set(true)); 151 | resultSetIterator.close(); 152 | assertThat(tattleTale.get()).isTrue(); 153 | assertThat(resultSet.isClosed()).isTrue(); 154 | } 155 | 156 | } 157 | 158 | @Override 159 | public Connection getConnection() { 160 | return embeddedDb.getConnection(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/SqlStatementTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc; 19 | 20 | import org.junit.Test; 21 | 22 | import static com.github.nwillc.funjdbc.SqlStatement.sql; 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | 26 | public class SqlStatementTest { 27 | public static final String SELECT_1 = "SELECT 1"; 28 | 29 | @Test 30 | public void testSetArgs() { 31 | final String sql = "SELECT * FROM FOO WHERE x = '%s'"; 32 | final String x1 = "foo"; 33 | final String x2 = "bar"; 34 | final SqlStatement sqlStatement = sql(sql, x1); 35 | assertThat(sqlStatement.toString()).isEqualTo(String.format(sql, x1)); 36 | sqlStatement.setArgs(x2); 37 | assertThat(sqlStatement.toString()).isEqualTo(String.format(sql, x2)); 38 | } 39 | 40 | @Test 41 | public void testToStringNoArgs() { 42 | final String sql = "SELECT * FROM FOO"; 43 | final SqlStatement sqlStatement = sql(sql); 44 | 45 | assertThat(sqlStatement.toString()).isEqualTo(sql); 46 | } 47 | 48 | @Test 49 | public void testToStringNoArgsArray() { 50 | final String sql = "SELECT * FROM FOO"; 51 | final SqlStatement sqlStatement = sql(sql); 52 | 53 | assertThat(sqlStatement.toString()).isEqualTo(sql); 54 | } 55 | 56 | @Test 57 | public void testToStringNullArgs() { 58 | final String sql = "SELECT * FROM FOO"; 59 | final SqlStatement sqlStatement = sql(sql, (Object[])null); 60 | 61 | assertThat(sqlStatement.toString()).isEqualTo(sql); 62 | } 63 | 64 | @Test 65 | public void testToStringArgs() { 66 | final String sql = "SELECT * FROM FOO WHERE x = '%s' AND y = %d"; 67 | final String x = "foo"; 68 | final int y = 10; 69 | final SqlStatement sqlStatement = sql(sql, x, y); 70 | 71 | assertThat(sqlStatement.toString()).isEqualTo(String.format(sql, x, y)); 72 | } 73 | 74 | @Test 75 | public void testSqlStatic() { 76 | final SqlStatement sql = sql(SELECT_1); 77 | assertThat(sql).isInstanceOf(SqlStatement.class); 78 | assertThat(sql.toString()).isEqualTo(SELECT_1); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/TestDbInitialization.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 5 | * 6 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 7 | */ 8 | 9 | package com.github.nwillc.funjdbc; 10 | 11 | import org.zapodot.junit.db.plugin.InitializationPlugin; 12 | 13 | import java.sql.Connection; 14 | import java.sql.SQLException; 15 | 16 | import static com.github.nwillc.funjdbc.SqlStatement.sql; 17 | 18 | public class TestDbInitialization implements DbAccessor, InitializationPlugin { 19 | private Connection connection; 20 | 21 | @Override 22 | public void connectionMade(String name, Connection connection) { 23 | this.connection = connection; 24 | try { 25 | dbExecute(sql("CREATE TABLE WORDS ( WORD CHAR(20) )")); 26 | dbExecute(sql("INSERT INTO WORDS (WORD) VALUES ('a')")); 27 | dbExecute(sql("INSERT INTO WORDS (WORD) VALUES ('a')")); 28 | dbExecute(sql("INSERT INTO WORDS (WORD) VALUES ('b')")); 29 | dbExecute(sql("CREATE TABLE KEYED(ID BIGINT AUTO_INCREMENT, WORD CHAR(20))")); 30 | } catch (SQLException e) { 31 | throw new UncheckedSQLException(e); 32 | } 33 | } 34 | 35 | @Override 36 | public Connection getConnection() { 37 | return connection; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/UncheckedSQLExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc; 19 | 20 | import org.junit.Test; 21 | 22 | import java.sql.SQLException; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | public class UncheckedSQLExceptionTest { 27 | 28 | @Test 29 | public void testConstructorForGeneralThrowable() { 30 | final String msg = "foo"; 31 | final NullPointerException nullPointerException = new NullPointerException(); 32 | 33 | final UncheckedSQLException uncheckedSQLException = new UncheckedSQLException(msg, nullPointerException); 34 | 35 | assertThat(uncheckedSQLException.getMessage()).isEqualTo(msg); 36 | assertThat(uncheckedSQLException.getCause()).isEqualTo(nullPointerException); 37 | assertThat(uncheckedSQLException.getErrorCode().isPresent()).isFalse(); 38 | assertThat(uncheckedSQLException.getSqlState().isPresent()).isFalse(); 39 | } 40 | 41 | @Test 42 | public void testConstructorForSQLException2() { 43 | Exception e = new SQLException(); 44 | UncheckedSQLException exception = new UncheckedSQLException(e); 45 | 46 | assertThat(exception.getCause()).isEqualTo(e); 47 | } 48 | 49 | @Test 50 | public void testConstructorForSQLException() { 51 | final String msg = "foo"; 52 | final String sqlState = "bar"; 53 | final int errorCode = 42; 54 | final SQLException sqlException = new SQLException(msg, sqlState, errorCode); 55 | 56 | final UncheckedSQLException uncheckedSQLException = new UncheckedSQLException(msg, sqlException); 57 | 58 | assertThat(uncheckedSQLException.getMessage()).isEqualTo(msg); 59 | assertThat(uncheckedSQLException.getCause()).isEqualTo(sqlException); 60 | assertThat(uncheckedSQLException.getErrorCode().isPresent()).isTrue(); 61 | assertThat(uncheckedSQLException.getErrorCode().get()).isEqualTo(errorCode); 62 | assertThat(uncheckedSQLException.getSqlState().isPresent()).isTrue(); 63 | assertThat(uncheckedSQLException.getSqlState().get()).isEqualTo(sqlState); 64 | } 65 | 66 | @Test 67 | public void testWhereCauseIsUncheckedSqlException() { 68 | 69 | final String msg = "foo"; 70 | final String sqlState = "bar"; 71 | final int errorCode = 42; 72 | final SQLException sqlException = new SQLException(msg, sqlState, errorCode); 73 | 74 | final UncheckedSQLException innerException = new UncheckedSQLException("Inner message", sqlException); 75 | final UncheckedSQLException uncheckedSQLException = new UncheckedSQLException(msg, innerException); 76 | 77 | assertThat(uncheckedSQLException.getMessage()).isEqualTo(msg); 78 | assertThat(uncheckedSQLException.getCause()).isEqualTo(innerException); 79 | assertThat(uncheckedSQLException.getErrorCode().isPresent()).isTrue(); 80 | assertThat(uncheckedSQLException.getErrorCode().get()).isEqualTo(errorCode); 81 | assertThat(uncheckedSQLException.getSqlState().isPresent()).isTrue(); 82 | assertThat(uncheckedSQLException.getSqlState().get()).isEqualTo(sqlState); 83 | } 84 | 85 | @Test 86 | public void testWithoutCause() { 87 | final UncheckedSQLException uncheckedSQLException = new UncheckedSQLException("Test", null); 88 | 89 | assertThat(uncheckedSQLException).isNotNull(); 90 | assertThat(uncheckedSQLException.getErrorCode().isPresent()).isFalse(); 91 | assertThat(uncheckedSQLException.getSqlState().isPresent()).isFalse(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/functions/EnricherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.functions; 19 | 20 | import mockit.Mocked; 21 | import mockit.integration.junit4.JMockit; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | 25 | import java.sql.ResultSet; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 30 | 31 | @RunWith(JMockit.class) 32 | public class EnricherTest { 33 | @Mocked 34 | private ResultSet resultSet; 35 | 36 | @Test 37 | public void testAndThenArgumentCheck() { 38 | Enricher enricher = (o, r) -> { 39 | }; 40 | 41 | assertThatThrownBy(() -> enricher.andThen(null)).isInstanceOf(NullPointerException.class); 42 | } 43 | 44 | @Test 45 | public void testAndThen() { 46 | AtomicInteger i = new AtomicInteger(0); 47 | Enricher enricher = (o, r) -> o.addAndGet(1); 48 | enricher = enricher.andThen((o, r) -> o.addAndGet(2)); 49 | 50 | enricher.accept(i, resultSet); 51 | 52 | assertThat(i.get()).isEqualTo(3); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/functions/ThrowingBiConsumerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.functions; 19 | 20 | import mockit.Mocked; 21 | import mockit.integration.junit4.JMockit; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | 25 | import java.sql.ResultSet; 26 | import java.sql.SQLException; 27 | 28 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 29 | 30 | @RunWith(JMockit.class) 31 | public class ThrowingBiConsumerTest { 32 | @Mocked 33 | private ResultSet resultSet; 34 | 35 | @Test 36 | public void testAcceptException() { 37 | Enricher enricher = (o, r) -> { 38 | throw new SQLException("test"); 39 | }; 40 | 41 | assertThatThrownBy(() -> enricher.accept(true, resultSet)).isInstanceOf(RuntimeException.class); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/performance/PerfTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.performance; 19 | 20 | import com.github.nwillc.funjdbc.functions.Extractor; 21 | import com.github.nwillc.funjdbc.utils.EFactory; 22 | import mockit.Expectations; 23 | import mockit.Mocked; 24 | import mockit.integration.junit4.JMockit; 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | import java.sql.ResultSet; 30 | import java.sql.SQLException; 31 | import java.util.concurrent.atomic.AtomicInteger; 32 | import java.util.logging.Logger; 33 | 34 | @RunWith(JMockit.class) 35 | public class PerfTest { 36 | private final static Logger LOGGER = Logger.getLogger(PerfTest.class.getName()); 37 | private static final int LOOPS = 1_000_000; 38 | @Mocked 39 | private ResultSet resultSet; 40 | private final Extractor codedBeanExtractor = new BeanExtractor(); 41 | private Extractor generatedBeanExtractor; 42 | private final AtomicInteger counter = new AtomicInteger(0); 43 | 44 | @Before 45 | public void setup() { 46 | EFactory factory = new EFactory<>(); 47 | generatedBeanExtractor = factory 48 | .add(Bean::setDoubleValue, ResultSet::getDouble, 1) 49 | .add(Bean::setIntegerValue, ResultSet::getInt, 2) 50 | .add(Bean::setStringValue, ResultSet::getString, 3) 51 | .withFactory(Bean::new) 52 | .getExtractor(); 53 | } 54 | 55 | @Test 56 | public void loop() throws Exception { 57 | new Expectations() {{ 58 | resultSet.getInt(anyInt); 59 | result = counter.incrementAndGet(); 60 | resultSet.getString(anyInt); 61 | result = String.valueOf(counter.incrementAndGet()); 62 | resultSet.getDouble(anyInt); 63 | result = counter.incrementAndGet(); 64 | }}; 65 | System.gc(); 66 | long start = System.currentTimeMillis(); 67 | for (int i = 0; i < LOOPS; i++) { 68 | codedBeanExtractor.extract(resultSet); 69 | } 70 | LOGGER.info("Coded Extractor: " + (System.currentTimeMillis() - start)); 71 | System.gc(); 72 | start = System.currentTimeMillis(); 73 | for (int i = 0; i < LOOPS; i++) { 74 | generatedBeanExtractor.extract(resultSet); 75 | } 76 | LOGGER.info("Generated Extractor: " + (System.currentTimeMillis() - start)); 77 | } 78 | 79 | private class BeanExtractor implements Extractor { 80 | @Override 81 | public Bean extract(ResultSet rs) throws SQLException { 82 | Bean b = new Bean(); 83 | b.setDoubleValue(rs.getDouble(1)); 84 | b.setIntegerValue(rs.getInt(2)); 85 | b.setStringValue(rs.getString(3)); 86 | return b; 87 | } 88 | } 89 | 90 | private static class Bean { 91 | private Integer integerValue; 92 | private String stringValue; 93 | private double doubleValue; 94 | 95 | public Bean() { 96 | } 97 | 98 | public void setDoubleValue(double doubleValue) { 99 | this.doubleValue = doubleValue; 100 | } 101 | 102 | public void setIntegerValue(Integer integerValue) { 103 | this.integerValue = integerValue; 104 | } 105 | 106 | public void setStringValue(String stringValue) { 107 | this.stringValue = stringValue; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/utils/CloserTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.utils; 19 | 20 | import com.github.nwillc.contracts.UtilityClassContract; 21 | import mockit.Mocked; 22 | import mockit.Verifications; 23 | import mockit.integration.junit4.JMockit; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | 27 | @RunWith(JMockit.class) 28 | public class CloserTest extends UtilityClassContract { 29 | @Mocked 30 | AutoCloseable autoCloseable; 31 | 32 | 33 | @Override 34 | public Class getClassToTest() { 35 | return Closer.class; 36 | } 37 | 38 | @Test 39 | public void testHandlesNull() { 40 | Closer.close(null); 41 | } 42 | 43 | @Test 44 | public void shouldClose() throws Exception { 45 | Closer.close(autoCloseable); 46 | new Verifications() {{ 47 | autoCloseable.close(); 48 | times = 1; 49 | }}; 50 | } 51 | 52 | @Test 53 | public void testHandlesThrownException() { 54 | Closer.close(() -> { 55 | throw new Exception(); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/utils/EFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.utils; 19 | 20 | import com.github.nwillc.funjdbc.UncheckedSQLException; 21 | import com.github.nwillc.funjdbc.functions.Enricher; 22 | import com.github.nwillc.funjdbc.functions.Extractor; 23 | import mockit.Expectations; 24 | import mockit.Mocked; 25 | import mockit.integration.junit4.JMockit; 26 | import org.junit.Before; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | 30 | import java.math.BigDecimal; 31 | import java.sql.ResultSet; 32 | import java.sql.SQLException; 33 | import java.sql.Time; 34 | import java.sql.Timestamp; 35 | import java.util.Date; 36 | import java.util.concurrent.TimeUnit; 37 | 38 | import static org.assertj.core.api.Assertions.assertThat; 39 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 40 | 41 | @RunWith(JMockit.class) 42 | public class EFactoryTest { 43 | private EFactory factory; 44 | @Mocked 45 | ResultSet resultSet; 46 | 47 | @Before 48 | public void setUp() { 49 | factory = new EFactory<>(); 50 | } 51 | 52 | @Test 53 | public void testConstructorNoFactory() { 54 | assertThatThrownBy(() -> factory.getExtractor()).isInstanceOf(NullPointerException.class); 55 | } 56 | 57 | @Test 58 | public void testConstructorNoExtractor() { 59 | assertThatThrownBy(() -> factory.withFactory(Bean::new).getExtractor()).isInstanceOf(NullPointerException.class); 60 | } 61 | 62 | @Test 63 | public void testConstructorNo() { 64 | final Extractor extractor = factory 65 | .withFactory(Bean::new) 66 | .add(Bean::setOne, ResultSet::getInt, 1) 67 | .getExtractor(); 68 | assertThat(extractor).isNotNull(); 69 | } 70 | 71 | @Test 72 | public void testInteger() throws Exception { 73 | final Extractor extractor = factory.add(Bean::setOne, ResultSet::getInt, 1).withFactory(Bean::new).getExtractor(); 74 | 75 | new Expectations() {{ 76 | resultSet.getInt(1); 77 | result = 5; 78 | }}; 79 | 80 | final Bean bean = extractor.extract(resultSet); 81 | assertThat(bean.one).isEqualTo(5); 82 | } 83 | 84 | @Test(expected = UncheckedSQLException.class) 85 | public void testIntegerException() throws Exception { 86 | final Extractor extractor = factory.add(Bean::setOne, ResultSet::getInt, 1).withFactory(Bean::new).getExtractor(); 87 | 88 | new Expectations() {{ 89 | resultSet.getInt(1); 90 | result = new SQLException(); 91 | }}; 92 | 93 | extractor.extract(resultSet); 94 | } 95 | 96 | @Test 97 | public void testString() throws Exception { 98 | final Extractor extractor = factory.add(Bean::setTwo, ResultSet::getString, 1).withFactory(Bean::new).getExtractor(); 99 | 100 | new Expectations() {{ 101 | resultSet.getString(1); 102 | result = "two"; 103 | }}; 104 | 105 | final Bean bean = extractor.extract(resultSet); 106 | assertThat(bean.two).isEqualTo("two"); 107 | } 108 | 109 | @Test 110 | public void testStringColumn() throws Exception { 111 | final Extractor extractor = factory.add(Bean::setTwo, ResultSet::getString, "word").withFactory(Bean::new).getExtractor(); 112 | 113 | new Expectations() {{ 114 | resultSet.getString("word"); 115 | result = "two"; 116 | }}; 117 | 118 | final Bean bean = extractor.extract(resultSet); 119 | assertThat(bean.two).isEqualTo("two"); 120 | } 121 | 122 | @Test(expected = UncheckedSQLException.class) 123 | public void testStringException() throws Exception { 124 | final Extractor extractor = factory.add(Bean::setTwo, ResultSet::getString, 1).withFactory(Bean::new).getExtractor(); 125 | 126 | new Expectations() {{ 127 | resultSet.getString(1); 128 | result = new SQLException(); 129 | }}; 130 | 131 | extractor.extract(resultSet); 132 | } 133 | 134 | @Test 135 | public void testBoolean() throws Exception { 136 | final Extractor extractor = factory.add(Bean::setThree, ResultSet::getBoolean, 1).withFactory(Bean::new).getExtractor(); 137 | 138 | new Expectations() {{ 139 | resultSet.getBoolean(1); 140 | result = true; 141 | }}; 142 | 143 | final Bean bean = extractor.extract(resultSet); 144 | assertThat(bean.three).isTrue(); 145 | } 146 | 147 | @Test(expected = UncheckedSQLException.class) 148 | public void testBooleanException() throws Exception { 149 | final Extractor extractor = factory.add(Bean::setThree, ResultSet::getBoolean, 1).withFactory(Bean::new).getExtractor(); 150 | 151 | new Expectations() {{ 152 | resultSet.getBoolean(1); 153 | result = new SQLException(); 154 | }}; 155 | 156 | extractor.extract(resultSet); 157 | } 158 | 159 | @Test 160 | public void testLong() throws Exception { 161 | final Extractor extractor = factory.add(Bean::setFour, ResultSet::getLong, 1).withFactory(Bean::new).getExtractor(); 162 | 163 | new Expectations() {{ 164 | resultSet.getLong(1); 165 | result = 42L; 166 | }}; 167 | 168 | final Bean bean = extractor.extract(resultSet); 169 | assertThat(bean.four).isEqualTo(42L); 170 | } 171 | 172 | @Test(expected = UncheckedSQLException.class) 173 | public void testLongException() throws Exception { 174 | final Extractor extractor = factory.add(Bean::setFour, ResultSet::getLong, 1).withFactory(Bean::new).getExtractor(); 175 | 176 | new Expectations() {{ 177 | resultSet.getLong(1); 178 | result = new SQLException(); 179 | }}; 180 | 181 | extractor.extract(resultSet); 182 | } 183 | 184 | @Test 185 | public void testDouble() throws Exception { 186 | final Extractor extractor = factory.add(Bean::setFive, ResultSet::getDouble, 1).withFactory(Bean::new).getExtractor(); 187 | 188 | new Expectations() {{ 189 | resultSet.getDouble(1); 190 | result = 3.142; 191 | }}; 192 | 193 | final Bean bean = extractor.extract(resultSet); 194 | assertThat(bean.five).isEqualTo(3.142); 195 | } 196 | 197 | @Test(expected = UncheckedSQLException.class) 198 | public void testDoubleException() throws Exception { 199 | final Extractor extractor = factory.add(Bean::setFive, ResultSet::getDouble, 1).withFactory(Bean::new).getExtractor(); 200 | 201 | new Expectations() {{ 202 | resultSet.getDouble(1); 203 | result = new SQLException(); 204 | }}; 205 | 206 | extractor.extract(resultSet); 207 | } 208 | 209 | @Test 210 | public void testFloat() throws Exception { 211 | final Extractor extractor = factory.add(Bean::setSix, ResultSet::getFloat, 1).withFactory(Bean::new).getExtractor(); 212 | 213 | new Expectations() {{ 214 | resultSet.getFloat(1); 215 | result = 3.142f; 216 | }}; 217 | 218 | final Bean bean = extractor.extract(resultSet); 219 | assertThat(bean.six).isEqualTo(3.142f); 220 | } 221 | 222 | @Test(expected = UncheckedSQLException.class) 223 | public void testFloatException() throws Exception { 224 | final Extractor extractor = factory.add(Bean::setSix, ResultSet::getFloat, 1).withFactory(Bean::new).getExtractor(); 225 | 226 | new Expectations() {{ 227 | resultSet.getFloat(1); 228 | result = new SQLException(); 229 | }}; 230 | 231 | extractor.extract(resultSet); 232 | } 233 | 234 | 235 | @Test 236 | public void testBigDecimal() throws Exception { 237 | final Extractor extractor = factory.add(Bean::setSeven, ResultSet::getBigDecimal, 1).withFactory(Bean::new).getExtractor(); 238 | 239 | new Expectations() {{ 240 | resultSet.getBigDecimal(1); 241 | result = BigDecimal.TEN; 242 | }}; 243 | 244 | final Bean bean = extractor.extract(resultSet); 245 | assertThat(bean.seven).isEqualTo(BigDecimal.TEN); 246 | } 247 | 248 | @Test(expected = UncheckedSQLException.class) 249 | public void testBigDecimalException() throws Exception { 250 | final Extractor extractor = factory.add(Bean::setSeven, ResultSet::getBigDecimal, 1).withFactory(Bean::new).getExtractor(); 251 | 252 | new Expectations() {{ 253 | resultSet.getBigDecimal(1); 254 | result = new SQLException(); 255 | }}; 256 | 257 | extractor.extract(resultSet); 258 | } 259 | 260 | @Test 261 | public void testTime() throws Exception { 262 | final Extractor extractor = factory.add(Bean::setEight, ResultSet::getTime, 1).withFactory(Bean::new).getExtractor(); 263 | 264 | final long time = TimeUnit.HOURS.toMillis(1) + TimeUnit.MINUTES.toMillis(30); 265 | 266 | new Expectations() {{ 267 | resultSet.getTime(1); 268 | result = new Time(time); 269 | }}; 270 | 271 | final Bean bean = extractor.extract(resultSet); 272 | assertThat(bean.eight.getTime()).isEqualTo(time); 273 | } 274 | 275 | @Test(expected = UncheckedSQLException.class) 276 | public void testTimeException() throws Exception { 277 | final Extractor extractor = factory.add(Bean::setEight, ResultSet::getTime, 1).withFactory(Bean::new).getExtractor(); 278 | 279 | new Expectations() {{ 280 | resultSet.getTime(1); 281 | result = new SQLException(); 282 | }}; 283 | 284 | extractor.extract(resultSet); 285 | } 286 | 287 | @Test 288 | public void testDate() throws Exception { 289 | final Extractor extractor = factory.add(Bean::setEight, ResultSet::getDate, 1).withFactory(Bean::new).getExtractor(); 290 | 291 | final long date = TimeUnit.DAYS.toMillis(40); 292 | 293 | new Expectations() {{ 294 | resultSet.getDate(1); 295 | result = new java.sql.Date(date); 296 | }}; 297 | 298 | final Bean bean = extractor.extract(resultSet); 299 | assertThat(bean.eight.getTime()).isEqualTo(date); 300 | } 301 | 302 | @Test(expected = UncheckedSQLException.class) 303 | public void testDateException() throws Exception { 304 | final Extractor extractor = factory.add(Bean::setEight, ResultSet::getDate, 1).withFactory(Bean::new).getExtractor(); 305 | 306 | new Expectations() {{ 307 | resultSet.getDate(1); 308 | result = new SQLException(); 309 | }}; 310 | 311 | extractor.extract(resultSet); 312 | } 313 | 314 | @Test 315 | public void testTimestamp() throws Exception { 316 | final Extractor extractor = factory.add(Bean::setEight, ResultSet::getTimestamp, 1).withFactory(Bean::new).getExtractor(); 317 | 318 | final long timestamp = TimeUnit.DAYS.toMillis(40) + TimeUnit.MINUTES.toMillis(15); 319 | 320 | new Expectations() {{ 321 | resultSet.getTimestamp(1); 322 | result = new Timestamp(timestamp); 323 | }}; 324 | 325 | final Bean bean = extractor.extract(resultSet); 326 | assertThat(bean.eight.getTime()).isEqualTo(timestamp); 327 | } 328 | 329 | @Test(expected = UncheckedSQLException.class) 330 | public void testTimestampException() throws Exception { 331 | final Extractor extractor = factory.add(Bean::setEight, ResultSet::getTimestamp, 1).withFactory(Bean::new).getExtractor(); 332 | 333 | new Expectations() {{ 334 | resultSet.getTimestamp(1); 335 | result = new SQLException(); 336 | }}; 337 | 338 | extractor.extract(resultSet); 339 | } 340 | 341 | @Test 342 | public void testEnricher() throws Exception { 343 | final Enricher enricher = factory.add(Bean::setOne, ResultSet::getInt, 1).getEnricher(); 344 | 345 | new Expectations() {{ 346 | resultSet.getInt(1); 347 | result = 42; 348 | }}; 349 | Bean bean = new Bean(); 350 | bean.setOne(0); 351 | enricher.accept(bean, resultSet); 352 | assertThat(bean.one).isEqualTo(42); 353 | } 354 | 355 | 356 | @Test 357 | public void testMultiple() throws Exception { 358 | final Extractor extractor = factory.add(Bean::setTwo, ResultSet::getString, 1) 359 | .add(Bean::setOne, ResultSet::getInt, 2) 360 | .add(Bean::setFour, ResultSet::getLong, 4) 361 | .withFactory(Bean::new) 362 | .getExtractor(); 363 | 364 | new Expectations() {{ 365 | resultSet.getString(1); 366 | result = "two"; 367 | resultSet.getInt(2); 368 | result = 1; 369 | resultSet.getLong(4); 370 | result = 20L; 371 | }}; 372 | 373 | final Bean bean = extractor.extract(resultSet); 374 | assertThat(bean.one).isEqualTo(1); 375 | assertThat(bean.two).isEqualTo("two"); 376 | assertThat(bean.four).isEqualTo(20L); 377 | } 378 | 379 | 380 | private static class Bean { 381 | int one; 382 | String two; 383 | boolean three; 384 | long four; 385 | double five; 386 | float six; 387 | BigDecimal seven; 388 | Date eight; 389 | 390 | void setOne(int one) { 391 | this.one = one; 392 | } 393 | 394 | void setTwo(String two) { 395 | this.two = two; 396 | } 397 | 398 | void setThree(boolean three) { 399 | this.three = three; 400 | } 401 | 402 | void setFour(long four) { 403 | this.four = four; 404 | } 405 | 406 | void setFive(double five) { 407 | this.five = five; 408 | } 409 | 410 | void setSix(float six) { 411 | this.six = six; 412 | } 413 | 414 | void setSeven(BigDecimal seven) { 415 | this.seven = seven; 416 | } 417 | 418 | void setEight(Date eight) { 419 | this.eight = eight; 420 | } 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/utils/ResultSetStreamTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.utils; 19 | 20 | import com.github.nwillc.contracts.UtilityClassContract; 21 | 22 | /** 23 | * 24 | */ 25 | public class ResultSetStreamTest extends UtilityClassContract { 26 | 27 | @Override 28 | public Class getClassToTest() { 29 | return ResultSetStream.class; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/github/nwillc/funjdbc/utils/ThrowablesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, nwillc@gmail.com 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | package com.github.nwillc.funjdbc.utils; 19 | 20 | import com.github.nwillc.contracts.UtilityClassContract; 21 | import com.github.nwillc.funjdbc.UncheckedSQLException; 22 | import org.junit.Test; 23 | 24 | import java.io.IOException; 25 | import java.io.UncheckedIOException; 26 | import java.sql.SQLException; 27 | import java.util.zip.DataFormatException; 28 | 29 | import static com.github.nwillc.funjdbc.utils.Throwables.propagate; 30 | import static org.assertj.core.api.Assertions.assertThat; 31 | 32 | public class ThrowablesTest extends UtilityClassContract { 33 | 34 | @Override 35 | public Class getClassToTest() { 36 | return Throwables.class; 37 | } 38 | 39 | @Test 40 | public void testHandlesRuntimeException() { 41 | final ArithmeticException arithmeticException = new ArithmeticException(); 42 | 43 | final RuntimeException runtimeException = propagate(arithmeticException); 44 | assertThat(runtimeException).isEqualTo(arithmeticException); 45 | } 46 | 47 | @Test 48 | public void testWrapsNormalException() { 49 | final DataFormatException dataFormatException = new DataFormatException(); 50 | 51 | final RuntimeException runtimeException = propagate(dataFormatException); 52 | assertThat(runtimeException).isNotNull(); 53 | assertThat(RuntimeException.class).isAssignableFrom(runtimeException.getClass()); 54 | assertThat(runtimeException.getCause()).isEqualTo(dataFormatException); 55 | } 56 | 57 | @Test 58 | public void testHandlesIOException() { 59 | final IOException ioException = new IOException(); 60 | 61 | final RuntimeException runtimeException = propagate(ioException); 62 | assertThat(runtimeException).isNotNull(); 63 | assertThat(runtimeException).isInstanceOf(UncheckedIOException.class); 64 | assertThat(runtimeException.getCause()).isEqualTo(ioException); 65 | } 66 | 67 | @Test 68 | public void testHandlesSQLException() { 69 | final String msg = "foo"; 70 | final String sqlState = "bar"; 71 | final int errorCode = 42; 72 | final SQLException sqlException = new SQLException(msg, sqlState, errorCode); 73 | 74 | final RuntimeException runtimeException = propagate(sqlException); 75 | assertThat(runtimeException).isNotNull(); 76 | assertThat(runtimeException).isInstanceOf(UncheckedSQLException.class); 77 | assertThat(runtimeException.getCause()).isEqualTo(sqlException); 78 | } 79 | } 80 | --------------------------------------------------------------------------------