mirror of
https://github.com/moparisthebest/JdbcMapper
synced 2024-11-24 10:02:14 -05:00
Remove Blob and Clob annotations in favor of SQL bind syntax
This commit is contained in:
parent
ba7fb733b2
commit
edd6fc6e65
@ -103,35 +103,6 @@ public interface JdbcMapper extends Closeable {
|
||||
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
|
||||
*/
|
||||
|
@ -34,7 +34,7 @@ import static com.moparisthebest.jdbc.codegen.JdbcMapperFactory.SUFFIX;
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_5)
|
||||
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 boolean java8;
|
||||
@ -334,19 +334,37 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
final Matcher bindParamMatcher = paramPattern.matcher(sql.value());
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
while (bindParamMatcher.find()) {
|
||||
final String paramName = bindParamMatcher.group(5);
|
||||
final String paramName = bindParamMatcher.group(7);
|
||||
final VariableElement bindParam = paramMap.get(paramName);
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
unusedParams.remove(paramName);
|
||||
final String clobBlob = bindParamMatcher.group(5);
|
||||
final String inColumnName = bindParamMatcher.group(2);
|
||||
if (inColumnName == null) {
|
||||
bindParams.add(bindParam);
|
||||
bindParamMatcher.appendReplacement(sb, "?");
|
||||
if(clobBlob == null){
|
||||
bindParams.add(bindParam);
|
||||
} else {
|
||||
// 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 {
|
||||
bindParams.add(new InListVariableElement(bindParam));
|
||||
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 String replacement;
|
||||
switch (databaseType) {
|
||||
@ -632,34 +650,37 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
String method = null;
|
||||
|
||||
// special behavior
|
||||
if (param instanceof InListVariableElement) {
|
||||
final boolean collection;
|
||||
final TypeMirror componentType;
|
||||
if (o.getKind() == TypeKind.ARRAY) {
|
||||
collection = false;
|
||||
componentType = ((ArrayType) o).getComponentType();
|
||||
} else if (o.getKind() == TypeKind.DECLARED && types.isAssignable(o, collectionType)) {
|
||||
collection = true;
|
||||
final DeclaredType dt = (DeclaredType) o;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
componentType = dt.getTypeArguments().get(0);
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), ((InListVariableElement) param).delegate);
|
||||
return;
|
||||
}
|
||||
w.write("ps.setArray(");
|
||||
w.write(Integer.toString(index));
|
||||
w.write(", ");
|
||||
final String type = types.isAssignable(componentType, numberType) ? arrayNumberTypeName : arrayStringTypeName;
|
||||
switch (databaseType) {
|
||||
case ORACLE:
|
||||
w.write("conn.unwrap(oracle.jdbc.OracleConnection.class).createOracleArray(\"");
|
||||
if (param instanceof SpecialVariableElement) {
|
||||
final SpecialVariableElement specialParam = (SpecialVariableElement) param;
|
||||
switch (specialParam.specialType) {
|
||||
case IN_LIST: {
|
||||
final boolean collection;
|
||||
final TypeMirror componentType;
|
||||
if (o.getKind() == TypeKind.ARRAY) {
|
||||
collection = false;
|
||||
componentType = ((ArrayType) o).getComponentType();
|
||||
} else if (o.getKind() == TypeKind.DECLARED && types.isAssignable(o, collectionType)) {
|
||||
collection = true;
|
||||
final DeclaredType dt = (DeclaredType) o;
|
||||
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(), specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
componentType = dt.getTypeArguments().get(0);
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
w.write("ps.setArray(");
|
||||
w.write(Integer.toString(index));
|
||||
w.write(", ");
|
||||
final String type = types.isAssignable(componentType, numberType) ? arrayNumberTypeName : arrayStringTypeName;
|
||||
switch (databaseType) {
|
||||
case ORACLE:
|
||||
w.write("conn.unwrap(oracle.jdbc.OracleConnection.class).createOracleArray(\"");
|
||||
|
||||
// todo: if oracle driver is not on compile-time classpath, would need to do:
|
||||
// we could also create a fake-oracle module that just had a oracle.jdbc.OracleConnection class implementing createOracleArray()...
|
||||
// todo: if oracle driver is not on compile-time classpath, would need to do:
|
||||
// we could also create a fake-oracle module that just had a oracle.jdbc.OracleConnection class implementing createOracleArray()...
|
||||
/*
|
||||
private static final Class<?> oracleConnection;
|
||||
private static final Method createArray;
|
||||
@ -677,51 +698,50 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
createArray = ca;
|
||||
}
|
||||
*/
|
||||
//w.write("(Array) createArray.invoke(conn.unwrap(oracleConnection), \"");
|
||||
break;
|
||||
case STANDARD:
|
||||
w.write("conn.createArrayOf(\"");
|
||||
break;
|
||||
default:
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", ((InListVariableElement) param).delegate);
|
||||
}
|
||||
w.write(type);
|
||||
w.write("\", ");
|
||||
w.write(variableName);
|
||||
if (collection)
|
||||
w.write(".toArray()");
|
||||
w.write("));\n");
|
||||
return;
|
||||
}
|
||||
final JdbcMapper.Blob blob = param.getAnnotation(JdbcMapper.Blob.class);
|
||||
if (blob != null) {
|
||||
if (types.isAssignable(o, stringType)) {
|
||||
w.write("try {\n\t\t\t\tps.setBlob(");
|
||||
w.write(Integer.toString(index));
|
||||
w.write(", ");
|
||||
w.write(variableName);
|
||||
w.write(" == null ? null : new java.io.ByteArrayInputStream(");
|
||||
w.write(variableName);
|
||||
w.write(".getBytes(\"");
|
||||
w.write(blob.charsetName());
|
||||
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}\n");
|
||||
return;
|
||||
} 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);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
final JdbcMapper.Clob clob = param.getAnnotation(JdbcMapper.Clob.class);
|
||||
if (clob != null) {
|
||||
if (types.isAssignable(o, stringType)) {
|
||||
method = "Clob";
|
||||
variableName = variableName + " == null ? null : new java.io.StringReader(" + variableName + ")";
|
||||
} 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);
|
||||
//w.write("(Array) createArray.invoke(conn.unwrap(oracleConnection), \"");
|
||||
break;
|
||||
case STANDARD:
|
||||
w.write("conn.createArrayOf(\"");
|
||||
break;
|
||||
default:
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", specialParam.delegate);
|
||||
}
|
||||
w.write(type);
|
||||
w.write("\", ");
|
||||
w.write(variableName);
|
||||
if (collection)
|
||||
w.write(".toArray()");
|
||||
w.write("));\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(", ");
|
||||
w.write(variableName);
|
||||
w.write(" == null ? null : new java.io.ByteArrayInputStream(");
|
||||
w.write(variableName);
|
||||
w.write(".getBytes(\"");
|
||||
w.write(specialParam.blobStringCharset == null ? "UTF-8" : specialParam.blobStringCharset);
|
||||
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}\n");
|
||||
return;
|
||||
} 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", specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
case CLOB: {
|
||||
if (types.isAssignable(o, stringType)) {
|
||||
method = "Clob";
|
||||
variableName = variableName + " == null ? null : new java.io.StringReader(" + variableName + ")";
|
||||
} else if (!(types.isAssignable(o, readerType) || types.isAssignable(o, clobType))) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Clob only valid for String, Clob, Reader", specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// end special behavior
|
||||
|
@ -96,33 +96,33 @@ public class SimpleSQLChecker implements SQLChecker {
|
||||
public Object getFakeBindParam(final VariableElement param, final Connection conn, final ArrayInList arrayInList) throws SQLException {
|
||||
final TypeMirror o = param.asType();
|
||||
// special behavior
|
||||
if (param instanceof InListVariableElement) {
|
||||
final TypeMirror componentType;
|
||||
if (o.getKind() == TypeKind.ARRAY) {
|
||||
componentType = ((ArrayType) o).getComponentType();
|
||||
} else if (o.getKind() == TypeKind.DECLARED && types.isAssignable(o, collectionType)) {
|
||||
final DeclaredType dt = (DeclaredType) o;
|
||||
componentType = dt.getTypeArguments().get(0);
|
||||
} else {
|
||||
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((InListVariableElement) param).delegate);
|
||||
return null;
|
||||
}
|
||||
if (types.isAssignable(componentType, numberType)) {
|
||||
return arrayInList.toArray(conn, true, new Long[]{0L}); // todo: not quite right, oh well for now
|
||||
} else if (types.isAssignable(componentType, stringType) || types.isAssignable(componentType, enumType)) {
|
||||
return arrayInList.toArray(conn, false, new String[]{defaultString});
|
||||
} else {
|
||||
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((InListVariableElement) param).delegate);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
final JdbcMapper.Blob blob = param.getAnnotation(JdbcMapper.Blob.class);
|
||||
if (blob != null) {
|
||||
return new ByteArrayInputStream(new byte[1]);
|
||||
} else {
|
||||
final JdbcMapper.Clob clob = param.getAnnotation(JdbcMapper.Clob.class);
|
||||
if (clob != null) {
|
||||
return new StringReader(defaultString);
|
||||
if (param instanceof SpecialVariableElement) {
|
||||
final SpecialVariableElement specialParam = (SpecialVariableElement) param;
|
||||
switch (specialParam.specialType) {
|
||||
case IN_LIST: {
|
||||
final TypeMirror componentType;
|
||||
if (o.getKind() == TypeKind.ARRAY) {
|
||||
componentType = ((ArrayType) o).getComponentType();
|
||||
} else if (o.getKind() == TypeKind.DECLARED && types.isAssignable(o, collectionType)) {
|
||||
final DeclaredType dt = (DeclaredType) o;
|
||||
componentType = dt.getTypeArguments().get(0);
|
||||
} else {
|
||||
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((SpecialVariableElement) param).delegate);
|
||||
return null;
|
||||
}
|
||||
if (types.isAssignable(componentType, numberType)) {
|
||||
return arrayInList.toArray(conn, true, new Long[]{0L}); // todo: not quite right, oh well for now
|
||||
} else if (types.isAssignable(componentType, stringType) || types.isAssignable(componentType, enumType)) {
|
||||
return arrayInList.toArray(conn, false, new String[]{defaultString});
|
||||
} else {
|
||||
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((SpecialVariableElement) param).delegate);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case BLOB:
|
||||
return new ByteArrayInputStream(new byte[1]);
|
||||
case CLOB:
|
||||
return new StringReader(defaultString);
|
||||
}
|
||||
}
|
||||
// end special behavior
|
||||
|
@ -9,12 +9,26 @@ import java.util.Set;
|
||||
/**
|
||||
* 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 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.specialType = specialType;
|
||||
this.blobStringCharset = blobStringCharset;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -85,4 +99,11 @@ class InListVariableElement implements VariableElement {
|
||||
}
|
||||
|
||||
//IFJAVA8_END
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InListVariableElement{" +
|
||||
"delegate=" + delegate +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -42,8 +42,8 @@ public interface PersonDAO extends JdbcMapper {
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
|
||||
void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
|
||||
void setFirstNameBlob(@JdbcMapper.Blob String firstName, long personNo) throws SQLException;
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {blob:firstName} WHERE person_no = {personNo}")
|
||||
void setFirstNameBlob(String firstName, long personNo) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}")
|
||||
long getPersonNo(String lastName) throws SQLException;
|
||||
|
@ -42,8 +42,8 @@ public interface PrestoPersonDAO extends PersonDAO {
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
|
||||
void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
|
||||
void setFirstNameBlob(@JdbcMapper.Blob String firstName, long personNo) throws SQLException;
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {blob:firstName} WHERE person_no = {personNo}")
|
||||
void setFirstNameBlob(String firstName, long personNo) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}")
|
||||
long getPersonNo(String lastName) throws SQLException;
|
||||
|
Loading…
Reference in New Issue
Block a user