mirror of
https://github.com/moparisthebest/JdbcMapper
synced 2025-02-16 15:10:19 -05:00
Implement in lists for Oracle and standard SQL in JdbcMapper
This commit is contained in:
parent
2162e35456
commit
8d9a01766f
@ -35,6 +35,14 @@ public interface JdbcMapper extends Closeable {
|
||||
* @return
|
||||
*/
|
||||
Class<? extends SQLParser> sqlParser() default SQLParser.class;
|
||||
|
||||
/**
|
||||
* This defaults to SimpleSQLParser, PrestoSQLParser is another option for Java 8, or implement your own
|
||||
* @return
|
||||
*/
|
||||
DatabaseType databaseType() default DatabaseType.DEFAULT;
|
||||
String arrayNumberTypeName() default "";
|
||||
String arrayStringTypeName() default "";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@ -109,4 +117,24 @@ public interface JdbcMapper extends Closeable {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DatabaseType {
|
||||
DEFAULT(null, null),
|
||||
STANDARD("number", "text"),
|
||||
ORACLE("ARRAY_NUM_TYPE", "ARRAY_STR_TYPE");
|
||||
|
||||
public final String arrayNumberTypeName, arrayStringTypeName;
|
||||
|
||||
private DatabaseType(final String arrayNumberTypeName, final String arrayStringTypeName) {
|
||||
this.arrayNumberTypeName = arrayNumberTypeName;
|
||||
this.arrayStringTypeName = arrayStringTypeName;
|
||||
}
|
||||
|
||||
public DatabaseType nonDefault(final DatabaseType def) {
|
||||
if(this != DEFAULT)
|
||||
return this;
|
||||
//return def == DEFAULT ? STANDARD : def;
|
||||
return def; // we guarantee this to be not DEFAULT in JdbcMapperProcessor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by mopar on 6/1/17.
|
||||
*/
|
||||
class InListVariableElement implements VariableElement {
|
||||
|
||||
final VariableElement delegate;
|
||||
|
||||
InListVariableElement(final VariableElement delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getConstantValue() {
|
||||
return delegate.getConstantValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeMirror asType() {
|
||||
return delegate.asType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementKind getKind() {
|
||||
return delegate.getKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends AnnotationMirror> getAnnotationMirrors() {
|
||||
return delegate.getAnnotationMirrors();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> A getAnnotation(final Class<A> annotationType) {
|
||||
return delegate.getAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Modifier> getModifiers() {
|
||||
return delegate.getModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Name getSimpleName() {
|
||||
return delegate.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element getEnclosingElement() {
|
||||
return delegate.getEnclosingElement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Element> getEnclosedElements() {
|
||||
return delegate.getEnclosedElements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return delegate.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, P> R accept(final ElementVisitor<R, P> v, final P p) {
|
||||
return delegate.accept(v, p);
|
||||
}
|
||||
}
|
@ -3,9 +3,7 @@ package com.moparisthebest.jdbc.codegen;
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.MirroredTypeException;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.Diagnostic;
|
||||
@ -23,16 +21,19 @@ import static com.moparisthebest.jdbc.TryClose.tryClose;
|
||||
* Created by mopar on 5/24/17.
|
||||
*/
|
||||
@SupportedAnnotationTypes("com.moparisthebest.jdbc.codegen.JdbcMapper.Mapper")
|
||||
@SupportedOptions({"jdbcMapper.databaseType", "jdbcMapper.arrayNumberTypeName", "jdbcMapper.arrayStringTypeName"})
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_5)
|
||||
public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
|
||||
private static final Pattern paramPattern = Pattern.compile("\\{([^}]+)\\}");
|
||||
private static final Pattern paramPattern = Pattern.compile("\\{(([^\\s]+)\\s+(([Nn][Oo][Tt]\\s+)?[Ii][Nn]\\s+))?([^}]+)\\}");
|
||||
private static final CompileTimeResultSetMapper rsm = new CompileTimeResultSetMapper();
|
||||
|
||||
private Types types;
|
||||
private TypeMirror sqlExceptionType, stringType, numberType, utilDateType, readerType, clobType,
|
||||
byteArrayType, inputStreamType, fileType, blobType, sqlArrayType
|
||||
byteArrayType, inputStreamType, fileType, blobType, sqlArrayType, collectionType
|
||||
;
|
||||
private JdbcMapper.DatabaseType defaultDatabaseType;
|
||||
private String defaultArrayNumberTypeName, defaultArrayStringTypeName;
|
||||
|
||||
public JdbcMapperProcessor() {
|
||||
//out.println("JdbcMapperProcessor running!");
|
||||
@ -58,6 +59,15 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
//byteArrayType = elements.getTypeElement(byte.class.getCanonicalName()).asType();
|
||||
byteArrayType = types.getArrayType(types.getPrimitiveType(TypeKind.BYTE));
|
||||
sqlArrayType = elements.getTypeElement(java.sql.Array.class.getCanonicalName()).asType();
|
||||
collectionType = types.getDeclaredType(elements.getTypeElement(Collection.class.getCanonicalName()), types.getWildcardType(null, null));
|
||||
final String databaseType = processingEnv.getOptions().get("JdbcMapper.databaseType");
|
||||
defaultDatabaseType = databaseType == null ? JdbcMapper.DatabaseType.STANDARD : JdbcMapper.DatabaseType.valueOf(databaseType);
|
||||
defaultArrayNumberTypeName = processingEnv.getOptions().get("JdbcMapper.arrayNumberTypeName");
|
||||
if(defaultArrayNumberTypeName == null || defaultArrayNumberTypeName.isEmpty())
|
||||
defaultArrayNumberTypeName = defaultDatabaseType.arrayNumberTypeName;
|
||||
defaultArrayStringTypeName = processingEnv.getOptions().get("JdbcMapper.arrayStringTypeName");
|
||||
if(defaultArrayStringTypeName == null || defaultArrayStringTypeName.isEmpty())
|
||||
defaultArrayStringTypeName = defaultDatabaseType.arrayStringTypeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -78,6 +88,9 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
}
|
||||
final TypeElement genClass = (TypeElement) element;
|
||||
final JdbcMapper.Mapper mapper = genClass.getAnnotation(JdbcMapper.Mapper.class);
|
||||
final JdbcMapper.DatabaseType databaseType = mapper.databaseType().nonDefault(defaultDatabaseType);
|
||||
final String arrayNumberTypeName = !mapper.arrayNumberTypeName().isEmpty() ? mapper.arrayNumberTypeName() : defaultArrayNumberTypeName;
|
||||
final String arrayStringTypeName = !mapper.arrayStringTypeName().isEmpty() ? mapper.arrayStringTypeName() : defaultArrayStringTypeName;
|
||||
final String sqlParserMirror = getSqlParser(mapper).toString();
|
||||
//final SQLParser parser = new SimpleSQLParser();//(SQLParser)Class.forName(mapper.sqlParser().getCanonicalName()).newInstance();
|
||||
//final SQLParser parser = mapper.sqlParser().equals(SQLParser.class) ? new SimpleSQLParser() : mapper.sqlParser().newInstance();
|
||||
@ -223,14 +236,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(1);
|
||||
final String paramName = bindParamMatcher.group(5);
|
||||
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;
|
||||
}
|
||||
bindParams.add(bindParam);
|
||||
bindParamMatcher.appendReplacement(sb, "?");
|
||||
final String inColumnName = bindParamMatcher.group(2);
|
||||
if(inColumnName == null) {
|
||||
bindParams.add(bindParam);
|
||||
bindParamMatcher.appendReplacement(sb, "?");
|
||||
} else {
|
||||
bindParams.add(new InListVariableElement(bindParam));
|
||||
final boolean not = bindParamMatcher.group(4) != null;
|
||||
final String replacement;
|
||||
switch (databaseType) {
|
||||
case ORACLE:
|
||||
replacement = not ?
|
||||
"(" + inColumnName + " NOT IN(select column_value from table(?)))" :
|
||||
"(" + inColumnName + " IN(select column_value from table(?)))";
|
||||
break;
|
||||
case STANDARD:
|
||||
replacement = not ?
|
||||
"(" + inColumnName + " != ANY(?))" :
|
||||
"(" + inColumnName + " = ANY(?))";
|
||||
break;
|
||||
default:
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", bindParam);
|
||||
return false;
|
||||
}
|
||||
bindParamMatcher.appendReplacement(sb, replacement);
|
||||
}
|
||||
}
|
||||
bindParamMatcher.appendTail(sb);
|
||||
sqlStatement = sb.toString();
|
||||
@ -258,7 +294,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
// now bind parameters
|
||||
int count = 0;
|
||||
for (final VariableElement param : bindParams)
|
||||
setObject(w, ++count, param);
|
||||
setObject(w, ++count, databaseType, arrayNumberTypeName, arrayStringTypeName, param);
|
||||
|
||||
if (!parsedSQl.isSelect()) {
|
||||
if (returnType.equals("void")) {
|
||||
@ -347,13 +383,74 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setObject(final Writer w, final int index, final VariableElement param) throws SQLException, IOException {
|
||||
private void setObject(final Writer w, final int index, final JdbcMapper.DatabaseType databaseType, final String arrayNumberTypeName, final String arrayStringTypeName, final VariableElement param) throws SQLException, IOException {
|
||||
String variableName = param.getSimpleName().toString();
|
||||
final TypeMirror o = param.asType();
|
||||
w.write("\t\t\t");
|
||||
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(\"");
|
||||
|
||||
// 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;
|
||||
|
||||
static {
|
||||
Class<?> oc;
|
||||
Method ca;
|
||||
try {
|
||||
oc = Class.forName("oracle.jdbc.OracleConnection");
|
||||
ca = oc.getDeclaredMethod("createOracleArray", String.class, Object.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
oracleConnection = oc;
|
||||
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)) {
|
||||
@ -457,6 +554,37 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
return float.class;
|
||||
case DOUBLE:
|
||||
return double.class;
|
||||
case ARRAY:
|
||||
// yuck
|
||||
final TypeMirror arrayComponentType = ((ArrayType)tm).getComponentType();
|
||||
switch (arrayComponentType.getKind()) {
|
||||
case BOOLEAN:
|
||||
return boolean[].class;
|
||||
case BYTE:
|
||||
return byte[].class;
|
||||
case SHORT:
|
||||
return short[].class;
|
||||
case INT:
|
||||
return int[].class;
|
||||
case LONG:
|
||||
return long[].class;
|
||||
case CHAR:
|
||||
return char[].class;
|
||||
case FLOAT:
|
||||
return float[].class;
|
||||
case DOUBLE:
|
||||
return double[].class;
|
||||
case ARRAY:
|
||||
throw new RuntimeException("multi-dimensional arrays are not supported");
|
||||
default:
|
||||
return Class.forName("[L" + arrayComponentType.toString() + ";");
|
||||
}
|
||||
case DECLARED:
|
||||
if(!((DeclaredType)tm).getTypeArguments().isEmpty()) {
|
||||
final String classWithGenerics = tm.toString();
|
||||
return Class.forName(classWithGenerics.substring(0, classWithGenerics.indexOf('<')));
|
||||
}
|
||||
// fallthrough otherwise...
|
||||
default:
|
||||
return Class.forName(tm.toString());
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import com.moparisthebest.jdbc.dto.FieldPerson;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -14,6 +13,7 @@ import java.util.Map;
|
||||
*/
|
||||
@JdbcMapper.Mapper(
|
||||
// jndiName = "bob",
|
||||
// databaseType = JdbcMapper.DatabaseType.ORACLE
|
||||
// cachePreparedStatements = false
|
||||
// , sqlParser = SimpleSQLParser.class
|
||||
)
|
||||
@ -40,6 +40,12 @@ public interface PersonDAO {
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE last_name = {lastName}")
|
||||
List<FieldPerson> getPeople(String lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE {last_name not in lastName}")
|
||||
List<FieldPerson> getPeople(String[] lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE {last_name in lastName}")
|
||||
List<FieldPerson> getPeople(List<String> lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE last_name = {lastName}")
|
||||
FieldPerson[] getPeopleArray(String lastName) throws SQLException;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user