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

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.apache.geode.cache.Region;
import org.apache.geode.cache.query.CacheUtils;
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.Struct;
import org.apache.geode.cache.query.data.PortfolioPdx;
import org.apache.geode.cache.query.internal.CompiledSelect;
import org.apache.geode.cache.query.internal.DefaultQuery;
import org.apache.geode.cache.query.internal.aggregate.AbstractAggregator;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.cache.query.types.StructType;

public abstract class PdxGroupByTestImpl implements GroupByTestInterface {

  String queries[] = {"select  p.status as status, p.ID from /portfolio p where p.ID > 0 "// ResultSet

  };

  public abstract Region createRegion(String regionName, Class constraint);

  @Override
  @Test
  public void testConvertibleGroupByQuery_1() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);

    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.status as status, p.ID from /portfolio p where p.ID > 0 group by status, p.ID ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    assertTrue(cs.isDistinct());
    assertTrue(cs.isOrderBy());
    assertFalse(cs.isGroupBy());
  }

  @Override
  @Test
  public void testConvertibleGroupByQuery_refer_column() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr = "select  p.shortID  from /portfolio p where p.ID >= 0 group by p.shortID ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    SelectResults<Short> results = (SelectResults<Short>) query.execute();
    Iterator<Short> iter = results.asList().iterator();
    int counter = 0;
    while (iter.hasNext()) {
      Short shortID = iter.next();
      assertEquals(counter++, shortID.intValue());
    }
    assertEquals(39, counter - 1);
  }

  @Override
  @Test
  public void testConvertibleGroupByQuery_refer_column_alias_Bug520141() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.shortID as short_id  from /portfolio p where p.ID >= 0 group by short_id ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    SelectResults<Struct> results = (SelectResults<Struct>) query.execute();
    Iterator<Struct> iter = results.asList().iterator();
    int counter = 0;
    while (iter.hasNext()) {
      Struct str = iter.next();
      assertEquals(counter++, ((Short) str.get("short_id")).intValue());
    }
    assertEquals(39, counter - 1);
  }

  @Override
  @Test
  public void testAggregateFuncCountStar() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i % 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.status as status, Count(*) as countt from /portfolio p where p.ID > 0 group by status ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    int activeCount = 0;
    int inactiveCount = 0;
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.status.equals("active")) {
        ++activeCount;
      } else if (pf.status.equals("inactive")) {
        ++inactiveCount;
      } else {
        fail("unexpected status");
      }
    }

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Integer", fieldTypes[1].getSimpleClassName());
      if (struct.get("status").equals("active")) {
        assertEquals(activeCount, ((Integer) struct.get("countt")).intValue());
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(inactiveCount, ((Integer) struct.get("countt")).intValue());
      } else {
        fail("unexpected value of status");
      }
    }
    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Integer", fieldTypes[1].getSimpleClassName());

  }

  @Override
  @Test
  public void testAggregateFuncCountDistinctStar_1() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i % 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.status as status, count(distinct p.shortID) as countt from /portfolio p where p.ID > 0 group by status ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    Set<Object> distinctShortIDActive = new HashSet<Object>();
    Set<Object> distinctShortIDInactive = new HashSet<Object>();
    int inactiveCount = 0;
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.status.equals("active")) {
        distinctShortIDActive.add(pf.shortID);
      } else if (pf.status.equals("inactive")) {
        distinctShortIDInactive.add(pf.shortID);
      } else {
        fail("unexpected status");
      }
    }

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Integer", fieldTypes[1].getSimpleClassName());
      if (struct.get("status").equals("active")) {
        assertEquals(distinctShortIDActive.size(), ((Integer) struct.get("countt")).intValue());
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(distinctShortIDInactive.size(), ((Integer) struct.get("countt")).intValue());
      } else {
        fail("unexpected value of status");
      }
    }

    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Integer", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testAggregateFuncCountDistinctStar_2() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.status as status, COUNT(distinct p.shortID) as countt from /portfolio p where p.ID > 0 group by status ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    Set<Object> distinctShortIDActive = new HashSet<Object>();
    Set<Object> distinctShortIDInactive = new HashSet<Object>();
    int inactiveCount = 0;
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.status.equals("active")) {
        distinctShortIDActive.add(pf.shortID);
      } else if (pf.status.equals("inactive")) {
        distinctShortIDInactive.add(pf.shortID);
      } else {
        fail("unexpected status");
      }
    }

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Integer", fieldTypes[1].getSimpleClassName());
      if (struct.get("status").equals("active")) {
        assertEquals(distinctShortIDActive.size(), ((Integer) struct.get("countt")).intValue());
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(distinctShortIDInactive.size(), ((Integer) struct.get("countt")).intValue());
      } else {
        fail("unexpected value of status");
      }
    }

    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Integer", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testAggregateFuncSum() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.status as status, Sum(p.ID) as summ from /portfolio p where p.ID > 0 group by status ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    int activeSum = 0;
    int inactiveSum = 0;
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.status.equals("active")) {
        activeSum += pf.getID();
      } else if (pf.status.equals("inactive")) {
        inactiveSum += pf.getID();
      } else {
        fail("unexpected value of status");
      }
    }

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Number", fieldTypes[1].getSimpleClassName());

      if (struct.get("status").equals("active")) {
        assertEquals(activeSum, ((Integer) struct.get("summ")).intValue());
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(inactiveSum, ((Integer) struct.get("summ")).intValue());
      } else {
        fail("unexpected value of status");
      }
    }

    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Number", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testAggregateFuncSumDistinct() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.status as status, SUM (distinct p.shortID) as summ from /portfolio p where p.ID > 0 group by status ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    Set<Short> activeSum = new HashSet<Short>();
    Set<Short> inactiveSum = new HashSet<Short>();
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.status.equals("active")) {
        activeSum.add(pf.shortID);
      } else if (pf.status.equals("inactive")) {
        inactiveSum.add(pf.shortID);
      } else {
        fail("unexpected value of status");
      }
    }

    int activeSumm = 0, inactiveSumm = 0;
    for (Short val : activeSum) {
      activeSumm += val.intValue();
    }

    for (Short val : inactiveSum) {
      inactiveSumm += val.intValue();
    }

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Number", fieldTypes[1].getSimpleClassName());
      if (struct.get("status").equals("active")) {
        assertEquals(activeSumm, ((Integer) struct.get("summ")).intValue());
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(inactiveSumm, ((Integer) struct.get("summ")).intValue());
      } else {
        fail("unexpected value of status");
      }
    }

    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Number", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testAggregateFuncNoGroupBy() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr = "select   sum(p.ID) as summ , Max(p.ID) as maxx, min(p.ID) as minn,"
        + " avg(p.ID) as average from /portfolio p where p.ID > 0 ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(1, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    int sum = 0;
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      sum += pf.getID();
      if (pf.getID() > max) {
        max = pf.getID();
      }
      if (pf.getID() < min) {
        min = pf.getID();
      }
    }

    float avg = sum / rgn.size();

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      assertEquals(sum, ((Integer) struct.get("summ")).intValue());
      assertEquals(max, ((Integer) struct.get("maxx")).intValue());
      assertEquals(min, ((Integer) struct.get("minn")).intValue());
      assertEquals(avg, ((Number) struct.get("average")).floatValue(), 0.0f);
    }
  }

  @Override
  @Test
  public void testAggregateFuncAvg() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr = "select   p.status as status,  Avg(p.ID) as average from "
        + "/portfolio p where p.ID > 0 group by status";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    double sumIDActive = 0, sumIDInactive = 0;
    int numActive = 0, numInactive = 0;
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.getID() > 0) {
        if (pf.status.equals("active")) {
          sumIDActive += pf.getID();
          ++numActive;
        } else if (pf.status.equals("inactive")) {
          sumIDInactive += pf.getID();
          ++numInactive;
        }
      }
    }

    Number avgActive = AbstractAggregator.downCast(sumIDActive / numActive);
    Number avgInactive = AbstractAggregator.downCast(sumIDInactive / numInactive);

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Number", fieldTypes[1].getSimpleClassName());
      if (struct.get("status").equals("active")) {
        assertEquals(avgActive, struct.get("average"));
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(avgInactive, struct.get("average"));
      } else {
        fail("unexpected value of status");
      }
    }

    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Number", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testAggregateFuncAvgDistinct() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr = "select   p.status as status,  avg(distinct p.shortID) as average from "
        + "/portfolio p where p.ID > 0 group by status";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    Set<Short> sumIDActiveSet = new HashSet<Short>(), sumIDInactiveSet = new HashSet<Short>();

    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.getID() > 0) {
        if (pf.status.equals("active")) {
          sumIDActiveSet.add(pf.shortID);

        } else if (pf.status.equals("inactive")) {
          sumIDInactiveSet.add(pf.shortID);
        }
      }
    }
    double sumActive = 0, sumInactive = 0;
    for (Short shortt : sumIDActiveSet) {
      sumActive += shortt.doubleValue();
    }

    for (Short shortt : sumIDInactiveSet) {
      sumInactive += shortt.doubleValue();
    }

    Number avgActive = AbstractAggregator.downCast(sumActive / sumIDActiveSet.size());
    Number avgInactive = AbstractAggregator.downCast(sumInactive / sumIDInactiveSet.size());

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Number", fieldTypes[1].getSimpleClassName());

      if (struct.get("status").equals("active")) {
        assertEquals(avgActive, struct.get("average"));
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(avgInactive, struct.get("average"));
      } else {
        fail("unexpected value of status");
      }
    }
    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Number", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testAggregateFuncWithOrderBy() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 600; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      if (pf.status.equals("active")) {
        pf.shortID = (short) ((short) i % 5);
      } else {
        pf.shortID = (short) ((short) i % 11);
      }
      region.put("key-" + i, pf);
    }

    String[] queries = {
        "select   p.status as status,  avg(distinct p.shortID) as average from /portfolio p where p.ID > 0 group by status order by average",
        "select   p.shortID as shid,  avg( p.ID) as average from /portfolio p where p.ID > 0 group by shid order by average desc",
        "select   p.shortID as shid,  avg( p.ID) as average from /portfolio p where p.ID > 0 group by shid order by  avg(p.ID)",
        "select   p.shortID as shid,  avg( p.ID) as average from /portfolio p where p.ID > 0 group by shid order by  avg(p.ID) desc, shid asc",
        "select   p.shortID as shid,  avg( p.ID) as average from /portfolio p where p.ID > 0 group by shid order by  avg(p.ID) desc, shid desc",
        "select   p.status as status,  p.shortID as shid  from /portfolio p where p.ID > 0 group by status, shid order by  shid desc",
        "select   p.shortID as shid,  count(*) as countt  from /portfolio p where p.ID > 0 group by p.shortID order by  count(*) desc"};
    Object[][] r = new Object[queries.length][2];
    QueryService qs = CacheUtils.getQueryService();
    for (int i = 0; i < queries.length; ++i) {
      Query query = qs.newQuery(queries[i]);

      SelectResults sr = (SelectResults) query.execute();
      r[i][0] = sr;
    }

    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();

    // Compare each of the query results with queries fired without order by ,
    // but without order by
    ssOrrs.compareExternallySortedQueriesWithOrderBy(queries, r);
  }

  @Override
  @Test
  public void testComplexValueAggregateFuncAvgDistinct() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select   p.status as status,  avg(distinct element(select iter.shortID from /portfolio iter where iter.ID = p.ID) ) as average from "
            + "/portfolio p where p.ID > 0 group by status";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    Set<Short> sumIDActiveSet = new HashSet<Short>(), sumIDInactiveSet = new HashSet<Short>();

    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.getID() > 0) {
        if (pf.status.equals("active")) {
          sumIDActiveSet.add(pf.shortID);

        } else if (pf.status.equals("inactive")) {
          sumIDInactiveSet.add(pf.shortID);
        }
      }
    }
    double sumActive = 0, sumInactive = 0;
    for (Short shortt : sumIDActiveSet) {
      sumActive += shortt.doubleValue();
    }

    for (Short shortt : sumIDInactiveSet) {
      sumInactive += shortt.doubleValue();
    }

    Number avgActive = AbstractAggregator.downCast(sumActive / sumIDActiveSet.size());
    Number avgInactive = AbstractAggregator.downCast(sumInactive / sumIDInactiveSet.size());

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Number", fieldTypes[1].getSimpleClassName());

      if (struct.get("status").equals("active")) {
        assertEquals(avgActive, struct.get("average"));
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(avgInactive, struct.get("average"));
      } else {
        fail("unexpected value of status");
      }
    }
    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Number", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testAggregateFuncMax() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.status as status, Max(p.ID) as Maxx from /portfolio p where p.ID > 0 group by status ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    int activeMaxID = 0;
    int inactiveMaxID = 0;
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.status.equals("active")) {
        if (pf.getID() > activeMaxID) {
          activeMaxID = pf.getID();
        }
      } else if (pf.status.equals("inactive")) {
        if (pf.getID() > inactiveMaxID) {
          inactiveMaxID = pf.getID();
        }
      } else {
        fail("unexpected value of status");
      }
    }

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Number", fieldTypes[1].getSimpleClassName());

      if (struct.get("status").equals("active")) {
        assertEquals(activeMaxID, ((Integer) struct.get("Maxx")).intValue());
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(inactiveMaxID, ((Integer) struct.get("Maxx")).intValue());
      } else {
        fail("unexpected value of status");
      }
    }

    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Number", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testSumWithMultiColumnGroupBy() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }

    Map<String, Integer> expectedData = new HashMap<String, Integer>();
    for (Object o : region.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      String key = pf.status + "_" + pf.shortID;
      if (expectedData.containsKey(key)) {
        int sum = expectedData.get(key).intValue() + pf.getID();
        expectedData.put(key, sum);
      } else {
        expectedData.put(key, pf.getID());
      }
    }

    String queryStr =
        "select  p.status as status, p.shortID as shortID, sum(p.ID) as summ from /portfolio p"
            + " where p.ID > 0 group by status, shortID ";

    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(expectedData.size(), sr.size());
    Iterator iter = sr.iterator();

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("short", fieldTypes[1].getSimpleClassName());
      assertEquals("Number", fieldTypes[2].getSimpleClassName());

      String key = struct.get("status") + "_" + struct.get("shortID");
      int sum = ((Integer) struct.get("summ")).intValue();
      assertTrue(expectedData.containsKey(key));
      assertEquals(expectedData.get(key).intValue(), sum);
    }

    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("short", fieldTypes[1].getSimpleClassName());
    assertEquals("Number", fieldTypes[2].getSimpleClassName());

  }

  @Override
  @Test
  public void testAggregateFuncMin() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    String queryStr =
        "select  p.status as status, Min(p.ID) as Minn from /portfolio p where p.ID > 0 group by status ";
    QueryService qs = CacheUtils.getQueryService();
    Query query = qs.newQuery(queryStr);
    CompiledSelect cs = ((DefaultQuery) query).getSelect();
    SelectResults sr = (SelectResults) query.execute();
    assertTrue(sr.getCollectionType().getElementType().isStructType());
    assertEquals(2, sr.size());
    Iterator iter = sr.iterator();
    Region rgn = CacheUtils.getRegion("portfolio");
    int activeMinID = Integer.MAX_VALUE;
    int inactiveMinID = Integer.MAX_VALUE;
    for (Object o : rgn.values()) {
      PortfolioPdx pf = (PortfolioPdx) o;
      if (pf.status.equals("active")) {
        if (pf.getID() < activeMinID) {
          activeMinID = pf.getID();
        }
      } else if (pf.status.equals("inactive")) {
        if (pf.getID() < inactiveMinID) {
          inactiveMinID = pf.getID();
        }
      } else {
        fail("unexpected value of status");
      }
    }

    while (iter.hasNext()) {
      Struct struct = (Struct) iter.next();
      StructType structType = struct.getStructType();
      ObjectType[] fieldTypes = structType.getFieldTypes();
      assertEquals("String", fieldTypes[0].getSimpleClassName());
      assertEquals("Number", fieldTypes[1].getSimpleClassName());

      if (struct.get("status").equals("active")) {
        assertEquals(activeMinID, ((Integer) struct.get("Minn")).intValue());
      } else if (struct.get("status").equals("inactive")) {
        assertEquals(inactiveMinID, ((Integer) struct.get("Minn")).intValue());
      } else {
        fail("unexpected value of status");
      }
    }

    ObjectType elementType = sr.getCollectionType().getElementType();
    assertTrue(elementType.isStructType());
    StructType structType = (StructType) elementType;
    ObjectType[] fieldTypes = structType.getFieldTypes();
    assertEquals("String", fieldTypes[0].getSimpleClassName());
    assertEquals("Number", fieldTypes[1].getSimpleClassName());
  }

  @Override
  @Test
  public void testCompactRangeIndex() throws Exception {

    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    QueryService qs = CacheUtils.getQueryService();
    String[] queries = {
        "select pos.secId from  /portfolio  p, p.positions.values pos where NOT (pos.secId IN SET('SUN', 'ORCL')) group by pos.secId  ", // 6
        "select pos.secId , count(pos.ID) from /portfolio p, p.positions.values pos where  pos.secId > 'APPL' group by pos.secId ",// 7

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

    // Execute Queries without Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = null;
      try {
        q = CacheUtils.getQueryService().newQuery(queries[i]);
        CacheUtils.getLogger().info("Executing query: " + queries[i]);
        SelectResults sr = (SelectResults) q.execute();
        r[i][0] = sr;
        assertTrue(sr.size() > 0);
      } catch (Exception e) {
        e.printStackTrace();
        fail(q.getQueryString());
      }
    }
    // Create Indexes
    qs.createIndex("statusIndex", "status", "/portfolio");
    // Execute Queries with Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = null;
      try {
        q = CacheUtils.getQueryService().newQuery(queries[i]);
        CacheUtils.getLogger().info("Executing query: " + queries[i]);
        r[i][1] = q.execute();
        SelectResults rcw = (SelectResults) r[i][1];
        int indexLimit = queries[i].indexOf("limit");
        int limit = -1;
        boolean limitQuery = indexLimit != -1;
        if (limitQuery) {
          limit = Integer.parseInt(queries[i].substring(indexLimit + 5).trim());
        }
        assertTrue("Result size is " + rcw.size() + " and limit is " + limit,
            !limitQuery || rcw.size() <= limit);
        String colType = rcw.getCollectionType().getSimpleClassName();
      } catch (Exception e) {
        e.printStackTrace();
        fail(q.getQueryString());
      }
    }
    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();

    ssOrrs.CompareQueryResultsWithoutAndWithIndexes(r, queries.length, true, queries);
  }

  @Test
  public void testDistinctCountWithoutGroupBy() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    QueryService qs = CacheUtils.getQueryService();
    String[] queries = {
        "select  count(distinct pos.secId) from /portfolio p, p.positions.values pos where  pos.secId > 'APPL' ",// 10

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

    // Execute Queries without Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = null;
      try {
        q = CacheUtils.getQueryService().newQuery(queries[i]);
        CacheUtils.getLogger().info("Executing query: " + queries[i]);
        SelectResults sr = (SelectResults) q.execute();
        r[i][0] = sr;
        assertTrue(sr.size() > 0);
      } catch (Exception e) {
        e.printStackTrace();
        fail(q.getQueryString());
      }
    }
    // Create Indexes
    qs.createIndex("secIdIndex", "pos.secId", "/portfolio p, p.positions.values pos");
    // Execute Queries with Indexes
    for (int i = 0; i < queries.length; i++) {
      Query q = null;
      try {
        q = CacheUtils.getQueryService().newQuery(queries[i]);
        CacheUtils.getLogger().info("Executing query: " + queries[i]);
        r[i][1] = q.execute();
        SelectResults rcw = (SelectResults) r[i][1];
        int indexLimit = queries[i].indexOf("limit");
        int limit = -1;
        boolean limitQuery = indexLimit != -1;
        if (limitQuery) {
          limit = Integer.parseInt(queries[i].substring(indexLimit + 5).trim());
        }
        assertTrue("Result size is " + rcw.size() + " and limit is " + limit,
            !limitQuery || rcw.size() <= limit);
        String colType = rcw.getCollectionType().getSimpleClassName();
      } catch (Exception e) {
        e.printStackTrace();
        fail(q.getQueryString());
      }
    }
    StructSetOrResultsSet ssOrrs = new StructSetOrResultsSet();

    ssOrrs.CompareQueryResultsWithoutAndWithIndexes(r, queries.length, true, queries);

  }

  @Override
  @Test
  public void testLimitWithGroupBy() throws Exception {
    Region region = this.createRegion("portfolio", PortfolioPdx.class);
    for (int i = 1; i < 200; ++i) {
      PortfolioPdx pf = new PortfolioPdx(i);
      pf.shortID = (short) ((short) i / 5);
      region.put("key-" + i, pf);
    }
    QueryService qs = CacheUtils.getQueryService();
    String queryStr =
        "select pos.secId as a, count( *) as x from /portfolio p, p.positions.values pos group by a  limit 5 ";
    Query q = qs.newQuery(queryStr);

    SelectResults sr = (SelectResults) q.execute();
    assertEquals(5, sr.size());

    queryStr =
        "select pos.secId as a, count(*) as x from /portfolio p, p.positions.values pos group by a  limit 0 ";
    q = qs.newQuery(queryStr);

    sr = (SelectResults) q.execute();
    assertEquals(0, sr.size());

    queryStr =
        "select pos.secId as a, count(*) as x from /portfolio p, p.positions.values pos group by a  order by count(*) limit 5 ";
    q = qs.newQuery(queryStr);

    sr = (SelectResults) q.execute();
    assertEquals(5, sr.size());

  }

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

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

}
