[ISSUE #478] Support ClickHouse source and sink - #496
Conversation
|
@odbozhou hi, please take a look! :) |
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
This PR modifies 13 file(s) with 1266 lines of diff. Changes look reasonable.
Automated review by github-manager-bot
| @@ -0,0 +1,51 @@ | |||
| ##### ClickHouseSourceConnector fully-qualified name | |||
There was a problem hiding this comment.
Large diff (1266 lines). Consider breaking into smaller, focused PRs for easier review.
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
PR received and logged for review. This PR requires detailed code review by a maintainer.
Diff size: 1266 lines
Author: joeCarf (CONTRIBUTOR)
Automated review by RockteMQ-AI
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
Review of PR #496: [ISSUE #478] Support ClickHouse source and sink
Findings: 15 issue(s) identified (4 critical).
CLA: unknown
Please address the inline comments above.
Automated review by github-manager-bot
Additional notes (not anchored to a changed line)
- [CRITICAL]
connectors/rocketmq-connect-clickhouse:1— No tests are present anywhere in this PR. There are no unit or integration tests for the source task (including offset tracking), sink task, helper client retry logic, or config loading. This is a mandatory gap for a data integration connector where correctness of offset tracking and error handling is critical. (line outside diff)
| } | ||
| }); | ||
| ps.executeUpdate(); | ||
|
|
There was a problem hiding this comment.
Infinite loop bug in insertJson: retryCount is never incremented. The loop while (retryCount < this.retry) will spin forever if insertJson returns false, causing the thread to hang indefinitely.
| } | ||
|
|
||
| private Connection getConnection(String url, Properties properties) throws SQLException { | ||
| ClickHouseDataSource dataSource = new ClickHouseDataSource(url, properties); |
There was a problem hiding this comment.
insertJson silently swallows all exceptions and returns false with no logging. The caller has no way to distinguish a transient error from a permanent schema/data error, and the root cause is completely lost.
| recordList.add(r); | ||
| } | ||
| return recordList; | ||
|
|
There was a problem hiding this comment.
System.out.println used for connection diagnostics instead of the SLF4J logger already present in this class. This will not be suppressible via logging configuration and pollutes stdout in production.
| if (config.getAccessToken() != null) { | ||
| return ClickHouseCredentials.fromAccessToken(config.getAccessToken()); | ||
| } | ||
| throw new RuntimeException("Credentials cannot be empty!"); |
There was a problem hiding this comment.
ping() creates a new ClickHouseClient on every call but only closes it on success. If ping returns false after all retries, the client opened in the last failed iteration is never closed, leaking a connection resource.
| .build(); | ||
|
|
||
| return this.server; | ||
| } |
There was a problem hiding this comment.
getCredentials throws a RuntimeException when neither username/password nor accessToken is set, but this is thrown inside the constructor during create(). The error message is not descriptive enough to guide operators. More importantly, a missing-credential config should be caught at connector validation time (in start()), not deferred to client construction.
| } | ||
|
|
||
| public void load(KeyValue props) { | ||
| properties2Object(props, this); |
There was a problem hiding this comment.
Config key lookup lowercases the setter name (e.g. setClickHouseHost -> clickhousehost) but the constants file defines CLICKHOUSE_HOST = "clickhousehost". This works, but the mapping is fragile: adding a setter whose lowercase name doesn't match the constant (e.g. setUserName -> username vs CLICKHOUSE_USERNAME = "username") will silently fail to populate the field. userName maps to 'username' which does match, but this convention is undocumented and error-prone.
|
|
||
| public class ClickHouseSinkConfig extends ClickHouseBaseConfig { | ||
| public static final Set<String> SINK_REQUEST_CONFIG = new HashSet<String>() { | ||
| { |
There was a problem hiding this comment.
SINK_REQUEST_CONFIG only requires clickhousehost and clickhouseport, omitting database, username, and password. A connector started without credentials will pass validation but fail at runtime in getCredentials(). Minimum required fields should include database, username, and password (or accesstoken).
| <groupId>io.openmessaging</groupId> | ||
| <artifactId>openmessaging-connector</artifactId> | ||
| <version>0.1.4</version> | ||
| <scope>compile</scope> |
There was a problem hiding this comment.
junit version is set to RELEASE, which is a Maven version alias that resolves to the latest available version at build time. This makes builds non-reproducible and can introduce unexpected breaking changes. Pin to a specific version (e.g. 4.13.2).
| List<KeyValue> configs = new ArrayList<>(); | ||
| for (int i = 0; i < maxTasks; i++) { | ||
| configs.add(this.keyValue); | ||
| } |
There was a problem hiding this comment.
taskConfigs adds the same shared KeyValue reference to every task slot. If tasks are run concurrently and any task mutates the KeyValue object, all tasks will be affected. Each task should receive an independent copy of the configuration.
| List<KeyValue> configs = new ArrayList<>(); | ||
| for (int i = 0; i < maxTasks; i++) { | ||
| configs.add(this.keyValue); | ||
| } |
There was a problem hiding this comment.
Same shared-reference issue as ClickHouseSinkConnector.taskConfigs: all task slots receive the same KeyValue object reference.
|
Issue Evaluation Category: This issue references #478 and proposes adding ClickHouse connectors (both source and sink). Note: This appears to be a PR submission. If you have implementation code ready, please submit it as a pull request directly. Feasibility: Adding ClickHouse connectors is a valuable enhancement for data integration scenarios. Age: This issue is from May 2023. If this feature is still desired, please confirm or submit a PR. Automated evaluation by RockteMQ-AI |
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
The connector has a critical infinite-loop bug in the sink retry path, uses incorrect OFFSET-based pagination for the source, relies on fragile private-field reflection, and ships with zero executable tests (all commented out with hardcoded credentials).
Findings
- [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/helper/ClickHouseHelperClient.java:155— Infinite loop bug.retryCountis never incremented inside thewhileloop. IfinsertJson(...)keeps returningfalse, this loops forever, blocking the sink task thread permanently. AddretryCount++after theifblock. - [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:63— OFFSET-based pagination is incorrect for a source connector.SELECT * LIMIT N OFFSET Mwill skip or duplicate rows if data is inserted or deleted between polls. It also has O(N) cost on ClickHouse for large offsets. Use a cursor-based approach (e.g.,WHERE id > lastSeenId ORDER BY id LIMIT N) for correctness and performance. - [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:101— Fragile reflection hack. Accessing the privatecolumnsfield ofClickHouseRecordviagetDeclaredField+setAccessiblewill break with any library update that renames or restructures the field. Use the public API (ClickHouseRecord.getColumns()or iterate via the record's public interface) instead. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/config/ClickHouseBaseConfig.java:131— Silently swallowingThrowablehides configuration errors. If a setter throws (e.g.,NumberFormatExceptionfrom a bad port value), the field staysnulland the connector fails later with an unrelated error. At minimum, log the exception so misconfigurations are diagnosable. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:69— Exception is swallowed inpoll(). Thecatchblock logs a message but does not include the exceptioneas a parameter, losing the stack trace. Also, returning a partialreslist after an error may cause offset gaps. Passeto the logger:log.error("...", e)and consider re-throwing or returning an empty list. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:78— SQL injection risk. Thetableparameter is interpolated directly into the SQL string viaString.format. While it comes from connector config (not user input per request), a misconfigured or malicious table name liketableName; DROP TABLE ...could be dangerous. Validate or quote the identifier. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/sink/ClickHouseSinkTask.java:55—record.getData()may not be aStruct. The unchecked cast(Struct) record.getData()will throwClassCastExceptionif the record carries a different payload type (e.g., aMapor raw bytes). Add aninstanceofcheck before casting. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/helper/ClickHouseHelperClient.java:133—System.out.printlnin production code. Use the class logger (LOGGER.info(...)) instead ofSystem.out.printlnfor the connection message ingetConnection(). - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceConnector.java:40— All tasks share the sameKeyValuereference.taskConfigsaddsthis.keyValueto the listmaxTaskstimes. If any task mutates theKeyValue, all tasks are affected. Return defensive copies or create newKeyValueinstances per task. - [INFO]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/config/ClickHouseSinkConfig.java:25— Double-brace initialization creates an anonymous inner class holding a reference to the outer class (if any). For a static field this is fine in practice, but preferCollections.unmodifiableSet(new HashSet<>(Arrays.asList(...)))orSet.of(...)for clarity and to avoid the anonymous class. - [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/test/java/org/apache/rocketmq/connect/clickhouse/sink/ClickHouseSinkTaskTest.java:18— All test logic is commented out. There are zero executable tests in both test files. The PR description claims unit tests were written, but nothing actually runs. This must be addressed before merge — at minimum, add mocked unit tests forput(),poll(), config loading, and the retry logic. - [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/test/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTaskTest.java:14— All test logic is commented out, and tests contain hardcoded credentials (120.48.26.195, password123456). Even though commented, these should be removed entirely. Write proper unit tests using mocks instead of integration tests against a real server. - [INFO]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/config/ClickHouseConstants.java:39— Constant naming violates Java conventions.timeoutSecondsDefaultandretryCountDefaultshould beTIMEOUT_SECONDS_DEFAULTandRETRY_COUNT_DEFAULT(UPPER_SNAKE_CASE forstatic finalfields). - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/helper/ClickHouseHelperClient.java:90—query()loads all records into memory. For large result sets, collecting allClickHouseRecords into anArrayListbefore returning can cause OOM. Consider streaming or chunked processing, especially since the source task already pages with LIMIT. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:88— New Schema built on every record.clickHouseRecord2ConnectRecordcreates a newSchemaand field list for every single row. For tables with stable schemas, build the schema once (e.g., on first poll orstart()) and reuse it. This is a significant per-record allocation on the hot path.
Automated review by github-manager-bot
| return true; | ||
| } | ||
|
|
||
| public void insertJson(String jsonString, String table) { |
There was a problem hiding this comment.
Infinite loop bug. retryCount is never incremented inside the while loop. If insertJson(...) keeps returning false, this loops forever, blocking the sink task thread permanently. Add retryCount++ after the if block.
| String sql = buildSql(config.getTable(), ClickHouseConstants.MAX_NUMBER_SEND_CONNECT_RECORD_EACH_TIME, offset); | ||
|
|
||
| try { | ||
| List<ClickHouseRecord> recordList = helperClient.query(sql); |
There was a problem hiding this comment.
OFFSET-based pagination is incorrect for a source connector. SELECT * LIMIT N OFFSET M will skip or duplicate rows if data is inserted or deleted between polls. It also has O(N) cost on ClickHouse for large offsets. Use a cursor-based approach (e.g., WHERE id > lastSeenId ORDER BY id LIMIT N) for correctness and performance.
| } | ||
|
|
||
| private List<Field> buildFields( | ||
| ClickHouseRecord clickHouseRecord) throws NoSuchFieldException, IllegalAccessException { |
There was a problem hiding this comment.
Fragile reflection hack. Accessing the private columns field of ClickHouseRecord via getDeclaredField + setAccessible will break with any library update that renames or restructures the field. Use the public API (ClickHouseRecord.getColumns() or iterate via the record's public interface) instead.
| continue; | ||
| } | ||
| method.invoke(object, arg); | ||
| } |
There was a problem hiding this comment.
Silently swallowing Throwable hides configuration errors. If a setter throws (e.g., NumberFormatException from a bad port value), the field stays null and the connector fails later with an unrelated error. At minimum, log the exception so misconfigurations are diagnosable.
| } | ||
| } catch (Exception e) { | ||
| log.error(String.format("Fail to poll data from clickhouse! Table=%s offset=%d", config.getTable(), offset)); | ||
| } |
There was a problem hiding this comment.
Exception is swallowed in poll(). The catch block logs a message but does not include the exception e as a parameter, losing the stack trace. Also, returning a partial res list after an error may cause offset gaps. Pass e to the logger: log.error("...", e) and consider re-throwing or returning an empty list.
| import org.apache.rocketmq.connect.clickhouse.config.ClickHouseConstants; | ||
|
|
||
|
|
||
| class ClickHouseSinkTaskTest { |
There was a problem hiding this comment.
All test logic is commented out. There are zero executable tests in both test files. The PR description claims unit tests were written, but nothing actually runs. This must be addressed before merge — at minimum, add mocked unit tests for put(), poll(), config loading, and the retry logic.
|
|
||
| public class ClickHouseSourceTaskTest { | ||
|
|
||
| // private static final String host = "120.48.26.195"; |
There was a problem hiding this comment.
All test logic is commented out, and tests contain hardcoded credentials (120.48.26.195, password 123456). Even though commented, these should be removed entirely. Write proper unit tests using mocks instead of integration tests against a real server.
|
|
||
| public static final String CLICKHOUSE_OFFSET = "OFFSET"; | ||
|
|
||
| public static final String CLICKHOUSE_PARTITION = "CLICKHOUSE_PARTITION"; |
There was a problem hiding this comment.
Constant naming violates Java conventions. timeoutSecondsDefault and retryCountDefault should be TIMEOUT_SECONDS_DEFAULT and RETRY_COUNT_DEFAULT (UPPER_SNAKE_CASE for static final fields).
| retryCount++; | ||
| LOGGER.warn(String.format("Ping retry %d out of %d", retryCount, retry)); | ||
| } | ||
| LOGGER.error("unable to ping to clickhouse server. "); |
There was a problem hiding this comment.
query() loads all records into memory. For large result sets, collecting all ClickHouseRecords into an ArrayList before returning can cause OOM. Consider streaming or chunked processing, especially since the source task already pages with LIMIT.
|
|
||
| private ConnectRecord clickHouseRecord2ConnectRecord(ClickHouseRecord clickHouseRecord, | ||
| long offset) throws NoSuchFieldException, IllegalAccessException { | ||
| Schema schema = SchemaBuilder.struct().name(config.getTable()).build(); |
There was a problem hiding this comment.
New Schema built on every record. clickHouseRecord2ConnectRecord creates a new Schema and field list for every single row. For tables with stable schemas, build the schema once (e.g., on first poll or start()) and reuse it. This is a significant per-record allocation on the hot path.
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
21 finding(s) to address.
Findings
- [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/helper/ClickHouseHelperClient.java:138— Infinite retry loop:retryCountis never incremented inside thewhileloop ininsertJson(String, String). IfinsertJson(the private overload) keeps returningfalse, this loop will spin forever, blocking the sink task thread indefinitely. AddretryCount++inside the loop. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/helper/ClickHouseHelperClient.java:122— UsingSystem.out.printlnfor logging connection info ingetConnection. Use the SLF4J logger instead to respect log-level configuration and avoid stdout pollution in production. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/helper/ClickHouseHelperClient.java:126—insertJson(String, String, String, String)swallows all exceptions silently and returnsfalse. The caller (the publicinsertJson) then either retries infinitely (due to the missing increment bug) or logs only a generic error. The root-cause exception should be logged with the stack trace so failures can be diagnosed. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/helper/ClickHouseHelperClient.java:112—properties2ObjectinClickHouseBaseConfigcatchesThrowableand silently ignores it (catch (Throwable ignored)). This masks configuration errors — e.g., a malformed port number will be silently skipped, leaving the field null and causing a confusing NullPointerException later. At minimum, log a warning. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/config/ClickHouseBaseConfig.java:100— The reflection-basedproperties2Objectderives config keys by stripping thesetprefix and lowercasing. This meanssetClickHouseHostmaps to keyclickhousehost,setClickHousePortmaps toclickhouseport, etc. While this matches the current constants, it is fragile — any rename of a setter silently changes the expected config key. A dedicated config-key-to-setter mapping or explicit annotations would be safer. - [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:60— Offset semantics are wrong: the source usesLIMIT N OFFSET offsetwhich treats the offset as a row-number skip. This is not safe for tables with concurrent inserts/deletes — rows can be skipped or duplicated between polls. A robust approach would use an auto-incrementing primary key or a timestamp watermark (e.g.,WHERE id > offset ORDER BY id LIMIT N). The current design only works correctly for static, append-only tables. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:67— The exception inpoll()is caught and logged but the error message itself does not include the exception (e) as a parameter. The stack trace is lost, making it very hard to diagnose query failures (e.g., SQL syntax errors, permission issues). Addeas a second argument tolog.error. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:95—buildFieldsuses reflection to access the privatecolumnsfield onClickHouseRecord(getDeclaredField("columns")). This is an internal implementation detail of the ClickHouse client library and can break with any library upgrade. Consider using the public API (e.g.,clickHouseRecord.getColumns()or iterating via the public interface) if available. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:63— Thepoll()method unconditionally queriesSELECT * FROM table LIMIT 2000 OFFSET N. For large tables, OFFSET-based pagination becomes increasingly slow as the offset grows (ClickHouse must scan and discard all skipped rows). This is a significant performance concern for long-running connectors. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTask.java:60— No sleep/backoff betweenpoll()calls when data is exhausted or on error. The framework may callpoll()in a tight loop, hammering the ClickHouse server with repeated identical queries. Most source connectors add a configurable poll interval or a small sleep when no new data is found. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceConnector.java:41—taskConfigs(maxTasks)returns the sameKeyValuereference for every task. If the framework or tasks mutate the config object, all tasks would see the same mutation. Consider returning defensive copies (e.g., a newDefaultKeyValuepopulated from the original) per task. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/sink/ClickHouseSinkConnector.java:41— Same issue as the source connector:taskConfigsshares the sameKeyValuereference across all tasks. Return per-task copies to prevent cross-task interference. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/sink/ClickHouseSinkTask.java:47—put()usesrecord.getSchema().getName()as the target table name. This means the producer must set the schema name to the exact ClickHouse table name. There is no fallback or configuration for a default target table, making the sink connector's behavior opaque if the schema name is null or does not match a ClickHouse table. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/sink/ClickHouseSinkTask.java:51— The sink castsrecord.getData()toStructwithout any type check. If the record data is aMap, a primitive, or null, this will throw aClassCastExceptionwith no meaningful error message. Add a null/type guard. - [WARNING]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/config/ClickHouseBaseConfig.java:36— Thedatabasefield has no default value and is not in the required-config sets (REQUEST_CONFIG/SINK_REQUEST_CONFIG). If omitted,ClickHouseNode.builder().database(null)and the JDBC URL will contain a null database, causing connection failures. Either make it required or provide a sensible default (e.g.,"default"). - [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/test/java/org/apache/rocketmq/connect/clickhouse/sink/ClickHouseSinkTaskTest.java:17— The entire test class is commented out — there are zero actual unit tests. The PR checklist claims >80% test coverage, but this connector has no executable tests at all. Both test files also hardcode an IP address (120.48.26.195) and credentials, which would be a security concern even if uncommented. Proper mocked unit tests are needed. - [CRITICAL]
connectors/rocketmq-connect-clickhouse/src/test/java/org/apache/rocketmq/connect/clickhouse/source/ClickHouseSourceTaskTest.java:13— Same as the sink test: the entire test is commented out, providing zero test coverage. Additionally, it importssleepfromThreadand useswhile(true)— this is a manual integration script, not a unit test. - [WARNING]
connectors/rocketmq-connect-clickhouse/pom.xml:176— JUnit dependency uses<version>RELEASE</version>, which resolves to the latest release at build time. This makes builds non-reproducible and can introduce unexpected breaking changes. Pin to a specific version (e.g.,4.13.2). - [INFO]
connectors/rocketmq-connect-clickhouse/pom.xml:168—fastjson1.2.83 is used for JSON serialization. This library has a long history of deserialization vulnerabilities. Consider using a more widely audited alternative (e.g., Jackson) which is also more commonly used in the RocketMQ ecosystem. - [INFO]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/helper/ClickHouseHelperClient.java:83— Theping()method creates a newClickHouseClientfor each call. Sincepingis called during task startup, this is not a hot path, but the client should ideally be reused or at least the pattern should be consistent with thequery()method which correctly uses try-with-resources. - [INFO]
connectors/rocketmq-connect-clickhouse/src/main/java/org/apache/rocketmq/connect/clickhouse/config/ClickHouseConstants.java:42— Naming convention inconsistency:timeoutSecondsDefaultandretryCountDefaultuse camelCase while all other constants use UPPER_SNAKE_CASE. AlsoMAX_NUMBER_SEND_CONNECT_RECORD_EACH_TIMEuses a different naming pattern. Standardize to UPPER_SNAKE_CASE for static finals.
Automated review by github-manager-bot
| } | ||
|
|
||
| private boolean insertJson(String jsonString, String table, String sql, String url) { | ||
|
|
There was a problem hiding this comment.
Infinite retry loop: retryCount is never incremented inside the while loop in insertJson(String, String). If insertJson (the private overload) keeps returning false, this loop will spin forever, blocking the sink task thread indefinitely. Add retryCount++ inside the loop.
| } catch (Exception e) { | ||
| retryCount++; | ||
| LOGGER.warn(String.format("Query retry %d out of %d", retryCount, retry), e); | ||
| ce = e; |
There was a problem hiding this comment.
Using System.out.println for logging connection info in getConnection. Use the SLF4J logger instead to respect log-level configuration and avoid stdout pollution in production.
| } | ||
| } | ||
| throw new RuntimeException(ce); | ||
|
|
There was a problem hiding this comment.
insertJson(String, String, String, String) swallows all exceptions silently and returns false. The caller (the public insertJson) then either retries infinitely (due to the missing increment bug) or logs only a generic error. The root-cause exception should be logged with the stack trace so failures can be diagnosed.
| .format(clickHouseFormat) | ||
| .query(query) | ||
| .execute().get()) { | ||
|
|
There was a problem hiding this comment.
properties2Object in ClickHouseBaseConfig catches Throwable and silently ignores it (catch (Throwable ignored)). This masks configuration errors — e.g., a malformed port number will be silently skipped, leaving the field null and causing a confusing NullPointerException later. At minimum, log a warning.
| } | ||
|
|
||
| private void properties2Object(final KeyValue p, final Object object) { | ||
|
|
There was a problem hiding this comment.
The reflection-based properties2Object derives config keys by stripping the set prefix and lowercasing. This means setClickHouseHost maps to key clickhousehost, setClickHousePort maps to clickhouseport, etc. While this matches the current constants, it is fragile — any rename of a setter silently changes the expected config key. A dedicated config-key-to-setter mapping or explicit annotations would be safer.
| import static java.lang.Thread.sleep; | ||
|
|
||
| public class ClickHouseSourceTaskTest { | ||
|
|
There was a problem hiding this comment.
Same as the sink test: the entire test is commented out, providing zero test coverage. Additionally, it imports sleep from Thread and uses while(true) — this is a manual integration script, not a unit test.
| <dependency> | ||
| <groupId>org.lz4</groupId> | ||
| <artifactId>lz4-java</artifactId> | ||
| <version>1.8.0</version> |
There was a problem hiding this comment.
JUnit dependency uses <version>RELEASE</version>, which resolves to the latest release at build time. This makes builds non-reproducible and can introduce unexpected breaking changes. Pin to a specific version (e.g., 4.13.2).
| <dependency> | ||
| <groupId>com.clickhouse</groupId> | ||
| <artifactId>clickhouse-jdbc</artifactId> | ||
| <version>0.4.5</version> |
There was a problem hiding this comment.
fastjson 1.2.83 is used for JSON serialization. This library has a long history of deserialization vulnerabilities. Consider using a more widely audited alternative (e.g., Jackson) which is also more commonly used in the RocketMQ ecosystem.
| int retryCount = 0; | ||
|
|
||
| while (retryCount < retry) { | ||
| if (clientPing.ping(server, timeout)) { |
There was a problem hiding this comment.
The ping() method creates a new ClickHouseClient for each call. Since ping is called during task startup, this is not a hot path, but the client should ideally be reused or at least the pattern should be consistent with the query() method which correctly uses try-with-resources.
| public static final String CLICKHOUSE_PARTITION = "CLICKHOUSE_PARTITION"; | ||
|
|
||
| public static final Integer timeoutSecondsDefault = 30; | ||
|
|
There was a problem hiding this comment.
Naming convention inconsistency: timeoutSecondsDefault and retryCountDefault use camelCase while all other constants use UPPER_SNAKE_CASE. Also MAX_NUMBER_SEND_CONNECT_RECORD_EACH_TIME uses a different naming pattern. Standardize to UPPER_SNAKE_CASE for static finals.
What is the purpose of the change
support ClickHouse connectors (both source and sink)
issue link: #478
Brief changelog
XX
Verifying this change
XXXX
Follow this checklist to help us incorporate your contribution quickly and easily. Notice,
it would be helpful if you could finish the following 5 checklist(the last one is not necessary)before request the community to review your PR.[ISSUE #123] Fix UnknownException when host config not exist. Each commit in the pull request should have a meaningful subject line and body.mvn -B clean apache-rat:check findbugs:findbugs checkstyle:checkstyleto make sure basic checks pass. Runmvn clean install -DskipITsto make sure unit-test pass. Runmvn clean test-compile failsafe:integration-testto make sure integration-test pass.