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.sql.dialect;
  
  import org.apache.calcite.avatica.util.TimeUnitRange;
  import org.apache.calcite.rel.type.RelDataType;
  import org.apache.calcite.sql.SqlBasicCall;
  import org.apache.calcite.sql.SqlCall;
  import org.apache.calcite.sql.SqlDialect;
  import org.apache.calcite.sql.SqlIdentifier;
  import org.apache.calcite.sql.SqlLiteral;
  import org.apache.calcite.sql.SqlNode;
  import org.apache.calcite.sql.SqlNodeList;
  import org.apache.calcite.sql.SqlWriter;
  import org.apache.calcite.sql.fun.SqlCase;
  import org.apache.calcite.sql.fun.SqlFloorFunction;
  import org.apache.calcite.sql.fun.SqlStdOperatorTable;
  import org.apache.calcite.sql.parser.SqlParserPos;
  
  import com.google.common.collect.ImmutableList;
  
  import org.checkerframework.checker.nullness.qual.Nullable;
  
  /**
   * A <code>SqlDialect</code> implementation for the Hsqldb database.
   */
  public class HsqldbSqlDialect extends SqlDialect {
    public static final SqlDialect.Context DEFAULT_CONTEXT = SqlDialect.EMPTY_CONTEXT
        .withDatabaseProduct(SqlDialect.DatabaseProduct.HSQLDB);
  
    public static final SqlDialect DEFAULT = new HsqldbSqlDialect(DEFAULT_CONTEXT);
  
    /** Creates an HsqldbSqlDialect. */
    public HsqldbSqlDialect(Context context) {
      super(context);
    }
  
    @Override public boolean supportsCharSet() {
      return false;
    }
  
    @Override public boolean supportsAggregateFunctionFilter() {
      return false;
    }
  
    @Override public boolean supportsWindowFunctions() {
      return false;
    }
  
    @Override public void unparseCall(SqlWriter writer, SqlCall call,
        int leftPrec, int rightPrec) {
      switch (call.getKind()) {
      case FLOOR:
        if (call.operandCount() != 2) {
          super.unparseCall(writer, call, leftPrec, rightPrec);
          return;
        }
  
        final SqlLiteral timeUnitNode = call.operand(1);
        final TimeUnitRange timeUnit = timeUnitNode.getValueAs(TimeUnitRange.class);
  
        final String translatedLit = convertTimeUnit(timeUnit);
        SqlCall call2 =
            SqlFloorFunction.replaceTimeUnitOperand(call, translatedLit,
                timeUnitNode.getParserPosition());
        SqlFloorFunction.unparseDatetimeFunction(writer, call2, "TRUNC", true);
        break;
  
      default:
        super.unparseCall(writer, call, leftPrec, rightPrec);
      }
    }
  
    @Override public void unparseOffsetFetch(SqlWriter writer, @Nullable SqlNode offset,
        @Nullable SqlNode fetch) {
      unparseFetchUsingLimit(writer, offset, fetch);
    }
  
    @Override public SqlNode rewriteSingleValueExpr(SqlNode aggCall, RelDataType relDataType) {
      final SqlNode operand = ((SqlBasicCall) aggCall).operand(0);
      final SqlLiteral nullLiteral = SqlLiteral.createNull(SqlParserPos.ZERO);
      final SqlNode unionOperand =
          SqlStdOperatorTable.VALUES.createCall(SqlParserPos.ZERO,
              SqlLiteral.createApproxNumeric("0", SqlParserPos.ZERO));
      // For hsqldb, generate
      //   CASE COUNT(*)
      //   WHEN 0 THEN NULL
      //   WHEN 1 THEN MIN(<result>)
      //   ELSE (VALUES 1 UNION ALL VALUES 1)
      //   END
      final SqlNode caseExpr =
          new SqlCase(SqlParserPos.ZERO,
              SqlStdOperatorTable.COUNT.createCall(SqlParserPos.ZERO,
                  ImmutableList.of(SqlIdentifier.STAR)),
              SqlNodeList.of(
                  SqlLiteral.createExactNumeric("0", SqlParserPos.ZERO),
                  SqlLiteral.createExactNumeric("1", SqlParserPos.ZERO)),
              SqlNodeList.of(
                  nullLiteral,
                  SqlStdOperatorTable.MIN.createCall(SqlParserPos.ZERO, operand)),
              SqlStdOperatorTable.SCALAR_QUERY.createCall(SqlParserPos.ZERO,
                  SqlStdOperatorTable.UNION_ALL
                      .createCall(SqlParserPos.ZERO, unionOperand, unionOperand)));
  
      LOGGER.debug("SINGLE_VALUE rewritten into [{}]", caseExpr);
  
      return caseExpr;
    }
  
    private static String convertTimeUnit(TimeUnitRange unit) {
      switch (unit) {
      case YEAR:
        return "YYYY";
      case MONTH:
        return "MM";
      case DAY:
        return "DD";
      case WEEK:
        return "WW";
      case HOUR:
        return "HH24";
      case MINUTE:
        return "MI";
      case SECOND:
        return "SS";
      default:
        throw new AssertionError("could not convert time unit to HSQLDB equivalent: "
            + unit);
      }
    }
  }
  