/*
* 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.impl.predicates;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.internal.serialization.InternalSerializationService;
import com.hazelcast.internal.serialization.impl.DefaultSerializationServiceBuilder;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.query.EntryObject;
import com.hazelcast.query.IndexAwarePredicate;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.PredicateBuilder;
import com.hazelcast.query.Predicates;
import com.hazelcast.query.QueryException;
import com.hazelcast.query.SampleObjects.Employee;
import com.hazelcast.query.SampleObjects.Value;
import com.hazelcast.query.impl.AttributeType;
import com.hazelcast.query.impl.Index;
import com.hazelcast.query.impl.IndexImpl;
import com.hazelcast.query.impl.QueryContext;
import com.hazelcast.query.impl.QueryEntry;
import com.hazelcast.query.impl.QueryableEntry;
import com.hazelcast.query.impl.getters.Extractors;
import com.hazelcast.query.impl.getters.ReflectionHelper;
import com.hazelcast.test.HazelcastSerialClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.annotation.QuickTest;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import static com.hazelcast.instance.TestUtil.toData;
import static com.hazelcast.query.Predicates.and;
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.in;
import static com.hazelcast.query.Predicates.instanceOf;
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.or;
import static com.hazelcast.query.Predicates.regex;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(HazelcastSerialClassRunner.class)
@Category(QuickTest.class)
public class PredicatesTest extends HazelcastTestSupport {
private static final String ATTRIBUTE = "DUMMY_ATTRIBUTE_IGNORED";
private final InternalSerializationService ss = new DefaultSerializationServiceBuilder().build();
@Test
public void testAndPredicate_whenFirstIndexAwarePredicateIsNotIndexed() throws Exception {
final HazelcastInstance instance = createHazelcastInstance();
final IMap<Object, Object> map = instance.getMap("map");
map.addIndex("name", false);
String name = randomString();
map.put("key", new Value(name));
final ShouldExecuteOncePredicate<?, ?> indexAwareNotIndexedPredicate = new ShouldExecuteOncePredicate<Object, Object>();
final EqualPredicate equalPredicate = new EqualPredicate("name", name);
final AndPredicate andPredicate = new AndPredicate(indexAwareNotIndexedPredicate, equalPredicate);
map.values(andPredicate);
}
static class ShouldExecuteOncePredicate<K, V> implements IndexAwarePredicate<K, V> {
boolean executed = false;
@Override
public boolean apply(Map.Entry<K, V> mapEntry) {
if (!executed) {
executed = true;
return true;
}
throw new RuntimeException();
}
@Override
public Set<QueryableEntry<K, V>> filter(final QueryContext queryContext) {
return null;
}
@Override
public boolean isIndexed(final QueryContext queryContext) {
return false;
}
}
@Test
public void testEqual() {
assertPredicateTrue(equal(ATTRIBUTE, "value"), "value");
assertPredicateFalse(equal(ATTRIBUTE, "value1"), "value");
assertPredicateTrue(equal(ATTRIBUTE, TRUE), true);
assertPredicateTrue(equal(ATTRIBUTE, true), TRUE);
assertPredicateFalse(equal(ATTRIBUTE, true), FALSE);
assertPredicateFalse(equal(ATTRIBUTE, new BigDecimal("1.23E3")), new BigDecimal("1.23E2"));
assertPredicateTrue(equal(ATTRIBUTE, new BigDecimal("1.23E3")), new BigDecimal("1.23E3"));
assertPredicateFalse(equal(ATTRIBUTE, 15.22), 15.23);
assertPredicateTrue(equal(ATTRIBUTE, 15.22), 15.22);
assertPredicateFalse(equal(ATTRIBUTE, 16), 15);
}
@Test
public void testAnd() {
final Predicate and1 = and(greaterThan(ATTRIBUTE, 4), lessThan(ATTRIBUTE, 6));
assertPredicateTrue(and1, 5);
final Predicate and2 = and(greaterThan(ATTRIBUTE, 5), lessThan(ATTRIBUTE, 6));
assertPredicateFalse(and2, 4);
final Predicate and3 = and(greaterThan(ATTRIBUTE, 4), lessThan(ATTRIBUTE, 6), equal(ATTRIBUTE, 5));
assertPredicateTrue(and3, 5);
final Predicate and4 = Predicates.and(greaterThan(ATTRIBUTE, 3), lessThan(ATTRIBUTE, 6), equal(ATTRIBUTE, 4));
assertPredicateFalse(and4, 5);
}
@Test
public void testOr() {
final Predicate or1 = or(equal(ATTRIBUTE, 3), equal(ATTRIBUTE, 4), equal(ATTRIBUTE, 5));
assertPredicateTrue(or1, 4);
assertPredicateFalse(or1, 6);
}
@Test
public void testGreaterEqual() {
assertPredicateTrue(greaterEqual(ATTRIBUTE, 5), 5);
}
@Test
public void testLessThan() {
assertPredicateTrue(lessThan(ATTRIBUTE, 7), 6);
assertPredicateFalse(lessThan(ATTRIBUTE, 3), 4);
assertPredicateFalse(lessThan(ATTRIBUTE, 4), 4);
assertPredicateTrue(lessThan(ATTRIBUTE, "tc"), "bz");
assertPredicateFalse(lessThan(ATTRIBUTE, "gx"), "h0");
}
@Test
public void testGreaterThan() {
assertPredicateTrue(greaterThan(ATTRIBUTE, 5), 6);
assertPredicateFalse(greaterThan(ATTRIBUTE, 5), 4);
assertPredicateFalse(greaterThan(ATTRIBUTE, 5), 5);
assertPredicateTrue(greaterThan(ATTRIBUTE, "aa"), "xa");
assertPredicateFalse(greaterThan(ATTRIBUTE, "da"), "cz");
assertPredicateTrue(greaterThan(ATTRIBUTE, new BigDecimal("1.23E2")), new BigDecimal("1.23E3"));
}
@Test
public void testLessEqual() {
assertPredicateTrue(lessEqual(ATTRIBUTE, 4), 4);
}
@Test
public void testPredicatesAgainstANullField() {
assertFalse_withNullEntry(lessEqual("nullField", 1));
assertFalse_withNullEntry(in("nullField", 1));
assertFalse_withNullEntry(lessThan("nullField", 1));
assertFalse_withNullEntry(greaterEqual("nullField", 1));
assertFalse_withNullEntry(greaterThan("nullField", 1));
assertFalse_withNullEntry(equal("nullField", 1));
assertFalse_withNullEntry(notEqual("nullField", null));
assertFalse_withNullEntry(between("nullField", 1, 1));
assertTrue_withNullEntry(like("nullField", null));
assertTrue_withNullEntry(ilike("nullField", null));
assertTrue_withNullEntry(regex("nullField", null));
assertTrue_withNullEntry(notEqual("nullField", 1));
}
@Test
public void testBetween() {
assertPredicateTrue(between(ATTRIBUTE, 4, 6), 5);
assertPredicateTrue(between(ATTRIBUTE, 5, 6), 5);
assertPredicateTrue(between(ATTRIBUTE, "abc", "xyz"), "prs");
assertPredicateFalse(between(ATTRIBUTE, "klmn", "xyz"), "efgh");
assertPredicateFalse(between(ATTRIBUTE, 6, 7), 5);
}
@Test
public void testIn() {
assertPredicateTrue(in(ATTRIBUTE, 4, 7, 8, 5), 5);
assertPredicateTrue(in(ATTRIBUTE, 5, 7, 8), 5);
assertPredicateFalse(in(ATTRIBUTE, 6, 7, 8), 5);
assertPredicateFalse(in(ATTRIBUTE, 6, 7, 8), 9);
}
@Test
public void testLike() {
assertPredicateTrue(like(ATTRIBUTE, "J%"), "Java");
assertPredicateTrue(like(ATTRIBUTE, "Ja%"), "Java");
assertPredicateTrue(like(ATTRIBUTE, "J_v_"), "Java");
assertPredicateTrue(like(ATTRIBUTE, "_av_"), "Java");
assertPredicateTrue(like(ATTRIBUTE, "_a__"), "Java");
assertPredicateTrue(like(ATTRIBUTE, "J%v_"), "Java");
assertPredicateTrue(like(ATTRIBUTE, "J%_"), "Java");
assertPredicateFalse(like(ATTRIBUTE, "java"), "Java");
assertPredicateFalse(like(ATTRIBUTE, "j%"), "Java");
assertPredicateFalse(like(ATTRIBUTE, "J_a"), "Java");
assertPredicateFalse(like(ATTRIBUTE, "J_ava"), "Java");
assertPredicateFalse(like(ATTRIBUTE, "J_a_a"), "Java");
assertPredicateFalse(like(ATTRIBUTE, "J_av__"), "Java");
assertPredicateFalse(like(ATTRIBUTE, "J_Va"), "Java");
assertPredicateTrue(like(ATTRIBUTE, "Java World"), "Java World");
assertPredicateTrue(like(ATTRIBUTE, "Java%ld"), "Java World");
assertPredicateTrue(like(ATTRIBUTE, "%World"), "Java World");
assertPredicateTrue(like(ATTRIBUTE, "Java_World"), "Java World");
assertPredicateTrue(like(ATTRIBUTE, "J.-*.*\\%"), "J.-*.*%");
assertPredicateTrue(like(ATTRIBUTE, "J\\_"), "J_");
assertPredicateTrue(like(ATTRIBUTE, "J%"), "Java");
}
@Test
public void testILike() {
assertPredicateFalse(like(ATTRIBUTE, "JavaWorld"), "Java World");
assertPredicateTrue(ilike(ATTRIBUTE, "Java_World"), "java World");
assertPredicateTrue(ilike(ATTRIBUTE, "java%ld"), "Java World");
assertPredicateTrue(ilike(ATTRIBUTE, "%world"), "Java World");
assertPredicateFalse(ilike(ATTRIBUTE, "Java_World"), "gava World");
}
@Test
public void testILike_Id() {
ILikePredicate predicate = (ILikePredicate) ilike(ATTRIBUTE, "Java_World");
assertThat(predicate.getId(), allOf(equalTo(6), equalTo(PredicateDataSerializerHook.ILIKE_PREDICATE)));
}
@Test
public void testIsInstanceOf() {
assertTrue(instanceOf(Long.class).apply(new DummyEntry(1L)));
assertFalse(instanceOf(Long.class).apply(new DummyEntry("Java")));
assertTrue(instanceOf(Number.class).apply(new DummyEntry(4)));
}
@Test
public void testCriteriaAPI() {
Object value = new Employee(12, "abc-123-xvz", 34, true, 10D);
EntryObject e = new PredicateBuilder().getEntryObject();
EntryObject e2 = e.get("age");
Predicate predicate = e2.greaterEqual(29).and(e2.lessEqual(36));
assertTrue(predicate.apply(createEntry("1", value)));
e = new PredicateBuilder().getEntryObject();
assertTrue(e.get("id").equal(12).apply(createEntry("1", value)));
}
@Test(expected = NullPointerException.class)
public void testBetweenNull() {
Predicates.between(ATTRIBUTE, null, null);
}
@Test(expected = NullPointerException.class)
public void testLessThanNull() {
Predicates.lessThan(ATTRIBUTE, null);
}
@Test(expected = NullPointerException.class)
public void testLessEqualNull() {
Predicates.lessEqual(ATTRIBUTE, null);
}
@Test(expected = NullPointerException.class)
public void testGreaterThanNull() {
Predicates.greaterThan(ATTRIBUTE, null);
}
@Test(expected = NullPointerException.class)
public void testGreaterEqualNull() {
Predicates.greaterEqual(ATTRIBUTE, null);
}
@Test(expected = NullPointerException.class)
public void testInNullWithNullArray() {
Predicates.in(ATTRIBUTE, null);
}
@Test
public void testNotEqualsPredicateDoesNotUseIndex() {
Index dummyIndex = new IndexImpl("foo", false, ss, Extractors.empty());
QueryContext mockQueryContext = mock(QueryContext.class);
when(mockQueryContext.getIndex(anyString())).thenReturn(dummyIndex);
NotEqualPredicate p = new NotEqualPredicate("foo", "bar");
boolean indexed = p.isIndexed(mockQueryContext);
assertFalse(indexed);
}
private class DummyEntry extends QueryEntry {
DummyEntry(Comparable attribute) {
super(ss, toData("1"), attribute, Extractors.empty());
}
@Override
public Comparable getAttributeValue(String attributeName) throws QueryException {
return (Comparable) getValue();
}
@Override
public AttributeType getAttributeType(String attributeName) {
return ReflectionHelper.getAttributeType(getValue().getClass());
}
}
private class NullDummyEntry extends QueryableEntry {
private Integer nullField;
private NullDummyEntry() {
}
public Integer getNullField() {
return nullField;
}
public void setNullField(Integer nullField) {
this.nullField = nullField;
}
@Override
public Object getValue() {
return null;
}
@Override
public Object setValue(Object value) {
return null;
}
@Override
public Object getKey() {
return 1;
}
@Override
public Comparable getAttributeValue(String attributeName) throws QueryException {
return null;
}
@Override
public AttributeType getAttributeType(String attributeName) {
return AttributeType.INTEGER;
}
@Override
protected Object getTargetObject(boolean key) {
return null;
}
@Override
public Data getKeyData() {
return null;
}
@Override
public Data getValueData() {
return null;
}
}
private Entry createEntry(final Object key, final Object value) {
return new QueryEntry(ss, toData(key), value, Extractors.empty());
}
private void assertPredicateTrue(Predicate p, Comparable comparable) {
assertTrue(p.apply(new DummyEntry(comparable)));
}
private void assertPredicateFalse(Predicate p, Comparable comparable) {
assertFalse(p.apply(new DummyEntry(comparable)));
}
private void assertTrue_withNullEntry(Predicate p) {
assertTrue(p.apply(new NullDummyEntry()));
}
private void assertFalse_withNullEntry(Predicate p) {
assertFalse(p.apply(new NullDummyEntry()));
}
}