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
)