/*
 * 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.geode.cache.query.functional;

import static org.apache.geode.cache.Region.SEPARATOR;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.query.CacheUtils;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.IndexType;
import org.apache.geode.cache.query.Query;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.cache.query.data.Portfolio;
import org.apache.geode.cache.query.data.Position;
import org.apache.geode.cache.query.internal.QueryObserver;
import org.apache.geode.cache.query.internal.QueryObserverAdapter;
import org.apache.geode.cache.query.internal.QueryObserverHolder;
import org.apache.geode.cache.query.internal.ResultsBag;
import org.apache.geode.cache.query.internal.ResultsCollectionWrapper;
import org.apache.geode.cache.query.internal.index.IndexManager;
import org.apache.geode.cache.query.internal.index.IndexManager.TestHook;
import org.apache.geode.cache.query.internal.types.ObjectTypeImpl;
import org.apache.geode.test.dunit.ThreadUtils;
import org.apache.geode.test.junit.categories.OQLQueryTest;

@Category({OQLQueryTest.class})
public class LikePredicateJUnitTest {

  @Before
  public void setUp() throws Exception {
    CacheUtils.startCache();
  }

  @After
  public void tearDown() throws Exception {
    CacheUtils.closeCache();
  }

  @Test
  public void testLikePercentageTerminated_1_withoutBindParam() throws Exception {
    likePercentageTerminated_1(false);
  }

  @Test
  public void testLikePercentageTerminated_1_withBindParam() throws Exception {
    likePercentageTerminated_1(true);
  }

  /**
   * Tests simple % terminated pattern with atleast one preceding character
   */
  private void likePercentageTerminated_1(boolean useBindParam) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    char ch = 'd';
    String base = "abc";
    for (int i = 1; i < 6; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    base = "abd";
    ch = 'd';
    for (int i = 6; i < 11; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    QueryService qs = cache.getQueryService();

    String predicate = "";
    if (useBindParam) {
      predicate = "$1";
    } else {
      predicate = " 'abc%'";
    }

    Query q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);
    SelectResults results;
    if (useBindParam) {
      results = (SelectResults) q.execute(new Object[] {"abc%"});
    } else {
      results = (SelectResults) q.execute();
    }
    ResultsBag bag = new ResultsBag(null);
    for (int i = 1; i < 6; ++i) {
      bag.add(region.get(i));
    }
    SelectResults expectedResults =
        new ResultsCollectionWrapper(new ObjectTypeImpl(Object.class), bag.asSet());
    SelectResults[][] rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);

    // Create Index
    qs.createIndex("status", IndexType.FUNCTIONAL, "ps.status", SEPARATOR + "pos ps");
    QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
      private boolean indexCalled = false;

      @Override
      public void afterIndexLookup(Collection results) {
        indexCalled = true;
      }

      @Override
      public void endQuery() {
        assertTrue(indexCalled);
      }

    });
    if (useBindParam) {
      results = (SelectResults) q.execute(new Object[] {"abc%"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs[0][0] = results;
    rs[0][1] = expectedResults;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    QueryObserverHolder.setInstance(old);
  }

  @Test
  public void testLikePercentageTerminated_2_withoutParams() throws Exception {
    likePercentageTerminated_2(false);
  }

  @Test
  public void testLikePercentageTerminated_2_withParams() throws Exception {
    likePercentageTerminated_2(true);
  }

  /**
   * Tests a pattern which just contains a single % indicating all match
   */
  private void likePercentageTerminated_2(boolean useBindParam) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    char ch = 'd';
    String base = "abc";
    for (int i = 1; i < 6; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    base = "abd";
    ch = 'd';
    for (int i = 6; i < 11; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    QueryService qs = cache.getQueryService();

    String predicate = "";
    if (useBindParam) {
      predicate = "$1";
    } else {
      predicate = " '%'";
    }

    Query q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);
    SelectResults results;
    if (useBindParam) {
      results = (SelectResults) q.execute(new Object[] {"%"});
    } else {
      results = (SelectResults) q.execute();
    }
    ResultsBag bag = new ResultsBag(null);
    for (int i = 1; i < 11; ++i) {
      bag.add(region.get(i));
    }
    SelectResults expectedResults =
        new ResultsCollectionWrapper(new ObjectTypeImpl(Object.class), bag.asSet());
    SelectResults[][] rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);

    // Create Index
    qs.createIndex("status", IndexType.FUNCTIONAL, "ps.status", SEPARATOR + "pos ps");
    QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
      private boolean indexCalled = false;

      @Override
      public void afterIndexLookup(Collection results) {
        indexCalled = true;
      }

      @Override
      public void endQuery() {
        assertTrue(indexCalled);
      }

    });
    if (useBindParam) {
      results = (SelectResults) q.execute(new Object[] {"%"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs[0][0] = results;
    rs[0][1] = expectedResults;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    QueryObserverHolder.setInstance(old);
  }

  @Test
  public void testLikePercentageTerminated_3_withoutBindPrms() throws Exception {
    likePercentageTerminated_3(false);
  }

  @Test
  public void testLikePercentageTerminated_3_withBindPrms() throws Exception {
    likePercentageTerminated_3(true);
  }

  /**
   * Tests a simple % terminated like predicate with an OR condition
   */
  private void likePercentageTerminated_3(boolean useBindPrm) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    char ch = 'd';
    String base = "abc";
    for (int i = 1; i < 6; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    base = "abd";
    ch = 'd';
    for (int i = 6; i < 11; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    QueryService qs = cache.getQueryService();

    String predicate = "";
    if (useBindPrm) {
      predicate = "$1";
    } else {
      predicate = " 'abc%'";
    }
    Query q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate
            + " OR ps.ID > 6");
    SelectResults results;
    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"abc%"});
    } else {
      results = (SelectResults) q.execute();
    }
    ResultsBag bag = new ResultsBag(null);
    for (int i = 1; i < 11; ++i) {
      if (i != 6) {
        bag.add(region.get(i));
      }
    }
    SelectResults expectedResults =
        new ResultsCollectionWrapper(new ObjectTypeImpl(Object.class), bag.asSet());
    SelectResults[][] rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);

    // Create Index
    qs.createIndex("status", IndexType.FUNCTIONAL, "ps.status", SEPARATOR + "pos ps");

    QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
      private boolean indexCalled = false;

      @Override
      public void afterIndexLookup(Collection results) {
        indexCalled = true;
      }

      @Override
      public void endQuery() {
        assertFalse(indexCalled);
      }

    });
    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"abc%"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs[0][0] = results;
    rs[0][1] = expectedResults;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    qs.createIndex("id", IndexType.FUNCTIONAL, "ps.ID", SEPARATOR + "pos ps");
    QueryObserverHolder.setInstance(new QueryObserverAdapter() {
      private final boolean[] indexCalled = new boolean[] {false, false};

      private int i = 0;

      @Override
      public void afterIndexLookup(Collection results) {
        indexCalled[i++] = true;
      }

      @Override
      public void endQuery() {
        for (final boolean b : indexCalled) {
          assertTrue(b);
        }
      }

    });

    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"abc%"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs[0][0] = results;
    rs[0][1] = expectedResults;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    QueryObserverHolder.setInstance(old);
  }

  @Test
  public void testLikePercentageTerminated_4_withoutBindPrms() throws Exception {
    likePercentageTerminated_4(false);
  }

  @Test
  public void testLikePercentageTerminated_4_withBindPrms() throws Exception {
    likePercentageTerminated_4(true);
  }

  /**
   * Tests a simple % terminated like predicate with an AND condition
   */
  private void likePercentageTerminated_4(boolean useBindPrm) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    String base = "abc";
    String pattern = base;
    // so we will get string like abcdcdcdcdcdc
    for (int i = 1; i < 200; ++i) {
      Portfolio pf = new Portfolio(i);
      pattern += "dc";
      pf.status = pattern;
      region.put(i, pf);
    }

    base = "abd";
    pattern = base;
    // so we will get string like abddcdcdcd
    for (int i = 201; i < 400; ++i) {
      Portfolio pf = new Portfolio(i);
      pattern += "dc";
      pf.status = pattern;

      region.put(i, pf);
    }

    QueryService qs = cache.getQueryService();

    String predicate = "";
    if (useBindPrm) {
      predicate = "$1";
    } else {
      predicate = " 'abc%'";
    }
    Query q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate
            + " AND ps.ID > 2 AND ps.ID < 150");
    SelectResults results;
    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"abc%"});
    } else {
      results = (SelectResults) q.execute();
    }
    ResultsBag bag = new ResultsBag(null);
    for (int i = 3; i < 150; ++i) {
      bag.add(region.get(i));
    }
    SelectResults expectedResults =
        new ResultsCollectionWrapper(new ObjectTypeImpl(Object.class), bag.asSet());
    SelectResults[][] rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);

    // Create Index
    qs.createIndex("status", IndexType.FUNCTIONAL, "ps.status", SEPARATOR + "pos ps");
    QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
      private final boolean[] indexCalled = new boolean[] {false, false};

      private int i = 0;

      @Override
      public void afterIndexLookup(Collection results) {
        indexCalled[i++] = true;
      }

      @Override
      public void endQuery() {
        assertTrue(indexCalled[0]);
        assertFalse(indexCalled[1]);
      }

    });
    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"abc%"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs[0][0] = results;
    rs[0][1] = expectedResults;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    qs.createIndex("id", IndexType.FUNCTIONAL, "ps.ID", SEPARATOR + "pos ps");
    QueryObserverHolder.setInstance(new QueryObserverAdapter() {
      private final boolean[] indexCalled = new boolean[] {false, false};

      private int i = 0;

      @Override
      public void afterIndexLookup(Collection results) {
        indexCalled[i++] = true;
      }

      @Override
      public void endQuery() {
        // Only one indexed condition should be called
        boolean indexInvoked = false;
        for (final boolean b : indexCalled) {
          indexInvoked = indexInvoked || b;
        }
        assertTrue(indexInvoked);
      }
    });

    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"abc%"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs[0][0] = results;
    rs[0][1] = expectedResults;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    QueryObserverHolder.setInstance(old);
  }

  @Test
  public void testLikePercentageTerminated_5_withoutBindPrms() throws Exception {
    likePercentageTerminated_5(false);
  }

  @Test
  public void testLikePercentageTerminated_5_withBindPrms() throws Exception {
    likePercentageTerminated_5(true);
  }

  private void likePercentageTerminated_5(boolean useBindPrm) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    char ch = 'd';
    String base = "abc";
    for (int i = 1; i < 6; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    QueryService qs = cache.getQueryService();

    String predicate = "";
    if (useBindPrm) {
      predicate = "$1";
    } else {
      predicate = " 'a%c%'";
    }
    Query q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);

    SelectResults results;
    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"a%bc%"});
    } else {
      results = (SelectResults) q.execute();
    }

    ResultsBag bag = new ResultsBag(null);
    for (int i = 1; i < 6; ++i) {
      bag.add(region.get(i));
    }
    SelectResults expectedResults =
        new ResultsCollectionWrapper(new ObjectTypeImpl(Object.class), bag.asSet());
    SelectResults[][] rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    // Create Index
    qs.createIndex("status", IndexType.FUNCTIONAL, "ps.status", SEPARATOR + "pos ps");

    q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);

    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"a%bc%"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);

    if (useBindPrm) {
      predicate = "$1";
    } else {
      predicate = "'abc_'";
    }
    q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);

    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"abc_"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);

    if (useBindPrm) {
      predicate = "$1";
    } else {
      predicate = "'_bc_'";
    }
    q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);
    if (useBindPrm) {
      results = (SelectResults) q.execute(new Object[] {"_bc_"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
  }

  @Test
  public void testEqualityForm_1_withoutBindParams() throws Exception {
    equalityForm_1(false);
  }

  @Test
  public void testEqualityForm_1_withBindParams() throws Exception {
    equalityForm_1(true);
  }

  /**
   * Tests simple non % or non _ terminated string which in effect means equality
   */
  private void equalityForm_1(boolean useBindPrms) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    char ch = 'd';
    String base = "abc";
    for (int i = 1; i < 6; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    base = "abd";
    ch = 'd';
    for (int i = 6; i < 11; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + ch;
      ch += 1;
      region.put(i, pf);
    }

    QueryService qs = cache.getQueryService();

    String predicate = "";
    if (useBindPrms) {
      predicate = "$1";
    } else {
      predicate = " 'abcd'";
    }
    Query q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);
    SelectResults results;
    if (useBindPrms) {
      results = (SelectResults) q.execute(new Object[] {"abcd"});
    } else {
      results = (SelectResults) q.execute();
    }

    ResultsBag bag = new ResultsBag(null);

    bag.add(region.get(1));
    SelectResults expectedResults =
        new ResultsCollectionWrapper(new ObjectTypeImpl(Object.class), bag.asSet());

    SelectResults[][] rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);

    // Create Index
    qs.createIndex("status", IndexType.FUNCTIONAL, "ps.status", SEPARATOR + "pos ps");
    QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
      private boolean indexCalled = false;

      @Override
      public void afterIndexLookup(Collection results) {
        indexCalled = true;
      }

      @Override
      public void endQuery() {
        assertTrue(indexCalled);
      }

    });
    if (useBindPrms) {
      results = (SelectResults) q.execute(new Object[] {"abcd"});
    } else {
      results = (SelectResults) q.execute();
    }

    rs[0][0] = results;
    rs[0][1] = expectedResults;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    QueryObserverHolder.setInstance(old);
  }

  @Test
  public void testEqualityForm_2_withoutBindPrms() throws Exception {
    equalityForm_2(false);
  }

  @Test
  public void testEqualityForm_2_withBindPrms() throws Exception {
    equalityForm_2(true);
  }

  /**
   * Tests simple \% or \ _ terminated string which in effect means equality
   */
  private void equalityForm_2(boolean useBindPrms) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    String str = "d_";
    String base = "abc";
    for (int i = 1; i < 6; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + str;
      region.put(i, pf);
    }

    base = "abc";
    str = "d%";
    for (int i = 6; i < 11; ++i) {
      Portfolio pf = new Portfolio(i);
      pf.status = base + str;
      region.put(i, pf);
    }

    QueryService qs = cache.getQueryService();

    String predicate = "";
    if (useBindPrms) {
      predicate = "$1";
    } else {
      predicate = " 'abcd\\_'";
    }
    Query q = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);
    SelectResults results;
    if (useBindPrms) {
      results = (SelectResults) q.execute(new Object[] {"abcd\\_"});
    } else {
      results = (SelectResults) q.execute();
    }

    ResultsBag bag = new ResultsBag(null);

    for (int i = 1; i < 6; ++i) {
      bag.add(region.get(i));
    }
    SelectResults expectedResults =
        new ResultsCollectionWrapper(new ObjectTypeImpl(Object.class), bag.asSet());

    SelectResults[][] rs = new SelectResults[][] {{results, expectedResults}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);
    predicate = "";
    if (useBindPrms) {
      predicate = "$1";
    } else {
      predicate = " 'abcd\\%'";
    }
    Query q1 = qs.newQuery(
        "SELECT distinct *  FROM " + SEPARATOR + "pos ps WHERE ps.status like " + predicate);
    if (useBindPrms) {
      results = (SelectResults) q1.execute(new Object[] {"abcd\\%"});
    } else {
      results = (SelectResults) q1.execute();
    }
    bag = new ResultsBag(null);
    for (int i = 6; i < 11; ++i) {
      bag.add(region.get(i));
    }
    SelectResults expectedResults1 =
        new ResultsCollectionWrapper(new ObjectTypeImpl(Object.class), bag.asSet());


    SelectResults[][] rs1 = new SelectResults[][] {{results, expectedResults1}};
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs1, this);

    // Create Index
    qs.createIndex("status", IndexType.FUNCTIONAL, "ps.status", SEPARATOR + "pos ps");
    QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
      private boolean indexCalled = false;

      @Override
      public void afterIndexLookup(Collection results) {
        indexCalled = true;
      }

      @Override
      public void endQuery() {
        assertTrue(indexCalled);
      }

    });
    if (useBindPrms) {
      results = (SelectResults) q.execute(new Object[] {"abcd\\_"});
    } else {
      results = (SelectResults) q.execute();
    }
    rs[0][0] = results;
    rs[0][1] = expectedResults;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs, this);

    if (useBindPrms) {
      results = (SelectResults) q1.execute(new Object[] {"abcd\\%"});
    } else {
      results = (SelectResults) q1.execute();
    }
    rs1[0][0] = results;
    rs1[0][1] = expectedResults1;
    CacheUtils.compareResultsOfWithAndWithoutIndex(rs1, this);
    QueryObserverHolder.setInstance(old);
  }

  @Test
  public void testRegexMetaCharWithoutBindPrms() throws Exception {
    regexMetaChar(false);
  }

  @Test
  public void testRegexMetaCharWithBindPrms() throws Exception {
    regexMetaChar(true);
  }

  /**
   * Tests for regular expression meta chars. This has no special meaning with Like.
   */
  private void regexMetaChar(boolean useBindPrms) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    String[] values = new String[] {"active", "act**ve", "ac+t+ve", "?act?ve", "act)ve^",
        "|+act(ve", "act*+|ve", "^+act.ve+^", "act[]ve", "act][ve", "act^[a-z]ve", "act/ve",
        "inactive", "acxtxve", "ac(tiv)e", "act()ive", "act{}ive", "act{ive"};

    // Add values to region.
    for (int i = 0; i < values.length; i++) {
      region.put(i, values[i]);
    }

    // Add % and _ with escape char.
    region.put(values.length + 1, "act%+ive");
    region.put(values.length + 2, "act_+ive");

    QueryService qs = cache.getQueryService();
    Query q;
    SelectResults results;

    for (final String s : values) {
      if (!useBindPrms) {
        q = qs.newQuery(
            "select p from " + SEPARATOR + "pos.values p where p like '" + s + "'");
        results = (SelectResults) q.execute();
      } else {
        q = qs.newQuery("select p from " + SEPARATOR + "pos.values p where p like $1");
        results = (SelectResults) q.execute(new Object[] {s});
      }
      List r = results.asList();
      if (r.size() != 1 || !r.get(0).equals(s)) {
        fail("Unexpected result. expected :" + s + " for the like predicate: " + s
            + " found : " + (r.size() == 1 ? r.get(0) : "Result size not equal to 1"));
      }
    }

    // Create Index
    qs.createIndex("p", IndexType.FUNCTIONAL, "p", SEPARATOR + "pos.values p");

    for (final String value : values) {
      if (!useBindPrms) {
        q = qs.newQuery(
            "select p from " + SEPARATOR + "pos.values p where p like '" + value + "'");
        results = (SelectResults) q.execute();
      } else {
        q = qs.newQuery("select p from " + SEPARATOR + "pos.values p where p like $1");
        results = (SelectResults) q.execute(new Object[] {value});
      }
      List r = results.asList();
      if (r.size() != 1 || !r.get(0).equals(value)) {
        fail("Unexpected result. expected :" + value + " for the like predicate: " + value
            + " found : " + (r.size() == 1 ? r.get(0) : "Result size not equal to 1"));
      }
    }
  }

  @Test
  public void testEnhancedLikeWithoutBindPrms() throws Exception {
    enhancedLike(false);
  }

  @Test
  public void testEnhancedLikeWithBindPrms() throws Exception {
    enhancedLike(true);
  }

  /**
   * Tests with combination of % and _ Supported from 6.6
   */
  private void enhancedLike(boolean useBindPrms) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    String[] values = new String[] {"active",};

    for (int i = 0; i < values.length; i++) {
      region.put(i, values[i]);
    }

    QueryService qs = cache.getQueryService();
    Query q;
    SelectResults results;

    String[] likePredicates = new String[] {"active", "act%%ve", "a%e", "%ctiv%", "%c%iv%",
        "%ctive", "%%ti%", "activ_", "_ctive", "ac_ive", "_c_iv_", "_ctiv%", "__tive", "act__e",
        "a%iv_", "a_tiv%", "%", "ac%",};
    for (final String predicate : likePredicates) {
      if (!useBindPrms) {
        q = qs.newQuery(
            "select p from " + SEPARATOR + "pos.values p where p like '" + predicate + "'");
        results = (SelectResults) q.execute();
      } else {
        q = qs.newQuery("select p from " + SEPARATOR + "pos.values p where p like $1");
        results = (SelectResults) q.execute(new Object[] {predicate});
      }
      List r = results.asList();
      if (r.size() != 1 || !r.get(0).equals(values[0])) {
        fail("Unexpected result. expected :" + values[0] + " for the like predicate: "
            + predicate + " found : "
            + (r.size() == 1 ? r.get(0) : "Result size not equal to 1"));
      }
    }

    // Create Index
    qs.createIndex("p", IndexType.FUNCTIONAL, "p", SEPARATOR + "pos.values p");

    for (final String likePredicate : likePredicates) {
      if (!useBindPrms) {
        q = qs.newQuery(
            "select p from " + SEPARATOR + "pos.values p where p like '" + likePredicate + "'");
        results = (SelectResults) q.execute();
      } else {
        q = qs.newQuery("select p from " + SEPARATOR + "pos.values p where p like $1");
        results = (SelectResults) q.execute(new Object[] {likePredicate});
      }
      List r = results.asList();
      if (r.size() != 1 || !r.get(0).equals(values[0])) {
        fail("Unexpected result. expected :" + values[0] + " for the like predicate: "
            + likePredicate + " found : "
            + (r.size() == 1 ? r.get(0) : "Result size not equal to 1"));
      }
    }
  }

  @Test
  public void testEnhancedLike2WithoutBindPrms() throws Exception {
    enhancedLike2(false);
  }

  @Test
  public void testEnhancedLike2WithBindPrms() throws Exception {
    enhancedLike2(true);
  }

  private void enhancedLike2(boolean useBindPrms) throws Exception {
    Cache cache = CacheUtils.getCache();
    AttributesFactory attributesFactory = new AttributesFactory();
    RegionAttributes regionAttributes = attributesFactory.create();

    Region region = cache.createRegion("pos", regionAttributes);

    String[] values = new String[] {"ac\\tive", "X\\\\X", "Y%Y", "Z\\%Z", "pass\\ive",
        "inact\\%+ive", "1inact\\_+ive",};

    for (int i = 0; i < values.length; i++) {
      region.put(i, values[i]);
    }

    QueryService qs = cache.getQueryService();
    Query q;
    SelectResults results;

    String[] result = new String[] {"ac\\tive", "ac\\tive", "ac\\tive", "Y%Y", "X\\\\X", "X\\\\X",
        "Z\\%Z", "inact\\%+ive", "1inact\\_+ive",};
    String[] likePredicates = new String[] {"ac\\\\tive", "ac\\\\%", "ac_tive", "Y\\%Y", "X__X",
        "X%X", "Z\\\\\\%Z", "inact\\\\%+ive", "1inact\\\\_+ive",};
    for (int i = 0; i < likePredicates.length; i++) {
      if (!useBindPrms) {
        q = qs.newQuery(
            "select p from " + SEPARATOR + "pos.values p where p like '" + likePredicates[i] + "'");
        results = (SelectResults) q.execute();
      } else {
        q = qs.newQuery("select p from " + SEPARATOR + "pos.values p where p like $1");
        results = (SelectResults) q.execute(new Object[] {likePredicates[i]});
      }
      List r = results.asList();
      if (r.size() != 1 || !r.get(0).equals(result[i])) {
        fail("Unexpected result. expected :" + result[i] + " for the like predicate: "
            + likePredicates[i] + " found : "
            + (r.size() == 1 ? r.get(0) : "Result size not equal to 1"));
      }
    }

    // Create Index
    qs.createIndex("p", IndexType.FUNCTIONAL, "p", SEPARATOR + "pos.values p");

    for (int i = 0; i < likePredicates.length; i++) {
      if (!useBindPrms) {
        q = qs.newQuery(
            "select p from " + SEPARATOR + "pos.values p where p like '" + likePredicates[i] + "'");
        results = (SelectResults) q.execute();
      } else {
        q = qs.newQuery("select p from " + SEPARATOR + "pos.values p where p like $1");
        results = (SelectResults) q.execute(new Object[] {likePredicates[i]});
      }
      List r = results.asList();
      if (r.size() != 1 || !r.get(0).equals(result[i])) {
        fail("Unexpected result. expected :" + result[i] + " for the like predicate: "
            + likePredicates[i] + " found : "
            + (r.size() == 1 ? r.get(0) : "Result size not equal to 1"));
      }
