diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce9971b --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +target/ +*.log +*.log.gz +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..bf82ff0 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..dc3affc --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..f87f5c1 --- /dev/null +++ b/api/README.md @@ -0,0 +1 @@ +# TODO \ No newline at end of file diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 0000000..3f8ed10 --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + + com.pwinckles.jdbcgen + jdbc-gen-parent + 1.0-SNAPSHOT + ../pom.xml + + + jdbc-gen-api + 1.0-SNAPSHOT + + jdbc-gen-api + API for jdbc-gen + + + + + + + + + + diff --git a/api/src/main/java/com/pwinckles/jdbcgen/BasePatch.java b/api/src/main/java/com/pwinckles/jdbcgen/BasePatch.java new file mode 100644 index 0000000..c7a63ed --- /dev/null +++ b/api/src/main/java/com/pwinckles/jdbcgen/BasePatch.java @@ -0,0 +1,19 @@ +package com.pwinckles.jdbcgen; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public abstract class BasePatch { + + private final Map data = new HashMap<>(); + + public Map getData() { + return Collections.unmodifiableMap(data); + } + + protected void put(String key, Object value) { + data.put(key, value); + } + +} diff --git a/api/src/main/java/com/pwinckles/jdbcgen/JdbcGen.java b/api/src/main/java/com/pwinckles/jdbcgen/JdbcGen.java new file mode 100644 index 0000000..4b205a1 --- /dev/null +++ b/api/src/main/java/com/pwinckles/jdbcgen/JdbcGen.java @@ -0,0 +1,14 @@ +package com.pwinckles.jdbcgen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface JdbcGen { + + String name() default ""; + +} diff --git a/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenColumn.java b/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenColumn.java new file mode 100644 index 0000000..fdfb090 --- /dev/null +++ b/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenColumn.java @@ -0,0 +1,16 @@ +package com.pwinckles.jdbcgen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface JdbcGenColumn { + + String name(); + + boolean identity() default false; + +} diff --git a/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenDb.java b/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenDb.java new file mode 100644 index 0000000..484da62 --- /dev/null +++ b/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenDb.java @@ -0,0 +1,38 @@ +package com.pwinckles.jdbcgen; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +public interface JdbcGenDb { + + // TODO javadoc + + E select(I id, Connection conn) throws SQLException; + + List selectAll(Connection conn) throws SQLException; + + List selectAll(C orderBy, OrderDirection direction, Connection conn) throws SQLException; + + long count(Connection conn) throws SQLException; + + I insert(E entity, Connection conn) throws SQLException; + + I insert(P entity, Connection conn) throws SQLException; + + // TODO note that all ids must be specified or absent + List insert(List entities, Connection conn) throws SQLException; + + int update(E entity, Connection conn) throws SQLException; + + int update(I id, P entity, Connection conn) throws SQLException; + + int[] update(List entities, Connection conn) throws SQLException; + + int delete(I id, Connection conn) throws SQLException; + + int[] delete(List ids, Connection conn) throws SQLException; + + int deleteAll(Connection conn) throws SQLException; + +} diff --git a/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenTable.java b/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenTable.java new file mode 100644 index 0000000..5e7a83d --- /dev/null +++ b/api/src/main/java/com/pwinckles/jdbcgen/JdbcGenTable.java @@ -0,0 +1,14 @@ +package com.pwinckles.jdbcgen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface JdbcGenTable { + + String name(); + +} diff --git a/api/src/main/java/com/pwinckles/jdbcgen/OrderDirection.java b/api/src/main/java/com/pwinckles/jdbcgen/OrderDirection.java new file mode 100644 index 0000000..f04f570 --- /dev/null +++ b/api/src/main/java/com/pwinckles/jdbcgen/OrderDirection.java @@ -0,0 +1,17 @@ +package com.pwinckles.jdbcgen; + +public enum OrderDirection { + + ASCENDING("ASC"), + DESCENDING("DESC"); + + private final String value; + + OrderDirection(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..ccab6ca --- /dev/null +++ b/justfile @@ -0,0 +1,23 @@ +# List available commands +default: + just --list + +# Build +build: + ./mvnw -DskipTests clean package + +# Install into local M2 +install: build + ./mvnw -DskipTests clean install + +# Run tests +test: + ./mvnw clean test + +# Run tests that match pattern +test-filter PATTERN: + ./mvnw clean test -Dtest={{PATTERN}} + +# Apply code formatter +format: + ./mvnw spotless:apply diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..b7f0646 --- /dev/null +++ b/mvnw @@ -0,0 +1,287 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.1.1 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + printf '%s' "$(cd "$basedir"; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname $0)") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $wrapperUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + QUIET="--quiet" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + elif command -v curl > /dev/null; then + QUIET="--silent" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=`cygpath --path --windows "$javaSource"` + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..cba1f04 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,187 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.1.1 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5ea7814 --- /dev/null +++ b/pom.xml @@ -0,0 +1,198 @@ + + + 4.0.0 + + com.pwinckles.jdbcgen + jdbc-gen-parent + 1.0-SNAPSHOT + pom + + jdbc-gen-parent + Parent POM for jdbc-gen + https://github.com/pwinckles/jdbc-gen + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + Peter Winckles + + + + + scm:git:git://github.com/pwinckles/jdbcg.git + scm:git:ssh://github.com:pwinckles/jdbcg.git + https://github.com/pwinckles/jdbcg + + + + api + processor + test + + + + UTF-8 + 11 + 11 + + + + + + + com.squareup + javapoet + 1.13.0 + + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + com.google.guava + guava + 32.0.1-jre + + + + + org.junit.jupiter + junit-jupiter + 5.9.3 + test + + + org.assertj + assertj-core + 3.24.2 + test + + + io.toolisticon.cute + cute + 0.12.1 + test + + + io.toolisticon.cute + extension-junit5 + 0.12.1 + test + + + org.hsqldb + hsqldb + 2.7.2 + test + + + com.h2database + h2 + 2.1.214 + test + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + com.diffplug.spotless + spotless-maven-plugin + 2.35.0 + + + + + + + 2.28.0 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0 + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + org.apache.maven.plugins + maven-install-plugin + 3.1.1 + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + + org.apache.maven.plugins + maven-site-plugin + 3.12.1 + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + ${java.home}/bin/javadoc + + + + attach-javadocs + + jar + + + + + + + + diff --git a/processor/README.md b/processor/README.md new file mode 100644 index 0000000..f87f5c1 --- /dev/null +++ b/processor/README.md @@ -0,0 +1 @@ +# TODO \ No newline at end of file diff --git a/processor/pom.xml b/processor/pom.xml new file mode 100644 index 0000000..7a665e8 --- /dev/null +++ b/processor/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + + com.pwinckles.jdbcgen + jdbc-gen-parent + 1.0-SNAPSHOT + ../pom.xml + + + jdbc-gen-processor + 1.0-SNAPSHOT + + jdbc-gen-processor + Annotation processor for jdbc-gen + + + + com.pwinckles.jdbcgen + jdbc-gen-api + 1.0-SNAPSHOT + + + com.squareup + javapoet + + + com.google.guava + guava + + + + + org.junit.jupiter + junit-jupiter + test + + + io.toolisticon.cute + cute + test + + + io.toolisticon.cute + extension-junit5 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + -proc:none + + + + + + diff --git a/processor/src/main/java/com/pwinckles/jdbcgen/processor/BeanUtil.java b/processor/src/main/java/com/pwinckles/jdbcgen/processor/BeanUtil.java new file mode 100644 index 0000000..4130b65 --- /dev/null +++ b/processor/src/main/java/com/pwinckles/jdbcgen/processor/BeanUtil.java @@ -0,0 +1,22 @@ +package com.pwinckles.jdbcgen.processor; + +public final class BeanUtil { + + private BeanUtil() { + + } + + public static String getterName(String fieldName, boolean isBoolean) { + var prefix = isBoolean ? "is" : "get"; + return beanMethodName(prefix, fieldName); + } + + public static String setterName(String fieldName) { + return beanMethodName("set", fieldName); + } + + private static String beanMethodName(String prefix, String fieldName) { + return prefix + String.valueOf(fieldName.charAt(0)).toUpperCase() + fieldName.substring(1); + } + +} diff --git a/processor/src/main/java/com/pwinckles/jdbcgen/processor/ColumnSpec.java b/processor/src/main/java/com/pwinckles/jdbcgen/processor/ColumnSpec.java new file mode 100644 index 0000000..7ec87ef --- /dev/null +++ b/processor/src/main/java/com/pwinckles/jdbcgen/processor/ColumnSpec.java @@ -0,0 +1,148 @@ +package com.pwinckles.jdbcgen.processor; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import java.util.Objects; + +public class ColumnSpec { + + private final String columnName; + private final boolean identity; + private final VariableElement fieldElement; + private final ExecutableElement getterElement; + private final ExecutableElement setterElement; + private final FieldGetMethod getMethod; + private final FieldSetMethod setMethod; + + public static Builder builder() { + return new Builder(); + } + + private ColumnSpec(String columnName, + boolean identity, + VariableElement fieldElement, + ExecutableElement getterElement, + ExecutableElement setterElement, + FieldGetMethod getMethod, + FieldSetMethod setMethod) { + this.columnName = Objects.requireNonNull(columnName, "columnName cannot be null"); + this.identity = identity; + this.fieldElement = Objects.requireNonNull(fieldElement, "fieldElement cannot be null"); + this.getterElement = getterElement; + this.setterElement = setterElement; + this.getMethod = Objects.requireNonNull(getMethod, "getMethod cannot be null"); + this.setMethod = Objects.requireNonNull(setMethod, "setMethod cannot be null"); + + if (getMethod == FieldGetMethod.GETTER) { + Objects.requireNonNull(getterElement, "getterElement cannot be null"); + } + if (setMethod == FieldSetMethod.SETTER) { + Objects.requireNonNull(setterElement, "setterElement cannot be null"); + } + } + + public String getColumnName() { + return columnName; + } + + public String getFieldName() { + return fieldElement.getSimpleName().toString(); + } + + public boolean isPrimitive() { + return fieldElement.asType().getKind().isPrimitive(); + } + + public boolean isIdentity() { + return identity; + } + + public VariableElement getFieldElement() { + return fieldElement; + } + + public ExecutableElement getGetterElement() { + return getterElement; + } + + public String getGetterName() { + if (getterElement == null) { + return null; + } + return getterElement.getSimpleName().toString(); + } + + public ExecutableElement getSetterElement() { + return setterElement; + } + + public String getSetterName() { + if (setterElement == null) { + return null; + } + return setterElement.getSimpleName().toString(); + } + + public FieldGetMethod getGetMethod() { + return getMethod; + } + + public FieldSetMethod getSetMethod() { + return setMethod; + } + + public static class Builder { + private String columnName; + private boolean identity; + private VariableElement fieldElement; + private ExecutableElement getterElement; + private ExecutableElement setterElement; + private FieldGetMethod getMethod; + private FieldSetMethod setMethod; + + public Builder withColumnName(String columnName) { + this.columnName = columnName; + return this; + } + + public Builder withIdentity(boolean identity) { + this.identity = identity; + return this; + } + + public Builder withFieldElement(VariableElement fieldElement) { + this.fieldElement = fieldElement; + return this; + } + + public Builder withGetterElement(ExecutableElement getterElement) { + this.getterElement = getterElement; + return this; + } + + public Builder withSetterElement(ExecutableElement setterElement) { + this.setterElement = setterElement; + return this; + } + + public Builder withGetMethod(FieldGetMethod getMethod) { + this.getMethod = getMethod; + return this; + } + + public Builder withSetMethod(FieldSetMethod setMethod) { + this.setMethod = setMethod; + return this; + } + + public ColumnSpec build() { + return new ColumnSpec(columnName, + identity, + fieldElement, + getterElement, + setterElement, + getMethod, + setMethod); + } + } +} diff --git a/processor/src/main/java/com/pwinckles/jdbcgen/processor/DbClassGenerator.java b/processor/src/main/java/com/pwinckles/jdbcgen/processor/DbClassGenerator.java new file mode 100644 index 0000000..aa48965 --- /dev/null +++ b/processor/src/main/java/com/pwinckles/jdbcgen/processor/DbClassGenerator.java @@ -0,0 +1,754 @@ +package com.pwinckles.jdbcgen.processor; + +import com.google.common.base.CaseFormat; +import com.pwinckles.jdbcgen.BasePatch; +import com.pwinckles.jdbcgen.JdbcGenDb; +import com.pwinckles.jdbcgen.OrderDirection; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; + +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.PROTECTED; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; + +public class DbClassGenerator { + + public JavaFile generate(EntitySpec entitySpec) { + var entityType = TypeName.get(entitySpec.getTypeElement().asType()); + var idType = TypeName.get(entitySpec.getIdentityColumn().getFieldElement().asType()).box(); + var patchType = ClassName.get(entitySpec.getPackageName(), entitySpec.getDbClassName(), "Patch"); + var columnType = ClassName.get(entitySpec.getPackageName(), entitySpec.getDbClassName(), "Column"); + + // TODO javadoc + var builder = TypeSpec.classBuilder(entitySpec.getDbClassName()) + .addSuperinterface(ParameterizedTypeName.get( + ClassName.get(JdbcGenDb.class), + entityType, + idType, + patchType, + columnType)); + + if (entitySpec.getTypeElement().getModifiers().contains(PUBLIC)) { + builder.addModifiers(PUBLIC); + } else if (entitySpec.getTypeElement().getModifiers().contains(PROTECTED)) { + builder.addModifiers(PROTECTED); + } + + builder.addType(genColumnsEnum(entitySpec)) + .addType(genPatchClass(patchType, idType, entitySpec)) + .addMethod(genSelect(idType, entitySpec)) + .addMethod(genSelectAll(entityType, entitySpec)) + .addMethod(genSelectAllOrdered(entityType, columnType, entitySpec)) + .addMethod(genCount(entitySpec)) + .addMethod(genInsert(entityType, idType, entitySpec)) + .addMethod(genInsertPatch(patchType, idType, entitySpec)) + .addMethod(genInsertList(entityType, idType, entitySpec)) + .addMethod(genUpdatePatch(entityType, entitySpec)) + .addMethod(genUpdatePatch(patchType, idType, entitySpec)) + .addMethod(genUpdateList(entityType, entitySpec)) + .addMethod(genDelete(idType, entitySpec)) + .addMethod(genDeleteList(idType, entitySpec)) + .addMethod(genDeleteAll(entitySpec)) + .addMethod(genInsertWithSpecifiedId(entityType, idType, entitySpec)) + .addMethod(genInsertListWithSpecifiedId(entityType, idType, entitySpec)) + .addMethod(genFromResultSet(entityType, entitySpec)) + .addMethod(genPrepareInsert(entityType, entitySpec)) + .addMethod(genPrepareUpdate(entityType, entitySpec)) + .addMethod(genGetNullableValue()); + + if (!entitySpec.getIdentityColumn().isPrimitive()) { + builder.addMethod(genInsertWithGeneratedId(entityType, idType, entitySpec)) + .addMethod(genInsertListWithGeneratedId(entityType, idType, entitySpec)); + } + + return JavaFile.builder(entitySpec.getPackageName(), builder.build()) + .addStaticImport(Collectors.class, "toList") + .addStaticImport(Collections.class, "unmodifiableMap") + .addStaticImport(Statement.class, "RETURN_GENERATED_KEYS") + .build(); + } + + private TypeSpec genColumnsEnum(EntitySpec entitySpec) { + var builder = TypeSpec.enumBuilder("Column") + .addModifiers(PUBLIC); + + entitySpec.getColumns().forEach(column -> builder.addEnumConstant(toEnumCase(column.getFieldName()), + TypeSpec.anonymousClassBuilder("$S", column.getColumnName()) + .build())); + + builder.addField(String.class, "value", PRIVATE, FINAL) + .addMethod(MethodSpec.constructorBuilder() + .addParameter(String.class, "value") + .addStatement("this.value = value") + .build()); + + return builder.build(); + } + + private TypeSpec genPatchClass(TypeName patchType, TypeName idType, EntitySpec entitySpec) { + var builder = TypeSpec.classBuilder("Patch") + .addModifiers(PUBLIC, STATIC) + .superclass(BasePatch.class); + + var idFieldName = entitySpec.getIdentityColumn().getFieldName(); + + builder.addMethod(MethodSpec.methodBuilder(BeanUtil.getterName(idFieldName, false)) + .addModifiers(PUBLIC) + .returns(idType) + .addStatement("return ($T) getData().get($S)", + idType, + entitySpec.getIdentityColumn().getColumnName()) + .build()); + + entitySpec.getColumns().forEach(column -> { + var field = column.getFieldElement(); + var fieldName = field.getSimpleName().toString(); + builder.addMethod(MethodSpec.methodBuilder(BeanUtil.setterName(fieldName)) + .addModifiers(PUBLIC) + .returns(patchType) + .addParameter(TypeName.get(field.asType()), fieldName) + .addStatement("put($S, $N)", column.getColumnName(), fieldName) + .addStatement("return this") + .build()); + }); + + return builder.build(); + } + + private MethodSpec genSelect(TypeName idType, EntitySpec entitySpec) { + var query = "SELECT " + columnNames(entitySpec) + + " FROM " + entitySpec.getTableName() + + " WHERE " + entitySpec.getIdentityColumn().getColumnName() + " = ? LIMIT 1"; + return MethodSpec.methodBuilder("select") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(TypeName.get(entitySpec.getTypeElement().asType())) + .addParameter(idType, "id") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.prepareStatement($S))", query) + .addStatement("stmt.setObject(1, id)") + .addStatement("var rs = stmt.executeQuery()") + .beginControlFlow("if (rs.next())") + .addStatement("return fromResultSet(rs)") + .endControlFlow() + .endControlFlow() + .addStatement("return null") + .build(); + } + + private MethodSpec genSelectAll(TypeName entityType, EntitySpec entitySpec) { + var query = "SELECT " + columnNames(entitySpec) + " FROM " + entitySpec.getTableName(); + return MethodSpec.methodBuilder("selectAll") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(listType(entityType)) + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .addStatement("var results = new $T<$T>()", ArrayList.class, entityType) + .beginControlFlow("try (var stmt = conn.createStatement())") + .addStatement("var rs = stmt.executeQuery($S)", query) + .beginControlFlow("while (rs.next())") + .addStatement("results.add(fromResultSet(rs))") + .endControlFlow() + .endControlFlow() + .addStatement("return results") + .build(); + } + + private MethodSpec genSelectAllOrdered(TypeName entityType, TypeName columnType, EntitySpec entitySpec) { + var query = "SELECT " + columnNames(entitySpec) + " FROM " + entitySpec.getTableName() + " ORDER BY "; + return MethodSpec.methodBuilder("selectAll") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(listType(entityType)) + .addParameter(columnType, "orderBy") + .addParameter(OrderDirection.class, "direction") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .addStatement("var results = new $T<$T>()", ArrayList.class, entityType) + .beginControlFlow("try (var stmt = conn.createStatement())") + .addStatement("var rs = stmt.executeQuery($S + orderBy.value + \" \" + direction.getValue())", query) + .beginControlFlow("while (rs.next())") + .addStatement("results.add(fromResultSet(rs))") + .endControlFlow() + .endControlFlow() + .addStatement("return results") + .build(); + } + + private MethodSpec genCount(EntitySpec entitySpec) { + var query = "SELECT COUNT(" + entitySpec.getIdentityColumn().getColumnName() + ") FROM " + entitySpec.getTableName(); + return MethodSpec.methodBuilder("count") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(long.class) + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.createStatement())") + .addStatement("var rs = stmt.executeQuery($S)", query) + .beginControlFlow("if (rs.next())") + .addStatement("return rs.getLong(1)") + .endControlFlow() + .endControlFlow() + .addStatement("return 0") + .build(); + } + + private MethodSpec genInsert(TypeName entityType, TypeName idType, EntitySpec entitySpec) { + var builder = MethodSpec.methodBuilder("insert") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(idType) + .addParameter(entityType, "entity") + .addParameter(Connection.class, "conn") + .addException(SQLException.class); + + if (entitySpec.getIdentityColumn().isPrimitive()) { + builder.addStatement("return insertWithSpecifiedId(entity, conn)"); + } else { + builder.beginControlFlow("if (entity.$L == null)", fieldAccess(entitySpec.getIdentityColumn())) + .addStatement("return insertWithGeneratedId(entity, conn)") + .endControlFlow() + .addStatement("return insertWithSpecifiedId(entity, conn)"); + } + + return builder.build(); + } + + private MethodSpec genInsertPatch(TypeName patchType, TypeName idType, EntitySpec entitySpec) { + var idGetterName = BeanUtil.getterName(entitySpec.getIdentityColumn().getFieldName(), false); + return MethodSpec.methodBuilder("insert") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(idType) + .addParameter(patchType, "entity") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("if (entity.getData().isEmpty())") + .addStatement("throw new $T($S)", SQLException.class, "No data specified") + .endControlFlow() + .addCode("\n") + .addStatement("boolean generatedId = entity.$N() == null", idGetterName) + .addStatement("var data = entity.getData()") + .addStatement("var keys = new ArrayList<>(data.keySet())") + .addCode("\n") + .addStatement("var queryBuilder = new StringBuilder($S)", "INSERT INTO " +entitySpec.getTableName() + "(") + .addCode("\n") + .beginControlFlow("for (var it = keys.iterator(); it.hasNext();)") + .addStatement("queryBuilder.append(it.next())") + .beginControlFlow("if (it.hasNext())") + .addStatement("queryBuilder.append(\", \")") + .endControlFlow() + .endControlFlow() + .addCode("\n") + .addStatement("queryBuilder.append(\") VALUES (\")") + .addCode("\n") + .beginControlFlow("if (keys.size() > 1)") + .addStatement("queryBuilder.append(\"?, \".repeat(keys.size() - 1))") + .endControlFlow() + .addStatement("queryBuilder.append(\"?)\")") + .addCode("\n") + .addStatement("$T stmt", PreparedStatement.class) + .beginControlFlow("if (generatedId)") + .addStatement("stmt = conn.prepareStatement(queryBuilder.toString(), $T.RETURN_GENERATED_KEYS)", Statement.class) + .nextControlFlow("else") + .addStatement("stmt = conn.prepareStatement(queryBuilder.toString())") + .endControlFlow() + .addCode("\n") + .beginControlFlow("try") + .beginControlFlow("for (int i = 0; i < keys.size(); i++)") + .addStatement("var value = data.get(keys.get(i))") + .addStatement("stmt.setObject(i + 1, value)") + .endControlFlow() + .addCode("\n") + .addStatement("stmt.executeUpdate()") + .addCode("\n") + .beginControlFlow("if (generatedId)") + .addStatement("var rs = stmt.getGeneratedKeys()") + .beginControlFlow("if (rs.next())") + .addStatement("return getNullableValue(rs, 1, $T.class)", idType) + .nextControlFlow("else") + .addStatement("throw new $T($S)", SQLException.class, "Generated id was not returned.") + .endControlFlow() + .nextControlFlow("else") + .addStatement("return entity.$N()", idGetterName) + .endControlFlow() + .nextControlFlow("finally") + .addStatement("stmt.close()") + .endControlFlow() + .build(); + } + + private MethodSpec genInsertList(TypeName entityType, TypeName idType, EntitySpec entitySpec) { + var builder = MethodSpec.methodBuilder("insert") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(listType(idType)) + .addParameter(listType(entityType), "entities") + .addParameter(Connection.class, "conn") + .addException(SQLException.class); + + if (entitySpec.getIdentityColumn().isPrimitive()) { + builder.addStatement("return insertWithSpecifiedId(entities, conn)"); + } else{ + builder.beginControlFlow("if (!entities.isEmpty() && entities.get(0).$L == null)", fieldAccess(entitySpec.getIdentityColumn())) + .addStatement("return insertWithGeneratedId(entities, conn)") + .endControlFlow() + .addStatement("return insertWithSpecifiedId(entities, conn)"); + } + + return builder.build(); + } + + private MethodSpec genUpdatePatch(TypeName entityType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("update") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(int.class) + .addParameter(entityType, "entity") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.prepareStatement($S))", buildUpdateQuery(entitySpec)) + .addStatement("prepareUpdate(entity, stmt)") + .addStatement("return stmt.executeUpdate()") + .endControlFlow() + .build(); + } + + private MethodSpec genUpdatePatch(TypeName patchType, TypeName idType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("update") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(int.class) + .addParameter(idType, "id") + .addParameter(patchType, "entity") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("if (entity.getData().isEmpty())") + .addStatement("throw new $T($S)", SQLException.class, "No data specified") + .endControlFlow() + .addCode("\n") + .addStatement("var data = entity.getData()") + .addStatement("var keys = new ArrayList<>(data.keySet())") + .addCode("\n") + .addStatement("var queryBuilder = new StringBuilder(\"UPDATE $L SET \")", entitySpec.getTableName()) + .addCode("\n") + .beginControlFlow("for (var it = keys.iterator(); it.hasNext();)") + .addStatement("queryBuilder.append(it.next()).append(\" = ?\")") + .beginControlFlow("if (it.hasNext())") + .addStatement("queryBuilder.append(\", \")") + .endControlFlow() + .endControlFlow() + .addCode("\n") + .addStatement("queryBuilder.append(\" WHERE $L = ?\")", entitySpec.getIdentityColumn().getColumnName()) + .addCode("\n") + .beginControlFlow("try (var stmt = conn.prepareStatement(queryBuilder.toString()))") + .beginControlFlow("for (int i = 0; i < keys.size(); i++)") + .addStatement("var value = data.get(keys.get(i))") + .addStatement("stmt.setObject(i + 1, value)") + .endControlFlow() + .addStatement("stmt.setObject(keys.size() + 1, id)") + .addStatement("return stmt.executeUpdate()") + .endControlFlow() + .build(); + } + + private MethodSpec genUpdateList(TypeName entityType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("update") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(int[].class) + .addParameter(listType(entityType), "entities") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.prepareStatement($S))", buildUpdateQuery(entitySpec)) + .beginControlFlow("for (var entity : entities)") + .addStatement("prepareUpdate(entity, stmt)") + .addStatement("stmt.addBatch()") + .endControlFlow() + .addStatement("return stmt.executeBatch()") + .endControlFlow() + .build(); + } + + private MethodSpec genDelete(TypeName idType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("delete") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(int.class) + .addParameter(idType, "id") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.prepareStatement($S))", buildDeleteQuery(entitySpec)) + .addStatement("stmt.setObject(1, id)") + .addStatement("return stmt.executeUpdate()") + .endControlFlow() + .build(); + } + + private MethodSpec genDeleteList(TypeName idType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("delete") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(int[].class) + .addParameter(listType(idType), "ids") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.prepareStatement($S))", buildDeleteQuery(entitySpec)) + .beginControlFlow("for (var id : ids)") + .addStatement("stmt.setObject(1, id)") + .addStatement("stmt.addBatch()") + .endControlFlow() + .addStatement("return stmt.executeBatch()") + .endControlFlow() + .build(); + } + + private MethodSpec genDeleteAll(EntitySpec entitySpec) { + return MethodSpec.methodBuilder("deleteAll") + .addJavadoc("{@inheritDoc}") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(int.class) + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.createStatement())") + .addStatement("return stmt.executeUpdate(\"DELETE FROM $L\")", entitySpec.getTableName()) + .endControlFlow() + .build(); + } + + private MethodSpec genInsertWithGeneratedId(TypeName entityType, TypeName idType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("insertWithGeneratedId") + .addModifiers(PRIVATE) + .returns(idType) + .addParameter(entityType, "entity") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.prepareStatement($S, $T.RETURN_GENERATED_KEYS))", buildInsertQueryWithoutId(entitySpec), Statement.class) + .addStatement("prepareInsert(entity, stmt)") + .addStatement("stmt.executeUpdate()") + .addStatement("var rs = stmt.getGeneratedKeys()") + .beginControlFlow("if (rs.next())") + .addStatement("return getNullableValue(rs, 1, $T.class)", idType) + .nextControlFlow("else") + .addStatement("throw new $T($S)", SQLException.class, "Generated id was not returned.") + .endControlFlow() + .endControlFlow() + .build(); + } + + private MethodSpec genInsertWithSpecifiedId(TypeName entityType, TypeName idType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("insertWithSpecifiedId") + .addModifiers(PRIVATE) + .returns(idType) + .addParameter(entityType, "entity") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.prepareStatement($S))", buildInsertQueryWithId(entitySpec)) + .addStatement("prepareInsert(entity, stmt)") + .addStatement("stmt.executeUpdate()") + .endControlFlow() + .addStatement("return entity.$L", fieldAccess(entitySpec.getIdentityColumn())) + .build(); + } + + private MethodSpec genInsertListWithGeneratedId(TypeName entityType, TypeName idType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("insertWithGeneratedId") + .addModifiers(PRIVATE) + .returns(listType(idType)) + .addParameter(listType(entityType), "entities") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .addStatement("var ids = new ArrayList<$T>()", idType) + .beginControlFlow("try (var stmt = conn.prepareStatement($S, $T.RETURN_GENERATED_KEYS))", buildInsertQueryWithoutId(entitySpec), Statement.class) + .beginControlFlow("for (var entity : entities)") + .addStatement("prepareInsert(entity, stmt)") + .addStatement("stmt.addBatch()") + .endControlFlow() + .addStatement("stmt.executeBatch()") + .addStatement("var rs = stmt.getGeneratedKeys()") + .beginControlFlow("while (rs.next())") + .addStatement("ids.add(getNullableValue(rs, 1, $T.class))", idType) + .endControlFlow() + .endControlFlow() + .addStatement("return ids") + .build(); + } + + private MethodSpec genInsertListWithSpecifiedId(TypeName entityType, TypeName idType, EntitySpec entitySpec) { + return MethodSpec.methodBuilder("insertWithSpecifiedId") + .addModifiers(PRIVATE) + .returns(listType(idType)) + .addParameter(listType(entityType), "entities") + .addParameter(Connection.class, "conn") + .addException(SQLException.class) + .beginControlFlow("try (var stmt = conn.prepareStatement($S))", buildInsertQueryWithId(entitySpec)) + .beginControlFlow("for (var entity : entities)") + .addStatement("prepareInsert(entity, stmt)") + .addStatement("stmt.addBatch()") + .endControlFlow() + .addStatement("stmt.executeBatch()") + .endControlFlow() + .addStatement("return entities.stream().map(entity -> entity.$L).collect($T.toList())", fieldAccess(entitySpec.getIdentityColumn()), Collectors.class) + .build(); + } + + private MethodSpec genFromResultSet(TypeName entityType, EntitySpec entitySpec) { + var builder = MethodSpec.methodBuilder("fromResultSet") + .addModifiers(PRIVATE) + .returns(entityType) + .addParameter(ResultSet.class, "rs") + .addException(SQLException.class) + .addStatement("int i = 1"); + + if (entitySpec.isCanonicalConstructor()) { + var args = new ArrayList<>(); + var stmtBuilder = new StringBuilder("return new $T("); + args.add(entityType); + + for (var it = entitySpec.getColumns().iterator(); it.hasNext();) { + var column = it.next(); + if (column.isPrimitive()) { + stmtBuilder.append(resultSetPrimitiveGet(column.getFieldElement().asType())); + } else { + stmtBuilder.append("getNullableValue(rs, i++, $T.class)"); + args.add(TypeName.get(column.getFieldElement().asType())); + } + + if (it.hasNext()) { + stmtBuilder.append(", "); + } + } + + stmtBuilder.append(")"); + builder.addStatement(stmtBuilder.toString(), args.toArray(new Object[]{})); + } else { + builder.addStatement("var entity = new $T()", entityType); + + entitySpec.getColumns().forEach(column -> { + var primitive = column.isPrimitive(); + var fieldType = TypeName.get(column.getFieldElement().asType()); + + String getMethod; + if (primitive) { + getMethod = resultSetPrimitiveGet(column.getFieldElement().asType()); + } else { + getMethod = "getNullableValue(rs, i++, $T.class)"; + } + + String statement; + if (column.getSetMethod() == FieldSetMethod.DIRECT) { + statement = "entity." + column.getFieldName() + " = " + getMethod; + + } else { + statement = "entity." + column.getSetterName() + "(" + getMethod + ")"; + } + + if (primitive) { + builder.addStatement(statement); + } else { + builder.addStatement(statement, fieldType); + } + }); + + builder.addStatement("return entity"); + } + + return builder.build(); + } + + private MethodSpec genPrepareInsert(TypeName entityType, EntitySpec entitySpec) { + var builder = MethodSpec.methodBuilder("prepareInsert") + .addModifiers(PRIVATE) + .returns(void.class) + .addParameter(entityType, "entity") + .addParameter(PreparedStatement.class, "stmt") + .addException(SQLException.class) + .addStatement("int i = 1"); + + if (entitySpec.getIdentityColumn().isPrimitive()) { + builder.addStatement("stmt.setObject(i++, entity.$L)", fieldAccess(entitySpec.getIdentityColumn())); + } else { + builder.beginControlFlow("if (entity.$L != null)", fieldAccess(entitySpec.getIdentityColumn())) + .addStatement("stmt.setObject(i++, entity.$L)", fieldAccess(entitySpec.getIdentityColumn())) + .endControlFlow(); + } + + entitySpec.getColumns().stream() + .filter(column -> !column.isIdentity()) + .forEach(column -> { + builder.addStatement("stmt.setObject(i++, entity.$L)", fieldAccess(column)); + }); + + return builder.build(); + } + + private MethodSpec genPrepareUpdate(TypeName entityType, EntitySpec entitySpec) { + var builder = MethodSpec.methodBuilder("prepareUpdate") + .addModifiers(PRIVATE) + .returns(void.class) + .addParameter(entityType, "entity") + .addParameter(PreparedStatement.class, "stmt") + .addException(SQLException.class) + .addStatement("int i = 1"); + + entitySpec.getColumns().stream() + .filter(column -> !column.isIdentity()) + .forEach(column -> { + builder.addStatement("stmt.setObject(i++, entity.$L)", fieldAccess(column)); + }); + + builder.addStatement("stmt.setObject(i++, entity.$L)", fieldAccess(entitySpec.getIdentityColumn())); + + return builder.build(); + } + + private MethodSpec genGetNullableValue() { + var typeVar = TypeVariableName.get("T"); + return MethodSpec.methodBuilder("getNullableValue") + .addModifiers(PRIVATE) + .addTypeVariable(typeVar) + .returns(typeVar) + .addParameter(ResultSet.class, "rs") + .addParameter(int.class, "index") + .addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), typeVar), "clazz") + .addException(SQLException.class) + .addStatement("var value = rs.getObject(index, clazz)") + .beginControlFlow("if (rs.wasNull())") + .addStatement("return null") + .endControlFlow() + .addStatement("return value") + .build(); + } + + private String columnNames(EntitySpec entitySpec) { + return entitySpec.getColumns().stream() + .map(ColumnSpec::getColumnName) + .collect(Collectors.joining(", ")); + } + + private String columnNamesWithoutId(EntitySpec entitySpec) { + return entitySpec.getColumns().stream() + .filter(column -> !column.isIdentity()) + .map(ColumnSpec::getColumnName) + .collect(Collectors.joining(", ")); + } + + private String genPlaceholders(int count) { + var builder = new StringBuilder(); + if (count > 1) { + builder.append("?, ".repeat(count - 1)); + } + builder.append("?"); + return builder.toString(); + } + + private String fieldAccess(ColumnSpec column) { + if (column.getGetMethod() == FieldGetMethod.DIRECT) { + return column.getFieldName(); + } + return column.getGetterName() + "()"; + } + + private String resultSetPrimitiveGet(TypeMirror type) { + switch (type.getKind()) { + case LONG: + return "rs.getLong(i++)"; + case BOOLEAN: + return "rs.getBoolean(i++)"; + case INT: + return "rs.getInt(i++)"; + case DOUBLE: + return "rs.getDouble(i++)"; + case FLOAT: + return "rs.getFloat(i++)"; + case SHORT: + return "rs.getShort(i++)"; + case BYTE: + return "rs.getByte(i++)"; + case ARRAY: + var component = ((ArrayType) type).getComponentType(); + if (component.getKind() == TypeKind.BYTE) { + return "rs.getBytes(i++)"; + } + throw new RuntimeException("Byte arrays are the only array type that is currently supported. Found type: " + component); + default: + throw new RuntimeException("Unmapped type: " + type); + } + } + + private String toEnumCase(String name) { + return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, name); + } + + private ParameterizedTypeName listType(TypeName typeName) { + return ParameterizedTypeName.get(ClassName.get(List.class), typeName); + } + + private String buildInsertQueryWithId(EntitySpec entitySpec) { + var columnNames = columnNames(entitySpec); + return "INSERT INTO " + entitySpec.getTableName() + " (" + columnNames + ") VALUES (" + genPlaceholders(entitySpec.getColumns().size()) + ")"; + } + + private String buildInsertQueryWithoutId(EntitySpec entitySpec) { + var columnNames = columnNamesWithoutId(entitySpec); + return "INSERT INTO " + entitySpec.getTableName() + " (" + columnNames + ") VALUES (" + genPlaceholders(entitySpec.getColumns().size() - 1) + ")"; + } + + private String buildUpdateQuery(EntitySpec entitySpec) { + var queryBuilder = new StringBuilder("UPDATE ") + .append(entitySpec.getTableName()) + .append(" SET "); + + entitySpec.getColumns().stream() + .filter(column -> !column.isIdentity()) + .forEach(column -> queryBuilder.append(column.getColumnName()).append(" = ?, ")); + + queryBuilder.delete(queryBuilder.length() - 2, queryBuilder.length()); + + queryBuilder.append(" WHERE ") + .append(entitySpec.getIdentityColumn().getColumnName()) + .append(" = ?"); + + return queryBuilder.toString(); + } + + private String buildDeleteQuery(EntitySpec entitySpec) { + return "DELETE FROM " + entitySpec.getTableName() + " WHERE " + entitySpec.getIdentityColumn().getColumnName() + " = ?"; + } + +} diff --git a/processor/src/main/java/com/pwinckles/jdbcgen/processor/EntityAnalyzer.java b/processor/src/main/java/com/pwinckles/jdbcgen/processor/EntityAnalyzer.java new file mode 100644 index 0000000..04d876a --- /dev/null +++ b/processor/src/main/java/com/pwinckles/jdbcgen/processor/EntityAnalyzer.java @@ -0,0 +1,234 @@ +package com.pwinckles.jdbcgen.processor; + +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenTable; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class EntityAnalyzer { + + private final ProcessingEnvironment processingEnv; + + public EntityAnalyzer(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + } + + public EntitySpec analyze(TypeElement entity) { + validateEntityType(entity); + + var entitySpecBuilder = EntitySpec.builder() + .withTypeElement(entity); + + entitySpecBuilder.withPackageName(processingEnv.getElementUtils() + .getPackageOf(entity).getQualifiedName().toString()); + + var genAnnotation = getAndValidateGenAnnotation(entity); + + if (genAnnotation.name() == null || genAnnotation.name().isBlank()) { + entitySpecBuilder.withDbClassName(entity.getSimpleName().toString() + "Db"); + } else { + entitySpecBuilder.withDbClassName(genAnnotation.name()); + } + + var tableAnnotation = getAndValidateTableAnnotation(entity); + entitySpecBuilder.withTableName(tableAnnotation.name()); + + var columnFields = getAndValidateColumnFields(entity); + + var constructor = resolveConstructor(entity, columnFields); + var hasCanonicalConstructor = !constructor.getParameters().isEmpty(); + entitySpecBuilder.withConstructorElement(constructor) + .withCanonicalConstructor(hasCanonicalConstructor); + + var columnSpecs = resolveColumnSpecs(entity, columnFields, hasCanonicalConstructor); + entitySpecBuilder.withColumns(columnSpecs); + + columnSpecs.stream() + .filter(ColumnSpec::isIdentity).findFirst() + .ifPresent(entitySpecBuilder::withIdentityColumn); + + return entitySpecBuilder.build(); + } + + private void validateEntityType(TypeElement entity) { + if (entity.getModifiers().contains(Modifier.PRIVATE)) { + throw new RuntimeException(entity.getQualifiedName() + " must not be private."); + } + if (entity.getNestingKind() == NestingKind.MEMBER && !entity.getModifiers().contains(Modifier.STATIC)) { + throw new RuntimeException(entity.getQualifiedName() + " must be static."); + } + if (entity.getModifiers().contains(Modifier.ABSTRACT)) { + throw new RuntimeException(entity.getQualifiedName() + " must not be abstract."); + } + if (!entity.getTypeParameters().isEmpty()) { + throw new RuntimeException(entity.getQualifiedName() + " must not have type parameters."); + } + } + + private JdbcGen getAndValidateGenAnnotation(TypeElement entity) { + var genAnnotations = entity.getAnnotationsByType(JdbcGen.class); + if (genAnnotations.length != 1) { + throw new RuntimeException(entity.getQualifiedName() + " must have exactly one @JdbcGen annotation."); + } + return genAnnotations[0]; + } + + private JdbcGenTable getAndValidateTableAnnotation(TypeElement entity) { + var tableAnnotations = entity.getAnnotationsByType(JdbcGenTable.class); + + // TODO this is not true once joins are supported + if (tableAnnotations.length != 1) { + throw new RuntimeException(entity.getQualifiedName() + " must have exactly one @JdbcGenTable annotation."); + } + + var tableAnnotation = tableAnnotations[0]; + var tableName = tableAnnotation.name(); + + if (tableName == null || tableName.isBlank()) { + throw new IllegalArgumentException("@JdbcGenTable(name) on " + + entity.getQualifiedName() + + " cannot be blank."); + } + + return tableAnnotation; + } + + private List getAndValidateColumnFields(TypeElement entity) { + var columnFields = entity.getEnclosedElements().stream() + .filter(element -> { + var annotations = element.getAnnotationsByType(JdbcGenColumn.class); + + if (annotations.length > 1) { + throw new RuntimeException(entity.getQualifiedName() + " must not have more than one @JdbcGenColumn annotation."); + } else if (annotations.length == 1) { + var name = annotations[0].name(); + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("@JdbcGenColumn(name) on " + + entity.getQualifiedName() + " cannot be blank."); + } + } + + return annotations.length == 1; + }) + .map(VariableElement.class::cast) + .collect(Collectors.toList()); + + if (columnFields.isEmpty()) { + throw new RuntimeException(entity.getQualifiedName() + " must have at least one field annotated with @JdbcGenColumn."); + } + + if (columnFields.stream().filter(field -> field.getAnnotationsByType(JdbcGenColumn.class)[0].identity()).count() != 1) { + throw new RuntimeException(entity.getQualifiedName() + " must have exactly one field annotated with @JdbcGenColumn(identity = true)."); + } + + return columnFields; + } + + private ExecutableElement resolveConstructor(TypeElement entity, List columnFields) { + var columnTypes = columnFields.stream().map(VariableElement::asType).collect(Collectors.toList()); + + var publicConstructors = entity.getEnclosedElements().stream() + .filter(element -> element.getKind() == ElementKind.CONSTRUCTOR) + .map(ExecutableElement.class::cast) + .filter(element -> !element.getModifiers().contains(Modifier.PRIVATE)) + .collect(Collectors.toList()); + + ExecutableElement defaultConstructor = null; + + for (var constructor : publicConstructors) { + var paramTypes = constructor.getParameters().stream().map(VariableElement::asType).collect(Collectors.toList()); + + if (Objects.equals(columnTypes, paramTypes)) { + return constructor; + } else if (paramTypes.isEmpty()) { + defaultConstructor = constructor; + } + } + + if (defaultConstructor == null) { + throw new RuntimeException(entity.getQualifiedName() + " must hava non-private default constructor or canonical constructor."); + } + + return defaultConstructor; + } + + private List resolveColumnSpecs(TypeElement entity, + List columnFields, + boolean hasCanonicalConstructor) { + var columnSpecs = new ArrayList(); + + var candidateMethodMap = entity.getEnclosedElements().stream() + .filter(element -> element.getKind() == ElementKind.METHOD) + .map(ExecutableElement.class::cast) + .filter(element -> !element.getModifiers().contains(Modifier.PRIVATE)) + .collect(Collectors.toMap(element -> element.getSimpleName().toString(), Function.identity())); + + for (var columnField : columnFields) { + var name = columnField.getSimpleName().toString(); + var fieldIsPrivate = columnField.getModifiers().contains(Modifier.PRIVATE); + var columnAnnotation = columnField.getAnnotationsByType(JdbcGenColumn.class)[0]; + var columnSpecBuilder = ColumnSpec.builder() + .withColumnName(columnAnnotation.name()) + .withIdentity(columnAnnotation.identity()) + .withFieldElement(columnField); + + var getter = candidateMethodMap.get(getterName(columnField)); + if (getter != null + && getter.getParameters().isEmpty() + && columnField.asType().equals(getter.getReturnType())) { + columnSpecBuilder.withGetterElement(getter) + .withGetMethod(FieldGetMethod.GETTER); + } else if (fieldIsPrivate) { + throw new RuntimeException(entity.getQualifiedName() + "." + name + " must either be non-private or have a non-private getter."); + } else { + columnSpecBuilder.withGetMethod(FieldGetMethod.DIRECT); + } + + var setter = candidateMethodMap.get(BeanUtil.setterName(name)); + if (setter != null + && setter.getParameters().size() == 1 + && columnField.asType().equals(setter.getParameters().get(0).asType())) { + columnSpecBuilder.withSetterElement(setter); + + if (!hasCanonicalConstructor) { + columnSpecBuilder.withSetMethod(FieldSetMethod.SETTER); + } else { + columnSpecBuilder.withSetMethod(FieldSetMethod.CONSTRUCTOR); + } + } else if (hasCanonicalConstructor) { + columnSpecBuilder.withSetMethod(FieldSetMethod.CONSTRUCTOR); + } else if (!fieldIsPrivate && !columnField.getModifiers().contains(Modifier.FINAL)) { + columnSpecBuilder.withSetMethod(FieldSetMethod.DIRECT); + } else { + throw new RuntimeException(entity.getQualifiedName() + "." + name + " must be writable by one of the following, non-private mechanisms: " + + "canonical constructor, setter, or non-final field."); + } + + columnSpecs.add(columnSpecBuilder.build()); + } + + return columnSpecs; + } + + private String getterName(VariableElement field) { + return BeanUtil.getterName(field.getSimpleName().toString(), + processingEnv.getTypeUtils().getPrimitiveType(TypeKind.BOOLEAN).equals(field.asType())); + } + +} diff --git a/processor/src/main/java/com/pwinckles/jdbcgen/processor/EntitySpec.java b/processor/src/main/java/com/pwinckles/jdbcgen/processor/EntitySpec.java new file mode 100644 index 0000000..a733c6d --- /dev/null +++ b/processor/src/main/java/com/pwinckles/jdbcgen/processor/EntitySpec.java @@ -0,0 +1,134 @@ +package com.pwinckles.jdbcgen.processor; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import java.util.List; +import java.util.Objects; + +public class EntitySpec { + + private final String packageName; + private final String dbClassName; + private final String tableName; + private final TypeElement typeElement; + private final ExecutableElement constructorElement; + private final ColumnSpec identityColumn; + private final List columns; + private final boolean canonicalConstructor; + + public static Builder builder() { + return new Builder(); + } + + private EntitySpec(String packageName, + String dbClassName, + String tableName, + TypeElement typeElement, + ExecutableElement constructorElement, + ColumnSpec identityColumn, + List columns, + boolean canonicalConstructor) { + this.packageName = Objects.requireNonNull(packageName, "packageName cannot be null"); + this.dbClassName = Objects.requireNonNull(dbClassName, "dbClassName cannot be null"); + this.tableName = Objects.requireNonNull(tableName, "tableName cannot be null"); + this.typeElement = Objects.requireNonNull(typeElement, "typeElement cannot be null"); + this.constructorElement = Objects.requireNonNull(constructorElement, "constructorElement cannot be null"); + this.identityColumn = Objects.requireNonNull(identityColumn, "identityColumn cannot be null"); + this.columns = Objects.requireNonNull(columns, "columns cannot be null"); + this.canonicalConstructor = canonicalConstructor; + } + + public String getPackageName() { + return packageName; + } + + public String getDbClassName() { + return dbClassName; + } + + public String getTableName() { + return tableName; + } + + public TypeElement getTypeElement() { + return typeElement; + } + + public ExecutableElement getConstructorElement() { + return constructorElement; + } + + public ColumnSpec getIdentityColumn() { + return identityColumn; + } + + public List getColumns() { + return columns; + } + + public boolean isCanonicalConstructor() { + return canonicalConstructor; + } + + public static class Builder { + private String packageName; + private String dbClassName; + private String tableName; + private TypeElement typeElement; + private ExecutableElement constructorElement; + private ColumnSpec identityColumn; + private List columns; + private boolean canonicalConstructor; + + public Builder withPackageName(String packageName) { + this.packageName = packageName; + return this; + } + + public Builder withDbClassName(String dbClassName) { + this.dbClassName = dbClassName; + return this; + } + + public Builder withTableName(String tableName) { + this.tableName = tableName; + return this; + } + + public Builder withTypeElement(TypeElement typeElement) { + this.typeElement = typeElement; + return this; + } + + public Builder withConstructorElement(ExecutableElement constructorElement) { + this.constructorElement = constructorElement; + return this; + } + + public Builder withIdentityColumn(ColumnSpec identityColumn) { + this.identityColumn = identityColumn; + return this; + } + + public Builder withColumns(List columns) { + this.columns = columns; + return this; + } + + public Builder withCanonicalConstructor(boolean canonicalConstructor) { + this.canonicalConstructor = canonicalConstructor; + return this; + } + + public EntitySpec build() { + return new EntitySpec(packageName, + dbClassName, + tableName, + typeElement, + constructorElement, + identityColumn, + columns, + canonicalConstructor); + } + } +} diff --git a/processor/src/main/java/com/pwinckles/jdbcgen/processor/FieldGetMethod.java b/processor/src/main/java/com/pwinckles/jdbcgen/processor/FieldGetMethod.java new file mode 100644 index 0000000..cd454b5 --- /dev/null +++ b/processor/src/main/java/com/pwinckles/jdbcgen/processor/FieldGetMethod.java @@ -0,0 +1,8 @@ +package com.pwinckles.jdbcgen.processor; + +public enum FieldGetMethod { + + DIRECT, + GETTER + +} diff --git a/processor/src/main/java/com/pwinckles/jdbcgen/processor/FieldSetMethod.java b/processor/src/main/java/com/pwinckles/jdbcgen/processor/FieldSetMethod.java new file mode 100644 index 0000000..0663cbe --- /dev/null +++ b/processor/src/main/java/com/pwinckles/jdbcgen/processor/FieldSetMethod.java @@ -0,0 +1,9 @@ +package com.pwinckles.jdbcgen.processor; + +public enum FieldSetMethod { + + DIRECT, + SETTER, + CONSTRUCTOR + +} diff --git a/processor/src/main/java/com/pwinckles/jdbcgen/processor/JdbcGenProcessor.java b/processor/src/main/java/com/pwinckles/jdbcgen/processor/JdbcGenProcessor.java new file mode 100644 index 0000000..c064d5b --- /dev/null +++ b/processor/src/main/java/com/pwinckles/jdbcgen/processor/JdbcGenProcessor.java @@ -0,0 +1,66 @@ +package com.pwinckles.jdbcgen.processor; + +import com.pwinckles.jdbcgen.JdbcGen; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@SupportedAnnotationTypes({ + "com.pwinckles.jdbcgen.JdbcGen" +}) +@SupportedSourceVersion(SourceVersion.RELEASE_11) +public class JdbcGenProcessor extends AbstractProcessor { + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + try { + var entityAnalyzer = new EntityAnalyzer(processingEnv); + var dbClassGenerator = new DbClassGenerator(); + + var entitySpecs = roundEnv.getElementsAnnotatedWith(JdbcGen.class).stream() + .map(TypeElement.class::cast) + .map(entityAnalyzer::analyze) + .collect(Collectors.toList()); + + validateGeneratedNames(entitySpecs); + + for (var entitySpec : entitySpecs) { + var javaFile = dbClassGenerator.generate(entitySpec); + javaFile.writeTo(processingEnv.getFiler()); + } + } catch (Exception e) { + var message = e.getMessage(); + if (message == null) { + message = "JdbcGen failed with exception: " + e; + } + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message); + } + return true; + } + + private void validateGeneratedNames(List entitySpecs) { + var seenFqns = new HashMap(); + + for (var entitySpec : entitySpecs) { + var fqn = entitySpec.getPackageName() + "." + entitySpec.getDbClassName(); + var existing = seenFqns.get(fqn); + if (existing != null) { + throw new RuntimeException(existing.getTypeElement().getQualifiedName() + + " and " + entitySpec.getTypeElement().getQualifiedName() + " both generate " + + fqn + ". Either rename one of the classes or set the @JdbcGen(name) attribute to change the name of the generated class."); + } else { + seenFqns.put(fqn, entitySpec); + } + } + } + +} diff --git a/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..f8a9e48 --- /dev/null +++ b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.pwinckles.jdbcgen.processor.JdbcGenProcessor diff --git a/processor/src/test/java/com/pwinckles/jdbcgen/processor/JdbcGenProcessorTest.java b/processor/src/test/java/com/pwinckles/jdbcgen/processor/JdbcGenProcessorTest.java new file mode 100644 index 0000000..f72e78a --- /dev/null +++ b/processor/src/test/java/com/pwinckles/jdbcgen/processor/JdbcGenProcessorTest.java @@ -0,0 +1,58 @@ +package com.pwinckles.jdbcgen.processor; + +import io.toolisticon.cute.CompileTestBuilder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +public class JdbcGenProcessorTest { + + private static Stream invalidTests() { + return Stream.of( + Arguments.of("MissingTableAnnotation", + "com.pwinckles.jdbcgen.processor.invalid.MissingTableAnnotation must have exactly one @JdbcGenTable annotation."), + Arguments.of("MissingColumnAnnotation", + "com.pwinckles.jdbcgen.processor.invalid.MissingColumnAnnotation must have at least one field annotated with @JdbcGenColumn."), + Arguments.of("MissingIdentityAnnotation", + "com.pwinckles.jdbcgen.processor.invalid.MissingIdentityAnnotation must have exactly one field annotated with @JdbcGenColumn(identity = true)."), + Arguments.of("NonStaticInnerClass", + "com.pwinckles.jdbcgen.processor.invalid.NonStaticInnerClass.Inner must be static."), + Arguments.of("PrivateClass", + "com.pwinckles.jdbcgen.processor.invalid.PrivateClass.Inner must not be private."), + Arguments.of("AbstractClass", + "com.pwinckles.jdbcgen.processor.invalid.AbstractClass must not be abstract."), + Arguments.of("GenericClass", + "com.pwinckles.jdbcgen.processor.invalid.GenericClass must not have type parameters."), + Arguments.of("MissingGetter", + "com.pwinckles.jdbcgen.processor.invalid.MissingGetter.id must either be non-private or have a non-private getter."), + Arguments.of("MissingSetter", + "com.pwinckles.jdbcgen.processor.invalid.MissingSetter.id must be writable by one of the following, non-private mechanisms: canonical constructor, setter, or non-final field."), + Arguments.of("GetterWrongType", + "com.pwinckles.jdbcgen.processor.invalid.GetterWrongType.id must either be non-private or have a non-private getter."), + Arguments.of("SetterWrongType", + "com.pwinckles.jdbcgen.processor.invalid.SetterWrongType.id must be writable by one of the following, non-private mechanisms: canonical constructor, setter, or non-final field."), + Arguments.of("PrivateGetter", + "com.pwinckles.jdbcgen.processor.invalid.PrivateGetter.id must either be non-private or have a non-private getter."), + Arguments.of("PrivateSetter", + "com.pwinckles.jdbcgen.processor.invalid.PrivateSetter.id must be writable by one of the following, non-private mechanisms: canonical constructor, setter, or non-final field."), + Arguments.of("PrivateConstructor", + "com.pwinckles.jdbcgen.processor.invalid.PrivateConstructor must hava non-private default constructor or canonical constructor."), + Arguments.of("Collision", + "com.pwinckles.jdbcgen.processor.invalid.Collision.First and com.pwinckles.jdbcgen.processor.invalid.Collision.Second both generate com.pwinckles.jdbcgen.processor.invalid.FirstDb. Either rename one of the classes or set the @JdbcGen(name) attribute to change the name of the generated class.") + ); + } + + @ParameterizedTest + @MethodSource("invalidTests") + public void executeInvalidTest(String className, String errorMessage) { + CompileTestBuilder.compilationTest() + .addProcessors(JdbcGenProcessor.class) + .addSources("invalid/" + className + ".java") + .compilationShouldFail() + .expectErrorMessageThatContains(errorMessage) + .executeTest(); + } + +} diff --git a/processor/src/test/resources/invalid/AbstractClass.java b/processor/src/test/resources/invalid/AbstractClass.java new file mode 100644 index 0000000..0ed2bfc --- /dev/null +++ b/processor/src/test/resources/invalid/AbstractClass.java @@ -0,0 +1,17 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public abstract class AbstractClass { + + @JdbcGenColumn(name = "id", identity = true) + Long id; + + @JdbcGenColumn(name = "val") + String value; + +} diff --git a/processor/src/test/resources/invalid/Collision.java b/processor/src/test/resources/invalid/Collision.java new file mode 100644 index 0000000..188adee --- /dev/null +++ b/processor/src/test/resources/invalid/Collision.java @@ -0,0 +1,29 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +public class Collision { + + @JdbcGen + @JdbcGenTable(name = " test") + public static class First { + @JdbcGenColumn(name = "id", identity = true) + Long id; + + @JdbcGenColumn(name = "val") + String value; + } + + @JdbcGen(name = "FirstDb") + @JdbcGenTable(name = " test") + public static class Second { + @JdbcGenColumn(name = "id", identity = true) + Long id; + + @JdbcGenColumn(name = "val") + String value; + } + +} diff --git a/processor/src/test/resources/invalid/GenericClass.java b/processor/src/test/resources/invalid/GenericClass.java new file mode 100644 index 0000000..0e213c8 --- /dev/null +++ b/processor/src/test/resources/invalid/GenericClass.java @@ -0,0 +1,17 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public class GenericClass { + + @JdbcGenColumn(name = "id", identity = true) + I id; + + @JdbcGenColumn(name = "val") + String value; + +} diff --git a/processor/src/test/resources/invalid/GetterWrongType.java b/processor/src/test/resources/invalid/GetterWrongType.java new file mode 100644 index 0000000..bcaae3e --- /dev/null +++ b/processor/src/test/resources/invalid/GetterWrongType.java @@ -0,0 +1,34 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public class GetterWrongType { + + @JdbcGenColumn(name = "id", identity = true) + private Long id; + + @JdbcGenColumn(name = "val") + private String value; + + public Integer getId() { + return (Integer) id; + } + + public GetterWrongType setId(Long id) { + this.id = id; + return this; + } + + public String getValue() { + return value; + } + + public GetterWrongType setValue(String value) { + this.value = value; + return this; + } +} diff --git a/processor/src/test/resources/invalid/MissingColumnAnnotation.java b/processor/src/test/resources/invalid/MissingColumnAnnotation.java new file mode 100644 index 0000000..10c8e8e --- /dev/null +++ b/processor/src/test/resources/invalid/MissingColumnAnnotation.java @@ -0,0 +1,15 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = "test") +public class MissingColumnAnnotation { + + Long id; + + String value; + +} diff --git a/processor/src/test/resources/invalid/MissingGetter.java b/processor/src/test/resources/invalid/MissingGetter.java new file mode 100644 index 0000000..583fa99 --- /dev/null +++ b/processor/src/test/resources/invalid/MissingGetter.java @@ -0,0 +1,30 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public class MissingGetter { + + @JdbcGenColumn(name = "id", identity = true) + private Long id; + + @JdbcGenColumn(name = "val") + private String value; + + public MissingGetter setId(Long id) { + this.id = id; + return this; + } + + public String getValue() { + return value; + } + + public MissingGetter setValue(String value) { + this.value = value; + return this; + } +} diff --git a/processor/src/test/resources/invalid/MissingIdentityAnnotation.java b/processor/src/test/resources/invalid/MissingIdentityAnnotation.java new file mode 100644 index 0000000..33b3eb8 --- /dev/null +++ b/processor/src/test/resources/invalid/MissingIdentityAnnotation.java @@ -0,0 +1,17 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = "test") +public class MissingIdentityAnnotation { + + @JdbcGenColumn(name = "id") + Long id; + + @JdbcGenColumn(name = "val") + String value; + +} diff --git a/processor/src/test/resources/invalid/MissingSetter.java b/processor/src/test/resources/invalid/MissingSetter.java new file mode 100644 index 0000000..6b7f668 --- /dev/null +++ b/processor/src/test/resources/invalid/MissingSetter.java @@ -0,0 +1,29 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public class MissingSetter { + + @JdbcGenColumn(name = "id", identity = true) + private Long id; + + @JdbcGenColumn(name = "val") + private String value; + + public Long getId() { + return id; + } + + public String getValue() { + return value; + } + + public MissingSetter setValue(String value) { + this.value = value; + return this; + } +} diff --git a/processor/src/test/resources/invalid/MissingTableAnnotation.java b/processor/src/test/resources/invalid/MissingTableAnnotation.java new file mode 100644 index 0000000..352957a --- /dev/null +++ b/processor/src/test/resources/invalid/MissingTableAnnotation.java @@ -0,0 +1,15 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; + +@JdbcGen +public class MissingTableAnnotation { + + @JdbcGenColumn(name = "id", identity = true) + Long id; + + @JdbcGenColumn(name = "val") + String value; + +} diff --git a/processor/src/test/resources/invalid/NonStaticInnerClass.java b/processor/src/test/resources/invalid/NonStaticInnerClass.java new file mode 100644 index 0000000..028434d --- /dev/null +++ b/processor/src/test/resources/invalid/NonStaticInnerClass.java @@ -0,0 +1,19 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +public class NonStaticInnerClass { + + @JdbcGen + @JdbcGenTable(name = "test") + public class Inner { + @JdbcGenColumn(name = "id", identity = true) + Long id; + + @JdbcGenColumn(name = "val") + String value; + } + +} diff --git a/processor/src/test/resources/invalid/PrivateClass.java b/processor/src/test/resources/invalid/PrivateClass.java new file mode 100644 index 0000000..404aff4 --- /dev/null +++ b/processor/src/test/resources/invalid/PrivateClass.java @@ -0,0 +1,19 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +public class PrivateClass { + + @JdbcGen + @JdbcGenTable(name = " test") + private static class Inner { + @JdbcGenColumn(name = "id", identity = true) + Long id; + + @JdbcGenColumn(name = "val") + String value; + } + +} diff --git a/processor/src/test/resources/invalid/PrivateConstructor.java b/processor/src/test/resources/invalid/PrivateConstructor.java new file mode 100644 index 0000000..506feb5 --- /dev/null +++ b/processor/src/test/resources/invalid/PrivateConstructor.java @@ -0,0 +1,38 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public class PrivateConstructor { + + @JdbcGenColumn(name = "id", identity = true) + private Long id; + + @JdbcGenColumn(name = "val") + private String value; + + private PrivateConstructor() { + + } + + public Long getId() { + return id; + } + + public PrivateConstructor setId(Long id) { + this.id = id; + return this; + } + + public String getValue() { + return value; + } + + public PrivateConstructor setValue(String value) { + this.value = value; + return this; + } +} diff --git a/processor/src/test/resources/invalid/PrivateGetter.java b/processor/src/test/resources/invalid/PrivateGetter.java new file mode 100644 index 0000000..b0d90c4 --- /dev/null +++ b/processor/src/test/resources/invalid/PrivateGetter.java @@ -0,0 +1,34 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public class PrivateGetter { + + @JdbcGenColumn(name = "id", identity = true) + private Long id; + + @JdbcGenColumn(name = "val") + private String value; + + private Long getId() { + return id; + } + + public PrivateGetter setId(Long id) { + this.id = id; + return this; + } + + public String getValue() { + return value; + } + + public PrivateGetter setValue(String value) { + this.value = value; + return this; + } +} diff --git a/processor/src/test/resources/invalid/PrivateSetter.java b/processor/src/test/resources/invalid/PrivateSetter.java new file mode 100644 index 0000000..500cc90 --- /dev/null +++ b/processor/src/test/resources/invalid/PrivateSetter.java @@ -0,0 +1,34 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public class PrivateSetter { + + @JdbcGenColumn(name = "id", identity = true) + private Long id; + + @JdbcGenColumn(name = "val") + private String value; + + public Long getId() { + return id; + } + + private PrivateSetter setId(Long id) { + this.id = id; + return this; + } + + public String getValue() { + return value; + } + + public PrivateSetter setValue(String value) { + this.value = value; + return this; + } +} diff --git a/processor/src/test/resources/invalid/SetterWrongType.java b/processor/src/test/resources/invalid/SetterWrongType.java new file mode 100644 index 0000000..9ac554e --- /dev/null +++ b/processor/src/test/resources/invalid/SetterWrongType.java @@ -0,0 +1,34 @@ +package com.pwinckles.jdbcgen.processor.invalid; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = " test") +public class SetterWrongType { + + @JdbcGenColumn(name = "id", identity = true) + private Long id; + + @JdbcGenColumn(name = "val") + private String value; + + public Long getId() { + return id; + } + + public SetterWrongType setId(Integer id) { + this.id = (Long) id; + return this; + } + + public String getValue() { + return value; + } + + public SetterWrongType setValue(String value) { + this.value = value; + return this; + } +} diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..f87f5c1 --- /dev/null +++ b/test/README.md @@ -0,0 +1 @@ +# TODO \ No newline at end of file diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 0000000..ee50778 --- /dev/null +++ b/test/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + + com.pwinckles.jdbcgen + jdbc-gen-parent + 1.0-SNAPSHOT + ../pom.xml + + + jdbc-gen-test + 1.0-SNAPSHOT + + jdbc-gen-test + Tests for jdbc-gen + + + + com.pwinckles.jdbcgen + jdbc-gen-processor + 1.0-SNAPSHOT + + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + org.hsqldb + hsqldb + test + + + com.h2database + h2 + test + + + org.apache.commons + commons-lang3 + test + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + + + + + + diff --git a/test/src/main/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntity.java b/test/src/main/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntity.java new file mode 100644 index 0000000..e2f72b4 --- /dev/null +++ b/test/src/main/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntity.java @@ -0,0 +1,100 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.UUID; + +@JdbcGen +@JdbcGenTable(name = "all_types") +public class DirectAllTypesEntity implements Cloneable { + + @JdbcGenColumn(name = "at_id", identity = true) + public Long longId; + + @JdbcGenColumn(name = "at_long_prim") + public long longPrim; + + @JdbcGenColumn(name = "at_int_obj") + public Integer intObj; + + @JdbcGenColumn(name = "at_int_prim") + public int intPrim; + + @JdbcGenColumn(name = "at_short_obj") + public Short shortObj; + + @JdbcGenColumn(name = "at_short_prim") + public short shortPrim; + + @JdbcGenColumn(name = "at_double_obj") + public Double doubleObj; + + @JdbcGenColumn(name = "at_double_prim") + public double doublePrim; + + @JdbcGenColumn(name = "at_bool_obj") + public Boolean boolObj; + + @JdbcGenColumn(name = "at_bool_prim") + public boolean boolPrim; + + @JdbcGenColumn(name = "at_string") + public String string; + + @JdbcGenColumn(name = "at_instant") + public Instant instant; + + @JdbcGenColumn(name = "at_local_date_time") + public LocalDateTime localDateTime; + + @JdbcGenColumn(name = "at_local_date") + public LocalDate localDate; + + @JdbcGenColumn(name = "at_offset_date_time") + public OffsetDateTime offsetDateTime; + + @JdbcGenColumn(name = "at_date") + public Date date; + + @JdbcGenColumn(name = "at_timestamp") + public Timestamp timestamp; + + @JdbcGenColumn(name = "at_byte_array") + public byte[] byteArray; + + @JdbcGenColumn(name = "at_uuid") + public UUID uuid; + + @Override + public DirectAllTypesEntity clone() { + var clone = new DirectAllTypesEntity(); + clone.longId = longId; + clone.longPrim = longPrim; + clone.intObj = intObj; + clone.intPrim = intPrim; + clone.shortObj = shortObj; + clone.shortPrim = shortPrim; + clone.doubleObj = doubleObj; + clone.doublePrim = doublePrim; + clone.boolObj = boolObj; + clone.boolPrim = boolPrim; + clone.string = string; + clone.instant = instant; + clone.localDateTime = localDateTime; + clone.localDate = localDate; + clone.offsetDateTime = offsetDateTime; + clone.date = date; + clone.timestamp = timestamp; + clone.byteArray = byteArray; + clone.uuid = uuid; + return clone; + } +} diff --git a/test/src/main/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntity.java b/test/src/main/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntity.java new file mode 100644 index 0000000..af29c6d --- /dev/null +++ b/test/src/main/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntity.java @@ -0,0 +1,192 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.UUID; + +@JdbcGen +@JdbcGenTable(name = "all_types") +public class GetterConstructorAllTypesEntity { + + @JdbcGenColumn(name = "at_id", identity = true) + private final Long longId; + + @JdbcGenColumn(name = "at_long_prim") + private final long longPrim; + + @JdbcGenColumn(name = "at_int_obj") + private final Integer intObj; + + @JdbcGenColumn(name = "at_int_prim") + private final int intPrim; + + @JdbcGenColumn(name = "at_short_obj") + private final Short shortObj; + + @JdbcGenColumn(name = "at_short_prim") + private final short shortPrim; + + @JdbcGenColumn(name = "at_double_obj") + private final Double doubleObj; + + @JdbcGenColumn(name = "at_double_prim") + private final double doublePrim; + + @JdbcGenColumn(name = "at_bool_obj") + private final Boolean boolObj; + + @JdbcGenColumn(name = "at_bool_prim") + private final boolean boolPrim; + + @JdbcGenColumn(name = "at_string") + private final String string; + + @JdbcGenColumn(name = "at_instant") + private final Instant instant; + + @JdbcGenColumn(name = "at_local_date_time") + private final LocalDateTime localDateTime; + + @JdbcGenColumn(name = "at_local_date") + private final LocalDate localDate; + + @JdbcGenColumn(name = "at_offset_date_time") + private final OffsetDateTime offsetDateTime; + + @JdbcGenColumn(name = "at_date") + private final Date date; + + @JdbcGenColumn(name = "at_timestamp") + private final Timestamp timestamp; + + @JdbcGenColumn(name = "at_byte_array") + private final byte[] byteArray; + + @JdbcGenColumn(name = "at_uuid") + private final UUID uuid; + + public GetterConstructorAllTypesEntity(Long longId, + long longPrim, + Integer intObj, + int intPrim, + Short shortObj, + short shortPrim, + Double doubleObj, + double doublePrim, + Boolean boolObj, + boolean boolPrim, + String string, + Instant instant, + LocalDateTime localDateTime, + LocalDate localDate, + OffsetDateTime offsetDateTime, + Date date, + Timestamp timestamp, + byte[] byteArray, + UUID uuid) { + this.longId = longId; + this.longPrim = longPrim; + this.intObj = intObj; + this.intPrim = intPrim; + this.shortObj = shortObj; + this.shortPrim = shortPrim; + this.doubleObj = doubleObj; + this.doublePrim = doublePrim; + this.boolObj = boolObj; + this.boolPrim = boolPrim; + this.string = string; + this.instant = instant; + this.localDateTime = localDateTime; + this.localDate = localDate; + this.offsetDateTime = offsetDateTime; + this.date = date; + this.timestamp = timestamp; + this.byteArray = byteArray; + this.uuid = uuid; + } + + public Long getLongId() { + return longId; + } + + public long getLongPrim() { + return longPrim; + } + + public Integer getIntObj() { + return intObj; + } + + public int getIntPrim() { + return intPrim; + } + + public Short getShortObj() { + return shortObj; + } + + public short getShortPrim() { + return shortPrim; + } + + public Double getDoubleObj() { + return doubleObj; + } + + public double getDoublePrim() { + return doublePrim; + } + + public Boolean getBoolObj() { + return boolObj; + } + + public boolean isBoolPrim() { + return boolPrim; + } + + public String getString() { + return string; + } + + public Instant getInstant() { + return instant; + } + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public OffsetDateTime getOffsetDateTime() { + return offsetDateTime; + } + + public Date getDate() { + return date; + } + + public Timestamp getTimestamp() { + return timestamp; + } + + public byte[] getByteArray() { + return byteArray; + } + + public UUID getUuid() { + return uuid; + } + +} diff --git a/test/src/main/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntity.java b/test/src/main/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntity.java new file mode 100644 index 0000000..1525491 --- /dev/null +++ b/test/src/main/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntity.java @@ -0,0 +1,271 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.UUID; + +@JdbcGen +@JdbcGenTable(name = "all_types") +public class GetterSetterAllTypesEntity implements Cloneable { + + @JdbcGenColumn(name = "at_id", identity = true) + private Long longId; + + @JdbcGenColumn(name = "at_long_prim") + private long longPrim; + + @JdbcGenColumn(name = "at_int_obj") + private Integer intObj; + + @JdbcGenColumn(name = "at_int_prim") + private int intPrim; + + @JdbcGenColumn(name = "at_short_obj") + private Short shortObj; + + @JdbcGenColumn(name = "at_short_prim") + private short shortPrim; + + @JdbcGenColumn(name = "at_double_obj") + private Double doubleObj; + + @JdbcGenColumn(name = "at_double_prim") + private double doublePrim; + + @JdbcGenColumn(name = "at_bool_obj") + private Boolean boolObj; + + @JdbcGenColumn(name = "at_bool_prim") + private boolean boolPrim; + + @JdbcGenColumn(name = "at_string") + private String string; + + @JdbcGenColumn(name = "at_instant") + private Instant instant; + + @JdbcGenColumn(name = "at_local_date_time") + private LocalDateTime localDateTime; + + @JdbcGenColumn(name = "at_local_date") + private LocalDate localDate; + + @JdbcGenColumn(name = "at_offset_date_time") + private OffsetDateTime offsetDateTime; + + @JdbcGenColumn(name = "at_date") + private Date date; + + @JdbcGenColumn(name = "at_timestamp") + private Timestamp timestamp; + + @JdbcGenColumn(name = "at_byte_array") + private byte[] byteArray; + + @JdbcGenColumn(name = "at_uuid") + private UUID uuid; + + public Long getLongId() { + return longId; + } + + public GetterSetterAllTypesEntity setLongId(Long longId) { + this.longId = longId; + return this; + } + + public long getLongPrim() { + return longPrim; + } + + public GetterSetterAllTypesEntity setLongPrim(long longPrim) { + this.longPrim = longPrim; + return this; + } + + public Integer getIntObj() { + return intObj; + } + + public GetterSetterAllTypesEntity setIntObj(Integer intObj) { + this.intObj = intObj; + return this; + } + + public int getIntPrim() { + return intPrim; + } + + public GetterSetterAllTypesEntity setIntPrim(int intPrim) { + this.intPrim = intPrim; + return this; + } + + public Short getShortObj() { + return shortObj; + } + + public GetterSetterAllTypesEntity setShortObj(Short shortObj) { + this.shortObj = shortObj; + return this; + } + + public short getShortPrim() { + return shortPrim; + } + + public GetterSetterAllTypesEntity setShortPrim(short shortPrim) { + this.shortPrim = shortPrim; + return this; + } + + public Double getDoubleObj() { + return doubleObj; + } + + public GetterSetterAllTypesEntity setDoubleObj(Double doubleObj) { + this.doubleObj = doubleObj; + return this; + } + + public double getDoublePrim() { + return doublePrim; + } + + public GetterSetterAllTypesEntity setDoublePrim(double doublePrim) { + this.doublePrim = doublePrim; + return this; + } + + public Boolean getBoolObj() { + return boolObj; + } + + public GetterSetterAllTypesEntity setBoolObj(Boolean boolObj) { + this.boolObj = boolObj; + return this; + } + + public boolean isBoolPrim() { + return boolPrim; + } + + public GetterSetterAllTypesEntity setBoolPrim(boolean boolPrim) { + this.boolPrim = boolPrim; + return this; + } + + public String getString() { + return string; + } + + public GetterSetterAllTypesEntity setString(String string) { + this.string = string; + return this; + } + + public Instant getInstant() { + return instant; + } + + public GetterSetterAllTypesEntity setInstant(Instant instant) { + this.instant = instant; + return this; + } + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public GetterSetterAllTypesEntity setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + return this; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public GetterSetterAllTypesEntity setLocalDate(LocalDate localDate) { + this.localDate = localDate; + return this; + } + + public OffsetDateTime getOffsetDateTime() { + return offsetDateTime; + } + + public GetterSetterAllTypesEntity setOffsetDateTime(OffsetDateTime offsetDateTime) { + this.offsetDateTime = offsetDateTime; + return this; + } + + public Date getDate() { + return date; + } + + public GetterSetterAllTypesEntity setDate(Date date) { + this.date = date; + return this; + } + + public Timestamp getTimestamp() { + return timestamp; + } + + public GetterSetterAllTypesEntity setTimestamp(Timestamp timestamp) { + this.timestamp = timestamp; + return this; + } + + public byte[] getByteArray() { + return byteArray; + } + + public GetterSetterAllTypesEntity setByteArray(byte[] byteArray) { + this.byteArray = byteArray; + return this; + } + + public UUID getUuid() { + return uuid; + } + + public GetterSetterAllTypesEntity setUuid(UUID uuid) { + this.uuid = uuid; + return this; + } + + @Override + public GetterSetterAllTypesEntity clone() { + var clone = new GetterSetterAllTypesEntity(); + clone.longId = longId; + clone.longPrim = longPrim; + clone.intObj = intObj; + clone.intPrim = intPrim; + clone.shortObj = shortObj; + clone.shortPrim = shortPrim; + clone.doubleObj = doubleObj; + clone.doublePrim = doublePrim; + clone.boolObj = boolObj; + clone.boolPrim = boolPrim; + clone.string = string; + clone.instant = instant; + clone.localDateTime = localDateTime; + clone.localDate = localDate; + clone.offsetDateTime = offsetDateTime; + clone.date = date; + clone.timestamp = timestamp; + clone.byteArray = byteArray; + clone.uuid = uuid; + return clone; + } +} diff --git a/test/src/main/java/com/pwinckles/jdbcgen/test/PrimitiveIdEntity.java b/test/src/main/java/com/pwinckles/jdbcgen/test/PrimitiveIdEntity.java new file mode 100644 index 0000000..edb5d30 --- /dev/null +++ b/test/src/main/java/com/pwinckles/jdbcgen/test/PrimitiveIdEntity.java @@ -0,0 +1,39 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +@JdbcGen +@JdbcGenTable(name = "primitive_id") +public class PrimitiveIdEntity implements Cloneable{ + + @JdbcGenColumn(name = "id", identity = true) + private long id; + + @JdbcGenColumn(name = "val") + private String value; + + public long getId() { + return id; + } + + public PrimitiveIdEntity setId(long id) { + this.id = id; + return this; + } + + public String getValue() { + return value; + } + + public PrimitiveIdEntity setValue(String value) { + this.value = value; + return this; + } + + @Override + public PrimitiveIdEntity clone() { + return new PrimitiveIdEntity().setId(id).setValue(value); + } +} diff --git a/test/src/main/java/com/pwinckles/jdbcgen/test/UuidIdEntity.java b/test/src/main/java/com/pwinckles/jdbcgen/test/UuidIdEntity.java new file mode 100644 index 0000000..4ea5587 --- /dev/null +++ b/test/src/main/java/com/pwinckles/jdbcgen/test/UuidIdEntity.java @@ -0,0 +1,41 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +import java.util.UUID; + +@JdbcGen +@JdbcGenTable(name = "uuid_id") +public class UuidIdEntity implements Cloneable{ + + @JdbcGenColumn(name = "id", identity = true) + private UUID id; + + @JdbcGenColumn(name = "val") + private String value; + + public UUID getId() { + return id; + } + + public UuidIdEntity setId(UUID id) { + this.id = id; + return this; + } + + public String getValue() { + return value; + } + + public UuidIdEntity setValue(String value) { + this.value = value; + return this; + } + + @Override + public UuidIdEntity clone() { + return new UuidIdEntity().setId(id).setValue(value); + } +} diff --git a/test/src/main/java/com/pwinckles/jdbcgen/test/Wrapper.java b/test/src/main/java/com/pwinckles/jdbcgen/test/Wrapper.java new file mode 100644 index 0000000..008e06e --- /dev/null +++ b/test/src/main/java/com/pwinckles/jdbcgen/test/Wrapper.java @@ -0,0 +1,541 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.JdbcGen; +import com.pwinckles.jdbcgen.JdbcGenColumn; +import com.pwinckles.jdbcgen.JdbcGenTable; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.UUID; + +class Wrapper { + + @JdbcGen(name = "DirectAllTypesEntityInnerDb") + @JdbcGenTable(name = "all_types") + static class DirectAllTypesEntity implements Cloneable { + + @JdbcGenColumn(name = "at_id", identity = true) + Long longId; + + @JdbcGenColumn(name = "at_long_prim") + long longPrim; + + @JdbcGenColumn(name = "at_int_obj") + Integer intObj; + + @JdbcGenColumn(name = "at_int_prim") + int intPrim; + + @JdbcGenColumn(name = "at_short_obj") + Short shortObj; + + @JdbcGenColumn(name = "at_short_prim") + short shortPrim; + + @JdbcGenColumn(name = "at_double_obj") + Double doubleObj; + + @JdbcGenColumn(name = "at_double_prim") + double doublePrim; + + @JdbcGenColumn(name = "at_bool_obj") + Boolean boolObj; + + @JdbcGenColumn(name = "at_bool_prim") + boolean boolPrim; + + @JdbcGenColumn(name = "at_string") + String string; + + @JdbcGenColumn(name = "at_instant") + Instant instant; + + @JdbcGenColumn(name = "at_local_date_time") + LocalDateTime localDateTime; + + @JdbcGenColumn(name = "at_local_date") + LocalDate localDate; + + @JdbcGenColumn(name = "at_offset_date_time") + OffsetDateTime offsetDateTime; + + @JdbcGenColumn(name = "at_date") + Date date; + + @JdbcGenColumn(name = "at_timestamp") + Timestamp timestamp; + + @JdbcGenColumn(name = "at_byte_array") + byte[] byteArray; + + @JdbcGenColumn(name = "at_uuid") + UUID uuid; + + @Override + public DirectAllTypesEntity clone() { + var clone = new DirectAllTypesEntity(); + clone.longId = longId; + clone.longPrim = longPrim; + clone.intObj = intObj; + clone.intPrim = intPrim; + clone.shortObj = shortObj; + clone.shortPrim = shortPrim; + clone.doubleObj = doubleObj; + clone.doublePrim = doublePrim; + clone.boolObj = boolObj; + clone.boolPrim = boolPrim; + clone.string = string; + clone.instant = instant; + clone.localDateTime = localDateTime; + clone.localDate = localDate; + clone.offsetDateTime = offsetDateTime; + clone.date = date; + clone.timestamp = timestamp; + clone.byteArray = byteArray; + clone.uuid = uuid; + return clone; + } + } + + @JdbcGen(name = "GetterConstructorAllTypesEntityInnerDb") + @JdbcGenTable(name = "all_types") + static class GetterConstructorAllTypesEntity { + + @JdbcGenColumn(name = "at_id", identity = true) + private final Long longId; + + @JdbcGenColumn(name = "at_long_prim") + private final long longPrim; + + @JdbcGenColumn(name = "at_int_obj") + private final Integer intObj; + + @JdbcGenColumn(name = "at_int_prim") + private final int intPrim; + + @JdbcGenColumn(name = "at_short_obj") + private final Short shortObj; + + @JdbcGenColumn(name = "at_short_prim") + private final short shortPrim; + + @JdbcGenColumn(name = "at_double_obj") + private final Double doubleObj; + + @JdbcGenColumn(name = "at_double_prim") + private final double doublePrim; + + @JdbcGenColumn(name = "at_bool_obj") + private final Boolean boolObj; + + @JdbcGenColumn(name = "at_bool_prim") + private final boolean boolPrim; + + @JdbcGenColumn(name = "at_string") + private final String string; + + @JdbcGenColumn(name = "at_instant") + private final Instant instant; + + @JdbcGenColumn(name = "at_local_date_time") + private final LocalDateTime localDateTime; + + @JdbcGenColumn(name = "at_local_date") + private final LocalDate localDate; + + @JdbcGenColumn(name = "at_offset_date_time") + private final OffsetDateTime offsetDateTime; + + @JdbcGenColumn(name = "at_date") + private final Date date; + + @JdbcGenColumn(name = "at_timestamp") + private final Timestamp timestamp; + + @JdbcGenColumn(name = "at_byte_array") + private final byte[] byteArray; + + @JdbcGenColumn(name = "at_uuid") + private final UUID uuid; + + GetterConstructorAllTypesEntity(Long longId, + long longPrim, + Integer intObj, + int intPrim, + Short shortObj, + short shortPrim, + Double doubleObj, + double doublePrim, + Boolean boolObj, + boolean boolPrim, + String string, + Instant instant, + LocalDateTime localDateTime, + LocalDate localDate, + OffsetDateTime offsetDateTime, + Date date, + Timestamp timestamp, + byte[] byteArray, + UUID uuid) { + this.longId = longId; + this.longPrim = longPrim; + this.intObj = intObj; + this.intPrim = intPrim; + this.shortObj = shortObj; + this.shortPrim = shortPrim; + this.doubleObj = doubleObj; + this.doublePrim = doublePrim; + this.boolObj = boolObj; + this.boolPrim = boolPrim; + this.string = string; + this.instant = instant; + this.localDateTime = localDateTime; + this.localDate = localDate; + this.offsetDateTime = offsetDateTime; + this.date = date; + this.timestamp = timestamp; + this.byteArray = byteArray; + this.uuid = uuid; + } + + Long getLongId() { + return longId; + } + + long getLongPrim() { + return longPrim; + } + + Integer getIntObj() { + return intObj; + } + + int getIntPrim() { + return intPrim; + } + + Short getShortObj() { + return shortObj; + } + + short getShortPrim() { + return shortPrim; + } + + Double getDoubleObj() { + return doubleObj; + } + + double getDoublePrim() { + return doublePrim; + } + + Boolean getBoolObj() { + return boolObj; + } + + boolean isBoolPrim() { + return boolPrim; + } + + String getString() { + return string; + } + + Instant getInstant() { + return instant; + } + + LocalDateTime getLocalDateTime() { + return localDateTime; + } + + LocalDate getLocalDate() { + return localDate; + } + + OffsetDateTime getOffsetDateTime() { + return offsetDateTime; + } + + Date getDate() { + return date; + } + + Timestamp getTimestamp() { + return timestamp; + } + + byte[] getByteArray() { + return byteArray; + } + + UUID getUuid() { + return uuid; + } + + } + + @JdbcGen(name = "GetterSetterAllTypesEntityInnerDb") + @JdbcGenTable(name = "all_types") + static class GetterSetterAllTypesEntity implements Cloneable { + + @JdbcGenColumn(name = "at_id", identity = true) + private Long longId; + + @JdbcGenColumn(name = "at_long_prim") + private long longPrim; + + @JdbcGenColumn(name = "at_int_obj") + private Integer intObj; + + @JdbcGenColumn(name = "at_int_prim") + private int intPrim; + + @JdbcGenColumn(name = "at_short_obj") + private Short shortObj; + + @JdbcGenColumn(name = "at_short_prim") + private short shortPrim; + + @JdbcGenColumn(name = "at_double_obj") + private Double doubleObj; + + @JdbcGenColumn(name = "at_double_prim") + private double doublePrim; + + @JdbcGenColumn(name = "at_bool_obj") + private Boolean boolObj; + + @JdbcGenColumn(name = "at_bool_prim") + private boolean boolPrim; + + @JdbcGenColumn(name = "at_string") + private String string; + + @JdbcGenColumn(name = "at_instant") + private Instant instant; + + @JdbcGenColumn(name = "at_local_date_time") + private LocalDateTime localDateTime; + + @JdbcGenColumn(name = "at_local_date") + private LocalDate localDate; + + @JdbcGenColumn(name = "at_offset_date_time") + private OffsetDateTime offsetDateTime; + + @JdbcGenColumn(name = "at_date") + private Date date; + + @JdbcGenColumn(name = "at_timestamp") + private Timestamp timestamp; + + @JdbcGenColumn(name = "at_byte_array") + private byte[] byteArray; + + @JdbcGenColumn(name = "at_uuid") + private UUID uuid; + + Long getLongId() { + return longId; + } + + GetterSetterAllTypesEntity setLongId(Long longId) { + this.longId = longId; + return this; + } + + long getLongPrim() { + return longPrim; + } + + GetterSetterAllTypesEntity setLongPrim(long longPrim) { + this.longPrim = longPrim; + return this; + } + + Integer getIntObj() { + return intObj; + } + + GetterSetterAllTypesEntity setIntObj(Integer intObj) { + this.intObj = intObj; + return this; + } + + int getIntPrim() { + return intPrim; + } + + GetterSetterAllTypesEntity setIntPrim(int intPrim) { + this.intPrim = intPrim; + return this; + } + + Short getShortObj() { + return shortObj; + } + + GetterSetterAllTypesEntity setShortObj(Short shortObj) { + this.shortObj = shortObj; + return this; + } + + short getShortPrim() { + return shortPrim; + } + + GetterSetterAllTypesEntity setShortPrim(short shortPrim) { + this.shortPrim = shortPrim; + return this; + } + + Double getDoubleObj() { + return doubleObj; + } + + GetterSetterAllTypesEntity setDoubleObj(Double doubleObj) { + this.doubleObj = doubleObj; + return this; + } + + double getDoublePrim() { + return doublePrim; + } + + GetterSetterAllTypesEntity setDoublePrim(double doublePrim) { + this.doublePrim = doublePrim; + return this; + } + + Boolean getBoolObj() { + return boolObj; + } + + GetterSetterAllTypesEntity setBoolObj(Boolean boolObj) { + this.boolObj = boolObj; + return this; + } + + boolean isBoolPrim() { + return boolPrim; + } + + GetterSetterAllTypesEntity setBoolPrim(boolean boolPrim) { + this.boolPrim = boolPrim; + return this; + } + + String getString() { + return string; + } + + GetterSetterAllTypesEntity setString(String string) { + this.string = string; + return this; + } + + Instant getInstant() { + return instant; + } + + GetterSetterAllTypesEntity setInstant(Instant instant) { + this.instant = instant; + return this; + } + + LocalDateTime getLocalDateTime() { + return localDateTime; + } + + GetterSetterAllTypesEntity setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + return this; + } + + LocalDate getLocalDate() { + return localDate; + } + + GetterSetterAllTypesEntity setLocalDate(LocalDate localDate) { + this.localDate = localDate; + return this; + } + + OffsetDateTime getOffsetDateTime() { + return offsetDateTime; + } + + GetterSetterAllTypesEntity setOffsetDateTime(OffsetDateTime offsetDateTime) { + this.offsetDateTime = offsetDateTime; + return this; + } + + Date getDate() { + return date; + } + + GetterSetterAllTypesEntity setDate(Date date) { + this.date = date; + return this; + } + + Timestamp getTimestamp() { + return timestamp; + } + + GetterSetterAllTypesEntity setTimestamp(Timestamp timestamp) { + this.timestamp = timestamp; + return this; + } + + byte[] getByteArray() { + return byteArray; + } + + GetterSetterAllTypesEntity setByteArray(byte[] byteArray) { + this.byteArray = byteArray; + return this; + } + + UUID getUuid() { + return uuid; + } + + GetterSetterAllTypesEntity setUuid(UUID uuid) { + this.uuid = uuid; + return this; + } + + @Override + public GetterSetterAllTypesEntity clone() { + var clone = new GetterSetterAllTypesEntity(); + clone.longId = longId; + clone.longPrim = longPrim; + clone.intObj = intObj; + clone.intPrim = intPrim; + clone.shortObj = shortObj; + clone.shortPrim = shortPrim; + clone.doubleObj = doubleObj; + clone.doublePrim = doublePrim; + clone.boolObj = boolObj; + clone.boolPrim = boolPrim; + clone.string = string; + clone.instant = instant; + clone.localDateTime = localDateTime; + clone.localDate = localDate; + clone.offsetDateTime = offsetDateTime; + clone.date = date; + clone.timestamp = timestamp; + clone.byteArray = byteArray; + clone.uuid = uuid; + return clone; + } + } + +} diff --git a/test/src/main/java/com/pwinckles/jdbcgen/test/prototype/Example.java b/test/src/main/java/com/pwinckles/jdbcgen/test/prototype/Example.java new file mode 100644 index 0000000..a9186b4 --- /dev/null +++ b/test/src/main/java/com/pwinckles/jdbcgen/test/prototype/Example.java @@ -0,0 +1,46 @@ +package com.pwinckles.jdbcgen.test.prototype; + +import java.time.Instant; + +public class Example { + + private Long id; + private String name; + private Instant timestamp; + + public Long getId() { + return id; + } + + public Example setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Example setName(String name) { + this.name = name; + return this; + } + + public Instant getTimestamp() { + return timestamp; + } + + public Example setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + return this; + } + + @Override + public String toString() { + return "Example{" + + "id=" + id + + ", name='" + name + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} diff --git a/test/src/main/java/com/pwinckles/jdbcgen/test/prototype/ExampleDb.java b/test/src/main/java/com/pwinckles/jdbcgen/test/prototype/ExampleDb.java new file mode 100644 index 0000000..305198a --- /dev/null +++ b/test/src/main/java/com/pwinckles/jdbcgen/test/prototype/ExampleDb.java @@ -0,0 +1,384 @@ +package com.pwinckles.jdbcgen.test.prototype; + +import com.pwinckles.jdbcgen.BasePatch; +import com.pwinckles.jdbcgen.JdbcGenDb; +import com.pwinckles.jdbcgen.OrderDirection; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class ExampleDb implements JdbcGenDb { + + // TODO javadoc + + public enum Column { + ID("id"), + NAME("name"), + TIMESTAMP("timestamp"); + + private final String value; + + Column(String value) { + this.value = value; + } + } + + public static class Patch extends BasePatch { + + public Long getId() { + return (Long) getData().get("id"); + } + + public Patch setId(Long id) { + put("id", id); + return this; + } + + public Patch setName(String name) { + put("name", name); + return this; + } + + public Patch setTimestamp(Instant timestamp) { + put("timestamp", timestamp); + return this; + } + + } + + /** + * {@inheritDoc} + */ + @Override + public Example select(Long id, Connection conn) throws SQLException { + try (var stmt = conn.prepareStatement("SELECT id, name, timestamp FROM example WHERE id = ? LIMIT 1")) { + stmt.setObject(1, id); + var rs = stmt.executeQuery(); + if (rs.next()) { + return fromResultSet(rs); + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public List selectAll(Connection conn) throws SQLException { + var results = new ArrayList(); + + try (var stmt = conn.createStatement()) { + var rs = stmt.executeQuery("SELECT id, name, timestamp FROM example"); + while (rs.next()) { + results.add(fromResultSet(rs)); + } + } + + return results; + } + + /** + * {@inheritDoc} + */ + @Override + public List selectAll(Column orderBy, OrderDirection direction, Connection conn) throws SQLException { + var results = new ArrayList(); + + try (var stmt = conn.createStatement()) { + var rs = stmt.executeQuery("SELECT id, name, timestamp FROM example ORDER BY " + orderBy.value + " " + direction.getValue()); + while (rs.next()) { + results.add(fromResultSet(rs)); + } + } + + return results; + } + + /** + * {@inheritDoc} + */ + @Override + public long count(Connection conn) throws SQLException { + try (var stmt = conn.createStatement()) { + var rs = stmt.executeQuery("SELECT COUNT(id) FROM example"); + if (rs.next()) { + return rs.getLong(1); + } + } + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public Long insert(Example entity, Connection conn) throws SQLException { + if (entity.getId() == null) { + return insertWithGeneratedId(entity, conn); + } + return insertWithSpecifiedId(entity, conn); + } + + private Long insertWithGeneratedId(Example entity, Connection conn) throws SQLException { + try (var stmt = conn.prepareStatement("INSERT INTO example (name, timestamp) VALUES (?, ?)", + Statement.RETURN_GENERATED_KEYS)) { + prepareInsert(entity, stmt); + stmt.executeUpdate(); + var rs = stmt.getGeneratedKeys(); + if (rs.next()) { + return rs.getLong(1); + } else { + throw new SQLException("Generated id was not returned."); + } + } + } + + private Long insertWithSpecifiedId(Example entity, Connection conn) throws SQLException { + try (var stmt = conn.prepareStatement("INSERT INTO example (id, name, timestamp) VALUES (?, ?, ?)")) { + prepareInsert(entity, stmt); + stmt.executeUpdate(); + } + return entity.getId(); + } + + /** + * {@inheritDoc} + */ + @Override + public Long insert(Patch entity, Connection conn) throws SQLException { + if (entity.getData().isEmpty()) { + throw new SQLException("No data specified"); + } + + boolean generatedId = entity.getId() == null; + var data = entity.getData(); + var keys = new ArrayList<>(data.keySet()); + + var queryBuilder = new StringBuilder("INSERT INTO example ("); + + for (var it = keys.iterator(); it.hasNext();) { + queryBuilder.append(it.next()); + if (it.hasNext()) { + queryBuilder.append(", "); + } + } + + queryBuilder.append(") VALUES ("); + + if (keys.size() > 1) { + queryBuilder.append("?, ".repeat(keys.size() - 1)); + } + queryBuilder.append("?)"); + + PreparedStatement stmt; + if (generatedId) { + stmt = conn.prepareStatement(queryBuilder.toString(), Statement.RETURN_GENERATED_KEYS); + } else { + stmt = conn.prepareStatement(queryBuilder.toString()); + } + + try { + for (int i = 0; i < keys.size(); i++) { + var value = data.get(keys.get(i)); + stmt.setObject(i + 1, value); + } + + stmt.executeUpdate(); + + if (generatedId) { + var rs = stmt.getGeneratedKeys(); + if (rs.next()) { + return rs.getLong(1); + } else { + throw new SQLException("Generated id was not returned."); + } + } else { + return entity.getId(); + } + } finally { + stmt.close(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List insert(List entities, Connection conn) throws SQLException { + if (!entities.isEmpty() && entities.get(0).getId() == null) { + return insertWithGeneratedId(entities, conn); + } + return insertWithSpecifiedId(entities, conn); + } + + private List insertWithGeneratedId(List entities, Connection conn) throws SQLException { + var ids = new ArrayList(); + + try (var stmt = conn.prepareStatement("INSERT INTO example (name, timestamp) VALUES (?, ?)", + Statement.RETURN_GENERATED_KEYS)) { + for (var entity : entities) { + prepareInsert(entity, stmt); + stmt.addBatch(); + } + + stmt.executeBatch(); + + var rs = stmt.getGeneratedKeys(); + while (rs.next()) { + ids.add(rs.getLong(1)); + } + } + + return ids; + } + + private List insertWithSpecifiedId(List entities, Connection conn) throws SQLException { + try (var stmt = conn.prepareStatement("INSERT INTO example (id, name, timestamp) VALUES (?, ?, ?)")) { + for (var entity : entities) { + prepareInsert(entity, stmt); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + + return entities.stream().map(Example::getId).collect(Collectors.toList()); + } + + /** + * {@inheritDoc} + */ + @Override + public int update(Example entity, Connection conn) throws SQLException { + try (var stmt = conn.prepareStatement("UPDATE example SET name = ?, timestamp = ? WHERE id = ?")) { + prepareUpdate(entity, stmt); + return stmt.executeUpdate(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int update(Long id, Patch entity, Connection conn) throws SQLException { + if (entity.getData().isEmpty()) { + throw new SQLException("No data specified"); + } + + var data = entity.getData(); + var keys = new ArrayList<>(data.keySet()); + + var queryBuilder = new StringBuilder("UPDATE example SET "); + + for (var it = keys.iterator(); it.hasNext();) { + queryBuilder.append(it.next()).append(" = ?"); + if (it.hasNext()) { + queryBuilder.append(", "); + } + } + + queryBuilder.append(" WHERE id = ?"); + + try (var stmt = conn.prepareStatement(queryBuilder.toString())) { + for (int i = 0; i < keys.size(); i++) { + var value = data.get(keys.get(i)); + stmt.setObject(i + 1, value); + } + stmt.setObject(keys.size() + 1, id); + + return stmt.executeUpdate(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int[] update(List entities, Connection conn) throws SQLException { + try (var stmt = conn.prepareStatement("UPDATE example SET name = ?, timestamp = ? WHERE id = ?")) { + for (var entity : entities) { + prepareUpdate(entity, stmt); + stmt.addBatch(); + } + + return stmt.executeBatch(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int delete(Long id, Connection conn) throws SQLException { + try (var stmt = conn.prepareStatement("DELETE FROM example WHERE id = ?")) { + stmt.setObject(1, id); + return stmt.executeUpdate(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int[] delete(List ids, Connection conn) throws SQLException { + try (var stmt = conn.prepareStatement("DELETE FROM example WHERE id = ?")) { + for (var id : ids) { + stmt.setObject(1, id); + stmt.addBatch(); + } + return stmt.executeBatch(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int deleteAll(Connection conn) throws SQLException { + try (var stmt = conn.createStatement()) { + return stmt.executeUpdate("DELETE FROM example"); + } + } + + private Example fromResultSet(ResultSet rs) throws SQLException { + int i = 1; + var entity = new Example(); + entity.setId(getNullableValue(rs, i++, Long.class)); + entity.setName(getNullableValue(rs, i++, String.class)); + entity.setTimestamp(getNullableValue(rs, i++, Instant.class)); + return entity; + } + + private T getNullableValue(ResultSet rs, int index, Class clazz) throws SQLException { + var value = rs.getObject(index, clazz); + if (rs.wasNull()) { + return null; + } + return value; + } + + private void prepareInsert(Example entity, PreparedStatement stmt) throws SQLException { + int i = 1; + if (entity.getId() != null) { + stmt.setObject(i++, entity.getId()); + } + stmt.setObject(i++, entity.getName()); + stmt.setObject(i++, entity.getTimestamp()); + } + + private void prepareUpdate(Example entity, PreparedStatement stmt) throws SQLException { + int i = 1; + stmt.setObject(i++, entity.getName()); + stmt.setObject(i++, entity.getTimestamp()); + stmt.setObject(i++, entity.getId()); + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/BaseAllTypesTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/BaseAllTypesTest.java new file mode 100644 index 0000000..c7edb72 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/BaseAllTypesTest.java @@ -0,0 +1,47 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.BasePatch; +import com.pwinckles.jdbcgen.JdbcGenDb; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class BaseAllTypesTest extends TestBase { + + protected BaseAllTypesTest(JdbcGenDb db) { + super(db); + } + + @Override + protected void createTable(Connection conn) throws SQLException { + try (var stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE all_types (" + + "at_id IDENTITY PRIMARY KEY," + + " at_long_prim BIGINT NOT NULL," + + " at_int_obj INT," + + " at_int_prim INT NOT NULL," + + " at_short_obj SMALLINT," + + " at_short_prim SMALLINT NOT NULL," + + " at_double_obj DOUBLE," + + " at_double_prim DOUBLE NOT NULL," + + " at_bool_obj BOOLEAN," + + " at_bool_prim BOOLEAN NOT NULL," + + " at_string VARCHAR(255)," + + " at_instant TIMESTAMP," + + " at_local_date_time TIMESTAMP," + + " at_local_date DATE," + + " at_offset_date_time TIMESTAMP WITH TIME ZONE," + + " at_date DATE," + + " at_timestamp TIMESTAMP," + + " at_byte_array BLOB," + + " at_uuid UUID" + + ")"); + } + } +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntityDbTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntityDbTest.java new file mode 100644 index 0000000..bf23c19 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntityDbTest.java @@ -0,0 +1,169 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.test.util.TestUtil; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.UUID; + +public class DirectAllTypesEntityDbTest extends BaseAllTypesTest { + + public DirectAllTypesEntityDbTest() { + super(new DirectAllTypesEntityDb()); + } + + @Override + protected Long getId(DirectAllTypesEntity entity) { + return entity.longId; + } + + @Override + protected DirectAllTypesEntity setId(Long id, DirectAllTypesEntity entity) { + var updated = entity.clone(); + updated.longId = id; + return updated; + } + + @Override + protected DirectAllTypesEntity newEntity() { + var entity = new DirectAllTypesEntity(); + entity.intObj = RandomUtils.nextInt(); + entity.intPrim = RandomUtils.nextInt(); + entity.shortObj = (short) 1; + entity.shortPrim = (short) 2; + entity.doubleObj = RandomUtils.nextDouble(); + entity.doublePrim = RandomUtils.nextDouble(); + entity.boolObj = true; + entity.boolPrim = false; + entity.string = RandomStringUtils.randomAlphanumeric(10); + entity.instant = TestUtil.now(); + entity.localDateTime = TestUtil.nowLocalDateTime(); + entity.localDate = LocalDate.now(); + entity.offsetDateTime = TestUtil.nowOffsetDateTime(); + entity.date = new Date(2023, 6, 25); + entity.timestamp = new Timestamp(System.currentTimeMillis()); + entity.byteArray = RandomUtils.nextBytes(10); + entity.uuid = UUID.randomUUID(); + return entity; + } + + @Override + protected DirectAllTypesEntity newEntityWithId() { + var entity = newEntity(); + entity.longId = nextId(); + return entity; + } + + @Override + protected DirectAllTypesEntity updateEntity(DirectAllTypesEntity entity) { + var updated = entity.clone(); + updated.intObj = RandomUtils.nextInt(); + updated.intPrim = RandomUtils.nextInt(); + updated.shortObj = (short) 3; + updated.shortPrim = (short) 4; + updated.doubleObj = RandomUtils.nextDouble(); + updated.doublePrim = RandomUtils.nextDouble(); + updated.boolObj = false; + updated.boolPrim = true; + updated.string = RandomStringUtils.randomAlphanumeric(10); + updated.instant = TestUtil.now(); + updated.localDateTime = TestUtil.nowLocalDateTime(); + updated.localDate = LocalDate.now(); + updated.offsetDateTime = TestUtil.nowOffsetDateTime(); + updated.date = new Date(2023, 6, 26); + updated.timestamp = new Timestamp(System.currentTimeMillis()); + updated.byteArray = RandomUtils.nextBytes(10); + updated.uuid = UUID.randomUUID(); + return updated; + } + + @Override + protected DirectAllTypesEntityDb.Patch patchAll(DirectAllTypesEntity entity) { + return new DirectAllTypesEntityDb.Patch() + .setLongPrim(entity.longPrim) + .setIntObj(entity.intObj) + .setIntPrim(entity.intPrim) + .setShortObj(entity.shortObj) + .setShortPrim(entity.shortPrim) + .setDoubleObj(entity.doubleObj) + .setDoublePrim(entity.doublePrim) + .setBoolObj(entity.boolObj) + .setBoolPrim(entity.boolPrim) + .setString(entity.string) + .setInstant(entity.instant) + .setLocalDateTime(entity.localDateTime) + .setLocalDate(entity.localDate) + .setOffsetDateTime(entity.offsetDateTime) + .setDate(entity.date) + .setTimestamp(entity.timestamp) + .setByteArray(entity.byteArray) + .setUuid(entity.uuid); + } + + @Override + protected DirectAllTypesEntity nullEntity(DirectAllTypesEntity entity) { + var updated = entity.clone(); + updated.intObj = null; + updated.shortObj = null; + updated.doubleObj = null; + updated.boolObj = null; + updated.string = null; + updated.instant = null; + updated.localDateTime = null; + updated.localDate = null; + updated.offsetDateTime = null; + updated.date = null; + updated.timestamp = null; + updated.byteArray = null; + updated.uuid = null; + return updated; + } + + @Override + protected DirectAllTypesEntityDb.Patch nullPatchAll() { + return new DirectAllTypesEntityDb.Patch() + .setIntObj(null) + .setShortObj(null) + .setDoubleObj(null) + .setBoolObj(null) + .setString(null) + .setInstant(null) + .setLocalDateTime(null) + .setLocalDate(null) + .setOffsetDateTime(null) + .setDate(null) + .setTimestamp(null) + .setByteArray(null) + .setUuid(null); + } + + @Override + protected Pair patchPartial(DirectAllTypesEntity entity) { + var updated = entity.clone(); + updated.string = RandomStringUtils.randomAlphanumeric(15); + updated.uuid = UUID.randomUUID(); + updated.instant = TestUtil.now(); + updated.date = null; + + return ImmutablePair.of(updated, new DirectAllTypesEntityDb.Patch() + .setString(updated.string) + .setUuid(updated.uuid) + .setInstant(updated.instant) + .setDate(updated.date)); + } + + @Override + protected DirectAllTypesEntityDb.Patch addRequiredFields(DirectAllTypesEntity entity, DirectAllTypesEntityDb.Patch patch) { + return patch.setBoolPrim(entity.boolPrim) + .setDoublePrim(entity.doublePrim) + .setIntPrim(entity.intPrim) + .setShortPrim(entity.shortPrim) + .setLongPrim(entity.longPrim); + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntityInnerDbTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntityInnerDbTest.java new file mode 100644 index 0000000..80da189 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/DirectAllTypesEntityInnerDbTest.java @@ -0,0 +1,169 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.test.util.TestUtil; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.UUID; + +public class DirectAllTypesEntityInnerDbTest extends BaseAllTypesTest { + + public DirectAllTypesEntityInnerDbTest() { + super(new DirectAllTypesEntityInnerDb()); + } + + @Override + protected Long getId(Wrapper.DirectAllTypesEntity entity) { + return entity.longId; + } + + @Override + protected Wrapper.DirectAllTypesEntity setId(Long id, Wrapper.DirectAllTypesEntity entity) { + var updated = entity.clone(); + updated.longId = id; + return updated; + } + + @Override + protected Wrapper.DirectAllTypesEntity newEntity() { + var entity = new Wrapper.DirectAllTypesEntity(); + entity.intObj = RandomUtils.nextInt(); + entity.intPrim = RandomUtils.nextInt(); + entity.shortObj = (short) 1; + entity.shortPrim = (short) 2; + entity.doubleObj = RandomUtils.nextDouble(); + entity.doublePrim = RandomUtils.nextDouble(); + entity.boolObj = true; + entity.boolPrim = false; + entity.string = RandomStringUtils.randomAlphanumeric(10); + entity.instant = TestUtil.now(); + entity.localDateTime = TestUtil.nowLocalDateTime(); + entity.localDate = LocalDate.now(); + entity.offsetDateTime = TestUtil.nowOffsetDateTime(); + entity.date = new Date(2023, 6, 25); + entity.timestamp = new Timestamp(System.currentTimeMillis()); + entity.byteArray = RandomUtils.nextBytes(10); + entity.uuid = UUID.randomUUID(); + return entity; + } + + @Override + protected Wrapper.DirectAllTypesEntity newEntityWithId() { + var entity = newEntity(); + entity.longId = nextId(); + return entity; + } + + @Override + protected Wrapper.DirectAllTypesEntity updateEntity(Wrapper.DirectAllTypesEntity entity) { + var updated = entity.clone(); + updated.intObj = RandomUtils.nextInt(); + updated.intPrim = RandomUtils.nextInt(); + updated.shortObj = (short) 3; + updated.shortPrim = (short) 4; + updated.doubleObj = RandomUtils.nextDouble(); + updated.doublePrim = RandomUtils.nextDouble(); + updated.boolObj = false; + updated.boolPrim = true; + updated.string = RandomStringUtils.randomAlphanumeric(10); + updated.instant = TestUtil.now(); + updated.localDateTime = TestUtil.nowLocalDateTime(); + updated.localDate = LocalDate.now(); + updated.offsetDateTime = TestUtil.nowOffsetDateTime(); + updated.date = new Date(2023, 6, 26); + updated.timestamp = new Timestamp(System.currentTimeMillis()); + updated.byteArray = RandomUtils.nextBytes(10); + updated.uuid = UUID.randomUUID(); + return updated; + } + + @Override + protected DirectAllTypesEntityInnerDb.Patch patchAll(Wrapper.DirectAllTypesEntity entity) { + return new DirectAllTypesEntityInnerDb.Patch() + .setLongPrim(entity.longPrim) + .setIntObj(entity.intObj) + .setIntPrim(entity.intPrim) + .setShortObj(entity.shortObj) + .setShortPrim(entity.shortPrim) + .setDoubleObj(entity.doubleObj) + .setDoublePrim(entity.doublePrim) + .setBoolObj(entity.boolObj) + .setBoolPrim(entity.boolPrim) + .setString(entity.string) + .setInstant(entity.instant) + .setLocalDateTime(entity.localDateTime) + .setLocalDate(entity.localDate) + .setOffsetDateTime(entity.offsetDateTime) + .setDate(entity.date) + .setTimestamp(entity.timestamp) + .setByteArray(entity.byteArray) + .setUuid(entity.uuid); + } + + @Override + protected Wrapper.DirectAllTypesEntity nullEntity(Wrapper.DirectAllTypesEntity entity) { + var updated = entity.clone(); + updated.intObj = null; + updated.shortObj = null; + updated.doubleObj = null; + updated.boolObj = null; + updated.string = null; + updated.instant = null; + updated.localDateTime = null; + updated.localDate = null; + updated.offsetDateTime = null; + updated.date = null; + updated.timestamp = null; + updated.byteArray = null; + updated.uuid = null; + return updated; + } + + @Override + protected DirectAllTypesEntityInnerDb.Patch nullPatchAll() { + return new DirectAllTypesEntityInnerDb.Patch() + .setIntObj(null) + .setShortObj(null) + .setDoubleObj(null) + .setBoolObj(null) + .setString(null) + .setInstant(null) + .setLocalDateTime(null) + .setLocalDate(null) + .setOffsetDateTime(null) + .setDate(null) + .setTimestamp(null) + .setByteArray(null) + .setUuid(null); + } + + @Override + protected Pair patchPartial(Wrapper.DirectAllTypesEntity entity) { + var updated = entity.clone(); + updated.string = RandomStringUtils.randomAlphanumeric(15); + updated.uuid = UUID.randomUUID(); + updated.instant = TestUtil.now(); + updated.date = null; + + return ImmutablePair.of(updated, new DirectAllTypesEntityInnerDb.Patch() + .setString(updated.string) + .setUuid(updated.uuid) + .setInstant(updated.instant) + .setDate(updated.date)); + } + + @Override + protected DirectAllTypesEntityInnerDb.Patch addRequiredFields(Wrapper.DirectAllTypesEntity entity, DirectAllTypesEntityInnerDb.Patch patch) { + return patch.setBoolPrim(entity.boolPrim) + .setDoublePrim(entity.doublePrim) + .setIntPrim(entity.intPrim) + .setShortPrim(entity.shortPrim) + .setLongPrim(entity.longPrim); + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntityDbTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntityDbTest.java new file mode 100644 index 0000000..fdb3fa2 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntityDbTest.java @@ -0,0 +1,209 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.test.util.TestUtil; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.UUID; + +public class GetterConstructorAllTypesEntityDbTest extends BaseAllTypesTest { + + public GetterConstructorAllTypesEntityDbTest() { + super(new GetterConstructorAllTypesEntityDb()); + } + + @Override + protected Long getId(GetterConstructorAllTypesEntity entity) { + return entity.getLongId(); + } + + @Override + protected GetterConstructorAllTypesEntity setId(Long id, GetterConstructorAllTypesEntity entity) { + return new GetterConstructorAllTypesEntity( + id, + entity.getLongPrim(), + entity.getIntObj(), + entity.getIntPrim(), + entity.getShortObj(), + entity.getShortPrim(), + entity.getDoubleObj(), + entity.getDoublePrim(), + entity.getBoolObj(), + entity.isBoolPrim(), + entity.getString(), + entity.getInstant(), + entity.getLocalDateTime(), + entity.getLocalDate(), + entity.getOffsetDateTime(), + entity.getDate(), + entity.getTimestamp(), + entity.getByteArray(), + entity.getUuid() + ); + } + + @Override + protected GetterConstructorAllTypesEntity newEntity() { + return new GetterConstructorAllTypesEntity( + null, + RandomUtils.nextLong(), + RandomUtils.nextInt(), + RandomUtils.nextInt(), + (short) 1, + (short) 2, + RandomUtils.nextDouble(), + RandomUtils.nextDouble(), + true, + false, + RandomStringUtils.randomAlphanumeric(10), + TestUtil.now(), + TestUtil.nowLocalDateTime(), + LocalDate.now(), + TestUtil.nowOffsetDateTime(), + new Date(2023, 6, 25), + new Timestamp(System.currentTimeMillis()), + RandomUtils.nextBytes(10), + UUID.randomUUID()); + } + + @Override + protected GetterConstructorAllTypesEntity newEntityWithId() { + return setId(nextId(), newEntity()); + } + + @Override + protected GetterConstructorAllTypesEntity updateEntity(GetterConstructorAllTypesEntity entity) { + return new GetterConstructorAllTypesEntity( + entity.getLongId(), + RandomUtils.nextLong(), + RandomUtils.nextInt(), + RandomUtils.nextInt(), + (short) 3, + (short) 4, + RandomUtils.nextDouble(), + RandomUtils.nextDouble(), + false, + true, + RandomStringUtils.randomAlphanumeric(10), + TestUtil.now(), + TestUtil.nowLocalDateTime(), + LocalDate.now(), + TestUtil.nowOffsetDateTime(), + new Date(2023, 6, 26), + new Timestamp(System.currentTimeMillis()), + RandomUtils.nextBytes(10), + UUID.randomUUID()); + } + + @Override + protected GetterConstructorAllTypesEntityDb.Patch patchAll(GetterConstructorAllTypesEntity entity) { + return new GetterConstructorAllTypesEntityDb.Patch() + .setLongPrim(entity.getLongPrim()) + .setIntObj(entity.getIntObj()) + .setIntPrim(entity.getIntPrim()) + .setShortObj(entity.getShortObj()) + .setShortPrim(entity.getShortPrim()) + .setDoubleObj(entity.getDoubleObj()) + .setDoublePrim(entity.getDoublePrim()) + .setBoolObj(entity.getBoolObj()) + .setBoolPrim(entity.isBoolPrim()) + .setString(entity.getString()) + .setInstant(entity.getInstant()) + .setLocalDateTime(entity.getLocalDateTime()) + .setLocalDate(entity.getLocalDate()) + .setOffsetDateTime(entity.getOffsetDateTime()) + .setDate(entity.getDate()) + .setTimestamp(entity.getTimestamp()) + .setByteArray(entity.getByteArray()) + .setUuid(entity.getUuid()); + } + + @Override + protected GetterConstructorAllTypesEntity nullEntity(GetterConstructorAllTypesEntity entity) { + return new GetterConstructorAllTypesEntity( + entity.getLongId(), + entity.getLongPrim(), + null, + entity.getIntPrim(), + null, + entity.getShortPrim(), + null, + entity.getDoublePrim(), + null, + entity.isBoolPrim(), + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + } + + @Override + protected GetterConstructorAllTypesEntityDb.Patch nullPatchAll() { + return new GetterConstructorAllTypesEntityDb.Patch() + .setIntObj(null) + .setShortObj(null) + .setDoubleObj(null) + .setBoolObj(null) + .setString(null) + .setInstant(null) + .setLocalDateTime(null) + .setLocalDate(null) + .setOffsetDateTime(null) + .setDate(null) + .setTimestamp(null) + .setByteArray(null) + .setUuid(null); + } + + @Override + protected Pair patchPartial(GetterConstructorAllTypesEntity entity) { + var updated = new GetterConstructorAllTypesEntity( + entity.getLongId(), + entity.getLongPrim(), + entity.getIntObj(), + entity.getIntPrim(), + entity.getShortObj(), + entity.getShortPrim(), + entity.getDoubleObj(), + entity.getDoublePrim(), + entity.getBoolObj(), + entity.isBoolPrim(), + RandomStringUtils.randomAlphanumeric(15), + TestUtil.now(), + entity.getLocalDateTime(), + entity.getLocalDate(), + entity.getOffsetDateTime(), + null, + entity.getTimestamp(), + entity.getByteArray(), + UUID.randomUUID() + ); + + return ImmutablePair.of(updated, new GetterConstructorAllTypesEntityDb.Patch() + .setString(updated.getString()) + .setUuid(updated.getUuid()) + .setInstant(updated.getInstant()) + .setDate(updated.getDate())); + } + + @Override + protected GetterConstructorAllTypesEntityDb.Patch addRequiredFields(GetterConstructorAllTypesEntity entity, GetterConstructorAllTypesEntityDb.Patch patch) { + return patch.setBoolPrim(entity.isBoolPrim()) + .setDoublePrim(entity.getDoublePrim()) + .setIntPrim(entity.getIntPrim()) + .setShortPrim(entity.getShortPrim()) + .setLongPrim(entity.getLongPrim()); + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntityInnerDbTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntityInnerDbTest.java new file mode 100644 index 0000000..ffea8af --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/GetterConstructorAllTypesEntityInnerDbTest.java @@ -0,0 +1,209 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.test.util.TestUtil; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.UUID; + +public class GetterConstructorAllTypesEntityInnerDbTest extends BaseAllTypesTest { + + public GetterConstructorAllTypesEntityInnerDbTest() { + super(new GetterConstructorAllTypesEntityInnerDb()); + } + + @Override + protected Long getId(Wrapper.GetterConstructorAllTypesEntity entity) { + return entity.getLongId(); + } + + @Override + protected Wrapper.GetterConstructorAllTypesEntity setId(Long id, Wrapper.GetterConstructorAllTypesEntity entity) { + return new Wrapper.GetterConstructorAllTypesEntity( + id, + entity.getLongPrim(), + entity.getIntObj(), + entity.getIntPrim(), + entity.getShortObj(), + entity.getShortPrim(), + entity.getDoubleObj(), + entity.getDoublePrim(), + entity.getBoolObj(), + entity.isBoolPrim(), + entity.getString(), + entity.getInstant(), + entity.getLocalDateTime(), + entity.getLocalDate(), + entity.getOffsetDateTime(), + entity.getDate(), + entity.getTimestamp(), + entity.getByteArray(), + entity.getUuid() + ); + } + + @Override + protected Wrapper.GetterConstructorAllTypesEntity newEntity() { + return new Wrapper.GetterConstructorAllTypesEntity( + null, + RandomUtils.nextLong(), + RandomUtils.nextInt(), + RandomUtils.nextInt(), + (short) 1, + (short) 2, + RandomUtils.nextDouble(), + RandomUtils.nextDouble(), + true, + false, + RandomStringUtils.randomAlphanumeric(10), + TestUtil.now(), + TestUtil.nowLocalDateTime(), + LocalDate.now(), + TestUtil.nowOffsetDateTime(), + new Date(2023, 6, 25), + new Timestamp(System.currentTimeMillis()), + RandomUtils.nextBytes(10), + UUID.randomUUID()); + } + + @Override + protected Wrapper.GetterConstructorAllTypesEntity newEntityWithId() { + return setId(nextId(), newEntity()); + } + + @Override + protected Wrapper.GetterConstructorAllTypesEntity updateEntity(Wrapper.GetterConstructorAllTypesEntity entity) { + return new Wrapper.GetterConstructorAllTypesEntity( + entity.getLongId(), + RandomUtils.nextLong(), + RandomUtils.nextInt(), + RandomUtils.nextInt(), + (short) 3, + (short) 4, + RandomUtils.nextDouble(), + RandomUtils.nextDouble(), + false, + true, + RandomStringUtils.randomAlphanumeric(10), + TestUtil.now(), + TestUtil.nowLocalDateTime(), + LocalDate.now(), + TestUtil.nowOffsetDateTime(), + new Date(2023, 6, 26), + new Timestamp(System.currentTimeMillis()), + RandomUtils.nextBytes(10), + UUID.randomUUID()); + } + + @Override + protected GetterConstructorAllTypesEntityInnerDb.Patch patchAll(Wrapper.GetterConstructorAllTypesEntity entity) { + return new GetterConstructorAllTypesEntityInnerDb.Patch() + .setLongPrim(entity.getLongPrim()) + .setIntObj(entity.getIntObj()) + .setIntPrim(entity.getIntPrim()) + .setShortObj(entity.getShortObj()) + .setShortPrim(entity.getShortPrim()) + .setDoubleObj(entity.getDoubleObj()) + .setDoublePrim(entity.getDoublePrim()) + .setBoolObj(entity.getBoolObj()) + .setBoolPrim(entity.isBoolPrim()) + .setString(entity.getString()) + .setInstant(entity.getInstant()) + .setLocalDateTime(entity.getLocalDateTime()) + .setLocalDate(entity.getLocalDate()) + .setOffsetDateTime(entity.getOffsetDateTime()) + .setDate(entity.getDate()) + .setTimestamp(entity.getTimestamp()) + .setByteArray(entity.getByteArray()) + .setUuid(entity.getUuid()); + } + + @Override + protected Wrapper.GetterConstructorAllTypesEntity nullEntity(Wrapper.GetterConstructorAllTypesEntity entity) { + return new Wrapper.GetterConstructorAllTypesEntity( + entity.getLongId(), + entity.getLongPrim(), + null, + entity.getIntPrim(), + null, + entity.getShortPrim(), + null, + entity.getDoublePrim(), + null, + entity.isBoolPrim(), + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + } + + @Override + protected GetterConstructorAllTypesEntityInnerDb.Patch nullPatchAll() { + return new GetterConstructorAllTypesEntityInnerDb.Patch() + .setIntObj(null) + .setShortObj(null) + .setDoubleObj(null) + .setBoolObj(null) + .setString(null) + .setInstant(null) + .setLocalDateTime(null) + .setLocalDate(null) + .setOffsetDateTime(null) + .setDate(null) + .setTimestamp(null) + .setByteArray(null) + .setUuid(null); + } + + @Override + protected Pair patchPartial(Wrapper.GetterConstructorAllTypesEntity entity) { + var updated = new Wrapper.GetterConstructorAllTypesEntity( + entity.getLongId(), + entity.getLongPrim(), + entity.getIntObj(), + entity.getIntPrim(), + entity.getShortObj(), + entity.getShortPrim(), + entity.getDoubleObj(), + entity.getDoublePrim(), + entity.getBoolObj(), + entity.isBoolPrim(), + RandomStringUtils.randomAlphanumeric(15), + TestUtil.now(), + entity.getLocalDateTime(), + entity.getLocalDate(), + entity.getOffsetDateTime(), + null, + entity.getTimestamp(), + entity.getByteArray(), + UUID.randomUUID() + ); + + return ImmutablePair.of(updated, new GetterConstructorAllTypesEntityInnerDb.Patch() + .setString(updated.getString()) + .setUuid(updated.getUuid()) + .setInstant(updated.getInstant()) + .setDate(updated.getDate())); + } + + @Override + protected GetterConstructorAllTypesEntityInnerDb.Patch addRequiredFields(Wrapper.GetterConstructorAllTypesEntity entity, GetterConstructorAllTypesEntityInnerDb.Patch patch) { + return patch.setBoolPrim(entity.isBoolPrim()) + .setDoublePrim(entity.getDoublePrim()) + .setIntPrim(entity.getIntPrim()) + .setShortPrim(entity.getShortPrim()) + .setLongPrim(entity.getLongPrim()); + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntityDbTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntityDbTest.java new file mode 100644 index 0000000..659b8b5 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntityDbTest.java @@ -0,0 +1,203 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.OrderDirection; +import com.pwinckles.jdbcgen.test.util.TestUtil; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.Connection; +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class GetterSetterAllTypesEntityDbTest extends BaseAllTypesTest { + + public GetterSetterAllTypesEntityDbTest() { + super(new GetterSetterAllTypesEntityDb()); + } + + @ParameterizedTest + @MethodSource("dbs") + public void selectAllOrderBy(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var originals = new ArrayList<>(List.of( + newEntityWithId().setString("d"), + newEntityWithId().setString("c"), + newEntityWithId().setString("b"), + newEntityWithId().setString("a") + )); + + db.insert(originals, conn); + + var results = db.selectAll(GetterSetterAllTypesEntityDb.Column.LONG_ID, OrderDirection.ASCENDING, conn); + assertEntities(originals, results); + + results = db.selectAll(GetterSetterAllTypesEntityDb.Column.STRING, OrderDirection.DESCENDING, conn); + assertEntities(originals, results); + + Collections.reverse(originals); + + results = db.selectAll(GetterSetterAllTypesEntityDb.Column.LONG_ID, OrderDirection.DESCENDING, conn); + assertEntities(originals, results); + + results = db.selectAll(GetterSetterAllTypesEntityDb.Column.STRING, OrderDirection.ASCENDING, conn); + assertEntities(originals, results); + } + } + + @Override + protected Long getId(GetterSetterAllTypesEntity entity) { + return entity.getLongId(); + } + + @Override + protected GetterSetterAllTypesEntity setId(Long id, GetterSetterAllTypesEntity entity) { + return entity.clone().setLongId(id); + } + + @Override + protected GetterSetterAllTypesEntity newEntity() { + return new GetterSetterAllTypesEntity() + .setLongPrim(RandomUtils.nextLong()) + .setIntObj(RandomUtils.nextInt()) + .setIntPrim(RandomUtils.nextInt()) + .setShortObj((short) 1) + .setShortPrim((short) 2) + .setDoubleObj(RandomUtils.nextDouble()) + .setDoublePrim(RandomUtils.nextDouble()) + .setBoolObj(true) + .setBoolPrim(false) + .setString(RandomStringUtils.randomAlphanumeric(10)) + .setInstant(TestUtil.now()) + .setLocalDateTime(TestUtil.nowLocalDateTime()) + .setLocalDate(LocalDate.now()) + .setOffsetDateTime(TestUtil.nowOffsetDateTime()) + .setDate(new Date(2023, 6, 25)) + .setTimestamp(new Timestamp(System.currentTimeMillis())) + .setByteArray(RandomUtils.nextBytes(10)) + .setUuid(UUID.randomUUID()); + } + + @Override + protected GetterSetterAllTypesEntity newEntityWithId() { + return newEntity().setLongId(nextId()); + } + + @Override + protected GetterSetterAllTypesEntity updateEntity(GetterSetterAllTypesEntity entity) { + return entity.clone() + .setLongPrim(RandomUtils.nextLong()) + .setIntObj(RandomUtils.nextInt()) + .setIntPrim(RandomUtils.nextInt()) + .setShortObj((short) 3) + .setShortPrim((short) 4) + .setDoubleObj(RandomUtils.nextDouble()) + .setDoublePrim(RandomUtils.nextDouble()) + .setBoolObj(false) + .setBoolPrim(true) + .setString(RandomStringUtils.randomAlphanumeric(10)) + .setInstant(TestUtil.now()) + .setLocalDateTime(TestUtil.nowLocalDateTime()) + .setLocalDate(LocalDate.now()) + .setOffsetDateTime(TestUtil.nowOffsetDateTime()) + .setDate(new Date(2023, 6, 26)) + .setTimestamp(new Timestamp(System.currentTimeMillis())) + .setByteArray(RandomUtils.nextBytes(10)) + .setUuid(UUID.randomUUID()); + } + + @Override + protected GetterSetterAllTypesEntityDb.Patch patchAll(GetterSetterAllTypesEntity entity) { + return new GetterSetterAllTypesEntityDb.Patch() + .setLongPrim(entity.getLongPrim()) + .setIntObj(entity.getIntObj()) + .setIntPrim(entity.getIntPrim()) + .setShortObj(entity.getShortObj()) + .setShortPrim(entity.getShortPrim()) + .setDoubleObj(entity.getDoubleObj()) + .setDoublePrim(entity.getDoublePrim()) + .setBoolObj(entity.getBoolObj()) + .setBoolPrim(entity.isBoolPrim()) + .setString(entity.getString()) + .setInstant(entity.getInstant()) + .setLocalDateTime(entity.getLocalDateTime()) + .setLocalDate(entity.getLocalDate()) + .setOffsetDateTime(entity.getOffsetDateTime()) + .setDate(entity.getDate()) + .setTimestamp(entity.getTimestamp()) + .setByteArray(entity.getByteArray()) + .setUuid(entity.getUuid()); + } + + @Override + protected GetterSetterAllTypesEntity nullEntity(GetterSetterAllTypesEntity entity) { + return entity.clone() + .setIntObj(null) + .setShortObj(null) + .setDoubleObj(null) + .setBoolObj(null) + .setString(null) + .setInstant(null) + .setLocalDateTime(null) + .setLocalDate(null) + .setOffsetDateTime(null) + .setDate(null) + .setTimestamp(null) + .setByteArray(null) + .setUuid(null); + } + + @Override + protected GetterSetterAllTypesEntityDb.Patch nullPatchAll() { + return new GetterSetterAllTypesEntityDb.Patch() + .setIntObj(null) + .setShortObj(null) + .setDoubleObj(null) + .setBoolObj(null) + .setString(null) + .setInstant(null) + .setLocalDateTime(null) + .setLocalDate(null) + .setOffsetDateTime(null) + .setDate(null) + .setTimestamp(null) + .setByteArray(null) + .setUuid(null); + } + + @Override + protected Pair patchPartial(GetterSetterAllTypesEntity entity) { + var updated = entity.clone() + .setString(RandomStringUtils.randomAlphanumeric(15)) + .setUuid(UUID.randomUUID()) + .setInstant(TestUtil.now()) + .setDate(null); + + return ImmutablePair.of(updated, new GetterSetterAllTypesEntityDb.Patch() + .setString(updated.getString()) + .setUuid(updated.getUuid()) + .setInstant(updated.getInstant()) + .setDate(updated.getDate())); + } + + @Override + protected GetterSetterAllTypesEntityDb.Patch addRequiredFields(GetterSetterAllTypesEntity entity, GetterSetterAllTypesEntityDb.Patch patch) { + return patch.setBoolPrim(entity.isBoolPrim()) + .setDoublePrim(entity.getDoublePrim()) + .setIntPrim(entity.getIntPrim()) + .setShortPrim(entity.getShortPrim()) + .setLongPrim(entity.getLongPrim()); + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntityInnerDbTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntityInnerDbTest.java new file mode 100644 index 0000000..d22f793 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/GetterSetterAllTypesEntityInnerDbTest.java @@ -0,0 +1,203 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.OrderDirection; +import com.pwinckles.jdbcgen.test.util.TestUtil; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.Connection; +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class GetterSetterAllTypesEntityInnerDbTest extends BaseAllTypesTest { + + public GetterSetterAllTypesEntityInnerDbTest() { + super(new GetterSetterAllTypesEntityInnerDb()); + } + + @Override + protected Long getId(Wrapper.GetterSetterAllTypesEntity entity) { + return entity.getLongId(); + } + + @ParameterizedTest + @MethodSource("dbs") + public void selectAllOrderBy(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var originals = new ArrayList<>(List.of( + newEntityWithId().setString("d"), + newEntityWithId().setString("c"), + newEntityWithId().setString("b"), + newEntityWithId().setString("a") + )); + + db.insert(originals, conn); + + var results = db.selectAll(GetterSetterAllTypesEntityInnerDb.Column.LONG_ID, OrderDirection.ASCENDING, conn); + assertEntities(originals, results); + + results = db.selectAll(GetterSetterAllTypesEntityInnerDb.Column.STRING, OrderDirection.DESCENDING, conn); + assertEntities(originals, results); + + Collections.reverse(originals); + + results = db.selectAll(GetterSetterAllTypesEntityInnerDb.Column.LONG_ID, OrderDirection.DESCENDING, conn); + assertEntities(originals, results); + + results = db.selectAll(GetterSetterAllTypesEntityInnerDb.Column.STRING, OrderDirection.ASCENDING, conn); + assertEntities(originals, results); + } + } + + @Override + protected Wrapper.GetterSetterAllTypesEntity setId(Long id, Wrapper.GetterSetterAllTypesEntity entity) { + return entity.clone().setLongId(id); + } + + @Override + protected Wrapper.GetterSetterAllTypesEntity newEntity() { + return new Wrapper.GetterSetterAllTypesEntity() + .setLongPrim(RandomUtils.nextLong()) + .setIntObj(RandomUtils.nextInt()) + .setIntPrim(RandomUtils.nextInt()) + .setShortObj((short) 1) + .setShortPrim((short) 2) + .setDoubleObj(RandomUtils.nextDouble()) + .setDoublePrim(RandomUtils.nextDouble()) + .setBoolObj(true) + .setBoolPrim(false) + .setString(RandomStringUtils.randomAlphanumeric(10)) + .setInstant(TestUtil.now()) + .setLocalDateTime(TestUtil.nowLocalDateTime()) + .setLocalDate(LocalDate.now()) + .setOffsetDateTime(TestUtil.nowOffsetDateTime()) + .setDate(new Date(2023, 6, 25)) + .setTimestamp(new Timestamp(System.currentTimeMillis())) + .setByteArray(RandomUtils.nextBytes(10)) + .setUuid(UUID.randomUUID()); + } + + @Override + protected Wrapper.GetterSetterAllTypesEntity newEntityWithId() { + return newEntity().setLongId(nextId()); + } + + @Override + protected Wrapper.GetterSetterAllTypesEntity updateEntity(Wrapper.GetterSetterAllTypesEntity entity) { + return entity.clone() + .setLongPrim(RandomUtils.nextLong()) + .setIntObj(RandomUtils.nextInt()) + .setIntPrim(RandomUtils.nextInt()) + .setShortObj((short) 3) + .setShortPrim((short) 4) + .setDoubleObj(RandomUtils.nextDouble()) + .setDoublePrim(RandomUtils.nextDouble()) + .setBoolObj(false) + .setBoolPrim(true) + .setString(RandomStringUtils.randomAlphanumeric(10)) + .setInstant(TestUtil.now()) + .setLocalDateTime(TestUtil.nowLocalDateTime()) + .setLocalDate(LocalDate.now()) + .setOffsetDateTime(TestUtil.nowOffsetDateTime()) + .setDate(new Date(2023, 6, 26)) + .setTimestamp(new Timestamp(System.currentTimeMillis())) + .setByteArray(RandomUtils.nextBytes(10)) + .setUuid(UUID.randomUUID()); + } + + @Override + protected GetterSetterAllTypesEntityInnerDb.Patch patchAll(Wrapper.GetterSetterAllTypesEntity entity) { + return new GetterSetterAllTypesEntityInnerDb.Patch() + .setLongPrim(entity.getLongPrim()) + .setIntObj(entity.getIntObj()) + .setIntPrim(entity.getIntPrim()) + .setShortObj(entity.getShortObj()) + .setShortPrim(entity.getShortPrim()) + .setDoubleObj(entity.getDoubleObj()) + .setDoublePrim(entity.getDoublePrim()) + .setBoolObj(entity.getBoolObj()) + .setBoolPrim(entity.isBoolPrim()) + .setString(entity.getString()) + .setInstant(entity.getInstant()) + .setLocalDateTime(entity.getLocalDateTime()) + .setLocalDate(entity.getLocalDate()) + .setOffsetDateTime(entity.getOffsetDateTime()) + .setDate(entity.getDate()) + .setTimestamp(entity.getTimestamp()) + .setByteArray(entity.getByteArray()) + .setUuid(entity.getUuid()); + } + + @Override + protected Wrapper.GetterSetterAllTypesEntity nullEntity(Wrapper.GetterSetterAllTypesEntity entity) { + return entity.clone() + .setIntObj(null) + .setShortObj(null) + .setDoubleObj(null) + .setBoolObj(null) + .setString(null) + .setInstant(null) + .setLocalDateTime(null) + .setLocalDate(null) + .setOffsetDateTime(null) + .setDate(null) + .setTimestamp(null) + .setByteArray(null) + .setUuid(null); + } + + @Override + protected GetterSetterAllTypesEntityInnerDb.Patch nullPatchAll() { + return new GetterSetterAllTypesEntityInnerDb.Patch() + .setIntObj(null) + .setShortObj(null) + .setDoubleObj(null) + .setBoolObj(null) + .setString(null) + .setInstant(null) + .setLocalDateTime(null) + .setLocalDate(null) + .setOffsetDateTime(null) + .setDate(null) + .setTimestamp(null) + .setByteArray(null) + .setUuid(null); + } + + @Override + protected Pair patchPartial(Wrapper.GetterSetterAllTypesEntity entity) { + var updated = entity.clone() + .setString(RandomStringUtils.randomAlphanumeric(15)) + .setUuid(UUID.randomUUID()) + .setInstant(TestUtil.now()) + .setDate(null); + + return ImmutablePair.of(updated, new GetterSetterAllTypesEntityInnerDb.Patch() + .setString(updated.getString()) + .setUuid(updated.getUuid()) + .setInstant(updated.getInstant()) + .setDate(updated.getDate())); + } + + @Override + protected GetterSetterAllTypesEntityInnerDb.Patch addRequiredFields(Wrapper.GetterSetterAllTypesEntity entity, GetterSetterAllTypesEntityInnerDb.Patch patch) { + return patch.setBoolPrim(entity.isBoolPrim()) + .setDoublePrim(entity.getDoublePrim()) + .setIntPrim(entity.getIntPrim()) + .setShortPrim(entity.getShortPrim()) + .setLongPrim(entity.getLongPrim()); + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/PrimitiveIdEntityTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/PrimitiveIdEntityTest.java new file mode 100644 index 0000000..6dfc8e2 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/PrimitiveIdEntityTest.java @@ -0,0 +1,85 @@ +package com.pwinckles.jdbcgen.test; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.sql.Connection; +import java.sql.SQLException; + +public class PrimitiveIdEntityTest extends TestBase { + + protected PrimitiveIdEntityTest() { + super(new PrimitiveIdEntityDb()); + } + + @Override + protected Long getId(PrimitiveIdEntity entity) { + return entity.getId(); + } + + @Override + protected PrimitiveIdEntity setId(Long id, PrimitiveIdEntity entity) { + return entity.clone().setId(id); + } + + @Override + protected PrimitiveIdEntity newEntity() { + return new PrimitiveIdEntity() + .setId(nextId()) + .setValue(RandomStringUtils.randomAlphanumeric(10)); + } + + @Override + protected PrimitiveIdEntity newEntityWithId() { + return newEntity(); + } + + @Override + protected PrimitiveIdEntity updateEntity(PrimitiveIdEntity entity) { + return entity.clone() + .setValue(RandomStringUtils.randomAlphanumeric(10)); + } + + @Override + protected PrimitiveIdEntityDb.Patch patchAll(PrimitiveIdEntity entity) { + return new PrimitiveIdEntityDb.Patch() + .setValue(entity.getValue()); + } + + @Override + protected PrimitiveIdEntity nullEntity(PrimitiveIdEntity entity) { + return entity.clone() + .setValue(null); + } + + @Override + protected PrimitiveIdEntityDb.Patch nullPatchAll() { + return new PrimitiveIdEntityDb.Patch() + .setValue(null); + } + + @Override + protected Pair patchPartial(PrimitiveIdEntity entity) { + var updated = entity.clone() + .setValue(RandomStringUtils.randomAlphanumeric(15)); + + return ImmutablePair.of(updated, new PrimitiveIdEntityDb.Patch() + .setValue(updated.getValue())); + } + + @Override + protected PrimitiveIdEntityDb.Patch addRequiredFields(PrimitiveIdEntity entity, PrimitiveIdEntityDb.Patch patch) { + return patch.setId(entity.getId()); + } + + @Override + protected void createTable(Connection conn) throws SQLException { + try (var stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE primitive_id (" + + "id IDENTITY PRIMARY KEY," + + " val VARCHAR(255)" + + ")"); + } + } +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/TestBase.java b/test/src/test/java/com/pwinckles/jdbcgen/test/TestBase.java new file mode 100644 index 0000000..58372bf --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/TestBase.java @@ -0,0 +1,209 @@ +package com.pwinckles.jdbcgen.test; + +import com.pwinckles.jdbcgen.BasePatch; +import com.pwinckles.jdbcgen.JdbcGenDb; +import org.apache.commons.lang3.tuple.Pair; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public abstract class TestBase { + + protected static Stream dbs() throws SQLException { + return Stream.of( + Arguments.of(DriverManager.getConnection("jdbc:hsqldb:mem:testdb;shutdown=true", "SA", "")), + Arguments.of(DriverManager.getConnection("jdbc:h2:mem:testdb", "SA", "")) + ); + } + + private long serial = 1234L; + protected JdbcGenDb db; + + protected TestBase(JdbcGenDb db) { + this.db = db; + } + + @ParameterizedTest + @MethodSource("dbs") + public void basicOperations(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var original = newEntity(); + var id = db.insert(original, conn); + original = setId(id, original); + assertEntity(original, conn); + + var updated = updateEntity(original); + db.update(updated, conn); + assertEntity(updated, conn); + + var updatedPair = patchPartial(updated); + db.update(id, updatedPair.getRight(), conn); + assertEntity(updatedPair.getLeft(), conn); + + var updated2 = nullEntity(updatedPair.getLeft()); + db.update(updated2, conn); + assertEntity(updated2, conn); + + var updated3 = updateEntity(updated2); + var updated3Patch = patchAll(updated3); + db.update(id, updated3Patch, conn); + assertEntity(updated3, conn); + + var updated4 = nullEntity(updated3); + var updated4Patch = nullPatchAll(); + db.update(id, updated4Patch, conn); + assertEntity(updated4, conn); + + db.delete(id, conn); + assertDoesNotExist(id, conn); + } + } + + @ParameterizedTest + @MethodSource("dbs") + public void insertWithSpecifiedId(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var original = newEntityWithId(); + db.insert(original, conn); + assertEntity(original, conn); + + var originals = List.of( + newEntityWithId(), + newEntityWithId(), + newEntityWithId(), + newEntityWithId() + ); + + db.insert(originals, conn); + assertEntities(originals, conn); + } + } + + @ParameterizedTest + @MethodSource("dbs") + public void partialInsert(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + var updatedPair = patchPartial(nullEntity(newEntity())); + var partial = addRequiredFields(updatedPair.getLeft(), updatedPair.getRight()); + var id = db.insert(partial, conn); + assertEntity(setId(id, updatedPair.getLeft()), conn); + } + } + + @ParameterizedTest + @MethodSource("dbs") + public void bulkOperations(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var originals = new ArrayList<>(List.of( + newEntity(), + newEntity(), + newEntity(), + newEntity() + )); + + var ids = db.insert(originals, conn); + + for (int i = 0; i < originals.size(); i++) { + originals.set(i, setId(ids.get(i), originals.get(i))); + } + + assertCount(originals.size(), conn); + assertAllEntities(originals, conn); + + var updates = List.of( + updateEntity(originals.get(1)), + updateEntity(originals.get(3)) + ); + db.update(updates, conn); + assertAllEntities(List.of(originals.get(0), updates.get(0), originals.get(2), updates.get(1)), conn); + + var deletes = List.of(getId(originals.get(0)), getId(originals.get(3))); + db.delete(deletes, conn); + assertCount(2, conn); + assertAllEntities(List.of(updates.get(0), originals.get(2)), conn); + + db.deleteAll(conn); + assertCount(0, conn); + assertAllEntities(Collections.emptyList(), conn); + } + } + + protected abstract I getId(E entity); + + protected abstract E setId(I id, E entity); + + protected abstract E newEntity(); + + protected abstract E newEntityWithId(); + + protected abstract E updateEntity(E entity); + + protected abstract P patchAll(E entity); + + protected abstract E nullEntity(E entity); + + protected abstract P nullPatchAll(); + + protected abstract Pair patchPartial(E entity); + + protected abstract P addRequiredFields(E entity, P patch); + + protected abstract void createTable(Connection conn) throws SQLException; + + protected long nextId() { + return serial++; + } + + protected void assertCount(long expected, Connection conn) throws SQLException { + Assertions.assertThat(db.count(conn)).isEqualTo(expected); + } + + protected void assertEntity(E expected, Connection conn) throws SQLException { + Assertions.assertThat(db.select(getId(expected), conn)) + .usingRecursiveComparison() + .isEqualTo(expected); + } + + protected void assertEntities(List expected, Connection conn) { + var actual = expected.stream().map(e -> { + try { + return db.select(getId(e), conn); + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + }).collect(Collectors.toList()); + assertEntities(expected, actual); + } + + protected void assertAllEntities(List expected, Connection conn) throws SQLException { + assertEntities(expected, db.selectAll(conn)); + } + + protected void assertEntities(List expected, List actual) { + Assertions.assertThat(actual) + .usingRecursiveComparison() + .isEqualTo(expected); + } + + protected void assertDoesNotExist(I id, Connection conn) throws SQLException { + Assertions.assertThat(db.select(id, conn)).isNull(); + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/UuidIdEntityTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/UuidIdEntityTest.java new file mode 100644 index 0000000..db145ee --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/UuidIdEntityTest.java @@ -0,0 +1,95 @@ +package com.pwinckles.jdbcgen.test; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Comparator; +import java.util.Iterator; +import java.util.UUID; +import java.util.stream.Stream; + +public class UuidIdEntityTest extends TestBase { + + // Need to do this so the uuids are ordered + private final Iterator uuids = Stream.generate(UUID::randomUUID) + .limit(50) + .sorted(Comparator.comparing(UUID::toString)) + .iterator(); + + protected UuidIdEntityTest() { + super(new UuidIdEntityDb()); + } + + @Override + protected UUID getId(UuidIdEntity entity) { + return entity.getId(); + } + + @Override + protected UuidIdEntity setId(UUID id, UuidIdEntity entity) { + return entity.clone().setId(id); + } + + @Override + protected UuidIdEntity newEntity() { + return new UuidIdEntity() + .setId(uuids.next()) + .setValue(RandomStringUtils.randomAlphanumeric(10)); + } + + @Override + protected UuidIdEntity newEntityWithId() { + return newEntity(); + } + + @Override + protected UuidIdEntity updateEntity(UuidIdEntity entity) { + return entity.clone() + .setValue(RandomStringUtils.randomAlphanumeric(10)); + } + + @Override + protected UuidIdEntityDb.Patch patchAll(UuidIdEntity entity) { + return new UuidIdEntityDb.Patch() + .setValue(entity.getValue()); + } + + @Override + protected UuidIdEntity nullEntity(UuidIdEntity entity) { + return entity.clone() + .setValue(null); + } + + @Override + protected UuidIdEntityDb.Patch nullPatchAll() { + return new UuidIdEntityDb.Patch() + .setValue(null); + } + + @Override + protected Pair patchPartial(UuidIdEntity entity) { + var updated = entity.clone() + .setValue(RandomStringUtils.randomAlphanumeric(15)); + + return ImmutablePair.of(updated, new UuidIdEntityDb.Patch() + .setValue(updated.getValue())); + } + + @Override + protected UuidIdEntityDb.Patch addRequiredFields(UuidIdEntity entity, UuidIdEntityDb.Patch patch) { + return patch.setId(entity.getId()); + } + + @Override + protected void createTable(Connection conn) throws SQLException { + try (var stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE uuid_id (" + + "id UUID PRIMARY KEY," + + " val VARCHAR(255)" + + ")"); + } + } +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/prototype/ExampleTest.java b/test/src/test/java/com/pwinckles/jdbcgen/test/prototype/ExampleTest.java new file mode 100644 index 0000000..693d370 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/prototype/ExampleTest.java @@ -0,0 +1,206 @@ +package com.pwinckles.jdbcgen.test.prototype; + +import com.pwinckles.jdbcgen.OrderDirection; +import com.pwinckles.jdbcgen.test.util.TestUtil; +import org.apache.commons.lang3.RandomStringUtils; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ExampleTest { + + private ExampleDb exampleDb = new ExampleDb(); + + private static Stream dbs() throws SQLException { + return Stream.of( + Arguments.of(DriverManager.getConnection("jdbc:hsqldb:mem:testdb;shutdown=true", "SA", "")), + Arguments.of(DriverManager.getConnection("jdbc:h2:mem:testdb", "SA", "")) + ); + } + + @ParameterizedTest + @MethodSource("dbs") + public void basicOperations(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var original = example(); + var id = exampleDb.insert(original, conn); + assertEntity(original.setId(id), conn); + + var updated = original.setName("test - updated").setTimestamp(TestUtil.now()); + exampleDb.update(updated, conn); + assertEntity(updated, conn); + + exampleDb.update(id, new ExampleDb.Patch().setName("test - partial"), conn); + assertEntity(updated.setName("test - partial"), conn); + + exampleDb.update(id, new ExampleDb.Patch().setName(null).setTimestamp(null), conn); + assertEntity(updated.setName(null).setTimestamp(null), conn); + + exampleDb.delete(id, conn); + assertDoesNotExist(id, conn); + } + } + + @ParameterizedTest + @MethodSource("dbs") + public void insertWithSpecifiedId(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var original = example().setId(112233L); + exampleDb.insert(original, conn); + assertEntity(original, conn); + + var originals = List.of( + example().setId(22L), + example().setId(33L), + example().setId(44L), + example().setId(55L) + ); + + exampleDb.insert(originals, conn); + assertEntities(originals, conn); + } + } + + @ParameterizedTest + @MethodSource("dbs") + public void partialInsert(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + var id = exampleDb.insert(new ExampleDb.Patch().setName("partial"), conn); + assertEntity(new Example().setId(id).setName("partial"), conn); + } + } + + @ParameterizedTest + @MethodSource("dbs") + public void bulkOperations(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var originals = List.of( + example(), + example(), + example(), + example() + ); + + var ids = exampleDb.insert(originals, conn); + + for (int i = 0; i < originals.size(); i++) { + originals.get(i).setId(ids.get(i)); + } + + assertCount(originals.size(), conn); + assertAllEntities(originals, conn); + + var updates = List.of( + originals.get(1).setName("updated"), + originals.get(3).setName("updated too").setTimestamp(TestUtil.now()) + ); + exampleDb.update(updates, conn); + assertAllEntities(originals, conn); + + var deletes = List.of(originals.get(0).getId(), originals.get(3).getId()); + exampleDb.delete(deletes, conn); + assertCount(2, conn); + assertAllEntities(List.of(originals.get(1), originals.get(2)), conn); + + exampleDb.deleteAll(conn); + assertCount(0, conn); + assertAllEntities(Collections.emptyList(), conn); + } + } + + @ParameterizedTest + @MethodSource("dbs") + public void selectAllOrderBy(Connection conn) throws SQLException { + try (conn) { + createTable(conn); + + var originals = new ArrayList<>(List.of( + new Example().setId(1L).setName("d"), + new Example().setId(2L).setName("c"), + new Example().setId(3L).setName("b"), + new Example().setId(4L).setName("a") + )); + + exampleDb.insert(originals, conn); + + var results = exampleDb.selectAll(ExampleDb.Column.ID, OrderDirection.ASCENDING, conn); + assertEntities(originals, results); + + results = exampleDb.selectAll(ExampleDb.Column.NAME, OrderDirection.DESCENDING, conn); + assertEntities(originals, results); + + Collections.reverse(originals); + + results = exampleDb.selectAll(ExampleDb.Column.ID, OrderDirection.DESCENDING, conn); + assertEntities(originals, results); + + results = exampleDb.selectAll(ExampleDb.Column.NAME, OrderDirection.ASCENDING, conn); + assertEntities(originals, results); + } + } + + private void assertCount(long expected, Connection conn) throws SQLException { + Assertions.assertThat(exampleDb.count(conn)).isEqualTo(expected); + } + + private void assertEntity(Example expected, Connection conn) throws SQLException { + Assertions.assertThat(exampleDb.select(expected.getId(), conn)) + .usingRecursiveComparison() + .isEqualTo(expected); + } + + private void assertEntities(List expected, Connection conn) { + var actual = expected.stream().map(e -> { + try { + return exampleDb.select(e.getId(), conn); + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + }).collect(Collectors.toList()); + assertEntities(expected, actual); + } + + private void assertAllEntities(List expected, Connection conn) throws SQLException { + assertEntities(expected, exampleDb.selectAll(conn)); + } + + private void assertEntities(List expected, List actual) { + Assertions.assertThat(actual) + .usingRecursiveComparison() + .isEqualTo(expected); + } + + private void assertDoesNotExist(long id, Connection conn) throws SQLException { + Assertions.assertThat(exampleDb.select(id, conn)).isNull(); + } + + private Example example() { + return new Example() + .setName(RandomStringUtils.randomAlphanumeric(10)) + .setTimestamp(TestUtil.now()); + } + + private void createTable(Connection conn) throws SQLException { + try (var stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE example (id IDENTITY PRIMARY KEY, name VARCHAR(255), timestamp TIMESTAMP)"); + } + } + +} diff --git a/test/src/test/java/com/pwinckles/jdbcgen/test/util/TestUtil.java b/test/src/test/java/com/pwinckles/jdbcgen/test/util/TestUtil.java new file mode 100644 index 0000000..a558ed4 --- /dev/null +++ b/test/src/test/java/com/pwinckles/jdbcgen/test/util/TestUtil.java @@ -0,0 +1,29 @@ +package com.pwinckles.jdbcgen.test.util; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public final class TestUtil { + + private TestUtil() {} + + public static Instant now() { + return Instant.now().truncatedTo(ChronoUnit.MICROS); + } + + public static LocalDateTime nowLocalDateTime() { + return LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + } + + public static ZonedDateTime nowZonedDateTime() { + return ZonedDateTime.now().truncatedTo(ChronoUnit.MICROS); + } + + public static OffsetDateTime nowOffsetDateTime() { + return OffsetDateTime.now().truncatedTo(ChronoUnit.MICROS); + } + +}