github-actions[bot] commented on code in PR #64878:
URL: https://github.com/apache/doris/pull/64878#discussion_r3610840399


##########
fe/fe-core/src/main/java/org/apache/doris/load/routineload/RoutineLoadJob.java:
##########
@@ -2128,23 +2232,49 @@ private void validateFlexiblePartialUpdateForAlter() 
throws UserException {
         }
         OlapTable olapTable = (OlapTable) table;
 
-        // Validate table-level constraints (MoW, skip_bitmap, 
light_schema_change, variant columns)
         olapTable.validateForFlexiblePartialUpdate();
+        String format = 
this.jobProperties.getOrDefault(FileFormatProperties.PROP_FORMAT, "csv");
+        if (!"json".equalsIgnoreCase(format)) {
+            throw new DdlException("Flexible partial update only supports JSON 
format, but current job uses: "
+                    + format);
+        }
+        if (Boolean.parseBoolean(this.jobProperties.getOrDefault(
+                JsonFileFormatProperties.PROP_FUZZY_PARSE, "false"))) {
+            throw new DdlException("Flexible partial update does not support 
fuzzy_parse");
+        }
+        String jsonPaths = getJsonPaths();
+        if (jsonPaths != null && !jsonPaths.isEmpty()) {
+            throw new DdlException("Flexible partial update does not support 
jsonpaths");
+        }
+        if (columnDescs != null && !columnDescs.descs.isEmpty()) {
+            throw new DdlException("Flexible partial update does not support 
COLUMNS specification");
+        }
+    }
+
+    /**
+     * Validate flexible partial update constraints when altering routine load 
job.
+     */
+    private void validateFlexiblePartialUpdateForAlter(OlapTable targetTable,
+            Map<String, String> alteredJobProperties) throws UserException {
+        // Validate table-level constraints (MoW, skip_bitmap, 
light_schema_change, variant columns)
+        targetTable.validateForFlexiblePartialUpdate();
+        Map<String, String> effectiveJobProperties = 
Maps.newHashMap(jobProperties);
+        effectiveJobProperties.putAll(alteredJobProperties);

Review Comment:
   [P1] Validate the staged load descriptor here. A paused JSON UPSERT job with 
no current column mapping can issue ALTER ... COLUMNS(k1) 
PROPERTIES("unique_key_update_mode"="UPDATE_FLEXIBLE_COLUMNS"); both analysis 
and the mutation-time recheck still see the old empty columnDescs, then Kafka 
applies and journals the flexible mode plus COLUMNS together. The statement 
succeeds with a definition that CREATE ROUTINE LOAD explicitly rejects as 
unsupported. Please overlay command.getRoutineLoadDesc() into the effective 
definition under the job lock before validating flexible-update constraints.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterRoutineLoadCommand.java:
##########
@@ -315,33 +356,65 @@ private void checkJobProperties() throws UserException {
         }
     }
 
-    private void checkDataSourceProperties() throws UserException {
+    private void checkDataSourceProperties(RoutineLoadJob job) throws 
UserException {
         if (MapUtils.isEmpty(dataSourceMapProperties)) {
             return;
         }
-        RoutineLoadJob job = Env.getCurrentEnv().getRoutineLoadManager()
-                .getJob(getDbName(), getJobName());
+        String effectiveDataSourceType = dataSourceType == null ? 
job.getDataSourceType().name() : dataSourceType;
+        if 
(!effectiveDataSourceType.equalsIgnoreCase(job.getDataSourceType().name())) {
+            throw new AnalysisException("The specified job type is not: " + 
effectiveDataSourceType);
+        }
         this.dataSourceProperties = RoutineLoadDataSourcePropertyFactory
-            .createDataSource(job.getDataSourceType().name(), 
dataSourceMapProperties, job.isMultiTable());
+            .createDataSource(effectiveDataSourceType, 
dataSourceMapProperties, job.isMultiTable());
         dataSourceProperties.setAlter(true);
-        dataSourceProperties.setTimezone(job.getTimezone());
+        dataSourceProperties.setTimezone(analyzedJobProperties.getOrDefault(
+                CreateRoutineLoadInfo.TIMEZONE, job.getTimezone()));
         dataSourceProperties.analyze();
     }
 
-    private void checkPartialUpdate() throws UserException {
-        if (!isPartialUpdate) {
+    private void validateAlterJobProperties(RoutineLoadJob job,
+            TUniqueKeyUpdateMode effectiveUniqueKeyUpdateMode) throws 
UserException {
+        if (effectiveUniqueKeyUpdateMode == TUniqueKeyUpdateMode.UPSERT) {
             return;
         }
-        RoutineLoadJob job = Env.getCurrentEnv().getRoutineLoadManager()
-                .getJob(getDbName(), getDbName());
         if (job.isMultiTable()) {
-            throw new AnalysisException("load by PARTIAL_COLUMNS is not 
supported in multi-table load.");
+            throw new AnalysisException("Partial update is not supported in 
multi-table load.");
         }
         Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException(job.getDbFullName());
         Table table = db.getTableOrAnalysisException(job.getTableName());
-        if (isPartialUpdate && !((OlapTable) 
table).getEnableUniqueKeyMergeOnWrite()) {
-            throw new AnalysisException("load by PARTIAL_COLUMNS is only 
supported in unique table MoW");
+        job.validateAlterJobProperties((OlapTable) table, 
analyzedJobProperties, effectiveUniqueKeyUpdateMode);
+    }
+
+    private void validateTargetTable(ConnectContext ctx, RoutineLoadJob job,
+            TUniqueKeyUpdateMode effectiveUniqueKeyUpdateMode) throws 
UserException {
+        if (job.getDataSourceType() != LoadDataSourceType.KAFKA) {

Review Comment:
   [P1] Align this guard with the advertised Kinesis support. The PR 
description and release note say that SET TARGET TABLE can be combined with 
Kafka/Kinesis source properties and require only that the explicit source type 
match the existing job, but every paused single-table Kinesis job is rejected 
here. KinesisRoutineLoadJob also has no target-ID/descriptor mutation or replay 
path, so the documented statement cannot work. Please either implement atomic 
live/replay support for Kinesis or explicitly narrow the release note and 
documentation to Kafka-only target switching.



##########
fe/fe-core/src/main/java/org/apache/doris/load/routineload/kafka/KafkaRoutineLoadJob.java:
##########
@@ -766,91 +786,95 @@ public Map<String, String> getCustomProperties() {
         return getMaskedCustomProperties("property.");
     }
 
+    @Override
+    public boolean appliesRoutineLoadDescAtomically() {
+        return true;
+    }
+
     @Override
     public void modifyProperties(AlterRoutineLoadCommand command) throws 
UserException {
-        Map<String, String> jobProperties = command.getAnalyzedJobProperties();
         KafkaDataSourceProperties dataSourceProperties = 
(KafkaDataSourceProperties) command.getDataSourceProperties();
-        if (null != dataSourceProperties) {
-            // if the partition offset is set by timestamp, convert it to real 
offset
-            convertOffset(dataSourceProperties);
-        }
 
         writeLock();
         try {
             if (getState() != JobState.PAUSED) {
                 throw new DdlException("Only supports modification of PAUSED 
jobs");
             }
+            if (command.getRoutineLoadDesc() != null && 
command.getLoadPropertyTableId() != tableId) {
+                throw new DdlException("Routine load target table changed 
while validating load properties; "
+                        + "please retry ALTER ROUTINE LOAD");
+            }
+            if (!command.hasTargetTable()) {
+                validateAlterJobPropertiesForMutation(command);
+            }
 
-            modifyPropertiesInternal(jobProperties, dataSourceProperties);
-
-            AlterRoutineLoadJobOperationLog log = new 
AlterRoutineLoadJobOperationLog(this.id,
-                    jobProperties, dataSourceProperties);
-            Env.getCurrentEnv().getEditLog().logAlterRoutineLoadJob(log);
+            // Kafka offset resolution may perform external I/O. Keep it 
outside DB/table metadata locks.
+            PreparedKafkaProperties preparedDataSourceProperties =
+                    prepareDataSourceProperties(dataSourceProperties, false);
+            unprotectApplyAlter(command, dataSourceProperties, 
preparedDataSourceProperties);
         } finally {
             writeUnlock();
         }
     }
 
-    private void convertOffset(KafkaDataSourceProperties dataSourceProperties) 
throws UserException {
+    private void unprotectApplyAlter(AlterRoutineLoadCommand command,
+            KafkaDataSourceProperties dataSourceProperties,
+            PreparedKafkaProperties preparedDataSourceProperties) throws 
UserException {
+        modifyPropertiesInternal(command.getAnalyzedJobProperties(), 
dataSourceProperties,
+                preparedDataSourceProperties);
+        if (command.hasTargetTable()) {
+            tableId = command.getTargetTableId();
+        }
+        setRoutineLoadDesc(command.getRoutineLoadDesc());
+        AlterRoutineLoadJobOperationLog log = new 
AlterRoutineLoadJobOperationLog(id,
+                command.getAnalyzedJobProperties(), dataSourceProperties, 
command.getTargetTableId(),
+                command.getRoutineLoadDesc());
+        Env.getCurrentEnv().getEditLog().logAlterRoutineLoadJob(log);
+    }
+
+    private List<Pair<Integer, Long>> resolveOffsets(KafkaDataSourceProperties 
dataSourceProperties,
+            KafkaDataSourceSnapshot dataSourceSnapshot) throws UserException {
         List<Pair<Integer, Long>> partitionOffsets = 
dataSourceProperties.getKafkaPartitionOffsets();
         if (partitionOffsets.isEmpty()) {
-            return;
+            return partitionOffsets;
         }
-        List<Pair<Integer, Long>> newOffsets;
         if (dataSourceProperties.isOffsetsForTimes()) {
-            newOffsets = KafkaUtil.getOffsetsForTimes(brokerList, topic, 
convertedCustomProperties, partitionOffsets);
-        } else {
-            newOffsets = KafkaUtil.getRealOffsets(brokerList, topic, 
convertedCustomProperties, partitionOffsets);
+            return KafkaUtil.getOffsetsForTimes(dataSourceSnapshot.brokerList, 
dataSourceSnapshot.topic,
+                    dataSourceSnapshot.convertedCustomProperties, 
partitionOffsets);
         }
-        dataSourceProperties.setKafkaPartitionOffsets(newOffsets);
+        return KafkaUtil.getRealOffsets(dataSourceSnapshot.brokerList, 
dataSourceSnapshot.topic,
+                dataSourceSnapshot.convertedCustomProperties, 
partitionOffsets);
     }
 
     private void modifyPropertiesInternal(Map<String, String> jobProperties,
                                           KafkaDataSourceProperties 
dataSourceProperties)
             throws UserException {
-        if (null != dataSourceProperties) {
-            List<Pair<Integer, Long>> kafkaPartitionOffsets = 
Lists.newArrayList();
-            Map<String, String> customKafkaProperties = Maps.newHashMap();
-
-            if 
(MapUtils.isNotEmpty(dataSourceProperties.getOriginalDataSourceProperties())) {
-                kafkaPartitionOffsets = 
dataSourceProperties.getKafkaPartitionOffsets();
-                customKafkaProperties = 
dataSourceProperties.getCustomKafkaProperties();
-            }
-
-            // convertCustomProperties and check partitions before reset 
progress to make modify operation atomic
-            if (!customKafkaProperties.isEmpty()) {
-                this.customProperties.putAll(customKafkaProperties);
-                convertCustomProperties(true);
-            }
-
-            if (!kafkaPartitionOffsets.isEmpty()) {
-                ((KafkaProgress) 
progress).checkPartitions(kafkaPartitionOffsets);
-            }
+        PreparedKafkaProperties preparedDataSourceProperties = 
prepareDataSourceProperties(dataSourceProperties, true);
+        modifyPropertiesInternal(jobProperties, dataSourceProperties, 
preparedDataSourceProperties);
+    }
 
-            if (Config.isCloudMode()) {
-                Cloud.ResetRLProgressRequest.Builder builder = 
Cloud.ResetRLProgressRequest.newBuilder()
-                        
.setRequestIp(FrontendOptions.getLocalHostAddressCached());
-                builder.setCloudUniqueId(Config.cloud_unique_id);
-                builder.setDbId(dbId);
-                builder.setJobId(id);
-                if (!kafkaPartitionOffsets.isEmpty()) {
-                    Map<Integer, Long> partitionOffsetMap = new HashMap<>();
-                    for (Pair<Integer, Long> pair : kafkaPartitionOffsets) {
-                        // The reason why the value recorded in MS in cloud 
mode needs to be subtracted by one is
-                        // this value will be incremented
-                        // when pulling MS persistent progress data and 
updating memory
-                        // in routineLoadJob.updateCloudProgress().
-                        partitionOffsetMap.put(pair.first, pair.second - 1);
-                    }
-                    builder.putAllPartitionToOffset(partitionOffsetMap);
-                }
-                resetCloudProgress(builder);
+    private void modifyPropertiesInternal(Map<String, String> jobProperties,
+                                          KafkaDataSourceProperties 
dataSourceProperties,
+                                          PreparedKafkaProperties 
preparedDataSourceProperties)
+            throws UserException {
+        if (null != dataSourceProperties) {
+            Preconditions.checkNotNull(preparedDataSourceProperties);
+            KafkaDataSourceSnapshot dataSourceSnapshot = 
preparedDataSourceProperties.dataSourceSnapshot;
+            List<Pair<Integer, Long>> kafkaPartitionOffsets = 
preparedDataSourceProperties.kafkaPartitionOffsets;
+            
dataSourceProperties.setKafkaPartitionOffsets(kafkaPartitionOffsets);
+
+            if (dataSourceSnapshot.customPropertiesChanged) {
+                this.customProperties.clear();
+                
this.customProperties.putAll(dataSourceSnapshot.customProperties);
+                this.convertedCustomProperties.clear();
+                
this.convertedCustomProperties.putAll(dataSourceSnapshot.convertedCustomProperties);
+                this.kafkaDefaultOffSet = 
dataSourceSnapshot.kafkaDefaultOffset;
             }
 
             // It is necessary to reset the Kafka progress cache if topic 
change,
             // and should reset cache before modifying partition offset.
-            if (!Strings.isNullOrEmpty(dataSourceProperties.getTopic())) {
-                this.topic = dataSourceProperties.getTopic();
+            if (dataSourceSnapshot.resetProgress) {
+                this.topic = dataSourceSnapshot.topic;
                 this.progress = new KafkaProgress();

Review Comment:
   [P1] Re-seed pinned partitions on a topic-only switch. A job created with 
kafka_partitions=0 can be paused and altered with only kafka_topic=B; this 
branch clears progress but leaves customKafkaPartitions/currentKafkaPartitions 
at [0]. On resume the pinned branch skips Kafka refresh and returns false, so 
updateNewPartitionProgress() is never called, and divideRoutineLoadJob() puts a 
null offset into the new task even when B has partition 0. Please either 
require explicit offsets for a pinned topic switch or validate the retained IDs 
against the staged topic and seed them from the effective default before 
committing.



-- 
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]

Reply via email to