This is an automated email from the ASF dual-hosted git repository.
SvenO3 pushed a commit to branch
4367-warn-on-incompatible-dataset-schema-changes-affecting-measurements-in-pipelines
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to
refs/heads/4367-warn-on-incompatible-dataset-schema-changes-affecting-measurements-in-pipelines
by this push:
new 6e30262d3e Detect data type change of measurements
6e30262d3e is described below
commit 6e30262d3e2ad7580105867b2e316e38bcedd5ab
Author: Sven Oehler <[email protected]>
AuthorDate: Tue May 5 10:33:30 2026 +0200
Detect data type change of measurements
---
.../update/PipelineMeasurementChangeDetector.java | 135 +++++++++++++++++++++
.../pipeline/update/PipelineUpdateCoordinator.java | 18 ++-
2 files changed, 151 insertions(+), 2 deletions(-)
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineMeasurementChangeDetector.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineMeasurementChangeDetector.java
new file mode 100644
index 0000000000..700c104f6f
--- /dev/null
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineMeasurementChangeDetector.java
@@ -0,0 +1,135 @@
+/*
+ * 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.streampipes.manager.pipeline.update;
+
+import org.apache.streampipes.model.DataSinkType;
+import org.apache.streampipes.model.SpDataStream;
+import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.pipeline.Pipeline;
+import org.apache.streampipes.model.schema.EventProperty;
+import org.apache.streampipes.model.schema.EventPropertyPrimitive;
+import org.apache.streampipes.model.schema.EventSchema;
+import org.apache.streampipes.model.schema.PropertyScope;
+import org.apache.streampipes.vocabulary.SO;
+import org.apache.streampipes.vocabulary.XSD;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class PipelineMeasurementChangeDetector {
+
+ private static final String DATA_LAKE_SINK_APP_ID =
"org.apache.streampipes.sinks.internal.jvm.datalake";
+
+ public boolean hasCriticalMeasurementFieldChange(Pipeline pipeline,
+ String affectedElementId,
+ EventSchema
updatedEventSchema) {
+ if (!hasDatabaseSink(pipeline)) {
+ return false;
+ }
+
+ return getEventSchema(pipeline, affectedElementId)
+ .map(existingEventSchema ->
+ hasCriticalMeasurementFieldChange(existingEventSchema,
updatedEventSchema))
+ .orElse(false);
+ }
+
+ private boolean hasDatabaseSink(Pipeline pipeline) {
+ return pipeline.getActions().stream()
+ .anyMatch(this::isDatabaseSink);
+ }
+
+ private boolean isDatabaseSink(DataSinkInvocation dataSink) {
+ return DATA_LAKE_SINK_APP_ID.equals(dataSink.getAppId())
+ ||
dataSink.getCategory().stream().anyMatch(DataSinkType.DATABASE.name()::equals);
+ }
+
+ private Optional<EventSchema> getEventSchema(Pipeline pipeline,
+ String affectedElementId) {
+ return pipeline
+ .getStreams()
+ .stream()
+ .filter(stream -> stream.getElementId().equals(affectedElementId))
+ .findFirst()
+ .map(SpDataStream::getEventSchema);
+ }
+
+ private boolean hasCriticalMeasurementFieldChange(EventSchema
existingEventSchema,
+ EventSchema
updatedEventSchema) {
+ var existingMeasurementFields = existingEventSchema
+ .getEventProperties()
+ .stream()
+ .filter(this::isMeasurementField)
+ .collect(Collectors.toMap(
+ EventProperty::getRuntimeName,
+ Function.identity(),
+ (existingProperty, duplicateProperty) -> existingProperty
+ ));
+
+ return updatedEventSchema
+ .getEventProperties()
+ .stream()
+ .filter(this::isMeasurementField)
+ .anyMatch(updatedProperty -> {
+ var existingProperty =
existingMeasurementFields.get(updatedProperty.getRuntimeName());
+ return existingProperty != null &&
hasCriticalFieldTypeChange(existingProperty, updatedProperty);
+ });
+ }
+
+ private boolean isMeasurementField(EventProperty eventProperty) {
+ return
!PropertyScope.DIMENSION_PROPERTY.name().equals(eventProperty.getPropertyScope());
+ }
+
+ private boolean hasCriticalFieldTypeChange(EventProperty existingProperty,
+ EventProperty updatedProperty) {
+ return toStorageType(existingProperty) != toStorageType(updatedProperty);
+ }
+
+ private StorageType toStorageType(EventProperty eventProperty) {
+ if (eventProperty instanceof EventPropertyPrimitive primitiveProperty) {
+ return toPrimitiveStorageType(primitiveProperty.getRuntimeType());
+ } else {
+ return StorageType.STRING;
+ }
+ }
+
+ private StorageType toPrimitiveStorageType(String runtimeType) {
+ if (XSD.INTEGER.toString().equals(runtimeType)
+ || XSD.LONG.toString().equals(runtimeType)) {
+ return StorageType.INTEGER;
+ } else if (XSD.FLOAT.toString().equals(runtimeType)
+ || XSD.DOUBLE.toString().equals(runtimeType)
+ || SO.NUMBER.equals(runtimeType)) {
+ return StorageType.FLOAT;
+ } else if (XSD.BOOLEAN.toString().equals(runtimeType)) {
+ return StorageType.BOOLEAN;
+ } else {
+ return StorageType.STRING;
+ }
+ }
+
+ private enum StorageType {
+ INTEGER,
+ FLOAT,
+ BOOLEAN,
+ STRING
+ }
+}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinator.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinator.java
index 6de0a17945..1f81c988ff 100644
---
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinator.java
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinator.java
@@ -49,9 +49,11 @@ public class PipelineUpdateCoordinator {
private static final PipelinesStats PIPELINES_STATS = new PipelinesStats();
private final ExtensionServiceRequestManager requestManager;
+ private final PipelineMeasurementChangeDetector
measurementFieldChangeDetector;
public PipelineUpdateCoordinator(ExtensionServiceRequestManager
requestManager) {
this.requestManager = requestManager;
+ this.measurementFieldChangeDetector = new
PipelineMeasurementChangeDetector();
}
public void updatePipelines(SpDataStream dataStream) {
@@ -101,6 +103,11 @@ public class PipelineUpdateCoordinator {
}
var storedPipeline =
PipelineManager.getPipeline(pipeline.getPipelineId());
+ var hasChangedMeasurementField =
measurementFieldChangeDetector.hasCriticalMeasurementFieldChange(
+ storedPipeline,
+ affectedElementId,
+ updatedEventSchema
+ );
var updatedPipeline = updatePipeline(storedPipeline, affectedElementId,
updatedStreamName, updatedEventSchema);
try {
@@ -108,7 +115,7 @@ public class PipelineUpdateCoordinator {
var modificationMessage = verificationHandler.verifyPipeline();
var updateInfo = makeUpdateInfo(modificationMessage, updatedPipeline);
var modifiedPipeline =
verificationHandler.makeModifiedPipeline().pipeline();
- var canAutoMigrate = canAutoMigrate(modificationMessage);
+ var canAutoMigrate = canAutoMigrate(modificationMessage) &&
!hasChangedMeasurementField;
if (!canAutoMigrate) {
modifiedPipeline.setHealthStatus(PipelineHealthStatus.REQUIRES_ATTENTION);
@@ -139,10 +146,17 @@ public class PipelineUpdateCoordinator {
var updateInfos = new ArrayList<PipelineUpdateInfo>();
affectedPipelines.forEach(pipeline -> {
+ var hasChangedMeasurementField =
measurementFieldChangeDetector.hasCriticalMeasurementFieldChange(
+ pipeline,
+ affectedElementId,
+ updatedEventSchema
+ );
var updatedPipeline = updatePipeline(pipeline, affectedElementId,
updatedStreamName, updatedEventSchema);
try {
var modificationMessage = new
PipelineVerificationHandlerV2(updatedPipeline, requestManager).verifyPipeline();
- updateInfos.add(makeUpdateInfo(modificationMessage, updatedPipeline));
+ var updateInfo = makeUpdateInfo(modificationMessage, updatedPipeline);
+ updateInfo.setCanAutoMigrate(updateInfo.isCanAutoMigrate() &&
!hasChangedMeasurementField);
+ updateInfos.add(updateInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}