Remove Blob and Clob annotations in favor of SQL bind syntax

This commit is contained in:
Travis Burtrum 2018-03-14 02:25:11 -04:00
parent ba7fb733b2
commit edd6fc6e65
7 changed files with 187 additions and 136 deletions

View File

@ -103,35 +103,6 @@ public interface JdbcMapper extends Closeable {
long maxRows() default -1; long maxRows() default -1;
} }
// these are to annotate parameters for special bind to PreparedStatement behavior
/**
* Use PreparedStatement.setBlob
*
* Only required for String
*
* Also valid for byte[], InputStream, Blob, File. But for those .setBlob is called even without this annotation
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER})
public @interface Blob {
/**
* The charsetName sent into String.getBytes()
*/
String charsetName() default "UTF-8";
}
/**
* Use PreparedStatement.setClob
*
* Only valid for String
*
* Also valid for Clob, Reader. But for those .setClob is called even without this annotation
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER})
public @interface Clob {}
/** /**
* Equivalent to QueryMapper.toObject for arrays, and .toSingleMap for maps * Equivalent to QueryMapper.toObject for arrays, and .toSingleMap for maps
*/ */

View File

@ -34,7 +34,7 @@ import static com.moparisthebest.jdbc.codegen.JdbcMapperFactory.SUFFIX;
@SupportedSourceVersion(SourceVersion.RELEASE_5) @SupportedSourceVersion(SourceVersion.RELEASE_5)
public class JdbcMapperProcessor extends AbstractProcessor { public class JdbcMapperProcessor extends AbstractProcessor {
private static final Pattern paramPattern = Pattern.compile("\\{(([^\\s]+)\\s+(([Nn][Oo][Tt]\\s+)?[Ii][Nn]\\s+))?([^}]+)\\}"); 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 SourceVersion RELEASE_8; public static final SourceVersion RELEASE_8;
public static boolean java8; public static boolean java8;
@ -334,19 +334,37 @@ public class JdbcMapperProcessor extends AbstractProcessor {
final Matcher bindParamMatcher = paramPattern.matcher(sql.value()); final Matcher bindParamMatcher = paramPattern.matcher(sql.value());
final StringBuffer sb = new StringBuffer(); final StringBuffer sb = new StringBuffer();
while (bindParamMatcher.find()) { while (bindParamMatcher.find()) {
final String paramName = bindParamMatcher.group(5); final String paramName = bindParamMatcher.group(7);
final VariableElement bindParam = paramMap.get(paramName); final VariableElement bindParam = paramMap.get(paramName);
if (bindParam == null) { if (bindParam == null) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@JdbcMapper.SQL sql has bind param '%s' not in method parameter list", paramName), methodElement); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@JdbcMapper.SQL sql has bind param '%s' not in method parameter list", paramName), methodElement);
continue; continue;
} }
unusedParams.remove(paramName); unusedParams.remove(paramName);
final String clobBlob = bindParamMatcher.group(5);
final String inColumnName = bindParamMatcher.group(2); final String inColumnName = bindParamMatcher.group(2);
if (inColumnName == null) { if (inColumnName == null) {
bindParams.add(bindParam);
bindParamMatcher.appendReplacement(sb, "?"); bindParamMatcher.appendReplacement(sb, "?");
if(clobBlob == null){
bindParams.add(bindParam);
} else { } else {
bindParams.add(new InListVariableElement(bindParam)); // regex ensures this can only be C for clob or B for blob
final boolean clobNotBlob = 'C' == Character.toUpperCase(clobBlob.charAt(0));
final String blobCharset = bindParamMatcher.group(6);
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 == null ? null :
blobCharset.substring(0, blobCharset.indexOf(':')).trim()
));
}
}
} else {
if(clobBlob != null)
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "cannot combine in/not in and clob/blob", bindParam);
bindParams.add(new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.IN_LIST));
final boolean not = bindParamMatcher.group(4) != null; final boolean not = bindParamMatcher.group(4) != null;
final String replacement; final String replacement;
switch (databaseType) { switch (databaseType) {
@ -632,7 +650,10 @@ public class JdbcMapperProcessor extends AbstractProcessor {
String method = null; String method = null;
// special behavior // special behavior
if (param instanceof InListVariableElement) { if (param instanceof SpecialVariableElement) {
final SpecialVariableElement specialParam = (SpecialVariableElement) param;
switch (specialParam.specialType) {
case IN_LIST: {
final boolean collection; final boolean collection;
final TypeMirror componentType; final TypeMirror componentType;
if (o.getKind() == TypeKind.ARRAY) { if (o.getKind() == TypeKind.ARRAY) {
@ -642,12 +663,12 @@ public class JdbcMapperProcessor extends AbstractProcessor {
collection = true; collection = true;
final DeclaredType dt = (DeclaredType) o; final DeclaredType dt = (DeclaredType) o;
if (dt.getTypeArguments().isEmpty()) { if (dt.getTypeArguments().isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax requires a Collection with a generic type parameter" + o.toString(), ((InListVariableElement) param).delegate); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax requires a Collection with a generic type parameter" + o.toString(), specialParam.delegate);
return; return;
} }
componentType = dt.getTypeArguments().get(0); componentType = dt.getTypeArguments().get(0);
} else { } else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), ((InListVariableElement) param).delegate); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), specialParam.delegate);
return; return;
} }
w.write("ps.setArray("); w.write("ps.setArray(");
@ -683,7 +704,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write("conn.createArrayOf(\""); w.write("conn.createArrayOf(\"");
break; break;
default: default:
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", ((InListVariableElement) param).delegate); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", specialParam.delegate);
} }
w.write(type); w.write(type);
w.write("\", "); w.write("\", ");
@ -693,8 +714,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write("));\n"); w.write("));\n");
return; return;
} }
final JdbcMapper.Blob blob = param.getAnnotation(JdbcMapper.Blob.class); case BLOB: {
if (blob != null) {
if (types.isAssignable(o, stringType)) { if (types.isAssignable(o, stringType)) {
w.write("try {\n\t\t\t\tps.setBlob("); w.write("try {\n\t\t\t\tps.setBlob(");
w.write(Integer.toString(index)); w.write(Integer.toString(index));
@ -703,27 +723,27 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write(" == null ? null : new java.io.ByteArrayInputStream("); w.write(" == null ? null : new java.io.ByteArrayInputStream(");
w.write(variableName); w.write(variableName);
w.write(".getBytes(\""); w.write(".getBytes(\"");
w.write(blob.charsetName()); w.write(specialParam.blobStringCharset == null ? "UTF-8" : specialParam.blobStringCharset);
w.write("\")));\n\t\t\t} catch (java.io.UnsupportedEncodingException e) {\n" + w.write("\")));\n\t\t\t} catch (java.io.UnsupportedEncodingException e) {\n" +
"\t\t\t\tthrow new SQLException(\"String to Blob UnsupportedEncodingException\", e);\n" + "\t\t\t\tthrow new SQLException(\"String to Blob UnsupportedEncodingException\", e);\n" +
"\t\t\t}\n"); "\t\t\t}\n");
return; return;
} else if (!(types.isAssignable(o, inputStreamType) || types.isAssignable(o, blobType) || types.isAssignable(o, fileType) || types.isAssignable(o, byteArrayType))) { } else if (!(types.isAssignable(o, inputStreamType) || types.isAssignable(o, blobType) || types.isAssignable(o, fileType) || types.isAssignable(o, byteArrayType))) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Blob only valid for String, byte[], Blob, InputStream, and File", param); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Blob only valid for String, byte[], Blob, InputStream, and File", specialParam.delegate);
return; return;
} }
} else { }
final JdbcMapper.Clob clob = param.getAnnotation(JdbcMapper.Clob.class); case CLOB: {
if (clob != null) {
if (types.isAssignable(o, stringType)) { if (types.isAssignable(o, stringType)) {
method = "Clob"; method = "Clob";
variableName = variableName + " == null ? null : new java.io.StringReader(" + variableName + ")"; variableName = variableName + " == null ? null : new java.io.StringReader(" + variableName + ")";
} else if (!(types.isAssignable(o, readerType) || types.isAssignable(o, clobType))) { } else if (!(types.isAssignable(o, readerType) || types.isAssignable(o, clobType))) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Clob only valid for String, Clob, Reader", param); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Clob only valid for String, Clob, Reader", specialParam.delegate);
return; return;
} }
} }
} }
}
// end special behavior // end special behavior
// we are going to put most common ones up top so it should execute faster normally // we are going to put most common ones up top so it should execute faster normally

View File

@ -96,7 +96,10 @@ public class SimpleSQLChecker implements SQLChecker {
public Object getFakeBindParam(final VariableElement param, final Connection conn, final ArrayInList arrayInList) throws SQLException { public Object getFakeBindParam(final VariableElement param, final Connection conn, final ArrayInList arrayInList) throws SQLException {
final TypeMirror o = param.asType(); final TypeMirror o = param.asType();
// special behavior // special behavior
if (param instanceof InListVariableElement) { if (param instanceof SpecialVariableElement) {
final SpecialVariableElement specialParam = (SpecialVariableElement) param;
switch (specialParam.specialType) {
case IN_LIST: {
final TypeMirror componentType; final TypeMirror componentType;
if (o.getKind() == TypeKind.ARRAY) { if (o.getKind() == TypeKind.ARRAY) {
componentType = ((ArrayType) o).getComponentType(); componentType = ((ArrayType) o).getComponentType();
@ -104,7 +107,7 @@ public class SimpleSQLChecker implements SQLChecker {
final DeclaredType dt = (DeclaredType) o; final DeclaredType dt = (DeclaredType) o;
componentType = dt.getTypeArguments().get(0); componentType = dt.getTypeArguments().get(0);
} else { } else {
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((InListVariableElement) param).delegate); getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((SpecialVariableElement) param).delegate);
return null; return null;
} }
if (types.isAssignable(componentType, numberType)) { if (types.isAssignable(componentType, numberType)) {
@ -112,16 +115,13 @@ public class SimpleSQLChecker implements SQLChecker {
} else if (types.isAssignable(componentType, stringType) || types.isAssignable(componentType, enumType)) { } else if (types.isAssignable(componentType, stringType) || types.isAssignable(componentType, enumType)) {
return arrayInList.toArray(conn, false, new String[]{defaultString}); return arrayInList.toArray(conn, false, new String[]{defaultString});
} else { } else {
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((InListVariableElement) param).delegate); getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((SpecialVariableElement) param).delegate);
return null; return null;
} }
} }
final JdbcMapper.Blob blob = param.getAnnotation(JdbcMapper.Blob.class); case BLOB:
if (blob != null) {
return new ByteArrayInputStream(new byte[1]); return new ByteArrayInputStream(new byte[1]);
} else { case CLOB:
final JdbcMapper.Clob clob = param.getAnnotation(JdbcMapper.Clob.class);
if (clob != null) {
return new StringReader(defaultString); return new StringReader(defaultString);
} }
} }

View File

@ -9,12 +9,26 @@ import java.util.Set;
/** /**
* Created by mopar on 6/1/17. * Created by mopar on 6/1/17.
*/ */
class InListVariableElement implements VariableElement { class SpecialVariableElement implements VariableElement {
enum SpecialType {
IN_LIST,
CLOB,
BLOB,
}
final VariableElement delegate; final VariableElement delegate;
final SpecialType specialType;
final String blobStringCharset;
InListVariableElement(final VariableElement delegate) { SpecialVariableElement(final VariableElement delegate, final SpecialType specialType) {
this(delegate, specialType, null);
}
SpecialVariableElement(final VariableElement delegate, final SpecialType specialType, final String blobStringCharset) {
this.delegate = delegate; this.delegate = delegate;
this.specialType = specialType;
this.blobStringCharset = blobStringCharset;
} }
@Override @Override
@ -85,4 +99,11 @@ class InListVariableElement implements VariableElement {
} }
//IFJAVA8_END //IFJAVA8_END
@Override
public String toString() {
return "InListVariableElement{" +
"delegate=" + delegate +
'}';
}
} }

View File

@ -0,0 +1,39 @@
package com.moparisthebest.jdbc.codegen;
import org.junit.Test;
import java.util.regex.Matcher;
import static com.moparisthebest.jdbc.codegen.JdbcMapperProcessor.paramPattern;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class ParamPatternTest {
@Test
public void testPattern() {
testMatch("{personNo}", null, null, null, null, null, null, "personNo");
testMatch("{last_name IN lastNames}", "last_name IN ", "last_name", "IN ", null, null, null, "lastNames");
testMatch("{last_name not in lastNames}", "last_name not in ", "last_name", "not in ", "not ", null, null, "lastNames");
testMatch("{clob:comment}", null, null, null, null, "clob:", null, "comment");
testMatch("{clob: comment}", null, null, null, null, "clob: ", null, "comment");
testMatch("{blob: comment}", null, null, null, null, "blob: ", null, "comment");
testMatch("{Blob: comment}", null, null, null, null, "Blob: ", null, "comment");
testMatch("{blob:utf-16:comment}", null, null, null, null, "blob:utf-16:", "utf-16:", "comment");
String blobCharset = "utf-16:";
assertEquals("utf-16", blobCharset.substring(0, blobCharset.indexOf(':')).trim());
blobCharset = "utf-16 : ";
assertEquals("utf-16", blobCharset.substring(0, blobCharset.indexOf(':')).trim());
}
private static void testMatch(final String sql, final String... expected) {
final Matcher bindParamMatcher = paramPattern.matcher(sql);
while (bindParamMatcher.find()) {
final String[] matches = new String[bindParamMatcher.groupCount()];
for (int x = 0; x < matches.length; )
matches[x] = bindParamMatcher.group(++x);
//System.out.println(bindParamMatcher.group() + ": " + java.util.Arrays.toString(matches));
assertArrayEquals(bindParamMatcher.group(), expected, matches);
}
}
}

View File

@ -42,8 +42,8 @@ public interface PersonDAO extends JdbcMapper {
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}") @JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException; void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException;
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}") @JdbcMapper.SQL("UPDATE person SET first_name = {blob:firstName} WHERE person_no = {personNo}")
void setFirstNameBlob(@JdbcMapper.Blob String firstName, long personNo) throws SQLException; void setFirstNameBlob(String firstName, long personNo) throws SQLException;
@JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}") @JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}")
long getPersonNo(String lastName) throws SQLException; long getPersonNo(String lastName) throws SQLException;

View File

@ -42,8 +42,8 @@ public interface PrestoPersonDAO extends PersonDAO {
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}") @JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException; void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException;
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}") @JdbcMapper.SQL("UPDATE person SET first_name = {blob:firstName} WHERE person_no = {personNo}")
void setFirstNameBlob(@JdbcMapper.Blob String firstName, long personNo) throws SQLException; void setFirstNameBlob(String firstName, long personNo) throws SQLException;
@JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}") @JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}")
long getPersonNo(String lastName) throws SQLException; long getPersonNo(String lastName) throws SQLException;