diff --git a/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapper.java b/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapper.java index 2b4ca57..2e72a3e 100644 --- a/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapper.java +++ b/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapper.java @@ -128,9 +128,11 @@ public interface JdbcMapper extends Closeable { public enum DatabaseType { DEFAULT(null, null), + BIND(null, null), STANDARD("numeric", "text"), UNNEST("NUMERIC", "VARCHAR"), - ORACLE("ARRAY_NUM_TYPE", "ARRAY_STR_TYPE"); + ORACLE("ARRAY_NUM_TYPE", "ARRAY_STR_TYPE"), + ; public final String arrayNumberTypeName, arrayStringTypeName; diff --git a/common/src/main/java/com/moparisthebest/jdbc/util/InListUtil.java b/common/src/main/java/com/moparisthebest/jdbc/util/InListUtil.java index e2eeee8..39268b8 100644 --- a/common/src/main/java/com/moparisthebest/jdbc/util/InListUtil.java +++ b/common/src/main/java/com/moparisthebest/jdbc/util/InListUtil.java @@ -1,11 +1,14 @@ package com.moparisthebest.jdbc.util; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; public class InListUtil { + public static final int defaultMaxSize = Integer.parseInt(System.getProperty("QueryMapper.BindInList.defaultMaxSize", "999")); + private static List> split(final List list, final int maxLength) { final int listSize = list.size(); final List> ret = new ArrayList>(); @@ -49,11 +52,35 @@ public class InListUtil { return sb.append(")").toString(); } - public static String toInList(final String fieldName, final Collection items, final int maxSize) { + private static String toInListNonEmpty(final String fieldName, final Collection items, final int maxSize) { return toInList(fieldName, items, maxSize, " IN (", " OR "); } - public static String toNotInList(final String fieldName, final Collection items, final int maxSize) { + private static String toNotInListNonEmpty(final String fieldName, final Collection items, final int maxSize) { return toInList(fieldName, items, maxSize, " NOT IN (", " AND "); } + + public static String toInList(final String fieldName, final Collection items, final int maxSize) { + return items == null || items.isEmpty() ? "(0=1)" : toInListNonEmpty(fieldName, items, maxSize); + } + + public static String toNotInList(final String fieldName, final Collection items, final int maxSize) { + return items == null || items.isEmpty() ? "(1=1)" : toNotInListNonEmpty(fieldName, items, maxSize); + } + + public static String toInList(final String fieldName, final Collection items) { + return toInList(fieldName, items, defaultMaxSize); + } + + public static String toNotInList(final String fieldName, final Collection items) { + return toInList(fieldName, items, defaultMaxSize); + } + + public static String toInList(final String fieldName, final T[] items) { + return toInList(fieldName, Arrays.asList(items), defaultMaxSize); + } + + public static String toNotInList(final String fieldName, final T[] items) { + return toInList(fieldName, Arrays.asList(items), defaultMaxSize); + } } diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java index 6271469..e4ec0fa 100644 --- a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java @@ -126,7 +126,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { enumType = types.getDeclaredType(elements.getTypeElement(Enum.class.getCanonicalName()), types.getWildcardType(null, null)); final String databaseType = processingEnv.getOptions().get("jdbcMapper.databaseType"); - defaultDatabaseType = databaseType == null || databaseType.isEmpty() ? JdbcMapper.DatabaseType.STANDARD : JdbcMapper.DatabaseType.valueOf(databaseType); + defaultDatabaseType = databaseType == null || databaseType.isEmpty() ? JdbcMapper.DatabaseType.STANDARD : JdbcMapper.DatabaseType.valueOf(databaseType.toUpperCase()); defaultArrayNumberTypeName = processingEnv.getOptions().get("jdbcMapper.arrayNumberTypeName"); if (defaultArrayNumberTypeName == null || defaultArrayNumberTypeName.isEmpty()) defaultArrayNumberTypeName = defaultDatabaseType.arrayNumberTypeName; @@ -194,9 +194,13 @@ public class JdbcMapperProcessor extends AbstractProcessor { case UNNEST: arrayInList = new UnNestArrayInList(arrayNumberTypeName, arrayStringTypeName); break; + case BIND: + //arrayInList = BindInList.instance(); + arrayInList = null; // todo: do something here + break; default: - // no support - arrayInList = null; + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Mapper.databaseType unsupported", element); + continue; } } else { arrayInList = null; @@ -386,7 +390,16 @@ public class JdbcMapperProcessor extends AbstractProcessor { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "cannot combine in/not in and clob/blob", bindParam); SpecialVariableElement inListBindParam = inListBindParams.get(paramName); if(inListBindParam == null) { - inListBindParam = new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.IN_LIST, ++inListBindParamsIdx); + inListBindParam = new SpecialVariableElement(bindParam, + databaseType == JdbcMapper.DatabaseType.BIND ? SpecialVariableElement.SpecialType.BIND_IN_LIST : SpecialVariableElement.SpecialType.IN_LIST, + ++inListBindParamsIdx); + if(databaseType == JdbcMapper.DatabaseType.BIND) { + final TypeMirror o = bindParam.asType(); + if(o.getKind() == TypeKind.DECLARED && types.isAssignable(o, streamType)) { + // todo: this allows them to name underscore names and create collisions, I'm ok with it + inListBindParam.setName("_" + inListBindParam.getSimpleName() + "StreamAsBindArray"); + } + } inListBindParams.put(paramName, inListBindParam); } bindParams.add(inListBindParam); @@ -408,6 +421,11 @@ public class JdbcMapperProcessor extends AbstractProcessor { "(" + inColumnName + " NOT IN(UNNEST(?)))" : "(" + inColumnName + " IN(UNNEST(?)))"; break; + case BIND: + replacement = "REPLACEMEWITHUNQUOTEDQUOTEPLZ + com.moparisthebest.jdbc.util.InListUtil.to" + + (not ? "Not" : "") + "InList(REPLACEMEWITHUNQUOTEDQUOTEPLZ" + + inColumnName + "REPLACEMEWITHUNQUOTEDQUOTEPLZ, " + inListBindParam.getName() + ") + REPLACEMEWITHUNQUOTEDQUOTEPLZ"; + break; default: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", bindParam); return false; @@ -441,13 +459,14 @@ public class JdbcMapperProcessor extends AbstractProcessor { processingEnv.getMessager().printMessage(warnOnUnusedParams != null ? Diagnostic.Kind.MANDATORY_WARNING : Diagnostic.Kind.ERROR, String.format("@JdbcMapper.SQL method has unused parameter '%s'", unusedParam.getKey()), unusedParam.getValue()); } } + final boolean notBindInList = !inListBindParams.isEmpty() && databaseType != JdbcMapper.DatabaseType.BIND; final SQLParser parsedSQl = ManualSQLParser.getSQLParser(sql, parser, sqlStatement); // now implementation w.write("\t\tPreparedStatement ps = null;\n"); if (parsedSQl.isSelect()) w.write("\t\tResultSet rs = null;\n"); - if(!inListBindParams.isEmpty()) + if(notBindInList) w.append("\t\tfinal Array[] _bindArrays = new Array[").append(Integer.toString(inListBindParams.size())).append("];\n"); w.write("\t\ttry {\n"); for (final SpecialVariableElement param : inListBindParams.values()) @@ -463,13 +482,21 @@ public class JdbcMapperProcessor extends AbstractProcessor { w.write("conn.prepareStatement("); } w.write('"'); - w.write(sqlStatement.replace("\"", "\\\"").replace("\n", "\\n\" +\n\t\t\t \"")); + w.write(sqlStatement.replace("\"", "\\\"") + .replace("\n", "\\n\" +\n\t\t\t \"") + .replace("REPLACEMEWITHUNQUOTEDQUOTEPLZ", "\"")); w.write("\");\n"); // now bind parameters - int count = 0; - for (final VariableElement param : bindParams) - setObject(w, ++count, param); + if(!inListBindParams.isEmpty() && databaseType == JdbcMapper.DatabaseType.BIND) { + w.write("\t\t\tint psParamCount = 0;\n"); + for (final VariableElement param : bindParams) + setObject(w, "++psParamCount", param); + } else { + int count = 0; + for (final VariableElement param : bindParams) + setObject(w, Integer.toString(++count), param); + } boolean closeRs = true; if (!parsedSQl.isSelect()) { @@ -503,7 +530,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { if (!sqlExceptionThrown) w.write("\t\t} catch(SQLException e) {\n\t\t\tthrow new RuntimeException(e);\n"); w.write("\t\t} finally {\n"); - if(!inListBindParams.isEmpty()) + if(notBindInList) w.append("\t\t\tfor(final Array _bindArray : _bindArrays)\n\t\t\t\ttryClose(_bindArray);\n"); if (parsedSQl.isSelect()) w.write("\t\t\ttryClose(rs);\n"); @@ -520,7 +547,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { w.write("\t\t\tif(e instanceof SQLException)\n\t\t\t\tthrow (SQLException)e;\n"); w.write("\t\t\tif(e instanceof RuntimeException)\n\t\t\t\tthrow (RuntimeException)e;\n"); w.write("\t\t\tthrow new RuntimeException(e);\n"); - if(!inListBindParams.isEmpty()) { + if(notBindInList) { w.write("\t\t} finally {\n"); w.append("\t\t\tfor(final Array _bindArray : _bindArrays)\n\t\t\t\ttryClose(_bindArray);\n"); } @@ -690,7 +717,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { } private void setArray(final Writer w, final JdbcMapper.DatabaseType databaseType, final String arrayNumberTypeName, final String arrayStringTypeName, final SpecialVariableElement specialParam) throws IOException { - final String variableName = specialParam.getSimpleName().toString(); + final String variableName = specialParam.getName(); final TypeMirror o = specialParam.asType(); final InListArgType inListArgType; @@ -726,6 +753,16 @@ public class JdbcMapperProcessor extends AbstractProcessor { return; } IFJAVA6_END*/ + if(databaseType == JdbcMapper.DatabaseType.BIND) { + specialParam.setComponentTypeString(componentType.toString()); + // only need to do something special for streams here... + if(inListArgType == InListArgType.STREAM) { + // todo: is array or collection better here, doesn't matter to us... + w.append("\t\t\tfinal ").append(specialParam.getComponentTypeString()).append("[] ").append(specialParam.getName()).append(" = "); + w.append(specialParam.getSimpleName()).append(".toArray(").append(specialParam.getComponentTypeString()).append("[]::new);\n"); + } + return; // we don't want any of the following + } w.append("\t\t\t_bindArrays[").append(Integer.toString(specialParam.index)).append("] = "); final String type = types.isAssignable(componentType, numberType) ? arrayNumberTypeName : arrayStringTypeName; switch (databaseType) { @@ -774,7 +811,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { w.write(");\n"); } - private void setObject(final Writer w, final int index, final VariableElement param) throws IOException { + private void setObject(final Writer w, final String index, final VariableElement param) throws IOException { String variableName = param.getSimpleName().toString(); final TypeMirror o = param.asType(); w.write("\t\t\t"); @@ -784,16 +821,21 @@ public class JdbcMapperProcessor extends AbstractProcessor { if (param instanceof SpecialVariableElement) { final SpecialVariableElement specialParam = (SpecialVariableElement) param; switch (specialParam.specialType) { + case BIND_IN_LIST: { + w.append("for(final ").append(specialParam.getComponentTypeString()).append(" _bindInListParam : ").append(specialParam.getName()).append(")\n"); + w.append("\t\t\t\tps.setObject(").append(index).append(", _bindInListParam);\n"); + return; + } case IN_LIST: { w.write("ps.setArray("); - w.write(Integer.toString(index)); + w.write(index); w.append(", _bindArrays[").append(Integer.toString(specialParam.index)).append("]);\n"); return; } case BLOB: { if (types.isAssignable(o, stringType)) { w.write("try {\n\t\t\t\tps.setBlob("); - w.write(Integer.toString(index)); + w.write(index); w.write(", "); w.write(variableName); w.write(" == null ? null : new java.io.ByteArrayInputStream("); @@ -884,7 +926,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { w.write("ps.set"); w.write(method); w.write('('); - w.write(Integer.toString(index)); + w.write(index); w.write(", "); w.write(variableName); w.write(");\n"); diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/SpecialVariableElement.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/SpecialVariableElement.java index cbb906d..97910df 100644 --- a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/SpecialVariableElement.java +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/SpecialVariableElement.java @@ -12,6 +12,7 @@ import java.util.Set; class SpecialVariableElement implements VariableElement { enum SpecialType { + BIND_IN_LIST, IN_LIST, CLOB, BLOB, @@ -22,6 +23,8 @@ class SpecialVariableElement implements VariableElement { final String blobStringCharset; final int index; + String name, componentTypeString; + SpecialVariableElement(final VariableElement delegate, final SpecialType specialType) { this(delegate, specialType, null, 0); } @@ -39,6 +42,23 @@ class SpecialVariableElement implements VariableElement { this.specialType = specialType; this.blobStringCharset = blobStringCharset; this.index = index; + this.name = getSimpleName().toString(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getComponentTypeString() { + return componentTypeString; + } + + public void setComponentTypeString(String componentTypeString) { + this.componentTypeString = componentTypeString; } @Override diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/BindInList.java b/querymapper/src/main/java/com/moparisthebest/jdbc/BindInList.java index 370f2a0..13862ec 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/BindInList.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/BindInList.java @@ -3,6 +3,7 @@ package com.moparisthebest.jdbc; import java.sql.Connection; import java.util.*; +import static com.moparisthebest.jdbc.util.InListUtil.defaultMaxSize; import static com.moparisthebest.jdbc.util.InListUtil.toInList; /** @@ -10,8 +11,6 @@ import static com.moparisthebest.jdbc.util.InListUtil.toInList; */ public class BindInList implements InList { - private static final int defaultMaxSize = Integer.parseInt(System.getProperty("QueryMapper.BindInList.defaultMaxSize", "999")); - private static final InList instance = new BindInList(); public static InList instance() { diff --git a/test/pom.xml b/test/pom.xml index f6a62a2..ffcbdfe 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -194,5 +194,21 @@ + + bind + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -AjdbcMapper.databaseType=BIND + + + + + + \ No newline at end of file diff --git a/test/src/main/java/com/moparisthebest/jdbc/codegen/QmDao.java b/test/src/main/java/com/moparisthebest/jdbc/codegen/QmDao.java index 73983a1..9af0e27 100644 --- a/test/src/main/java/com/moparisthebest/jdbc/codegen/QmDao.java +++ b/test/src/main/java/com/moparisthebest/jdbc/codegen/QmDao.java @@ -245,6 +245,9 @@ public interface QmDao extends JdbcMapper { @SQL(selectStrVal) ZoneOffset getZoneOffsetString(long valNo) throws SQLException; + @SQL("SELECT person_no, first_name, last_name, birth_date from person WHERE {person_no IN personNos} ORDER BY person_no") + List getFieldPeopleStream(Stream personNos) throws SQLException; + //IFJAVA8_END @SQL("SELECT person_no, first_name, last_name, birth_date from person WHERE {person_no IN personNos} ORDER BY person_no") diff --git a/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperQmDao.java b/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperQmDao.java index c579f00..facafd3 100644 --- a/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperQmDao.java +++ b/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperQmDao.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.*; //IFJAVA8_START +import java.util.stream.Collectors; import java.util.stream.Stream; import java.time.*; //IFJAVA8_END @@ -453,6 +454,11 @@ public class QueryMapperQmDao implements QmDao { return qm.toObject(selectStrVal, ZoneOffset.class, valNo); } + @Override + public List getFieldPeopleStream(final Stream personNos) throws SQLException { + return lqm.toList("SELECT * from person WHERE " + inListReplace + " ORDER BY person_no", FieldPerson.class, lqm.inList("person_no", personNos.collect(Collectors.toList()))); + } + //IFJAVA8_END @Override diff --git a/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperTypeQmDao.java b/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperTypeQmDao.java index 5b0fe5c..6e87994 100644 --- a/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperTypeQmDao.java +++ b/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperTypeQmDao.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; //IFJAVA8_START +import java.util.stream.Collectors; import java.util.stream.Stream; import java.time.*; //IFJAVA8_END @@ -332,6 +333,11 @@ public class QueryMapperTypeQmDao extends QueryMapperQmDao { return qm.toType(selectStrVal, new TypeReference() {}, valNo); } + @Override + public List getFieldPeopleStream(final Stream personNos) throws SQLException { + return lqm.toType("SELECT * from person WHERE " + inListReplace + " ORDER BY person_no", new TypeReference>() {}, lqm.inList("person_no", personNos.collect(Collectors.toList()))); + } + //IFJAVA8_END @Override diff --git a/test/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java b/test/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java index 7c2ea0d..35d913a 100644 --- a/test/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java +++ b/test/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java @@ -75,16 +75,16 @@ public class QueryMapperTest { static { final Collection jUrls = new ArrayList(); final String jdbcUrl = System.getProperty("jdbcUrl", "all"); - if(jdbcUrl.equals("all")) { - //jUrls.add("jdbc:hsqldb:mem:testDB"); // remove this from all since it supports custom InList, until generic inlist supported in JdbcMapper + if(jdbcUrl.equals("all") || jdbcUrl.equals("bind")) { + jUrls.add("jdbc:hsqldb:mem:testDB"); jUrls.add("jdbc:derby:memory:testDB;create=true"); jUrls.add("jdbc:h2:mem:testDB"); jUrls.add("jdbc:sqlite::memory:"); - } else if(jdbcUrl.equals("hsql") || jdbcUrl.equals("hsqldb")) { + } else if(jdbcUrl.equals("hsql") || jdbcUrl.equals("hsqldb") || jdbcUrl.equals("unnest")) { jUrls.add("jdbc:hsqldb:mem:testDB"); } else if(jdbcUrl.equals("derby")) { jUrls.add("jdbc:derby:memory:testDB;create=true"); - } else if(jdbcUrl.equals("h2")) { + } else if(jdbcUrl.equals("h2") || jdbcUrl.equals("any")) { jUrls.add("jdbc:h2:mem:testDB"); } else if(jdbcUrl.equals("sqlite")) { jUrls.add("jdbc:sqlite::memory:"); @@ -521,28 +521,6 @@ public class QueryMapperTest { assertArrayEquals(people, fromDb.toArray()); } - private static boolean supportsInList(final QmDao qm) { - return qm instanceof QueryMapperQmDao || supportsArrayInList(qm.getConnection()); - } - - @Test - public void testListQueryMapperList() throws SQLException { - if(!supportsInList(qm)) - return; - final List fromDb = qm.getFieldPeople(Arrays.asList(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo())); - assertArrayEquals(people, fromDb.toArray()); - } - - @Test - public void testListQueryMapperListMultiple() throws SQLException { - if(!supportsInList(qm)) - return; - final List fromDb = qm.getFieldPeopleByName( - Arrays.asList(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo()), - Arrays.asList(people[0].getFirstName(), people[1].getFirstName(), people[2].getFirstName())); - assertArrayEquals(people, fromDb.toArray()); - } - @Test public void testResultSetIterable() throws SQLException { final ResultSetIterable rsi = qm.getThreePeopleResultSetIterable(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo()); @@ -683,5 +661,25 @@ public class QueryMapperTest { ); } + @Test + public void testListQueryMapperStream() throws SQLException { + final List fromDb = qm.getFieldPeopleStream(Stream.of(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo())); + assertArrayEquals(people, fromDb.toArray()); + } + //IFJAVA8_END + + @Test + public void testListQueryMapperList() throws SQLException { + final List fromDb = qm.getFieldPeople(Arrays.asList(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo())); + assertArrayEquals(people, fromDb.toArray()); + } + + @Test + public void testListQueryMapperListMultiple() throws SQLException { + final List fromDb = qm.getFieldPeopleByName( + Arrays.asList(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo()), + Arrays.asList(people[0].getFirstName(), people[1].getFirstName(), people[2].getFirstName())); + assertArrayEquals(people, fromDb.toArray()); + } }