/*
 * 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.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

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

import org.apache.geode.cache.Region;
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.CompiledValue;
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.index.IndexManager;
import org.apache.geode.cache.query.types.StructType;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.test.junit.categories.OQLQueryTest;

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

  Region region;

  QueryService qs;

  @Before
  public void setUp() throws Exception {
    CacheUtils.startCache();
    region = CacheUtils.createRegion("portfolios", Portfolio.class);
    // Add 10 unique objects
    Position.cnt = 0;
    for (int i = 1; i < 11; ++i) {
      Object p = new Portfolio(i);
      GemFireCacheImpl.getInstance().getLogger().fine(p.toString());
      region.put(Integer.toString(i), p);
    }
    // Add 5 pairs of same Object starting from 11 to 20
    /*
     * for(int i=11; i< 21 ;) { region.put( Integer.toString(i), new Portfolio(i)); region.put(
     * Integer.toString(i+1), new Portfolio(i)); i +=2; }
     */
    qs = CacheUtils.getQueryService();
  }

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


  @Test
  public void testLikeWithLimitWithParameter() throws Exception {
    String queryString =
        "SELECT DISTINCT entry FROM $1 entry WHERE entry.key like $2 ORDER BY entry.key LIMIT $3 ";
    SelectResults result;
    Region region = CacheUtils.createRegion("portfolios1", Portfolio.class);
    for (int i = 0; i < 100; i++) {
      region.put("p" + i, new Portfolio(i));
    }

    Object[] params = new Object[3];
    params[0] = region.entrySet();
    params[1] = "p%";
    params[2] = 5;

    SelectResults results = (SelectResults) qs.newQuery(queryString).execute(params);
    assertEquals(5, results.size());
  }

  @Test
  public void testDistinctLimitWithParameter() throws Exception {
    String queryString = "SELECT DISTINCT entry FROM $1 entry LIMIT $2 ";
    SelectResults result;
    Region region = CacheUtils.createRegion("portfolios1", Portfolio.class);
    for (int i = 0; i < 100; i++) {
      region.put(i, new Portfolio(i));
    }

    Object[] params = new Object[2];
    params[0] = region;
    params[1] = 5;

    SelectResults results = (SelectResults) qs.newQuery(queryString).execute(params);
    assertEquals(5, results.size());
  }

  @Test
  public void testLimitWithParameter() throws Exception {
    String queryString = "SELECT * from " + SEPARATOR + "portfolios1 LIMIT $1 ";
    SelectResults result;
    Region region = CacheUtils.createRegion("portfolios1", Portfolio.class);
    for (int i = 0; i < 100; i++) {
      region.put(i, new Portfolio(i));
    }

    int limit = 5;
    Object[] params = new Object[1];
    params[0] = limit;

    SelectResults results = (SelectResults) qs.newQuery(queryString).execute(params);
    assertEquals(limit, results.size());

    limit = 1;
    params[0] = limit;
    results = (SelectResults) qs.newQuery(queryString).execute(params);
    assertEquals(limit, results.size());

    limit = 0;
    params[0] = limit;
    results = (SelectResults) qs.newQuery(queryString).execute(params);
    assertEquals(limit, results.size());

    limit = 10;
    params[0] = limit;
    results = (SelectResults) qs.newQuery(queryString).execute(params);
    assertEquals(limit, results.size());
  }

  @Test
  public void testLimitWithParameterNotSet() throws Exception {
    String queryString = "SELECT * from " + SEPARATOR + "portfolios1 LIMIT $1 ";
    SelectResults result;
    Region region = CacheUtils.createRegion("portfolios1", Portfolio.class);
    for (int i = 0; i < 100; i++) {
      region.put(i, new Portfolio(i));
    }

    Object[] params = new Object[1];
    SelectResults results = (SelectResults) qs.newQuery(queryString).execute(params);
    assertEquals(region.size(), results.size());
  }

  @Test
  public void testLimitWithNullParameterObject() throws Exception {
    String queryString = "SELECT * from " + SEPARATOR + "portfolios1 LIMIT $1 ";
    SelectResults result;
    Region region = CacheUtils.createRegion("portfolios1", Portfolio.class);
    for (int i = 0; i < 100; i++) {
      region.put(i, new Portfolio(i));
    }
    try {
      SelectResults results = (SelectResults) qs.newQuery(queryString).execute();
    } catch (IllegalArgumentException e) {
      // we expect an illegal argument exception
      assertTrue(true);
    }
  }

  // Test to see if bind argument limit is applied correctly for indexed and non indexed
  // queries.
  @Test
  public void testLimitWithParameterForNonAndIndexedQuery() throws Exception {
    String queryString = "SELECT * from " + SEPARATOR + "portfolios1 WHERE shortID = $1 LIMIT $2 ";
    SelectResults result;
    Region region = CacheUtils.createRegion("portfolios1", Portfolio.class);
    for (int i = 0; i < 100; i++) {
      Portfolio p = new Portfolio(i);
      p.shortID = Integer.valueOf(i % 10).shortValue();
      region.put(i, p);
    }

    Object[] params = new Object[2];
    params[0] = 5;
    int[] limits = {1, 5, 0, 10};
    for (final int limit : limits) {
      params[1] = limit;

      SelectResults results =
          helpTestIndexForQuery(queryString, "shortID", SEPARATOR + "portfolios1", params);
      assertEquals(limit, results.size());
      // clear out indexes for next query.
      qs.removeIndexes();
    }

  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for ResultBag wrapped by a SelectResults
   *
   * Tests ResultBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryForResultBag() {
    try {
      Query query;
      SelectResults result;
      String queryString =
          "SELECT DISTINCT * FROM " + SEPARATOR + "portfolios pf WHERE pf.ID > 0 limit 5";
      query = qs.newQuery(queryString);
      final int[] num = new int[1];
      num[0] = 0;
      QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
        @Override
        public void afterIterationEvaluation(Object result) {
          num[0] += 1;
        }
      });
      result = (SelectResults) query.execute();
      assertEquals(5, num[0]);
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for ResultBag wrapped by a SelectResults . This test contains
   * projection attributes. Since the attribute is unique every time, the limit will be satisfied
   * with first 5 iterations
   *
   * Tests ResultBag behaviour
   *
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryForResultBagWithProjectionAttribute() {
    try {
      Query query;
      SelectResults result;
      String queryString =
          "SELECT DISTINCT pf.ID FROM " + SEPARATOR + "portfolios pf WHERE pf.ID > 0 limit 5";
      query = qs.newQuery(queryString);
      final int[] num = new int[1];
      num[0] = 0;
      QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
        @Override
        public void afterIterationEvaluation(Object result) {
          num[0] += 1;
        }
      });
      result = (SelectResults) query.execute();
      assertEquals(5, num[0]);
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for ResultBag wrapped by a SelectResults if the iteration included
   * duplicate elements. If the distinct clause is present then duplicate elements even if
   * satisfying the where clause should not be considered as part of the resultset as distinct will
   * eliminate them
   *
   * Tests ResultBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryWithDuplicatesInIterationForResultBag() {
    try {
      Region region1 = CacheUtils.createRegion("portfolios1", Portfolio.class);
      // Add 5 pairs of same Object starting from 11 to 20
      for (int i = 11; i < 21;) {
        region1.put(Integer.toString(i), new Portfolio(i));
        region1.put(Integer.toString(i + 1), new Portfolio(i));
        i += 2;
      }
      Query query;
      SelectResults result;
      final int[] num = new int[1];
      final int[] numRepeat = new int[1];
      numRepeat[0] = 0;
      final Set data = new HashSet();
      num[0] = 0;
      // In the worst possible case all the unique values come in
      // consecutive order & hence only 5 iterations will yield the
      // result
      QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
        @Override
        public void afterIterationEvaluation(Object result) {
          num[0] += 1;
        }

        @Override
        public void beforeIterationEvaluation(CompiledValue ritr, Object currObject) {
          if (data.contains(currObject)) {
            numRepeat[0] += 1;
          } else {
            data.add(currObject);
          }
        }

      });
      String queryString =
          "SELECT DISTINCT * FROM " + SEPARATOR + "portfolios1  pf WHERE pf.ID > 10 limit 5";
      query = qs.newQuery(queryString);
      result = (SelectResults) query.execute();
      assertEquals((5 + numRepeat[0]), num[0]);
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());

    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for ResultBag wrapped by a SelectResults if the iteration included
   * duplicate elements. If the distinct clause is present then duplicate elements even if
   * satisfying the where clause should not be considered as part as distinct will eliminate them.
   * This test validates the above behaviour if projection sttribute is present and the projection
   * attribute may be duplicate
   *
   * Tests ResultBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryWithDuplicatesInIterationWithProjectionAttributeForResultBag() {
    try {
      Region region1 = CacheUtils.createRegion("portfolios1", Portfolio.class);
      // Add 5 pairs of same Object starting from 11 to 20
      for (int i = 11; i < 21;) {
        region1.put(Integer.toString(i), new Portfolio(i));
        region1.put(Integer.toString(i + 1), new Portfolio(i));
        i += 2;
      }
      Query query;
      SelectResults result;
      final int[] num = new int[1];
      num[0] = 0;
      final int[] numRepeat = new int[1];
      numRepeat[0] = 0;
      final Set data = new HashSet();
      QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
        @Override
        public void afterIterationEvaluation(Object result) {
          num[0] += 1;
        }

        @Override
        public void beforeIterationEvaluation(CompiledValue ritr, Object currObject) {
          if (data.contains(currObject)) {
            numRepeat[0] += 1;
          } else {
            data.add(currObject);
          }
        }
      });
      String queryString =
          "SELECT DISTINCT pf.ID FROM " + SEPARATOR + "portfolios1  pf WHERE pf.ID > 10 limit 5";
      query = qs.newQuery(queryString);
      result = (SelectResults) query.execute();
      assertEquals((5 + numRepeat[0]), num[0]);
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());

    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for StructBag wrapped by a SelectResults . This test contains
   * projection attributes. Since the attribute is unique every time, the limit will be satisfied
   * with first 5 iterations
   *
   * Tests StructBag behaviour
   *
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryForStructBagWithProjectionAttribute() {
    try {
      Query query;
      SelectResults result;
      query = qs.newQuery(
          "SELECT DISTINCT pf.ID, pf.createTime FROM " + SEPARATOR
              + "portfolios pf WHERE pf.ID > 0 limit 5");
      final int[] num = new int[1];
      num[0] = 0;
      QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
        @Override
        public void afterIterationEvaluation(Object result) {
          num[0] += 1;
        }
      });
      result = (SelectResults) query.execute();
      assertEquals(5, num[0]);
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
      assertTrue(wrapper.getCollectionType().getElementType() instanceof StructType);
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for StructBag wrapped by a SelectResults
   *
   * Tests StructBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryForStructBag() {
    try {
      Query query;
      SelectResults result;
      String queryString =
          "SELECT DISTINCT * FROM " + SEPARATOR
              + "portfolios pf, pf.positions.values WHERE pf.ID > 0 limit 5";
      query = qs.newQuery(queryString);
      final int[] num = new int[1];
      num[0] = 0;
      QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
        @Override
        public void afterIterationEvaluation(Object result) {
          num[0] += 1;
        }
      });
      result = (SelectResults) query.execute();
      assertEquals(5, num[0]);
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for StructBag wrapped by a SelectResults
   *
   * Tests StructBag behaviour
   */
  @Ignore
  @Test
  public void testLimitQueryForStructBagWithRangeIndex() {
    try {
      Query query;
      SelectResults result;
      String queryString =
          "SELECT * FROM " + SEPARATOR
              + "portfolios pf, pf.positions.values pos WHERE pf.ID > 1  AND pos.secId = 'GOOG' limit 1";
      query = qs.newQuery(queryString);

      MyQueryObserverAdapter observer = new MyQueryObserverAdapter();
      QueryObserver old = QueryObserverHolder.setInstance(observer);

      Index index = qs.createIndex("idIndex", "pf.ID", SEPARATOR + "portfolios pf");
      Index posindex =
          qs.createIndex("posIndex", "pos.secId",
              SEPARATOR + "portfolios pf, pf.positions.values pos");
      assertNotNull(index);
      assertNotNull(posindex);
      result = (SelectResults) query.execute();
      assertEquals(1, result.size());
      assertFalse(observer.limitAppliedAtIndex);
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for StructBag wrapped by a SelectResults if the iteration included
   * duplicate elements. If the distinct clause is present then duplicate elements even if
   * satisfying the where clause should not be considered as part as distinct will eliminate them.
   * This test validates the above behaviour if projection attribute is present and the projection
   * attribute may be duplicate
   *
   * Tests StructBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryWithDuplicatesInIterationWithProjectionAttributeForStructBag() {
    try {
      Region region1 = CacheUtils.createRegion("portfolios1", Portfolio.class);
      // Add 5 pairs of same Object starting from 11 to 20
      for (int i = 11; i < 21;) {
        region1.put(Integer.toString(i), new Portfolio(i));
        region1.put(Integer.toString(i + 1), new Portfolio(i));
        i += 2;
      }
      Query query;
      SelectResults result;
      final int[] num = new int[1];
      num[0] = 0;
      final int[] numRepeat = new int[1];
      numRepeat[0] = 0;
      final Set data = new HashSet();

      QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
        @Override
        public void afterIterationEvaluation(Object result) {
          num[0] += 1;
        }

        @Override
        public void beforeIterationEvaluation(CompiledValue ritr, Object currObject) {
          if (data.contains(currObject)) {
            numRepeat[0] += 1;
          } else {
            data.add(currObject);
          }
        }
      });
      String queryString =
          "SELECT DISTINCT pf.ID , pf.createTime FROM " + SEPARATOR
              + "portfolios1  pf WHERE pf.ID > 10 limit 5";
      query = qs.newQuery(queryString);
      result = (SelectResults) query.execute();
      assertEquals((5 + numRepeat[0]), num[0]);
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());

    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for StructBag wrapped by a SelectResults if the iteration included
   * duplicate elements. If the distinct clause is present then duplicate elements even if
   * satisfying the where clause should not be considered as part of the resultset as distinct will
   * eliminate them
   *
   * Tests StructBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryWithDuplicatesInIterationForStructBag() {
    try {
      Region region1 = CacheUtils.createRegion("portfolios1", Portfolio.class);
      // Add 5 pairs of same Object starting from 11 to 20
      for (int i = 11; i < 21;) {
        region1.put(Integer.toString(i), new Portfolio(i));
        region1.put(Integer.toString(i + 1), new Portfolio(i));
        i += 2;
      }
      Query query;
      SelectResults result;
      final int[] num = new int[1];
      num[0] = 0;
      final int[] numRepeat = new int[1];
      numRepeat[0] = 0;
      final Set data = new HashSet();
      QueryObserver old = QueryObserverHolder.setInstance(new QueryObserverAdapter() {
        @Override
        public void afterIterationEvaluation(Object result) {
          num[0] += 1;
        }

        @Override
        public void beforeIterationEvaluation(CompiledValue ritr, Object currObject) {
          if (data.contains(currObject)) {
            numRepeat[0] += 1;
          } else if (currObject instanceof Portfolio) {
            data.add(currObject);
          }
        }
      });
      String queryString =
          "SELECT DISTINCT * FROM " + SEPARATOR
              + "portfolios1  pf, pf.collectionHolderMap.keySet  WHERE pf.ID > 10 limit 20";
      query = qs.newQuery(queryString);
      result = (SelectResults) query.execute();
      assertEquals((20 + 4 * numRepeat[0]), num[0]);
      assertTrue(result instanceof SelectResults);
      assertEquals(20, result.size());
      SelectResults wrapper = result;
      assertEquals(20, wrapper.asSet().size());

    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  // Asif:Test the index results behaviour also

  // Shobhit: Testing the Limit behavior now with indexes for all above test cases.

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for ResultBag wrapped by a SelectResults
   *
   * Tests ResultBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryForResultBagWithIndex() {
    try {
      Query query;
      SelectResults result;
      String queryString =
          "SELECT DISTINCT * FROM " + SEPARATOR + "portfolios pf WHERE pf.ID > 0 limit 5";
      query = qs.newQuery(queryString);
      MyQueryObserverAdapter observer = new MyQueryObserverAdapter();
      QueryObserver old = QueryObserverHolder.setInstance(observer);
      Index index = qs.createIndex("idIndex", "pf.ID", SEPARATOR + "portfolios pf");
      assertNotNull(index);
      result = (SelectResults) query.execute();
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
      assertTrue(observer.limitAppliedAtIndex);
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for ResultBag wrapped by a SelectResults . This test contains
   * projection attributes. Since the attribute is unique every time, the limit will be satisfied
   * with first 5 iterations
   *
   * Tests ResultBag behaviour
   *
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryForResultBagWithProjectionAttributeWithIndex() {
    try {
      Query query;
      SelectResults result;
      String queryString =
          "SELECT DISTINCT pf.ID FROM " + SEPARATOR + "portfolios pf WHERE pf.ID > 0 limit 5";
      query = qs.newQuery(queryString);

      MyQueryObserverAdapter observer = new MyQueryObserverAdapter();
      QueryObserver old = QueryObserverHolder.setInstance(observer);
      Index index = qs.createIndex("idIndex", "pf.ID", SEPARATOR + "portfolios pf");
      assertNotNull(index);

      result = (SelectResults) query.execute();
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
      assertTrue(observer.limitAppliedAtIndex);
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for ResultBag wrapped by a SelectResults if the iteration included
   * duplicate elements. If the distinct clause is present then duplicate elements even if
   * satisfying the where clause should not be considered as part of the resultset as distinct will
   * eliminate them
   *
   * Tests ResultBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryWithDuplicatesInIterationForResultBagWithIndex() {
    try {
      Region region1 = CacheUtils.createRegion("portfolios1", Portfolio.class);
      // Add 5 pairs of same Object starting from 11 to 20
      for (int i = 11; i < 21;) {
        region1.put(Integer.toString(i), new Portfolio(i));
        region1.put(Integer.toString(i + 1), new Portfolio(i));
        i += 2;
      }
      Query query;
      SelectResults result;

      MyQueryObserverAdapter observer = new MyQueryObserverAdapter();
      QueryObserver old = QueryObserverHolder.setInstance(observer);
      Index index = qs.createIndex("idIndex", "pf.ID", SEPARATOR + "portfolios1 pf");
      assertNotNull(index);

      String queryString =
          "SELECT DISTINCT * FROM " + SEPARATOR + "portfolios1  pf WHERE pf.ID > 10 limit 5";
      query = qs.newQuery(queryString);
      result = (SelectResults) query.execute();
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
      assertTrue(observer.limitAppliedAtIndex);
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for ResultBag wrapped by a SelectResults if the iteration included
   * duplicate elements. If the distinct clause is present then duplicate elements even if
   * satisfying the where clause should not be considered as part as distinct will eliminate them.
   * This test validates the above behaviour if projection sttribute is present and the projection
   * attribute may be duplicate
   *
   * Tests ResultBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryWithDuplicatesInIterationWithProjectionAttributeForResultBagWithIndex() {
    try {
      Region region1 = CacheUtils.createRegion("portfolios1", Portfolio.class);
      // Add 5 pairs of same Object starting from 11 to 20
      for (int i = 11; i < 21;) {
        region1.put(Integer.toString(i), new Portfolio(i));
        region1.put(Integer.toString(i + 1), new Portfolio(i));
        i += 2;
      }
      Query query;
      SelectResults result;

      MyQueryObserverAdapter observer = new MyQueryObserverAdapter();
      QueryObserver old = QueryObserverHolder.setInstance(observer);

      Index index = qs.createIndex("idIndex", "pf.ID", SEPARATOR + "portfolios1 pf");
      assertNotNull(index);
      String queryString =
          "SELECT DISTINCT pf.ID FROM " + SEPARATOR + "portfolios1  pf WHERE pf.ID > 10 limit 5";
      query = qs.newQuery(queryString);
      result = (SelectResults) query.execute();
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
      assertTrue(observer.limitAppliedAtIndex);
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for StructBag wrapped by a SelectResults . This test contains
   * projection attributes. Since the attribute is unique every time, the limit will be satisfied
   * with first 5 iterations
   *
   * Tests StructBag behaviour
   *
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryForStructBagWithProjectionAttributeWithIndex() {
    try {
      Query query;
      SelectResults result;
      query = qs.newQuery(
          "SELECT DISTINCT pf.ID, pf.createTime FROM " + SEPARATOR
              + "portfolios pf WHERE pf.ID > 0 limit 5");

      MyQueryObserverAdapter observer = new MyQueryObserverAdapter();
      QueryObserver old = QueryObserverHolder.setInstance(observer);

      Index index = qs.createIndex("idIndex", "pf.ID", SEPARATOR + "portfolios pf");
      assertNotNull(index);
      result = (SelectResults) query.execute();
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
      assertTrue(wrapper.getCollectionType().getElementType() instanceof StructType);
      assertTrue(observer.limitAppliedAtIndex);
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
   * Tests the limit functionality for Iter evaluated query with distinct clause This tests the
   * basic limit functionality for StructBag wrapped by a SelectResults
   *
   * Tests StructBag behaviour
   */
  @Test
  public void testLimitDistinctIterEvaluatedQueryForStructBagWithIndex() {
    try {
      Query query;
      SelectResults result;
      String queryString =
          "SELECT DISTINCT * FROM " + SEPARATOR
              + "portfolios pf, pf.positions.values WHERE pf.ID > 0 limit 5";
      query = qs.newQuery(queryString);

      MyQueryObserverAdapter observer = new MyQueryObserverAdapter();
      QueryObserver old = QueryObserverHolder.setInstance(observer);

      Index index =
          qs.createIndex("idIndex", "pf.ID", SEPARATOR + "portfolios pf, pf.positions.values");
      assertNotNull(index);
      result = (SelectResults) query.execute();
      assertTrue(result instanceof SelectResults);
      assertEquals(5, result.size());
      SelectResults wrapper = result;
      assertEquals(5, wrapper.asSet().size());
      // currently this is false because we disabled limit application at the range index level
      assertFalse(observer.limitAppliedAtIndex);
    } catch (Exception e) {
      CacheUtils.getLogger().error(e);
      fail(e.toString());
    } finally {
      QueryObserverHolder.setInstance(new QueryObserverAdapter());
    }
  }

  /**
