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

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


The following commit(s) were added to refs/heads/main by this push:
     new 7a4f230f3e [#12903] fix(trino-connector): Keep MySQL time/timestamp 
precision in the Trino connector (#12906)
7a4f230f3e is described below

commit 7a4f230f3ef94356ade7b3890e1a0212f50f2dae
Author: qbhan <[email protected]>
AuthorDate: Mon Oct 12 14:40:50 2026 +0800

    [#12903] fix(trino-connector): Keep MySQL time/timestamp precision in the 
Trino connector (#12906)
    
    ### What changes were proposed in this pull request?
    
    Four commits:
    
    1. **Read path** (`MySQLDataTypeTransformer.getTrinoType`): map the
    precision reported by the MySQL catalog to the Trino type with the same
    precision, capped at 6.
    2. **MySQL catalog** (`MysqlTableOperations`): add a
    `renderDefaultValue` hook to `JdbcTableOperations` and render
    `CURRENT_TIMESTAMP(p)` for columns with a precision, which MySQL
    requires.
    3. **Write path** (`MySQLDataTypeTransformer.getGravitinoType`): pass
    the Trino precision through, capped at 6, and implement
    `ConnectorMetadata.getSupportedType` through a new
    `GeneralDataTypeTransformer.getSupportedType` hook so that CTAS from a
    source with a precision above 6 is coerced by Trino instead of failing
    when the pages are written.
    4. **MySQL catalog**: when the JDBC driver cannot report the fractional
    seconds precision (MySQL Connector/J before 8.0.16, where
    `calculateDatetimePrecision` returns null), read it from
    `information_schema.columns.COLUMN_TYPE`. This reuses the
    `information_schema` query #13036 added for lossy types and only runs
    when a `TIME`/`DATETIME`/`TIMESTAMP` column has an unknown precision, so
    a current driver pays nothing. Rows are grouped by their exact
    `TABLE_SCHEMA` so that with `lower_case_table_names=0` two schemas
    differing only in case are told apart, while with
    `lower_case_table_names=1` a request whose case differs from the stored
    name still resolves.
    
    Tests: integration testset `00016_datetime_precision`, updated
    `00006`/`00011` expectations and the `trino-452-446` patch;
    `CatalogMysqlIT.testColumnDefaultValueWithDatetimePrecision` (create
    table and add column with `CURRENT_TIMESTAMP` defaults on precision
    columns);
    `TestMysqlTableOperations.testLoadDatetimePrecisionWithoutDriverSupport`
    (simulates the old driver by overriding `calculateDatetimePrecision`);
    unit tests in `TestMySQLDataTypeTransformer`,
    `TestGravitinoMetadataGetSupportedType` and
    `TestMysqlTableOperationsSqlGeneration`. The type mapping is documented
    in `docs/trino-connector/catalog-mysql.md` and the driver note in
    `docs/jdbc-mysql-catalog.md` is updated.
    
    ### Why are the changes needed?
    
    The Gravitino Trino connector mapped every MySQL
    `TIME`/`DATETIME`/`TIMESTAMP` column to precision 0 in both directions.
    Reading a column with fractional seconds failed with `Expected 0s for
    digits beyond precision 0`, and tables created through Trino silently
    lost the requested precision. The native Trino MySQL connector maps the
    precision exactly in both directions; this PR aligns the Gravitino
    connector with it.
    
    Passing the precision through on the write side requires the MySQL
    catalog to render `CURRENT_TIMESTAMP(p)`, since `DATETIME(6) DEFAULT
    CURRENT_TIMESTAMP` is rejected by MySQL with `Invalid default value`.
    
    The precision the catalog reported used to depend on the driver version:
    with Connector/J before 8.0.16 it was unknown, the connector fell back
    to precision 0 while a table created through Trino got `DATETIME(3)`,
    and reading it back failed. Reading the precision from
    `information_schema` removes that dependency.
    
    Fix: #12903
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes.
    - MySQL columns with fractional seconds are exposed as `time(p)` /
    `timestamp(p)` / `timestamp(p) with time zone` instead of precision 0.
    - `TIMESTAMP` without a precision, which is `timestamp(3)` in Trino, now
    creates a `DATETIME(3)` column instead of `DATETIME`, matching the
    native Trino MySQL connector. The same applies to `TIME` and `TIMESTAMP
    WITH TIME ZONE`.
    - The fractional seconds precision reported by the MySQL catalog no
    longer depends on the MySQL Connector/J version.
    
    ### How was this patch tested?
    
    - Unit tests listed above; `./gradlew
    :trino-connector:trino-connector:test :catalogs:catalog-jdbc-mysql:test
    -PskipITs` passes.
    - New Docker tests
    `CatalogMysqlIT.testColumnDefaultValueWithDatetimePrecision` and
    `TestMysqlTableOperations.testLoadDatetimePrecisionWithoutDriverSupport`.
    - Integration testset `00016_datetime_precision` covering read of a
    MySQL table with `TIME/DATETIME/TIMESTAMP(3|6)` columns,
    create/insert/CTAS through Trino, and CTAS from a precision-9 source;
    updated `00006`/`00011` expectations and the `trino-452-446` patch.
    - Verified against MySQL 8.0.46 that `DATETIME(6) DEFAULT
    CURRENT_TIMESTAMP` is rejected while `CURRENT_TIMESTAMP(6)` is accepted,
    that `information_schema.columns.COLUMN_TYPE` carries the precision
    (`datetime(6)`, `time(3)`, bare `datetime` for 0), and that the schema
    resolution behaves as described with `lower_case_table_names` set to 0
    and to 1.
---
 .../converter/JdbcColumnDefaultValueConverter.java |   4 +-
 .../jdbc/operation/JdbcTableOperations.java        |  15 ++-
 .../mysql/operation/MysqlTableOperations.java      | 122 +++++++++++++++----
 .../mysql/integration/test/CatalogMysqlIT.java     |  69 +++++++++++
 .../mysql/operation/TestMysqlTableOperations.java  |  59 ++++++++++
 .../TestMysqlTableOperationsSqlGeneration.java     |  63 ++++++++++
 docs/jdbc-mysql-catalog.md                         |  22 +---
 docs/trino-connector/catalog-mysql.md              |  14 +++
 .../docker-script/init/mysql/init.sql              |  21 ++++
 .../testsets/jdbc-mysql/00006_datatype.txt         |  14 +--
 .../jdbc-mysql/00011_column_properties.txt         |  14 +--
 .../jdbc-mysql/00016_datetime_precision.sql        |  41 +++++++
 .../jdbc-mysql/00016_datetime_precision.txt        |  54 +++++++++
 .../trino-ci-testset/testsets/trino-452-446.patch  |  37 ++++--
 .../trino/connector/GravitinoMetadata.java         |   6 +
 .../jdbc/mysql/MySQLDataTypeTransformer.java       |  63 ++++++++--
 .../connector/util/GeneralDataTypeTransformer.java |  12 ++
 .../TestGravitinoMetadataGetSupportedType.java     |  66 +++++++++++
 .../jdbc/mysql/TestMySQLDataTypeTransformer.java   | 131 +++++++++++++++++++++
 19 files changed, 746 insertions(+), 81 deletions(-)

diff --git 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/converter/JdbcColumnDefaultValueConverter.java
 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/converter/JdbcColumnDefaultValueConverter.java
index f2fa7d85b0..44c14eb0a9 100644
--- 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/converter/JdbcColumnDefaultValueConverter.java
+++ 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/converter/JdbcColumnDefaultValueConverter.java
@@ -34,7 +34,9 @@ import org.apache.gravitino.rel.types.Types;
 
 public class JdbcColumnDefaultValueConverter {
 
-  protected static final String CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP";
+  /** The DDL rendering of the current timestamp default value function. */
+  public static final String CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP";
+
   protected static final String NULL = "NULL";
   protected static final DateTimeFormatter DATE_TIME_FORMATTER =
       new DateTimeFormatterBuilder()
diff --git 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcTableOperations.java
 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcTableOperations.java
index 00cf4a9111..3aaf10b881 100644
--- 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcTableOperations.java
+++ 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcTableOperations.java
@@ -63,6 +63,7 @@ import 
org.apache.gravitino.rel.expressions.transforms.Transform;
 import org.apache.gravitino.rel.expressions.transforms.Transforms;
 import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.rel.indexes.Indexes;
+import org.apache.gravitino.rel.types.Type;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -115,10 +116,22 @@ public abstract class JdbcTableOperations implements 
TableOperation {
     if (DEFAULT_VALUE_NOT_SET.equals(column.defaultValue())) {
       return;
     }
-    String defaultValue = 
columnDefaultValueConverter.fromGravitino(column.defaultValue());
+    String defaultValue = renderDefaultValue(column.dataType(), 
column.defaultValue());
     sqlBuilder.append("DEFAULT ").append(defaultValue).append(SPACE);
   }
 
+  /**
+   * Renders a column default value for DDL. Subclasses override it when the 
rendering depends on
+   * the column type.
+   *
+   * @param type the column type.
+   * @param defaultValue the default value, never {@link 
Column#DEFAULT_VALUE_NOT_SET}.
+   * @return the default value as it appears after the DEFAULT keyword.
+   */
+  protected String renderDefaultValue(Type type, Expression defaultValue) {
+    return columnDefaultValueConverter.fromGravitino(defaultValue);
+  }
+
   @Override
   public void create(
       String databaseName,
diff --git 
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
 
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
index 0417d05d08..7f80ceba9c 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
@@ -18,6 +18,7 @@
  */
 package org.apache.gravitino.catalog.mysql.operation;
 
+import static 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter.CURRENT_TIMESTAMP;
 import static 
org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils.escapeSqlLiteral;
 import static 
org.apache.gravitino.catalog.mysql.MysqlTablePropertiesMetadata.MYSQL_AUTO_INCREMENT_OFFSET_KEY;
 import static 
org.apache.gravitino.catalog.mysql.MysqlTablePropertiesMetadata.MYSQL_ENGINE_KEY;
@@ -52,6 +53,7 @@ import org.apache.gravitino.exceptions.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.TableChange;
+import org.apache.gravitino.rel.expressions.Expression;
 import org.apache.gravitino.rel.expressions.distributions.Distribution;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
 import org.apache.gravitino.rel.expressions.transforms.Transform;
@@ -218,10 +220,12 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
   }
 
   /**
-   * VARBINARY/ENUM/SET fall back to a bare Types.ExternalType, and BIT/BINARY 
of any width both
-   * collapse to plain Types.BinaryType, because JdbcTypeConverter only sees 
TYPE_NAME/COLUMN_SIZE.
-   * If any column needs it, this queries information_schema.columns once for 
the whole table to
-   * recover the full declaration (e.g. "enum('a','b','c')", "binary(16)") and 
fixes those columns.
+   * VARBINARY/ENUM/SET fall back to a bare Types.ExternalType, BIT/BINARY of 
any width both
+   * collapse to plain Types.BinaryType, and the fractional seconds precision 
of TIME/DATETIME/
+   * TIMESTAMP is unknown when the driver predates 8.0.16, because 
JdbcTypeConverter only sees
+   * TYPE_NAME/COLUMN_SIZE. If any column needs it, this queries 
information_schema.columns once for
+   * the whole table to recover the full declaration (e.g. 
"enum('a','b','c')", "binary(16)",
+   * "datetime(6)") and fixes those columns.
    */
   private static void correctLossyTypeColumns(
       Connection connection, String databaseName, String tableName, 
JdbcTable.Builder tableBuilder)
@@ -247,7 +251,7 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
 
   private static boolean isLossyTypeCandidate(Column column) {
     Type type = column.dataType();
-    if (type instanceof Types.BinaryType) {
+    if (type instanceof Types.BinaryType || hasUnknownDatetimePrecision(type)) 
{
       return true;
     }
     if (type instanceof Types.ExternalType) {
@@ -263,13 +267,47 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
     if (StringUtils.isEmpty(fullType)) {
       return column;
     }
+    Type type = column.dataType();
+    if (hasUnknownDatetimePrecision(type)) {
+      // MySQL leaves the precision out of the declaration when it is 0, e.g. 
"datetime".
+      return copyWithType(column, withDatetimePrecision(type, 
parseLength(fullType, 0)));
+    }
     // A plain BINARY(1) (or unspecified length) already round-trips to 
Types.BinaryType as-is.
-    if (column.dataType() instanceof Types.BinaryType && parseLength(fullType) 
<= 1) {
+    if (type instanceof Types.BinaryType && parseLength(fullType, 1) <= 1) {
       return column;
     }
+    return copyWithType(column, Types.ExternalType.of(fullType));
+  }
+
+  private static boolean hasUnknownDatetimePrecision(Type type) {
+    if (type instanceof Types.TimeType) {
+      return !((Types.TimeType) type).hasPrecisionSet();
+    }
+    if (type instanceof Types.TimestampType) {
+      return !((Types.TimestampType) type).hasPrecisionSet();
+    }
+    return false;
+  }
+
+  private static Type withDatetimePrecision(Type type, int precision) {
+    if (type instanceof Types.TimeType) {
+      return Types.TimeType.of(precision);
+    }
+    return ((Types.TimestampType) type).hasTimeZone()
+        ? Types.TimestampType.withTimeZone(precision)
+        : Types.TimestampType.withoutTimeZone(precision);
+  }
+
+  /** Parses the "(n)" of a column declaration such as "binary(16)" or 
"datetime(6)". */
+  private static int parseLength(String fullType, int defaultLength) {
+    Matcher matcher = LENGTH_PATTERN.matcher(fullType);
+    return matcher.find() ? Integer.parseInt(matcher.group(1)) : defaultLength;
+  }
+
+  private static JdbcColumn copyWithType(JdbcColumn column, Type type) {
     return JdbcColumn.builder()
         .withName(column.name())
-        .withType(Types.ExternalType.of(fullType))
+        .withType(type)
         .withComment(column.comment())
         .withNullable(column.nullable())
         .withAutoIncrement(column.autoIncrement())
@@ -277,19 +315,16 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
         .build();
   }
 
-  private static int parseLength(String fullType) {
-    Matcher matcher = LENGTH_PATTERN.matcher(fullType);
-    return matcher.find() ? Integer.parseInt(matcher.group(1)) : 1;
-  }
-
   private static Map<String, String> fetchColumnFullTypes(
       Connection connection, String databaseName, String tableName) throws 
SQLException {
-    Map<String, String> columnTypes = new HashMap<>();
-    // TABLE_SCHEMA/TABLE_NAME comparisons in information_schema use a 
case-insensitive collation,
-    // so with lower_case_table_names=0 a schema holding both "a_b" and "A_B" 
would return rows for
-    // both; re-check TABLE_NAME exactly, matching the pattern used in 
getColumnBuilder.
+    // TABLE_SCHEMA/TABLE_NAME comparisons in information_schema use a 
case-insensitive collation.
+    // TABLE_NAME is re-checked exactly, matching the pattern used in 
getColumnBuilder. The rows are
+    // grouped by their exact TABLE_SCHEMA: with lower_case_table_names=0 both 
"a_b" and "A_B" may
+    // exist and only the exact one is wanted, while with 
lower_case_table_names=1 the stored name
+    // may differ in case from the requested one and is then the only 
candidate.
+    Map<String, Map<String, String>> columnTypesBySchema = new HashMap<>();
     String query =
-        "SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE FROM 
information_schema.columns "
+        "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_TYPE FROM 
information_schema.columns "
             + "WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?";
     try (PreparedStatement statement = connection.prepareStatement(query)) {
       statement.setString(1, databaseName);
@@ -297,12 +332,29 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
       try (ResultSet resultSet = statement.executeQuery()) {
         while (resultSet.next()) {
           if (Objects.equals(resultSet.getString("TABLE_NAME"), tableName)) {
-            columnTypes.put(resultSet.getString("COLUMN_NAME"), 
resultSet.getString("COLUMN_TYPE"));
+            columnTypesBySchema
+                .computeIfAbsent(resultSet.getString("TABLE_SCHEMA"), schema 
-> new HashMap<>())
+                .put(resultSet.getString("COLUMN_NAME"), 
resultSet.getString("COLUMN_TYPE"));
           }
         }
       }
     }
-    return columnTypes;
+    Map<String, String> exactSchemaTypes = 
columnTypesBySchema.get(databaseName);
+    if (exactSchemaTypes != null) {
+      return exactSchemaTypes;
+    }
+    if (columnTypesBySchema.size() == 1) {
+      return columnTypesBySchema.values().iterator().next();
+    }
+    if (columnTypesBySchema.size() > 1) {
+      LOG.warn(
+          "Schemas {} differ only in case from {} and none matches exactly, 
leaving the column "
+              + "types of {} as reported by the driver",
+          columnTypesBySchema.keySet(),
+          databaseName,
+          tableName);
+    }
+    return Collections.emptyMap();
   }
 
   @Override
@@ -412,6 +464,29 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
     return result;
   }
 
+  /**
+   * {@inheritDoc}
+   *
+   * <p>MySQL requires the fractional seconds precision of CURRENT_TIMESTAMP 
to match the one of the
+   * column: {@code DATETIME(6) DEFAULT CURRENT_TIMESTAMP} is rejected while 
{@code DATETIME(6)
+   * DEFAULT CURRENT_TIMESTAMP(6)} is accepted. Columns without a precision, 
or with precision 0,
+   * keep the bare CURRENT_TIMESTAMP.
+   *
+   * @see <a 
href="https://dev.mysql.com/doc/refman/8.0/en/timestamp-initialization.html";>Automatic
+   *     Initialization and Updating for TIMESTAMP and DATETIME</a>
+   */
+  @Override
+  protected String renderDefaultValue(Type type, Expression defaultValue) {
+    String rendered = super.renderDefaultValue(type, defaultValue);
+    if (CURRENT_TIMESTAMP.equals(rendered) && type instanceof 
Types.TimestampType) {
+      int precision = ((Types.TimestampType) type).precision();
+      if (precision > 0) {
+        return String.format("%s(%d)", rendered, precision);
+      }
+    }
+    return rendered;
+  }
+
   private String updateColumnAutoIncrementDefinition(
       JdbcTable table, TableChange.UpdateColumnAutoIncrement change) {
     if (change.fieldName().length > 1) {
@@ -571,7 +646,7 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
     if (!Column.DEFAULT_VALUE_NOT_SET.equals(addColumn.getDefaultValue())) {
       columnDefinition
           .append("DEFAULT ")
-          
.append(columnDefaultValueConverter.fromGravitino(addColumn.getDefaultValue()))
+          .append(renderDefaultValue(addColumn.getDataType(), 
addColumn.getDefaultValue()))
           .append(SPACE);
     }
 
@@ -797,11 +872,10 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
       String driverVersion = getMySQLDriverVersion();
       if (driverVersion != null && 
!isMySQLDriverVersionSupported(driverVersion)) {
         LOG.warn(
-            "MySQL driver version {} is below 8.0.16, columnSize may not be 
accurate for precision calculation. "
-                + "Returning null for {} type precision. Driver version: {}",
+            "MySQL driver version {} is below 8.0.16, columnSize may not be 
accurate for precision "
+                + "calculation. Falling back to information_schema for the {} 
precision.",
             driverVersion,
-            upperTypeName,
-            driverVersion);
+            upperTypeName);
         return null;
       }
     }
diff --git 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
index b0a8e08139..6fef5372f9 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
@@ -535,6 +535,75 @@ public class CatalogMysqlIT extends BaseIT {
         createdTable.columns()[4].defaultValue());
   }
 
+  @Test
+  void testColumnDefaultValueWithDatetimePrecision() {
+    // MySQL rejects `DATETIME(6) DEFAULT CURRENT_TIMESTAMP`: once a column 
declares a fractional
+    // seconds precision, CURRENT_TIMESTAMP has to carry the same one.
+    Column col1 =
+        Column.of(
+            "col_1",
+            Types.TimestampType.withoutTimeZone(6),
+            "col_1_comment",
+            false,
+            false,
+            DEFAULT_VALUE_OF_CURRENT_TIMESTAMP);
+    Column col2 =
+        Column.of(
+            "col_2",
+            Types.TimestampType.withTimeZone(3),
+            "col_2_comment",
+            false,
+            false,
+            DEFAULT_VALUE_OF_CURRENT_TIMESTAMP);
+    Column col3 =
+        Column.of(
+            "col_3",
+            Types.TimestampType.withoutTimeZone(),
+            "col_3_comment",
+            false,
+            false,
+            DEFAULT_VALUE_OF_CURRENT_TIMESTAMP);
+
+    NameIdentifier tableIdent =
+        NameIdentifier.of(schemaName, 
GravitinoITUtils.genRandomName("mysql_it_table"));
+    TableCatalog tableCatalog = catalog.asTableCatalog();
+    tableCatalog.createTable(tableIdent, new Column[] {col1, col2, col3}, 
null, ImmutableMap.of());
+
+    // An added column is rendered the same way as the ones above.
+    Column col4 =
+        Column.of(
+            "col_4",
+            Types.TimestampType.withoutTimeZone(6),
+            "col_4_comment",
+            false,
+            false,
+            DEFAULT_VALUE_OF_CURRENT_TIMESTAMP);
+    tableCatalog.alterTable(
+        tableIdent,
+        TableChange.addColumn(
+            new String[] {col4.name()},
+            col4.dataType(),
+            col4.comment(),
+            TableChange.ColumnPosition.defaultPos(),
+            col4.nullable(),
+            col4.autoIncrement(),
+            col4.defaultValue()));
+
+    Table createdTable = tableCatalog.loadTable(tableIdent);
+    Assertions.assertEquals(
+        Types.TimestampType.withoutTimeZone(6), 
createdTable.columns()[0].dataType());
+    Assertions.assertEquals(
+        Types.TimestampType.withTimeZone(3), 
createdTable.columns()[1].dataType());
+    Assertions.assertEquals(
+        Types.TimestampType.withoutTimeZone(0), 
createdTable.columns()[2].dataType());
+    Assertions.assertEquals(
+        Types.TimestampType.withoutTimeZone(6), 
createdTable.columns()[3].dataType());
+    for (Column column : createdTable.columns()) {
+      Assertions.assertEquals(
+          DEFAULT_VALUE_OF_CURRENT_TIMESTAMP, column.defaultValue(), 
column.name());
+    }
+  }
+
   @Test
   // MySQL support column default value expression after 8.0.13
   // see https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html
diff --git 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
index f4cd86e9e6..9a871df0e3 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
@@ -37,6 +37,8 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.catalog.jdbc.JdbcColumn;
 import org.apache.gravitino.catalog.jdbc.JdbcTable;
 import org.apache.gravitino.catalog.jdbc.utils.DataSourceUtils;
+import 
org.apache.gravitino.catalog.mysql.converter.MysqlColumnDefaultValueConverter;
+import org.apache.gravitino.catalog.mysql.converter.MysqlTypeConverter;
 import org.apache.gravitino.exceptions.GravitinoRuntimeException;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.TableChange;
@@ -1143,6 +1145,63 @@ public class TestMysqlTableOperations extends TestMysql {
         "Non-datetime type should return 0 precision");
   }
 
+  @Test
+  public void testLoadDatetimePrecisionWithoutDriverSupport() throws Exception 
{
+    // MySQL Connector/J before 8.0.16 reports a COLUMN_SIZE the fractional 
seconds precision
+    // cannot be derived from, which calculateDatetimePrecision signals by 
returning null. The
+    // precision must then be recovered from information_schema instead of 
being left unset.
+    String tableName = RandomStringUtils.randomAlphabetic(16) + 
"_datetime_precision_table";
+    DataSource dataSource = 
DataSourceUtils.createDataSource(getMySQLCatalogProperties());
+    try {
+      try (Connection connection = dataSource.getConnection()) {
+        connection.setCatalog(TEST_DB_NAME.toString());
+        try (Statement statement = connection.createStatement()) {
+          statement.execute(
+              String.format(
+                  "CREATE TABLE `%s` ("
+                      + "c_int INT, "
+                      + "c_time TIME(3), "
+                      + "c_time_no_precision TIME, "
+                      + "c_datetime DATETIME(6), "
+                      + "c_datetime_no_precision DATETIME, "
+                      + "c_timestamp TIMESTAMP(3) NULL)",
+                  tableName));
+        }
+      }
+
+      MysqlTableOperations tableOperations =
+          new MysqlTableOperations() {
+            @Override
+            public Integer calculateDatetimePrecision(String typeName, int 
columnSize, int scale) {
+              return null;
+            }
+          };
+      tableOperations.initialize(
+          dataSource,
+          JDBC_EXCEPTION_CONVERTER,
+          new MysqlTypeConverter(),
+          new MysqlColumnDefaultValueConverter(),
+          Collections.emptyMap());
+
+      JdbcTable table = tableOperations.load(TEST_DB_NAME.toString(), 
tableName);
+      Map<String, Type> typeByColumn =
+          
Arrays.stream(table.columns()).collect(Collectors.toMap(Column::name, 
Column::dataType));
+
+      // A column without a precision must be left untouched by the correction.
+      Assertions.assertEquals(Types.IntegerType.get(), 
typeByColumn.get("c_int"));
+      Assertions.assertEquals(Types.TimeType.of(3), 
typeByColumn.get("c_time"));
+      Assertions.assertEquals(Types.TimeType.of(0), 
typeByColumn.get("c_time_no_precision"));
+      Assertions.assertEquals(
+          Types.TimestampType.withoutTimeZone(6), 
typeByColumn.get("c_datetime"));
+      Assertions.assertEquals(
+          Types.TimestampType.withoutTimeZone(0), 
typeByColumn.get("c_datetime_no_precision"));
+      Assertions.assertEquals(Types.TimestampType.withTimeZone(3), 
typeByColumn.get("c_timestamp"));
+    } finally {
+      TABLE_OPERATIONS.drop(TEST_DB_NAME.toString(), tableName);
+      DataSourceUtils.closeDataSource(dataSource);
+    }
+  }
+
   @Test
   public void testLoadColumnsWithLossyTypeDeclarations() throws Exception {
     // These types either drop their parameters (VARBINARY/ENUM/SET) or 
collapse distinct widths
diff --git 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperationsSqlGeneration.java
index ad8966c2c8..7f240760a0 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperationsSqlGeneration.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperationsSqlGeneration.java
@@ -24,7 +24,9 @@ import org.apache.gravitino.catalog.jdbc.JdbcTable;
 import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
 import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
 import org.apache.gravitino.catalog.mysql.converter.MysqlTypeConverter;
+import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.TableChange;
+import org.apache.gravitino.rel.expressions.FunctionExpression;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
 import org.apache.gravitino.rel.expressions.literals.Literals;
 import org.apache.gravitino.rel.expressions.transforms.Transforms;
@@ -106,6 +108,67 @@ public class TestMysqlTableOperationsSqlGeneration {
         "Should contain DEFAULT value but was: " + sql);
   }
 
+  @Test
+  public void testCurrentTimestampDefaultValueKeepsColumnPrecision() {
+    TestableMysqlTableOperations ops = new TestableMysqlTableOperations();
+
+    JdbcColumn datetime =
+        JdbcColumn.builder()
+            .withName("datetime_col")
+            .withType(Types.TimestampType.withoutTimeZone())
+            .withDefaultValue(Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP)
+            .build();
+    JdbcColumn datetime0 =
+        JdbcColumn.builder()
+            .withName("datetime_col_0")
+            .withType(Types.TimestampType.withoutTimeZone(0))
+            .withDefaultValue(Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP)
+            .build();
+    JdbcColumn datetime6 =
+        JdbcColumn.builder()
+            .withName("datetime_col_6")
+            .withType(Types.TimestampType.withoutTimeZone(6))
+            .withDefaultValue(Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP)
+            .build();
+    JdbcColumn timestamp3 =
+        JdbcColumn.builder()
+            .withName("timestamp_col_3")
+            .withType(Types.TimestampType.withTimeZone(3))
+            .withDefaultValue(Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP)
+            .build();
+    JdbcColumn upperCase =
+        JdbcColumn.builder()
+            .withName("upper_case_col")
+            .withType(Types.TimestampType.withoutTimeZone(6))
+            .withDefaultValue(FunctionExpression.of("CURRENT_TIMESTAMP"))
+            .build();
+
+    String sql =
+        ops.createTableSql(
+            "test_table", new JdbcColumn[] {datetime, datetime0, datetime6, 
timestamp3, upperCase});
+    Assertions.assertTrue(
+        sql.contains("`datetime_col` datetime NULL DEFAULT CURRENT_TIMESTAMP 
"), sql);
+    Assertions.assertTrue(
+        sql.contains("`datetime_col_0` datetime(0) NULL DEFAULT 
CURRENT_TIMESTAMP "), sql);
+    Assertions.assertTrue(
+        sql.contains("`datetime_col_6` datetime(6) NULL DEFAULT 
CURRENT_TIMESTAMP(6) "), sql);
+    Assertions.assertTrue(
+        sql.contains("`timestamp_col_3` timestamp(3) NULL DEFAULT 
CURRENT_TIMESTAMP(3) "), sql);
+    Assertions.assertTrue(
+        sql.contains("`upper_case_col` datetime(6) NULL DEFAULT 
CURRENT_TIMESTAMP(6) "), sql);
+
+    String alterSql =
+        ops.alterTableSql(
+            "test_table",
+            TableChange.addColumn(
+                new String[] {"added_col"},
+                Types.TimestampType.withoutTimeZone(6),
+                Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP));
+    Assertions.assertTrue(
+        alterSql.contains("ADD COLUMN `added_col` datetime(6) DEFAULT 
CURRENT_TIMESTAMP(6) "),
+        alterSql);
+  }
+
   @Test
   public void testEscapeCommentsInGeneratedSql() {
     TestableMysqlTableOperations ops = new TestableMysqlTableOperations();
diff --git a/docs/jdbc-mysql-catalog.md b/docs/jdbc-mysql-catalog.md
index 978ffdb6aa..ec15f1736e 100644
--- a/docs/jdbc-mysql-catalog.md
+++ b/docs/jdbc-mysql-catalog.md
@@ -72,26 +72,14 @@ message if the driver is absent.
 
 ### Driver Version Compatibility
 
-The MySQL catalog includes driver version compatibility checks for datetime 
precision calculation:
-
-- **MySQL Connector/J versions >= 8.0.16**: Full support for datetime 
precision calculation
-- **MySQL Connector/J versions < 8.0.16**: Limited support - datetime 
precision calculation returns `null` with a warning log
-
-This limitation affects the following datetime types:
-- `TIME(p)` - time precision
-- `TIMESTAMP(p)` - timestamp precision  
-- `DATETIME(p)` - datetime precision
-
-When using an unsupported driver version, the system will:
-1. Continue to work normally with default precision (0)
-2. Log a warning message indicating the driver version limitation
-3. Return `null` for precision calculations to avoid incorrect results
+The fractional seconds precision of `TIME(p)`, `DATETIME(p)` and 
`TIMESTAMP(p)` columns is derived from the
+JDBC metadata, whose `COLUMN_SIZE` is only accurate with MySQL Connector/J 
8.0.16 or later. With an older
+driver the catalog logs a warning and falls back to 
`information_schema.columns` for the precision.
 
 **Example warning log:**
 ```
-WARN: MySQL driver version mysql-connector-java-8.0.11 is below 8.0.16, 
-columnSize may not be accurate for precision calculation. 
-Returning null for TIMESTAMP type precision. Driver version: 
mysql-connector-java-8.0.11
+WARN: MySQL driver version mysql-connector-java-8.0.11 is below 8.0.16, 
columnSize may not be accurate
+for precision calculation. Falling back to information_schema for the 
TIMESTAMP precision.
 ```
 
 **Recommended driver versions:**
diff --git a/docs/trino-connector/catalog-mysql.md 
b/docs/trino-connector/catalog-mysql.md
index 99f007fe64..ed7088cab1 100644
--- a/docs/trino-connector/catalog-mysql.md
+++ b/docs/trino-connector/catalog-mysql.md
@@ -25,6 +25,20 @@ The Gravitino Trino connector supports `CREATE TABLE AS 
SELECT`.
 `CREATE OR REPLACE TABLE AS SELECT` is not supported. Use `DROP TABLE` 
followed by `CREATE TABLE AS SELECT` as an alternative.
 :::
 
+### Time type mapping
+
+`TIME`, `TIMESTAMP` and `TIMESTAMP WITH TIME ZONE` columns keep their 
precision in both directions, the same way
+the native Trino MySQL connector does:
+
+| Trino type                    | MySQL type     |
+|-------------------------------|----------------|
+| `TIME(n)`                     | `TIME(n)`      |
+| `TIMESTAMP(n)`                | `DATETIME(n)`  |
+| `TIMESTAMP(n) WITH TIME ZONE` | `TIMESTAMP(n)` |
+
+`n` ranges from 0 to 6, a higher precision is reduced to 6. `TIMESTAMP` 
without a precision is `TIMESTAMP(3)` in
+Trino, so it creates a `DATETIME(3)` column.
+
 ## Alter Table
 
 Support for the following alter table operations:
diff --git a/integration-test-common/docker-script/init/mysql/init.sql 
b/integration-test-common/docker-script/init/mysql/init.sql
index f6486358d1..4a71a3342a 100644
--- a/integration-test-common/docker-script/init/mysql/init.sql
+++ b/integration-test-common/docker-script/init/mysql/init.sql
@@ -135,6 +135,27 @@ INSERT INTO gt_mysql_test_all_type.demo (
     ST_GeomFromText('POINT(10 20)'),        -- point_col
     ST_GeomFromText('LINESTRING(0 0, 10 10)') -- geometry_col
 );
+CREATE TABLE gt_mysql_test_all_type.demo_datetime_precision
+(
+    id                  INT NOT NULL PRIMARY KEY,
+    time_col_3          TIME(3),
+    time_col_6          TIME(6),
+    datetime_col_3      DATETIME(3),
+    datetime_col_6      DATETIME(6),
+    timestamp_col_3     TIMESTAMP(3) NULL,
+    timestamp_col_6     TIMESTAMP(6) NULL
+);
+
+INSERT INTO gt_mysql_test_all_type.demo_datetime_precision VALUES (
+    1,
+    '14:30:00.123',
+    '14:30:00.123456',
+    '2025-07-04 14:30:00.123',
+    '2025-07-04 14:30:00.123456',
+    '2025-07-04 14:30:00.123',
+    '2025-07-04 14:30:00.123456'
+);
+
 CREATE DATABASE gt_mysql_test_column_properties;
 /*
 
+----------------+-------------------------------------+------+-----+---------------------+-------------------+
diff --git 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00006_datatype.txt
 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00006_datatype.txt
index b7658af842..6d88278385 100644
--- 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00006_datatype.txt
+++ 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00006_datatype.txt
@@ -18,9 +18,9 @@ CREATE TABLE
    f11 integer,
    f12 bigint,
    f13 date,
-   f14 time(0),
-   f15 timestamp(0),
-   f16 timestamp(0) with time zone,
+   f14 time(3),
+   f15 timestamp(3),
+   f16 timestamp(3) with time zone,
    f17 json
 )
 COMMENT ''
@@ -32,7 +32,7 @@ INSERT: 1 row
 
 INSERT: 1 row
 
-"Sample text 1","Text1               
","65","123.456","7.89","12.34","false","1","100","1000","1000","100000","2024-01-01","08:00:00","2024-01-01
 08:00:00","2024-01-01 08:00:00 UTC","{""x"":300,""y"":""AFRICA"",""z"":null}"
+"Sample text 1","Text1               
","65","123.456","7.89","12.34","false","1","100","1000","1000","100000","2024-01-01","08:00:00.000","2024-01-01
 08:00:00.000","2024-01-01 08:00:00.000 
UTC","{""x"":300,""y"":""AFRICA"",""z"":null}"
 "","","","","","","","","","","","","","","","",""
 
 CREATE TABLE
@@ -51,9 +51,9 @@ CREATE TABLE
    f11 integer NOT NULL,
    f12 bigint NOT NULL,
    f13 date NOT NULL,
-   f14 time(0) NOT NULL,
-   f15 timestamp(0) NOT NULL,
-   f16 timestamp(0) with time zone NOT NULL,
+   f14 time(3) NOT NULL,
+   f15 timestamp(3) NOT NULL,
+   f16 timestamp(3) with time zone NOT NULL,
    f17 json NOT NULL
 )
 COMMENT ''
diff --git 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00011_column_properties.txt
 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00011_column_properties.txt
index 117d2f1d4f..e5a192e21f 100644
--- 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00011_column_properties.txt
+++ 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00011_column_properties.txt
@@ -46,7 +46,7 @@ WITH (
    date_col_2 date,
    date_col_3 date,
    timestamp_col_1 timestamp(0) with time zone WITH (default = 
'CURRENT_TIMESTAMP'),
-   timestamp_col_2 timestamp(0) with time zone WITH (default = 
'CURRENT_TIMESTAMP')
+   timestamp_col_2 timestamp(6) with time zone WITH (default = 
'CURRENT_TIMESTAMP')
 )
 COMMENT ''
 WITH (
@@ -70,10 +70,10 @@ CREATE TABLE
    f11 integer WITH (default = '1'),
    f12 bigint WITH (default = '1'),
    f13 date WITH (default = '2024-04-01'),
-   f14 time(0) WITH (default = '08:00'),
-   f15 timestamp(0) WITH (default = '2012-12-31 11:30:45'),
-   f16 timestamp(0) with time zone WITH (default = '2012-12-31 11:30:45'),
-   f17 timestamp(0) with time zone WITH (default = 'CURRENT_TIMESTAMP')
+   f14 time(3) WITH (default = '08:00'),
+   f15 timestamp(3) WITH (default = '2012-12-31 11:30:45'),
+   f16 timestamp(3) with time zone WITH (default = '2012-12-31 11:30:45'),
+   f17 timestamp(3) with time zone WITH (default = 'CURRENT_TIMESTAMP')
 )
 COMMENT ''
 WITH (
@@ -91,7 +91,7 @@ CREATE TABLE
 
 "CREATE TABLE 
gt_mysql.gt_mysql_test_column_properties_v2.test_create_with_auto_increment_1 (
    key1 integer NOT NULL WITH (auto_increment = true),
-   f1 timestamp(0),
+   f1 timestamp(3),
    f2 date
 )
 COMMENT ''
@@ -107,7 +107,7 @@ CREATE TABLE
 "CREATE TABLE 
gt_mysql.gt_mysql_test_column_properties_v2.test_create_with_auto_increment_2 (
    key1 integer NOT NULL WITH (auto_increment = true),
    key2 integer NOT NULL,
-   f1 timestamp(0),
+   f1 timestamp(3),
    f2 date
 )
 COMMENT ''
diff --git 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.sql
 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.sql
new file mode 100644
index 0000000000..ee22633e45
--- /dev/null
+++ 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.sql
@@ -0,0 +1,41 @@
+-- Table create by integration-test-common/docker-script/init/mysql/init.sql
+show create table gt_mysql.gt_mysql_test_all_type.demo_datetime_precision;
+
+select * from gt_mysql.gt_mysql_test_all_type.demo_datetime_precision;
+
+CREATE TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_v2 (
+    id INT NOT NULL,
+    time_col TIME,
+    time_col_6 TIME(6),
+    datetime_col TIMESTAMP,
+    datetime_col_6 TIMESTAMP(6),
+    datetime_col_9 TIMESTAMP(9),
+    timestamp_col TIMESTAMP WITH TIME ZONE,
+    timestamp_col_6 TIMESTAMP(6) WITH TIME ZONE WITH 
(default='CURRENT_TIMESTAMP')
+)
+WITH (
+   primary_key = ARRAY['id']
+);
+
+SHOW CREATE TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_v2;
+
+INSERT INTO gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_v2
+VALUES (1, TIME '14:30:00.123', TIME '14:30:00.123456', TIMESTAMP '2025-07-04 
14:30:00.123', TIMESTAMP '2025-07-04 14:30:00.123456', TIMESTAMP '2025-07-04 
14:30:00.123456', TIMESTAMP '2025-07-04 14:30:00.123 UTC', TIMESTAMP 
'2025-07-04 14:30:00.123456 UTC'),
+       (2, TIME '23:59:59.999', TIME '23:59:59.999999', TIMESTAMP '2025-12-31 
23:59:59.999', TIMESTAMP '2025-12-31 23:59:59.999999', TIMESTAMP '2025-12-31 
23:59:59.999999', TIMESTAMP '2025-12-31 23:59:59.999 UTC', TIMESTAMP 
'2025-12-31 23:59:59.999999 UTC');
+
+SELECT * FROM gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_v2 ORDER 
BY id;
+
+CREATE TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_ctas AS 
SELECT * FROM gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_v2;
+
+SELECT * FROM gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_ctas 
ORDER BY id;
+
+DROP TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_ctas;
+
+CREATE TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_ctas_9 AS
+SELECT TIME '14:30:00.123456789' AS time_col_9, TIMESTAMP '2025-07-04 
14:30:00.123456789' AS datetime_col_9, TIMESTAMP '2025-07-04 14:30:00.123456789 
UTC' AS timestamp_col_9;
+
+SELECT * FROM gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_ctas_9;
+
+DROP TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_ctas_9;
+
+DROP TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_v2;
diff --git 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.txt
 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.txt
new file mode 100644
index 0000000000..51b2568b97
--- /dev/null
+++ 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.txt
@@ -0,0 +1,54 @@
+"CREATE TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision (
+   id integer NOT NULL,
+   time_col_3 time(3),
+   time_col_6 time(6),
+   datetime_col_3 timestamp(3),
+   datetime_col_6 timestamp(6),
+   timestamp_col_3 timestamp(3) with time zone,
+   timestamp_col_6 timestamp(6) with time zone
+)
+COMMENT ''
+WITH (
+   engine = 'InnoDB',
+   primary_key = ARRAY['id']
+)"
+
+"1","14:30:00.123","14:30:00.123456","2025-07-04 14:30:00.123","2025-07-04 
14:30:00.123456","2025-07-04 14:30:00.123 UTC","2025-07-04 14:30:00.123456 UTC"
+
+CREATE TABLE
+
+"CREATE TABLE gt_mysql.gt_mysql_test_all_type.demo_datetime_precision_v2 (
+   id integer NOT NULL,
+   time_col time(3),
+   time_col_6 time(6),
+   datetime_col timestamp(3),
+   datetime_col_6 timestamp(6),
+   datetime_col_9 timestamp(6),
+   timestamp_col timestamp(3) with time zone,
+   timestamp_col_6 timestamp(6) with time zone WITH (default = 
'CURRENT_TIMESTAMP')
+)
+COMMENT ''
+WITH (
+   engine = 'InnoDB',
+   primary_key = ARRAY['id']
+)"
+
+INSERT: 2 rows
+
+"1","14:30:00.123","14:30:00.123456","2025-07-04 14:30:00.123","2025-07-04 
14:30:00.123456","2025-07-04 14:30:00.123456","2025-07-04 14:30:00.123 
UTC","2025-07-04 14:30:00.123456 UTC"
+"2","23:59:59.999","23:59:59.999999","2025-12-31 23:59:59.999","2025-12-31 
23:59:59.999999","2025-12-31 23:59:59.999999","2025-12-31 23:59:59.999 
UTC","2025-12-31 23:59:59.999999 UTC"
+
+CREATE TABLE: 2 rows
+
+"1","14:30:00.123","14:30:00.123456","2025-07-04 14:30:00.123","2025-07-04 
14:30:00.123456","2025-07-04 14:30:00.123456","2025-07-04 14:30:00.123 
UTC","2025-07-04 14:30:00.123456 UTC"
+"2","23:59:59.999","23:59:59.999999","2025-12-31 23:59:59.999","2025-12-31 
23:59:59.999999","2025-12-31 23:59:59.999999","2025-12-31 23:59:59.999 
UTC","2025-12-31 23:59:59.999999 UTC"
+
+DROP TABLE
+
+CREATE TABLE: 1 row
+
+"14:30:00.123457","2025-07-04 14:30:00.123457","2025-07-04 14:30:00.123457 UTC"
+
+DROP TABLE
+
+DROP TABLE
diff --git 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/trino-452-446.patch
 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/trino-452-446.patch
index 369b9f456b..a6c6ce731f 100644
--- 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/trino-452-446.patch
+++ 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/trino-452-446.patch
@@ -61,9 +61,9 @@ index 117d2f1d4..c3e55683f 100644
     date_col_2 date,
     date_col_3 date,
 -   timestamp_col_1 timestamp(0) with time zone WITH (default = 
'CURRENT_TIMESTAMP'),
--   timestamp_col_2 timestamp(0) with time zone WITH (default = 
'CURRENT_TIMESTAMP')
+-   timestamp_col_2 timestamp(6) with time zone WITH (default = 
'CURRENT_TIMESTAMP')
 +   timestamp_col_1 timestamp(0) with time zone WITH ( default = 
'CURRENT_TIMESTAMP' ),
-+   timestamp_col_2 timestamp(0) with time zone WITH ( default = 
'CURRENT_TIMESTAMP' )
++   timestamp_col_2 timestamp(6) with time zone WITH ( default = 
'CURRENT_TIMESTAMP' )
  )
  COMMENT ''
  WITH (
@@ -83,10 +83,10 @@ index 117d2f1d4..c3e55683f 100644
 -   f11 integer WITH (default = '1'),
 -   f12 bigint WITH (default = '1'),
 -   f13 date WITH (default = '2024-04-01'),
--   f14 time(0) WITH (default = '08:00'),
--   f15 timestamp(0) WITH (default = '2012-12-31 11:30:45'),
--   f16 timestamp(0) with time zone WITH (default = '2012-12-31 11:30:45'),
--   f17 timestamp(0) with time zone WITH (default = 'CURRENT_TIMESTAMP')
+-   f14 time(3) WITH (default = '08:00'),
+-   f15 timestamp(3) WITH (default = '2012-12-31 11:30:45'),
+-   f16 timestamp(3) with time zone WITH (default = '2012-12-31 11:30:45'),
+-   f17 timestamp(3) with time zone WITH (default = 'CURRENT_TIMESTAMP')
 +   key1 integer NOT NULL WITH ( auto_increment = true ),
 +   f1 varchar(200) WITH ( default = 'VARCHAR' ),
 +   f2 char(20) WITH ( default = 'CHAR' ),
@@ -99,10 +99,10 @@ index 117d2f1d4..c3e55683f 100644
 +   f11 integer WITH ( default = '1' ),
 +   f12 bigint WITH ( default = '1' ),
 +   f13 date WITH ( default = '2024-04-01' ),
-+   f14 time(0) WITH ( default = '08:00' ),
-+   f15 timestamp(0) WITH ( default = '2012-12-31 11:30:45' ),
-+   f16 timestamp(0) with time zone WITH ( default = '2012-12-31 11:30:45' ),
-+   f17 timestamp(0) with time zone WITH ( default = 'CURRENT_TIMESTAMP' )
++   f14 time(3) WITH ( default = '08:00' ),
++   f15 timestamp(3) WITH ( default = '2012-12-31 11:30:45' ),
++   f16 timestamp(3) with time zone WITH ( default = '2012-12-31 11:30:45' ),
++   f17 timestamp(3) with time zone WITH ( default = 'CURRENT_TIMESTAMP' )
  )
  COMMENT ''
  WITH (
@@ -112,7 +112,7 @@ index 117d2f1d4..c3e55683f 100644
  "CREATE TABLE 
gt_mysql.gt_mysql_test_column_properties_v2.test_create_with_auto_increment_1 (
 -   key1 integer NOT NULL WITH (auto_increment = true),
 +   key1 integer NOT NULL WITH ( auto_increment = true ),
-    f1 timestamp(0),
+    f1 timestamp(3),
     f2 date
  )
 @@ -105,7 +105,7 @@ DROP TABLE
@@ -122,7 +122,7 @@ index 117d2f1d4..c3e55683f 100644
 -   key1 integer NOT NULL WITH (auto_increment = true),
 +   key1 integer NOT NULL WITH ( auto_increment = true ),
     key2 integer NOT NULL,
-    f1 timestamp(0),
+    f1 timestamp(3),
     f2 date
 @@ -121,7 +121,7 @@ DROP TABLE
  CREATE TABLE
@@ -142,3 +142,16 @@ index 117d2f1d4..c3e55683f 100644
     key2 integer,
     col1 integer
  )
+diff --git 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.txt
 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.txt
+index 51f226ea2..b57d66f9e 100644
+--- 
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.txt
++++ 
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00016_datetime_precision.txt
+@@ -25,7 +25,7 @@ CREATE TABLE
+    datetime_col_6 timestamp(6),
+    datetime_col_9 timestamp(6),
+    timestamp_col timestamp(3) with time zone,
+-   timestamp_col_6 timestamp(6) with time zone WITH (default = 
'CURRENT_TIMESTAMP')
++   timestamp_col_6 timestamp(6) with time zone WITH ( default = 
'CURRENT_TIMESTAMP' )
+ )
+ COMMENT ''
+ WITH (
diff --git 
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoMetadata.java
 
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoMetadata.java
index 3d578aa72a..9c37025507 100644
--- 
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoMetadata.java
+++ 
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoMetadata.java
@@ -230,6 +230,12 @@ public abstract class GravitinoMetadata implements 
ConnectorMetadata {
     catalogConnectorMetadata.createTable(table, saveMode == SaveMode.IGNORE);
   }
 
+  @Override
+  public Optional<Type> getSupportedType(
+      ConnectorSession session, Map<String, Object> tableProperties, Type 
type) {
+    return metadataAdapter.getDataTypeTransformer().getSupportedType(type);
+  }
+
   @Override
   public ConnectorOutputTableHandle beginCreateTable(
       ConnectorSession session,
diff --git 
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/MySQLDataTypeTransformer.java
 
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/MySQLDataTypeTransformer.java
index b6ecf8696d..7a2f57c573 100644
--- 
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/MySQLDataTypeTransformer.java
+++ 
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/MySQLDataTypeTransformer.java
@@ -24,6 +24,7 @@ import io.trino.spi.type.CharType;
 import io.trino.spi.type.TimeType;
 import io.trino.spi.type.TimestampType;
 import io.trino.spi.type.TimestampWithTimeZoneType;
+import java.util.Optional;
 import org.apache.gravitino.rel.types.Type;
 import org.apache.gravitino.rel.types.Type.Name;
 import org.apache.gravitino.rel.types.Types;
@@ -49,13 +50,23 @@ public class MySQLDataTypeTransformer extends 
GeneralDataTypeTransformer {
       return io.trino.spi.type.VarcharType.createUnboundedVarcharType();
     } else if (Name.TIMESTAMP == type.name()) {
       Types.TimestampType timestampType = (Types.TimestampType) type;
-      if (timestampType.hasTimeZone()) {
-        return TimestampWithTimeZoneType.TIMESTAMP_TZ_SECONDS;
-      } else {
-        return TimestampType.TIMESTAMP_SECONDS;
-      }
+      // Fall back to the MySQL default fractional seconds precision of 0 when 
the catalog does
+      // not report one.
+      int precision =
+          timestampType.hasPrecisionSet()
+              ? toMySQLFractionalSecondsPrecision(timestampType.precision())
+              : TRINO_SECONDS_PRECISION;
+      return timestampType.hasTimeZone()
+          ? 
TimestampWithTimeZoneType.createTimestampWithTimeZoneType(precision)
+          : TimestampType.createTimestampType(precision);
     } else if (Name.TIME == type.name()) {
-      return TimeType.TIME_SECONDS;
+      Types.TimeType timeType = (Types.TimeType) type;
+      // Precision unknown, same fallback as TIMESTAMP above.
+      int precision =
+          timeType.hasPrecisionSet()
+              ? toMySQLFractionalSecondsPrecision(timeType.precision())
+              : TRINO_SECONDS_PRECISION;
+      return TimeType.createTimeType(precision);
     } else if (Name.EXTERNAL == type.name()) {
       String catalogString = ((Types.ExternalType) type).catalogString();
       return MySQLExternalDataType.safeValueOf(catalogString).getTrinoType();
@@ -64,6 +75,18 @@ public class MySQLDataTypeTransformer extends 
GeneralDataTypeTransformer {
     return super.getTrinoType(type);
   }
 
+  @Override
+  public Optional<io.trino.spi.type.Type> 
getSupportedType(io.trino.spi.type.Type type) {
+    if (type instanceof TimeType
+        || type instanceof TimestampType
+        || type instanceof TimestampWithTimeZoneType) {
+      // The column is created with the precision kept by getGravitinoType, at 
most 6 for MySQL.
+      io.trino.spi.type.Type supported = getTrinoType(getGravitinoType(type));
+      return supported.equals(type) ? Optional.empty() : 
Optional.of(supported);
+    }
+    return super.getSupportedType(type);
+  }
+
   @Override
   public Type getGravitinoType(io.trino.spi.type.Type type) {
     Class<? extends io.trino.spi.type.Type> typeClass = type.getClass();
@@ -104,16 +127,32 @@ public class MySQLDataTypeTransformer extends 
GeneralDataTypeTransformer {
     } else if (typeClass == JSON_TYPE.getClass()) {
       return 
Types.ExternalType.of(MySQLExternalDataType.JSON.getMysqlTypeName());
     } else if (io.trino.spi.type.TimeType.class.isAssignableFrom(typeClass)) {
-      // MySQL only supports time type with second (0) precision
-      return Types.TimeType.of(TRINO_SECONDS_PRECISION);
+      // Same as the native Trino MySQL connector: time(p) is stored as 
TIME(p), p capped at 6
+      return Types.TimeType.of(toMySQLFractionalSecondsPrecision(((TimeType) 
type).getPrecision()));
     } else if 
(io.trino.spi.type.TimestampType.class.isAssignableFrom(typeClass)) {
-      // MySQL only supports timestamp type (without time zone) with second 
(0) precision
-      return Types.TimestampType.withoutTimeZone(TRINO_SECONDS_PRECISION);
+      // timestamp(p) is stored as DATETIME(p), p capped at 6
+      return Types.TimestampType.withoutTimeZone(
+          toMySQLFractionalSecondsPrecision(((TimestampType) 
type).getPrecision()));
     } else if 
(io.trino.spi.type.TimestampWithTimeZoneType.class.isAssignableFrom(typeClass)) 
{
-      // MySQL only supports timestamp with time zone type with second (0) 
precision
-      return Types.TimestampType.withTimeZone(TRINO_SECONDS_PRECISION);
+      // timestamp(p) with time zone is stored as TIMESTAMP(p), p capped at 6
+      return Types.TimestampType.withTimeZone(
+          toMySQLFractionalSecondsPrecision(((TimestampWithTimeZoneType) 
type).getPrecision()));
     }
 
     return super.getGravitinoType(type);
   }
+
+  /**
+   * Maps a time/timestamp precision between Gravitino and Trino. The Trino 
type must carry the same
+   * precision as the MySQL column, otherwise Trino rejects the values read 
from MySQL (for example,
+   * "Expected 0s for digits beyond precision 0").
+   *
+   * @param precision the precision of the Gravitino or Trino type.
+   * @return the precision to use for the other side, capped at the MySQL 
maximum of 6.
+   */
+  private static int toMySQLFractionalSecondsPrecision(int precision) {
+    // MySQL supports a fractional seconds precision from 0 to 6 (microseconds 
precision), see
+    // https://dev.mysql.com/doc/refman/8.0/en/fractional-seconds.html
+    return Math.min(TRINO_MICROS_PRECISION, precision);
+  }
 }
diff --git 
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/util/GeneralDataTypeTransformer.java
 
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/util/GeneralDataTypeTransformer.java
index 90c5222512..069d61f4e0 100644
--- 
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/util/GeneralDataTypeTransformer.java
+++ 
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/util/GeneralDataTypeTransformer.java
@@ -188,6 +188,18 @@ public class GeneralDataTypeTransformer {
     return StructType.of(fields);
   }
 
+  /**
+   * Returns the Trino type a column of the requested type is created with 
when it differs from the
+   * requested one. Trino coerces the values written by CREATE TABLE AS SELECT 
to this type, so the
+   * pages written by the engine match the column the internal connector reads 
and writes.
+   *
+   * @param type the type requested by the user.
+   * @return the type the column is created with, or empty when the requested 
type is used as is.
+   */
+  public Optional<Type> getSupportedType(Type type) {
+    return Optional.empty();
+  }
+
   /**
    * Transforms a Trino datatype to a Gravitino datatype.
    *
diff --git 
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoMetadataGetSupportedType.java
 
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoMetadataGetSupportedType.java
new file mode 100644
index 0000000000..70651c8d33
--- /dev/null
+++ 
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoMetadataGetSupportedType.java
@@ -0,0 +1,66 @@
+/*
+ * 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.gravitino.trino.connector;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.trino.spi.connector.ConnectorMetadata;
+import io.trino.spi.connector.ConnectorSession;
+import io.trino.spi.type.TimestampType;
+import io.trino.spi.type.VarcharType;
+import java.util.Collections;
+import java.util.Optional;
+import org.apache.gravitino.trino.connector.catalog.CatalogConnectorMetadata;
+import 
org.apache.gravitino.trino.connector.catalog.CatalogConnectorMetadataAdapter;
+import 
org.apache.gravitino.trino.connector.catalog.jdbc.mysql.MySQLDataTypeTransformer;
+import org.junit.jupiter.api.Test;
+
+public class TestGravitinoMetadataGetSupportedType {
+
+  @Test
+  public void testDelegatesToDataTypeTransformer() {
+    CatalogConnectorMetadataAdapter metadataAdapter = 
mock(CatalogConnectorMetadataAdapter.class);
+    when(metadataAdapter.getDataTypeTransformer()).thenReturn(new 
MySQLDataTypeTransformer());
+    ConnectorSession session = mock(ConnectorSession.class);
+    GravitinoMetadata metadata =
+        new StubGravitinoMetadata(
+            mock(CatalogConnectorMetadata.class), metadataAdapter, 
mock(ConnectorMetadata.class));
+
+    assertEquals(
+        Optional.of(TimestampType.TIMESTAMP_MICROS),
+        metadata.getSupportedType(session, Collections.emptyMap(), 
TimestampType.TIMESTAMP_NANOS));
+    assertEquals(
+        Optional.empty(),
+        metadata.getSupportedType(session, Collections.emptyMap(), 
TimestampType.TIMESTAMP_MICROS));
+    assertEquals(
+        Optional.empty(),
+        metadata.getSupportedType(session, Collections.emptyMap(), 
VarcharType.VARCHAR));
+  }
+
+  private static final class StubGravitinoMetadata extends GravitinoMetadata {
+    private StubGravitinoMetadata(
+        CatalogConnectorMetadata catalogConnectorMetadata,
+        CatalogConnectorMetadataAdapter metadataAdapter,
+        ConnectorMetadata internalMetadata) {
+      super(catalogConnectorMetadata, metadataAdapter, internalMetadata);
+    }
+  }
+}
diff --git 
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/TestMySQLDataTypeTransformer.java
 
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/TestMySQLDataTypeTransformer.java
index 8aee5f8e02..b556eb2aed 100644
--- 
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/TestMySQLDataTypeTransformer.java
+++ 
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/TestMySQLDataTypeTransformer.java
@@ -26,8 +26,12 @@ import io.trino.spi.type.DateType;
 import io.trino.spi.type.DoubleType;
 import io.trino.spi.type.IntegerType;
 import io.trino.spi.type.RealType;
+import io.trino.spi.type.TimeType;
+import io.trino.spi.type.TimestampType;
+import io.trino.spi.type.TimestampWithTimeZoneType;
 import io.trino.spi.type.VarbinaryType;
 import io.trino.spi.type.VarcharType;
+import java.util.Optional;
 import org.apache.gravitino.rel.types.Type;
 import org.apache.gravitino.rel.types.Types;
 import org.apache.gravitino.trino.connector.util.GeneralDataTypeTransformer;
@@ -109,6 +113,133 @@ public class TestMySQLDataTypeTransformer {
         io.trino.spi.type.DecimalType.createDecimalType(20, 0));
   }
 
+  @Test
+  public void testGravitinoTimestampToTrinoType() {
+    GeneralDataTypeTransformer transformer = new MySQLDataTypeTransformer();
+
+    // MySQL DATETIME/TIMESTAMP default to a fractional seconds precision of 0.
+    Assertions.assertEquals(
+        TimestampType.TIMESTAMP_SECONDS,
+        transformer.getTrinoType(Types.TimestampType.withoutTimeZone()));
+    Assertions.assertEquals(
+        TimestampWithTimeZoneType.TIMESTAMP_TZ_SECONDS,
+        transformer.getTrinoType(Types.TimestampType.withTimeZone()));
+
+    // The Trino type must keep the precision of the MySQL column, otherwise 
reading a
+    // DATETIME(3)/TIMESTAMP(6) column fails with "Expected 0s for digits 
beyond precision 0".
+    for (int precision = 0; precision <= 6; precision++) {
+      Assertions.assertEquals(
+          TimestampType.createTimestampType(precision),
+          
transformer.getTrinoType(Types.TimestampType.withoutTimeZone(precision)));
+      Assertions.assertEquals(
+          TimestampWithTimeZoneType.createTimestampWithTimeZoneType(precision),
+          
transformer.getTrinoType(Types.TimestampType.withTimeZone(precision)));
+    }
+
+    // MySQL supports at most microseconds precision.
+    Assertions.assertEquals(
+        TimestampType.TIMESTAMP_MICROS,
+        transformer.getTrinoType(Types.TimestampType.withoutTimeZone(9)));
+    Assertions.assertEquals(
+        TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS,
+        transformer.getTrinoType(Types.TimestampType.withTimeZone(12)));
+  }
+
+  @Test
+  public void testGravitinoTimeToTrinoType() {
+    GeneralDataTypeTransformer transformer = new MySQLDataTypeTransformer();
+
+    // MySQL TIME defaults to a fractional seconds precision of 0.
+    Assertions.assertEquals(TimeType.TIME_SECONDS, 
transformer.getTrinoType(Types.TimeType.get()));
+
+    for (int precision = 0; precision <= 6; precision++) {
+      Assertions.assertEquals(
+          TimeType.createTimeType(precision),
+          transformer.getTrinoType(Types.TimeType.of(precision)));
+    }
+
+    // MySQL supports at most microseconds precision.
+    Assertions.assertEquals(TimeType.TIME_MICROS, 
transformer.getTrinoType(Types.TimeType.of(12)));
+  }
+
+  @Test
+  public void testTrinoTimestampToGravitinoType() {
+    GeneralDataTypeTransformer transformer = new MySQLDataTypeTransformer();
+
+    // Same as the native Trino MySQL connector: the precision is kept, 
DATETIME(p)/TIMESTAMP(p)
+    for (int precision = 0; precision <= 6; precision++) {
+      Assertions.assertEquals(
+          Types.TimestampType.withoutTimeZone(precision),
+          
transformer.getGravitinoType(TimestampType.createTimestampType(precision)));
+      Assertions.assertEquals(
+          Types.TimestampType.withTimeZone(precision),
+          transformer.getGravitinoType(
+              
TimestampWithTimeZoneType.createTimestampWithTimeZoneType(precision)));
+    }
+
+    // MySQL supports at most microseconds precision.
+    Assertions.assertEquals(
+        Types.TimestampType.withoutTimeZone(6),
+        transformer.getGravitinoType(TimestampType.TIMESTAMP_NANOS));
+    Assertions.assertEquals(
+        Types.TimestampType.withTimeZone(6),
+        
transformer.getGravitinoType(TimestampWithTimeZoneType.TIMESTAMP_TZ_PICOS));
+
+    // Round trip keeps the precision, so a table created through Trino reads 
back with the same
+    // types.
+    Assertions.assertEquals(
+        TimestampType.TIMESTAMP_MICROS,
+        
transformer.getTrinoType(transformer.getGravitinoType(TimestampType.TIMESTAMP_MICROS)));
+    Assertions.assertEquals(
+        TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS,
+        transformer.getTrinoType(
+            
transformer.getGravitinoType(TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS)));
+  }
+
+  @Test
+  public void testTrinoTimeToGravitinoType() {
+    GeneralDataTypeTransformer transformer = new MySQLDataTypeTransformer();
+
+    for (int precision = 0; precision <= 6; precision++) {
+      Assertions.assertEquals(
+          Types.TimeType.of(precision),
+          transformer.getGravitinoType(TimeType.createTimeType(precision)));
+    }
+
+    // MySQL supports at most microseconds precision.
+    Assertions.assertEquals(
+        Types.TimeType.of(6), 
transformer.getGravitinoType(TimeType.TIME_NANOS));
+
+    Assertions.assertEquals(
+        TimeType.TIME_MICROS,
+        
transformer.getTrinoType(transformer.getGravitinoType(TimeType.TIME_MICROS)));
+  }
+
+  @Test
+  public void testSupportedType() {
+    GeneralDataTypeTransformer transformer = new MySQLDataTypeTransformer();
+
+    // Up to microseconds the requested type is used as is.
+    Assertions.assertEquals(
+        Optional.empty(), 
transformer.getSupportedType(TimestampType.TIMESTAMP_MICROS));
+    Assertions.assertEquals(
+        Optional.empty(),
+        
transformer.getSupportedType(TimestampWithTimeZoneType.TIMESTAMP_TZ_SECONDS));
+    Assertions.assertEquals(Optional.empty(), 
transformer.getSupportedType(TimeType.TIME_MILLIS));
+    Assertions.assertEquals(
+        Optional.empty(), 
transformer.getSupportedType(VarcharType.createVarcharType(10)));
+
+    // Above microseconds the column is created with precision 6 and the 
values are coerced.
+    Assertions.assertEquals(
+        Optional.of(TimestampType.TIMESTAMP_MICROS),
+        transformer.getSupportedType(TimestampType.TIMESTAMP_NANOS));
+    Assertions.assertEquals(
+        Optional.of(TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS),
+        
transformer.getSupportedType(TimestampWithTimeZoneType.TIMESTAMP_TZ_PICOS));
+    Assertions.assertEquals(
+        Optional.of(TimeType.TIME_MICROS), 
transformer.getSupportedType(TimeType.TIME_NANOS));
+  }
+
   @Test
   public void testGravitinoExternalTypeToTrinoType() {
     GeneralDataTypeTransformer generalDataTypeTransformer = new 
MySQLDataTypeTransformer();

Reply via email to