Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #75 #107

Merged
merged 10 commits into from
Feb 6, 2015
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public static void process(RDBMSContext rdbmsContext) {

if(rdbmsContext.getQueryExpression() != null) {
// Dynamically create the first SQL statements to generate the input for the next defined expressions
List<SelectStmt> inputStmt = Translator.ORACLE.translate(rdbmsContext);
List<SelectStmt> inputStmt = Translator.getImpl(rdbmsContext.getRdbms().getDialect()).translate(rdbmsContext);

rdbmsContext.setInitialInput(true);
new ExecuteSQLCommand(rdbmsContext, inputStmt).execute();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ public String toString() {
ObjectNode root = jsonNodeFactory.objectNode();
ObjectNode objectNode = jsonNodeFactory.objectNode();

JsonNode jsonNode = map == null? jsonNodeFactory.nullNode() : jsonNodeFactory.objectNode();
if(map != null) {
for(Map.Entry<String, List<Var>> a : map.entrySet()){
ArrayNode jsonNodes = jsonNodeFactory.arrayNode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,175 +18,208 @@
*/
package com.redhat.lightblue.metadata.rdbms.converter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.sql.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;

/**
* A simple PreparedStatement wrapper to make SQL statements easier. Source
* inspired from:
* <ul>
* <li>http://www.javaworld.com/article/2077706/core-java/named-parameters-for-preparedstatement.html<?li>
* <li>https://github.com/bnewport/Samples/blob/master/wxsutils/src/main/java/com/devwebsphere/jdbc/loader/NamedParameterStatement.java</li>
* <li>https://github.com/colloquium/spacewalk/blob/d4bee4ec00fc89e00dd5c74a684298ebf0e2f686/java/code/src/com/redhat/rhn/common/db/NamedPreparedStatement.java
* </ul>
*/
public class NamedParameterStatement {
/**
* The statement this object is wrapping.
*/
private final PreparedStatement statement;
private static final Logger LOGGER = LoggerFactory.getLogger(NamedParameterStatement.class);
public static final char EMPTY = ' ';

/**
* Maps parameter names to arrays of integers which are the parameter
* indices.
*/
private Map<String, int[]> indexMap;
private final PreparedStatement statement;
private Map<String, int[]> variablesMap;

public NamedParameterStatement(Connection connection, String query) throws SQLException {
String parsedQuery = parse(query);
String parsedQuery = prepare(query);
LOGGER.debug("NamedParameterStatement constructor-> query:{} parsedQuery:{}", query, parsedQuery);
statement = connection.prepareStatement(parsedQuery);
}

private String parse(String query) {
// NOTE regular expressions don't work well for ignoring parameter-like strings inside quotes.
int length = query.length();
StringBuilder parsedQuery = new StringBuilder(length);
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
int index = 1;
// Setting a good initialCapacity for a better performance
HashMap<String, List<Integer>> indexes = new HashMap<>(15);

for (int i = 0; i < length; i++) {
private int findColon(int start, StringBuffer query) {
char quotationStart = EMPTY;
boolean inSingleQuotes = false;
boolean inDoubleQuotes = false;
for (int i = start; i < query.length(); i++) {
char c = query.charAt(i);
if (inSingleQuote) {
if (c == '\'') {
inSingleQuote = false;
}
} else if (inDoubleQuote) {
if (c == '"') {
inDoubleQuote = false;
}
} else {
if (c == '\'') {
inSingleQuote = true;
} else if (c == '"') {
inDoubleQuote = true;
} else if (c == ':' && i + 1 < length && Character.isJavaIdentifierStart(query.charAt(i + 1))) {
//isJavaIdentifierStart-> make sure that the parameter's chars are all qualified for java (which would be more readable)
int j = i + 2;
while (j < length && Character.isJavaIdentifierPart(query.charAt(j))) {
j++;
}
String name = query.substring(i + 1, j);
c = '?'; // replace the parameter with a question mark
i += name.length(); // skip past the end if the parameter

List<Integer> indexList = indexes.get(name);
if (indexList == null) {
indexList = new LinkedList<>();
indexes.put(name, indexList);

//for escaped (checking if it necessary)
char before = EMPTY;
if(i != 0){
before = query.charAt(i-1);
}
// quote or double quote should be ignored
if ((c == '"' || c == '\'') && before != '\\') {
if(c=='\''){
if(inSingleQuotes){
inSingleQuotes = false;
if(quotationStart == '\''){
quotationStart = EMPTY;
inDoubleQuotes = false;//
}
}else{
inSingleQuotes = true;
if(quotationStart == EMPTY){
quotationStart = c;
}
}
indexList.add(index);

index++;
} else if(c == '"'){
if(inDoubleQuotes){
inDoubleQuotes = false;
if(quotationStart == '"'){
quotationStart = EMPTY;
inSingleQuotes = false;
}
}else{
inDoubleQuotes = true;
if(quotationStart == EMPTY){
quotationStart = c;
}
}
}

}
if ((quotationStart == '\'' && inSingleQuotes) || (quotationStart == '"' && inDoubleQuotes)) {
continue;
}

// get parameter
if (c == ':' && query.length() > i+1 && (query.charAt(i + 1) != '=') && (query.charAt(i + 1) != ' ')) {
return i;
}
parsedQuery.append(c);
}
return -1;
}

private static int findEndofVariable(StringBuffer sql, int idx) {
int i = idx + 1;
while (i < sql.length() && Character.isJavaIdentifierPart(sql.charAt(i))) {
i++;
}
return i;
}

protected String prepare(String query) {
StringBuffer sql = new StringBuffer(query);
HashMap<String, List<Integer>> variables = new HashMap<>();
int idx = findColon(0, sql);
int variableNumber = 1;

while (idx != -1) {
int end = findEndofVariable(sql, idx);
String name = sql.substring(idx + 1, end);
sql = sql.replace(idx, end, "?");

indexMap = new HashMap<>(indexes.size());
// replace the lists of Integer objects with arrays of integers
for (Map.Entry<String, List<Integer>> entry : indexes.entrySet()) {
List<Integer> variableList = variables.get(name);
if (variableList == null) {
variableList = new ArrayList<>();
variables.put(name, variableList);
}
variableList.add(variableNumber);
variableNumber++;


idx = findColon(idx + 1 , sql);
}

variablesMap = new HashMap<>(variables.size());
for (Map.Entry<String, List<Integer>> entry : variables.entrySet()) {
List<Integer> list = entry.getValue();
int[] intIndexes = new int[list.size()];
int[] indexes = new int[list.size()];
int i = 0;
for (Integer x : list) {
intIndexes[i++] = x;
indexes[i++] = x;
}
indexMap.put(entry.getKey(), intIndexes);
variablesMap.put(entry.getKey(), indexes);
}

return parsedQuery.toString();
return sql.toString();
}

private int[] getIndexes(String name) {
int[] indexes = indexMap.get(name);

private int[] getVariableIndexes(String name) {
int[] indexes = variablesMap.get(name);
if (indexes == null) {
//throw new IllegalStateException("Parameter not found: " + name);
return new int[]{};// As the parameters doesnt specify the Statement it will belong, this will make possible to try indexes that weren't be used in this Statement
LOGGER.debug("getVariableIndexes didn't find the variable:{} in :{}", name, variablesMap.keySet());
throw new IllegalStateException("Parameter not found: " + name);
}
return indexes;
}

public void setObject(String name, Object value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setObject(index, value);
}
}

public void setString(String name, String value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setString(index, value);
}
}

public void setInt(String name, int value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setInt(index, value);
}
}

public void setBigDecimal(String name, BigDecimal value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setBigDecimal(index, value);
}
}

public void setLong(String name, long value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setLong(index, value);
}
}

public void setTimestamp(String name, Timestamp value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setTimestamp(index, value);
}
}

public void setTime(String name, Time value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setTime(index, value);
}
}

public void setDouble(String name, double value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setDouble(index, value);
}
}

public void setBoolean(String name, boolean value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setBoolean(index, value);
}
}

public void setBytes(String name, byte[] value) throws SQLException {
int[] indexes = getIndexes(name);
int[] indexes = getVariableIndexes(name);
for (int index : indexes) {
statement.setBytes(index, value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ public abstract class Translator {
NARY_TO_SQL.put(NaryLogicalOperator._or, "or");
}

public static Translator getImpl(String type) {
if("oracle".equals(type)) {
return ORACLE;
}
throw new IllegalArgumentException("RDBMS required for metadata isn't supported. Type="+type);
}

public String generateStatement(SelectStmt selectStmt) {
StringBuilder queryStringBuilder = new StringBuilder();
generatePre(selectStmt, queryStringBuilder);
Expand Down Expand Up @@ -345,6 +352,8 @@ protected void recursiveTranslateArrayContains(TranslationContext translationCon
//Possible subquery or it will need to run a query before this
//{ _id: 1, results: [ 82, 85, 88 ] } { _id: 2, results: [ 75, 88, 89 ] } ->{ results: { elemMatch: { gte: 80, lt: 85 } } }->{ "_id" : 1, "results" : [ 82, 85, 88 ] }
protected void recursiveTranslateArrayElemMatch(TranslationContext translationContext, ArrayMatchExpression arrayMatchExpression) {
/*
TODO Need to define what would happen in this scenario (not supported yet)
FieldTreeNode arrayNode = resolve(translationContext.fieldTreeNode, arrayMatchExpression.getArray());
if (arrayNode instanceof ArrayField) {
ArrayElement arrayElement = ((ArrayField) arrayNode).getElement();
Expand All @@ -353,11 +362,11 @@ protected void recursiveTranslateArrayElemMatch(TranslationContext translationCo
translationContext.fieldTreeNode = arrayElement;
recursiveTranslateQuery(translationContext, arrayMatchExpression.getElemMatch());
String path = translatePath(arrayMatchExpression.getArray());
// TODO Need to define what would happen in this scenario (not supported yet)
translationContext.fieldTreeNode = tmpFieldTreeNode;
throw Error.get(RDBMSConstants.ERR_NO_OPERATOR, arrayMatchExpression.toString());
}
}
*/
throw com.redhat.lightblue.util.Error.get(RDBMSConstants.INV_FIELD, arrayMatchExpression.toString());
}

Expand Down Expand Up @@ -482,17 +491,13 @@ protected void recursiveTranslateUnaryLogicalExpression(TranslationContext trans
}

protected void recursiveTranslateValueComparisonExpression(TranslationContext translationContext, ValueComparisonExpression valueComparisonExpression){
StringBuilder str = new StringBuilder();
// We have to deal with array references here
Value rvalue = valueComparisonExpression.getRvalue();
Path lField = valueComparisonExpression.getField();
int ln = lField.nAnys();
if (ln > 0) {
// TODO Need to define what would happen in this scenario
throw Error.get(RDBMSConstants.ERR_NO_OPERATOR, valueComparisonExpression.toString());
} else if (ln > 0) {
// TODO Need to define what would happen in this scenario
throw Error.get(RDBMSConstants.ERR_NO_OPERATOR, valueComparisonExpression.toString());
} else {
// No ANYs, direct comparison
String field = lField.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@
import com.redhat.lightblue.metadata.rdbms.model.Statement;
import com.redhat.lightblue.metadata.rdbms.model.Then;
import com.redhat.lightblue.util.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RDBMSPropertyParserImpl<T> extends PropertyParser<T> {

private static final Logger LOGGER = LoggerFactory.getLogger(RDBMSPropertyParserImpl.class);
public static final String NAME = "rdbms";

@Override
Expand Down Expand Up @@ -280,7 +283,7 @@ private If parseIf(MetadataParser<T> p, T ifT) {

private Then parseThenOrElse(MetadataParser<T> p, T t, String name, Then then) {
try {
String loopOperator = p.getStringProperty(t, name); // getStringProperty doesnt throw execption when field doesnt exist (but if it doesnt and it isnt the right type it throws and execption)
String loopOperator = p.getStringProperty(t, name); // getStringProperty doesnt throw exception when field doesnt exist (but if it doesnt and it isnt the right type it throws and execption)
if (loopOperator != null) {
then.setLoopOperator(loopOperator);
} else {
Expand All @@ -292,7 +295,9 @@ private Then parseThenOrElse(MetadataParser<T> p, T t, String name, Then then) {
List<T> expressionsT = p.getObjectList(t, name);
List<Expression> expressions = parseExpressions(p, expressionsT);
then.setExpressions(expressions);
} catch (Throwable te) {
} catch (Exception te) {
LOGGER.error("Expression returned an exception",te);

return null;
}

Expand Down
Loading