ExpandableSQL.java
package org.codefilarete.stalactite.sql.statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.codefilarete.tool.Strings;
import org.codefilarete.stalactite.sql.statement.SQLParameterParser.Parameter;
import org.codefilarete.stalactite.sql.statement.SQLParameterParser.ParsedSQL;
/**
* Class that helps to adapt an SQL String with named parameters to a prepared statement, according to the values of those parameters :
* It manages SQL expansion for parameters that have Collection value ('?' are added as many as necessary)
* Kind of wrapper over {@link ParsedSQL}, delegates parsing to {@link SQLParameterParser}.
*
* @author Guillaume Mary
*/
public class ExpandableSQL {
public static Map<String, Integer> sizes(Map<String, Object> values) {
HashMap<String, Integer> toReturn = new HashMap<>();
for (Entry<String, Object> entry : values.entrySet()) {
toReturn.put(entry.getKey(), entry.getValue() instanceof Collection ? ((Collection) entry.getValue()).size() : 1);
}
return toReturn;
}
private final Map<String, ExpandableParameter> expandableParameters;
private final ParsedSQL parsedSQL;
private String preparedSQL;
public ExpandableSQL(ParsedSQL parsedSQL, Map<String, Integer> parameterValuesSize) {
this.parsedSQL = parsedSQL;
this.expandableParameters = new HashMap<>(this.parsedSQL.getSqlSnippets().size() / 2);
convertParsedParametersToExpandableParameters(parameterValuesSize);
}
private void convertParsedParametersToExpandableParameters(Map<String, Integer> parameterValuesSize) {
StringBuilder preparedSQLBuilder = new StringBuilder();
// index for prepared statement mark (?), used to compute mark indexes of parameters
int markIndex = 1;
for (Object sqlSnippet : parsedSQL.getSqlSnippets()) {
if (sqlSnippet instanceof Parameter) {
String parameterName = ((Parameter) sqlSnippet).getName();
Integer valueSize = parameterValuesSize.get(parameterName);
if (valueSize == null) {
throw new IllegalArgumentException("Size is not given for parameter " + parameterName + " hence expansion is not possible");
} else {
buildParameter(parameterName, valueSize, markIndex, preparedSQLBuilder);
markIndex += valueSize;
}
} else {
// sql snippet
preparedSQLBuilder.append(sqlSnippet);
}
}
this.preparedSQL = preparedSQLBuilder.toString();
}
private void buildParameter(String parameterName, int valueSize, int firstIndex, StringBuilder preparedSQLBuilder) {
ExpandableParameter expandableParameter = this.expandableParameters.computeIfAbsent(parameterName, name -> new ExpandableParameter(name, valueSize));
expandableParameter.buildMarkIndexes(firstIndex);
expandableParameter.catParameterMarks(preparedSQLBuilder);
}
public Map<String, ExpandableParameter> getExpandableParameters() {
return expandableParameters;
}
public String getPreparedSQL() {
return preparedSQL;
}
/**
* Class that represents a named parameter in a SQL statement.
* It allows extension of single parameter mark (coded as a question mark '?') to multiple ones in case of
* Collection value. This is done on the {@link #catParameterMarks(StringBuilder)} method.
*/
public static class ExpandableParameter {
public static final String SQL_PARAMETER_MARK = "?";
public static final String SQL_PARAMETER_SEPARATOR = ", ";
public static final String SQL_PARAMETER_MARK_1 = SQL_PARAMETER_MARK + SQL_PARAMETER_SEPARATOR;
public static final String SQL_PARAMETER_MARK_10 =
SQL_PARAMETER_MARK_1 + SQL_PARAMETER_MARK_1 +
SQL_PARAMETER_MARK_1 + SQL_PARAMETER_MARK_1 +
SQL_PARAMETER_MARK_1 + SQL_PARAMETER_MARK_1 +
SQL_PARAMETER_MARK_1 + SQL_PARAMETER_MARK_1 +
SQL_PARAMETER_MARK_1 + SQL_PARAMETER_MARK_1;
public static final String SQL_PARAMETER_MARK_100 =
SQL_PARAMETER_MARK_10 + SQL_PARAMETER_MARK_10 +
SQL_PARAMETER_MARK_10 + SQL_PARAMETER_MARK_10 +
SQL_PARAMETER_MARK_10 + SQL_PARAMETER_MARK_10 +
SQL_PARAMETER_MARK_10 + SQL_PARAMETER_MARK_10 +
SQL_PARAMETER_MARK_10 + SQL_PARAMETER_MARK_10;
/** The parameter name */
private final String parameterName;
/** PreparedStatement parameter mark count for this parameter, 1 for single value, N for Collection value */
private final int markCount;
/** Index of the parameter in the PreparedStatement, the first one for Collection value */
private int[] indexes;
private ExpandableParameter(String parameterName, int markCount) {
this.parameterName = parameterName;
this.markCount = markCount;
}
public String getParameterName() {
return parameterName;
}
/**
* Gives all indexes of this instance.
*
* @return a one-sized array for single value parameter, a n-sized array for n-sized collection value parameter
*/
public int[] getMarkIndexes() {
return indexes;
}
private void buildMarkIndexes(int startIndex) {
int offset;
if (indexes == null) {
offset = 0;
indexes = new int[markCount];
} else {
// parameter already has indexes: we keep them (what a nice array copy !)
offset = indexes.length;
int[] newIndexes = new int[offset + markCount];
System.arraycopy(indexes, 0, newIndexes, 0, offset);
indexes = newIndexes;
}
// build mark indexes
for (int i=0; i < markCount; i++) {
indexes[i+offset] = startIndex++;
}
}
private StringBuilder catParameterMarks(StringBuilder stringBuilder) {
if (markCount > 1) {
return expandParameters(stringBuilder);
} else {
return stringBuilder.append(SQL_PARAMETER_MARK);
}
}
/**
* Convert the single valued parameter to a multiple one: add as many '?' as necessary
* @return the changed sql
* @param stringBuilder
*/
protected StringBuilder expandParameters(StringBuilder stringBuilder) {
StringBuilder sqlParameters = Strings.repeat(stringBuilder, markCount, SQL_PARAMETER_MARK_1, SQL_PARAMETER_MARK_100, SQL_PARAMETER_MARK_10);
sqlParameters.setLength(sqlParameters.length() - 2);
return sqlParameters;
}
}
}