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

yuqi1129 pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new 84bea1a4de [Cherry-pick to branch-1.3] [#14687] fix(mysql): render and 
reload Boolean defaults (#14688) (#14717)
84bea1a4de is described below

commit 84bea1a4deb8969c254d586ddac99cd887f125e0
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Nov 6 17:30:25 2026 +0800

    [Cherry-pick to branch-1.3] [#14687] fix(mysql): render and reload Boolean 
defaults (#14688) (#14717)
    
    Backport the MySQL Boolean-default fix to `branch-1.3`. Boolean defaults
    render as numeric 1/0 and BIT(1) defaults reload as Boolean literals,
    preserving create, alter and insert behavior.
    
    **Cherry-pick Information:**
    - Original commit: 171ce25fc828c70341b8e2466f53959157db96d3 (#14688)
    - Target branch: `branch-1.3`
    - Status: Conflicts resolved.
    
    Resolved the conflict in `CatalogMysqlIT`, retaining the Boolean
    regression and excluding unrelated datetime-precision test context
    absent from the target branch. The added and removed code lines match
    the original commit in all four files.
    
    Validation on the resolved branch:
    - 64 relevant unit tests and 3 `CatalogMysqlIT` tests passed with JDK
    17, an embedded Gravitino server and a real MySQL 8.0 container.
    - All 18 converter regression cases fail with the old converter and pass
    after restoring the fix.
    - Module Spotless, conflict-marker scan and `git diff --check` passed.
    
    The Trino SQL suite, MySQL 5.7 and deployed-server matrix were not rerun
    locally.
    
    ---------
    
    Co-authored-by: Qi Yu <[email protected]>
    Co-authored-by: yuqi <[email protected]>
---
 .../MysqlColumnDefaultValueConverter.java          | 41 +++++++++
 .../TestMysqlColumnDefaultValueConverter.java      | 98 ++++++++++++++++++++++
 .../mysql/integration/test/CatalogMysqlIT.java     | 80 +++++++++++++++++-
 .../jdbc-mysql/00011_column_properties.txt         |  4 +-
 4 files changed, 220 insertions(+), 3 deletions(-)

diff --git 
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/converter/MysqlColumnDefaultValueConverter.java
 
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/converter/MysqlColumnDefaultValueConverter.java
index 3ea3476fd9..ecdf91aa81 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/converter/MysqlColumnDefaultValueConverter.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/converter/MysqlColumnDefaultValueConverter.java
@@ -25,17 +25,48 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.format.DateTimeParseException;
+import javax.annotation.Nullable;
 import org.apache.commons.lang3.StringUtils;
 import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
 import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
 import org.apache.gravitino.rel.expressions.Expression;
 import org.apache.gravitino.rel.expressions.UnparsedExpression;
+import org.apache.gravitino.rel.expressions.literals.Literal;
 import org.apache.gravitino.rel.expressions.literals.Literals;
 import org.apache.gravitino.rel.types.Decimal;
 import org.apache.gravitino.rel.types.Types;
 
 public class MysqlColumnDefaultValueConverter extends 
JdbcColumnDefaultValueConverter {
 
+  /**
+   * Renders Boolean literal defaults as numeric 1 or 0. Gravitino maps 
BOOLEAN to MySQL BIT, which
+   * rejects the quoted 'true' and 'false' strings. The check uses the literal 
type, so a Boolean
+   * default that is loaded from BIT(1) and then re-rendered by MODIFY COLUMN 
keeps working after a
+   * type change to a numeric column. Other defaults use the shared JDBC 
rendering.
+   *
+   * @param defaultValue the Gravitino default expression
+   * @return the SQL expression, or null when no default is set
+   * @throws IllegalArgumentException if a Boolean default is not true, false, 
1 or 0
+   */
+  @Override
+  @Nullable
+  public String fromGravitino(Expression defaultValue) {
+    if (defaultValue instanceof Literal
+        && ((Literal<?>) defaultValue).dataType() instanceof 
Types.BooleanType) {
+      String value = String.valueOf(((Literal<?>) 
defaultValue).value()).trim();
+      if ("true".equalsIgnoreCase(value) || "1".equals(value)) {
+        return "1";
+      }
+      if ("false".equalsIgnoreCase(value) || "0".equals(value)) {
+        return "0";
+      }
+      throw new IllegalArgumentException(
+          String.format(
+              "Invalid boolean column default value: %s; use true, false, 1 or 
0", value));
+    }
+    return super.fromGravitino(defaultValue);
+  }
+
   @Override
   public Expression toGravitino(
       JdbcTypeConverter.JdbcTypeBean type,
@@ -60,6 +91,16 @@ public class MysqlColumnDefaultValueConverter extends 
JdbcColumnDefaultValueConv
     }
 
     switch (type.getTypeName().toLowerCase()) {
+      case MysqlTypeConverter.BIT:
+        if (type.getColumnSize() == null || type.getColumnSize() == 1) {
+          if ("b'1'".equalsIgnoreCase(columnDefaultValue) || 
"1".equals(columnDefaultValue)) {
+            return Literals.booleanLiteral(true);
+          }
+          if ("b'0'".equalsIgnoreCase(columnDefaultValue) || 
"0".equals(columnDefaultValue)) {
+            return Literals.booleanLiteral(false);
+          }
+        }
+        return UnparsedExpression.of(columnDefaultValue);
       case MysqlTypeConverter.TINYINT:
         return Literals.byteLiteral(Byte.valueOf(columnDefaultValue));
       case MysqlTypeConverter.TINYINT_UNSIGNED:
diff --git 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/converter/TestMysqlColumnDefaultValueConverter.java
 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/converter/TestMysqlColumnDefaultValueConverter.java
new file mode 100644
index 0000000000..c52d0d28a1
--- /dev/null
+++ 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/converter/TestMysqlColumnDefaultValueConverter.java
@@ -0,0 +1,98 @@
+/*
+ * 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.catalog.mysql.converter;
+
+import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.expressions.UnparsedExpression;
+import org.apache.gravitino.rel.expressions.literals.Literals;
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class TestMysqlColumnDefaultValueConverter {
+  private final MysqlColumnDefaultValueConverter converter = new 
MysqlColumnDefaultValueConverter();
+
+  @ParameterizedTest
+  @ValueSource(strings = {"true", "TRUE", "1"})
+  void testTrueDefaultsAreUnquoted(String value) {
+    Assertions.assertEquals(
+        "1", converter.fromGravitino(Literals.of(value, 
Types.BooleanType.get())));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"false", "FALSE", "0"})
+  void testFalseDefaultsAreUnquoted(String value) {
+    Assertions.assertEquals(
+        "0", converter.fromGravitino(Literals.of(value, 
Types.BooleanType.get())));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"", "2", "-1", "truthy", "true OR 1=1"})
+  void testInvalidBooleanDefaultsAreRejected(String value) {
+    IllegalArgumentException error =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () -> converter.fromGravitino(Literals.of(value, 
Types.BooleanType.get())));
+    Assertions.assertTrue(error.getMessage().contains("boolean"));
+    Assertions.assertTrue(error.getMessage().contains("use true, false, 1 or 
0"));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"b'1'", "B'1'", "1"})
+  void testBitOneDefaultsLoadAsTrue(String value) {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("BIT");
+    type.setColumnSize(1);
+    Assertions.assertEquals(
+        Literals.booleanLiteral(true), converter.toGravitino(type, value, 
false, false));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"b'0'", "B'0'", "0"})
+  void testBitOneDefaultsLoadAsFalse(String value) {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("bit");
+    type.setColumnSize(1);
+    Assertions.assertEquals(
+        Literals.booleanLiteral(false), converter.toGravitino(type, value, 
false, false));
+  }
+
+  @Test
+  void testOtherDefaultsKeepTheirMeaning() {
+    Assertions.assertEquals("1", 
converter.fromGravitino(Literals.booleanLiteral(true)));
+    Assertions.assertEquals("0", 
converter.fromGravitino(Literals.booleanLiteral(false)));
+    Assertions.assertEquals("'true'", 
converter.fromGravitino(Literals.stringLiteral("true")));
+    Assertions.assertEquals("NULL", converter.fromGravitino(Literals.NULL));
+    
Assertions.assertNull(converter.fromGravitino(Column.DEFAULT_VALUE_NOT_SET));
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("bit");
+    type.setColumnSize(8);
+    Assertions.assertEquals(
+        UnparsedExpression.of("b'10'"), converter.toGravitino(type, "b'10'", 
false, false));
+    type.setColumnSize(null);
+    Assertions.assertEquals(
+        Literals.booleanLiteral(true), converter.toGravitino(type, "b'1'", 
false, false));
+    type.setColumnSize(1);
+    Assertions.assertEquals(
+        UnparsedExpression.of("b'10'"), converter.toGravitino(type, "b'10'", 
false, false));
+    Assertions.assertEquals(
+        UnparsedExpression.of("unknown()"), converter.toGravitino(type, 
"unknown()", true, false));
+    Assertions.assertEquals(Literals.NULL, converter.toGravitino(type, null, 
false, true));
+  }
+}
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 40b7216e90..f28ec29a0e 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
@@ -26,7 +26,11 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -539,6 +543,60 @@ public class CatalogMysqlIT extends BaseIT {
         createdTable.columns()[4].defaultValue());
   }
 
+  @Test
+  void testBooleanDefaultValues() throws SQLException {
+    NameIdentifier ident =
+        NameIdentifier.of(schemaName, 
GravitinoITUtils.genRandomName("bool_defaults"));
+    catalog
+        .asTableCatalog()
+        .createTable(
+            ident,
+            new Column[] {
+              Column.of(
+                  "enabled",
+                  Types.BooleanType.get(),
+                  null,
+                  false,
+                  false,
+                  Literals.of("true", Types.BooleanType.get())),
+              Column.of(
+                  "disabled",
+                  Types.BooleanType.get(),
+                  null,
+                  false,
+                  false,
+                  Literals.of("false", Types.BooleanType.get()))
+            },
+            null,
+            ImmutableMap.of());
+    Table created = catalog.asTableCatalog().loadTable(ident);
+    Assertions.assertEquals(Literals.booleanLiteral(true), 
created.columns()[0].defaultValue());
+    Assertions.assertEquals(Literals.booleanLiteral(false), 
created.columns()[1].defaultValue());
+
+    catalog
+        .asTableCatalog()
+        .alterTable(
+            ident,
+            TableChange.updateColumnDefaultValue(
+                new String[] {"enabled"}, Literals.of("0", 
Types.BooleanType.get())),
+            TableChange.updateColumnDefaultValue(
+                new String[] {"disabled"}, Literals.of("1", 
Types.BooleanType.get())));
+    Table altered = catalog.asTableCatalog().loadTable(ident);
+    Assertions.assertEquals(Literals.booleanLiteral(false), 
altered.columns()[0].defaultValue());
+    Assertions.assertEquals(Literals.booleanLiteral(true), 
altered.columns()[1].defaultValue());
+    assertBooleanDefaultInsert(ident, false, true);
+
+    // MODIFY COLUMN re-renders the loaded BIT(1) default. Before Boolean 
reloading, the loaded
+    // b'0' value was an unparsed expression and this comment change failed.
+    catalog
+        .asTableCatalog()
+        .alterTable(ident, TableChange.updateColumnComment(new String[] 
{"enabled"}, "flag"));
+    Table commented = catalog.asTableCatalog().loadTable(ident);
+    Assertions.assertEquals("flag", commented.columns()[0].comment());
+    Assertions.assertEquals(Literals.booleanLiteral(false), 
commented.columns()[0].defaultValue());
+    assertBooleanDefaultInsert(ident, false, true);
+  }
+
   @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
@@ -671,7 +729,7 @@ public class CatalogMysqlIT extends BaseIT {
               Literals.decimalLiteral(Decimal.of("1.2", 6, 2)), 
column.defaultValue());
           break;
         case "bit_col_1":
-          Assertions.assertEquals(UnparsedExpression.of("b'1'"), 
column.defaultValue());
+          Assertions.assertEquals(Literals.booleanLiteral(true), 
column.defaultValue());
           break;
         default:
           Assertions.fail(
@@ -2487,4 +2545,24 @@ public class CatalogMysqlIT extends BaseIT {
       mysqlService.executeQuery("DROP DATABASE IF EXISTS `" + dottedSchemaName 
+ "`");
     }
   }
+
+  private void assertBooleanDefaultInsert(NameIdentifier ident, boolean 
enabled, boolean disabled)
+      throws SQLException {
+    String qualified = "`" + schemaName + "`.`" + ident.name() + "`";
+    try (Connection connection =
+            DriverManager.getConnection(
+                MYSQL_CONTAINER.getJdbcUrl(TEST_DB_NAME),
+                MYSQL_CONTAINER.getUsername(),
+                MYSQL_CONTAINER.getPassword());
+        Statement statement = connection.createStatement()) {
+      statement.executeUpdate("DELETE FROM " + qualified);
+      statement.executeUpdate("INSERT INTO " + qualified + " () VALUES ()");
+      try (ResultSet rows = statement.executeQuery("SELECT enabled, disabled 
FROM " + qualified)) {
+        Assertions.assertTrue(rows.next());
+        Assertions.assertEquals(enabled, rows.getBoolean("enabled"));
+        Assertions.assertEquals(disabled, rows.getBoolean("disabled"));
+        Assertions.assertFalse(rows.next());
+      }
+    }
+  }
 }
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..581e33952e 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
@@ -1,5 +1,5 @@
 "CREATE TABLE 
gt_mysql.gt_mysql_test_column_properties.demo_all_data_types_default_value (
-   col_bit boolean,
+   col_bit boolean WITH (default = 'true'),
    col_tinyint tinyint WITH (default = '0'),
    col_smallint smallint WITH (default = '-1'),
    col_mediumint integer,
@@ -27,7 +27,7 @@
    col_enum varchar,
    col_set varchar,
    col_json json,
-   col_boolean boolean,
+   col_boolean boolean WITH (default = 'true'),
    col_geometry varbinary,
    col_point varbinary
 )

Reply via email to