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]

Reply via email to