/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZooDB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.internal.query; import static org.zoodb.internal.query.TypeConverterTools.toDouble; import static org.zoodb.internal.query.TypeConverterTools.toLong; import java.lang.reflect.Field; import java.util.Collection; import java.util.Map; import org.zoodb.api.impl.ZooPC; import org.zoodb.internal.DataDeSerializerNoClass; import org.zoodb.internal.ZooFieldDef; import org.zoodb.internal.query.QueryParser.COMP_OP; import org.zoodb.internal.query.TypeConverterTools.COMPARISON_TYPE; import org.zoodb.internal.util.DBLogger; public final class QueryTerm { static final Object THIS = new Object(); static final Object NULL = new NullClass(); /** So NULL has a different type than other objects. */ private static final class NullClass{}; /** Represent result from evaluating functions on references that are 'null'. */ static final Object INVALID = new InvalidClass(); /** So INVALID has a different type than other objects. */ private static final class InvalidClass{}; private QueryParameter lhsParam; private final Object lhsValue; private final ZooFieldDef lhsFieldDef; private final QueryFunction lhsFunction; private final COMP_OP op; private final String rhsParamName; private final Object rhsValue; private QueryParameter rhsParam; private final ZooFieldDef rhsFieldDef; private final QueryFunction rhsFunction; //Even if the COMPARISON type is not known, it may be constant (type in collection or similar). //That means we could create an execution branch that assumes the comparison type of the //previous execution, or the previous few executions. private final COMPARISON_TYPE compType; public QueryTerm(QueryFunction lhsFunction, boolean negate) { this.lhsParam = null; switch (lhsFunction.op()) { case THIS: this.lhsFunction = null; this.lhsValue = THIS; break; case CONSTANT: this.lhsFunction = null; this.lhsValue = lhsFunction.getConstant(); break; case PARAM: this.lhsFunction = null; this.lhsValue = null; this.lhsParam = (QueryParameter) lhsFunction.getConstant(); break; default: this.lhsFunction = lhsFunction; this.lhsValue = null; } this.lhsFieldDef = null; this.op = COMP_OP.EQ.inverstIfTrue(negate); this.rhsParamName = null; this.rhsValue = true; this.rhsFieldDef = null; this.rhsFunction = null; this.compType = COMPARISON_TYPE.BOOLEAN; } public QueryTerm(Object lhsValue, ZooFieldDef lhsFieldDef, QueryFunction lhsFunction, COMP_OP op, String rhsParamName, Object rhsValue, ZooFieldDef rhsFieldDef, QueryFunction rhsFunction, boolean negate) { COMPARISON_TYPE lhsCt; COMPARISON_TYPE rhsCt; //LHS this.lhsParam = null; if (lhsFunction != null) { switch (lhsFunction.op()) { case THIS: this.lhsFunction = null; this.lhsValue = THIS; lhsCt = COMPARISON_TYPE.PC; break; case CONSTANT: this.lhsFunction = null; this.lhsValue = lhsFunction.evaluate(null, null); lhsCt = COMPARISON_TYPE.fromObject(this.lhsValue); break; case PARAM: this.lhsFunction = null; this.lhsValue = null; this.lhsParam = (QueryParameter) lhsFunction.getConstant(); //may be null Class<?> retType = lhsFunction.getReturnType(); lhsCt = retType != null ? COMPARISON_TYPE.fromClass(retType) : null; break; default: this.lhsFunction = lhsFunction; this.lhsValue = null; lhsCt = COMPARISON_TYPE.fromClass(lhsFunction.getReturnType()); } } else { this.lhsFunction = null; this.lhsValue = lhsValue; if (lhsFieldDef != null) { lhsCt = COMPARISON_TYPE.fromClass(lhsFieldDef.getJavaType()); } else { lhsCt = COMPARISON_TYPE.fromObject(lhsValue); } } this.lhsFieldDef = lhsFieldDef; //OP this.op = op.inverstIfTrue(negate); //RHS this.rhsParamName = rhsParamName; this.rhsFieldDef = rhsFieldDef; if (rhsFunction != null) { switch (rhsFunction.op()) { case THIS: this.rhsFunction = null; this.rhsValue = THIS; rhsCt = COMPARISON_TYPE.PC; break; case CONSTANT: this.rhsFunction = null; this.rhsValue = rhsFunction.evaluate(null, null); rhsCt = COMPARISON_TYPE.fromObject(this.rhsValue); break; case PARAM: this.rhsFunction = null; this.rhsValue = null; this.rhsParam = (QueryParameter) rhsFunction.getConstant(); Class<?> retType = rhsFunction.getReturnType(); //may be null if (retType == null && lhsCt != null) { rhsCt = lhsCt; } else { rhsCt = COMPARISON_TYPE.fromClass(retType); } break; default: this.rhsFunction = rhsFunction; this.rhsValue = null; rhsCt = COMPARISON_TYPE.fromClass(rhsFunction.getReturnType()); } } else { this.rhsFunction = null; this.rhsValue = rhsValue; if (rhsFieldDef != null) { rhsCt = COMPARISON_TYPE.fromClass(rhsFieldDef.getJavaType()); } else { rhsCt = COMPARISON_TYPE.fromObject(rhsValue); } } //Try to infer type from compared type. This happen for example when we compare //implicit parameters or parameters that are only declared later. if (lhsCt == null && rhsCt != null) { lhsCt = rhsCt; } compType = COMPARISON_TYPE.fromOperands(lhsCt, rhsCt); switch (op) { case AE: case A: case LE: case L: if (!(lhsCt.canBeNumber() || compType == COMPARISON_TYPE.STRING)) { throw DBLogger.newUser( "Illegal operator for " + lhsCt + " vs " + rhsCt); } default: } } public boolean isParametrized() { return rhsParamName != null; } public String getParamName() { return rhsParamName; } public void setParameter(QueryParameter param) { this.rhsParam = param; } public QueryParameter getParameter() { return rhsParam; } public Object getValue(Object cand) { if (rhsFunction != null) { return rhsFunction.evaluate(cand, cand); } else if (rhsParam != null) { return rhsParam.getValue(); } if (rhsFieldDef != null) { try { return rhsFieldDef.getJavaField().get(cand); } catch (IllegalArgumentException e) { throw DBLogger.newFatalInternal("Cannot access field: " + rhsFieldDef.getName() + " class=\"" + cand.getClass().getName() + "\"," + " declaring class=\"" + rhsFieldDef.getDeclaringType().getClassName() + "\"", e); } catch (IllegalAccessException e) { throw DBLogger.newFatalInternal("Cannot access field: " + rhsFieldDef.getName(), e); } } if (rhsValue == THIS) { return cand; } return rhsValue; } private Object getLhsValue(Object cand) { if (lhsFunction != null) { return lhsFunction.evaluate(cand, cand); } else if (lhsValue == THIS){ return cand; } else if (lhsValue != null){ return lhsValue; } else if (lhsParam != null) { return lhsParam.getValue(); } else { // we cannot cache this, because sub-classes may have different field instances. //TODO cache per class? Or reset after query has processed first class set? Field lhsField = lhsFieldDef.getJavaField(); try { return lhsField.get(cand); } catch (IllegalArgumentException e) { throw DBLogger.newFatalInternal("Cannot access field: " + lhsField.getName() + " class=\"" + cand.getClass().getName() + "\"," + " declaring class=\"" + lhsField.getDeclaringClass().getName()+ "\"", e); } catch (IllegalAccessException e) { throw DBLogger.newFatalInternal("Cannot access field: " + lhsField.getName(), e); } } } @SuppressWarnings({ "rawtypes", "unchecked" }) public boolean evaluate(Object cand) { Object lhsVal = getLhsValue(cand); if (lhsVal == INVALID) { //Return 'true' only in case of '!='. return false;//op == COMP_OP.NE; } if (!op.isComparator()) { return evaluateBoolFunction(lhsVal, cand); } //TODO avoid indirection and store Parameter value in local _value field !!!!!!!!!!!!!!!! Object qVal = getValue(cand); if (lhsVal != null && qVal != null) { //could be null because of primitive objects switch (compType) { //TODO implement op.compare(int, int) directly //TODO convert constants only once and store converted values... //TODO write dedicated converters for <int,int>, <byte,byte>, ... to avoid type conversion case FLOAT: case DOUBLE: return op.evaluate(Double.compare(toDouble(lhsVal), toDouble(qVal))); case CHAR: case BYTE: case SHORT: case INT: case LONG: return op.evaluate(Long.compare(toLong(lhsVal), toLong(qVal))); default: break; } } if (lhsVal == null) { if (qVal == QueryTerm.NULL && op.allowsEqual()) { return true; } //ordering of null-value fields is not specified (JDO 3.0 14.6.6: Ordering Statement) //We specify: (null <= 'x' ==true) if (qVal != QueryTerm.NULL && op.allowsLess()) { return true; } } else if (lhsVal instanceof ZooPC && qVal instanceof ZooPC) { long oid1 = ((ZooPC)lhsVal).jdoZooGetOid(); long oid2 = ((ZooPC)qVal).jdoZooGetOid(); if (oid1 == oid2 && op.allowsEqual()) { return true; } else if (op == COMP_OP.EQ) { return false; //shortcut for most common case: oid1==oid2 } int res = Long.compare(oid2, oid1); if (res >= 1 && op.allowsLess()) { return true; } else if (res <= -1 && op.allowsMore()) { return true; } } else if (lhsVal instanceof ZooPC || qVal instanceof ZooPC) { //Either one of them is null or one of them is not a PC return op.allowsLess() || op.allowsMore(); } else if (qVal != QueryTerm.NULL && lhsVal != null) { if (qVal.equals(lhsVal) && op.allowsEqual()) { return true; } else if (op == COMP_OP.EQ) { return false; //shortcut for most common case: a==b } if (qVal instanceof Comparable) { Comparable qComp = (Comparable) qVal; try { int res = qComp.compareTo(lhsVal); //-1:< 0:== 1:> if (res >= 1 && op.allowsLess()) { return true; } else if (res <= -1 && op.allowsMore()) { return true; } } catch (ClassCastException e) { if ((lhsParam != null && lhsParam.getType() == null) || (rhsParam != null && rhsParam.getType() == null)) { throw DBLogger.newUser("Incomparable types in implicit parameters: " + lhsVal.getClass() + " vs " + qVal.getClass()); } //this should not happen throw e; } } } else { //here: qVal == QueryParser.NULL && oVal != null //Ordering of null-value fields is not specified (JDO 3.0 14.6.6: Ordering Statement) //We specify: (null <= 'x' ==true) if (op==COMP_OP.NE || op==COMP_OP.A || op==COMP_OP.AE) { return true; } } return false; } private boolean evaluateBoolFunction(Object lhsVal, Object cand) { if (lhsVal == null) { //According to JDO spec 14.6.2, calls on 'null' result in 'false' return false; } switch (op) { case COLL_contains: return ((Collection<?>)lhsVal).contains(getValue(cand)); case COLL_isEmpty: return ((Collection<?>)lhsVal).isEmpty(); case MAP_containsKey: return ((Map<?,?>)lhsVal).containsKey(getValue(cand)) ; case MAP_containsValue: return ((Map<?,?>)lhsVal).containsValue(getValue(cand)); case MAP_isEmpty: return ((Map<?,?>)lhsVal).isEmpty(); case STR_startsWith: return ((String)lhsVal).startsWith((String) getValue(cand)); case STR_endsWith: return ((String)lhsVal).endsWith((String) getValue(cand)); case STR_matches: return ((String)lhsVal).matches((String) getValue(cand)); case STR_contains_NON_JDO: return ((String)lhsVal).contains((String) getValue(cand)); default: throw new UnsupportedOperationException(op.name()); } } @SuppressWarnings({ "rawtypes", "unchecked" }) public boolean evaluate(DataDeSerializerNoClass dds, long pos) { // we cannot cache this, because sub-classes may have different field instances. //TODO cache per class? Or reset after query has processed first class set? //Field f = fieldDef.getJavaField(); long oVal; // try { dds.seekPos(pos); //TODO this doesn't really work for float/double oVal = dds.getAttrAsLong(lhsFieldDef.getDeclaringType(), lhsFieldDef); // } catch (IllegalArgumentException e) { // throw new JDOFatalInternalException("Cannot access field: " + fieldDef.getName() + // " cl=" + fieldDef.getDeclaringType().getClassName() + // " fcl=" + f.getDeclaringClass().getName(), e); // } //TODO avoid indirection and store Parameter value in local _value field !!!!!!!!!!!!!!!! //TODO using 'null' for now, knowing it won't work... Object qVal = getValue(null); if (qVal != QueryTerm.NULL) { if (qVal.equals(oVal) && (op==COMP_OP.EQ || op==COMP_OP.LE || op==COMP_OP.AE)) { return true; } if (qVal instanceof Comparable) { Comparable qComp = (Comparable) qVal; int res = qComp.compareTo(oVal); //-1:< 0:== 1:> if (res == 1 && (op == COMP_OP.LE || op==COMP_OP.L || op==COMP_OP.NE)) { return true; } else if (res == -1 && (op == COMP_OP.AE || op==COMP_OP.A || op==COMP_OP.NE)) { return true; } } } return false; } public String print() { StringBuilder sb = new StringBuilder(); if (lhsFieldDef != null) { sb.append(lhsFieldDef.getName()); } else if (lhsValue != null) { sb.append(lhsValue); } else if (lhsParam != null) { sb.append("P-" + lhsParam.getName()); } else if (lhsFunction != null) { sb.append(lhsFunction.toString()); } else { sb.append("???"); } sb.append(" "); sb.append(op); sb.append(" "); if (rhsFieldDef != null) { sb.append(rhsFieldDef.getName()); } else if (rhsValue != null) { sb.append(rhsValue); } else if (rhsParam != null) { sb.append("P-" + rhsParam.getName()); } else if (rhsFunction != null) { sb.append(rhsFunction.toString()); } else { sb.append("???"); } return sb.toString(); } ZooFieldDef getLhsFieldDef() { return lhsFieldDef; } COMP_OP getOp() { return op; } boolean isRhsFixed() { return rhsFieldDef == null; } public boolean isLhsFunction() { return lhsFunction != null; } public QueryFunction getLhsFunction() { return lhsFunction; } }