/** * This software is licensed to you under the Apache License, Version 2.0 (the * "Apache License"). * * LinkedIn's contributions are made under the Apache License. If you contribute * to the Software, the contributions will be deemed to have been made under the * Apache License, unless you expressly indicate otherwise. Please do not make any * contributions that would be inconsistent with the Apache License. * * You may obtain a copy of the Apache License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, this software * distributed under the Apache License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Apache * License for the specific language governing permissions and limitations for the * software governed under the Apache License. * * © 2012 LinkedIn Corp. All Rights Reserved. */ package com.senseidb.search.query; import java.util.HashSet; import java.util.Iterator; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.senseidb.util.JSONUtil.FastJSONArray; import com.senseidb.util.JSONUtil.FastJSONObject; public class ConstExpQueryConstructor extends QueryConstructor { public static final String QUERY_TYPE = "const_exp"; // "const_exp" : { // "lvalue" : 4, // "operator" : "in", // supported operations: (1) set operations: in, not_in, ==, != ; (2) single value operations: >, >=, ==, <, <=, !=, // "rvalue" : [4,5,6] // }, // // "const_exp" : { // "lvalue" : { // "function":"length", // different function may have different number of parameters; // "params": [ // we use json array to represent a list of parameters; // [5,6,7] // for function length, we only have one parameter, which is a json array; // ] // }, // "operator" : ">", // this example shows how to check if a value list is not empty; // "rvalue" : 0 // }, // // "in, not_in" are set operations, set can have string, or numerical values; // ">, >=, <, <=," are normal boolean operations, applied to simple numerical value; (such as an integer or double) // "!=, ==" can be applied to both simple numerical value and set and string; // // for set operations in or not_in, left value could be a single element, right side has to be a collection. // // Expression Query is mostly combined with other queries to form a boolean query, and filled by query template. // For example, it can be used in BQL template used by machine. @Override public Query doConstructQuery(JSONObject json) throws JSONException { boolean bool = false; String operator = null; Object lvalue = null; Object rvalue = null; Iterator<String> iter = json.keys(); if (!iter.hasNext()) throw new IllegalArgumentException("no operator or values specified in ExpressionQuery: " + json); while(iter.hasNext()) { String field = iter.next(); if(field.equals(QueryConstructor.OPERATOR_PARAM)) operator = json.optString(field); else if(field.equals(QueryConstructor.LEFT_VALUE)) lvalue = json.opt(field); else if(field.equals(QueryConstructor.RIGHT_VALUE)) rvalue = json.opt(field); } if(operator == null) throw new IllegalArgumentException("operator not defined in ExpressionQuery: " + json); if(lvalue == null) throw new IllegalArgumentException("left value not defined in ExpressionQuery: " + json); if(rvalue == null) throw new IllegalArgumentException("right value not defined in ExpressionQuery: " + json); // if either lvalue or rvalue has a built-in function, process this function firstly; // the returned result can only be json array or double value; if(lvalue instanceof JSONObject) lvalue = getValueFromFunction((JSONObject)lvalue); if(rvalue instanceof JSONObject) rvalue = getValueFromFunction((JSONObject)rvalue); // set operations only; if(operator.equals(QueryConstructor.OP_IN)) { bool = checkIn(lvalue, rvalue, json); } else if(operator.equals(QueryConstructor.OP_NOT_IN)) { bool = !checkIn(lvalue, rvalue, json); } // operations for set or signal values; else if(operator.equals(QueryConstructor.OP_EQUAL)) { bool = checkEqual(lvalue, rvalue, json); } else if(operator.equals(QueryConstructor.OP_NOT_EQUAL)) { bool = !checkEqual(lvalue, rvalue, json); } // single value comparisons only; else if(operator.equals(QueryConstructor.OP_GE) || operator.equals(QueryConstructor.OP_GT) || operator.equals(QueryConstructor.OP_LE) || operator.equals(QueryConstructor.OP_LT) ) { if(lvalue instanceof JSONArray || rvalue instanceof JSONArray) throw new IllegalArgumentException("operator " + operator + " is not defined for list, in ExpressionQuery: " + json); double ldouble = convertToDouble(lvalue, json); double rdouble = convertToDouble(rvalue, json); if(operator.equals(QueryConstructor.OP_GE)) // >= bool = ldouble >= rdouble; else if(operator.equals(QueryConstructor.OP_GT)) // > bool = ldouble > rdouble; else if(operator.equals(QueryConstructor.OP_LE)) // <= bool = ldouble <= rdouble; else if(operator.equals(QueryConstructor.OP_LT)) // < bool = ldouble < rdouble; } else { throw new IllegalArgumentException("Operator " + operator + " is not supported in ExpressionQuery: " + json); } Query q = null; if(bool == true) q = new MatchAllDocsQuery(); else q = new MatchNoneDocsQuery(); return q; } private double convertToDouble(Object value, JSONObject json) { if(value instanceof Number) { return ((Number) value).doubleValue(); } else throw new IllegalArgumentException("operator >, >=, <, <= can only be applied to double, int, long or float type data, in ExpressionQuery: " + json); } /** * @param funcJSON * @return the function result can only be either a Double or a JSONArray; * @throws JSONException */ private Object getValueFromFunction(JSONObject funcJSON) throws JSONException { String function = funcJSON.optString(QueryConstructor.FUNCTION_NAME); if(function.length()==0) throw new IllegalArgumentException("No function name is defined in ExpressionQuery's function json: " + funcJSON); JSONArray params = funcJSON.optJSONArray(QueryConstructor.PARAMS_PARAM); if(params == null) throw new IllegalArgumentException("No function param is defined in ExpressionQuery's function json: " + funcJSON); if(function.equals("length")) { // get the first and only the first parameter for length function; JSONArray param = params.optJSONArray(0); if(param == null) throw new IllegalArgumentException("No param is provided for function '" + function + "' defined in ExpressionQuery's function json: " + funcJSON); return (double)param.length(); } else throw new IllegalArgumentException("Unsupported function '" + function + "' in ExpressionQuery's function json: " + funcJSON); } private boolean checkIn(Object lvalue, Object rvalue, JSONObject json) throws JSONException { boolean bool = false; if(rvalue instanceof JSONArray) { JSONArray rarray = (JSONArray) rvalue; HashSet hs = new HashSet(); for(int i=0; i< rarray.length(); i++) { Object robj = rarray.get(i); hs.add(robj); } if(lvalue instanceof JSONArray) { JSONArray larray = (JSONArray) lvalue; bool = true; for(int j=0; j< larray.length(); j++) { Object lobj = larray.get(j); if(!hs.contains(lobj)) { bool = false; break; } } } else if(hs.contains(lvalue)) bool = true; else bool = false; } else { throw new IllegalArgumentException("operator not_in requires a list of objects as the right value, in ExpressionQuery: "+ json); } return bool; } private boolean checkEqual(Object lvalue, Object rvalue, JSONObject json) throws JSONException { boolean bool = false; if((lvalue instanceof JSONArray) && (rvalue instanceof JSONArray)) { JSONArray larray = (JSONArray) lvalue; JSONArray rarray = (JSONArray) rvalue; if(larray.length() != rarray.length()) bool = false; else { HashSet hs = new HashSet(); for(int i=0; i< larray.length(); i++) { Object lobj = larray.get(i); hs.add(lobj); } bool = true; for(int j=0; j< rarray.length(); j++) { Object robj = rarray.get(j); if(!hs.contains(robj)) { bool = false; break; } } } } else if(!(lvalue instanceof JSONArray) && !(rvalue instanceof JSONArray)) { if(lvalue instanceof String && rvalue instanceof String) bool = lvalue.equals(rvalue); else { double ldouble = convertToDouble(lvalue, json); double rdouble = convertToDouble(rvalue, json); bool = ldouble == rdouble; } } else throw new IllegalArgumentException("for == operator, left value and right value should be both simple values or both lists. in ExpressionQuery: " + json); return bool; } public static void main(String args[]) throws JSONException{ JSONObject json = new FastJSONObject(); JSONObject func = new FastJSONObject(); func.put("function", "length"); func.put("params", new FastJSONArray().put(new FastJSONArray())); json.put("lvalue", func); json.put("operator", "=="); json.put("rvalue", 0); ConstExpQueryConstructor c = new ConstExpQueryConstructor(); c.doConstructQuery(json); } }