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 extends TypeElement> 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);
+ }
+
+}