/* * 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; 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.query.SampleObjects.Employee; import com.hazelcast.query.SampleObjects.ObjectWithBigDecimal; import com.hazelcast.query.SampleObjects.ObjectWithBoolean; import com.hazelcast.query.SampleObjects.ObjectWithByte; import com.hazelcast.query.SampleObjects.ObjectWithChar; import com.hazelcast.query.SampleObjects.ObjectWithDate; import com.hazelcast.query.SampleObjects.ObjectWithDouble; import com.hazelcast.query.SampleObjects.ObjectWithFloat; import com.hazelcast.query.SampleObjects.ObjectWithInteger; import com.hazelcast.query.SampleObjects.ObjectWithLong; import com.hazelcast.query.SampleObjects.ObjectWithShort; import com.hazelcast.query.SampleObjects.ObjectWithSqlDate; import com.hazelcast.query.SampleObjects.ObjectWithSqlTimestamp; import com.hazelcast.query.SampleObjects.ObjectWithUUID; import com.hazelcast.query.impl.DateHelperTest; import com.hazelcast.query.impl.QueryEntry; import com.hazelcast.query.impl.getters.Extractors; import com.hazelcast.query.impl.predicates.AndPredicate; import com.hazelcast.query.impl.predicates.GreaterLessPredicate; import com.hazelcast.query.impl.predicates.OrPredicate; import com.hazelcast.query.impl.predicates.RegexPredicate; import com.hazelcast.test.HazelcastSerialClassRunner; import com.hazelcast.test.TestHazelcastInstanceFactory; 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.math.BigInteger; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import static com.hazelcast.instance.TestUtil.toData; import static com.hazelcast.test.HazelcastTestSupport.assertInstanceOf; import static com.hazelcast.test.HazelcastTestSupport.randomString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @RunWith(HazelcastSerialClassRunner.class) @Category(QuickTest.class) public class SqlPredicateTest { private final InternalSerializationService serializationService = new DefaultSerializationServiceBuilder().build(); // these are used to test compound predicates flattening TruePredicate leftOfOr = new TruePredicate(); TruePredicate rightOfOr = new TruePredicate(); TruePredicate leftOfAnd = new TruePredicate(); TruePredicate rightOfAnd = new TruePredicate(); static final String[] TEST_MATCHING_SQL_PREDICATES = new String[]{ "name = 'Joe' and age = 25 and (city = 'austin' or city = 'AUSTIN')", "name = 'Joe' or city = 'Athens'", "(name = 'Jane' or name = 'Joe' or city = 'AUSTIN') and age = 25", "(name = 'Jane' or name = 'Joe' or city = 'AUSTIN') and age = 25 and salary = 0", "(name = 'Jane' or name = 'Joe') and age = 25 and salary = 0 or age = 24", "name = 'Jane' or age = 25 and name = 'Joe'", // correct precedence is "name = 'Jane' or (age = 25 and name = 'Joe') "age = 35 or age = 24 or age = 31 or (name = 'Joe' and age = 25)", }; static final String[] TEST_NOT_MATCHING_SQL_PREDICATES = new String[]{ "name = 'Joe' and age = 21 and (city = 'austin' or city = 'ATHENS')", "name = 'Jane' or city = 'Athens'", "(name = 'Jane' or name = 'Catie' or city = 'San Jose') and age = 25", "(name = 'Joe' or name = 'Catie' or city = 'San Jose') and age = 21", "(name = 'Jane' or name = 'Joe' or city = 'AUSTIN') and age = 25 and salary = 10", "(name = 'Jane' or name = 'Catie' or city = 'San Jose') and age = 25 and salary = 10", "(name = 'Jane' or name = 'Joe') and age = 25 and salary = 13 or age = 24", "name = 'Jane' or age = 25 and name = 'Catie'", "age = 35 or age = 24 or age = 31 or (name = 'Joe' and age = 27)", }; @Test public void testSqlPredicates() { Employee employee = new Employee("Joe", "AUSTIN", 25, true, 0); for (String s : TEST_MATCHING_SQL_PREDICATES) { assertSqlMatching(s, employee); } for (String s : TEST_NOT_MATCHING_SQL_PREDICATES) { assertSqlNotMatching(s, employee); } } public static class Record { private String str1, str2, str3; public Record(String str1, String str2, String str3) { this.str1 = str1; this.str2 = str2; this.str3 = str3; } public String getStr1() { return str1; } public void setStr1(String str1) { this.str1 = str1; } public String getStr2() { return str2; } public void setStr2(String str2) { this.str2 = str2; } public String getStr3() { return str3; } public void setStr3(String str3) { this.str3 = str3; } } // ZD issue 1950 @Test public void testRecordPredicate() { Record record = new Record("ONE", "TWO", "THREE"); SqlPredicate predicate = new SqlPredicate("str1 = 'ONE' AND str2 = 'TWO' AND (str3 = 'THREE' OR str3 = 'three')"); Map.Entry entry = createEntry("1", record); assertTrue(predicate.apply(entry)); } @Test public void testEqualsWhenSqlMatches() { SqlPredicate sql1 = new SqlPredicate("foo='bar'"); SqlPredicate sql2 = new SqlPredicate("foo='bar'"); assertEquals(sql1, sql2); } @Test public void testEqualsWhenSqlDifferent() { SqlPredicate sql1 = new SqlPredicate("foo='bar'"); SqlPredicate sql2 = new SqlPredicate("foo='baz'"); assertNotEquals(sql1, sql2); } @Test public void testEqualsNull() { SqlPredicate sql = new SqlPredicate("foo='bar'"); assertNotEquals(sql, null); } @Test public void testEqualsSameObject() { SqlPredicate sql = new SqlPredicate("foo='bar'"); assertEquals(sql, sql); } @Test public void testHashCode() { SqlPredicate sql = new SqlPredicate("foo='bar'"); assertEquals("foo='bar'".hashCode(), sql.hashCode()); } @Test public void testSql_withEnum() { Employee value = createValue(); value.setState(SampleObjects.State.STATE2); Employee nullNameValue = createValue(null); assertSqlMatching("state == TestUtil.State.STATE2", value); assertSqlMatching("state == " + SampleObjects.State.STATE2, value); assertSqlNotMatching("state == TestUtil.State.STATE1", value); assertSqlNotMatching("state == TestUtil.State.STATE1", nullNameValue); assertSqlMatching("state == NULL", nullNameValue); } @Test public void testSql_withDate() { Date date = new Date(); ObjectWithDate value = new ObjectWithDate(date); SimpleDateFormat format = new SimpleDateFormat(DateHelperTest.DATE_FORMAT, Locale.US); assertSqlMatching("attribute > '" + format.format(new Date(0)) + "'", value); assertSqlMatching("attribute >= '" + format.format(new Date(0)) + "'", value); assertSqlMatching("attribute < '" + format.format(new Date(date.getTime() + 1000)) + "'", value); assertSqlMatching("attribute <= '" + format.format(new Date(date.getTime() + 1000)) + "'", value); } @Test public void testSql_withSqlDate() { java.sql.Date date = new java.sql.Date(System.currentTimeMillis()); ObjectWithSqlDate value = new ObjectWithSqlDate(date); SimpleDateFormat format = new SimpleDateFormat(DateHelperTest.SQL_DATE_FORMAT, Locale.US); assertSqlMatching("attribute > '" + format.format(new java.sql.Date(0)) + "'", value); assertSqlMatching("attribute >= '" + format.format(new java.sql.Date(0)) + "'", value); assertSqlMatching("attribute < '" + format.format(new java.sql.Date(date.getTime() + TimeUnit.DAYS.toMillis(2))) + "'", value); assertSqlMatching("attribute <= '" + format.format(new java.sql.Date(date.getTime() + TimeUnit.DAYS.toMillis(2))) + "'", value); } @Test public void testSql_withTimestamp() { Timestamp timestamp = new Timestamp(System.currentTimeMillis()); ObjectWithSqlTimestamp value = new ObjectWithSqlTimestamp(timestamp); SimpleDateFormat format = new SimpleDateFormat(DateHelperTest.TIMESTAMP_FORMAT, Locale.US); assertSqlMatching("attribute > '" + format.format(new Timestamp(0)) + "'", value); assertSqlMatching("attribute >= '" + format.format(new Timestamp(0)) + "'", value); assertSqlMatching("attribute < '" + format.format(new Timestamp(timestamp.getTime() + 1000)) + "'", value); assertSqlMatching("attribute <= '" + format.format(new Timestamp(timestamp.getTime() + 1000)) + "'", value); } @Test public void testSql_withBigDecimal() { ObjectWithBigDecimal value = new ObjectWithBigDecimal(new BigDecimal("1.23E3")); assertSqlMatching("attribute > '" + new BigDecimal("1.23E2") + "'", value); assertSqlMatching("attribute >= '" + new BigDecimal("1.23E3") + "'", value); assertSqlNotMatching("attribute = '" + new BigDecimal("1.23") + "'", value); assertSqlMatching("attribute = '1.23E3'", value); assertSqlMatching("attribute = 1.23E3", value); assertSqlNotMatching("attribute = 1.23", value); } @Test public void testSql_withBigInteger() { SampleObjects.ObjectWithBigInteger value = new SampleObjects.ObjectWithBigInteger(new BigInteger("123")); assertSqlMatching("attribute > '" + new BigInteger("122") + "'", value); assertSqlMatching("attribute >= '" + new BigInteger("123") + "'", value); assertSqlMatching("attribute = '" + new BigInteger("123") + "'", value); assertSqlNotMatching("attribute = '" + new BigInteger("122") + "'", value); assertSqlMatching("attribute < '" + new BigInteger("124") + "'", value); assertSqlMatching("attribute <= '" + new BigInteger("123") + "'", value); assertSqlMatching("attribute = 123", value); assertSqlMatching("attribute = '123'", value); assertSqlMatching("attribute != 124", value); assertSqlMatching("attribute <> 124", value); assertSqlNotMatching("attribute = 124", value); assertSqlMatching("attribute between 122 and 124", value); assertSqlMatching("attribute in (122, 123, 124)", value); } @Test public void testSql_withString() { Employee value = createValue(); Employee nullNameValue = new Employee(null, 34, true, 10D); assertSqlNotMatching("name = 'null'", nullNameValue); assertSqlMatching("name = null", nullNameValue); assertSqlMatching("name = NULL", nullNameValue); assertSqlMatching("name != null", value); assertSqlMatching("name != NULL", value); assertSqlMatching("name <> null", value); assertSqlMatching("name <> NULL", value); assertSqlMatching(" (name LIKE 'abc-%') AND (age <= " + 40 + ")", value); assertSqlMatching(" (name REGEX 'abc-.*') AND (age <= " + 40 + ")", value); assertSqlMatching("name='abc-123-xvz'", value); assertSqlMatching("name='abc 123-xvz'", createValue("abc 123-xvz")); assertSqlMatching("name='abc 123-xvz+(123)'", createValue("abc 123-xvz+(123)")); assertSqlNotMatching("name='abc 123-xvz+(123)'", createValue("abc123-xvz+(123)")); assertSqlMatching("name LIKE 'abc-%'", createValue("abc-123")); assertSqlMatching("name REGEX '^\\w{3}-\\d{3}-\\w{3}$'", value); assertSqlNotMatching("name REGEX '^[^\\w]{3}-\\d{3}-\\w{3}$'", value); assertSqlMatching(" (name ILIKE 'ABC-%') AND (age <= " + 40 + ")", value); } @Test public void testSql_withInteger() { ObjectWithInteger value = new ObjectWithInteger(34); assertSqlMatching("(attribute >= 20) AND (attribute <= 40)", value); assertSqlMatching("(attribute >= 20 ) AND (attribute <= 34)", value); assertSqlMatching("(attribute >= 34) AND (attribute <= 35)", value); assertSqlMatching("attribute IN (34, 35)", value); assertSqlNotMatching("attribute IN (33,35)", value); assertSqlNotMatching("attribute = 33", value); assertSqlMatching("attribute = 34", value); assertSqlMatching("attribute > 5", value); assertSqlMatching("attribute = -33", new ObjectWithInteger(-33)); } @Test public void testSql_withLong() { ObjectWithLong value = new ObjectWithLong(34L); assertSqlMatching("(attribute >= 20) AND (attribute <= 40)", value); assertSqlMatching("(attribute >= 20 ) AND (attribute <= 34)", value); assertSqlMatching("(attribute >= 34) AND (attribute <= 35)", value); assertSqlMatching("attribute IN (34, 35)", value); assertSqlNotMatching("attribute IN (33,35)", value); assertSqlNotMatching("attribute = 33", value); assertSqlMatching("attribute = 34", value); assertSqlMatching("attribute > 5", value); assertSqlMatching("attribute = -33", new ObjectWithLong(-33)); } @Test public void testSql_withDouble() { ObjectWithDouble value = new ObjectWithDouble(10.001D); assertSqlMatching("attribute > 5", value); assertSqlMatching("attribute > 5 and attribute < 11", value); assertSqlNotMatching("attribute > 15 or attribute < 10", value); assertSqlMatching("attribute between 9.99 and 10.01", value); assertSqlMatching("attribute between 5 and 15", value); } @Test public void testSql_withFloat() { ObjectWithFloat value = new ObjectWithFloat(10.001f); assertSqlMatching("attribute > 5", value); assertSqlMatching("attribute > 5 and attribute < 11", value); assertSqlNotMatching("attribute > 15 or attribute < 10", value); assertSqlMatching("attribute between 9.99 and 10.01", value); assertSqlMatching("attribute between 5 and 15", value); } @Test public void testSql_withShort() { ObjectWithShort value = new ObjectWithShort((short) 10); assertSqlMatching("attribute = 10", value); assertSqlNotMatching("attribute = 11", value); assertSqlMatching("attribute >= 10", value); assertSqlMatching("attribute <= 10", value); assertSqlMatching("attribute > 5", value); assertSqlMatching("attribute > 5 and attribute < 11", value); assertSqlNotMatching("attribute > 15 or attribute < 10", value); assertSqlMatching("attribute between 5 and 15", value); assertSqlMatching("attribute in (5, 10, 15)", value); assertSqlNotMatching("attribute in (5, 11, 15)", value); } @Test public void testSql_withByte() { ObjectWithByte value = new ObjectWithByte((byte) 10); assertSqlMatching("attribute = 10", value); assertSqlNotMatching("attribute = 11", value); assertSqlMatching("attribute >= 10", value); assertSqlMatching("attribute <= 10", value); assertSqlMatching("attribute > 5", value); assertSqlMatching("attribute > 5 and attribute < 11", value); assertSqlNotMatching("attribute > 15 or attribute < 10", value); assertSqlMatching("attribute between 5 and 15", value); assertSqlMatching("attribute in (5, 10, 15)", value); assertSqlNotMatching("attribute in (5, 11, 15)", value); } @Test public void testSql_withChar() { ObjectWithChar value = new ObjectWithChar('%'); assertSqlMatching("attribute = '%'", value); assertSqlNotMatching("attribute = '$'", value); assertSqlMatching("attribute in ('A', '#', '%')", value); assertSqlNotMatching("attribute in ('A', '#', '&')", value); } @Test public void testSql_withBoolean() { ObjectWithBoolean value = new ObjectWithBoolean(true); assertSqlMatching("attribute", value); assertSqlMatching("attribute = true", value); assertSqlNotMatching("attribute = false", value); assertSqlNotMatching("not attribute", value); } @Test public void testSql_withUUID() { UUID uuid = UUID.randomUUID(); ObjectWithUUID value = new ObjectWithUUID(uuid); assertSqlMatching("attribute = '" + uuid.toString() + "'", value); assertSqlNotMatching("attribute = '" + UUID.randomUUID().toString() + "'", value); } @Test public void testSqlPredicate() { assertEquals("name IN (name0,name2)", sql("name in ('name0', 'name2')")); assertEquals("(name LIKE 'joe' AND id=5)", sql("name like 'joe' AND id = 5")); assertEquals("(name REGEX '\\w*' AND id=5)", sql("name regex '\\w*' AND id = 5")); assertEquals("active=true", sql("active")); assertEquals("(active=true AND name=abc xyz 123)", sql("active AND name='abc xyz 123'")); assertEquals("(name LIKE 'abc-xyz+(123)' AND name=abc xyz 123)", sql("name like 'abc-xyz+(123)' AND name='abc xyz 123'")); assertEquals("(name REGEX '\\w{3}-\\w{3}+\\(\\d{3}\\)' AND name=abc xyz 123)", sql("name regex '\\w{3}-\\w{3}+\\(\\d{3}\\)' AND name='abc xyz 123'")); assertEquals("(active=true AND age>4)", sql("active and age > 4")); assertEquals("(active=true AND age>4)", sql("active and age>4")); assertEquals("(active=false AND age<=4)", sql("active=false AND age<=4")); assertEquals("(active=false AND age<=4)", sql("active= false and age <= 4")); assertEquals("(active=false AND age>=4)", sql("active=false AND (age>=4)")); assertEquals("(active=false OR age>=4)", sql("active =false or (age>= 4)")); assertEquals("name LIKE 'J%'", sql("name like 'J%'")); assertEquals("name REGEX 'J.*'", sql("name regex 'J.*'")); assertEquals("NOT(name LIKE 'J%')", sql("name not like 'J%'")); assertEquals("NOT(name REGEX 'J.*')", sql("name not regex 'J.*'")); assertEquals("(active=false OR name LIKE 'J%')", sql("active =false or name like 'J%'")); assertEquals("(active=false OR name LIKE 'Java World')", sql("active =false or name like 'Java World'")); assertEquals("(active=false OR name LIKE 'Java W% Again')", sql("active =false or name like 'Java W% Again'")); assertEquals("(active=false OR name REGEX 'J.*')", sql("active =false or name regex 'J.*'")); assertEquals("(active=false OR name REGEX 'Java World')", sql("active =false or name regex 'Java World'")); assertEquals("(active=false OR name REGEX 'Java W.* Again')", sql("active =false or name regex 'Java W.* Again'")); assertEquals("i<=-1", sql("i<= -1")); assertEquals("age IN (-1)", sql("age in (-1)")); assertEquals("age IN (10,15)", sql("age in (10, 15)")); assertEquals("NOT(age IN (10,15))", sql("age not in ( 10 , 15 )")); assertEquals("(active=true AND age BETWEEN 10 AND 15)", sql("active and age between 10 and 15")); assertEquals("(age IN (10,15) AND active=true)", sql("age IN (10, 15) and active")); assertEquals("(active=true OR age IN (10,15))", sql("active or (age in ( 10,15))")); assertEquals("(age>10 AND (active=true OR age IN (10,15)))", sql("age>10 AND (active or (age IN (10, 15 )))")); assertEquals("(age<=10 AND (active=true OR NOT(age IN (10,15))))", sql("age<=10 AND (active or (age not in (10 , 15)))")); assertEquals("age BETWEEN 10 AND 15", sql("age between 10 and 15")); assertEquals("NOT(age BETWEEN 10 AND 15)", sql("age not between 10 and 15")); assertEquals("(active=true AND age BETWEEN 10 AND 15)", sql("active and age between 10 and 15")); assertEquals("(age BETWEEN 10 AND 15 AND active=true)", sql("age between 10 and 15 and active")); assertEquals("(active=true OR age BETWEEN 10 AND 15)", sql("active or (age between 10 and 15)")); assertEquals("(age>10 AND (active=true OR age BETWEEN 10 AND 15))", sql("age>10 AND (active or (age between 10 and 15))")); assertEquals("(age<=10 AND (active=true OR NOT(age BETWEEN 10 AND 15)))", sql("age<=10 AND (active or (age not between 10 and 15))")); assertEquals("name ILIKE 'J%'", sql("name ilike 'J%'")); // issue #594 assertEquals("(name IN (name0,name2) AND age IN (2,5,8))", sql("name in('name0', 'name2') and age IN ( 2, 5 ,8)")); } @Test public void testSqlPredicateEscape() { assertEquals("(active=true AND name=abc x'yz 1'23)", sql("active AND name='abc x''yz 1''23'")); assertEquals("(active=true AND name=)", sql("active AND name=''")); } @Test(expected = RuntimeException.class) public void testInvalidSqlPredicate1() { new SqlPredicate("invalid sql"); } @Test(expected = RuntimeException.class) public void testInvalidSqlPredicate2() { new SqlPredicate(""); } // This test used to fail with a stack overflow exception, due to the way SQL predicates were // nested, missing an opportunity to flatten. // https://github.com/hazelcast/hazelcast/issues/7583 @Test public void testLongPredicate() { TestHazelcastInstanceFactory factory = new TestHazelcastInstanceFactory(1); HazelcastInstance hazelcastInstance = factory.newHazelcastInstance(); IMap<Integer, Integer> map = hazelcastInstance.getMap(randomString()); for (int i = 0; i < 8000; i++) { map.put(i, i); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < 8000; i++) { sb.append("intValue() == ").append(i).append(" or "); } sb.append(" intValue() == -1"); SqlPredicate predicate = new SqlPredicate(sb.toString()); // all entries must match Set<Map.Entry<Integer, Integer>> entries = map.entrySet(predicate); assertEquals(map.size(), entries.size()); factory.terminateAll(); } @Test public void testOr_whenBothPredicatesOr() { OrPredicate predicate1 = new OrPredicate(new SqlPredicate("a == 1"), new SqlPredicate("a == 2")); OrPredicate predicate2 = new OrPredicate(new SqlPredicate("a == 3")); OrPredicate concatenatedOr = SqlPredicate.flattenCompound(predicate1, predicate2, OrPredicate.class); assertEquals(3, concatenatedOr.getPredicates().length); } @Test public void testOr_whenLeftPredicateOr() { OrPredicate predicate1 = new OrPredicate(new SqlPredicate("a == 1"), new SqlPredicate("a == 2")); TruePredicate predicate2 = new TruePredicate(); OrPredicate concatenatedOr = SqlPredicate.flattenCompound(predicate1, predicate2, OrPredicate.class); assertEquals(3, concatenatedOr.getPredicates().length); assertInstanceOf(SqlPredicate.class, concatenatedOr.getPredicates()[0]); assertInstanceOf(SqlPredicate.class, concatenatedOr.getPredicates()[1]); assertSame(predicate2, concatenatedOr.getPredicates()[2]); } @Test public void testOr_whenRightPredicateOr() { TruePredicate predicate1 = new TruePredicate(); OrPredicate predicate2 = new OrPredicate(new SqlPredicate("a == 1"), new SqlPredicate("a == 2")); OrPredicate concatenatedOr = SqlPredicate.flattenCompound(predicate1, predicate2, OrPredicate.class); assertEquals(3, concatenatedOr.getPredicates().length); assertSame(predicate1, concatenatedOr.getPredicates()[0]); assertInstanceOf(SqlPredicate.class, concatenatedOr.getPredicates()[1]); assertInstanceOf(SqlPredicate.class, concatenatedOr.getPredicates()[2]); } @Test public void testOr_whenNoPredicateOr() { TruePredicate predicate1 = new TruePredicate(); TruePredicate predicate2 = new TruePredicate(); OrPredicate concatenatedOr = SqlPredicate.flattenCompound(predicate1, predicate2, OrPredicate.class); assertEquals(2, concatenatedOr.getPredicates().length); assertSame(predicate1, concatenatedOr.getPredicates()[0]); assertSame(predicate2, concatenatedOr.getPredicates()[1]); } @Test public void testAnd_whenBothPredicatesAnd() { AndPredicate predicate1 = new AndPredicate(new SqlPredicate("a == 1"), new SqlPredicate("a == 2")); AndPredicate predicate2 = new AndPredicate(new SqlPredicate("a == 3")); AndPredicate concatenatedOr = SqlPredicate.flattenCompound(predicate1, predicate2, AndPredicate.class); assertEquals(3, concatenatedOr.getPredicates().length); } @Test public void testAnd_whenLeftPredicateAnd() { AndPredicate predicate1 = new AndPredicate(new SqlPredicate("a == 1"), new SqlPredicate("a == 2")); TruePredicate predicate2 = new TruePredicate(); AndPredicate concatenatedOr = SqlPredicate.flattenCompound(predicate1, predicate2, AndPredicate.class); assertEquals(3, concatenatedOr.getPredicates().length); assertInstanceOf(SqlPredicate.class, concatenatedOr.getPredicates()[0]); assertInstanceOf(SqlPredicate.class, concatenatedOr.getPredicates()[1]); assertSame(predicate2, concatenatedOr.getPredicates()[2]); } @Test public void testAnd_whenRightPredicateAnd() { TruePredicate predicate1 = new TruePredicate(); AndPredicate predicate2 = new AndPredicate(new SqlPredicate("a == 1"), new SqlPredicate("a == 2")); AndPredicate concatenatedOr = SqlPredicate.flattenCompound(predicate1, predicate2, AndPredicate.class); assertEquals(3, concatenatedOr.getPredicates().length); assertSame(predicate1, concatenatedOr.getPredicates()[0]); assertInstanceOf(SqlPredicate.class, concatenatedOr.getPredicates()[1]); assertInstanceOf(SqlPredicate.class, concatenatedOr.getPredicates()[2]); } @Test public void testAnd_whenNoPredicateAnd() { TruePredicate predicate1 = new TruePredicate(); TruePredicate predicate2 = new TruePredicate(); AndPredicate concatenatedOr = SqlPredicate.flattenCompound(predicate1, predicate2, AndPredicate.class); assertEquals(2, concatenatedOr.getPredicates().length); assertSame(predicate1, concatenatedOr.getPredicates()[0]); assertSame(predicate2, concatenatedOr.getPredicates()[1]); } // (AND (OR A B) (AND C D)) is flattened to (AND (OR A B) C D) @Test public void testFlattenAnd_withOrAndPredicates() { OrPredicate orPredicate = new OrPredicate(leftOfOr, rightOfOr); AndPredicate andPredicate = new AndPredicate(leftOfAnd, rightOfAnd); AndPredicate flattenedCompoundAnd = SqlPredicate.flattenCompound(orPredicate, andPredicate, AndPredicate.class); assertSame(orPredicate, flattenedCompoundAnd.getPredicates()[0]); assertSame(leftOfAnd, flattenedCompoundAnd.getPredicates()[1]); assertSame(rightOfAnd, flattenedCompoundAnd.getPredicates()[2]); } // (AND (AND A B) (OR C D)) is flattened to (AND A B (OR C D)) @Test public void testFlattenAnd_withAndORPredicates() { OrPredicate orPredicate = new OrPredicate(leftOfOr, rightOfOr); AndPredicate andPredicate = new AndPredicate(leftOfAnd, rightOfAnd); AndPredicate flattenedCompoundAnd = SqlPredicate.flattenCompound(andPredicate, orPredicate, AndPredicate.class); assertSame(leftOfAnd, flattenedCompoundAnd.getPredicates()[0]); assertSame(rightOfAnd, flattenedCompoundAnd.getPredicates()[1]); assertSame(orPredicate, flattenedCompoundAnd.getPredicates()[2]); } // (OR (OR A B) (AND C D)) is flattened to (OR A B (AND C D)) @Test public void testFlattenOr_withOrAndPredicates() { OrPredicate orPredicate = new OrPredicate(leftOfOr, rightOfOr); AndPredicate andPredicate = new AndPredicate(leftOfAnd, rightOfAnd); OrPredicate flattenedCompoundOr = SqlPredicate.flattenCompound(orPredicate, andPredicate, OrPredicate.class); assertSame(leftOfOr, flattenedCompoundOr.getPredicates()[0]); assertSame(rightOfOr, flattenedCompoundOr.getPredicates()[1]); assertSame(andPredicate, flattenedCompoundOr.getPredicates()[2]); } // (OR (AND A B) (OR C D)) is flattened to (OR (AND A B) C D) @Test public void testFlattenOr_withAndOrPredicates() { OrPredicate orPredicate = new OrPredicate(leftOfOr, rightOfOr); AndPredicate andPredicate = new AndPredicate(leftOfAnd, rightOfAnd); OrPredicate flattenedCompoundOr = SqlPredicate.flattenCompound(andPredicate, orPredicate, OrPredicate.class); assertSame(andPredicate, flattenedCompoundOr.getPredicates()[0]); assertSame(leftOfOr, flattenedCompoundOr.getPredicates()[1]); assertSame(rightOfOr, flattenedCompoundOr.getPredicates()[2]); } // (OR (AND A B) (AND C D)) is not flattened @Test public void testFlattenOr_withTwoAndPredicates() { AndPredicate andLeft = new AndPredicate(leftOfOr, rightOfOr); AndPredicate andRight = new AndPredicate(leftOfAnd, rightOfAnd); OrPredicate flattenedCompoundOr = SqlPredicate.flattenCompound(andLeft, andRight, OrPredicate.class); assertSame(andLeft, flattenedCompoundOr.getPredicates()[0]); assertSame(andRight, flattenedCompoundOr.getPredicates()[1]); } // (AND (OR A B) (OR C D)) is not flattened @Test public void testFlattenAnd_withTwoOrPredicates() { OrPredicate orLeft = new OrPredicate(leftOfOr, rightOfOr); OrPredicate orRight = new OrPredicate(leftOfAnd, rightOfAnd); AndPredicate flattenedCompoundOr = SqlPredicate.flattenCompound(orLeft, orRight, AndPredicate.class); assertSame(orLeft, flattenedCompoundOr.getPredicates()[0]); assertSame(orRight, flattenedCompoundOr.getPredicates()[1]); } @Test // http://stackoverflow.com/questions/37382505/hazelcast-imap-valuespredicate-miss-data public void testAndWithRegex_stackOverflowIssue() { SqlPredicate sqlPredicate = new SqlPredicate("nextExecuteTime < 1463975296703 AND autoIncrementId REGEX '.*[5,6,7,8,9]$'"); Predicate predicate = sqlPredicate.predicate; AndPredicate andPredicate = (AndPredicate) predicate; assertEquals(GreaterLessPredicate.class, andPredicate.getPredicates()[0].getClass()); assertEquals(RegexPredicate.class, andPredicate.getPredicates()[1].getClass()); } private String sql(String sql) { return new SqlPredicate(sql).toString(); } private Map.Entry createEntry(final Object key, final Object value) { return new QueryEntry(serializationService, toData(key), value, Extractors.empty()); } private void assertSqlMatching(String s, Object value) { final SqlPredicate predicate = new SqlPredicate(s); final Map.Entry entry = createEntry("1", value); assertTrue("SQL predicate \"" + s + "\" should match but did not match the value.", predicate.apply(entry)); } private void assertSqlNotMatching(String s, Object value) { final SqlPredicate predicate = new SqlPredicate(s); final Map.Entry entry = createEntry("1", value); assertFalse("SQL predicate \"" + s + "\" should not match but did match the value.", predicate.apply(entry)); } private Employee createValue() { return new Employee("abc-123-xvz", 34, true, 10D); } private Employee createValue(String name) { return new Employee(name, 34, true, 10D); } }