This is an automated email from the ASF dual-hosted git repository.

mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new b25114f7c6 [CALCITE-7529] Casts between literals and TIME/TIMESTAMP 
can lose precision beyond milliseconds
b25114f7c6 is described below

commit b25114f7c616b6e3fb4007071406f79e765ff66f
Author: zzwqqq <[email protected]>
AuthorDate: Tue Jun 2 14:14:51 2026 +0800

    [CALCITE-7529] Casts between literals and TIME/TIMESTAMP can lose precision 
beyond milliseconds
---
 .../adapter/enumerable/RexToLixTranslator.java     |   6 +-
 .../java/org/apache/calcite/rex/RexBuilder.java    | 127 +++++++++++++
 .../org/apache/calcite/rex/RexExecutorImpl.java    |  27 ++-
 .../org/apache/calcite/util/BuiltInMethod.java     |   4 +
 .../org/apache/calcite/rex/RexExecutorTest.java    | 207 +++++++++++++++++++++
 .../org/apache/calcite/test/SqlOperatorTest.java   |   2 +-
 6 files changed, 367 insertions(+), 6 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index 61fa2b0d71..7d2cb6b732 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -491,7 +491,8 @@ private Expression getConvertExpression(
 
       case TIME:
         return RexImpTable.optimize2(operand, 
Expressions.isConstantNull(format)
-            ? Expressions.call(BuiltInMethod.UNIX_TIME_TO_STRING.method, 
operand)
+            ? 
Expressions.call(BuiltInMethod.UNIX_TIME_TO_STRING_WITH_PRECISION.method,
+                operand, Expressions.constant(sourceType.getPrecision()))
             : Expressions.call(
                 Expressions.new_(
                     BuiltInMethod.FORMAT_TIME.method.getDeclaringClass()),
@@ -508,7 +509,8 @@ private Expression getConvertExpression(
 
       case TIMESTAMP:
         return RexImpTable.optimize2(operand, 
Expressions.isConstantNull(format)
-            ? Expressions.call(BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method, 
operand)
+            ? 
Expressions.call(BuiltInMethod.UNIX_TIMESTAMP_TO_STRING_WITH_PRECISION.method,
+                operand, Expressions.constant(sourceType.getPrecision()))
             : Expressions.call(
                 Expressions.new_(
                     BuiltInMethod.FORMAT_TIMESTAMP.method.getDeclaringClass()),
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java 
b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index b77fe02830..95030b5b90 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -29,17 +29,21 @@
 import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
 import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.SqlFunctions;
+import org.apache.calcite.sql.SqlAbstractDateTimeLiteral;
 import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlCollation;
 import org.apache.calcite.sql.SqlIntervalQualifier;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.SqlTimeLiteral;
+import org.apache.calcite.sql.SqlTimestampLiteral;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.fun.SqlCountAggFunction;
 import org.apache.calcite.sql.fun.SqlLibraryOperators;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.parser.SqlParserUtil;
 import org.apache.calcite.sql.type.ArraySqlType;
 import org.apache.calcite.sql.type.IntervalSqlType;
 import org.apache.calcite.sql.type.MapSqlType;
@@ -805,6 +809,12 @@ public RexNode makeCast(
           && SqlTypeUtil.isExactNumeric(type)) {
         return makeCastBooleanToExact(type, exp);
       }
+      final RexNode literalCast =
+          makeCastForTemporalLiteral(
+              pos, type, literal, matchNullability, safe, format);
+      if (literalCast != null) {
+        return literalCast;
+      }
       if (canRemoveCastFromLiteral(type, value, typeName)) {
         switch (typeName) {
         case INTERVAL_YEAR:
@@ -877,6 +887,123 @@ public RexNode makeCast(
     return makeAbstractCast(pos, type, exp, safe, format);
   }
 
+  private @Nullable RexNode makeCastForTemporalLiteral(
+      SqlParserPos pos,
+      RelDataType type,
+      RexLiteral literal,
+      boolean matchNullability,
+      boolean safe,
+      RexLiteral format) {
+    if (!format.isNull()) {
+      return null;
+    }
+    if (SqlTypeUtil.isCharacter(literal.getType())) {
+      return makeCastFromCharacterLiteralToTemporal(
+          pos, type, literal, matchNullability, safe, format);
+    }
+    if (SqlTypeUtil.isCharacter(type)) {
+      return makeCharacterLiteralFromTemporalLiteral(type, literal);
+    }
+    return null;
+  }
+
+  private @Nullable RexNode makeCastFromCharacterLiteralToTemporal(
+      SqlParserPos pos,
+      RelDataType type,
+      RexLiteral literal,
+      boolean matchNullability,
+      boolean safe,
+      RexLiteral format) {
+    final NlsString nlsString = literal.getValueAs(NlsString.class);
+    if (nlsString == null) {
+      return null;
+    }
+    final String value = nlsString.getValue().trim();
+    final RexNode temporalLiteral;
+    try {
+      switch (type.getSqlTypeName()) {
+      case TIME:
+        final SqlTimeLiteral timeLiteral =
+            SqlParserUtil.parseTimeLiteral(value, pos);
+        if (!isExactFractionalSecondLiteral(timeLiteral, value)) {
+          return null;
+        }
+        final TimeString time =
+            requireNonNull(timeLiteral.getValueAs(TimeString.class),
+                "timeLiteral.getValueAs(TimeString.class)");
+        temporalLiteral = makeTimeLiteral(time, type.getPrecision());
+        break;
+      case TIMESTAMP:
+        final SqlTimestampLiteral timestampLiteral =
+            SqlParserUtil.parseTimestampLiteral(value, pos);
+        if (!isExactFractionalSecondLiteral(timestampLiteral, value)) {
+          return null;
+        }
+        final TimestampString timestamp =
+            requireNonNull(timestampLiteral.getValueAs(TimestampString.class),
+                "timestampLiteral.getValueAs(TimestampString.class)");
+        temporalLiteral = makeTimestampLiteral(timestamp, type.getPrecision());
+        break;
+      default:
+        return null;
+      }
+    } catch (RuntimeException e) {
+      return safe ? makeNullLiteral(type) : null;
+    }
+    if (type.isNullable()
+        && !temporalLiteral.getType().isNullable()
+        && matchNullability) {
+      return makeAbstractCast(pos, type, temporalLiteral, safe, format);
+    }
+    return temporalLiteral;
+  }
+
+  /** Converts a TIME or TIMESTAMP literal to a character literal.
+   *
+   * <p>Returns null if the literal is not a TIME or TIMESTAMP, the literal
+   * cannot be read as the corresponding temporal string value, or the 
formatted
+   * temporal value does not fit in the target character type. */
+  private @Nullable RexNode makeCharacterLiteralFromTemporalLiteral(
+      RelDataType type,
+      RexLiteral literal) {
+    // Format temporal literals directly so they do not go through generated
+    // code, whose TIME/TIMESTAMP runtime values have millisecond precision.
+    final String value;
+    final int precision = literal.getType().getPrecision();
+    switch (literal.getType().getSqlTypeName()) {
+    case TIME:
+      final TimeString time = literal.getValueAs(TimeString.class);
+      if (time == null) {
+        return null;
+      }
+      value = time.toString(precision);
+      break;
+    case TIMESTAMP:
+      final TimestampString timestamp = 
literal.getValueAs(TimestampString.class);
+      if (timestamp == null) {
+        return null;
+      }
+      value = timestamp.toString(precision);
+      break;
+    default:
+      return null;
+    }
+    // Only create a character literal if the formatted temporal value fits in
+    // the target type.
+    return SqlTypeUtil.comparePrecision(type.getPrecision(), value.length()) 
>= 0
+        ? makeLiteral(value, type, true)
+        : null;
+  }
+
+  /** Returns whether a parsed TIME or TIMESTAMP literal has fractional-second
+   * precision high enough to preserve the original character value exactly. */
+  private static boolean isExactFractionalSecondLiteral(
+      SqlAbstractDateTimeLiteral literal, String value) {
+    return literal.getPrec() != 0
+        && literal.getPrec() != RelDataType.PRECISION_NOT_SPECIFIED
+        && literal.toFormattedString().equals(value);
+  }
+
   /** Returns the lowest granularity unit for the given unit.
    * YEAR and MONTH intervals are stored as months;
    * HOUR, MINUTE, SECOND intervals are stored as milliseconds. */
diff --git a/core/src/main/java/org/apache/calcite/rex/RexExecutorImpl.java 
b/core/src/main/java/org/apache/calcite/rex/RexExecutorImpl.java
index 3a894d0fba..acfbbdc56d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexExecutorImpl.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexExecutorImpl.java
@@ -43,6 +43,7 @@
 
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Type;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -136,14 +137,34 @@ public static RexExecutable getExecutable(RexBuilder 
rexBuilder, List<RexNode> e
   @Override public void reduce(RexBuilder rexBuilder, List<RexNode> constExps,
       List<RexNode> reducedValues) {
     assert reducedValues.isEmpty();
+    final List<RexNode> exps = new ArrayList<>();
+    final List<Integer> ordinals = new ArrayList<>();
+    for (int i = 0; i < constExps.size(); i++) {
+      final RexNode constExp = constExps.get(i);
+      // Literals are already reduced. Keep them as Rex values instead of
+      // round-tripping through generated code.
+      if (!(constExp instanceof RexLiteral)) {
+        ordinals.add(i);
+        exps.add(constExp);
+      }
+    }
+    if (exps.isEmpty()) {
+      reducedValues.addAll(constExps);
+      return;
+    }
     try {
-      String code = compile(rexBuilder, constExps, (list, index, storageType) 
-> {
+      String code = compile(rexBuilder, exps, (list, index, storageType) -> {
         throw new UnsupportedOperationException();
       });
 
-      final RexExecutable executable = new RexExecutable(code, constExps);
+      final RexExecutable executable = new RexExecutable(code, exps);
       executable.setDataContext(dataContext);
-      executable.reduce(rexBuilder, constExps, reducedValues);
+      final List<RexNode> values = new ArrayList<>(exps.size());
+      executable.reduce(rexBuilder, exps, values);
+      reducedValues.addAll(constExps);
+      for (int i = 0; i < ordinals.size(); i++) {
+        reducedValues.set(ordinals.get(i), values.get(i));
+      }
     } catch (RuntimeException ex) {
       // Something went wrong during constant reduction (for example,
       // we may have attempted a division by zero).
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java 
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 98b67a02bb..81fc9d6f04 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -784,8 +784,12 @@ public enum BuiltInMethod {
       String.class, int.class),
   UNIX_DATE_TO_STRING(DateTimeUtils.class, "unixDateToString", int.class),
   UNIX_TIME_TO_STRING(DateTimeUtils.class, "unixTimeToString", int.class),
+  UNIX_TIME_TO_STRING_WITH_PRECISION(DateTimeUtils.class, "unixTimeToString",
+      int.class, int.class),
   UNIX_TIMESTAMP_TO_STRING(DateTimeUtils.class, "unixTimestampToString",
       long.class),
+  UNIX_TIMESTAMP_TO_STRING_WITH_PRECISION(DateTimeUtils.class,
+      "unixTimestampToString", long.class, int.class),
   INTERVAL_YEAR_MONTH_TO_STRING(DateTimeUtils.class,
       "intervalYearMonthToString", int.class, TimeUnitRange.class),
   INTERVAL_DAY_TIME_TO_STRING(DateTimeUtils.class, "intervalDayTimeToString",
diff --git a/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java 
b/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
index a98cffc000..16d2978355 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
@@ -15,11 +15,14 @@
  * limitations under the License.
  */
 package org.apache.calcite.rex;
+
 import org.apache.calcite.DataContext;
 import org.apache.calcite.DataContexts;
 import org.apache.calcite.avatica.util.ByteString;
+import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
 import org.apache.calcite.sql.SqlBinaryOperator;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
@@ -35,6 +38,7 @@
 import org.apache.calcite.util.DateString;
 import org.apache.calcite.util.NlsString;
 import org.apache.calcite.util.TestUtil;
+import org.apache.calcite.util.TimeString;
 import org.apache.calcite.util.TimestampString;
 import org.apache.calcite.util.Util;
 
@@ -160,6 +164,209 @@ protected void check(final Action action) {
     });
   }
 
+  @Test void testReduceTimeCastWithFractionalSeconds() {
+    checkHighPrecision((rexBuilder, executor) -> {
+      for (int precision = 1; precision <= 9; precision++) {
+        final RexNode cast =
+            rexBuilder.makeCast(
+                rexBuilder.getTypeFactory().createSqlType(SqlTypeName.TIME,
+                    precision),
+                rexBuilder.makeLiteral("12:34:56.123456789"));
+
+        final RexNode reduced = reduce(rexBuilder, executor, cast).get(0);
+
+        assertThat(reduced, instanceOf(RexLiteral.class));
+        assertThat(
+            ((RexLiteral) reduced).getValueAs(TimeString.class)
+                .toString(precision),
+            equalTo(new TimeString("12:34:56.123456789").round(precision)
+                .toString(precision)));
+      }
+    });
+  }
+
+  @Test void testReduceTimestampCastWithFractionalSeconds() {
+    checkHighPrecision((rexBuilder, executor) -> {
+      for (int precision = 1; precision <= 9; precision++) {
+        final RexNode cast =
+            rexBuilder.makeCast(
+                
rexBuilder.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP,
+                    precision),
+                rexBuilder.makeLiteral("2020-01-01 12:34:56.123456789"));
+
+        final RexNode reduced = reduce(rexBuilder, executor, cast).get(0);
+
+        assertThat(reduced, instanceOf(RexLiteral.class));
+        assertThat(
+            ((RexLiteral) reduced).getValueAs(TimestampString.class)
+                .toString(precision),
+            equalTo(new TimestampString("2020-01-01 12:34:56.123456789")
+                .round(precision).toString(precision)));
+      }
+    });
+  }
+
+  @Test void testReduceLiteralWithMicroseconds() {
+    checkHighPrecision((rexBuilder, executor) -> {
+      final RexLiteral timeLiteral =
+          rexBuilder.makeTimeLiteral(new TimeString("12:34:56.123456"), 6);
+      final RexLiteral timestampLiteral =
+          rexBuilder.makeTimestampLiteral(
+              new TimestampString("2020-01-01 12:34:56.123456"), 6);
+      final RexNode expression =
+          rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
+              rexBuilder.makeExactLiteral(BigDecimal.TEN),
+              rexBuilder.makeExactLiteral(BigDecimal.ONE));
+
+      final List<RexNode> reducedValues =
+          reduce(rexBuilder, executor, timeLiteral, expression, 
timestampLiteral);
+
+      assertThat(reducedValues, hasSize(3));
+      assertThat(
+          ((RexLiteral) 
reducedValues.get(0)).getValueAs(TimeString.class).toString(6),
+          equalTo("12:34:56.123456"));
+      assertThat(((RexLiteral) reducedValues.get(1)).getValue2(), 
equalTo(11L));
+      assertThat(
+          ((RexLiteral) reducedValues.get(2)).getValueAs(TimestampString.class)
+              .toString(6),
+          equalTo("2020-01-01 12:34:56.123456"));
+    });
+  }
+
+  @Test void testReduceTimeToVarcharWithFractionalSeconds() {
+    checkHighPrecision((rexBuilder, executor) -> {
+      final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
+      for (int precision = 1; precision <= 9; precision++) {
+        final RexNode castToTime =
+            rexBuilder.makeCast(
+                typeFactory.createSqlType(SqlTypeName.TIME,
+                precision),
+                rexBuilder.makeLiteral("12:34:56.123456789"));
+        final RexNode castToVarchar =
+            rexBuilder.makeCast(typeFactory.createSqlType(SqlTypeName.VARCHAR, 
30),
+                castToTime);
+
+        final RexNode reduced = reduce(rexBuilder, executor, 
castToVarchar).get(0);
+
+        assertThat(reduced, instanceOf(RexLiteral.class));
+        assertThat(((RexLiteral) reduced).getValueAs(String.class),
+            equalTo(new TimeString("12:34:56.123456789")
+                .toString(precision)));
+      }
+    });
+  }
+
+  @Test void testReduceTimeToVarcharWithMilliseconds() {
+    check((rexBuilder, executor) -> {
+      final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
+      final RexNode castToTime =
+          rexBuilder.makeCast(typeFactory.createSqlType(SqlTypeName.TIME, 3),
+              rexBuilder.makeLiteral("12:34:56.987654"));
+      final RexNode castToVarchar =
+          rexBuilder.makeCast(typeFactory.createSqlType(SqlTypeName.VARCHAR, 
30),
+              castToTime);
+
+      final RexNode reduced = reduce(rexBuilder, executor, 
castToVarchar).get(0);
+
+      assertThat(reduced, instanceOf(RexLiteral.class));
+      assertThat(((RexLiteral) reduced).getValueAs(String.class),
+          equalTo("12:34:56.987"));
+    });
+  }
+
+  @Test void testReduceTimeToVarcharWithZeroMilliseconds() {
+    check((rexBuilder, executor) -> {
+      final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
+      final RexNode castToTime =
+          rexBuilder.makeCast(typeFactory.createSqlType(SqlTypeName.TIME, 3),
+              rexBuilder.makeLiteral("12:34:56.000456"));
+      final RexNode castToVarchar =
+          rexBuilder.makeCast(typeFactory.createSqlType(SqlTypeName.VARCHAR, 
30),
+              castToTime);
+
+      final RexNode reduced = reduce(rexBuilder, executor, 
castToVarchar).get(0);
+
+      assertThat(reduced, instanceOf(RexLiteral.class));
+      assertThat(((RexLiteral) reduced).getValueAs(String.class),
+          equalTo("12:34:56.000"));
+    });
+  }
+
+  @Test void testReduceTimestampToVarcharWithFractionalSeconds() {
+    checkHighPrecision((rexBuilder, executor) -> {
+      final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
+      for (int precision = 1; precision <= 9; precision++) {
+        final RexNode castToTimestamp =
+            rexBuilder.makeCast(
+                typeFactory.createSqlType(SqlTypeName.TIMESTAMP,
+                precision),
+                rexBuilder.makeLiteral("2020-01-01 12:34:56.123456789"));
+        final RexNode castToVarchar =
+            rexBuilder.makeCast(typeFactory.createSqlType(SqlTypeName.VARCHAR, 
30),
+                castToTimestamp);
+
+        final RexNode reduced = reduce(rexBuilder, executor, 
castToVarchar).get(0);
+
+        assertThat(reduced, instanceOf(RexLiteral.class));
+        assertThat(((RexLiteral) reduced).getValueAs(String.class),
+            equalTo(new TimestampString("2020-01-01 12:34:56.123456789")
+                .toString(precision)));
+      }
+    });
+  }
+
+  @Test void testReduceTimestampToVarcharWithZeroMilliseconds() {
+    check((rexBuilder, executor) -> {
+      final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
+      final RexNode castToTimestamp =
+          rexBuilder.makeCast(typeFactory.createSqlType(SqlTypeName.TIMESTAMP, 
3),
+              rexBuilder.makeLiteral("2020-01-01 12:34:56.000456"));
+      final RexNode castToVarchar =
+          rexBuilder.makeCast(typeFactory.createSqlType(SqlTypeName.VARCHAR, 
30),
+              castToTimestamp);
+
+      final RexNode reduced = reduce(rexBuilder, executor, 
castToVarchar).get(0);
+
+      assertThat(reduced, instanceOf(RexLiteral.class));
+      assertThat(((RexLiteral) reduced).getValueAs(String.class),
+          equalTo("2020-01-01 12:34:56.000"));
+    });
+  }
+
+  private static void checkHighPrecision(Action action) {
+    action.check(new RexBuilder(highPrecisionTemporalTypeFactory()), 
executor());
+  }
+
+  private static List<RexNode> reduce(RexBuilder rexBuilder,
+      RexExecutorImpl executor, RexNode... nodes) {
+    final List<RexNode> reducedValues = new ArrayList<>();
+    executor.reduce(rexBuilder, ImmutableList.copyOf(nodes), reducedValues);
+    return reducedValues;
+  }
+
+  private static RelDataTypeFactory highPrecisionTemporalTypeFactory() {
+    return new JavaTypeFactoryImpl(
+        new RelDataTypeSystemImpl() {
+          @Override public int getMaxPrecision(SqlTypeName typeName) {
+            switch (typeName) {
+            case TIME:
+            case TIMESTAMP:
+              return 9;
+            default:
+              return super.getMaxPrecision(typeName);
+            }
+          }
+        });
+  }
+
+  private static RexExecutorImpl executor() {
+    return new RexExecutorImpl(
+        DataContexts.of(
+            ImmutableMap.of(
+                DataContext.Variable.TIME_ZONE.camelName, 
TimeZone.getTimeZone("GMT"),
+                DataContext.Variable.LOCALE.camelName, Locale.US)));
+  }
+
   private void checkConstant(final Object operand,
       final Function<RexBuilder, RexNode> function) {
     check((rexBuilder, executor) -> {
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java 
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index cf2792c5a6..c40f077d8d 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -2706,7 +2706,7 @@ private static void 
checkConcatWithSeparatorInPostgres(SqlOperatorFixture f) {
     f.checkString("concat_ws(',', 'a', array['b', 'c'], DATE '1945-02-24')",
         "a,[b, c],1945-02-24", "VARCHAR NOT NULL");
     f.checkString("concat_ws(',', timestamp '2024-07-06 12:15:48.678')",
-        "2024-07-06 12:15:48", "VARCHAR NOT NULL");
+        "2024-07-06 12:15:48.678", "VARCHAR NOT NULL");
     f.checkString("concat_ws(',', time '12:34:56', time '13:00:00', 2, 'abc')",
         "12:34:56,13:00:00,2,abc", "VARCHAR NOT NULL");
     f.checkString("concat_ws(',', null, null)", "", "VARCHAR NOT NULL");

Reply via email to