blob: c8c0472e81b2fd4a1bedc19f4027a791bf71a521 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.jackrabbit.oak.query.ast;
import java.util.Collections;
import java.util.Set;
import javax.jcr.PropertyType;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.PropertyValue;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
import org.apache.jackrabbit.oak.query.ValueConverter;
import org.apache.jackrabbit.oak.spi.query.fulltext.LikePattern;
import org.apache.jackrabbit.oak.query.index.FilterImpl;
import org.apache.jackrabbit.oak.plugins.memory.PropertyValues;
/**
* A comparison operation (including "like").
*/
public class ComparisonImpl extends ConstraintImpl {
private final DynamicOperandImpl operand1;
private final Operator operator;
private final StaticOperandImpl operand2;
public ComparisonImpl(DynamicOperandImpl operand1, Operator operator, StaticOperandImpl operand2) {
this.operand1 = operand1;
this.operator = operator;
this.operand2 = operand2;
}
public DynamicOperandImpl getOperand1() {
return operand1;
}
public Operator getOperator() {
return operator;
}
public StaticOperandImpl getOperand2() {
return operand2;
}
@Override
public Set<PropertyExistenceImpl> getPropertyExistenceConditions() {
PropertyExistenceImpl p = operand1.getPropertyExistence();
if (p == null) {
return Collections.emptySet();
}
return Collections.singleton(p);
}
@Override
public Set<SelectorImpl> getSelectors() {
return operand1.getSelectors();
}
@Override
public boolean evaluate() {
// JCR 2.0 spec, 6.7.16 Comparison:
// "operand1 may evaluate to an array of values"
PropertyValue p1 = operand1.currentProperty();
if (p1 == null) {
return false;
}
PropertyValue p2 = operand2.currentValue();
if (p2 == null) {
// if the property doesn't exist, the result is always false
// even for "null <> 'x'" (same as in SQL)
return false;
}
// "the value of operand2 is converted to the
// property type of the value of operand1" if possible
p2 = convertValueToType(p2, p1);
// if not possible, convert to the same type
if (p1.getType().tag() != p2.getType().tag()) {
// conversion failed: convert both to binary or string
int targetType = getCommonType(p1, p2);
p1 = convertToType(p1, targetType);
p2 = convertToType(p2, targetType);
}
if (p1.isArray()) {
// JCR 2.0 spec, 6.7.16 Comparison:
// "... constraint is satisfied as a whole if the comparison
// against any element of the array is satisfied."
Type<?> base = p1.getType().getBaseType();
for (int i = 0; i < p1.count(); i++) {
PropertyState value = PropertyStates.createProperty(
"value", p1.getValue(base, i), base);
if (operator.evaluate(PropertyValues.create(value), p2)) {
return true;
}
}
return false;
} else {
return operator.evaluate(p1, p2);
}
}
private static int getCommonType(PropertyValue p1, PropertyValue p2) {
if (p1.getType().tag() == PropertyType.BINARY || p2.getType().tag() == PropertyType.BINARY) {
return PropertyType.BINARY;
}
return PropertyType.STRING;
}
private PropertyValue convertToType(PropertyValue v, int targetType) {
try {
return ValueConverter.convert(v, targetType, query.getNamePathMapper());
} catch (IllegalArgumentException e) {
// not possible to convert
return v;
}
}
@Override
boolean accept(AstVisitor v) {
return v.visit(this);
}
@Override
public String toString() {
return operand1 + " " + operator + " " + operand2;
}
@Override
public void restrict(FilterImpl f) {
PropertyValue v = operand2.currentValue();
if (!ValueConverter.canConvert(
operand2.getPropertyType(),
operand1.getPropertyType())) {
throw new IllegalArgumentException(
"Unsupported conversion from property type " +
PropertyType.nameFromValue(operand2.getPropertyType()) +
" to property type " +
PropertyType.nameFromValue(operand1.getPropertyType()));
}
if (v != null) {
if (operator == Operator.LIKE) {
String pattern;
pattern = v.getValue(Type.STRING);
LikePattern p = new LikePattern(pattern);
String lowerBound = p.getLowerBound();
if (lowerBound != null) {
String upperBound = p.getUpperBound();
if (lowerBound.equals(upperBound)) {
// no wildcards: equality comparison
// but v may contain escaped wildcards, so we can't use it
PropertyValue pv = PropertyValues.newString(lowerBound);
operand1.restrict(f, Operator.EQUAL, pv);
} else if (operand1.supportsRangeConditions()) {
if (lowerBound != null) {
PropertyValue pv = PropertyValues.newString(lowerBound);
operand1.restrict(f, Operator.GREATER_OR_EQUAL, pv);
}
if (upperBound != null) {
PropertyValue pv = PropertyValues.newString(upperBound);
operand1.restrict(f, Operator.LESS_OR_EQUAL, pv);
}
} else {
// path conditions
operand1.restrict(f, operator, v);
}
} else {
// like '%' conditions
operand1.restrict(f, operator, v);
}
} else {
operand1.restrict(f, operator, v);
}
}
}
@Override
public void restrictPushDown(SelectorImpl s) {
if (operand2.currentValue() != null) {
if (operand1.canRestrictSelector(s)) {
s.restrictSelector(this);
}
}
}
@Override
public AstElement copyOf() {
return new ComparisonImpl(operand1.createCopy(), operator, operand2);
}
}