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.type;
  import org.apache.calcite.rel.type.RelDataType;
  import org.apache.calcite.rel.type.RelDataTypeFactory;
  import org.apache.calcite.rel.type.RelDataTypeField;
  import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
  import org.apache.calcite.rel.type.RelRecordType;
  
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.Lists;
  
  import org.junit.jupiter.api.Test;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  import static org.hamcrest.CoreMatchers.instanceOf;
  import static org.hamcrest.CoreMatchers.is;
  import static org.hamcrest.MatcherAssert.assertThat;
  import static org.hamcrest.Matchers.hasToString;
  import static org.junit.jupiter.api.Assertions.assertFalse;
  import static org.junit.jupiter.api.Assertions.assertNull;
  import static org.junit.jupiter.api.Assertions.assertTrue;
  import static org.junit.jupiter.api.Assertions.fail;
  
  /**
   * Test for {@link SqlTypeFactoryImpl}.
   */
  class SqlTypeFactoryTest {
  
    @Test void testLeastRestrictiveWithAny() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(Lists.newArrayList(f.sqlBigInt, f.sqlAny));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.ANY));
    }
  
    @Test void testLeastRestrictiveWithNumbers() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(Lists.newArrayList(f.sqlBigInt, f.sqlInt));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.BIGINT));
    }
  
    @Test void testLeastRestrictiveWithNullability() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(Lists.newArrayList(f.sqlVarcharNullable, f.sqlAny));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.ANY));
      assertThat(leastRestrictive.isNullable(), is(true));
    }
  
    /** Test case for
     * <a href="https://issues.apache.org/jira/browse/CALCITE-2994">[CALCITE-2994]
     * Least restrictive type among structs does not consider nullability</a>. */
    @Test void testLeastRestrictiveWithNullableStruct() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(ImmutableList.of(f.structOfIntNullable, f.structOfInt));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.ROW));
      assertThat(leastRestrictive.isNullable(), is(true));
    }
  
    @Test void testLeastRestrictiveWithNull() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(Lists.newArrayList(f.sqlNull, f.sqlNull));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.NULL));
      assertThat(leastRestrictive.isNullable(), is(true));
    }
  
    @Test void testLeastRestrictiveStructWithNull() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(Lists.newArrayList(f.sqlNull, f.structOfInt));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.ROW));
      assertThat(leastRestrictive.isNullable(), is(true));
    }
  
    @Test void testLeastRestrictiveForImpossibleWithArray() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.arraySqlChar10, f.sqlChar));
      assertNull(leastRestrictive);
    }
  
    @Test void testLeastRestrictiveForArrays() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.arraySqlChar10, f.arraySqlChar1));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.ARRAY));
      assertThat(leastRestrictive.isNullable(), is(false));
      assertThat(leastRestrictive.getComponentType().getPrecision(), is(10));
    }
  
    @Test void testLeastRestrictiveForMultisets() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.multisetSqlChar10Nullable, f.multisetSqlChar1));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.MULTISET));
      assertThat(leastRestrictive.isNullable(), is(true));
      assertThat(leastRestrictive.getComponentType().getPrecision(), is(10));
    }
  
    @Test void testLeastRestrictiveForMultisetsAndArrays() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.multisetSqlChar10Nullable, f.arraySqlChar1));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.MULTISET));
      assertThat(leastRestrictive.isNullable(), is(true));
      assertThat(leastRestrictive.getComponentType().getPrecision(), is(10));
    }
  
    @Test void testLeastRestrictiveForImpossibleWithMultisets() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.multisetSqlChar10Nullable, f.mapSqlChar1));
      assertNull(leastRestrictive);
    }
  
    @Test void testLeastRestrictiveForMaps() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.mapSqlChar10Nullable, f.mapSqlChar1));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.MAP));
      assertThat(leastRestrictive.isNullable(), is(true));
      assertThat(leastRestrictive.getKeyType().getPrecision(), is(10));
      assertThat(leastRestrictive.getValueType().getPrecision(), is(10));
    }
  
    @Test void testLeastRestrictiveForTimestamps() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.sqlTimestampPrec0, f.sqlTimestampPrec3));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.TIMESTAMP));
      assertThat(leastRestrictive.isNullable(), is(false));
      assertThat(leastRestrictive.getPrecision(), is(3));
    }
  
    @Test void testLeastRestrictiveForTimestamps2() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.sqlTimestampPrec3, f.sqlTimestampPrec0));
      assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.TIMESTAMP));
      assertThat(leastRestrictive.isNullable(), is(false));
      assertThat(leastRestrictive.getPrecision(), is(3));
    }
  
    @Test void testLeastRestrictiveForTimestampAndDate() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.sqlTimestampPrec3, f.sqlDate));
      assertNull(leastRestrictive);
    }
  
    @Test void testLeastRestrictiveForImpossibleWithMaps() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType leastRestrictive =
          f.typeFactory.leastRestrictive(
              Lists.newArrayList(f.mapSqlChar10Nullable, f.arraySqlChar1));
      assertNull(leastRestrictive);
    }
  
    /** Unit test for {@link SqlTypeUtil#comparePrecision(int, int)}
     * and  {@link SqlTypeUtil#maxPrecision(int, int)}. */
    @Test void testMaxPrecision() {
      final int un = RelDataType.PRECISION_NOT_SPECIFIED;
      checkPrecision(1, 1, 1, 0);
      checkPrecision(2, 1, 2, 1);
      checkPrecision(2, 100, 100, -1);
      checkPrecision(2, un, un, -1);
      checkPrecision(un, 2, un, 1);
      checkPrecision(un, un, un, 0);
    }
  
    /** Unit test for {@link ArraySqlType#getPrecedenceList()}. */
    @Test void testArrayPrecedenceList() {
      SqlTypeFixture f = new SqlTypeFixture();
      assertThat(checkPrecendenceList(f.arrayBigInt, f.arrayBigInt, f.arrayFloat),
          is(3));
      assertThat(
          checkPrecendenceList(f.arrayOfArrayBigInt, f.arrayOfArrayBigInt,
              f.arrayOfArrayFloat), is(3));
      assertThat(checkPrecendenceList(f.sqlBigInt, f.sqlBigInt, f.sqlFloat),
          is(3));
      assertThat(
          checkPrecendenceList(f.multisetBigInt, f.multisetBigInt,
              f.multisetFloat), is(3));
      assertThat(
          checkPrecendenceList(f.arrayBigInt, f.arrayBigInt,
              f.arrayBigIntNullable), is(0));
      try {
        int i = checkPrecendenceList(f.arrayBigInt, f.sqlBigInt, f.sqlInt);
        fail("Expected assert, got " + i);
      } catch (IllegalArgumentException e) {
        assertThat(e.getMessage(), is("must contain type: BIGINT"));
      }
    }
  
    private int checkPrecendenceList(RelDataType t, RelDataType type1, RelDataType type2) {
      return t.getPrecedenceList().compareTypePrecedence(type1, type2);
    }
  
    private void checkPrecision(int p0, int p1, int expectedMax,
        int expectedComparison) {
      assertThat(SqlTypeUtil.maxPrecision(p0, p1), is(expectedMax));
      assertThat(SqlTypeUtil.maxPrecision(p1, p0), is(expectedMax));
      assertThat(SqlTypeUtil.maxPrecision(p0, p0), is(p0));
      assertThat(SqlTypeUtil.maxPrecision(p1, p1), is(p1));
      assertThat(SqlTypeUtil.comparePrecision(p0, p1), is(expectedComparison));
      assertThat(SqlTypeUtil.comparePrecision(p0, p0), is(0));
      assertThat(SqlTypeUtil.comparePrecision(p1, p1), is(0));
    }
  
    /** Test case for
     * <a href="https://issues.apache.org/jira/browse/CALCITE-2464">[CALCITE-2464]
     * Allow to set nullability for columns of structured types</a>. */
    @Test void createStructTypeWithNullability() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataTypeFactory typeFactory = f.typeFactory;
      List<RelDataTypeField> fields = new ArrayList<>();
      RelDataTypeField field0 =
          new RelDataTypeFieldImpl("i", 0,
              typeFactory.createSqlType(SqlTypeName.INTEGER));
      RelDataTypeField field1 =
          new RelDataTypeFieldImpl("s", 1,
              typeFactory.createSqlType(SqlTypeName.VARCHAR));
      fields.add(field0);
      fields.add(field1);
      final RelDataType recordType = new RelRecordType(fields); // nullable false by default
      final RelDataType copyRecordType =
          typeFactory.createTypeWithNullability(recordType, true);
      assertFalse(recordType.isNullable());
      assertTrue(copyRecordType.isNullable());
    }
  
    /** Test case for
     * <a href="https://issues.apache.org/jira/browse/CALCITE-3429">[CALCITE-3429]
     * AssertionError thrown for user-defined table function with map argument</a>. */
    @Test void testCreateTypeWithJavaMapType() {
      SqlTypeFixture f = new SqlTypeFixture();
      RelDataType relDataType = f.typeFactory.createJavaType(Map.class);
      assertThat(relDataType.getSqlTypeName(), is(SqlTypeName.MAP));
      assertThat(relDataType.getKeyType().getSqlTypeName(), is(SqlTypeName.ANY));
  
      try {
        f.typeFactory.createSqlType(SqlTypeName.MAP);
        fail();
      } catch (AssertionError e) {
        assertThat(e.getMessage(), is("use createMapType() instead"));
      }
    }
  
    /** Test case for
     * <a href="https://issues.apache.org/jira/browse/CALCITE-3924">[CALCITE-3924]
     * Fix flakey test to handle TIMESTAMP and TIMESTAMP(0) correctly</a>. */
    @Test void testCreateSqlTypeWithPrecision() {
      SqlTypeFixture f = new SqlTypeFixture();
      checkCreateSqlTypeWithPrecision(f.typeFactory, SqlTypeName.TIME);
      checkCreateSqlTypeWithPrecision(f.typeFactory, SqlTypeName.TIMESTAMP);
      checkCreateSqlTypeWithPrecision(f.typeFactory, SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE);
      checkCreateSqlTypeWithPrecision(f.typeFactory, SqlTypeName.TIME_TZ);
      checkCreateSqlTypeWithPrecision(f.typeFactory, SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
      checkCreateSqlTypeWithPrecision(f.typeFactory, SqlTypeName.TIMESTAMP_TZ);
    }
  
    private void checkCreateSqlTypeWithPrecision(
        RelDataTypeFactory typeFactory, SqlTypeName sqlTypeName) {
      RelDataType ts = typeFactory.createSqlType(sqlTypeName);
      RelDataType tsWithoutPrecision = typeFactory.createSqlType(sqlTypeName, -1);
      RelDataType tsWithPrecision0 = typeFactory.createSqlType(sqlTypeName, 0);
      RelDataType tsWithPrecision1 = typeFactory.createSqlType(sqlTypeName, 1);
      RelDataType tsWithPrecision2 = typeFactory.createSqlType(sqlTypeName, 2);
      RelDataType tsWithPrecision3 = typeFactory.createSqlType(sqlTypeName, 3);
      // for instance, 8 exceeds max precision for timestamp which is 3
      RelDataType tsWithPrecision8 = typeFactory.createSqlType(sqlTypeName, 8);
  
      assertThat(ts, hasToString(sqlTypeName.getName() + "(0)"));
      assertThat(ts.getFullTypeString(), is(sqlTypeName.getName() + "(0) NOT NULL"));
      assertThat(tsWithoutPrecision, hasToString(sqlTypeName.getName()));
      assertThat(tsWithoutPrecision.getFullTypeString(), is(sqlTypeName.getName() + " NOT NULL"));
      assertThat(tsWithPrecision0, hasToString(sqlTypeName.getName() + "(0)"));
      assertThat(tsWithPrecision0.getFullTypeString(), is(sqlTypeName.getName() + "(0) NOT NULL"));
      assertThat(tsWithPrecision1, hasToString(sqlTypeName.getName() + "(1)"));
      assertThat(tsWithPrecision1.getFullTypeString(), is(sqlTypeName.getName() + "(1) NOT NULL"));
      assertThat(tsWithPrecision2, hasToString(sqlTypeName.getName() + "(2)"));
      assertThat(tsWithPrecision2.getFullTypeString(), is(sqlTypeName.getName() + "(2) NOT NULL"));
      assertThat(tsWithPrecision3, hasToString(sqlTypeName.getName() + "(3)"));
      assertThat(tsWithPrecision3.getFullTypeString(), is(sqlTypeName.getName() + "(3) NOT NULL"));
      assertThat(tsWithPrecision8, hasToString(sqlTypeName.getName() + "(3)"));
      assertThat(tsWithPrecision8.getFullTypeString(), is(sqlTypeName.getName() + "(3) NOT NULL"));
  
      assertThat(ts != tsWithoutPrecision, is(true));
      assertThat(ts == tsWithPrecision0, is(true));
      assertThat(tsWithPrecision3 == tsWithPrecision8, is(true));
    }
  
    /** Test that the {@code UNKNOWN} type is a {@link BasicSqlType} and remains
     * so when nullified. */
    @Test void testUnknownCreateWithNullabilityTypeConsistency() {
      final SqlTypeFixture f = new SqlTypeFixture();
  
      final RelDataType unknownType = f.typeFactory.createUnknownType();
      assertThat(unknownType, instanceOf(BasicSqlType.class));
      assertThat(unknownType.getSqlTypeName(), is(SqlTypeName.UNKNOWN));
      assertFalse(unknownType.isNullable());
      assertThat(unknownType.getFullTypeString(), is("UNKNOWN NOT NULL"));
  
      final RelDataType nullableType =
          f.typeFactory.createTypeWithNullability(unknownType, true);
      assertThat(nullableType, instanceOf(BasicSqlType.class));
      assertThat(nullableType.getSqlTypeName(), is(SqlTypeName.UNKNOWN));
      assertTrue(nullableType.isNullable());
      assertThat(nullableType.getFullTypeString(), is("UNKNOWN"));
  
      final RelDataType unknownType2 =
          f.typeFactory.createTypeWithNullability(nullableType, false);
      assertThat(unknownType2, is(unknownType));
      assertThat(unknownType2, instanceOf(BasicSqlType.class));
      assertThat(unknownType2.getSqlTypeName(), is(SqlTypeName.UNKNOWN));
      assertFalse(unknownType2.isNullable());
      assertThat(unknownType2.getFullTypeString(), is("UNKNOWN NOT NULL"));
    }
  }
  