Move InList and PreparedStatementUtil up to common, add support for custom sql/runtime bind in JdbcMapper

This commit is contained in:
Travis Burtrum 2019-02-07 01:09:15 -05:00
parent 5e7bf3e0e2
commit 26617e0781
21 changed files with 1003 additions and 333 deletions

View File

@ -55,29 +55,16 @@ public class ArrayInList implements InList {
} }
public <T> InListObject inList(final Connection conn, final String columnName, final Collection<T> values) throws SQLException { public <T> InListObject inList(final Connection conn, final String columnName, final Collection<T> values) throws SQLException {
return values == null || values.isEmpty() ? InListObject.inEmpty : new ArrayListObject( return values == null || values.isEmpty() ? InListObject.inEmpty : new InListObject(
columnAppendIn(columnName), columnAppendIn(columnName),
toArray(conn, values) toArray(conn, values)
); );
} }
public <T> InListObject notInList(final Connection conn, final String columnName, final Collection<T> values) throws SQLException { public <T> InListObject notInList(final Connection conn, final String columnName, final Collection<T> values) throws SQLException {
return values == null || values.isEmpty() ? InListObject.notInEmpty : new ArrayListObject( return values == null || values.isEmpty() ? InListObject.notInEmpty : new InListObject(
columnAppendNotIn(columnName), columnAppendNotIn(columnName),
toArray(conn, values) toArray(conn, values)
); );
} }
class ArrayListObject extends InListObject {
private final Array array;
public ArrayListObject(final String sql, final Array array) {
super(sql);
this.array = array;
}
public Array getArray() {
return array;
}
}
} }

View File

@ -34,29 +34,16 @@ public class BindInList implements InList {
} }
public <T> InListObject inList(final Connection conn, final String columnName, final Collection<T> values) { public <T> InListObject inList(final Connection conn, final String columnName, final Collection<T> values) {
return values == null || values.isEmpty() ? InListObject.inEmpty : new BindInListObject( return values == null || values.isEmpty() ? InListObject.inEmpty : new InListObject(
toInList(columnName, values, this.maxSize), toInList(columnName, values, this.maxSize),
values.toArray() values
); );
} }
public <T> InListObject notInList(final Connection conn, final String columnName, final Collection<T> values) { public <T> InListObject notInList(final Connection conn, final String columnName, final Collection<T> values) {
return values == null || values.isEmpty() ? InListObject.notInEmpty : new BindInListObject( return values == null || values.isEmpty() ? InListObject.notInEmpty : new InListObject(
toNotInList(columnName, values, this.maxSize), toNotInList(columnName, values, this.maxSize),
values.toArray() values
); );
} }
class BindInListObject extends InListObject {
private final Object[] bindObjects;
public BindInListObject(final String sql, final Object[] bindObjects) {
super(sql);
this.bindObjects = bindObjects;
}
public Object[] getBindObjects() {
return bindObjects;
}
}
} }

View File

@ -0,0 +1,108 @@
package com.moparisthebest.jdbc;
import com.moparisthebest.jdbc.codegen.JdbcMapper;
import com.moparisthebest.jdbc.util.Bindable;
import com.moparisthebest.jdbc.util.InListUtil;
import com.moparisthebest.jdbc.util.PreparedStatementUtil;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
/**
* For a column name and a Collection, return a Object usable by QueryMapper for binding to a PreparedStatement and
* ListQueryMapper for substituting in the query
*/
public interface InList {
InList defaultInList = InListObject.getDefaultInListInstance();
/**
* Returns an InList instance for use with this connection
* @param conn connection which may be inspected to determine best InList to use
* @return InList instance
*/
public InList instance(final Connection conn);
/**
* Returns an Object who's .toString returns a String for a query, and QueryMapper knows how to bind to a PreparedStatement
* @param columnName Column name for query
* @param values values for in list
* @return object
*/
public <T> InListObject inList(final Connection conn, final String columnName, final Collection<T> values) throws SQLException;
/**
* Returns an Object who's .toString returns a String for a query, and QueryMapper knows how to bind to a PreparedStatement
* @param columnName Column name for query
* @param values values for not in list
* @return object
*/
public <T> InListObject notInList(final Connection conn, final String columnName, final Collection<T> values) throws SQLException;
class InListObject implements Bindable {
static final InListObject inEmpty = new InListObject(InListUtil.inEmpty, null);
static final InListObject notInEmpty = new InListObject(InListUtil.notInEmpty, null);
public static InListObject inEmpty() {
return inEmpty;
}
public static InListObject notInEmpty() {
return notInEmpty;
}
private final String sql;
private final Object bindObject;
public InListObject(final String sql, final Object bindObject) {
this.sql = sql;
this.bindObject = bindObject;
}
@Override
public final String toString() {
return sql;
}
@Override
public Object getBindObject() {
return bindObject;
}
private static InList getDefaultInListInstance() {
try {
final String inListClassName = System.getProperty("QueryMapper.defaultInList.class");
if(inListClassName != null) {
final Class<?> inListClass = Class.forName(inListClassName);
final Method method = inListClass.getMethod(System.getProperty("QueryMapper.defaultInList.method", "instance"));
return (InList) method.invoke(null);
} else {
// todo: change default to OPTIMAL ?
final String type = System.getProperty("queryMapper.databaseType", System.getProperty("jdbcMapper.databaseType", "BIND"));
if(type.equals("OPTIMAL")) {
return OptimalInList.instance();
} else {
switch (JdbcMapper.DatabaseType.valueOf(type)) {
case DEFAULT:
case BIND:
return BindInList.instance();
case ANY:
return ArrayInList.instance();
case ORACLE:
return OracleArrayInList.instance();
case UNNEST:
return UnNestArrayInList.instance();
default:
throw new RuntimeException("Invalid queryMapper.databaseType: " + type);
}
}
}
} catch (Throwable e) {
// NEVER ignore
throw new RuntimeException(e);
}
}
}
}

View File

@ -1,5 +1,7 @@
package com.moparisthebest.jdbc.codegen; package com.moparisthebest.jdbc.codegen;
import com.moparisthebest.jdbc.util.SqlBuilder;
import java.io.Closeable; import java.io.Closeable;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -14,6 +16,14 @@ public interface JdbcMapper extends Closeable {
Connection getConnection(); Connection getConnection();
//IFJAVA8_START
default SqlBuilder sqlBuilder() {
return SqlBuilder.of(getConnection()); // todo: should this use the current inList ?
}
//IFJAVA8_END
@Override @Override
void close(); void close();

View File

@ -0,0 +1,31 @@
package com.moparisthebest.jdbc.util;
import static com.moparisthebest.jdbc.util.PreparedStatementUtil.noBind;
public interface Bindable {
Bindable empty = new Bindable() {
@Override
public Object getBindObject() {
return noBind;
}
@Override
public String toString() {
return "";
}
};
/**
* This returns raw SQL to be included in a query, can contain bind params as standard ?
* @return
*/
String toString();
/**
* This returns an object (or list of objects, or list of list etc) to bind to the SQL snippet returned by toString()
* PreparedStatementUtil must know how to bind this
* @return
*/
Object getBindObject();
}

View File

@ -0,0 +1,201 @@
package com.moparisthebest.jdbc.util;
import com.moparisthebest.jdbc.InList;
import java.io.*;
import java.nio.charset.Charset;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
//IFJAVA8_START
import java.time.*;
import static java.nio.charset.StandardCharsets.UTF_8;
//IFJAVA8_END
public class PreparedStatementUtil {
public static final Object noBind = new Object();
public static PreparedStatement bind(final PreparedStatement ps, final Object... bindObjects) throws SQLException {
recursiveBind(ps, bindObjects);
return ps;
}
public static void setObject(final PreparedStatement ps, final int index, final Object o) throws SQLException {
// we are going to put most common ones up top so it should execute faster normally
if (o == null || o instanceof String || o instanceof Number)
ps.setObject(index, o);
// java.util.Date support, put it in a Timestamp
else if (o instanceof java.util.Date)
ps.setObject(index, o.getClass().equals(java.util.Date.class) ? new java.sql.Timestamp(((java.util.Date)o).getTime()) : o);
//IFJAVA8_START// todo: other java.time types
else if (o instanceof Instant)
ps.setObject(index, java.sql.Timestamp.from((Instant)o));
else if (o instanceof LocalDateTime)
ps.setObject(index, java.sql.Timestamp.valueOf((LocalDateTime)o));
else if (o instanceof LocalDate)
ps.setObject(index, java.sql.Date.valueOf((LocalDate)o));
else if (o instanceof LocalTime)
ps.setObject(index, java.sql.Time.valueOf((LocalTime)o));
else if (o instanceof ZonedDateTime)
ps.setObject(index, java.sql.Timestamp.from(((ZonedDateTime)o).toInstant()));
else if (o instanceof OffsetDateTime)
ps.setObject(index, java.sql.Timestamp.from(((OffsetDateTime)o).toInstant()));
else if (o instanceof OffsetTime)
ps.setObject(index, java.sql.Time.valueOf(((OffsetTime)o).toLocalTime())); // todo: no timezone?
//IFJAVA8_END
// CLOB support
else if (o instanceof Reader)
ps.setClob(index, (Reader) o);
else if (o instanceof ClobString)
ps.setClob(index, ((ClobString) o).s == null ? null : new StringReader(((ClobString) o).s));
else if (o instanceof java.sql.Clob)
ps.setClob(index, (java.sql.Clob) o);
// BLOB support
else if (o instanceof byte[])
ps.setBlob(index, new ByteArrayInputStream((byte[]) o));
else if (o instanceof InputStream)
ps.setBlob(index, (InputStream) o);
else if (o instanceof File)
try {
ps.setBlob(index, new FileInputStream((File) o)); // todo: does this close this or leak a file descriptor?
} catch (FileNotFoundException e) {
throw new SQLException("File to Blob FileNotFoundException", e);
}
else if (o instanceof BlobString)
ps.setBlob(index, ((BlobString) o).s == null ? null : new ByteArrayInputStream(((BlobString) o).s.getBytes(((BlobString) o).charset)));
else if (o instanceof java.sql.Blob)
ps.setBlob(index, (java.sql.Blob) o);
else if (o instanceof java.sql.Array)
ps.setArray(index, (java.sql.Array) o);
else if (o instanceof Enum)
ps.setObject(index, ((Enum)o).name());
else
ps.setObject(index, o); // probably won't get here ever, but just in case...
/*
switch(ps.getParameterMetaData().getParameterType(index)){ // 'java.sql.SQLException: Unsupported feature', fully JDBC 3.0 compliant my ass, freaking oracle...
case Types.CLOB:
if(o instanceof String)
ps.setObject(index, o);
else if (o instanceof Reader)
ps.setClob(index, (Reader) o);
else if (o instanceof Clob)
ps.setClob(index, (Clob) o);
return;
case Types.BLOB:
if (o instanceof byte[])
ps.setBlob(index, new ByteArrayInputStream((byte[])o));
else if (o instanceof InputStream)
ps.setBlob(index, (InputStream) o);
else if (o instanceof File)
try {
ps.setBlob(index, new FileInputStream((File) o));
} catch (FileNotFoundException e) {
throw new SQLException("File to Blob FileNotFoundException", e);
}
else if (o instanceof Blob)
ps.setBlob(index, (Blob) o);
else if(o instanceof String)
try{
ps.setBlob(index, new ByteArrayInputStream(((String) o).getBytes("UTF-8")));
}catch(UnsupportedEncodingException e){
throw new SQLException("String to Blob UnsupportedEncodingException", e);
}
return;
default:
ps.setObject(index, o);
}
*/
}
public static int recursiveBind(final PreparedStatement ps, final Object... bindObjects) throws SQLException {
return recursiveBindIndex(ps, 0, bindObjects);
}
public static int recursiveBindIndex(final PreparedStatement ps, int index, final Object... bindObjects) throws SQLException {
if (bindObjects != null && bindObjects.length > 0) {
for (final Object o : bindObjects) {
if (o != null) {
if (o == InList.InListObject.inEmpty() || o == InList.InListObject.notInEmpty() || o == noBind) {
continue; // ignore
} else if (o instanceof Bindable) {
index = recursiveBindIndex(ps, index, ((Bindable) o).getBindObject());
continue;
} else if (o instanceof Object[]) {
index = recursiveBindIndex(ps, index, (Object[]) o);
continue;
} else if (o instanceof Collection) {
// is creating 1 array and doing 1 method call faster than falling through to iterator and making multiple method calls/arrays? *probably* ?
index = recursiveBindIndex(ps, index, ((Collection) o).toArray());
continue;
} else if(o instanceof Iterable) {
for(final Object o2 : (Iterable) o) {
index = recursiveBindIndex(ps, index, o2);
}
continue;
}
}
//System.out.printf("index: '%d' bound to '%s'\n", index+1, o);
setObject(ps, ++index, o);
//ps.setObject(++index, o);
}
}
return index;
}
public static Object wrapClob(String s) {
return new ClobString(s);
}
public static Object wrapBlob(String s) {
return new BlobString(s, UTF_8);
}
public static Object wrapBlob(final String s, final Charset charset) {
return new BlobString(s, charset == null ? UTF_8 : charset);
}
private static class StringWrapper {
public final String s;
private StringWrapper(String s) {
this.s = s;
}
public String toString() {
return s;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof StringWrapper)) return false;
StringWrapper that = (StringWrapper) o;
return !(s != null ? !s.equals(that.s) : that.s != null);
}
public int hashCode() {
return s != null ? s.hashCode() : 0;
}
}
private static class ClobString extends StringWrapper {
private ClobString(String s) {
super(s);
}
}
private static class BlobString extends StringWrapper {
private final Charset charset;
private BlobString(final String s, final Charset charset) {
super(s);
this.charset = charset;
}
}
/*IFJAVA6_START
private static final Charset UTF_8 = Charset.forName("UTF-8");
IFJAVA6_END*/
}

View File

@ -0,0 +1,463 @@
package com.moparisthebest.jdbc.util;
import com.moparisthebest.jdbc.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
//IFJAVA8_START
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
//IFJAVA8_END
import static com.moparisthebest.jdbc.InList.defaultInList;
public class SqlBuilder implements Appendable, CharSequence, Collection<Object>, Bindable {
private final StringBuilder sb;
private final Collection<Object> bindObjects;
private final InList inList;
private final Connection conn;
private SqlBuilder(final Connection conn, final InList inList, final StringBuilder sb, final Collection<Object> bindObjects) {
if(sb == null || bindObjects == null || inList == null || conn == null)
throw new NullPointerException("all arguments must be non-null");
this.sb = sb;
this.bindObjects = bindObjects;
this.inList = inList.instance(conn);
this.conn = conn;
}
public static SqlBuilder of(final Connection conn, final InList inList, final StringBuilder sb, final Collection<Object> bindObjects) {
return new SqlBuilder(conn, inList, sb, bindObjects);
}
public static SqlBuilder of(final Connection conn) {
return new SqlBuilder(conn, defaultInList, new StringBuilder(), new ArrayList<Object>());
}
public static SqlBuilder of(final Connection conn, final Collection<Object> bindObjects) {
return new SqlBuilder(conn, defaultInList, new StringBuilder(), bindObjects);
}
public static SqlBuilder of(final Connection conn, final StringBuilder sb) {
return new SqlBuilder(conn, defaultInList, sb, new ArrayList<Object>());
}
public static SqlBuilder of(final Connection conn, final InList inList) {
return new SqlBuilder(conn, inList, new StringBuilder(), new ArrayList<Object>());
}
// start custom SqlBuilder methods
public SqlBuilder append(final String sql, final Object bindObject) {
sb.append(sql);
this.bindObjects.add(bindObject);
return this;
}
public SqlBuilder append(final String sql, final Object... bindObjects) {
return this.append(sql, (Object) bindObjects);
}
public <T> SqlBuilder appendInList(final String columnName, final Collection<T> values) throws SQLException {
final InList.InListObject inListObject = inList.inList(conn, columnName, values);
return this.append(inListObject.toString(), inListObject.getBindObject());
}
public <T> SqlBuilder appendNotInList(final String columnName, final Collection<T> values) throws SQLException {
final InList.InListObject inListObject = inList.notInList(conn, columnName, values);
return this.append(inListObject.toString(), inListObject.getBindObject());
}
public StringBuilder getStringBuilder() {
return sb;
}
@Override
public Collection<Object> getBindObject() {
return bindObjects;
}
public InList getInList() {
return inList;
}
public Connection getConnection() {
return conn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SqlBuilder)) return false;
final SqlBuilder that = (SqlBuilder) o;
return bindObjects.equals(that.bindObjects) && sb.toString().equals(that.sb.toString());
}
@Override
public int hashCode() {
int result = sb.toString().hashCode();
result = 31 * result + bindObjects.hashCode();
return result;
}
// end custom SqlBuilder methods
// start StringBuilder delegates
public SqlBuilder append(Object obj) {
sb.append(obj);
return this;
}
public SqlBuilder append(String str) {
sb.append(str);
return this;
}
public SqlBuilder append(StringBuffer sb) {
this.sb.append(sb);
return this;
}
@Override
public SqlBuilder append(CharSequence s) {
sb.append(s);
return this;
}
@Override
public SqlBuilder append(CharSequence s, int start, int end) {
sb.append(s, start, end);
return this;
}
public SqlBuilder append(char[] str) {
sb.append(str);
return this;
}
public SqlBuilder append(char[] str, int offset, int len) {
sb.append(str, offset, len);
return this;
}
public SqlBuilder append(boolean b) {
sb.append(b);
return this;
}
@Override
public SqlBuilder append(char c) {
sb.append(c);
return this;
}
public SqlBuilder append(int i) {
sb.append(i);
return this;
}
public SqlBuilder append(long lng) {
sb.append(lng);
return this;
}
public SqlBuilder append(float f) {
sb.append(f);
return this;
}
public SqlBuilder append(double d) {
sb.append(d);
return this;
}
public SqlBuilder appendCodePoint(int codePoint) {
sb.appendCodePoint(codePoint);
return this;
}
public SqlBuilder delete(int start, int end) {
sb.delete(start, end);
return this;
}
public SqlBuilder deleteCharAt(int index) {
sb.deleteCharAt(index);
return this;
}
public SqlBuilder replace(int start, int end, String str) {
sb.replace(start, end, str);
return this;
}
public SqlBuilder insert(int index, char[] str, int offset, int len) {
sb.insert(index, str, offset, len);
return this;
}
public SqlBuilder insert(int offset, Object obj) {
sb.insert(offset, obj);
return this;
}
public SqlBuilder insert(int offset, String str) {
sb.insert(offset, str);
return this;
}
public SqlBuilder insert(int offset, char[] str) {
sb.insert(offset, str);
return this;
}
public SqlBuilder insert(int dstOffset, CharSequence s) {
sb.insert(dstOffset, s);
return this;
}
public SqlBuilder insert(int dstOffset, CharSequence s, int start, int end) {
sb.insert(dstOffset, s, start, end);
return this;
}
public SqlBuilder insert(int offset, boolean b) {
sb.insert(offset, b);
return this;
}
public SqlBuilder insert(int offset, char c) {
sb.insert(offset, c);
return this;
}
public SqlBuilder insert(int offset, int i) {
sb.insert(offset, i);
return this;
}
public SqlBuilder insert(int offset, long l) {
sb.insert(offset, l);
return this;
}
public SqlBuilder insert(int offset, float f) {
sb.insert(offset, f);
return this;
}
public SqlBuilder insert(int offset, double d) {
sb.insert(offset, d);
return this;
}
public int indexOf(String str) {
return sb.indexOf(str);
}
public int indexOf(String str, int fromIndex) {
return sb.indexOf(str, fromIndex);
}
public int lastIndexOf(String str) {
return sb.lastIndexOf(str);
}
public int lastIndexOf(String str, int fromIndex) {
return sb.lastIndexOf(str, fromIndex);
}
public SqlBuilder reverse() {
sb.reverse();
return this;
}
@Override
public String toString() {
return sb.toString();
}
@Override
public int length() {
return sb.length();
}
public int capacity() {
return sb.capacity();
}
public void ensureCapacity(int minimumCapacity) {
sb.ensureCapacity(minimumCapacity);
}
public void trimToSize() {
sb.trimToSize();
}
public void setLength(int newLength) {
sb.setLength(newLength);
}
@Override
public char charAt(int index) {
return sb.charAt(index);
}
public int codePointAt(int index) {
return sb.codePointAt(index);
}
public int codePointBefore(int index) {
return sb.codePointBefore(index);
}
public int codePointCount(int beginIndex, int endIndex) {
return sb.codePointCount(beginIndex, endIndex);
}
public int offsetByCodePoints(int index, int codePointOffset) {
return sb.offsetByCodePoints(index, codePointOffset);
}
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) {
sb.getChars(srcBegin, srcEnd, dst, dstBegin);
}
public void setCharAt(int index, char ch) {
sb.setCharAt(index, ch);
}
public String substring(int start) {
return sb.substring(start);
}
@Override
public CharSequence subSequence(int start, int end) {
return sb.subSequence(start, end);
}
public String substring(int start, int end) {
return sb.substring(start, end);
}
//IFJAVA8_START
@Override
public IntStream chars() {
return sb.chars();
}
@Override
public IntStream codePoints() {
return sb.codePoints();
}
//IFJAVA8_END
// end StringBuilder delegates
// start Collection delegates
@Override
public int size() {
return bindObjects.size();
}
@Override
public boolean isEmpty() {
return bindObjects.isEmpty();
}
@Override
public boolean contains(Object o) {
return bindObjects.contains(o);
}
@Override
public Iterator<Object> iterator() {
return bindObjects.iterator();
}
@Override
public Object[] toArray() {
return bindObjects.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return bindObjects.toArray(a);
}
@Override
public boolean add(Object e) {
return bindObjects.add(e);
}
@Override
public boolean remove(Object o) {
return bindObjects.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return bindObjects.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends Object> c) {
return bindObjects.addAll(c);
}
@Override
public boolean removeAll(Collection<?> c) {
return bindObjects.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return bindObjects.retainAll(c);
}
@Override
public void clear() {
bindObjects.clear();
}
//IFJAVA8_START
@Override
public boolean removeIf(Predicate<? super Object> filter) {
return bindObjects.removeIf(filter);
}
@Override
public Spliterator<Object> spliterator() {
return bindObjects.spliterator();
}
@Override
public Stream<Object> stream() {
return bindObjects.stream();
}
@Override
public Stream<Object> parallelStream() {
return bindObjects.parallelStream();
}
@Override
public void forEach(Consumer<? super Object> action) {
bindObjects.forEach(action);
}
//IFJAVA8_END
// end Collection delegates
}

View File

@ -3,6 +3,7 @@ package com.moparisthebest.jdbc.codegen;
import com.moparisthebest.jdbc.*; import com.moparisthebest.jdbc.*;
import com.moparisthebest.jdbc.codegen.spring.SpringRepository; import com.moparisthebest.jdbc.codegen.spring.SpringRepository;
import com.moparisthebest.jdbc.codegen.spring.SpringScope; import com.moparisthebest.jdbc.codegen.spring.SpringScope;
import com.moparisthebest.jdbc.util.Bindable;
import javax.annotation.processing.*; import javax.annotation.processing.*;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
@ -28,6 +29,7 @@ import java.util.stream.Stream;
import static com.moparisthebest.jdbc.TryClose.tryClose; import static com.moparisthebest.jdbc.TryClose.tryClose;
import static com.moparisthebest.jdbc.codegen.JdbcMapper.DatabaseType.ORACLE; import static com.moparisthebest.jdbc.codegen.JdbcMapper.DatabaseType.ORACLE;
import static com.moparisthebest.jdbc.codegen.JdbcMapperFactory.SUFFIX; import static com.moparisthebest.jdbc.codegen.JdbcMapperFactory.SUFFIX;
import static com.moparisthebest.jdbc.codegen.SpecialVariableElement.SpecialType.SQL;
/** /**
* Created by mopar on 5/24/17. * Created by mopar on 5/24/17.
@ -36,7 +38,7 @@ import static com.moparisthebest.jdbc.codegen.JdbcMapperFactory.SUFFIX;
@SupportedOptions({"jdbcMapper.databaseType", "jdbcMapper.arrayNumberTypeName", "jdbcMapper.arrayStringTypeName", "jdbcMapper.allowedMaxRowParamNames", "jdbcMapper.sqlCheckerClass"}) @SupportedOptions({"jdbcMapper.databaseType", "jdbcMapper.arrayNumberTypeName", "jdbcMapper.arrayStringTypeName", "jdbcMapper.allowedMaxRowParamNames", "jdbcMapper.sqlCheckerClass"})
public class JdbcMapperProcessor extends AbstractProcessor { public class JdbcMapperProcessor extends AbstractProcessor {
public static final Pattern paramPattern = Pattern.compile("\\{(([^\\s]+)\\s+(([Nn][Oo][Tt]\\s+)?[Ii][Nn]\\s+))?([BbCc][Ll][Oo][Bb]\\s*:\\s*([^:}]+\\s*:\\s*)?)?([^}]+)\\}"); public static final Pattern paramPattern = Pattern.compile("\\{(([^\\s]+)\\s+(([Nn][Oo][Tt]\\s+)?[Ii][Nn]\\s+))?([BbCcSs][LlQq][OoLl][Bb]?\\s*:\\s*([^:}]+\\s*:\\s*)?)?([^}]+)\\}");
public static final SourceVersion RELEASE_8; public static final SourceVersion RELEASE_8;
public static boolean java8; public static boolean java8;
@ -64,11 +66,10 @@ public class JdbcMapperProcessor extends AbstractProcessor {
} }
static TypeMirror sqlExceptionType, stringType, numberType, utilDateType, readerType, clobType, connectionType, jdbcMapperType, static TypeMirror sqlExceptionType, stringType, numberType, utilDateType, readerType, clobType, connectionType, jdbcMapperType,
byteArrayType, inputStreamType, fileType, blobType, sqlArrayType, collectionType, calendarType, cleanerType, enumType; byteArrayType, inputStreamType, fileType, blobType, sqlArrayType, collectionType, iterableType, bindableType, calendarType, cleanerType, enumType;
//IFJAVA8_START //IFJAVA8_START
static TypeMirror streamType, instantType, localDateTimeType, localDateType, localTimeType, zonedDateTimeType, offsetDateTimeType, offsetTimeType; static TypeMirror streamType, instantType, localDateTimeType, localDateType, localTimeType, zonedDateTimeType, offsetDateTimeType, offsetTimeType;
//IFJAVA8_END //IFJAVA8_END
private TypeElement cleanerElement;
private JdbcMapper.DatabaseType defaultDatabaseType; private JdbcMapper.DatabaseType defaultDatabaseType;
private String defaultArrayNumberTypeName, defaultArrayStringTypeName; private String defaultArrayNumberTypeName, defaultArrayStringTypeName;
private SQLChecker sqlChecker; private SQLChecker sqlChecker;
@ -124,9 +125,10 @@ public class JdbcMapperProcessor extends AbstractProcessor {
byteArrayType = types.getArrayType(types.getPrimitiveType(TypeKind.BYTE)); byteArrayType = types.getArrayType(types.getPrimitiveType(TypeKind.BYTE));
sqlArrayType = elements.getTypeElement(java.sql.Array.class.getCanonicalName()).asType(); sqlArrayType = elements.getTypeElement(java.sql.Array.class.getCanonicalName()).asType();
collectionType = types.getDeclaredType(elements.getTypeElement(Collection.class.getCanonicalName()), types.getWildcardType(null, null)); collectionType = types.getDeclaredType(elements.getTypeElement(Collection.class.getCanonicalName()), types.getWildcardType(null, null));
iterableType = types.getDeclaredType(elements.getTypeElement(Iterable.class.getCanonicalName()), types.getWildcardType(null, null));
cleanerElement = elements.getTypeElement(Cleaner.class.getCanonicalName()); bindableType = elements.getTypeElement(Bindable.class.getCanonicalName()).asType();
cleanerType = types.getDeclaredType(cleanerElement, types.getWildcardType(null, null)); cleanerType = types.getDeclaredType(elements.getTypeElement(Cleaner.class.getCanonicalName()), types.getWildcardType(null, null));
enumType = types.getDeclaredType(elements.getTypeElement(Enum.class.getCanonicalName()), types.getWildcardType(null, null)); enumType = types.getDeclaredType(elements.getTypeElement(Enum.class.getCanonicalName()), types.getWildcardType(null, null));
@ -344,6 +346,8 @@ public class JdbcMapperProcessor extends AbstractProcessor {
String calendarName = null, cleanerName = null; String calendarName = null, cleanerName = null;
CompileTimeResultSetMapper.MaxRows maxRows = CompileTimeResultSetMapper.MaxRows.getMaxRows(sql.maxRows()); CompileTimeResultSetMapper.MaxRows maxRows = CompileTimeResultSetMapper.MaxRows.getMaxRows(sql.maxRows());
boolean sqlExceptionThrown = false; boolean sqlExceptionThrown = false;
boolean sqlParam = false;
boolean sqlIterableParam = false;
{ {
// now parameters // now parameters
final List<? extends VariableElement> params = eeMethod.getParameters(); final List<? extends VariableElement> params = eeMethod.getParameters();
@ -390,29 +394,40 @@ public class JdbcMapperProcessor extends AbstractProcessor {
continue; continue;
} }
unusedParams.remove(paramName); unusedParams.remove(paramName);
final String clobBlob = bindParamMatcher.group(5); final String clobBlobSql = bindParamMatcher.group(5);
final String inColumnName = bindParamMatcher.group(2); final String inColumnName = bindParamMatcher.group(2);
if (inColumnName == null) { if (inColumnName == null) {
bindParamMatcher.appendReplacement(sb, "?"); if(clobBlobSql == null){
if(clobBlob == null){ bindParamMatcher.appendReplacement(sb, "?");
bindParams.add(bindParam); bindParams.add(bindParam);
} else { } else {
// regex ensures this can only be C for clob or B for blob final String upperClobBlobSql = clobBlobSql.toUpperCase();
final boolean clobNotBlob = 'C' == Character.toUpperCase(clobBlob.charAt(0)); String blobCharset = bindParamMatcher.group(6);
final String blobCharset = bindParamMatcher.group(6); if(blobCharset != null)
if(clobNotBlob) { blobCharset = blobCharset.substring(0, blobCharset.indexOf(':')).trim();
if(blobCharset != null) if(upperClobBlobSql.startsWith("SQL")) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "blob character set not valid with clob", bindParam); bindParamMatcher.appendReplacement(sb, "REPLACEMEWITHUNQUOTEDQUOTEPLZ + " + paramName + " + REPLACEMEWITHUNQUOTEDQUOTEPLZ");
bindParams.add(new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.CLOB)); final SpecialVariableElement sve = new SpecialVariableElement(bindParam, SQL, blobCharset);
bindParams.add(sve);
sqlParam = true;
sqlIterableParam |= sve.iterable || sve.bindable;
} else if(upperClobBlobSql.startsWith("CLOB") || upperClobBlobSql.startsWith("BLOB")) {
bindParamMatcher.appendReplacement(sb, "?");
final boolean clobNotBlob = 'C' == upperClobBlobSql.charAt(0);
if (clobNotBlob) {
if (blobCharset != null)
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "blob character set not valid with clob", bindParam);
bindParams.add(new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.CLOB));
} else {
bindParams.add(new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.BLOB, blobCharset));
}
} else { } else {
bindParams.add(new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.BLOB, blobCharset == null ? null : processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "special variable type can only be clob/blob/sql, not " + clobBlobSql, bindParam);
blobCharset.substring(0, blobCharset.indexOf(':')).trim()
));
} }
} }
} else { } else {
if(clobBlob != null) if(clobBlobSql != null)
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "cannot combine in/not in and clob/blob", bindParam); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "cannot combine in/not in and clob/blob/sql", bindParam);
SpecialVariableElement inListBindParam = inListBindParams.get(paramName); SpecialVariableElement inListBindParam = inListBindParams.get(paramName);
if(inListBindParam == null) { if(inListBindParam == null) {
inListBindParam = new SpecialVariableElement(bindParam, inListBindParam = new SpecialVariableElement(bindParam,
@ -502,7 +517,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write("\t\t\tps = "); w.write("\t\t\tps = ");
final boolean isGeneratedKeyLong = !parsedSQl.isSelect() && (returnType.equals("long") || returnType.equals("java.lang.Long")); final boolean isGeneratedKeyLong = !parsedSQl.isSelect() && (returnType.equals("long") || returnType.equals("java.lang.Long"));
// todo: make isGeneratedKeyLong work with cachePreparedStatements // todo: make isGeneratedKeyLong work with cachePreparedStatements
final boolean cachePreparedStatements = sql.cachePreparedStatement().combine(defaultCachePreparedStatements) && !bindInList && !isGeneratedKeyLong; final boolean cachePreparedStatements = sql.cachePreparedStatement().combine(defaultCachePreparedStatements) && !bindInList && !isGeneratedKeyLong && !sqlParam;
if (cachePreparedStatements) { if (cachePreparedStatements) {
w.write("this.prepareStatement("); w.write("this.prepareStatement(");
w.write(Integer.toString(cachedPreparedStatements)); w.write(Integer.toString(cachedPreparedStatements));
@ -527,14 +542,17 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write(");\n"); w.write(");\n");
// now bind parameters // now bind parameters
if(bindInList) { if(bindInList || sqlIterableParam) {
w.write("\t\t\tint psParamCount = 0;\n"); w.write("\t\t\tint psParamCount = 0;\n");
for (final VariableElement param : bindParams) for (final VariableElement param : bindParams)
setObject(w, "++psParamCount", param); setObject(w, "++psParamCount", param);
} else { } else {
int count = 0; int count = 0;
for (final VariableElement param : bindParams) for (final VariableElement param : bindParams) {
setObject(w, Integer.toString(++count), param); // better place/way to do this?
if(!(param instanceof SpecialVariableElement && ((SpecialVariableElement)param).specialType == SQL))
setObject(w, Integer.toString(++count), param);
}
} }
boolean closeRs = true; boolean closeRs = true;
@ -922,6 +940,16 @@ public class JdbcMapperProcessor extends AbstractProcessor {
} }
break; break;
} }
case SQL: {
if(specialParam.iterable || specialParam.bindable) {
w.append("psParamCount = com.moparisthebest.jdbc.util.PreparedStatementUtil.recursiveBindIndex(ps, psParamCount, ").append(specialParam.name);
if(specialParam.bindable)
w.append(".getBindObject()"); // handle null? I think no... because then the SQL query get's "null" put in it and meh... can return noBind or an empty array...
w.append(");");
}
w.append('\n');
return;
}
} }
} }
// end special behavior // end special behavior

View File

@ -128,6 +128,8 @@ public class SimpleSQLChecker implements SQLChecker {
return new ByteArrayInputStream(new byte[1]); return new ByteArrayInputStream(new byte[1]);
case CLOB: case CLOB:
return new StringReader(defaultString); return new StringReader(defaultString);
case SQL:
return specialParam.blobStringCharset == null ? "" : specialParam.blobStringCharset;
} }
} }
// end special behavior // end special behavior

View File

@ -16,12 +16,14 @@ class SpecialVariableElement implements VariableElement {
IN_LIST, IN_LIST,
CLOB, CLOB,
BLOB, BLOB,
SQL,
} }
final VariableElement delegate; final VariableElement delegate;
final SpecialType specialType; final SpecialType specialType;
final String blobStringCharset; final String blobStringCharset;
final int index; final int index;
final boolean iterable, bindable;
String name, componentTypeString; String name, componentTypeString;
@ -43,6 +45,8 @@ class SpecialVariableElement implements VariableElement {
this.blobStringCharset = blobStringCharset; this.blobStringCharset = blobStringCharset;
this.index = index; this.index = index;
this.name = getSimpleName().toString(); this.name = getSimpleName().toString();
this.iterable = specialType == SpecialType.SQL && JdbcMapperProcessor.types.isAssignable(delegate.asType(), JdbcMapperProcessor.iterableType);
this.bindable = !this.iterable && specialType == SpecialType.SQL && JdbcMapperProcessor.types.isAssignable(delegate.asType(), JdbcMapperProcessor.bindableType);
} }
public String getName() { public String getName() {

View File

@ -30,6 +30,14 @@ public class ParamPatternTest {
assertEquals("utf-16", blobCharset.substring(0, blobCharset.indexOf(':')).trim()); assertEquals("utf-16", blobCharset.substring(0, blobCharset.indexOf(':')).trim());
blobCharset = "utf-16 : "; blobCharset = "utf-16 : ";
assertEquals("utf-16", blobCharset.substring(0, blobCharset.indexOf(':')).trim()); assertEquals("utf-16", blobCharset.substring(0, blobCharset.indexOf(':')).trim());
testMatch("{sql:sqlStatement}", s(null, null, null, null, "sql:", null, "sqlStatement"));
testMatch("{sql: sqlStatement}", s(null, null, null, null, "sql: ", null, "sqlStatement"));
testMatch("{SQL: sqlStatement}", s(null, null, null, null, "SQL: ", null, "sqlStatement"));
testMatch("{Sql: sqlStatement}", s(null, null, null, null, "Sql: ", null, "sqlStatement"));
testMatch("{Sql : sqlStatement}", s(null, null, null, null, "Sql : ", null, "sqlStatement"));
testMatch("{sql:person:sqlStatement}", s(null, null, null, null, "sql:person:", "person:", "sqlStatement"));
testMatch("{sql:JOIN person ON p.person_no = b.person_no:sqlStatement}", s(null, null, null, null, "sql:JOIN person ON p.person_no = b.person_no:", "JOIN person ON p.person_no = b.person_no:", "sqlStatement"));
} }
private static void testMatch(final String sql, final Collection<String[]> expected) { private static void testMatch(final String sql, final Collection<String[]> expected) {

View File

@ -1,53 +0,0 @@
package com.moparisthebest.jdbc;
import com.moparisthebest.jdbc.util.InListUtil;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
/**
* For a column name and a Collection, return a Object usable by QueryMapper for binding to a PreparedStatement and
* ListQueryMapper for substituting in the query
*/
public interface InList {
/**
* Returns an InList instance for use with this connection
* @param conn connection which may be inspected to determine best InList to use
* @return InList instance
*/
public InList instance(final Connection conn);
/**
* Returns an Object who's .toString returns a String for a query, and QueryMapper knows how to bind to a PreparedStatement
* @param columnName Column name for query
* @param values values for in list
* @return object
*/
public <T> InListObject inList(final Connection conn, final String columnName, final Collection<T> values) throws SQLException;
/**
* Returns an Object who's .toString returns a String for a query, and QueryMapper knows how to bind to a PreparedStatement
* @param columnName Column name for query
* @param values values for not in list
* @return object
*/
public <T> InListObject notInList(final Connection conn, final String columnName, final Collection<T> values) throws SQLException;
class InListObject {
static final InListObject inEmpty = new InListObject(InListUtil.inEmpty);
static final InListObject notInEmpty = new InListObject(InListUtil.notInEmpty);
private final String sql;
public InListObject(final String sql) {
this.sql = sql;
}
@Override
public final String toString() {
return sql;
}
}
}

View File

@ -1,17 +1,9 @@
package com.moparisthebest.jdbc; package com.moparisthebest.jdbc;
import com.moparisthebest.jdbc.codegen.JdbcMapper;
import com.moparisthebest.jdbc.util.ResultSetIterable;
import java.lang.reflect.Method;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*;
//IFJAVA8_START import static com.moparisthebest.jdbc.InList.defaultInList;
import java.util.stream.Stream;
//IFJAVA8_END
public class ListQueryMapper extends QueryMapper { public class ListQueryMapper extends QueryMapper {

View File

@ -2,8 +2,10 @@ package com.moparisthebest.jdbc;
import com.moparisthebest.jdbc.codegen.JdbcMapper; import com.moparisthebest.jdbc.codegen.JdbcMapper;
import com.moparisthebest.jdbc.codegen.JdbcMapperFactory; import com.moparisthebest.jdbc.codegen.JdbcMapperFactory;
import com.moparisthebest.jdbc.util.PreparedStatementUtil;
import com.moparisthebest.jdbc.util.ResultSetIterable; import com.moparisthebest.jdbc.util.ResultSetIterable;
import com.moparisthebest.jdbc.util.ResultSetUtil; import com.moparisthebest.jdbc.util.ResultSetUtil;
import com.moparisthebest.jdbc.util.SqlBuilder;
import java.io.*; import java.io.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -13,15 +15,14 @@ import java.util.*;
//IFJAVA8_START //IFJAVA8_START
import java.time.*; import java.time.*;
import java.util.stream.Stream; import java.util.stream.Stream;
import static java.nio.charset.StandardCharsets.UTF_8;
//IFJAVA8_END //IFJAVA8_END
import static com.moparisthebest.jdbc.InList.defaultInList;
import static com.moparisthebest.jdbc.TryClose.tryClose; import static com.moparisthebest.jdbc.TryClose.tryClose;
public class QueryMapper implements JdbcMapper { public class QueryMapper implements JdbcMapper {
public static final Object noBind = new Object(); public static final Object noBind = PreparedStatementUtil.noBind;
public static final ResultSetMapper defaultRsm = new ResultSetMapper(); public static final ResultSetMapper defaultRsm = new ResultSetMapper();
protected static final int[] ORACLE_SINGLE_COLUMN_INDEX = new int[]{1}; protected static final int[] ORACLE_SINGLE_COLUMN_INDEX = new int[]{1};
@ -51,46 +52,8 @@ public class QueryMapper implements JdbcMapper {
return conn.prepareStatement(sql); return conn.prepareStatement(sql);
} }
}; };
private static final Charset UTF_8 = Charset.forName("UTF-8");
IFJAVA6_END*/ IFJAVA6_END*/
protected static final InList defaultInList = getDefaultInList();
private static InList getDefaultInList() {
try {
final String inListClassName = System.getProperty("QueryMapper.defaultInList.class");
if(inListClassName != null) {
final Class<?> inListClass = Class.forName(inListClassName);
final Method method = inListClass.getMethod(System.getProperty("QueryMapper.defaultInList.method", "instance"));
return (InList) method.invoke(null);
} else {
// todo: change default to OPTIMAL ?
final String type = System.getProperty("queryMapper.databaseType", System.getProperty("jdbcMapper.databaseType", "BIND"));
if(type.equals("OPTIMAL")) {
return OptimalInList.instance();
} else {
switch (JdbcMapper.DatabaseType.valueOf(type)) {
case DEFAULT:
case BIND:
return BindInList.instance();
case ANY:
return ArrayInList.instance();
case ORACLE:
return OracleArrayInList.instance();
case UNNEST:
return UnNestArrayInList.instance();
default:
throw new RuntimeException("Invalid queryMapper.databaseType: " + type);
}
}
}
} catch (Throwable e) {
// NEVER ignore
throw new RuntimeException(e);
}
}
protected final ResultSetMapper cm; protected final ResultSetMapper cm;
protected final Connection conn; protected final Connection conn;
protected final boolean closeConn; protected final boolean closeConn;
@ -167,53 +130,16 @@ public class QueryMapper implements JdbcMapper {
} }
} }
private static class StringWrapper {
public final String s;
private StringWrapper(String s) {
this.s = s;
}
public String toString() {
return s;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof StringWrapper)) return false;
StringWrapper that = (StringWrapper) o;
return !(s != null ? !s.equals(that.s) : that.s != null);
}
public int hashCode() {
return s != null ? s.hashCode() : 0;
}
}
private static class ClobString extends StringWrapper {
private ClobString(String s) {
super(s);
}
}
private static class BlobString extends StringWrapper {
private final Charset charset;
private BlobString(final String s, final Charset charset) {
super(s);
this.charset = charset;
}
}
public static Object wrapClob(String s) { public static Object wrapClob(String s) {
return new ClobString(s); return PreparedStatementUtil.wrapClob(s);
} }
public static Object wrapBlob(String s) { public static Object wrapBlob(String s) {
return new BlobString(s, UTF_8); return PreparedStatementUtil.wrapBlob(s);
} }
public static Object wrapBlob(final String s, final Charset charset) { public static Object wrapBlob(final String s, final Charset charset) {
return new BlobString(s, charset == null ? UTF_8 : charset); return PreparedStatementUtil.wrapBlob(s, charset);
} }
// start in list specific code // start in list specific code
@ -238,6 +164,10 @@ public class QueryMapper implements JdbcMapper {
recursiveReplace(sb, (Object[]) o); recursiveReplace(sb, (Object[]) o);
} else if (o instanceof Collection) { } else if (o instanceof Collection) {
recursiveReplace(sb, ((Collection) o).toArray()); recursiveReplace(sb, ((Collection) o).toArray());
} else if(o instanceof Iterable) {
for(final Object o2 : (Iterable) o) {
recursiveReplace(sb, o2);
}
} }
} }
} }
@ -262,142 +192,25 @@ public class QueryMapper implements JdbcMapper {
// end in list specific code // end in list specific code
public static void setObject(final PreparedStatement ps, final int index, final Object o) throws SQLException { public static void setObject(final PreparedStatement ps, final int index, final Object o) throws SQLException {
// we are going to put most common ones up top so it should execute faster normally PreparedStatementUtil.setObject(ps, index, o);
if (o == null || o instanceof String || o instanceof Number)
ps.setObject(index, o);
// java.util.Date support, put it in a Timestamp
else if (o instanceof java.util.Date)
ps.setObject(index, o.getClass().equals(java.util.Date.class) ? new java.sql.Timestamp(((java.util.Date)o).getTime()) : o);
//IFJAVA8_START// todo: other java.time types
else if (o instanceof Instant)
ps.setObject(index, java.sql.Timestamp.from((Instant)o));
else if (o instanceof LocalDateTime)
ps.setObject(index, java.sql.Timestamp.valueOf((LocalDateTime)o));
else if (o instanceof LocalDate)
ps.setObject(index, java.sql.Date.valueOf((LocalDate)o));
else if (o instanceof LocalTime)
ps.setObject(index, java.sql.Time.valueOf((LocalTime)o));
else if (o instanceof ZonedDateTime)
ps.setObject(index, java.sql.Timestamp.from(((ZonedDateTime)o).toInstant()));
else if (o instanceof OffsetDateTime)
ps.setObject(index, java.sql.Timestamp.from(((OffsetDateTime)o).toInstant()));
else if (o instanceof OffsetTime)
ps.setObject(index, java.sql.Time.valueOf(((OffsetTime)o).toLocalTime())); // todo: no timezone?
//IFJAVA8_END
// CLOB support
else if (o instanceof Reader)
ps.setClob(index, (Reader) o);
else if (o instanceof ClobString)
ps.setClob(index, ((ClobString) o).s == null ? null : new StringReader(((ClobString) o).s));
else if (o instanceof java.sql.Clob)
ps.setClob(index, (java.sql.Clob) o);
// BLOB support
else if (o instanceof byte[])
ps.setBlob(index, new ByteArrayInputStream((byte[]) o));
else if (o instanceof InputStream)
ps.setBlob(index, (InputStream) o);
else if (o instanceof File)
try {
ps.setBlob(index, new FileInputStream((File) o)); // todo: does this close this or leak a file descriptor?
} catch (FileNotFoundException e) {
throw new SQLException("File to Blob FileNotFoundException", e);
}
else if (o instanceof BlobString)
ps.setBlob(index, ((BlobString) o).s == null ? null : new ByteArrayInputStream(((BlobString) o).s.getBytes(((BlobString) o).charset)));
else if (o instanceof java.sql.Blob)
ps.setBlob(index, (java.sql.Blob) o);
else if (o instanceof ArrayInList.ArrayListObject)
ps.setArray(index, ((ArrayInList.ArrayListObject) o).getArray());
else if (o instanceof java.sql.Array)
ps.setArray(index, (java.sql.Array) o);
else if (o instanceof Enum)
ps.setObject(index, ((Enum)o).name());
else
ps.setObject(index, o); // probably won't get here ever, but just in case...
/*
switch(ps.getParameterMetaData().getParameterType(index)){ // 'java.sql.SQLException: Unsupported feature', fully JDBC 3.0 compliant my ass, freaking oracle...
case Types.CLOB:
if(o instanceof String)
ps.setObject(index, o);
else if (o instanceof Reader)
ps.setClob(index, (Reader) o);
else if (o instanceof Clob)
ps.setClob(index, (Clob) o);
return;
case Types.BLOB:
if (o instanceof byte[])
ps.setBlob(index, new ByteArrayInputStream((byte[])o));
else if (o instanceof InputStream)
ps.setBlob(index, (InputStream) o);
else if (o instanceof File)
try {
ps.setBlob(index, new FileInputStream((File) o));
} catch (FileNotFoundException e) {
throw new SQLException("File to Blob FileNotFoundException", e);
}
else if (o instanceof Blob)
ps.setBlob(index, (Blob) o);
else if(o instanceof String)
try{
ps.setBlob(index, new ByteArrayInputStream(((String) o).getBytes("UTF-8")));
}catch(UnsupportedEncodingException e){
throw new SQLException("String to Blob UnsupportedEncodingException", e);
}
return;
default:
ps.setObject(index, o);
}
*/
} }
public static int recursiveBind(final PreparedStatement ps, final Object... bindObjects) throws SQLException { public static int recursiveBind(final PreparedStatement ps, final Object... bindObjects) throws SQLException {
return recursiveBind(ps, 0, bindObjects); return PreparedStatementUtil.recursiveBind(ps, bindObjects);
}
private static int recursiveBind(final PreparedStatement ps, int index, final Object... bindObjects) throws SQLException {
if (bindObjects != null && bindObjects.length > 0) {
for (Object o : bindObjects) {
if (o != null) {
if (o == InList.InListObject.inEmpty || o == InList.InListObject.notInEmpty || o == noBind) {
continue; // ignore
} else if (o instanceof BindInList.BindInListObject) {
if (((BindInList.BindInListObject) o).getBindObjects() != null)
index = recursiveBind(ps, index, ((BindInList.BindInListObject) o).getBindObjects());
continue;
} else if (o instanceof Object[]) {
index = recursiveBind(ps, index, (Object[]) o);
continue;
} else if (o instanceof Collection) {
index = recursiveBind(ps, index, ((Collection) o).toArray());
continue;
}
}
//System.out.printf("index: '%d' bound to '%s'\n", index+1, o);
setObject(ps, ++index, o);
//ps.setObject(++index, o);
}
}
return index;
} }
public static PreparedStatement bindStatement(final PreparedStatement ps, final Object... bindObjects) throws SQLException { public static PreparedStatement bindStatement(final PreparedStatement ps, final Object... bindObjects) throws SQLException {
recursiveBind(ps, bindObjects); return PreparedStatementUtil.bind(ps, bindObjects);
return ps;
}
protected static PreparedStatement bind(final PreparedStatement ps, final Object... bindObjects) throws SQLException {
return bindStatement(ps, bindObjects);
} }
protected static ResultSet bindExecute(final PreparedStatement ps, final Object... bindObjects) throws SQLException { protected static ResultSet bindExecute(final PreparedStatement ps, final Object... bindObjects) throws SQLException {
return bind(ps, bindObjects).executeQuery(); return PreparedStatementUtil.bind(ps, bindObjects).executeQuery();
} }
// these update the database // these update the database
public int executeUpdate(PreparedStatement ps, final Object... bindObjects) throws SQLException { public int executeUpdate(PreparedStatement ps, final Object... bindObjects) throws SQLException {
return bind(ps, bindObjects).executeUpdate(); return PreparedStatementUtil.bind(ps, bindObjects).executeUpdate();
} }
public boolean executeUpdateSuccess(PreparedStatement ps, final Object... bindObjects) throws SQLException { public boolean executeUpdateSuccess(PreparedStatement ps, final Object... bindObjects) throws SQLException {

View File

@ -1,10 +1,10 @@
package com.moparisthebest.jdbc.codegen; package com.moparisthebest.jdbc.codegen;
import com.moparisthebest.jdbc.dto.*; import com.moparisthebest.jdbc.dto.*;
import com.moparisthebest.jdbc.util.CaseInsensitiveHashMap; import com.moparisthebest.jdbc.util.*;
import com.moparisthebest.jdbc.util.ResultSetIterable;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -264,4 +264,23 @@ public interface QmDao extends JdbcMapper {
@SQL("INSERT INTO a_thaoeu_table (a_thaoeu_table_val) VALUES ({value})") @SQL("INSERT INTO a_thaoeu_table (a_thaoeu_table_val) VALUES ({value})")
long insertGetGeneratedKey(long value) throws SQLException; long insertGetGeneratedKey(long value) throws SQLException;
@SQL("SELECT person_no FROM person WHERE {sql:sql}")
List<Long> selectRandomSql(String sql) throws SQLException;
@SQL("SELECT person_no FROM person WHERE {sql:sql}")
List<Long> selectRandomSqlBuilder(SqlBuilder sql) throws SQLException;
@SQL("SELECT person_no FROM person WHERE person_no = {personNo1} {sql:sql} OR first_name = {firstName}")
List<Long> selectRandomSql(long personNo1, String sql, String firstName) throws SQLException;
@SQL("SELECT person_no FROM person WHERE person_no = {personNo1} {sql:sql} OR first_name = {firstName}")
List<Long> selectRandomSqlBuilder(long personNo1, Bindable sql, String firstName) throws SQLException;
// these we just check if they generated
@SQL("INSERT {sql:sql}")
void insertRandomSqlCollection(Collection<Long> sql) throws SQLException;
@SQL("INSERT {sql:sql}")
void insertRandomSqlIterable(Iterable<Long> sql) throws SQLException;
} }

View File

@ -2,8 +2,7 @@ package com.moparisthebest.jdbc.codegen;
import com.moparisthebest.jdbc.*; import com.moparisthebest.jdbc.*;
import com.moparisthebest.jdbc.dto.*; import com.moparisthebest.jdbc.dto.*;
import com.moparisthebest.jdbc.util.CaseInsensitiveHashMap; import com.moparisthebest.jdbc.util.*;
import com.moparisthebest.jdbc.util.ResultSetIterable;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
@ -392,11 +391,11 @@ public class QueryMapperQmDao implements QmDao {
@Override @Override
public List<FieldPerson> getFieldPeopleByName(final List<Long> personNos, final List<String> names) throws SQLException { public List<FieldPerson> getFieldPeopleByName(final List<Long> personNos, final List<String> names) throws SQLException {
return qm.toList("SELECT * from person WHERE " + inListReplace + " AND (" + inListReplace + " OR " + inListReplace + ") ORDER BY person_no", return qm.toList("SELECT * from person WHERE " + inListReplace + " AND (" + inListReplace + " OR " + inListReplace + ") ORDER BY person_no",
FieldPerson.class, FieldPerson.class,
qm.inList("person_no", personNos), qm.inList("person_no", personNos),
qm.inList("first_name", names), qm.inList("first_name", names),
qm.inList("last_name", names) qm.inList("last_name", names)
); );
} }
@Override @Override
@ -413,4 +412,34 @@ public class QueryMapperQmDao implements QmDao {
public long insertGetGeneratedKey(long value) throws SQLException { public long insertGetGeneratedKey(long value) throws SQLException {
return qm.insertGetGeneratedKey("INSERT INTO a_thaoeu_table (a_thaoeu_table_val) VALUES (?)", value); return qm.insertGetGeneratedKey("INSERT INTO a_thaoeu_table (a_thaoeu_table_val) VALUES (?)", value);
} }
@Override
public List<Long> selectRandomSql(final String sql) throws SQLException {
return qm.toList("SELECT person_no FROM person WHERE " + sql, Long.class);
}
@Override
public List<Long> selectRandomSqlBuilder(final SqlBuilder sql) throws SQLException {
return qm.toList("SELECT person_no FROM person WHERE " + sql, Long.class, sql);
}
@Override
public List<Long> selectRandomSql(final long personNo1, final String sql, final String firstName) throws SQLException {
return qm.toList("SELECT person_no FROM person WHERE person_no = ? " + sql + " OR first_name = ?", Long.class, personNo1, firstName);
}
@Override
public List<Long> selectRandomSqlBuilder(final long personNo1, final Bindable sql, final String firstName) throws SQLException {
return qm.toList("SELECT person_no FROM person WHERE person_no = ? " + sql + " OR first_name = ?", Long.class, personNo1, sql, firstName);
}
@Override
public void insertRandomSqlCollection(final Collection<Long> sql) throws SQLException {
qm.executeUpdate("INSERT " + sql, sql);
}
@Override
public void insertRandomSqlIterable(final Iterable<Long> sql) throws SQLException {
qm.executeUpdate("INSERT " + sql, sql);
}
} }

View File

@ -3,8 +3,7 @@ package com.moparisthebest.jdbc.codegen;
import com.moparisthebest.jdbc.ResultSetMapper; import com.moparisthebest.jdbc.ResultSetMapper;
import com.moparisthebest.jdbc.TypeReference; import com.moparisthebest.jdbc.TypeReference;
import com.moparisthebest.jdbc.dto.*; import com.moparisthebest.jdbc.dto.*;
import com.moparisthebest.jdbc.util.CaseInsensitiveHashMap; import com.moparisthebest.jdbc.util.*;
import com.moparisthebest.jdbc.util.ResultSetIterable;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
@ -358,4 +357,24 @@ public class QueryMapperTypeQmDao extends QueryMapperQmDao {
public List<FieldPerson> getFieldPeopleNotIn(final List<Long> personNos) throws SQLException { public List<FieldPerson> getFieldPeopleNotIn(final List<Long> personNos) throws SQLException {
return qm.toType("SELECT * from person WHERE " + inListReplace + " ORDER BY person_no", new TypeReference<List<FieldPerson>>() {}, qm.notInList("person_no", personNos)); return qm.toType("SELECT * from person WHERE " + inListReplace + " ORDER BY person_no", new TypeReference<List<FieldPerson>>() {}, qm.notInList("person_no", personNos));
} }
@Override
public List<Long> selectRandomSql(final String sql) throws SQLException {
return qm.toType("SELECT person_no FROM person WHERE " + sql, new TypeReference<List<Long>>() {});
}
@Override
public List<Long> selectRandomSqlBuilder(final SqlBuilder sql) throws SQLException {
return qm.toType("SELECT person_no FROM person WHERE " + sql, new TypeReference<List<Long>>() {}, sql);
}
@Override
public List<Long> selectRandomSql(final long personNo1, final String sql, final String firstName) throws SQLException {
return qm.toType("SELECT person_no FROM person WHERE person_no = ? " + sql + " OR first_name = ?", new TypeReference<List<Long>>() {}, personNo1, firstName);
}
@Override
public List<Long> selectRandomSqlBuilder(final long personNo1, final Bindable sql, final String firstName) throws SQLException {
return qm.toType("SELECT person_no FROM person WHERE person_no = ? " + sql + " OR first_name = ?", new TypeReference<List<Long>>() {}, personNo1, sql, firstName);
}
} }

View File

@ -6,7 +6,9 @@ import com.moparisthebest.jdbc.codegen.QmDao;
import com.moparisthebest.jdbc.codegen.QueryMapperQmDao; import com.moparisthebest.jdbc.codegen.QueryMapperQmDao;
import com.moparisthebest.jdbc.codegen.QueryMapperTypeQmDao; import com.moparisthebest.jdbc.codegen.QueryMapperTypeQmDao;
import com.moparisthebest.jdbc.dto.*; import com.moparisthebest.jdbc.dto.*;
import com.moparisthebest.jdbc.util.Bindable;
import com.moparisthebest.jdbc.util.ResultSetIterable; import com.moparisthebest.jdbc.util.ResultSetIterable;
import com.moparisthebest.jdbc.util.SqlBuilder;
import org.junit.*; import org.junit.*;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
@ -756,4 +758,24 @@ public class QueryMapperTest {
final List<FieldPerson> fromDb = qm.getFieldPeopleNotIn(Arrays.asList(bosses[0].getPersonNo(), bosses[1].getPersonNo(), bosses[2].getPersonNo())); final List<FieldPerson> fromDb = qm.getFieldPeopleNotIn(Arrays.asList(bosses[0].getPersonNo(), bosses[1].getPersonNo(), bosses[2].getPersonNo()));
assertArrayEquals(people, fromDb.toArray()); assertArrayEquals(people, fromDb.toArray());
} }
@Test
public void testSelectRandomSql() throws Throwable {
final List<Long> arr = Arrays.asList(1L, 2L, 3L);
assertEquals(arr, qm.selectRandomSql("person_no in (1,2,3)"));
assertEquals(arr, qm.selectRandomSql(1L, " OR person_no in (2,3)", "NoNameMatch"));
assertEquals(Collections.singletonList(2L), qm.selectRandomSql(2L, "", "NoNameMatch"));
}
@Test
public void testSelectRandomSqlBuilder() throws Throwable {
final List<Long> arr = Arrays.asList(1L, 2L, 3L);
assertEquals(arr, qm.selectRandomSqlBuilder(SqlBuilder.of(qm.getConnection()).appendInList("person_no", arr)));
assertEquals(arr, qm.selectRandomSqlBuilder(SqlBuilder.of(qm.getConnection()).append("person_no = ? OR ", 1L).appendInList("person_no", Arrays.asList(2L, 3L))));
assertEquals(arr, qm.selectRandomSqlBuilder(SqlBuilder.of(qm.getConnection()).append("person_no = 1 OR ").appendInList("person_no", Arrays.asList(2L, 3L))));
assertEquals(arr, qm.selectRandomSqlBuilder(1L, SqlBuilder.of(qm.getConnection()).append(" OR person_no in (2,3)"), "NoNameMatch"));
assertEquals(Collections.singletonList(2L), qm.selectRandomSqlBuilder(2L, SqlBuilder.of(qm.getConnection()), "NoNameMatch"));
assertEquals(Collections.singletonList(3L), qm.selectRandomSqlBuilder(3L, Bindable.empty, "NoNameMatch"));
assertEquals(arr, qm.selectRandomSqlBuilder(2L, SqlBuilder.of(qm.getConnection()).append("OR person_no = ? OR ", 1L).appendInList("person_no", Collections.singletonList(3L)), "NoNameMatch"));
}
} }