morningman commented on code in PR #70121:
URL: https://github.com/apache/doris/pull/70121#discussion_r4463177785
##########
fe/fe-core/src/main/java/org/apache/doris/load/routineload/RoutineLoadJob.java:
##########
@@ -2171,6 +2196,51 @@ protected void modifyCommonJobProperties(Map<String,
String> jobProperties) thro
this.jobProperties.put(CreateRoutineLoadInfo.PARTIAL_COLUMNS,
String.valueOf(isPartialUpdate));
this.jobProperties.put(CreateRoutineLoadInfo.UNIQUE_KEY_UPDATE_MODE,
uniqueKeyUpdateMode.name());
}
+
+ // Tasks take enclose and escape from these fields, not from
jobProperties, where the caller puts the
+ // new values
+ if (jobProperties.containsKey(CsvFileFormatProperties.PROP_ENCLOSE)) {
+ this.enclose = csvControlByte(CsvFileFormatProperties.PROP_ENCLOSE,
+ jobProperties.get(CsvFileFormatProperties.PROP_ENCLOSE),
this.enclose);
+ }
+ if (jobProperties.containsKey(CsvFileFormatProperties.PROP_ESCAPE)) {
+ this.escape = csvControlByte(CsvFileFormatProperties.PROP_ESCAPE,
+ jobProperties.get(CsvFileFormatProperties.PROP_ESCAPE),
this.escape);
+ }
+ }
+
+ // The byte an enclose or escape value stands for, as CREATE takes it.
ALTER of an older version did not check
+ // the value, so the metadata can hold one that CREATE rejects. Such a
value never reached the job, and it does
+ // not now: the job keeps the byte it has, where an exception would fail
the replay of the metadata.
+ private byte csvControlByte(String key, String value, byte current) {
+ try {
+ return key.equals(CsvFileFormatProperties.PROP_ENCLOSE)
+ ? CsvFileFormatProperties.parseEnclose(value) :
CsvFileFormatProperties.parseEscape(value);
+ } catch (org.apache.doris.nereids.exceptions.AnalysisException e) {
+ LOG.warn("routine load job {} keeps its {} instead of the invalid
value '{}': {}",
+ id, key, value, e.getMessage());
+ return current;
+ }
+ }
+
+ // What CREATE records for enclose or escape: the value the statement
gave, which the replay parses as CREATE did.
+ // The byte itself does not survive a String: new String(new byte[]{b})
decodes every byte from 0x80 up to U+FFFD.
+ // Without a value the byte is 0, recorded as it always was.
+ private static String csvControlValue(CreateRoutineLoadInfo info, String
key, byte value) {
+ String given = info.getJobProperties().get(key);
+ return Strings.isNullOrEmpty(given) ? new String(new byte[]{value}) :
given;
+ }
+
+ // csvControlByte for a value CREATE or ALTER recorded. CREATE of an older
version recorded the byte, so a byte
+ // from 0x80 up came back as U+FFFD, which would now parse to 0xFD for
enclose and 0xEF for escape. That byte is
+ // lost: the job keeps the default, which is what it has run with after
every restart.
+ private byte recordedCsvControlByte(String key, String value, byte
current) {
+ if (value.equals("\uFFFD")) {
Review Comment:
Fixed in `73dbcc1d452`.
Confirmed. The fallback could not tell a U+FFFD that an older CREATE
recorded for a lost byte from one the user wrote, so a job created with
`"escape" = "�"` ran with escape 0xEF on the master and with no escape on a
follower as soon as the follower replayed the CREATE.
CREATE and ALTER ROUTINE LOAD now reject U+FFFD as `enclose` or `escape`
(`CreateRoutineLoadInfo#checkCsvControlValue`), so the metadata holds it only
from an older version: a byte its CREATE lost, or a value its ALTER stored
unchecked, which never reached the job either. The fallback in the replay stays
as it was. U+FFFD never stood for the byte the statement meant: it is what a
statement decodes to when its bytes are not UTF-8 (a Latin-1 client's `é`, for
example), and the parsers turn it into 0xFD and 0xEF, not into that byte. A
user who wants 0xFD as `enclose` can write `ý`, and 0xEF as `escape` any
character from U+F000 to U+FFFC.
`RoutineLoadJobReplayTest#testCreateRejectsTheReplacementCharacter` and
`AlterRoutineLoadCommandTest#testValidateRejectsTheReplacementCharacter` cover
both keys, and `test_routine_load_property` gets a `test { sql; exception }`
case for each key outside its Kafka guard, since CREATE checks the properties
before it connects to Kafka. Each new UT case fails when its check is removed.
--
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]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]