Copilot commented on code in PR #9761: URL: https://github.com/apache/gravitino/pull/9761#discussion_r2735754628
########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/ClickHouseCatalogPropertiesMeta.java: ########## @@ -0,0 +1,78 @@ +/* + * 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.clickhouse; + +import static org.apache.gravitino.connector.PropertyEntry.booleanPropertyEntry; +import static org.apache.gravitino.connector.PropertyEntry.stringPropertyEntry; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.util.List; +import java.util.Map; +import org.apache.gravitino.catalog.jdbc.JdbcCatalogPropertiesMetadata; +import org.apache.gravitino.connector.PropertyEntry; + +public class ClickHouseCatalogPropertiesMeta extends JdbcCatalogPropertiesMetadata { Review Comment: Naming is inconsistent with the rest of the codebase: most property-metadata classes are named *PropertiesMetadata (e.g., JdbcCatalogPropertiesMetadata, HiveCatalogPropertiesMetadata), but this one ends with *PropertiesMeta. Consider renaming to ClickHouseCatalogPropertiesMetadata (and updating its usages/tests) for consistency. ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseDatabaseOperations.java: ########## @@ -18,19 +18,104 @@ */ package org.apache.gravitino.catalog.clickhouse.operations; -import com.google.common.collect.Sets; +import static org.apache.gravitino.catalog.clickhouse.ClickHouseConfig.DEFAULT_CK_ON_CLUSTER; + +import com.google.common.collect.ImmutableSet; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Set; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.StringIdentifier; +import org.apache.gravitino.catalog.clickhouse.ClickHouseConfig; +import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter; import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations; public class ClickHouseDatabaseOperations extends JdbcDatabaseOperations { + private boolean onCluster = false; + private String clusterName = null; + + @Override + public void initialize( + DataSource dataSource, JdbcExceptionConverter exceptionMapper, Map<String, String> conf) { + super.initialize(dataSource, exceptionMapper, conf); + + final String cn = conf.get(ClickHouseConfig.CK_CLUSTER_NAME.getKey()); + if (StringUtils.isNotBlank(cn)) { + clusterName = cn; + } + + final String oc = + conf.getOrDefault( + ClickHouseConfig.CK_ON_CLUSTER.getKey(), String.valueOf(DEFAULT_CK_ON_CLUSTER)); + onCluster = Boolean.parseBoolean(oc); + + if (onCluster && StringUtils.isBlank(clusterName)) { + throw new IllegalArgumentException( + "ClickHouse 'ON CLUSTER' is enabled, but cluster name is not provided."); + } + } + @Override protected boolean supportSchemaComment() { - return false; + return true; } @Override protected Set<String> createSysDatabaseNameSet() { - return Sets.newHashSet(); + return ImmutableSet.of("information_schema", "INFORMATION_SCHEMA", "default", "system"); + } + + @Override + public List<String> listDatabases() { + List<String> databaseNames = new ArrayList<>(); + try (final Connection connection = getConnection()) { + // It is possible that other catalogs have been deleted, + // causing the following statement to error, + // so here we manually set a system catalog + connection.setCatalog(createSysDatabaseNameSet().iterator().next()); + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SHOW DATABASES")) { + while (resultSet.next()) { + String databaseName = resultSet.getString(1); + if (!isSystemDatabase(databaseName)) { + databaseNames.add(databaseName); + } + } + } + return databaseNames; + } catch (final SQLException se) { + throw this.exceptionMapper.toGravitinoException(se); + } + } + + @Override + protected String generateCreateDatabaseSql( + String databaseName, String comment, Map<String, String> properties) { + + String originComment = StringIdentifier.removeIdFromComment(comment); + if (!supportSchemaComment() && StringUtils.isNotEmpty(originComment)) { + throw new UnsupportedOperationException( + "Doesn't support setting schema comment: " + originComment); + } + + StringBuilder createDatabaseSql = + new StringBuilder(String.format("CREATE DATABASE `%s`", databaseName)); + if (onCluster && StringUtils.isNotBlank(clusterName)) { + createDatabaseSql.append(String.format(" ON CLUSTER %s", clusterName)); + } + + if (StringUtils.isNotEmpty(comment)) { Review Comment: The COMMENT clause is guarded by StringUtils.isNotEmpty(comment), but the SQL uses originComment (comment with the Gravitino identifier removed). When users provide a blank comment, JdbcCatalogOperations will still add the identifier to the comment, making `comment` non-empty while `originComment` becomes empty, producing `COMMENT ''`. Gate the clause on originComment instead (and use originComment consistently). ```suggestion if (StringUtils.isNotEmpty(originComment)) { ``` ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseDatabaseOperations.java: ########## @@ -18,19 +18,104 @@ */ package org.apache.gravitino.catalog.clickhouse.operations; -import com.google.common.collect.Sets; +import static org.apache.gravitino.catalog.clickhouse.ClickHouseConfig.DEFAULT_CK_ON_CLUSTER; + +import com.google.common.collect.ImmutableSet; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Set; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.StringIdentifier; +import org.apache.gravitino.catalog.clickhouse.ClickHouseConfig; +import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter; import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations; public class ClickHouseDatabaseOperations extends JdbcDatabaseOperations { + private boolean onCluster = false; + private String clusterName = null; + + @Override + public void initialize( + DataSource dataSource, JdbcExceptionConverter exceptionMapper, Map<String, String> conf) { + super.initialize(dataSource, exceptionMapper, conf); + + final String cn = conf.get(ClickHouseConfig.CK_CLUSTER_NAME.getKey()); + if (StringUtils.isNotBlank(cn)) { + clusterName = cn; + } + + final String oc = + conf.getOrDefault( + ClickHouseConfig.CK_ON_CLUSTER.getKey(), String.valueOf(DEFAULT_CK_ON_CLUSTER)); + onCluster = Boolean.parseBoolean(oc); + + if (onCluster && StringUtils.isBlank(clusterName)) { + throw new IllegalArgumentException( + "ClickHouse 'ON CLUSTER' is enabled, but cluster name is not provided."); + } + } + @Override protected boolean supportSchemaComment() { - return false; + return true; } @Override protected Set<String> createSysDatabaseNameSet() { - return Sets.newHashSet(); + return ImmutableSet.of("information_schema", "INFORMATION_SCHEMA", "default", "system"); + } + + @Override + public List<String> listDatabases() { + List<String> databaseNames = new ArrayList<>(); + try (final Connection connection = getConnection()) { + // It is possible that other catalogs have been deleted, + // causing the following statement to error, + // so here we manually set a system catalog + connection.setCatalog(createSysDatabaseNameSet().iterator().next()); + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SHOW DATABASES")) { + while (resultSet.next()) { + String databaseName = resultSet.getString(1); + if (!isSystemDatabase(databaseName)) { + databaseNames.add(databaseName); + } + } + } + return databaseNames; + } catch (final SQLException se) { + throw this.exceptionMapper.toGravitinoException(se); + } + } + + @Override + protected String generateCreateDatabaseSql( + String databaseName, String comment, Map<String, String> properties) { + + String originComment = StringIdentifier.removeIdFromComment(comment); + if (!supportSchemaComment() && StringUtils.isNotEmpty(originComment)) { Review Comment: generateCreateDatabaseSql ignores the `properties` argument entirely. Since this catalog now exposes schema properties metadata (e.g., CK_ON_CLUSTER), either consume relevant schema-level properties here (and define precedence vs catalog config) or explicitly reject unsupported schema properties (similar to JdbcDatabaseOperations throwing when properties is non-empty). ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseDatabaseOperations.java: ########## @@ -18,19 +18,104 @@ */ package org.apache.gravitino.catalog.clickhouse.operations; -import com.google.common.collect.Sets; +import static org.apache.gravitino.catalog.clickhouse.ClickHouseConfig.DEFAULT_CK_ON_CLUSTER; + +import com.google.common.collect.ImmutableSet; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Set; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.StringIdentifier; +import org.apache.gravitino.catalog.clickhouse.ClickHouseConfig; +import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter; import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations; public class ClickHouseDatabaseOperations extends JdbcDatabaseOperations { + private boolean onCluster = false; + private String clusterName = null; + + @Override + public void initialize( + DataSource dataSource, JdbcExceptionConverter exceptionMapper, Map<String, String> conf) { + super.initialize(dataSource, exceptionMapper, conf); + + final String cn = conf.get(ClickHouseConfig.CK_CLUSTER_NAME.getKey()); + if (StringUtils.isNotBlank(cn)) { + clusterName = cn; + } + + final String oc = + conf.getOrDefault( + ClickHouseConfig.CK_ON_CLUSTER.getKey(), String.valueOf(DEFAULT_CK_ON_CLUSTER)); + onCluster = Boolean.parseBoolean(oc); + + if (onCluster && StringUtils.isBlank(clusterName)) { + throw new IllegalArgumentException( + "ClickHouse 'ON CLUSTER' is enabled, but cluster name is not provided."); + } + } + @Override protected boolean supportSchemaComment() { - return false; + return true; } @Override protected Set<String> createSysDatabaseNameSet() { - return Sets.newHashSet(); + return ImmutableSet.of("information_schema", "INFORMATION_SCHEMA", "default", "system"); + } + + @Override + public List<String> listDatabases() { + List<String> databaseNames = new ArrayList<>(); + try (final Connection connection = getConnection()) { + // It is possible that other catalogs have been deleted, + // causing the following statement to error, + // so here we manually set a system catalog + connection.setCatalog(createSysDatabaseNameSet().iterator().next()); + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SHOW DATABASES")) { + while (resultSet.next()) { + String databaseName = resultSet.getString(1); + if (!isSystemDatabase(databaseName)) { + databaseNames.add(databaseName); + } + } + } + return databaseNames; + } catch (final SQLException se) { + throw this.exceptionMapper.toGravitinoException(se); + } + } + + @Override + protected String generateCreateDatabaseSql( + String databaseName, String comment, Map<String, String> properties) { + + String originComment = StringIdentifier.removeIdFromComment(comment); + if (!supportSchemaComment() && StringUtils.isNotEmpty(originComment)) { + throw new UnsupportedOperationException( + "Doesn't support setting schema comment: " + originComment); + } + + StringBuilder createDatabaseSql = + new StringBuilder(String.format("CREATE DATABASE `%s`", databaseName)); + if (onCluster && StringUtils.isNotBlank(clusterName)) { + createDatabaseSql.append(String.format(" ON CLUSTER %s", clusterName)); + } Review Comment: CREATE DATABASE appends "ON CLUSTER" when enabled, but DROP DATABASE still uses JdbcDatabaseOperations’ default SQL (no ON CLUSTER). If schemas are created cluster-wide, dropping without ON CLUSTER will only affect the local node. Consider overriding generateDropDatabaseSql to apply the same ON CLUSTER behavior when enabled. ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseExceptionConverter.java: ########## @@ -18,8 +18,36 @@ */ package org.apache.gravitino.catalog.clickhouse.converter; +import java.sql.SQLException; import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter; +import org.apache.gravitino.exceptions.GravitinoRuntimeException; +import org.apache.gravitino.exceptions.NoSuchSchemaException; +import org.apache.gravitino.exceptions.NoSuchTableException; +import org.apache.gravitino.exceptions.SchemaAlreadyExistsException; +import org.apache.gravitino.exceptions.TableAlreadyExistsException; public class ClickHouseExceptionConverter extends JdbcExceptionConverter { - // Implement ClickHouse specific exception conversions if needed + static final int UNKNOWN_DATABASE = 81; + static final int DATABASE_ALREADY_EXISTS = 82; + + static final int TABLE_ALREADY_EXISTS = 57; + static final int TABLE_IS_DROPPED = 218; + + @SuppressWarnings("FormatStringAnnotation") + @Override + public GravitinoRuntimeException toGravitinoException(SQLException sqlException) { + int errorCode = sqlException.getErrorCode(); + switch (errorCode) { + case DATABASE_ALREADY_EXISTS: + return new SchemaAlreadyExistsException(sqlException, sqlException.getMessage()); + case TABLE_ALREADY_EXISTS: + return new TableAlreadyExistsException(sqlException, sqlException.getMessage()); + case UNKNOWN_DATABASE: + return new NoSuchSchemaException(sqlException, sqlException.getMessage()); + case TABLE_IS_DROPPED: + return new NoSuchTableException(sqlException, sqlException.getMessage()); + default: + return new GravitinoRuntimeException(sqlException, sqlException.getMessage()); Review Comment: For unknown ClickHouse error codes, this converter always returns a generic GravitinoRuntimeException. That loses JdbcExceptionConverter’s default handling (e.g., mapping "Access denied" to ConnectionFailedException). Consider delegating the default branch to super.toGravitinoException(sqlException) so generic cases keep consistent behavior. ```suggestion return super.toGravitinoException(sqlException); ``` ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseExceptionConverter.java: ########## @@ -18,8 +18,36 @@ */ package org.apache.gravitino.catalog.clickhouse.converter; +import java.sql.SQLException; import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter; +import org.apache.gravitino.exceptions.GravitinoRuntimeException; +import org.apache.gravitino.exceptions.NoSuchSchemaException; +import org.apache.gravitino.exceptions.NoSuchTableException; +import org.apache.gravitino.exceptions.SchemaAlreadyExistsException; +import org.apache.gravitino.exceptions.TableAlreadyExistsException; public class ClickHouseExceptionConverter extends JdbcExceptionConverter { - // Implement ClickHouse specific exception conversions if needed + static final int UNKNOWN_DATABASE = 81; + static final int DATABASE_ALREADY_EXISTS = 82; + + static final int TABLE_ALREADY_EXISTS = 57; + static final int TABLE_IS_DROPPED = 218; + + @SuppressWarnings("FormatStringAnnotation") + @Override + public GravitinoRuntimeException toGravitinoException(SQLException sqlException) { + int errorCode = sqlException.getErrorCode(); + switch (errorCode) { Review Comment: New exception mapping logic is introduced here (ClickHouse-specific error codes), but there are no unit tests covering the mappings. Add tests that construct SQLException instances with these error codes and assert the expected Gravitino exception types, including the default/fallback behavior. ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/ClickHouseCatalogCapability.java: ########## @@ -1,26 +1,51 @@ /* - * 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 + * 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 + * 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. + * 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.clickhouse; -import org.apache.gravitino.catalog.jdbc.JdbcCatalogCapability; +import org.apache.gravitino.connector.capability.Capability; +import org.apache.gravitino.connector.capability.CapabilityResult; + +public class ClickHouseCatalogCapability implements Capability { + /** + * Regular expression explanation: ^[\w\p{L}-$/=]{1,64}$ + * + * <p>^ - Start of the string + * + * <p>[\w\p{L}-$/=]{1,64} - Consist of 1 to 64 characters of letters (both cases), digits, + * underscores, any kind of letter from any language, hyphens, dollar signs, slashes or equal + * signs + * + * <p>\w - matches [a-zA-Z0-9_] + * + * <p>\p{L} - matches any kind of letter from any language + * + * <p>$ - End of the string + */ + public static final String CLICKHOUSE_NAME_PATTERN = "^[\\w\\p{L}-$/=]{1,64}$"; -public class ClickHouseCatalogCapability extends JdbcCatalogCapability { - // No additional capabilities for ClickHouse at this time + @Override + public CapabilityResult specificationOnName(Scope scope, String name) { + // TODO: Validate the name against reserved words Review Comment: Avoid adding TODOs without a tracking issue/reference. Either implement the reserved-word validation now or convert this to a linkable issue reference (e.g., TODO(#xxxx)). ```suggestion // TODO(#xxxx): Validate the name against reserved words ``` ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/ClickHouseCatalogCapability.java: ########## @@ -1,26 +1,51 @@ /* - * 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 + * 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 + * 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. + * 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.clickhouse; -import org.apache.gravitino.catalog.jdbc.JdbcCatalogCapability; +import org.apache.gravitino.connector.capability.Capability; +import org.apache.gravitino.connector.capability.CapabilityResult; + +public class ClickHouseCatalogCapability implements Capability { + /** + * Regular expression explanation: ^[\w\p{L}-$/=]{1,64}$ + * + * <p>^ - Start of the string + * + * <p>[\w\p{L}-$/=]{1,64} - Consist of 1 to 64 characters of letters (both cases), digits, + * underscores, any kind of letter from any language, hyphens, dollar signs, slashes or equal + * signs + * + * <p>\w - matches [a-zA-Z0-9_] + * + * <p>\p{L} - matches any kind of letter from any language + * + * <p>$ - End of the string + */ + public static final String CLICKHOUSE_NAME_PATTERN = "^[\\w\\p{L}-$/=]{1,64}$"; Review Comment: The character class in CLICKHOUSE_NAME_PATTERN contains an unescaped '-' ("[\\w\\p{L}-$/=]"). In Java regex this can be parsed as a character range and can throw PatternSyntaxException (or match an unintended range). Escape the hyphen ("\\-") or move it to the start/end of the character class. ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/ClickHouseCatalogCapability.java: ########## @@ -1,26 +1,51 @@ /* - * 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 + * 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 + * 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. + * 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.clickhouse; -import org.apache.gravitino.catalog.jdbc.JdbcCatalogCapability; +import org.apache.gravitino.connector.capability.Capability; +import org.apache.gravitino.connector.capability.CapabilityResult; + +public class ClickHouseCatalogCapability implements Capability { + /** + * Regular expression explanation: ^[\w\p{L}-$/=]{1,64}$ + * + * <p>^ - Start of the string + * + * <p>[\w\p{L}-$/=]{1,64} - Consist of 1 to 64 characters of letters (both cases), digits, + * underscores, any kind of letter from any language, hyphens, dollar signs, slashes or equal + * signs + * + * <p>\w - matches [a-zA-Z0-9_] + * + * <p>\p{L} - matches any kind of letter from any language + * + * <p>$ - End of the string + */ + public static final String CLICKHOUSE_NAME_PATTERN = "^[\\w\\p{L}-$/=]{1,64}$"; -public class ClickHouseCatalogCapability extends JdbcCatalogCapability { - // No additional capabilities for ClickHouse at this time + @Override + public CapabilityResult specificationOnName(Scope scope, String name) { + // TODO: Validate the name against reserved words + if (!name.matches(CLICKHOUSE_NAME_PATTERN)) { + return CapabilityResult.unsupported( Review Comment: This class introduces new name-validation behavior; add unit tests for specificationOnName covering representative valid/invalid names to prevent regex regressions (special characters + 64-char limit). ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseDatabaseOperations.java: ########## @@ -18,19 +18,104 @@ */ package org.apache.gravitino.catalog.clickhouse.operations; -import com.google.common.collect.Sets; +import static org.apache.gravitino.catalog.clickhouse.ClickHouseConfig.DEFAULT_CK_ON_CLUSTER; + +import com.google.common.collect.ImmutableSet; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Set; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.StringIdentifier; +import org.apache.gravitino.catalog.clickhouse.ClickHouseConfig; +import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter; import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations; public class ClickHouseDatabaseOperations extends JdbcDatabaseOperations { + private boolean onCluster = false; + private String clusterName = null; + + @Override + public void initialize( + DataSource dataSource, JdbcExceptionConverter exceptionMapper, Map<String, String> conf) { + super.initialize(dataSource, exceptionMapper, conf); + + final String cn = conf.get(ClickHouseConfig.CK_CLUSTER_NAME.getKey()); + if (StringUtils.isNotBlank(cn)) { + clusterName = cn; + } + + final String oc = + conf.getOrDefault( + ClickHouseConfig.CK_ON_CLUSTER.getKey(), String.valueOf(DEFAULT_CK_ON_CLUSTER)); + onCluster = Boolean.parseBoolean(oc); + + if (onCluster && StringUtils.isBlank(clusterName)) { + throw new IllegalArgumentException( + "ClickHouse 'ON CLUSTER' is enabled, but cluster name is not provided."); + } + } + @Override protected boolean supportSchemaComment() { - return false; + return true; } @Override protected Set<String> createSysDatabaseNameSet() { - return Sets.newHashSet(); + return ImmutableSet.of("information_schema", "INFORMATION_SCHEMA", "default", "system"); + } Review Comment: JdbcDatabaseOperations#createSysDatabaseNameSet is documented to return system database names in lowercase (catalogs/catalog-jdbc-common/.../JdbcDatabaseOperations.java:229-234). This set includes an uppercase entry ("INFORMATION_SCHEMA"), which will never match because isSystemDatabase() lowercases before lookup. Keep entries lowercase-only. ########## catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseDatabaseOperations.java: ########## @@ -18,19 +18,104 @@ */ package org.apache.gravitino.catalog.clickhouse.operations; -import com.google.common.collect.Sets; +import static org.apache.gravitino.catalog.clickhouse.ClickHouseConfig.DEFAULT_CK_ON_CLUSTER; + +import com.google.common.collect.ImmutableSet; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Set; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.StringIdentifier; +import org.apache.gravitino.catalog.clickhouse.ClickHouseConfig; +import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter; import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations; public class ClickHouseDatabaseOperations extends JdbcDatabaseOperations { + private boolean onCluster = false; + private String clusterName = null; + + @Override + public void initialize( + DataSource dataSource, JdbcExceptionConverter exceptionMapper, Map<String, String> conf) { + super.initialize(dataSource, exceptionMapper, conf); + + final String cn = conf.get(ClickHouseConfig.CK_CLUSTER_NAME.getKey()); + if (StringUtils.isNotBlank(cn)) { + clusterName = cn; + } + + final String oc = + conf.getOrDefault( + ClickHouseConfig.CK_ON_CLUSTER.getKey(), String.valueOf(DEFAULT_CK_ON_CLUSTER)); + onCluster = Boolean.parseBoolean(oc); + + if (onCluster && StringUtils.isBlank(clusterName)) { + throw new IllegalArgumentException( + "ClickHouse 'ON CLUSTER' is enabled, but cluster name is not provided."); + } + } + @Override protected boolean supportSchemaComment() { - return false; + return true; } @Override protected Set<String> createSysDatabaseNameSet() { - return Sets.newHashSet(); + return ImmutableSet.of("information_schema", "INFORMATION_SCHEMA", "default", "system"); + } + + @Override + public List<String> listDatabases() { + List<String> databaseNames = new ArrayList<>(); + try (final Connection connection = getConnection()) { + // It is possible that other catalogs have been deleted, + // causing the following statement to error, + // so here we manually set a system catalog + connection.setCatalog(createSysDatabaseNameSet().iterator().next()); + try (Statement statement = connection.createStatement(); Review Comment: connection.setCatalog(createSysDatabaseNameSet().iterator().next()) relies on Set iteration order, which is not guaranteed. This can select different catalogs across JVMs and can fail if the chosen catalog isn’t present. Prefer a deterministic, known-to-exist catalog (e.g., "system" or "default") rather than iterator().next(). -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
