Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
74 changes: 74 additions & 0 deletions .github/workflows/zh-locale-compile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# This workflow verifies that the project compiles with the Chinese (zh) locale.
#
# IoTDB uses compile-time i18n (build-helper-maven-plugin): each participating module has
# twin EN/ZH Messages classes under src/main/i18n/{en,zh}/, swapped via the i18n.locale
# property. The default CI builds compile the EN locale only. This job activates the
# `with-zh-locale` profile (i18n.locale=zh) so the ZH source roots are compiled across
# the whole reactor, catching:
# - constants present in EN but missing in ZH (key-parity regressions),
# - constants present in ZH but not referenced / removed from EN,
# - format-specifier or type errors in the ZH Message classes,
# - source files that reference a constant only defined under the EN root.

name: ZH-Locale-Compile

on:
push:
branches:
- master
- "rel/*"
- "rc/*"
paths-ignore:
- "docs/**"
- "site/**"
- "iotdb-client/client-cpp/**"
- ".github/workflows/client-cpp-package.yml"
- ".github/scripts/package-client-cpp-*.sh"
- ".github/workflows/multi-language-client.yml"
pull_request:
branches:
- master
- "rel/*"
- "rc/*"
- "force_ci/**"
paths-ignore:
- "docs/**"
- "site/**"
- "iotdb-client/client-cpp/**"
- ".github/workflows/client-cpp-package.yml"
- ".github/scripts/package-client-cpp-*.sh"
- ".github/workflows/multi-language-client.yml"
# allow manually run the action:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
MAVEN_OPTS: -Xms2g -Xmx4g -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3
MAVEN_ARGS: --batch-mode --no-transfer-progress
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}

jobs:
zh-locale-compile:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: corretto
java-version: 17
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cache Maven packages
uses: actions/cache@v5
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2-
- name: Compile with ZH locale
shell: bash
run: mvn clean test-compile -P with-zh-locale -DskipTests
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,21 @@ Generated source directories that need to be on the source path:

- **Missing Thrift compiler**: The local machine may not have the `thrift` binary installed. Running `mvn clean package -pl <module> -am -DskipTests` will fail at the `iotdb-thrift` module. **Workaround**: To verify your changes compile, use `mvn compile -pl <module>` (without `-am` or `clean`) to leverage existing target caches.
- **Pre-existing compilation errors in unrelated modules**: The datanode module may have pre-existing compile errors in other subsystems (e.g., pipe, copyto) that cause `mvn clean test -pl iotdb-core/datanode -Dtest=XxxTest` to fail during compilation. **Workaround**: First run `mvn compile -pl iotdb-core/datanode` to confirm your changed files compile successfully. If the errors are in files you did not modify, they are pre-existing and do not affect your changes.
- **Single-module / no-`clean` compiles hide cross-module and i18n errors**: `mvn compile -pl <module>` (the thrift workaround above) builds only that module against the **installed** jars in `~/.m2` and the existing `target/` classes; an incremental `mvn test-compile` without `clean` reuses stale `target/*.class`. If you add/rename a `*Messages` constant (or any cross-module API) in module B but `~/.m2`/`target/` still holds B's old artifact, module A referencing the new symbol compiles fine against the stale class — the real `cannot find symbol` only surfaces in CI or a clean build. **Workaround**: for i18n / cross-module edits, verify with a full-reactor compile (no `-pl`, no `clean`): `mvn test-compile -DskipTests` and `mvn test-compile -DskipTests -P with-zh-locale`. Capture the maven exit code directly (`mvn ... ; echo $?`), not via `mvn ... && echo OK` — `set -e` does NOT fail on a command inside a `&&` list, so a `&&` chain can print a false success.

### i18n (Chinese Messages)

The project uses compile-time i18n via the `build-helper-maven-plugin`. The property `i18n.locale` (default: `en`) controls which source directory is added: `src/main/i18n/${i18n.locale}`. Activating `-P with-zh-locale` sets `i18n.locale=zh`, swapping English message constant classes for Chinese ones. Each module that participates has both `src/main/i18n/en/` and `src/main/i18n/zh/` directories containing Java classes with identical structure but different string literals.

**Rule — never inline a user-facing English string literal.** Every string a user, operator, or log reader will see MUST be a `public static final String` constant in the module's `*Messages` class (e.g. `DataNodeQueryMessages`), referenced as `MessagesClass.CONSTANT` — never a `"raw literal"`. This applies to: `LOGGER/log.info|warn|error|debug|trace(...)`, `throw new …Exception(…)`, `super(…)` in exception constructors, `resp.setMessage(…)`, the message arg of `requireNonNull/checkArgument/checkState/checkNotNull`, and the template of `String.format(…)`. A raw literal is invisible to the zh build and renders English under `-P with-zh-locale` — that is the bug this rule prevents. (`toString()` debug output is exempt.) Reviewers and AI agents must reject/fix any new code that introduces such a literal.

**Naming a new constant** (deterministic, matches existing files): `<PREFIX>_<NORMALIZED>_<HASH>` where `PREFIX` is `LOG_` for `LOGGER`/log calls, `MESSAGE_` for `setMessage(...)`, and `EXCEPTION_` for `throw new …(…)`, `super(…)`, and the message arg of `requireNonNull/checkArgument/checkState/checkNotNull` (the three prefixes the codebase already uses — ~`LOG_`/`MESSAGE_`/`EXCEPTION_`); `NORMALIZED` is the English value uppercased with non-alphanumeric runs collapsed to `_` and `%s`/`%d`/`{}` replaced by `ARG`; `HASH` is the first 8 hex chars of `md5(englishValue)`, uppercased — compute with `python3 -c "import hashlib;print(hashlib.md5(b'the english').hexdigest()[:8].upper())"`. Add the constant to **both** the `en` and `zh` files under the **same name** (zh gets a natural Chinese translation; preserve every `%s`/`%d`/`{}` exactly; keep config keys, SQL keywords, class/method names, and product terms like TsFile/DataRegion/Consensus/ConfigNode in English inside the Chinese string). Reuse an existing same-named constant instead of duplicating. **Pure-punctuation fragments** (`. `, `, `, `: `, `.`, etc.) are **exempt** — inline them, do not create a constant (punctuation does not localize, and a hashed `. ` constant hurts readability). A single message that is built from several pieces should be ONE constant holding the full template, not several fragments concatenated at the call site.

**Verify before commit**:
- `mvn spotless:apply -pl <module>` (the PostToolUse formatter does not add the i18n import; spotless does).
- Compile **both** locales: `mvn test-compile -DskipTests` AND `mvn test-compile -P with-zh-locale -DskipTests` (the `.github/workflows/zh-locale-compile.yml` CI enforces the zh build).
- The `en` and `zh` files must stay in key parity (same constant names) with identical format-specifier counts; a raw-literal scan of main java must return nothing.

### Code Style

- **Always run `mvn spotless:apply` after editing Java files**: Spotless runs `spotless:check` automatically during the `compile` phase. Format violations cause an immediate BUILD FAILURE. Make it a habit to run `mvn spotless:apply -pl <module>` right after editing, not at the end. For files under `integration-test/`, add `-P with-integration-tests`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,25 @@ public final class MqttMessages {
public static final String FIND_MQTT_PLUGIN =
"PayloadFormatManager(), find MQTT Payload Plugin {}.";
public static final String MQTT_PLUGIN_JAR_URLS = "MQTT Plugin jarURLs: {}";
public static final String UNKNOWN_PAYLOAD_FORMAT_NAMED = "Unknown payload format named: ";

// --- JSONPayloadFormatter ---
public static final String PAYLOAD_INVALID = "payload is invalidate";

private MqttMessages() {}
// ---------------------------------------------------------------------------
// Additional auto-collected messages
// ---------------------------------------------------------------------------
public static final String LOG_LINE_PATTERN_PARSING_FAILS_FAILED_LINE_MESSAGE_ARG_EXCEPTION_6EFB0EE2 = "The line pattern parsing fails, and the failed line message is {} ,exception is";
public static final String LOG_CONNECTION_REFUSED_CLIENT_ID_MISSING_EMPTY_VALID_CLIENT_ID_REQUIRED_A566DC15 =
"Connection refused: client_id is missing or empty. A valid client_id is required to establish"
+ " a connection.";
public static final String LOG_RECEIVE_PUBLISH_MESSAGE_CLIENTID_ARG_USERNAME_ARG_QOS_ARG_TOPIC_7E60C3A6 = "Receive publish message. clientId: {}, username: {}, qos: {}, topic: {}, payload: {}";
public static final String LOG_MQTT_JSON_INSERT_ERROR_CODE_ARG_MESSAGE_ARG_B1A78FBD = "mqtt json insert error, code={}, message={}";
public static final String LOG_MEET_ERROR_INSERTING_DATABASE_ARG_TABLE_ARG_TAGS_ARG_ATTRIBUTES_173457D5 =
"meet error when inserting database {}, table {}, tags {}, attributes {}, fields {}, at time"
+ " {}, because ";
public static final String LOG_MEET_ERROR_INSERTING_DEVICE_ARG_MEASUREMENTS_ARG_AT_TIME_ARG_680D67D2 = "meet error when inserting device {}, measurements {}, at time {}, because ";
public static final String LOG_START_MQTT_SERVICE_SUCCESSFULLY_LISTENING_IP_ARG_PORT_ARG_47CE46D5 = "Start MQTT service successfully, listening on ip {} port {}";

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,21 @@ public final class MqttMessages {
public static final String FIND_MQTT_PLUGIN =
"PayloadFormatManager(),找到 MQTT Payload 插件 {}。";
public static final String MQTT_PLUGIN_JAR_URLS = "MQTT 插件 jarURLs:{}";
public static final String UNKNOWN_PAYLOAD_FORMAT_NAMED = "未知 payload 格式名称:";

// --- JSONPayloadFormatter ---
public static final String PAYLOAD_INVALID = "payload 无效";

private MqttMessages() {}
// ---------------------------------------------------------------------------
// Additional auto-collected messages
// ---------------------------------------------------------------------------
public static final String LOG_LINE_PATTERN_PARSING_FAILS_FAILED_LINE_MESSAGE_ARG_EXCEPTION_6EFB0EE2 = "行模式解析失败,失败的行消息为 {},异常为";
public static final String LOG_CONNECTION_REFUSED_CLIENT_ID_MISSING_EMPTY_VALID_CLIENT_ID_REQUIRED_A566DC15 = "连接被拒绝:client_id 缺失或为空。建立连接需要有效的 client_id。";
public static final String LOG_RECEIVE_PUBLISH_MESSAGE_CLIENTID_ARG_USERNAME_ARG_QOS_ARG_TOPIC_7E60C3A6 = "收到 publish 消息。clientId: {}, 用户名: {}, qos: {}, 主题: {}, payload: {}";
public static final String LOG_MQTT_JSON_INSERT_ERROR_CODE_ARG_MESSAGE_ARG_B1A78FBD = "MQTT JSON 插入错误,code={},消息={}";
public static final String LOG_MEET_ERROR_INSERTING_DATABASE_ARG_TABLE_ARG_TAGS_ARG_ATTRIBUTES_173457D5 = "插入数据库 {}、表 {}、标签 {}、属性 {}、字段 {}、时间 {} 时遇到错误,原因:";
public static final String LOG_MEET_ERROR_INSERTING_DEVICE_ARG_MEASUREMENTS_ARG_AT_TIME_ARG_680D67D2 = "插入设备 {}、测点 {}、时间 {} 时遇到错误,原因:";
public static final String LOG_START_MQTT_SERVICE_SUCCESSFULLY_LISTENING_IP_ARG_PORT_ARG_47CE46D5 = "MQTT 服务启动成功,监听 IP {},端口 {}";

}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public List<Message> format(String topic, ByteBuf payload) {
messages.add(message);
} catch (Exception e) {
log.warn(
"The line pattern parsing fails, and the failed line message is {} ,exception is",
MqttMessages.LOG_LINE_PATTERN_PARSING_FAILS_FAILED_LINE_MESSAGE_ARG_EXCEPTION_6EFB0EE2,
line,
e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ public String getID() {
public void onConnect(InterceptConnectMessage msg) {
if (msg.getClientID() == null || msg.getClientID().trim().isEmpty()) {
LOG.error(
"Connection refused: client_id is missing or empty. A valid client_id is required to establish a connection.");
MqttMessages
.LOG_CONNECTION_REFUSED_CLIENT_ID_MISSING_EMPTY_VALID_CLIENT_ID_REQUIRED_A566DC15);
}
if (!clientIdToSessionMap.containsKey(msg.getClientID())) {
MqttClientSession session = new MqttClientSession(msg.getClientID());
Expand Down Expand Up @@ -136,7 +137,8 @@ public void onPublish(InterceptPublishMessage msg) {
String username = msg.getUsername();
MqttQoS qos = msg.getQos();
LOG.debug(
"Receive publish message. clientId: {}, username: {}, qos: {}, topic: {}, payload: {}",
MqttMessages
.LOG_RECEIVE_PUBLISH_MESSAGE_CLIENTID_ARG_USERNAME_ARG_QOS_ARG_TOPIC_7E60C3A6,
clientId,
username,
qos,
Expand Down Expand Up @@ -197,13 +199,13 @@ private void insertTable(TableMessage message, MqttClientSession session) {
if (tsStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()
&& tsStatus.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) {
LOG.warn(
"mqtt json insert error, code={}, message={}",
MqttMessages.LOG_MQTT_JSON_INSERT_ERROR_CODE_ARG_MESSAGE_ARG_B1A78FBD,
tsStatus.getCode(),
tsStatus.getMessage());
}
} catch (Exception e) {
LOG.warn(
"meet error when inserting database {}, table {}, tags {}, attributes {}, fields {}, at time {}, because ",
MqttMessages.LOG_MEET_ERROR_INSERTING_DATABASE_ARG_TABLE_ARG_TAGS_ARG_ATTRIBUTES_173457D5,
message.getDatabase(),
message.getTable(),
message.getTagKeys(),
Expand Down Expand Up @@ -316,14 +318,14 @@ private void insertTree(TreeMessage message, MqttClientSession session) {
if (tsStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()
&& tsStatus.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) {
LOG.warn(
"mqtt json insert error, code={}, message={}",
MqttMessages.LOG_MQTT_JSON_INSERT_ERROR_CODE_ARG_MESSAGE_ARG_B1A78FBD,
tsStatus.getCode(),
tsStatus.getMessage());
}
}
} catch (Exception e) {
LOG.warn(
"meet error when inserting device {}, measurements {}, at time {}, because ",
MqttMessages.LOG_MEET_ERROR_INSERTING_DEVICE_ARG_MEASUREMENTS_ARG_AT_TIME_ARG_680D67D2,
message.getDevice(),
message.getMeasurements(),
message.getTimestamp(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public void startup() {
}

LOG.info(
"Start MQTT service successfully, listening on ip {} port {}",
MqttMessages.LOG_START_MQTT_SERVICE_SUCCESSFULLY_LISTENING_IP_ARG_PORT_ARG_47CE46D5,
iotDBConfig.getMqttHost(),
iotDBConfig.getMqttPort());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ private static void init() {

public static PayloadFormatter getPayloadFormat(String name) {
PayloadFormatter formatter = mqttPayloadPluginMap.get(name);
Preconditions.checkArgument(formatter != null, "Unknown payload format named: " + name);
Preconditions.checkArgument(
formatter != null, MqttMessages.UNKNOWN_PAYLOAD_FORMAT_NAMED + name);
return formatter;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,53 @@ public final class RestMessages {

// --- RequestValidationHandler (v2) ---
public static final String PREFIX_PATHS_EMPTY = "prefix_paths should not be empty";
public static final String SQL_SHOULD_NOT_BE_NULL = "sql should not be null";
public static final String ROW_LIMIT_SHOULD_BE_POSITIVE = "row_limit should be positive";
public static final String ROW_LIMIT_CAMEL_SHOULD_BE_POSITIVE = "rowLimit should be positive";
public static final String PREFIX_PATHS_NOT_NULL = "prefix_paths should not be null";
public static final String TIMESTAMPS_NOT_NULL = "timestamps should not be null";
public static final String IS_ALIGNED_NOT_NULL = "is_aligned should not be null";
public static final String IS_ALIGNED_CAMEL_NOT_NULL = "isAligned should not be null";
public static final String DEVICE_NOT_NULL = "device should not be null";
public static final String DEVICE_ID_NOT_NULL = "deviceId should not be null";
public static final String DATA_TYPES_NOT_NULL = "data_types should not be null";
public static final String DATA_TYPES_CAMEL_NOT_NULL = "dataTypes should not be null";
public static final String MEASUREMENTS_NOT_NULL = "measurements should not be null";
public static final String VALUES_NOT_NULL = "values should not be null";
public static final String DEVICES_NOT_NULL = "devices should not be null";
public static final String DATA_TYPES_LIST_NOT_NULL = "data_types_list should not be null";
public static final String VALUES_LIST_NOT_NULL = "values_list should not be null";
public static final String MEASUREMENTS_LIST_NOT_NULL = "measurements_list should not be null";
public static final String EXPRESSION_NOT_NULL = "expression should not be null";
public static final String PREFIX_PATH_NOT_NULL = "prefix_path should not be null";
public static final String PREFIX_PATH_CAMEL_NOT_NULL = "prefixPath should not be null";
public static final String START_TIME_NOT_NULL = "start_time should not be null";
public static final String START_TIME_CAMEL_NOT_NULL = "startTime should not be null";
public static final String END_TIME_NOT_NULL = "end_time should not be null";
public static final String END_TIME_CAMEL_NOT_NULL = "endTime should not be null";
public static final String DATABASE_NOT_NULL = "database should not be null";
public static final String TABLE_NOT_NULL = "table should not be null";
public static final String COLUMN_NAMES_NOT_NULL = "column_names should not be null";
public static final String COLUMN_CATEGORIES_NOT_NULL =
"column_categories should not be null";
public static final String COLUMN_NAMES_AND_COLUMN_CATEGORIES_SIZE_MISMATCH =
"column_names and column_categories should have the same size";
public static final String COLUMN_CATEGORIES_AND_DATA_TYPES_SIZE_MISMATCH =
"column_categories and data_types should have the same size";
public static final String VALUES_AND_TIMESTAMPS_SIZE_MISMATCH =
"values and timestamps should have the same size";
public static final String ILLEGAL_TABLE_DATA_TYPE =
"The %s data type of %s is illegal";
public static final String ILLEGAL_DEVICE_MEASUREMENT_DATA_TYPE =
"The %s data type of %s.%s is illegal";
public static final String ROW_VALUES_SIZE_MISMATCH =
"The number of values in the %dth row is not equal to the data_types size";
public static final String ERROR_MESSAGE_SEPARATOR = ",";

private RestMessages() {}
// ---------------------------------------------------------------------------
// Additional auto-collected messages
// ---------------------------------------------------------------------------
public static final String EXCEPTION_UNSUPPORTED_DATA_TYPE_0521CEDE = "unsupported data type: ";

}
Loading
Loading