Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hazelcast.query.SqlPredicate Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.query;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import com.hazelcast.nio.serialization.IdentifiedDataSerializable;
import com.hazelcast.nio.serialization.impl.BinaryInterface;
import com.hazelcast.query.impl.Indexes;
import com.hazelcast.query.impl.QueryContext;
import com.hazelcast.query.impl.QueryableEntry;
import com.hazelcast.query.impl.predicates.AndPredicate;
import com.hazelcast.query.impl.predicates.CompoundPredicate;
import com.hazelcast.query.impl.predicates.OrPredicate;
import com.hazelcast.query.impl.predicates.PredicateDataSerializerHook;
import com.hazelcast.query.impl.predicates.Visitor;
import com.hazelcast.util.collection.ArrayUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.hazelcast.internal.serialization.impl.FactoryIdHelper.PREDICATE_DS_FACTORY_ID;
import static com.hazelcast.query.Predicates.between;
import static com.hazelcast.query.Predicates.equal;
import static com.hazelcast.query.Predicates.greaterEqual;
import static com.hazelcast.query.Predicates.greaterThan;
import static com.hazelcast.query.Predicates.ilike;
import static com.hazelcast.query.Predicates.lessEqual;
import static com.hazelcast.query.Predicates.lessThan;
import static com.hazelcast.query.Predicates.like;
import static com.hazelcast.query.Predicates.notEqual;
import static com.hazelcast.query.Predicates.regex;
/**
* This class contains methods related to conversion of sql query to predicate.
*/
@BinaryInterface
public class SqlPredicate
implements IndexAwarePredicate, VisitablePredicate, IdentifiedDataSerializable {
private static final long serialVersionUID = 1;
transient Predicate predicate;
private String sql;
public SqlPredicate(String sql) {
this.sql = sql;
predicate = createPredicate(sql);
}
public SqlPredicate() {
}
@Override
public boolean apply(Map.Entry mapEntry) {
return predicate.apply(mapEntry);
}
@Override
public boolean isIndexed(QueryContext queryContext) {
if (predicate instanceof IndexAwarePredicate) {
return ((IndexAwarePredicate) predicate).isIndexed(queryContext);
}
return false;
}
@Override
public Set filter(QueryContext queryContext) {
return ((IndexAwarePredicate) predicate).filter(queryContext);
}
@Override
public void writeData(ObjectDataOutput out) throws IOException {
out.writeUTF(sql);
}
@Override
public void readData(ObjectDataInput in) throws IOException {
sql = in.readUTF();
predicate = createPredicate(sql);
}
private int getApostropheIndex(String str, int start) {
return str.indexOf('\'', start);
}
private int getApostropheIndexIgnoringDoubles(String str, int start) {
int i = str.indexOf('\'', start);
int j = str.indexOf('\'', i + 1);
//ignore doubles
while (i == j - 1) {
i = str.indexOf('\'', j + 1);
j = str.indexOf('\'', i + 1);
}
return i;
}
private String removeEscapes(String phrase) {
return (phrase.length() > 2) ? phrase.replace("''", "'") : phrase;
}
private Predicate createPredicate(String sql) {
String paramSql = sql;
Map mapPhrases = new HashMap(1);
int apoIndex = getApostropheIndex(paramSql, 0);
if (apoIndex != -1) {
int phraseId = 0;
StringBuilder newSql = new StringBuilder();
while (apoIndex != -1) {
phraseId++;
int start = apoIndex + 1;
int end = getApostropheIndexIgnoringDoubles(paramSql, apoIndex + 1);
if (end == -1) {
throw new RuntimeException("Missing ' in sql");
}
String phrase = removeEscapes(paramSql.substring(start, end));
String key = "$" + phraseId;
mapPhrases.put(key, phrase);
String before = paramSql.substring(0, apoIndex);
paramSql = paramSql.substring(end + 1);
newSql.append(before);
newSql.append(key);
apoIndex = getApostropheIndex(paramSql, 0);
}
newSql.append(paramSql);
paramSql = newSql.toString();
}
Parser parser = new Parser();
List sqlTokens = parser.toPrefix(paramSql);
List tokens = new ArrayList(sqlTokens);
if (tokens.size() == 0) {
throw new RuntimeException("Invalid SQL: [" + paramSql + "]");
}
if (tokens.size() == 1) {
return eval(tokens.get(0));
}
root:
while (tokens.size() > 1) {
boolean foundOperand = false;
for (int i = 0; i < tokens.size(); i++) {
Object tokenObj = tokens.get(i);
if (tokenObj instanceof String && parser.isOperand((String) tokenObj)) {
String token = (String) tokenObj;
if ("=".equals(token) || "==".equals(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, equal((String) first, (Comparable) second));
} else if ("!=".equals(token) || "<>".equals(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, notEqual((String) first, (Comparable) second));
} else if (">".equals(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, greaterThan((String) first, (Comparable) second));
} else if (">=".equals(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, greaterEqual((String) first, (Comparable) second));
} else if ("<=".equals(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, lessEqual((String) first, (Comparable) second));
} else if ("<".equals(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, lessThan((String) first, (Comparable) second));
} else if ("LIKE".equalsIgnoreCase(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, like((String) first, (String) second));
} else if ("ILIKE".equalsIgnoreCase(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, ilike((String) first, (String) second));
} else if ("REGEX".equalsIgnoreCase(token)) {
int position = (i - 2);
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, regex((String) first, (String) second));
} else if ("IN".equalsIgnoreCase(token)) {
int position = i - 2;
validateOperandPosition(position);
Object exp = toValue(tokens.remove(position), mapPhrases);
String[] values = toValue(((String) tokens.remove(position)).split(","), mapPhrases);
setOrAdd(tokens, position, Predicates.in((String) exp, values));
} else if ("NOT".equalsIgnoreCase(token)) {
int position = i - 1;
validateOperandPosition(position);
Object exp = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, Predicates.not(eval(exp)));
} else if ("BETWEEN".equalsIgnoreCase(token)) {
int position = i - 3;
validateOperandPosition(position);
Object expression = tokens.remove(position);
Object from = toValue(tokens.remove(position), mapPhrases);
Object to = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, between((String) expression, (Comparable) from, (Comparable) to));
} else if ("AND".equalsIgnoreCase(token)) {
int position = i - 2;
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, flattenCompound(eval(first), eval(second), AndPredicate.class));
} else if ("OR".equalsIgnoreCase(token)) {
int position = i - 2;
validateOperandPosition(position);
Object first = toValue(tokens.remove(position), mapPhrases);
Object second = toValue(tokens.remove(position), mapPhrases);
setOrAdd(tokens, position, flattenCompound(eval(first), eval(second), OrPredicate.class));
} else {
throw new RuntimeException("Unknown token " + token);
}
continue root;
}
}
if (!foundOperand) {
throw new RuntimeException("Invalid SQL: [" + paramSql + "]");
}
}
return (Predicate) tokens.get(0);
}
private void validateOperandPosition(int pos) {
if (pos < 0) {
throw new RuntimeException("Invalid SQL: [" + sql + "]");
}
}
private Object toValue(final Object key, final Map phrases) {
final String value = phrases.get(key);
if (value != null) {
return value;
} else if (key instanceof String && ("null".equalsIgnoreCase((String) key))) {
return null;
} else {
return key;
}
}
private String[] toValue(final String[] keys, final Map phrases) {
for (int i = 0; i < keys.length; i++) {
final String value = phrases.get(keys[i]);
if (value != null) {
keys[i] = value;
}
}
return keys;
}
private void setOrAdd(List tokens, int position, Predicate predicate) {
if (tokens.size() == 0) {
tokens.add(predicate);
} else {
tokens.set(position, predicate);
}
}
private Predicate eval(Object statement) {
if (statement instanceof String) {
return equal((String) statement, "true");
} else {
return (Predicate) statement;
}
}
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
predicate = createPredicate(sql);
}
/**
* Return a {@link CompoundPredicate}, possibly flattened if one or both arguments is an instance of
* {@code CompoundPredicate}.
*
*/
static T flattenCompound(Predicate predicateLeft, Predicate predicateRight, Class klass) {
// The following could have been achieved with {@link com.hazelcast.query.impl.predicates.FlatteningVisitor},
// however since we only care for 2-argument flattening, we can avoid constructing a visitor and its internals
// for each token pass at the cost of the following explicit code.
Predicate[] predicates;
if (klass.isInstance(predicateLeft) || klass.isInstance(predicateRight)) {
Predicate[] left = getSubPredicatesIfClass(predicateLeft, klass);
Predicate[] right = getSubPredicatesIfClass(predicateRight, klass);
predicates = new Predicate[left.length + right.length];
ArrayUtils.concat(left, right, predicates);
} else {
predicates = new Predicate[] {predicateLeft, predicateRight};
}
try {
CompoundPredicate compoundPredicate = klass.newInstance();
compoundPredicate.setPredicates(predicates);
return (T) compoundPredicate;
} catch (InstantiationException e) {
throw new RuntimeException(String.format("%s should have a public default constructor", klass.getName()));
} catch (IllegalAccessException e) {
throw new RuntimeException(String.format("%s should have a public default constructor", klass.getName()));
}
}
private static Predicate[] getSubPredicatesIfClass(Predicate predicate, Class klass) {
if (klass.isInstance(predicate)) {
return ((CompoundPredicate) predicate).getPredicates();
} else {
return new Predicate[]{predicate};
}
}
@Override
public String toString() {
return predicate.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SqlPredicate)) {
return false;
}
SqlPredicate that = (SqlPredicate) o;
return sql.equals(that.sql);
}
@Override
public int hashCode() {
return sql.hashCode();
}
@Override
public Predicate accept(Visitor visitor, Indexes indexes) {
Predicate target = predicate;
if (predicate instanceof VisitablePredicate) {
target = ((VisitablePredicate) predicate).accept(visitor, indexes);
}
return target;
}
public Predicate getPredicate() {
return predicate;
}
@Override
public int getFactoryId() {
return PREDICATE_DS_FACTORY_ID;
}
@Override
public int getId() {
return PredicateDataSerializerHook.SQL_PREDICATE;
}
}