content: /*
   * 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.calcite.test;
  
  import org.apache.calcite.adapter.druid.DruidDateTimeUtils;
  import org.apache.calcite.avatica.util.TimeUnitRange;
  import org.apache.calcite.rel.rules.DateRangeRules;
  import org.apache.calcite.rex.RexNode;
  import org.apache.calcite.sql.fun.SqlStdOperatorTable;
  import org.apache.calcite.test.RexImplicationCheckerFixtures.Fixture;
  import org.apache.calcite.util.TimestampString;
  import org.apache.calcite.util.Util;
  
  import com.google.common.collect.ImmutableList;
  
  import org.hamcrest.Matcher;
  import org.joda.time.Interval;
  import org.junit.jupiter.api.Test;
  
  import java.util.Calendar;
  import java.util.List;
  
  import static org.hamcrest.CoreMatchers.is;
  import static org.hamcrest.CoreMatchers.notNullValue;
  import static org.hamcrest.MatcherAssert.assertThat;
  import static org.hamcrest.Matchers.hasToString;
  
  /** Unit tests for {@link DateRangeRules} algorithms. */
  class DruidDateRangeRulesTest {
  
    @Test void testExtractYearAndMonthFromDateColumn() {
      final Fixture2 f = new Fixture2();
      // AND(>=($8, 2014-01-01), <($8, 2015-01-01), >=($8, 2014-06-01), <($8, 2014-07-01))
      checkDateRange(f,
          f.and(f.eq(f.exYear, f.literal(2014)), f.eq(f.exMonth, f.literal(6))),
          is("[2014-06-01T00:00:00.000Z/2014-07-01T00:00:00.000Z]"));
    }
  
    @Test void testRangeCalc() {
      final Fixture2 f = new Fixture2();
      checkDateRange(f,
          f.and(
              f.le(f.timestampLiteral(2011, Calendar.JANUARY, 1), f.ts),
              f.le(f.ts, f.timestampLiteral(2012, Calendar.FEBRUARY, 2))),
          is("[2011-01-01T00:00:00.000Z/2012-02-02T00:00:00.001Z]"));
    }
  
    @Test void testExtractYearAndDayFromDateColumn() {
      final Fixture2 f = new Fixture2();
      // AND(AND(>=($8, 2010-01-01), <($8, 2011-01-01)),
      //     OR(AND(>=($8, 2010-01-31), <($8, 2010-02-01)),
      //        AND(>=($8, 2010-03-31), <($8, 2010-04-01)),
      //        AND(>=($8, 2010-05-31), <($8, 2010-06-01)),
      //        AND(>=($8, 2010-07-31), <($8, 2010-08-01)),
      //        AND(>=($8, 2010-08-31), <($8, 2010-09-01)),
      //        AND(>=($8, 2010-10-31), <($8, 2010-11-01)),
      //        AND(>=($8, 2010-12-31), <($8, 2011-01-01))))
      checkDateRange(f,
          f.and(f.eq(f.exYear, f.literal(2010)), f.eq(f.exDay, f.literal(31))),
          is("[2010-01-31T00:00:00.000Z/2010-02-01T00:00:00.000Z, "
              + "2010-03-31T00:00:00.000Z/2010-04-01T00:00:00.000Z, "
              + "2010-05-31T00:00:00.000Z/2010-06-01T00:00:00.000Z, "
              + "2010-07-31T00:00:00.000Z/2010-08-01T00:00:00.000Z, "
              + "2010-08-31T00:00:00.000Z/2010-09-01T00:00:00.000Z, "
              + "2010-10-31T00:00:00.000Z/2010-11-01T00:00:00.000Z, "
              + "2010-12-31T00:00:00.000Z/2011-01-01T00:00:00.000Z]"));
    }
  
    @Test void testExtractYearMonthDayFromDateColumn() {
      final Fixture2 f = new Fixture2();
      // AND(>=($8, 2011-01-01),"
      //     AND(>=($8, 2011-01-01), <($8, 2020-01-01)),
      //     OR(AND(>=($8, 2011-02-01), <($8, 2011-03-01)),
      //        AND(>=($8, 2012-02-01), <($8, 2012-03-01)),
      //        AND(>=($8, 2013-02-01), <($8, 2013-03-01)),
      //        AND(>=($8, 2014-02-01), <($8, 2014-03-01)),
      //        AND(>=($8, 2015-02-01), <($8, 2015-03-01)),
      //        AND(>=($8, 2016-02-01), <($8, 2016-03-01)),
      //        AND(>=($8, 2017-02-01), <($8, 2017-03-01)),
      //        AND(>=($8, 2018-02-01), <($8, 2018-03-01)),
      //        AND(>=($8, 2019-02-01), <($8, 2019-03-01))),
      //     OR(AND(>=($8, 2012-02-29), <($8, 2012-03-01)),
      //        AND(>=($8, 2016-02-29), <($8, 2016-03-01))))
      checkDateRange(f,
          f.and(f.gt(f.exYear, f.literal(2010)), f.lt(f.exYear, f.literal(2020)),
              f.eq(f.exMonth, f.literal(2)), f.eq(f.exDay, f.literal(29))),
          is("[2012-02-29T00:00:00.000Z/2012-03-01T00:00:00.000Z, "
              + "2016-02-29T00:00:00.000Z/2016-03-01T00:00:00.000Z]"));
    }
  
    @Test void testExtractYearMonthDayFromTimestampColumn() {
      final Fixture2 f = new Fixture2();
      // AND(>=($9, 2011-01-01),
      //     AND(>=($9, 2011-01-01), <($9, 2020-01-01)),
      //     OR(AND(>=($9, 2011-02-01), <($9, 2011-03-01)),
      //        AND(>=($9, 2012-02-01), <($9, 2012-03-01)),
      //        AND(>=($9, 2013-02-01), <($9, 2013-03-01)),
      //        AND(>=($9, 2014-02-01), <($9, 2014-03-01)),
      //        AND(>=($9, 2015-02-01), <($9, 2015-03-01)),
      //        AND(>=($9, 2016-02-01), <($9, 2016-03-01)),
      //        AND(>=($9, 2017-02-01), <($9, 2017-03-01)),
      //        AND(>=($9, 2018-02-01), <($9, 2018-03-01)),
      //        AND(>=($9, 2019-02-01), <($9, 2019-03-01))),
      //     OR(AND(>=($9, 2012-02-29), <($9, 2012-03-01)),"
      //        AND(>=($9, 2016-02-29), <($9, 2016-03-01))))
      checkDateRange(f,
          f.and(f.gt(f.exYear, f.literal(2010)),
              f.lt(f.exYear, f.literal(2020)),
              f.eq(f.exMonth, f.literal(2)), f.eq(f.exDay, f.literal(29))),
          is("[2012-02-29T00:00:00.000Z/2012-03-01T00:00:00.000Z, "
              + "2016-02-29T00:00:00.000Z/2016-03-01T00:00:00.000Z]"));
    }
  
    /** Test case for
     * <a href="https://issues.apache.org/jira/browse/CALCITE-1738">[CALCITE-1738]
     * Push CAST of literals to Druid</a>. */
    @Test void testFilterWithCast() {
      final Fixture2 f = new Fixture2();
      final Calendar c = Util.calendar();
      c.clear();
      c.set(2010, Calendar.JANUARY, 1);
      final TimestampString from = TimestampString.fromCalendarFields(c);
      c.clear();
      c.set(2011, Calendar.JANUARY, 1);
      final TimestampString to = TimestampString.fromCalendarFields(c);
  
      // d >= 2010-01-01 AND d < 2011-01-01
      checkDateRangeNoSimplify(f,
          f.and(
              f.ge(f.d, f.cast(f.timestampDataType, f.timestampLiteral(from))),
              f.lt(f.d, f.cast(f.timestampDataType, f.timestampLiteral(to)))),
          is("[2010-01-01T00:00:00.000Z/2011-01-01T00:00:00.000Z]"));
    }
  
    // For testFilterWithCast we need to no simplify the expression, which would
    // remove the CAST, in order to match the way expressions are presented when
    // HiveRexExecutorImpl is used in Hive
    private void checkDateRangeNoSimplify(Fixture f, RexNode e,
        Matcher<String> intervalMatcher) {
      e = DateRangeRules.replaceTimeUnits(f.rexBuilder, e, "UTC");
      final List<Interval> intervals =
          DruidDateTimeUtils.createInterval(e);
      assertThat(intervals, notNullValue());
      assertThat(intervals, hasToString(intervalMatcher));
    }
  
    private void checkDateRange(Fixture f, RexNode e, Matcher<String> intervalMatcher) {
      e = DateRangeRules.replaceTimeUnits(f.rexBuilder, e, "UTC");
      final RexNode e2 = f.simplify.simplify(e);
      List<Interval> intervals =
          DruidDateTimeUtils.createInterval(e2);
      if (intervals == null) {
        throw new AssertionError("null interval");
      }
      assertThat(intervals, hasToString(intervalMatcher));
    }
  
    /** Common expressions across tests. */
    private static class Fixture2 extends Fixture {
      private final RexNode exYear;
      private final RexNode exMonth;
      private final RexNode exDay;
  
      Fixture2() {
        exYear =
            rexBuilder.makeCall(SqlStdOperatorTable.EXTRACT,
                ImmutableList.of(rexBuilder.makeFlag(TimeUnitRange.YEAR), ts));
        exMonth =
            rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.EXTRACT,
                ImmutableList.of(rexBuilder.makeFlag(TimeUnitRange.MONTH), ts));
        exDay =
            rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.EXTRACT,
                ImmutableList.of(rexBuilder.makeFlag(TimeUnitRange.DAY), ts));
      }
  
      public RexNode timestampLiteral(int year, int month, int day) {
        final Calendar c = Util.calendar();
        c.clear();
        c.set(year, month, day);
        final TimestampString ts = TimestampString.fromCalendarFields(c);
        return timestampLiteral(ts);
      }
    }
  }
  