/*
 * 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.drill.exec;

import org.apache.drill.test.ClusterFixture;
import org.apache.drill.test.ClusterTest;
import org.apache.drill.test.rowSet.RowSetComparison;
import org.apache.drill.categories.UnlikelyTest;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.exec.physical.rowSet.RowSet;
import org.apache.drill.exec.proto.UserBitShared;
import org.apache.drill.exec.record.metadata.SchemaBuilder;
import org.apache.drill.exec.record.metadata.TupleMetadata;
import org.apache.drill.exec.work.foreman.SqlUnsupportedException;
import org.apache.drill.exec.work.foreman.UnsupportedFunctionException;

import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import java.nio.file.Paths;

public class TestWindowFunctions extends ClusterTest {
  private static void throwAsUnsupportedException(UserException ex) throws Exception {
    SqlUnsupportedException.errorClassNameToException(ex.getOrCreatePBError(false).getException().getExceptionClass());
    throw ex;
  }

  @BeforeClass
  public static void setupTestFiles() throws Exception {
    startCluster(ClusterFixture.builder(dirTestWatcher));
    dirTestWatcher.copyResourceToRoot(Paths.get("multilevel/parquet"));
    dirTestWatcher.copyResourceToRoot(Paths.get("window"));
  }

  @Test // DRILL-3196
  public void testSinglePartition() throws Exception {
    final String query = "select sum(n_nationKey) over(partition by n_nationKey) as col1, count(*) over(partition by n_nationKey) as col2 \n" +
        "from cp.`tpch/nation.parquet`";

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{0\\} aggs .*\\[SUM\\(\\$0\\), COUNT\\(\\)",
        "Scan.*columns=\\[`n_nationKey`\\].*"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\].*"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("col1", "col2")
        .baselineValues(0l, 1l)
        .baselineValues(1l, 1l)
        .baselineValues(2l, 1l)
        .baselineValues(3l, 1l)
        .baselineValues(4l, 1l)
        .baselineValues(5l, 1l)
        .baselineValues(6l, 1l)
        .baselineValues(7l, 1l)
        .baselineValues(8l, 1l)
        .baselineValues(9l, 1l)
        .baselineValues(10l, 1l)
        .baselineValues(11l, 1l)
        .baselineValues(12l, 1l)
        .baselineValues(13l, 1l)
        .baselineValues(14l, 1l)
        .baselineValues(15l, 1l)
        .baselineValues(16l, 1l)
        .baselineValues(17l, 1l)
        .baselineValues(18l, 1l)
        .baselineValues(19l, 1l)
        .baselineValues(20l, 1l)
        .baselineValues(21l, 1l)
        .baselineValues(22l, 1l)
        .baselineValues(23l, 1l)
        .baselineValues(24l, 1l)
        .build()
        .run();
  }

  @Test // DRILL-3196
  public void testSinglePartitionDefinedInWindowList() throws Exception {
    final String query = "select sum(n_nationKey) over w as col \n" +
        "from cp.`tpch/nation.parquet` \n" +
        "window w as (partition by n_nationKey order by n_nationKey)";

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{0\\} order by \\[0\\].*SUM\\(\\$0\\)",
        "Scan.*columns=\\[`n_nationKey`\\].*"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\].*"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("col")
        .baselineValues(0l)
        .baselineValues(1l)
        .baselineValues(2l)
        .baselineValues(3l)
        .baselineValues(4l)
        .baselineValues(5l)
        .baselineValues(6l)
        .baselineValues(7l)
        .baselineValues(8l)
        .baselineValues(9l)
        .baselineValues(10l)
        .baselineValues(11l)
        .baselineValues(12l)
        .baselineValues(13l)
        .baselineValues(14l)
        .baselineValues(15l)
        .baselineValues(16l)
        .baselineValues(17l)
        .baselineValues(18l)
        .baselineValues(19l)
        .baselineValues(20l)
        .baselineValues(21l)
        .baselineValues(22l)
        .baselineValues(23l)
        .baselineValues(24l)
        .build()
        .run();
  }

  @Test(expected = UnsupportedFunctionException.class) // DRILL-3182
  public void testWindowFunctionWithDistinct() throws Exception {
    try {
      final String query = "explain plan for select a2, count(distinct b2) over(partition by a2) \n" +
          "from cp.`tpch/nation.parquet`";

      run(query);
    } catch(UserException ex) {
      throwAsUnsupportedException(ex);
      throw ex;
    }
  }

  @Test(expected = UnsupportedFunctionException.class) // DRILL-3188
  public void testWindowFrame() throws Exception {
    try {
      final String query = "select a2, sum(a2) over(partition by a2 order by a2 rows between 1 preceding and 1 following ) \n" +
          "from cp.`tpch/nation.parquet` t \n" +
          "order by a2";

      run(query);
    } catch(UserException ex) {
        throwAsUnsupportedException(ex);
        throw ex;
    }
  }

  @Test(expected = UnsupportedFunctionException.class) // DRILL-3326
  public void testWindowWithAlias() throws Exception {
    try {
      String query = "explain plan for SELECT sum(n_nationkey) OVER (PARTITION BY n_name ORDER BY n_name ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) as col2 \n" +
          "from cp.`tpch/nation.parquet`";

      run(query);
    } catch(UserException ex) {
      throwAsUnsupportedException(ex);
      throw ex;
    }
  }

  @Test(expected = UnsupportedFunctionException.class) // DRILL-3189
  public void testWindowWithAllowDisallow() throws Exception {
    try {
      final String query = "select sum(n_nationKey) over(partition by n_nationKey \n" +
          "rows between unbounded preceding and unbounded following disallow partial) \n" +
          "from cp.`tpch/nation.parquet` \n" +
          "order by n_nationKey";

      run(query);
    } catch(UserException ex) {
      throwAsUnsupportedException(ex);
      throw ex;
    }
  }

  @Test // DRILL-3360
  public void testWindowInWindow() throws Exception {
    String query = "select rank() over(order by row_number() over(order by n_nationkey)) \n" +
      "from cp.`tpch/nation.parquet`";

    client.queryBuilder()
      .sql(query)
      .userExceptionMatcher()
      .expectedType(UserBitShared.DrillPBError.ErrorType.VALIDATION)
      .match();
  }

  @Test // DRILL-3280
  public void testMissingOverWithWindowClause() throws Exception {
    String query = "select rank(), cume_dist() over w \n" +
      "from cp.`tpch/nation.parquet` \n" +
      "window w as (partition by n_name order by n_nationkey)";

    client.queryBuilder()
      .sql(query)
      .userExceptionMatcher()
      .expectedType(UserBitShared.DrillPBError.ErrorType.VALIDATION)
      .match();
  }

  @Test // DRILL-3601
  public void testLeadMissingOver() throws Exception {
    String query = "select lead(n_nationkey) from cp.`tpch/nation.parquet`";

    client.queryBuilder()
      .sql(query)
      .userExceptionMatcher()
      .expectedType(UserBitShared.DrillPBError.ErrorType.VALIDATION)
      .match();
  }

  @Test // DRILL-3649
  public void testMissingOverWithConstant() throws Exception {
    String query = "select NTILE(1) from cp.`tpch/nation.parquet`";

    client.queryBuilder()
      .sql(query)
      .userExceptionMatcher()
      .expectedType(UserBitShared.DrillPBError.ErrorType.VALIDATION)
      .match();
  }

  @Test // DRILL-3344
  public void testWindowGroupBy() throws Exception {
    String query = "explain plan for SELECT max(n_nationkey) OVER (), n_name as col2 \n" +
        "from cp.`tpch/nation.parquet` \n" +
        "group by n_name";

    client.queryBuilder()
      .sql(query)
      .userExceptionMatcher()
      .expectedType(UserBitShared.DrillPBError.ErrorType.VALIDATION)
      .match();
  }

  @Test // DRILL-3346
  @Category(UnlikelyTest.class)
  public void testWindowGroupByOnView() throws Exception {
    try {
      String createView = "create view testWindowGroupByOnView(a, b) as \n" +
          "select n_nationkey, n_name from cp.`tpch/nation.parquet`";
      String query = "explain plan for SELECT max(a) OVER (), b as col2 \n" +
          "from testWindowGroupByOnView \n" +
          "group by b";

      client.runSqlSilently("use dfs.tmp");
      client.runSqlSilently(createView);
      client.queryBuilder()
        .sql(query)
        .userExceptionMatcher()
        .expectedType(UserBitShared.DrillPBError.ErrorType.VALIDATION)
        .match();
    } finally {
      run("drop view testWindowGroupByOnView");
    }
  }

  @Test // DRILL-3188
  public void testWindowFrameEquivalentToDefault() throws Exception {
    final String query1 = "select sum(n_nationKey) over(partition by n_nationKey order by n_nationKey) as col\n" +
        "from cp.`tpch/nation.parquet` t \n" +
        "order by n_nationKey";

    final String query2 = "select sum(n_nationKey) over(partition by n_nationKey order by n_nationKey \n" +
        "range between unbounded preceding and current row) as col \n" +
        "from cp.`tpch/nation.parquet` t \n" +
        "order by n_nationKey";

    final String query3 = "select sum(n_nationKey) over(partition by n_nationKey \n" +
        "rows BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as col \n" +
        "from cp.`tpch/nation.parquet` t \n" +
        "order by n_nationKey";

    // Validate the plan
    final String[] expectedPlan1 = {"Window.*partition \\{0\\} order by \\[0\\].*SUM\\(\\$0\\)",
        "Scan.*columns=\\[`n_nationKey`\\].*"};
    final String[] excludedPatterns1 = {"Scan.*columns=\\[`\\*`\\].*"};

    client.queryBuilder()
      .sql(query1)
      .planMatcher()
      .include(expectedPlan1)
      .exclude(excludedPatterns1)
      .match();

    testBuilder()
        .sqlQuery(query1)
        .unOrdered()
        .baselineColumns("col")
        .baselineValues(0l)
        .baselineValues(1l)
        .baselineValues(2l)
        .baselineValues(3l)
        .baselineValues(4l)
        .baselineValues(5l)
        .baselineValues(6l)
        .baselineValues(7l)
        .baselineValues(8l)
        .baselineValues(9l)
        .baselineValues(10l)
        .baselineValues(11l)
        .baselineValues(12l)
        .baselineValues(13l)
        .baselineValues(14l)
        .baselineValues(15l)
        .baselineValues(16l)
        .baselineValues(17l)
        .baselineValues(18l)
        .baselineValues(19l)
        .baselineValues(20l)
        .baselineValues(21l)
        .baselineValues(22l)
        .baselineValues(23l)
        .baselineValues(24l)
        .build()
        .run();

    final String[] expectedPlan2 = {"Window.*partition \\{0\\} order by \\[0\\].*SUM\\(\\$0\\)",
        "Scan.*columns=\\[`n_nationKey`\\].*"};
    final String[] excludedPatterns2 = {"Scan.*columns=\\[`\\*`\\].*"};

    client.queryBuilder()
      .sql(query2)
      .planMatcher()
      .include(expectedPlan2)
      .exclude(excludedPatterns2)
      .match();

    testBuilder()
        .sqlQuery(query2)
        .unOrdered()
        .baselineColumns("col")
        .baselineValues(0l)
        .baselineValues(1l)
        .baselineValues(2l)
        .baselineValues(3l)
        .baselineValues(4l)
        .baselineValues(5l)
        .baselineValues(6l)
        .baselineValues(7l)
        .baselineValues(8l)
        .baselineValues(9l)
        .baselineValues(10l)
        .baselineValues(11l)
        .baselineValues(12l)
        .baselineValues(13l)
        .baselineValues(14l)
        .baselineValues(15l)
        .baselineValues(16l)
        .baselineValues(17l)
        .baselineValues(18l)
        .baselineValues(19l)
        .baselineValues(20l)
        .baselineValues(21l)
        .baselineValues(22l)
        .baselineValues(23l)
        .baselineValues(24l)
        .build()
        .run();

    final String[] expectedPlan3 = {"Window.*partition \\{0\\}.*SUM\\(\\$0\\)",
        "Scan.*columns=\\[`n_nationKey`\\].*"};
    final String[] excludedPatterns3 = {"Scan.*columns=\\[`\\*`\\].*"};

    client.queryBuilder()
      .sql(query3)
      .planMatcher()
      .include(expectedPlan3)
      .exclude(excludedPatterns3)
      .match();

    testBuilder()
        .sqlQuery(query3)
        .unOrdered()
        .baselineColumns("col")
        .baselineValues(0l)
        .baselineValues(1l)
        .baselineValues(2l)
        .baselineValues(3l)
        .baselineValues(4l)
        .baselineValues(5l)
        .baselineValues(6l)
        .baselineValues(7l)
        .baselineValues(8l)
        .baselineValues(9l)
        .baselineValues(10l)
        .baselineValues(11l)
        .baselineValues(12l)
        .baselineValues(13l)
        .baselineValues(14l)
        .baselineValues(15l)
        .baselineValues(16l)
        .baselineValues(17l)
        .baselineValues(18l)
        .baselineValues(19l)
        .baselineValues(20l)
        .baselineValues(21l)
        .baselineValues(22l)
        .baselineValues(23l)
        .baselineValues(24l)
        .build()
        .run();
  }

  @Test // DRILL-3204
  public void testWindowWithJoin() throws Exception {
    final String query = "select sum(t1.r_regionKey) over(partition by t1.r_regionKey) as col \n" +
        "from cp.`tpch/region.parquet` t1, cp.`tpch/nation.parquet` t2 \n" +
        "where t1.r_regionKey = t2.n_nationKey \n" +
        "group by t1.r_regionKey";

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{0\\}.*SUM\\(\\$0\\)",
        "Scan.*columns=\\[`n_nationKey`\\].*",
        "Scan.*columns=\\[`n_nationKey`\\].*"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\].*"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("col")
        .baselineValues(0l)
        .baselineValues(1l)
        .baselineValues(2l)
        .baselineValues(3l)
        .baselineValues(4l)
        .build()
        .run();
  }

  @Test // DRILL-3298
  public void testCountEmptyPartitionByWithExchange() throws Exception {
    String query = "select count(*) over (order by o_orderpriority) as cnt from dfs.`multilevel/parquet` where o_custkey < 100";
    try {
      // Validate the plan
      final String[] expectedPlan = {"Window.*order by \\[0\\].*COUNT\\(\\)",
          "Scan.*columns=\\[`o_custkey`, `o_orderpriority`\\]"};
      final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\]"};

      client.queryBuilder()
        .sql(query)
        .planMatcher()
        .include(expectedPlan)
        .exclude(excludedPatterns)
        .match();

      testBuilder()
          .sqlQuery(query)
          .ordered()
          .baselineColumns("cnt")
          .optionSettingQueriesForTestQuery("alter session set `planner.slice_target` = 1")
          .baselineValues(1l)
          .baselineValues(4l)
          .baselineValues(4l)
          .baselineValues(4l)
          .build()
          .run();
    } finally {
      run("alter session set `planner.slice_target` = " + ExecConstants.SLICE_TARGET_DEFAULT);
    }
  }

  /* Verify the output of aggregate functions (which are reduced
    * eg: avg(x) = sum(x)/count(x)) return results of the correct
    * data type (double)
    */
  @Test
  public void testAvgVarianceWindowFunctions() throws Exception {
    final String avgQuery = "select avg(n_nationkey) over (partition by n_nationkey) col1 " +
        "from cp.`tpch/nation.parquet` " +
        "where n_nationkey = 1";

    // Validate the plan
    final String[] expectedPlan1 = {"Window.*partition \\{0\\} aggs .*SUM\\(\\$0\\), COUNT\\(\\$0\\)",
        "Scan.*columns=\\[`n_nationkey`\\]"};
    final String[] excludedPatterns1 = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(avgQuery)
      .planMatcher()
      .include(expectedPlan1)
      .exclude(excludedPatterns1)
      .match();

    testBuilder()
        .sqlQuery(avgQuery)
        .unOrdered()
        .baselineColumns("col1")
        .baselineValues(1.0d)
        .go();

    final String varianceQuery = "select var_pop(n_nationkey) over (partition by n_nationkey) col1 " +
        "from cp.`tpch/nation.parquet` " +
        "where n_nationkey = 1";

    // Validate the plan
    final String[] expectedPlan2 = {"Window.*partition \\{0\\} aggs .*SUM\\(\\$2\\), SUM\\(\\$1\\), COUNT\\(\\$1\\)",
        "Scan.*columns=\\[`n_nationkey`\\]"};
    final String[] excludedPatterns2 = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(varianceQuery)
      .planMatcher()
      .include(expectedPlan2)
      .exclude(excludedPatterns2)
      .match();

    testBuilder()
        .sqlQuery(varianceQuery)
        .unOrdered()
        .baselineColumns("col1")
        .baselineValues(0.0d)
        .go();
  }

  @Test
  public void testWindowFunctionWithKnownType() throws Exception {
    final String query = "select sum(cast(col_int as int)) over (partition by col_varchar) as col1 " +
        "from cp.`jsoninput/large_int.json` limit 1";

    // Validate the plan
    final String[] expectedPlan1 = {"Window.*partition \\{0\\} aggs .*SUM\\(\\$1\\)",
        "Scan.*columns=\\[`col_varchar`, `col_int`\\]"};
    final String[] excludedPatterns1 = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan1)
      .exclude(excludedPatterns1)
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("col1")
        .baselineValues(2147483649l)
        .go();

    final String avgQuery = "select avg(cast(col_int as int)) over (partition by col_varchar) as col1 " +
        "from cp.`jsoninput/large_int.json` limit 1";

    // Validate the plan
    final String[] expectedPlan2 = {"Window.*partition \\{0\\} aggs .*SUM\\(\\$1\\), COUNT\\(\\$1\\)",
        "Scan.*columns=\\[`col_varchar`, `col_int`\\]"};
    final String[] excludedPatterns2 = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(avgQuery)
      .planMatcher()
      .include(expectedPlan2)
      .exclude(excludedPatterns2)
      .match();

    testBuilder()
        .sqlQuery(avgQuery)
        .unOrdered()
        .baselineColumns("col1")
        .baselineValues(1.0737418245E9d)
        .go();
  }

  @Test
  public void testCompoundIdentifierInWindowDefinition() throws Exception {
    String root = "/multilevel/csv/1994/Q1/orders_94_q1.csv";
    String query = String.format("SELECT count(*) OVER w as col1, count(*) OVER w as col2 \n" +
        "FROM cp.`%s` \n" +
        "WINDOW w AS (PARTITION BY columns[1] ORDER BY columns[0] DESC)", root);

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{1\\} order by \\[0 DESC\\].*COUNT\\(\\)",
        "Scan.*columns=\\[`columns`\\[0\\], `columns`\\[1\\]\\]"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    // Validate the result
    testBuilder()
        .sqlQuery(query)
        .ordered()
        .baselineColumns("col1", "col2")
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .baselineValues((long) 1, (long) 1)
        .build()
        .run();
  }

  @Test
  @Category(UnlikelyTest.class)
  public void testRankWithGroupBy() throws Exception {
    final String query = "select dense_rank() over (order by l_suppkey) as rank1 " +
        " from cp.`tpch/lineitem.parquet` group by l_partkey, l_suppkey order by 1 desc limit 1";

    // Validate the plan
    final String[] expectedPlan = {"Window.*order by \\[1\\].*DENSE_RANK\\(\\)",
        "Scan.*columns=\\[`l_partkey`, `l_suppkey`\\]"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("rank1")
        .baselineValues(100l)
        .go();
  }

  @Test // DRILL-3404
  public void testWindowSumAggIsNotNull() throws Exception {
    String query = "select count(*) cnt from (select sum ( c1 ) over ( partition by c2 order by c1 asc nulls first ) w_sum from dfs.`window/table_with_nulls.parquet` ) sub_query where w_sum is not null";

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{1\\} order by \\[0 ASC-nulls-first\\].*SUM\\(\\$0\\)",
        "Scan.*columns=\\[`c1`, `c2`\\]"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    testBuilder()
      .sqlQuery(query)
      .ordered()
      .baselineColumns("cnt")
      .baselineValues(26L)
      .build().run();
  }

  @Test // DRILL-3292
  public void testWindowConstants() throws Exception {
    String query =
        "select rank() over w fn, sum(2) over w sumINTEGER, sum(employee_id) over w sumEmpId,\n" +
            "sum(cast(0.5 as double)) over w sumFLOAT \n" +
        "from cp.`employee.json` \n" +
        "where position_id = 2 \n" +
        "window w as(partition by position_id order by employee_id)";

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{0\\} order by \\[1\\].*RANK\\(\\), \\$SUM0\\(\\$2\\), SUM\\(\\$1\\), \\$SUM0\\(\\$3\\)",
        "Scan.*columns=\\[`position_id`, `employee_id`\\]"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    testBuilder()
        .sqlQuery(query)
        .ordered()
        .baselineColumns("fn", "sumINTEGER", "sumEmpId", "sumFLOAT")
        .baselineValues(1L, 2L, 2L, 0.5)
        .baselineValues(2L, 4L, 6L, 1.0)
        .baselineValues(3L, 6L, 11L, 1.5)
        .baselineValues(4L, 8L, 31L, 2.0)
        .baselineValues(5L, 10L, 52L, 2.5)
        .baselineValues(6L, 12L, 74L, 3.0)
        .build()
        .run();
  }

  @Test // DRILL-3567
  public void testMultiplePartitions1() throws Exception {
    String root = "/store/text/data/t.json";
    String query = String.format("select count(*) over(partition by b1 order by c1) as count1, \n" +
        "sum(a1)  over(partition by b1 order by c1) as sum1, \n" +
        "count(*) over(partition by a1 order by c1) as count2 \n" +
        "from cp.`%s`", root);

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{2\\} order by \\[1\\].*COUNT\\(\\)",
        "Window.*partition \\{0\\} order by \\[1\\].*COUNT\\(\\), SUM\\(\\$2\\)",
        "Scan.*columns=\\[`b1`, `c1`, `a1`\\]"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("count1", "sum1", "count2")
        .baselineValues(1l, 0l, 2l)
        .baselineValues(1l, 0l, 2l)
        .baselineValues(2l, 0l, 5l)
        .baselineValues(3l, 0l, 5l)
        .baselineValues(3l, 0l, 5l)
        .baselineValues(1l, 10l, 2l)
        .baselineValues(1l, 10l, 2l)
        .baselineValues(2l, 20l, 5l)
        .baselineValues(3l, 30l, 5l)
        .baselineValues(3l, 30l, 5l)
        .build()
        .run();
  }

  @Test // DRILL-3567
  public void testMultiplePartitions2() throws Exception {
    String root = "/store/text/data/t.json";
    String query = String.format("select count(*) over(partition by b1 order by c1) as count1, \n" +
        "count(*) over(partition by a1 order by c1) as count2, \n" +
        "sum(a1)  over(partition by b1 order by c1) as sum1 \n" +
        "from cp.`%s`", root);

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{2\\} order by \\[1\\].*COUNT\\(\\)",
        "Window.*partition \\{0\\} order by \\[1\\].*COUNT\\(\\), SUM\\(\\$2\\)",
        "Scan.*columns=\\[`b1`, `c1`, `a1`\\]"};
    final String[] excludedPatterns = {"Scan.*columns=\\[`\\*`\\]"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(excludedPatterns)
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("count1", "count2", "sum1")
        .baselineValues(1l, 2l, 0l)
        .baselineValues(1l, 2l, 0l)
        .baselineValues(2l, 5l, 0l)
        .baselineValues(3l, 5l, 0l)
        .baselineValues(3l, 5l, 0l)
        .baselineValues(1l, 2l, 10l)
        .baselineValues(1l, 2l, 10l)
        .baselineValues(2l, 5l, 20l)
        .baselineValues(3l, 5l, 30l)
        .baselineValues(3l, 5l, 30l)
        .build()
        .run();
  }

  @Test // see DRILL-3574
  public void testWithAndWithoutPartitions() throws Exception {
    String root = "/store/text/data/t.json";
    String query = String.format("select sum(a1) over(partition by b1, c1) as s1, sum(a1) over() as s2 \n" +
        "from cp.`%s` \n" +
        "order by a1", root);
    run("alter session set `planner.slice_target` = 1");

    // Validate the plan
    final String[] expectedPlan = {"Window\\(window#0=\\[window\\(aggs .*\n" +
        ".*UnionExchange"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(new String[]{})
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("s1", "s2")
        .baselineValues(0l, 50l)
        .baselineValues(0l, 50l)
        .baselineValues(0l, 50l)
        .baselineValues(0l, 50l)
        .baselineValues(0l, 50l)
        .baselineValues(10l, 50l)
        .baselineValues(10l, 50l)
        .baselineValues(10l, 50l)
        .baselineValues(20l, 50l)
        .baselineValues(20l, 50l)
        .build()
        .run();
  }

  @Test // see DRILL-3657
  public void testConstantsInMultiplePartitions() throws Exception {
    String root = "/store/text/data/t.json";
    String query = String.format(
        "select sum(1) over(partition by b1 order by a1) as sum1, sum(1) over(partition by a1) as sum2, rank() over(order by b1) as rank1, rank() over(order by 1) as rank2 \n" +
        "from cp.`%s` \n" +
        "order by 1, 2, 3, 4", root);

    // Validate the plan
    final String[] expectedPlan = {"Window.*\\$SUM0\\(\\$3\\).*\n" +
        ".*SelectionVectorRemover.*\n" +
        ".*Sort.*\n" +
        ".*Window.*\\$SUM0\\(\\$2\\).*"
    };

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(new String[]{})
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("sum1", "sum2", "rank1", "rank2")
        .baselineValues(2l, 5l, 1l, 1l)
        .baselineValues(2l, 5l, 1l, 1l)
        .baselineValues(2l, 5l, 6l, 1l)
        .baselineValues(2l, 5l, 6l, 1l)
        .baselineValues(3l, 5l, 3l, 1l)
        .baselineValues(3l, 5l, 3l, 1l)
        .baselineValues(3l, 5l, 3l, 1l)
        .baselineValues(3l, 5l, 8l, 1l)
        .baselineValues(3l, 5l, 8l, 1l)
        .baselineValues(3l, 5l, 8l, 1l)
        .build()
        .run();
  }

  @Test // DRILL-3580
  public void testExpressionInWindowFunction() throws Exception {
    String root = "/store/text/data/t.json";
    String query = String.format("select a1, b1, sum(b1) over (partition by a1) as c1, sum(a1 + b1) over (partition by a1) as c2\n" +
        "from cp.`%s`", root);

    // Validate the plan
    final String[] expectedPlan = {"Window\\(window#0=\\[window\\(partition \\{0\\} aggs .*\\[SUM\\(\\$1\\), SUM\\(\\$2\\)\\]"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(new String[]{})
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("a1", "b1", "c1", "c2")
        .baselineValues(0l, 1l, 8l, 8l)
        .baselineValues(0l, 1l, 8l, 8l)
        .baselineValues(0l, 2l, 8l, 8l)
        .baselineValues(0l, 2l, 8l, 8l)
        .baselineValues(0l, 2l, 8l, 8l)
        .baselineValues(10l, 3l, 21l, 71l)
        .baselineValues(10l, 3l, 21l, 71l)
        .baselineValues(10l, 5l, 21l, 71l)
        .baselineValues(10l, 5l, 21l, 71l)
        .baselineValues(10l, 5l, 21l, 71l)
        .build()
        .run();
  }

  @Test // see DRILL-3657
  public void testProjectPushPastWindow() throws Exception {
    String query = "select sum(n_nationkey) over(partition by 1 order by 1) as col1, \n" +
            "count(n_nationkey) over(partition by 1 order by 1) as col2 \n" +
            "from cp.`tpch/nation.parquet` \n" +
            "limit 5";

    // Validate the plan
    final String[] expectedPlan = {"Scan.*columns=\\[`n_nationkey`\\].*"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(new String[]{})
      .match();

    testBuilder()
        .sqlQuery(query)
        .unOrdered()
        .baselineColumns("col1", "col2")
        .baselineValues(300l, 25l)
        .baselineValues(300l, 25l)
        .baselineValues(300l, 25l)
        .baselineValues(300l, 25l)
        .baselineValues(300l, 25l)
        .build()
        .run();
  }

  @Test // DRILL-3679, DRILL-3680
  @Category(UnlikelyTest.class)
  public void testWindowFunInNestSubQ() throws Exception {
    final String query =
        " select n_nationkey , n_regionkey , " +
        "        lead(n_regionkey) OVER ( PARTITION BY n_regionkey ORDER BY n_nationkey) lead_c2 " +
        " FROM (SELECT n_nationkey ,n_regionkey, " +
        "          ntile(3) over(PARTITION BY n_regionkey ORDER BY n_nationkey) " +
        "       FROM cp.`tpch/nation.parquet`) " +
        " order by n_regionkey, n_nationkey";
    run(query);

    final String baselineQuery =
        "select n_nationkey , n_regionkey , " +
        "       lead(n_regionkey) OVER ( PARTITION BY n_regionkey ORDER BY n_nationkey) lead_c2 " +
        "FROM cp.`tpch/nation.parquet`   " +
        "order by n_regionkey, n_nationkey";

    testBuilder()
        .sqlQuery(query)
        .ordered()
        .sqlBaselineQuery(baselineQuery)
        .build()
        .run();

    final String query2 =
         " select rnum, position_id, " +
         "   ntile(4) over(order by position_id) " +
         " from (select position_id, row_number() " +
         "       over(order by position_id) as rnum " +
         "       from cp.`employee.json`)";

    final String baselineQuery2 =
        " select row_number() over(order by position_id) as rnum, " +
        "    position_id, " +
        "    ntile(4) over(order by position_id) " +
        " from cp.`employee.json`";

    testBuilder()
        .sqlQuery(query2)
        .ordered()
        .sqlBaselineQuery(baselineQuery2)
        .build()
        .run();
  }

  @Test
  public void testStatisticalWindowFunctions() throws Exception {
    final String sqlWindowFunctionQuery = "select " +
        "stddev_samp(employee_id) over (partition by 1) c1, " +
        "stddev_pop(employee_id) over (partition by 1) c2, " +
        "var_samp(employee_id) over (partition by 1) c3, " +
        "var_pop(employee_id) over (partition by 1) c4 from " +
        "cp.`employee.json` limit 1";

    testBuilder()
        .sqlQuery(sqlWindowFunctionQuery)
        .unOrdered()
        .baselineColumns("c1", "c2", "c3", "c4")
        .baselineValues(333.56708470261117d, 333.4226520980038d, 111266.99999699896d, 111170.66493206649d)
        .build()
        .run();
  }

  @Test // DRILL-2330
  @Category(UnlikelyTest.class)
  public void testNestedAggregates() throws Exception {

    final String query = "select sum(min(l_extendedprice))"
        + " over (partition by l_suppkey order by l_suppkey) as totprice"
        + " from cp.`tpch/lineitem.parquet` where l_suppkey <= 10 group by l_suppkey order by 1 desc";

    // Validate the plan
    final String[] expectedPlan = {"Window.*partition \\{0\\} order by \\[0\\].*SUM\\(\\$1\\).*",
            "HashAgg\\(group=\\[\\{0\\}\\].*\\[MIN\\(\\$1\\)\\]\\)"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(new String[]{})
      .match();

    // Validate the results
    testBuilder()
            .sqlQuery(query)
            .unOrdered()
            .baselineColumns("totprice")
            .baselineValues(1107.2)
            .baselineValues(998.09)
            .baselineValues(957.05)
            .baselineValues(953.05)
            .baselineValues(931.03)
            .baselineValues(926.02)
            .baselineValues(909.0)
            .baselineValues(906.0)
            .baselineValues(904.0)
            .baselineValues(904.0)
            .go();
  }

  @Test // DRILL-4795, DRILL-4796
  public void testNestedAggregates1() throws Exception {
    {
      String query = "select sum(min(l_extendedprice)) over (partition by l_suppkey)\n"
              + " from cp.`tpch/nation.parquet` where l_suppkey <= 10";

      client.queryBuilder()
        .sql(query)
        .userExceptionMatcher()
        .include("Expression 'l_suppkey' is not being grouped")
        .match();
    }
    {
      String query = "select sum(min(l_extendedprice)) over (partition by l_suppkey) as totprice\n"
          + " from cp.`tpch/nation.parquet` where l_suppkey <= 10";

      client.queryBuilder()
        .sql(query)
        .userExceptionMatcher()
        .include("Expression 'l_suppkey' is not being grouped")
        .match();
    }
    {
      String query = "select sum(min(l_extendedprice)) over w1 as totprice\n"
          + " from cp.`tpch/nation.parquet` where l_suppkey <= 10\n"
          + " window w1 as (partition by l_suppkey)";

      client.queryBuilder()
        .sql(query)
        .userExceptionMatcher()
        .include("Expression 'l_suppkey' is not being grouped")
        .match();
    }
    {
      String query = "select sum(min(l_extendedprice)) over (partition by n_nationkey)\n"
              + " from cp.`tpch/nation.parquet` where l_suppkey <= 10 group by l_suppkey";

      client.queryBuilder()
        .sql(query)
        .userExceptionMatcher()
        .include("Expression 'n_nationkey' is not being grouped")
        .match();
    }
    {
      String query = "select sum(min(l_extendedprice)) over (partition by n_nationkey) as totprice\n"
          + " from cp.`tpch/nation.parquet` where l_suppkey <= 10 group by l_suppkey";

      client.queryBuilder()
        .sql(query)
        .userExceptionMatcher()
        .include("Expression 'n_nationkey' is not being grouped")
        .match();
    }
    {
      String query = "select sum(min(l_extendedprice)) over w2 as totprice\n"
          + " from cp.`tpch/nation.parquet` where l_suppkey <= 10 group by l_suppkey\n"
          + " window w2 as (partition by n_nationkey)";

      client.queryBuilder()
        .sql(query)
        .userExceptionMatcher()
        .include("Expression 'n_nationkey' is not being grouped")
        .match();
    }
  }


  @Test // DRILL-4469
  public void testWindowOnSubqueryWithStar() throws Exception {
    String query = "SELECT SUM(n_nationkey) OVER w as s\n" +
        "FROM (SELECT * FROM cp.`tpch/nation.parquet`) subQry\n" +
        "WINDOW w AS (PARTITION BY region ORDER BY n_nationkey)\n" +
        "limit 1";

    final String[] expectedPlan = {"Scan.*columns=\\[`n_nationkey`, `region`\\].*"};

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(new String[]{})
      .match();

    testBuilder()
        .sqlQuery(query)
        .ordered()
        .baselineColumns("s")
        .baselineValues(0L)
        .build()
        .run();
  }

  @Test
  public void testWindowFunctionWithQualifyClause() throws Exception {
    String query = "select employee_id, full_name, birth_date " +
        "from cp.`employee.json` " +
        "qualify row_number() over (order by employee_id) between 5 and 7";

    String[] expectedPlan = {
      "Filter\\(condition=\\[SEARCH\\(\\$\\d, Sarg\\[\\[5..7\\]\\]\\)\\]\\)",
      "Window\\(.*?\\[window\\(order by \\[\\d\\] rows between UNBOUNDED PRECEDING and CURRENT ROW aggs \\[ROW_NUMBER\\(\\)\\]\\)\\]\\)"
    };

    client.queryBuilder()
      .sql(query)
      .planMatcher()
      .include(expectedPlan)
      .exclude(new String[]{})
      .match();

    RowSet results = queryBuilder().sql(query).rowSet();

    TupleMetadata expectedSchema = new SchemaBuilder()
      .addNullable("employee_id", MinorType.BIGINT)
      .addNullable("full_name", MinorType.VARCHAR)
      .addNullable("birth_date", MinorType.VARCHAR)
      .buildSchema();

    RowSet expected = client.rowSetBuilder(expectedSchema)
       .addRow(6, "Roberta Damstra", "1942-10-08")
       .addRow(7, "Rebecca Kanagaki", "1949-03-27")
       .addRow(8, "Kim Brunner", "1922-08-10")
       .build();

    new RowSetComparison(expected).verifyAndClearAll(results);

  }
}
