/*
 * 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.internal.index;

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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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.PartitionAttributesFactory;
import org.apache.geode.cache.Region;
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.QueryInvalidException;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.cache.query.Struct;
import org.apache.geode.cache.query.data.Portfolio;
import org.apache.geode.cache.query.functional.StructSetOrResultsSet;
import org.apache.geode.cache.query.internal.QueryObserverAdapter;
import org.apache.geode.cache.query.internal.QueryObserverHolder;
import org.apache.geode.cache.query.internal.index.IndexManager.TestHook;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.test.junit.categories.OQLIndexTest;

@Category({OQLIndexTest.class})
public class IndexUseJUnitTest {

  private Region region;
  private QueryService qs;

  @Before
  public void setUp() throws Exception {
    CacheUtils.startCache();
    region = CacheUtils.createRegion("pos", Portfolio.class);
    region.put("0", new Portfolio(0));
    region.put("1", new Portfolio(1));
    region.put("2", new Portfolio(2));
    region.put("3", new Portfolio(3));

    qs = CacheUtils.getQueryService();
    qs.createIndex("statusIndex", IndexType.FUNCTIONAL, "status", SEPARATOR + "pos");
    qs.createIndex("idIndex", IndexType.FUNCTIONAL, "ID", SEPARATOR + "pos");
    qs.createIndex("secIdIndex", IndexType.FUNCTIONAL, "P1.secId", SEPARATOR + "pos");
    qs.createIndex("secIdIndex2", IndexType.FUNCTIONAL, "P2.secId", SEPARATOR + "pos");
    qs.createIndex("p1secindex", "p.position1.secId", " " + SEPARATOR + "pos p ");
  }

  @After
  public void tearDown() throws Exception {
    CacheUtils.closeCache();
    IndexManager indexManager = ((LocalRegion) region).getIndexManager();
    if (indexManager != null) {
      indexManager.destroy();
    }
    IndexManager.TEST_RANGEINDEX_ONLY = false;
    RangeIndex.setTestHook(null);
    CompactRangeIndex.setTestHook(null);
  }

  @Test
  public void testIndexUseSingleCondition() throws Exception {
    String[][] testData = {{"status", "'active'"}, {"ID", "2"}, {"P1.secId", "'IBM'"},};
    String[] operators = {"=", "<>", "!=", "<", "<=", ">", ">="};
    for (String operator : operators) {
      for (final String[] testDatum : testData) {
        Query q = qs.newQuery(
            "SELECT DISTINCT * FROM " + SEPARATOR + "pos where " + testDatum[0] + " " + operator
                + " " + testDatum[1]);
        QueryObserverImpl observer = new QueryObserverImpl();
        QueryObserverHolder.setInstance(observer);
        q.execute();
        if (!observer.isIndexesUsed) {
          fail("Index not used for operator '" + operator + "'");
        }
      }
    }
  }

  @Test
  public void testIndexUseMultipleConditions() throws Exception {
    String[][] testData = {{"P1.secType = 'a'", "0"}, {"status = 'active' AND ID = 2", "1"},
        {"status = 'active' AND ID = 2 AND P1.secId  = 'IBM'", "1"},
        {"status = 'active' OR ID = 2", "2"},
        {"status = 'active' OR ID = 2 OR P1.secId  = 'IBM'", "3"},
        {"status = 'active' AND ID = 2 OR P1.secId  = 'IBM'", "2"},
        {"status = 'active' AND ( ID = 2 OR P1.secId  = 'IBM')", "1"},
        {"status = 'active' OR ID = 2 AND P1.secId  = 'IBM'", "2"},
        {"(status = 'active' OR ID = 2) AND P1.secId  = 'IBM'", "1"},
        {"NOT (status = 'active') AND ID = 2", "1"}, {"status = 'active' AND NOT( ID = 2 )", "1"},
        {"NOT (status = 'active') OR ID = 2", "2"}, {"status = 'active' OR NOT( ID = 2 )", "2"},
        {"status = 'active' AND P1.secType = 'a'", "1"},
        {"status = 'active' OR P1.secType = 'a'", "0"},
        {"status = 'active' AND ID =1 AND P1.secType = 'a'", "1"},
        {"status = 'active' AND ID = 1 OR P1.secType = 'a'", "0"},
        {"status = 'active' OR ID = 1 AND P1.secType = 'a'", "2"}, {"P2.secId = null", "1"},
        {"IS_UNDEFINED(P2.secId)", "1"}, {"IS_DEFINED(P2.secId)", "1"},
        {"P2.secId = UNDEFINED", "0"},};
    for (final String[] testDatum : testData) {
      Query q = qs.newQuery("SELECT DISTINCT * FROM " + SEPARATOR + "pos where " + testDatum[0]);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      q.execute();
      if (observer.indexesUsed.size() != Integer.parseInt(testDatum[1])) {
        fail("Wrong Index use for " + testDatum[0] + "\n Indexes used " + observer.indexesUsed);
      }
    }
  }

  /**
   * Test to check if Region object is passed as bind argument, the index utilization occurs or not
   */
  @Test
  public void testBug36421_part1() throws Exception {
    String[][] testData = {{"status", "'active'"},};
    String[] operators = {"="};
    for (String operator : operators) {
      for (final String[] testDatum : testData) {
        Query q = qs.newQuery("SELECT DISTINCT * FROM $1 where " + testDatum[0] + " " + operator
            + " " + testDatum[1]);
        QueryObserverImpl observer = new QueryObserverImpl();
        QueryObserverHolder.setInstance(observer);
        q.execute(CacheUtils.getRegion(SEPARATOR + "pos"));
        if (!observer.isIndexesUsed) {
          fail("Index not used for operator '" + operator + "'");
        }
      }
    }
  }

  /**
   * Test to check if Region short cut method is used for querying, the index utilization occurs or
   * not
   */
  @Test
  public void testBug36421_part2() throws Exception {
    String[][] testData = {{"status", "'active'"},};
    String[] operators = {"="};
    for (String operator : operators) {
      for (final String[] testDatum : testData) {

        QueryObserverImpl observer = new QueryObserverImpl();
        QueryObserverHolder.setInstance(observer);
        CacheUtils.getRegion(SEPARATOR + "pos")
            .query(testDatum[0] + " " + operator + " " + testDatum[1]);
        if (!observer.isIndexesUsed) {
          fail("Index not used for operator '" + operator + "'");
        }
      }
    }
  }

  /**
   * Test to check if a parametrized query when using different bind arguments of Region uses the
   * index correctly
   */
  @Test
  public void testBug36421_part3() throws Exception {
    Query q = qs.newQuery("SELECT DISTINCT * FROM $1 z where z.status = 'active'");
    QueryObserverImpl observer = new QueryObserverImpl();
    QueryObserverHolder.setInstance(observer);
    q.execute(CacheUtils.getRegion(SEPARATOR + "pos"));
    if (!observer.isIndexesUsed) {
      fail("Index not uesd for operator '='");
    }
    assertTrue(observer.indexesUsed.get(0).equals("statusIndex"));
    region = CacheUtils.createRegion("pos1", Portfolio.class);
    region.put("0", new Portfolio(0));
    region.put("1", new Portfolio(1));
    region.put("2", new Portfolio(2));
    region.put("3", new Portfolio(3));
    qs.createIndex("statusIndex1", IndexType.FUNCTIONAL, "pf1.status", SEPARATOR + "pos1 pf1");
    region.put("4", new Portfolio(4));
    observer = new QueryObserverImpl();
    QueryObserverHolder.setInstance(observer);
    q.execute(CacheUtils.getRegion(SEPARATOR + "pos1"));
    if (!observer.isIndexesUsed) {
      fail("Index not used for operator'='");
    }
    assertTrue(observer.indexesUsed.get(0).equals("statusIndex1"));
  }

  /**
   * Test to check if Region short cut method is used for querying, the Primary key index
   * utilization occurs or not
   */
  @Test
  public void testBug36421_part4() throws Exception {
    qs.createIndex("pkIndex", IndexType.PRIMARY_KEY, "pk", SEPARATOR + "pos");
    QueryObserverImpl observer = new QueryObserverImpl();
    QueryObserverHolder.setInstance(observer);
    SelectResults rs = CacheUtils.getRegion(SEPARATOR + "pos").query("pk = '2'");
    if (!observer.isIndexesUsed) {
      fail("Index not uesd for operator '='");
    }
    assertTrue(rs.size() == 1);
    assertTrue(((Portfolio) rs.iterator().next()).pkid.equals("2"));
    assertTrue(observer.indexesUsed.get(0).equals("pkIndex"));
    CacheUtils.getRegion(SEPARATOR + "pos").put("7", new Portfolio(7));
    observer = new QueryObserverImpl();
    QueryObserverHolder.setInstance(observer);
    rs = CacheUtils.getRegion(SEPARATOR + "pos").query("pk = '7'");
    if (!observer.isIndexesUsed) {
      fail("Index not used for operator '='");
    }
    assertTrue(rs.size() == 1);
    assertTrue(((Portfolio) rs.iterator().next()).pkid.equals("7"));
    assertTrue(observer.indexesUsed.get(0).equals("pkIndex"));
  }

  @Test
  public void testMapIndexUsageAllKeys() throws Exception {
    IndexManager.TEST_RANGEINDEX_ONLY = true;
    evaluateMapTypeIndexUsageAllKeys();
  }

  private void evaluateMapTypeIndexUsageAllKeys() throws Exception {
    QueryService qs = CacheUtils.getQueryService();
    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);
    int ID = 1;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (; ID <= 30; ++ID) {
      MapKeyIndexData mkid = new MapKeyIndexData(ID);
      for (int j = 1; j <= ID; ++j) {
        mkid.maap.put("key1", j * 1);
        mkid.maap.put("key2", j * 2);
        mkid.maap.put("key3", j * 3);
      }
      testRgn.put(ID, mkid);
    }
    String[] queries =
        {"SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap['key2'] >= 16"
        /* "SELECT DISTINCT * FROM /testRgn itr1  WHERE itr1.maap.get('key2') >= 16" , */
        };
    String[] queriesIndexNotUsed =
        {"SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap.get('key2') >= 16"};
    Object[][] r = new Object[queries.length][2];

    qs = CacheUtils.getQueryService();

    // Execute Queries without Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      r[i][0] = q.execute();
      CacheUtils.log("Executed query: " + queries[i]);
    }

    Index i1 =
        qs.createIndex("Index1", IndexType.FUNCTIONAL, "objs.maap[*]", SEPARATOR + "testRgn objs");

    // Execute Queries with Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      r[i][1] = q.execute();
      CacheUtils.log("Executing query: " + queries[i] + " with index created");
      if (!observer.isIndexesUsed) {
        fail("Index is NOT used");
      }
      Iterator itr = observer.indexesUsed.iterator();
      assertTrue(itr.hasNext());
      String temp = itr.next().toString();
      assertEquals(temp, "Index1");
    }
    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
    ssOrrs.CompareQueryResultsWithoutAndWithIndexes(r, queries.length, queries);

    // Test queries index not used
    for (final String s : queriesIndexNotUsed) {
      Query q = CacheUtils.getQueryService().newQuery(s);
      CacheUtils.getLogger().info("Executing query: " + s);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      CacheUtils.log("Executing query: " + s + " with index created");
      q.execute();
      assertFalse(observer.isIndexesUsed);
      Iterator itr = observer.indexesUsed.iterator();
      assertFalse(itr.hasNext());
    }
  }

  @Test
  public void testCompactMapIndexUsageWithIndexOnSingleKey() throws Exception {
    String[] queries =
        {"SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap['key2'] >= 3"};
    String[] queriesIndexNotUsed =
        {"SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap.get('key2') >= 3"};

    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);
    int ID = 1;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (; ID <= 30; ++ID) {
      MapKeyIndexData mkid = new MapKeyIndexData(ID);
      for (int j = 1; j <= ID; ++j) {
        mkid.addKeyValue("key1", j * 1);
        mkid.addKeyValue("key2", j * 2);
        mkid.addKeyValue("key3", j * 3);
      }
      testRgn.put(ID, mkid);
    }

    evaluateMapTypeIndexUsage("objs.maap['key2']", SEPARATOR + "testRgn objs", queries,
        queriesIndexNotUsed,
        CompactRangeIndex.class);

    String query = "SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.liist[0] >= 2";
    SelectResults withoutIndex, withIndex;
    Query q = CacheUtils.getQueryService().newQuery(query);
    CacheUtils.getLogger().info("Executing query: " + query);
    withoutIndex = (SelectResults) q.execute();
    CacheUtils.log("Executed query: " + query);

    Index i2 =
        qs.createIndex("Index2", IndexType.FUNCTIONAL, "objs.liist[0]", SEPARATOR + "testRgn objs");
    assertTrue(i2 instanceof CompactRangeIndex);
    CacheUtils.getLogger().info("Executing query: " + query);
    QueryObserverImpl observer = new QueryObserverImpl();
    QueryObserverHolder.setInstance(observer);
    withIndex = (SelectResults) q.execute();
    CacheUtils.log("Executing query: " + query + " with index created");
    if (!observer.isIndexesUsed) {
      fail("Index is NOT used");
    }
    Iterator itr = observer.indexesUsed.iterator();
    assertTrue(itr.hasNext());
    String temp = itr.next().toString();
    assertEquals(temp, "Index2");

    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
    ssOrrs.CompareQueryResultsWithoutAndWithIndexes(new Object[][] {{withoutIndex, withIndex}}, 1,
        queries);
  }

  @Test
  public void testCompactMapIndexUsageWithIndexOnMultipleKeys() throws Exception {
    String[] queries =
        {"SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap['key2'] >= 3",
            "SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap['key3'] >= 3"};
    String[] queriesIndexNotUsed =
        {"SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap.get('key2') >= 16",
            "SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap['key4'] >= 16",};

    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);
    int ID = 1;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (; ID <= 30; ++ID) {
      MapKeyIndexData mkid = new MapKeyIndexData(ID);
      for (int j = 1; j <= ID; ++j) {
        mkid.addKeyValue("key1", j * 1);
        mkid.addKeyValue("key2", j * 2);
        mkid.addKeyValue("key3", j * 3);
      }
      testRgn.put(ID, mkid);
    }
    evaluateMapTypeIndexUsage("objs.maap['key2','key3']", SEPARATOR + "testRgn objs", queries,
        queriesIndexNotUsed, CompactMapRangeIndex.class);
  }

  @Test
  public void testMapIndexUsageWithIndexOnMultipleKeys() throws Exception {
    IndexManager.TEST_RANGEINDEX_ONLY = true;
    String[] queries =
        {"SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap['key2'] >= 3",
            "SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap['key3'] >= 3"};
    String[] queriesIndexNotUsed =
        {"SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap.get('key2') >= 16",
            "SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap['key4'] >= 16",};


    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);
    int ID = 1;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (; ID <= 30; ++ID) {
      MapKeyIndexData mkid = new MapKeyIndexData(ID);
      for (int j = 1; j <= ID; ++j) {
        mkid.addKeyValue("key1", j * 1);
        mkid.addKeyValue("key2", j * 2);
        mkid.addKeyValue("key3", j * 3);
      }
      testRgn.put(ID, mkid);
    }

    evaluateMapTypeIndexUsage("objs.maap['key2','key3']", SEPARATOR + "testRgn objs", queries,
        queriesIndexNotUsed, MapRangeIndex.class);
  }

  private void evaluateMapTypeIndexUsage(String indexExpression, String fromClause,
      String[] queries, String[] queriesIndexNotUsed, Class expectedIndexClass) throws Exception {
    QueryService qs = CacheUtils.getQueryService();

    Object[][] r = new Object[queries.length][2];

    // Execute Queries without Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      r[i][0] = q.execute();
      CacheUtils.log("Executed query: " + queries[i]);
    }

    Index i1 = qs.createIndex("Index1", IndexType.FUNCTIONAL, indexExpression, fromClause);
    assertTrue(i1.getClass().equals(expectedIndexClass));

    // Execute Queries with Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      r[i][1] = q.execute();
      CacheUtils.log("Executing query: " + queries[i] + " with index created");
      if (!observer.isIndexesUsed) {
        fail("Index is NOT used");
      }
      Iterator itr = observer.indexesUsed.iterator();
      assertTrue(itr.hasNext());
      String temp = itr.next().toString();
      assertEquals(temp, "Index1");
    }
    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
    ssOrrs.CompareQueryResultsWithoutAndWithIndexes(r, queries.length, queries);

    // Test queries index not used
    for (final String s : queriesIndexNotUsed) {
      Query q = CacheUtils.getQueryService().newQuery(s);
      CacheUtils.getLogger().info("Executing query: " + s);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      CacheUtils.log("Executing query: " + s + " with index created");
      q.execute();
      assertFalse(observer.isIndexesUsed);
      Iterator itr = observer.indexesUsed.iterator();
      assertFalse(itr.hasNext());
    }
  }

  @Test
  public void testIndexUsageWithOrderBy() throws Exception {
    QueryService qs = CacheUtils.getQueryService();
    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);

    int numObjects = 30;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (int i = 0; i < numObjects; i++) {
      Portfolio p = new Portfolio(i);
      p.pkid = ("" + (numObjects - i));
      testRgn.put("" + i, p);
    }

    qs = CacheUtils.getQueryService();
    String[] queries =
        {"SELECT DISTINCT * FROM " + SEPARATOR
            + "testRgn p  WHERE p.ID <= 10 order by p.pkid asc limit 1",
            "SELECT DISTINCT * FROM " + SEPARATOR
                + "testRgn p  WHERE p.ID <= 10 order by p.pkid desc limit 1",};

    Object[][] r = new Object[queries.length][2];

    // Execute Queries without Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      // verify individual result
      SelectResults sr = (SelectResults) q.execute();
      List results = sr.asList();
      for (int rows = 0; rows < results.size(); rows++) {
        Portfolio p = (Portfolio) results.get(0);
        CacheUtils.getLogger().info("p: " + p);
        if (i == 0) {
          assertEquals(p.getID(), 10);
          assertEquals(p.pkid, "" + (numObjects - 10));
        } else if (i == 1) {
          assertEquals(p.getID(), 0);
          assertEquals(p.pkid, "" + numObjects);
        }
      }
      r[i][0] = sr;
      CacheUtils.log("Executed query: " + queries[i]);
    }

    Index i1 = qs.createIndex("Index1", IndexType.FUNCTIONAL, "p.ID", SEPARATOR + "testRgn p");

    // Execute Queries with Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      SelectResults sr = (SelectResults) q.execute();
      List results = sr.asList();
      for (int rows = 0; rows < results.size(); rows++) {
        Portfolio p = (Portfolio) results.get(0);
        CacheUtils.getLogger().info("index p: " + p);
        if (i == 0) {
          assertEquals(p.getID(), 10);
          assertEquals(p.pkid, "" + (numObjects - 10));
        } else if (i == 1) {
          assertEquals(p.getID(), 0);
          assertEquals(p.pkid, "" + numObjects);
        }
      }
      r[i][1] = sr;
      CacheUtils.log("Executing query: " + queries[i] + " with index created");
      if (!observer.isIndexesUsed) {
        fail("Index is NOT uesd");
      }
      Iterator itr = observer.indexesUsed.iterator();
      assertTrue(itr.hasNext());
      String temp = itr.next().toString();
      assertEquals(temp, "Index1");
    }
    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
    ssOrrs.CompareQueryResultsWithoutAndWithIndexes(r, queries.length, queries);
  }

  @Test
  public void testIndexUsageWithOrderBy3() throws Exception {
    QueryService qs = CacheUtils.getQueryService();
    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);

    int numObjects = 30;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (int i = 0; i < numObjects; i++) {
      Portfolio p = new Portfolio(i);
      p.pkid = ("" + (numObjects - i));
      testRgn.put("" + i, p);
    }

    qs = CacheUtils.getQueryService();
    String[] queries =
        {"SELECT DISTINCT * FROM " + SEPARATOR
            + "testRgn p  WHERE p.ID <= 10 order by ID asc limit 1",
            "SELECT DISTINCT * FROM " + SEPARATOR
                + "testRgn p  WHERE p.ID <= 10 order by p.ID desc limit 1",};

    Object[][] r = new Object[queries.length][2];

    Index i1 = qs.createIndex("Index1", IndexType.FUNCTIONAL, "p.ID", SEPARATOR + "testRgn p");

    // Execute Queries with Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      SelectResults sr = (SelectResults) q.execute();
      List results = sr.asList();
      for (int rows = 0; rows < results.size(); rows++) {
        Portfolio p = (Portfolio) results.get(0);
        CacheUtils.getLogger().info("index p: " + p);
        if (i == 0) {
          assertEquals(p.getID(), 0);

        } else if (i == 1) {
          assertEquals(p.getID(), 10);

        }
      }
      r[i][1] = sr;
      CacheUtils.log("Executing query: " + queries[i] + " with index created");
      if (!observer.isIndexesUsed) {
        fail("Index is NOT uesd");
      }
      Iterator itr = observer.indexesUsed.iterator();
      assertTrue(itr.hasNext());
      String temp = itr.next().toString();
      assertEquals(temp, "Index1");
    }
  }

  @Test
  public void testIndexUsageWithOrderBy2() throws Exception {
    QueryService qs = CacheUtils.getQueryService();
    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);

    int numObjects = 30;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (int i = 0; i < numObjects; i++) {
      Portfolio p = new Portfolio(i % 2);
      p.createTime = (numObjects - i);
      testRgn.put("" + i, p);
    }

    qs = CacheUtils.getQueryService();
    String[] queries = {
        "SELECT DISTINCT p.key, p.value FROM " + SEPARATOR
            + "testRgn.entrySet p  WHERE p.value.ID <= 10 order by p.value.createTime asc limit 1",
        "SELECT DISTINCT p.key, p.value FROM " + SEPARATOR
            + "testRgn.entrySet p  WHERE p.value.ID <= 10 order by p.value.createTime desc limit 1",};

    Object[][] r = new Object[queries.length][2];

    // Execute Queries without Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      // verify individual result
      SelectResults sr = (SelectResults) q.execute();
      List results = sr.asList();
      for (int rows = 0; rows < results.size(); rows++) {
        Struct s = (Struct) results.get(0);
        Portfolio p = (Portfolio) s.get("value");
        CacheUtils.getLogger().info("p: " + p);
        if (i == 0) {
          assertEquals(p.createTime, 1);
        } else if (i == 1) {
          assertEquals(p.createTime, numObjects);
        }
      }
      r[i][0] = sr;
      CacheUtils.log("Executed query: " + queries[i]);
    }

    Index i1 = qs.createIndex("Index1", IndexType.FUNCTIONAL, "p.value.ID",
        SEPARATOR + "testRgn.entrySet p");

    // Execute Queries with Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      SelectResults sr = (SelectResults) q.execute();
      List results = sr.asList();
      for (int rows = 0; rows < results.size(); rows++) {
        Struct s = (Struct) results.get(0);
        Portfolio p = (Portfolio) s.get("value");
        CacheUtils.getLogger().info("index p: " + p);
        if (i == 0) {
          assertEquals(p.createTime, 1);
        } else if (i == 1) {
          assertEquals(p.createTime, numObjects);
        }
      }
      r[i][1] = sr;
      CacheUtils.log("Executing query: " + queries[i] + " with index created");
      if (!observer.isIndexesUsed) {
        fail("Index is NOT uesd");
      }
      Iterator itr = observer.indexesUsed.iterator();
      assertTrue(itr.hasNext());
      String temp = itr.next().toString();
      assertEquals(temp, "Index1");
    }
    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
    ssOrrs.CompareQueryResultsWithoutAndWithIndexes(r, queries.length, queries);
  }

  @Test
  public void testIncorrectIndexOperatorSyntax() {
    QueryService qs = CacheUtils.getQueryService();
    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);
    int ID = 1;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (; ID <= 30; ++ID) {
      MapKeyIndexData mkid = new MapKeyIndexData(ID);
      for (int j = 1; j <= ID; ++j) {
        mkid.maap.put("key1", j * 1);
        mkid.maap.put("key2", j * 2);
        mkid.maap.put("key3", j * 3);
      }
      testRgn.put(ID, mkid);
    }

    qs = CacheUtils.getQueryService();
    try {
      Query q = CacheUtils.getQueryService()
          .newQuery(
              "SELECT DISTINCT * FROM " + SEPARATOR + "testRgn itr1  WHERE itr1.maap[*] >= 3");
      fail("Should have thrown exception");
    } catch (QueryInvalidException expected) {
      // ok
    }

    try {
      Query q = CacheUtils.getQueryService()
          .newQuery("SELECT DISTINCT * FROM " + SEPARATOR
              + "testRgn itr1  WHERE itr1.maap['key1','key2'] >= 3");
      fail("Should have thrown exception");
    } catch (QueryInvalidException expected) {
      // ok
    }
  }

  @Test
  public void testRangeGroupingBehaviourOfCompactMapIndex() throws Exception {
    QueryService qs = CacheUtils.getQueryService();
    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);
    int ID = 1;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (; ID <= 30; ++ID) {
      MapKeyIndexData mkid = new MapKeyIndexData(ID);
      for (int j = 1; j <= ID; ++j) {
        mkid.addKeyValue("key1", j * 1);
        mkid.addKeyValue("key2", j * 2);
        mkid.addKeyValue("key3", j * 3);
      }
      testRgn.put(ID, mkid);
    }

    String[] queries = {
        "SELECT DISTINCT * FROM " + SEPARATOR
            + "testRgn itr1  WHERE itr1.maap['key2'] >= 3 and itr1.maap['key2'] <=18",
        "SELECT DISTINCT * FROM " + SEPARATOR
            + "testRgn itr1  WHERE itr1.maap['key3'] >= 3  and  itr1.maap['key3'] >= 13 ",
        "SELECT DISTINCT * FROM " + SEPARATOR
            + "testRgn itr1  WHERE itr1.maap['key2'] >= 3  and  itr1.maap['key3'] >= 13 ",
        "SELECT DISTINCT * FROM " + SEPARATOR
            + "testRgn itr1  WHERE itr1.maap['key2'] >= 3  and  itr1.maap['key3'] < 18 "};

    Object[][] r = new Object[queries.length][2];

    // Execute Queries without Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      r[i][0] = q.execute();
      CacheUtils.log("Executed query: " + queries[i]);
    }

    Index i1 =
        qs.createIndex("Index1", IndexType.FUNCTIONAL, "objs.maap['key2','key3']",
            SEPARATOR + "testRgn objs");
    assertTrue(i1 instanceof CompactMapRangeIndex);
    // Execute Queries with Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = CacheUtils.getQueryService().newQuery(queries[i]);
      CacheUtils.getLogger().info("Executing query: " + queries[i]);
      QueryObserverImpl observer = new QueryObserverImpl();
      QueryObserverHolder.setInstance(observer);
      r[i][1] = q.execute();
      CacheUtils.log("Executing query: " + queries[i] + " with index created");
      if (!observer.isIndexesUsed) {
        fail("Index is NOT uesd");
      }
      Iterator itr = observer.indexesUsed.iterator();
      assertTrue(itr.hasNext());
      String temp = itr.next().toString();
      assertEquals(temp, "Index1");
    }
    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();
    ssOrrs.CompareQueryResultsWithoutAndWithIndexes(r, queries.length, queries);
  }

  @Test
  public void testRangeGroupingBehaviourOfMapIndex() throws Exception {
    IndexManager.TEST_RANGEINDEX_ONLY = true;
    QueryService qs = CacheUtils.getQueryService();
    LocalRegion testRgn = (LocalRegion) CacheUtils.createRegion("testRgn", null);
    int ID = 1;
    // Add some test data now
    // Add 5 main objects. 1 will contain key1, 2 will contain key1 & key2
    // and so on
    for (; ID <= 30; ++ID) {
      MapKeyIndexData mkid = new MapKeyIndexData(ID);
      for (int j = 1; j <= ID; ++j) {
        mkid.addKeyValue("key1", j * 1);
        mkid.addKeyValue("key2", j * 2);
