This is an automated email from the ASF dual-hosted git repository.
lihaosky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/master by this push:
new f84ba2d8714 [FLINK-39889][table] Thread USING CONNECTION clause into
CatalogTable and CreateTableOperation (#28385)
f84ba2d8714 is described below
commit f84ba2d871429f776a5a6ed9a3c3580fa50fcc17
Author: Hao Li <[email protected]>
AuthorDate: Tue Aug 25 08:47:13 2026 -0700
[FLINK-39889][table] Thread USING CONNECTION clause into CatalogTable and
CreateTableOperation (#28385)
* [FLINK-39889][table] Convert table sqlnode to operation
Thread the connection identifier from a parsed USING CONNECTION clause
in SqlCreateTable through to CatalogTable and the CreateTableOperation.
- Add Optional<UnresolvedIdentifier> getConnection() to CatalogTable, a
connection() builder method, and the field/accessor in
DefaultCatalogTable (equals/hashCode/toString/copy updated).
- Delegate getConnection() in ResolvedCatalogTable.
- Read SqlCreateTable.getConnection() in AbstractCreateTableConverter and
set it on the built CatalogTable.
- Serialize/deserialize the connection identifier in CatalogPropertiesUtil
under a reserved 'connection.identifier' key, excluded from connector
options, so it round-trips and appears in CreateTableOperation summaries.
- Add a withConnection() OperationMatcher and tests covering conversion and
property serde.
Part of FLIP-529.
* [FLINK-39889][table] Address review: assert connection directly,
uppercase SQL keywords
* [FLINK-39889][table] Address review: add tests for malformed connection
names
Covers parser rejection of malformed compound identifiers, too-many-parts
and whitespace-only identifier segments failing validation, and unicode
connection names being accepted.
* [FLINK-39889][table] Address review: add multibyte connection name test
---
.../flink/table/catalog/CatalogPropertiesUtil.java | 39 +++++++++++++
.../apache/flink/table/catalog/CatalogTable.java | 16 +++++-
.../flink/table/catalog/DefaultCatalogTable.java | 30 ++++++++--
.../flink/table/catalog/ResolvedCatalogTable.java | 5 ++
.../table/catalog/CatalogPropertiesUtilTest.java | 7 +++
.../table/AbstractCreateTableConverter.java | 6 ++
.../operations/SqlDdlToOperationConverterTest.java | 66 ++++++++++++++++++++++
7 files changed, 164 insertions(+), 5 deletions(-)
diff --git
a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/CatalogPropertiesUtil.java
b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/CatalogPropertiesUtil.java
index 58c8d76d858..fefd946f1d6 100644
---
a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/CatalogPropertiesUtil.java
+++
b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/CatalogPropertiesUtil.java
@@ -96,6 +96,9 @@ public final class CatalogPropertiesUtil {
final Optional<TableDistribution> distribution =
resolvedTable.getDistribution();
distribution.ifPresent(d -> serializeTableDistribution(properties,
d));
+ final Optional<UnresolvedIdentifier> connection =
resolvedTable.getConnection();
+ connection.ifPresent(c -> serializeConnection(properties, c));
+
properties.putAll(resolvedTable.getOptions());
properties.remove(IS_GENERIC); // reserved option
@@ -251,6 +254,8 @@ public final class CatalogPropertiesUtil {
final @Nullable TableDistribution distribution =
deserializeTableDistribution(properties);
+ final @Nullable UnresolvedIdentifier connection =
deserializeConnection(properties);
+
return CatalogTable.newBuilder()
.schema(schema)
.comment(comment)
@@ -258,6 +263,7 @@ public final class CatalogPropertiesUtil {
.distribution(distribution)
.options(options)
.snapshot(snapshot)
+ .connection(connection)
.build();
} catch (Exception e) {
throw new CatalogException("Error in deserializing catalog
table.", e);
@@ -445,12 +451,17 @@ public final class CatalogPropertiesUtil {
private static final String DISTRIBUTION_KEYS = compoundKey(DISTRIBUTION,
KEYS);
+ private static final String CONNECTION = "connection";
+
+ private static final String CONNECTION_IDENTIFIER =
compoundKey(CONNECTION, "identifier");
+
private static Map<String, String> deserializeOptions(Map<String, String>
map) {
return map.entrySet().stream()
.filter(
e -> {
final String key = e.getKey();
return !key.startsWith(DISTRIBUTION + SEPARATOR)
+ && !key.startsWith(CONNECTION + SEPARATOR)
&& !key.startsWith(PARTITION_KEYS +
SEPARATOR)
&& !key.startsWith(SCHEMA)
&& !key.equals(COMMENT)
@@ -652,6 +663,34 @@ public final class CatalogPropertiesUtil {
.collect(Collectors.toList()));
}
+ private static void serializeConnection(
+ Map<String, String> map, UnresolvedIdentifier connection) {
+ final List<String> parts = new ArrayList<>();
+ connection.getCatalogName().ifPresent(parts::add);
+ connection.getDatabaseName().ifPresent(parts::add);
+ parts.add(connection.getObjectName());
+
+ putIndexedProperties(
+ map,
+ CONNECTION_IDENTIFIER,
+ Collections.singletonList(NAME),
+
parts.stream().map(Collections::singletonList).collect(Collectors.toList()));
+ }
+
+ private static UnresolvedIdentifier deserializeConnection(Map<String,
String> map) {
+ final List<String> parts = new ArrayList<>();
+ int i = 0;
+ String partKey = compoundKey(CONNECTION_IDENTIFIER, i, NAME);
+ while (map.containsKey(partKey)) {
+ parts.add(getValue(map, partKey));
+ partKey = compoundKey(CONNECTION_IDENTIFIER, ++i, NAME);
+ }
+ if (parts.isEmpty()) {
+ return null;
+ }
+ return UnresolvedIdentifier.of(parts);
+ }
+
private static void serializeResolvedModelSchema(
Map<String, String> map,
ResolvedSchema inputSchema,
diff --git
a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/CatalogTable.java
b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/CatalogTable.java
index c7539cc556e..3a234e9d1ea 100644
---
a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/CatalogTable.java
+++
b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/CatalogTable.java
@@ -109,6 +109,14 @@ public interface CatalogTable extends CatalogBaseTable {
return Optional.empty();
}
+ /**
+ * Returns the identifier of the connection that this table uses if the
{@code USING CONNECTION}
+ * clause is defined.
+ */
+ default Optional<UnresolvedIdentifier> getConnection() {
+ return Optional.empty();
+ }
+
//
--------------------------------------------------------------------------------------------
/** Builder for configuring and creating instances of {@link
CatalogTable}. */
@@ -120,6 +128,7 @@ public interface CatalogTable extends CatalogBaseTable {
private Map<String, String> options = Collections.emptyMap();
private @Nullable Long snapshot;
private @Nullable TableDistribution distribution;
+ private @Nullable UnresolvedIdentifier connection;
private Builder() {}
@@ -154,9 +163,14 @@ public interface CatalogTable extends CatalogBaseTable {
return this;
}
+ public Builder connection(@Nullable UnresolvedIdentifier connection) {
+ this.connection = connection;
+ return this;
+ }
+
public CatalogTable build() {
return new DefaultCatalogTable(
- schema, comment, partitionKeys, options, snapshot,
distribution);
+ schema, comment, partitionKeys, options, snapshot,
distribution, connection);
}
}
}
diff --git
a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/DefaultCatalogTable.java
b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/DefaultCatalogTable.java
index f94c2399acc..b5e53959881 100644
---
a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/DefaultCatalogTable.java
+++
b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/DefaultCatalogTable.java
@@ -42,6 +42,7 @@ public class DefaultCatalogTable implements CatalogTable {
private final List<String> partitionKeys;
private final Map<String, String> options;
private final @Nullable Long snapshot;
+ private final @Nullable UnresolvedIdentifier connection;
protected DefaultCatalogTable(
Schema schema,
@@ -58,12 +59,24 @@ public class DefaultCatalogTable implements CatalogTable {
Map<String, String> options,
@Nullable Long snapshot,
@Nullable TableDistribution distribution) {
+ this(schema, comment, partitionKeys, options, snapshot, distribution,
null);
+ }
+
+ protected DefaultCatalogTable(
+ Schema schema,
+ @Nullable String comment,
+ List<String> partitionKeys,
+ Map<String, String> options,
+ @Nullable Long snapshot,
+ @Nullable TableDistribution distribution,
+ @Nullable UnresolvedIdentifier connection) {
this.schema = checkNotNull(schema, "Schema must not be null.");
this.comment = comment;
this.partitionKeys = checkNotNull(partitionKeys, "Partition keys must
not be null.");
this.options = checkNotNull(options, "Options must not be null.");
this.snapshot = snapshot;
this.distribution = distribution;
+ this.connection = connection;
checkArgument(
options.entrySet().stream()
@@ -96,6 +109,11 @@ public class DefaultCatalogTable implements CatalogTable {
return Optional.ofNullable(distribution);
}
+ @Override
+ public Optional<UnresolvedIdentifier> getConnection() {
+ return Optional.ofNullable(connection);
+ }
+
@Override
public Map<String, String> getOptions() {
return options;
@@ -109,13 +127,13 @@ public class DefaultCatalogTable implements CatalogTable {
@Override
public CatalogBaseTable copy() {
return new DefaultCatalogTable(
- schema, comment, partitionKeys, options, snapshot,
distribution);
+ schema, comment, partitionKeys, options, snapshot,
distribution, connection);
}
@Override
public CatalogTable copy(Map<String, String> options) {
return new DefaultCatalogTable(
- schema, comment, partitionKeys, options, snapshot,
distribution);
+ schema, comment, partitionKeys, options, snapshot,
distribution, connection);
}
@Override
@@ -142,12 +160,14 @@ public class DefaultCatalogTable implements CatalogTable {
&& Objects.equals(distribution, that.distribution)
&& partitionKeys.equals(that.partitionKeys)
&& options.equals(that.options)
- && Objects.equals(snapshot, that.snapshot);
+ && Objects.equals(snapshot, that.snapshot)
+ && Objects.equals(connection, that.connection);
}
@Override
public int hashCode() {
- return Objects.hash(schema, comment, distribution, partitionKeys,
options, snapshot);
+ return Objects.hash(
+ schema, comment, distribution, partitionKeys, options,
snapshot, connection);
}
@Override
@@ -166,6 +186,8 @@ public class DefaultCatalogTable implements CatalogTable {
+ ConfigurationUtils.hideSensitiveValues(options, List.of())
+ ", snapshot="
+ snapshot
+ + ", connection="
+ + connection
+ '}';
}
}
diff --git
a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/ResolvedCatalogTable.java
b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/ResolvedCatalogTable.java
index f83b2601eb7..bcc6a378d23 100644
---
a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/ResolvedCatalogTable.java
+++
b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/catalog/ResolvedCatalogTable.java
@@ -145,6 +145,11 @@ public final class ResolvedCatalogTable
return origin.getDistribution();
}
+ @Override
+ public Optional<UnresolvedIdentifier> getConnection() {
+ return origin.getConnection();
+ }
+
@Override
public ResolvedCatalogTable copy(Map<String, String> options) {
return new ResolvedCatalogTable(origin.copy(options), resolvedSchema);
diff --git
a/flink-table/flink-table-common/src/test/java/org/apache/flink/table/catalog/CatalogPropertiesUtilTest.java
b/flink-table/flink-table-common/src/test/java/org/apache/flink/table/catalog/CatalogPropertiesUtilTest.java
index 3712ff2057f..3ffc27c4b5d 100644
---
a/flink-table/flink-table-common/src/test/java/org/apache/flink/table/catalog/CatalogPropertiesUtilTest.java
+++
b/flink-table/flink-table-common/src/test/java/org/apache/flink/table/catalog/CatalogPropertiesUtilTest.java
@@ -160,6 +160,13 @@ class CatalogPropertiesUtilTest {
.distribution(rangeDist)
.build(),
resolvedSchema),
+ new ResolvedCatalogTable(
+ CatalogTable.newBuilder()
+ .schema(schema)
+ .comment("some comment")
+ .connection(UnresolvedIdentifier.of("mycat",
"mydb", "myconn"))
+ .build(),
+ resolvedSchema),
new ResolvedCatalogMaterializedTable(
CatalogMaterializedTable.newBuilder()
.schema(schema)
diff --git
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/operations/converters/table/AbstractCreateTableConverter.java
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/operations/converters/table/AbstractCreateTableConverter.java
index 5899c767f38..375166f60be 100644
---
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/operations/converters/table/AbstractCreateTableConverter.java
+++
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/operations/converters/table/AbstractCreateTableConverter.java
@@ -73,6 +73,11 @@ public abstract class AbstractCreateTableConverter<T extends
SqlCreateTable>
final Map<String, String> tableOptions =
mergeContext.getMergedTableOptions();
final TableDistribution distribution =
mergeContext.getMergedTableDistribution().orElse(null);
+ final UnresolvedIdentifier connection =
+ sqlCreateTable
+ .getConnection()
+ .map(c -> UnresolvedIdentifier.of(c.names))
+ .orElse(null);
final String comment = sqlCreateTable.getComment();
final CatalogTable catalogTable =
CatalogTable.newBuilder()
@@ -81,6 +86,7 @@ public abstract class AbstractCreateTableConverter<T extends
SqlCreateTable>
.distribution(distribution)
.options(tableOptions)
.partitionKeys(partitionKeys)
+ .connection(connection)
.build();
return context.getCatalogManager().resolveCatalogTable(catalogTable);
}
diff --git
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/operations/SqlDdlToOperationConverterTest.java
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/operations/SqlDdlToOperationConverterTest.java
index 2c5dd5d0d71..91de5b2aa89 100644
---
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/operations/SqlDdlToOperationConverterTest.java
+++
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/operations/SqlDdlToOperationConverterTest.java
@@ -24,6 +24,7 @@ import org.apache.flink.sql.parser.error.SqlValidateException;
import org.apache.flink.table.api.DataTypes;
import org.apache.flink.table.api.Schema;
import org.apache.flink.table.api.SqlDialect;
+import org.apache.flink.table.api.SqlParserException;
import org.apache.flink.table.api.ValidationException;
import org.apache.flink.table.catalog.Catalog;
import org.apache.flink.table.catalog.CatalogDatabaseImpl;
@@ -45,6 +46,7 @@ import org.apache.flink.table.catalog.ResolvedSchema;
import org.apache.flink.table.catalog.TableChange;
import org.apache.flink.table.catalog.TableDistribution;
import org.apache.flink.table.catalog.TableDistribution.Kind;
+import org.apache.flink.table.catalog.UnresolvedIdentifier;
import org.apache.flink.table.catalog.exceptions.DatabaseNotExistException;
import org.apache.flink.table.catalog.exceptions.FunctionAlreadyExistException;
import org.apache.flink.table.expressions.DefaultSqlFactory;
@@ -89,6 +91,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
import javax.annotation.Nullable;
@@ -658,6 +661,69 @@ class SqlDdlToOperationConverterTest extends
SqlNodeToOperationConversionTestBas
Collections.singletonList("a"), null)))));
}
+ @Test
+ void testCreateTableWithConnection() {
+ final String sql =
+ "CREATE TABLE derivedTable(\n"
+ + " a INT\n"
+ + ")\n"
+ + "USING CONNECTION mycat.mydb.myconn";
+ Operation operation = parseAndConvert(sql);
+ assertThat(operation).isInstanceOf(CreateTableOperation.class);
+ CreateTableOperation op = (CreateTableOperation) operation;
+ assertThat(op.getCatalogTable().getConnection())
+ .hasValue(UnresolvedIdentifier.of("mycat", "mydb", "myconn"));
+ }
+
+ @Test
+ void testCreateTableWithoutConnection() {
+ final String sql = "CREATE TABLE derivedTable(\n" + " a INT\n" + ")";
+ Operation operation = parseAndConvert(sql);
+ assertThat(operation).isInstanceOf(CreateTableOperation.class);
+ CreateTableOperation op = (CreateTableOperation) operation;
+ assertThat(op.getCatalogTable().getConnection()).isEmpty();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"mycat....mydb....myconn", ".", "...", ".2.2."})
+ void testCreateTableWithMalformedConnectionNameFailsToParse(String
connectionName) {
+ final String sql = "CREATE TABLE derivedTable(a INT) USING CONNECTION
" + connectionName;
+ assertThatThrownBy(() ->
parseAndConvert(sql)).isInstanceOf(SqlParserException.class);
+ }
+
+ @Test
+ void testCreateTableWithTooManyConnectionNameParts() {
+ final String sql =
+ "CREATE TABLE derivedTable(a INT) USING CONNECTION
mycat.mydb.mygroup.myconn";
+ assertThatThrownBy(() -> parseAndConvert(sql))
+ .isInstanceOf(ValidationException.class)
+ .hasMessageContaining("Object identifier must consist of 1 to
3 parts.");
+ }
+
+ @Test
+ void testCreateTableWithWhitespaceOnlyConnectionNamePart() {
+ final String sql = "CREATE TABLE derivedTable(a INT) USING CONNECTION
mycat.` `.myconn";
+ assertThatThrownBy(() -> parseAndConvert(sql))
+ .isInstanceOf(ValidationException.class)
+ .hasMessageContaining(
+ "Parts of the object identifier are null or
whitespace-only.");
+ }
+
+ @Test
+ void testCreateTableWithUnicodeConnectionName() {
+ final String sql = "CREATE TABLE derivedTable(a INT) USING CONNECTION
`😍.😍`";
+ CreateTableOperation op = (CreateTableOperation) parseAndConvert(sql);
+
assertThat(op.getCatalogTable().getConnection()).hasValue(UnresolvedIdentifier.of("😍.😍"));
+ }
+
+ @Test
+ void testCreateTableWithMultibyteConnectionName() {
+ final String sql = "CREATE TABLE derivedTable(a INT) USING CONNECTION
`目录`.`Привет`.`café`";
+ CreateTableOperation op = (CreateTableOperation) parseAndConvert(sql);
+ assertThat(op.getCatalogTable().getConnection())
+ .hasValue(UnresolvedIdentifier.of("目录", "Привет", "café"));
+ }
+
@Test
void testCreateTableInvalidDistribution() {
final String sql =