blob: 6d827da196cba3e3d086a830e7a514750bc62bd5 [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.phoenix.expression;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.PBaseColumn;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PName;
import org.apache.phoenix.schema.PNameFactory;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.tuple.MultiKeyValueTuple;
import org.apache.phoenix.schema.types.PBoolean;
import org.apache.phoenix.schema.types.PDataType;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class OrExpressionTest {
private OrExpression createOr(Expression lhs, Expression rhs) {
return new OrExpression(Arrays.asList(lhs, rhs));
}
private OrExpression createOr(Boolean x, Boolean y) {
return createOr(LiteralExpression.newConstant(x), LiteralExpression.newConstant(y));
}
private void testImmediateSingle(Boolean expected, Boolean lhs, Boolean rhs) {
OrExpression or = createOr(lhs, rhs);
ImmutableBytesWritable out = new ImmutableBytesWritable();
MultiKeyValueTuple tuple = new MultiKeyValueTuple();
boolean success = or.evaluate(tuple, out);
assertTrue(success);
assertEquals(expected, PBoolean.INSTANCE.toObject(out));
}
// Evaluating OR when values of both sides are known should immediately succeed
// and return the same result regardless of order.
private void testImmediate(Boolean expected, Boolean a, Boolean b) {
testImmediateSingle(expected, a, b);
testImmediateSingle(expected, b, a);
}
private PColumn pcolumn(final String name) {
return new PBaseColumn() {
@Override public PName getName() {
return PNameFactory.newName(name);
}
@Override public PDataType getDataType() {
return PBoolean.INSTANCE;
}
@Override public PName getFamilyName() {
return PNameFactory.newName(QueryConstants.DEFAULT_COLUMN_FAMILY);
}
@Override public int getPosition() {
return 0;
}
@Override public Integer getArraySize() {
return null;
}
@Override public byte[] getViewConstant() {
return new byte[0];
}
@Override public boolean isViewReferenced() {
return false;
}
@Override public String getExpressionStr() {
return null;
}
@Override public boolean isRowTimestamp() {
return false;
}
@Override public boolean isDynamic() {
return false;
}
@Override public byte[] getColumnQualifierBytes() {
return null;
}
@Override public long getTimestamp() {
return 0;
}
@Override public boolean isDerived() {
return false;
}
@Override public boolean isExcluded() {
return false;
}
@Override public SortOrder getSortOrder() {
return null;
}
};
}
private KeyValueColumnExpression kvExpr(final String name) {
return new KeyValueColumnExpression(pcolumn(name));
}
private Cell createCell(String name, Boolean value) {
byte[] valueBytes = value == null ? null : value ? PBoolean.TRUE_BYTES : PBoolean.FALSE_BYTES;
return CellUtil.createCell(
Bytes.toBytes("row"),
QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES,
Bytes.toBytes(name),
1,
KeyValue.Type.Put.getCode(),
valueBytes);
}
private void testPartialOneSideFirst(Boolean expected, Boolean lhs, Boolean rhs, boolean leftFirst) {
KeyValueColumnExpression lhsExpr = kvExpr("LHS");
KeyValueColumnExpression rhsExpr = kvExpr("RHS");
OrExpression or = createOr(lhsExpr, rhsExpr);
MultiKeyValueTuple tuple = new MultiKeyValueTuple(Collections.<Cell>emptyList());
ImmutableBytesWritable out = new ImmutableBytesWritable();
// with no data available, should fail
boolean success = or.evaluate(tuple, out);
assertFalse(success);
// with 1 datum available, should fail
if (leftFirst) {
tuple.setKeyValues(Collections.singletonList(createCell("LHS", lhs)));
} else {
tuple.setKeyValues(Collections.singletonList(createCell("RHS", rhs)));
}
success = or.evaluate(tuple, out);
assertFalse(success);
// with 2 data available, should succeed
tuple.setKeyValues(Arrays.asList(createCell("LHS", lhs), createCell("RHS", rhs)));
success = or.evaluate(tuple, out);
assertTrue(success);
assertEquals(expected, PBoolean.INSTANCE.toObject(out));
}
private void testPartialEvaluation(Boolean expected, Boolean x, Boolean y, boolean xFirst) {
testPartialOneSideFirst(expected, x, y, xFirst);
testPartialOneSideFirst(expected, y, x, !xFirst);
}
private void testShortCircuitOneSideFirst(Boolean expected, Boolean lhs, Boolean rhs, boolean leftFirst) {
KeyValueColumnExpression lhsExpr = kvExpr("LHS");
KeyValueColumnExpression rhsExpr = kvExpr("RHS");
OrExpression or = createOr(lhsExpr, rhsExpr);
MultiKeyValueTuple tuple = new MultiKeyValueTuple(Collections.<Cell>emptyList());
ImmutableBytesWritable out = new ImmutableBytesWritable();
// with no data available, should fail
boolean success = or.evaluate(tuple, out);
assertFalse(success);
// with 1 datum available, should succeed
if (leftFirst) {
tuple.setKeyValues(Collections.singletonList(createCell("LHS", lhs)));
} else {
tuple.setKeyValues(Collections.singletonList(createCell("RHS", rhs)));
}
success = or.evaluate(tuple, out);
assertTrue(success);
assertEquals(expected, PBoolean.INSTANCE.toObject(out));
}
private void testShortCircuit(Boolean expected, Boolean x, Boolean y, boolean xFirst) {
testShortCircuitOneSideFirst(expected, x, y, xFirst);
testShortCircuitOneSideFirst(expected, y, x, !xFirst);
}
@Test
public void testImmediateCertainty() {
testImmediate(true, true, true);
testImmediate(true, false, true);
testImmediate(false, false, false);
}
@Test
public void testImmediateUncertainty() {
testImmediate(true, true, null);
testImmediate(null, false, null);
testImmediate(null, null, null);
}
@Test
public void testPartialCertainty() {
// must evaluate both sides if FALSE is evaluated first
// F OR F = F
testPartialEvaluation(false, false, false, true);
testPartialEvaluation(false, false, false, false);
// T OR F = T
testPartialEvaluation(true, false, true, true);
testPartialEvaluation(true, true, false, false);
}
@Test
public void testPartialUncertainty() {
// T OR NULL = NULL
testPartialEvaluation(true, null, true, true);
testPartialEvaluation(true, true, null, false);
// must evaluate both sides if NULL is evaluated first
// F OR NULL = NULL
testPartialEvaluation(null, null, false, true);
testPartialEvaluation(null, false, null, false);
// NULL OR NULL = NULL
testPartialEvaluation(null, null, null, true);
testPartialEvaluation(null, null, null, false);
}
@Test
public void testShortCircuitCertainty() {
// need only to evaluate one side if TRUE is evaluated first
// T OR T = T
testShortCircuit(true, true, true, true);
testShortCircuit(true, true, true, false);
// T OR F = F
testShortCircuit(true, true, false, true);
testShortCircuit(true, false, true, false);
}
@Test
public void testShortCircuitUncertainty() {
// need only to evaluate one side if TRUE is evaluated first
testShortCircuit(true, true, null, true);
testShortCircuit(true, null, true, false);
}
@Test
public void testTruthTable() {
// See: https://en.wikipedia.org/wiki/Null_(SQL)#Comparisons_with_NULL_and_the_three-valued_logic_(3VL)
Boolean[][] testCases = new Boolean[][] {
// should short circuit?
// X, Y, if X first, if Y first, X OR Y,
{ true, true, true, true, true, },
{ true, false, true, false, true, },
{ false, false, false, false, false, },
{ true, null, true, false, true, },
{ false, null, false, false, null, },
{ null, null, false, false, null, },
};
for (Boolean[] testCase : testCases) {
Boolean x = testCase[0];
Boolean y = testCase[1];
boolean shouldShortCircuitWhenXEvaluatedFirst = testCase[2];
boolean shouldShortCircuitWhenYEvaluatedFirst = testCase[3];
Boolean expected = testCase[4];
// test both directions
testImmediate(expected, x, y);
if (shouldShortCircuitWhenXEvaluatedFirst) {
testShortCircuit(expected, x, y, true);
} else {
testPartialEvaluation(expected, x, y, true);
}
if (shouldShortCircuitWhenYEvaluatedFirst) {
testShortCircuit(expected, x, y, false);
} else {
testPartialEvaluation(expected, x, y, false);
}
}
}
}