This is an automated email from the ASF dual-hosted git repository.

dominikriemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new 060e660c3f feat(#4451): Add MSSQL CDC connector (#4452)
060e660c3f is described below

commit 060e660c3f489e280cb8ebb4a9b2668af41c8104
Author: Dominik Riemer <[email protected]>
AuthorDate: Mon May 11 22:02:58 2026 +0200

    feat(#4451): Add MSSQL CDC connector (#4452)
---
 .../apache/streampipes/commons/constants/Envs.java |   2 +
 .../commons/environment/DefaultEnvironment.java    |   5 +
 .../commons/environment/Environment.java           |   2 +
 streampipes-extensions/pom.xml                     |   1 +
 .../streampipes-connectors-cdc/pom.xml             |  85 +++++
 .../connectors/cdc/CdcConnectorsModuleExport.java  |  46 +++
 .../cdc/adapter/mssql/MsSqlCdcAdapter.java         | 416 +++++++++++++++++++++
 .../cdc/adapter/mssql/MsSqlCdcAdapterConfig.java   | 143 +++++++
 .../cdc/adapter/mssql/MsSqlMetadataClient.java     | 404 ++++++++++++++++++++
 .../documentation.md                               | 142 +++++++
 .../icon.png                                       | Bin 0 -> 58929 bytes
 .../strings.en                                     |  47 +++
 .../cdc/adapter/mssql/MsSqlCdcAdapterTest.java     | 173 +++++++++
 .../streampipes-extensions-all-iiot/pom.xml        |   6 +
 .../extensions/all/iiot/AllExtensionsIIoTInit.java |   4 +-
 .../streampipes-extensions-all-jvm/pom.xml         |   5 +
 .../extensions/all/jvm/AllExtensionsInit.java      |   2 +
 ...tic-runtime-resolvable-oneof-input.component.ts |   1 +
 18 files changed, 1483 insertions(+), 1 deletion(-)

diff --git 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
index 7e5a47aa66..c608e4d486 100644
--- 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
+++ 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
@@ -103,6 +103,7 @@ public enum Envs {
 
   SP_PULSAR_URL("SP_PULSAR_URL", "pulsar://localhost:6650"),
   SP_CORE_EXTENSION_TRANSPORT_MODE("SP_CORE_EXTENSION_TRANSPORT_MODE", "http"),
+  
SP_CORE_EXTENSIONS_TRANSPORT_MODE_TIMEOUT_SECONDS("SP_CORE_EXTENSIONS_TRANSPORT_MODE_TIMEOUT_SECONDS",
 "20"),
   SP_EXTENSION_TRANSPORT_MODE("SP_EXTENSION_TRANSPORT_MODE", "http"),
   SP_EXTENSION_REQUEST_TOPIC_PREFIX(
       "SP_EXTENSION_REQUEST_TOPIC_PREFIX",
@@ -155,6 +156,7 @@ public enum Envs {
   // PLC4X connection cache
   SP_PLC4X_CONN_MAX_WAIT_TIME_MS("SP_PLC4X_CONN_MAX_WAIT_TIME_MS", "20000"),
   SP_PLC4X_CONN_MAX_LEASE_TIME_MS("SP_PLC4X_CONN_MAX_LEASE_TIME_MS", "4000"),
+  SP_CDC_MSSQL_POLL_INTERVAL_MS("SP_CDC_MSSQL_POLL_INTERVAL_MS", "1000"),
 
   // Retention Local File
   SP_RETENTION_LOCAL_DIR("SP_RETENTION_LOCAL_DIR", "./ArchivedData"),
diff --git 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
index 76799749d6..edcd692de4 100644
--- 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
+++ 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
@@ -427,6 +427,11 @@ public class DefaultEnvironment implements Environment {
     return new IntEnvironmentVariable(Envs.SP_PLC4X_CONN_MAX_LEASE_TIME_MS);
   }
 
+  @Override
+  public IntEnvironmentVariable getMsSqlCdcPollIntervalMs() {
+    return new IntEnvironmentVariable(Envs.SP_CDC_MSSQL_POLL_INTERVAL_MS);
+  }
+
   @Override
   public BooleanEnvironmentVariable getFileLoggingEnabled() {
     return new BooleanEnvironmentVariable(Envs.SP_LOGGING_FILE_ENABLED);
diff --git 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
index 3bf54cce08..56a7d98c4d 100644
--- 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
+++ 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
@@ -202,6 +202,8 @@ public interface Environment {
 
   IntEnvironmentVariable getPlc4xMaxLeaseTimeMs();
 
+  IntEnvironmentVariable getMsSqlCdcPollIntervalMs();
+
   BooleanEnvironmentVariable getFileLoggingEnabled();
 
   BooleanEnvironmentVariable getConsoleLoggingEnabled();
diff --git a/streampipes-extensions/pom.xml b/streampipes-extensions/pom.xml
index 749c3bbf24..50bb621557 100644
--- a/streampipes-extensions/pom.xml
+++ b/streampipes-extensions/pom.xml
@@ -35,6 +35,7 @@
         <module>streampipes-connect-adapters-iiot</module>
 
         <module>streampipes-connectors-camel</module>
+        <module>streampipes-connectors-cdc</module>
         <module>streampipes-connectors-filewatcher</module>
         <module>streampipes-connectors-influx</module>
         <module>streampipes-connectors-kafka</module>
diff --git a/streampipes-extensions/streampipes-connectors-cdc/pom.xml 
b/streampipes-extensions/streampipes-connectors-cdc/pom.xml
new file mode 100644
index 0000000000..5bc89ec9c8
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-cdc/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.streampipes</groupId>
+        <artifactId>streampipes-extensions</artifactId>
+        <version>0.99.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>streampipes-connectors-cdc</artifactId>
+
+    <properties>
+        <debezium.version>3.4.3.Final</debezium.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-extensions-api</artifactId>
+            <version>0.99.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-extensions-management</artifactId>
+            <version>0.99.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-sdk</artifactId>
+            <version>0.99.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>io.debezium</groupId>
+            <artifactId>debezium-api</artifactId>
+            <version>${debezium.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.debezium</groupId>
+            <artifactId>debezium-embedded</artifactId>
+            <version>${debezium.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.github.classgraph</groupId>
+                    <artifactId>classgraph</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.debezium</groupId>
+            <artifactId>debezium-connector-sqlserver</artifactId>
+            <version>${debezium.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git 
a/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/CdcConnectorsModuleExport.java
 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/CdcConnectorsModuleExport.java
new file mode 100644
index 0000000000..604eb97105
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/CdcConnectorsModuleExport.java
@@ -0,0 +1,46 @@
+/*
+ * 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.extensions.connectors.cdc;
+
+import org.apache.streampipes.extensions.api.connect.StreamPipesAdapter;
+import org.apache.streampipes.extensions.api.declarer.IExtensionModuleExport;
+import org.apache.streampipes.extensions.api.migration.IModelMigrator;
+import org.apache.streampipes.extensions.api.pe.IStreamPipesPipelineElement;
+import 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapter;
+
+import java.util.Collections;
+import java.util.List;
+
+public class CdcConnectorsModuleExport implements IExtensionModuleExport {
+
+  @Override
+  public List<StreamPipesAdapter> adapters() {
+    return List.of(new MsSqlCdcAdapter());
+  }
+
+  @Override
+  public List<IStreamPipesPipelineElement<?>> pipelineElements() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public List<IModelMigrator<?, ?>> migrators() {
+    return Collections.emptyList();
+  }
+}
diff --git 
a/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapter.java
 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapter.java
new file mode 100644
index 0000000000..c771b701f1
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapter.java
@@ -0,0 +1,416 @@
+/*
+ * 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.extensions.connectors.cdc.adapter.mssql;
+
+import org.apache.streampipes.commons.environment.Environments;
+import org.apache.streampipes.commons.exceptions.SpConfigurationException;
+import org.apache.streampipes.commons.exceptions.connect.AdapterException;
+import org.apache.streampipes.extensions.api.connect.IAdapterConfiguration;
+import org.apache.streampipes.extensions.api.connect.IEventCollector;
+import org.apache.streampipes.extensions.api.connect.StreamPipesAdapter;
+import 
org.apache.streampipes.extensions.api.connect.context.IAdapterGuessSchemaContext;
+import 
org.apache.streampipes.extensions.api.connect.context.IAdapterRuntimeContext;
+import 
org.apache.streampipes.extensions.api.extractor.IAdapterParameterExtractor;
+import 
org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.api.monitoring.IExtensionsLogger;
+import org.apache.streampipes.extensions.api.runtime.SupportsRuntimeConfig;
+import org.apache.streampipes.model.connect.guess.SampleData;
+import org.apache.streampipes.model.extensions.ExtensionAssetType;
+import 
org.apache.streampipes.model.staticproperty.RuntimeResolvableOneOfStaticProperty;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
+import org.apache.streampipes.sdk.builder.adapter.AdapterConfigurationBuilder;
+import org.apache.streampipes.sdk.builder.adapter.SampleDataBuilder;
+import org.apache.streampipes.sdk.helpers.Labels;
+import org.apache.streampipes.sdk.helpers.Locales;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.debezium.engine.ChangeEvent;
+import io.debezium.engine.DebeziumEngine;
+import io.debezium.engine.format.Json;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeParseException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.DATABASE_KEY;
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.ENCRYPT_KEY;
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.HOST_KEY;
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.PASSWORD_KEY;
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.PORT_KEY;
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.TABLE_KEY;
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.TIMEZONE_KEY;
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.TRUST_SERVER_CERTIFICATE_KEY;
+import static 
org.apache.streampipes.extensions.connectors.cdc.adapter.mssql.MsSqlCdcAdapterConfig.USERNAME_KEY;
+
+public class MsSqlCdcAdapter implements StreamPipesAdapter, 
SupportsRuntimeConfig {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(MsSqlCdcAdapter.class);
+
+  public static final String ID = 
"org.apache.streampipes.connect.cdc.adapter.mssql";
+  static final long ENGINE_STARTUP_TIMEOUT_SECONDS = 10;
+
+  private static final TypeReference<Map<String, Object>> MAP_TYPE = new 
TypeReference<>() {
+  };
+
+  private final ObjectMapper mapper;
+  private final MsSqlMetadataClient metadataClient;
+
+  private DebeziumEngine<ChangeEvent<String, String>> engine;
+  private ExecutorService executorService;
+  private Map<String, MsSqlMetadataClient.TemporalColumnMode> temporalColumns;
+  private ZoneId configuredZoneId;
+  private volatile Throwable engineFailure;
+  private volatile boolean connectorStarted;
+  private volatile boolean stopRequested;
+
+  public MsSqlCdcAdapter() {
+    this(new ObjectMapper(), new MsSqlMetadataClient());
+  }
+
+  MsSqlCdcAdapter(ObjectMapper mapper, MsSqlMetadataClient metadataClient) {
+    this.mapper = mapper;
+    this.metadataClient = metadataClient;
+  }
+
+  @Override
+  public StaticProperty resolveConfiguration(String staticPropertyInternalName,
+                                             IStaticPropertyExtractor 
extractor) throws SpConfigurationException {
+     if (!TABLE_KEY.equals(staticPropertyInternalName)) {
+      return null;
+    }
+
+    MsSqlCdcAdapterConfig config = MsSqlCdcAdapterConfig.from(extractor, 
false);
+    RuntimeResolvableOneOfStaticProperty tableProperty =
+        extractor.getStaticPropertyByName(TABLE_KEY, 
RuntimeResolvableOneOfStaticProperty.class);
+    tableProperty.setOptions(metadataClient.discoverTables(config));
+    return tableProperty;
+  }
+
+  @Override
+  public IAdapterConfiguration declareConfig() {
+    return AdapterConfigurationBuilder.create(ID, 0, MsSqlCdcAdapter::new)
+        .withLocales(Locales.EN)
+        .withAssets(ExtensionAssetType.DOCUMENTATION, ExtensionAssetType.ICON)
+        .requiredTextParameter(Labels.withId(HOST_KEY))
+        .requiredIntegerParameter(Labels.withId(PORT_KEY), 1433)
+        .requiredTextParameter(Labels.withId(DATABASE_KEY))
+        .requiredTextParameter(Labels.withId(USERNAME_KEY))
+        .requiredSecret(Labels.withId(PASSWORD_KEY))
+        .requiredSlideToggle(Labels.withId(ENCRYPT_KEY), true)
+        .requiredSlideToggle(Labels.withId(TRUST_SERVER_CERTIFICATE_KEY), 
false)
+        .requiredTextParameter(Labels.withId(TIMEZONE_KEY), 
ZoneOffset.UTC.getId())
+        .requiredSingleValueSelectionFromContainer(
+            Labels.withId(TABLE_KEY),
+            java.util.List.of(HOST_KEY, PORT_KEY, DATABASE_KEY, USERNAME_KEY, 
PASSWORD_KEY, ENCRYPT_KEY,
+                TIMEZONE_KEY,
+                TRUST_SERVER_CERTIFICATE_KEY)
+        )
+        .buildConfiguration();
+  }
+
+  @Override
+  public void onAdapterStarted(IAdapterParameterExtractor extractor,
+                               IEventCollector collector,
+                               IAdapterRuntimeContext adapterRuntimeContext) 
throws AdapterException {
+    MsSqlCdcAdapterConfig config = 
MsSqlCdcAdapterConfig.from(extractor.getStaticPropertyExtractor());
+    metadataClient.validateSelectedTable(config);
+    this.temporalColumns = metadataClient.describeTemporalColumns(config);
+    this.configuredZoneId = config.getZoneId();
+    this.engineFailure = null;
+    this.connectorStarted = false;
+    this.stopRequested = false;
+
+    CountDownLatch startupLatch = new CountDownLatch(1);
+    Properties properties = buildDebeziumProperties(config, 
extractor.getAdapterDescription().getElementId());
+    this.engine = DebeziumEngine.create(Json.class)
+        .using(properties)
+        .using((success, message, error) -> handleEngineCompletion(
+            success,
+            message,
+            error,
+            adapterRuntimeContext.getLogger(),
+            startupLatch
+        ))
+        .using(createConnectorCallback(startupLatch))
+        .notifying(record -> processRecord(record, collector))
+        .build();
+
+    this.executorService = Executors.newSingleThreadExecutor();
+    this.executorService.execute(engine);
+    awaitStartup(startupLatch, adapterRuntimeContext.getLogger());
+  }
+
+  @Override
+  public void onAdapterStopped(IAdapterParameterExtractor extractor,
+                               IAdapterRuntimeContext adapterRuntimeContext) 
throws AdapterException {
+    this.stopRequested = true;
+
+    if (engine != null) {
+      try {
+        engine.close();
+      } catch (IOException e) {
+        throw new AdapterException("Failed to stop Debezium engine: " + 
e.getMessage(), e);
+      }
+    }
+
+    if (executorService != null) {
+      executorService.shutdown();
+      try {
+        if (!executorService.awaitTermination(15, TimeUnit.SECONDS)) {
+          LOG.warn("Debezium engine executor did not shut down within 15 
seconds.");
+        }
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        throw new AdapterException("Interrupted while waiting for Debezium 
engine shutdown.", e);
+      }
+    }
+
+    this.engine = null;
+    this.executorService = null;
+    this.temporalColumns = null;
+    this.configuredZoneId = null;
+    this.engineFailure = null;
+    this.connectorStarted = false;
+    this.stopRequested = false;
+  }
+
+  @Override
+  public SampleData onSampleDataRequested(IAdapterParameterExtractor extractor,
+                                          IAdapterGuessSchemaContext 
adapterGuessSchemaContext) throws AdapterException {
+    MsSqlCdcAdapterConfig config = 
MsSqlCdcAdapterConfig.from(extractor.getStaticPropertyExtractor());
+    metadataClient.validateSelectedTable(config);
+    return SampleDataBuilder.create()
+        .sample(metadataClient.generateSample(config))
+        .build();
+  }
+
+  Properties buildDebeziumProperties(MsSqlCdcAdapterConfig config, String 
elementId) {
+    Properties props = new Properties();
+    String logicalName = createLogicalName(config, elementId);
+    props.setProperty("name", logicalName + "-engine");
+    props.setProperty("connector.class", 
"io.debezium.connector.sqlserver.SqlServerConnector");
+    props.setProperty("offset.storage", 
"org.apache.kafka.connect.storage.MemoryOffsetBackingStore");
+    props.setProperty("schema.history.internal", 
"io.debezium.relational.history.MemorySchemaHistory");
+    props.setProperty("converter.schemas.enable", "false");
+    props.setProperty("key.converter.schemas.enable", "false");
+    props.setProperty("value.converter.schemas.enable", "false");
+    props.setProperty("include.schema.changes", "false");
+    props.setProperty("tombstones.on.delete", "false");
+    props.setProperty("snapshot.mode", "no_data");
+    props.setProperty("tasks.max", "1");
+    props.setProperty("decimal.handling.mode", "double");
+    props.setProperty(
+        "poll.interval.ms",
+        
String.valueOf(Environments.getEnvironment().getMsSqlCdcPollIntervalMs().getValueOrDefault())
+    );
+
+    props.setProperty("database.hostname", config.getHost());
+    props.setProperty("database.port", String.valueOf(config.getPort()));
+    props.setProperty("database.user", config.getUsername());
+    props.setProperty("database.password", config.getPassword());
+    props.setProperty("database.names", config.getDatabase());
+    props.setProperty("database.encrypt", String.valueOf(config.getEncrypt()));
+    props.setProperty("database.trustServerCertificate", 
String.valueOf(config.getTrustServerCertificate()));
+    props.setProperty("topic.prefix", logicalName);
+    props.setProperty("table.include.list", config.getTable());
+
+    return props;
+  }
+
+  String createLogicalName(MsSqlCdcAdapterConfig config, String elementId) {
+    String effectiveElementId = elementId == null || elementId.isBlank() ? 
"adapter" : elementId;
+
+    return (config.getDatabase() + "_" + config.getTable() + "_" + 
effectiveElementId)
+        .replace('.', '_')
+        .replaceAll("[^A-Za-z0-9_]", "_")
+        .toLowerCase();
+  }
+
+  private void processRecord(ChangeEvent<String, String> record, 
IEventCollector collector) {
+    if (record == null || record.value() == null) {
+      return;
+    }
+
+    try {
+      JsonNode root = mapper.readTree(record.value());
+      if (!"c".equals(root.path("op").asText())) {
+        return;
+      }
+
+      JsonNode afterNode = root.path("after");
+      if (!afterNode.isObject()) {
+        return;
+      }
+
+      collector.collect(normalizeRow(mapper.convertValue(afterNode, 
MAP_TYPE)));
+    } catch (Exception e) {
+      LOG.error("Failed to process Debezium change event", e);
+    }
+  }
+
+  private Map<String, Object> normalizeRow(Map<String, Object> row) {
+    if (row == null || row.isEmpty() || temporalColumns == null || 
temporalColumns.isEmpty()) {
+      return row;
+    }
+
+    Map<String, Object> normalized = new LinkedHashMap<>(row);
+    temporalColumns.forEach((columnName, temporalMode) -> {
+      if (normalized.containsKey(columnName)) {
+        normalized.put(columnName, 
normalizeTemporalValue(normalized.get(columnName), temporalMode));
+      }
+    });
+    return normalized;
+  }
+
+  Object normalizeTemporalValue(Object value, 
MsSqlMetadataClient.TemporalColumnMode temporalMode) {
+    if (value == null) {
+      return null;
+    }
+
+    return switch (temporalMode) {
+      case DATE_EPOCH_MILLIS -> normalizeDateValue(asLong(value));
+      case TIME_MILLIS -> asLong(value);
+      case TIME_MICROS -> asLong(value) / 1_000L;
+      case TIME_NANOS -> asLong(value) / 1_000_000L;
+      case TIMESTAMP_MILLIS -> normalizeTimestampValue(asLong(value), 1L);
+      case TIMESTAMP_MICROS -> normalizeTimestampValue(asLong(value), 1_000L);
+      case TIMESTAMP_NANOS -> normalizeTimestampValue(asLong(value), 
1_000_000L);
+      case DATETIMEOFFSET_MILLIS -> parseOffsetDateTime(value);
+    };
+  }
+
+  private long normalizeDateValue(long epochDays) {
+    LocalDate localDate = LocalDate.ofEpochDay(epochDays);
+    return 
localDate.atStartOfDay(getConfiguredZoneId()).toInstant().toEpochMilli();
+  }
+
+  private long normalizeTimestampValue(long rawValue, long divisor) {
+    long epochMillis = rawValue / divisor;
+    LocalDateTime localDateTime = 
Instant.ofEpochMilli(epochMillis).atOffset(ZoneOffset.UTC).toLocalDateTime();
+    return 
localDateTime.atZone(getConfiguredZoneId()).toInstant().toEpochMilli();
+  }
+
+  private long asLong(Object value) {
+    if (value instanceof Number number) {
+      return number.longValue();
+    }
+
+    return Long.parseLong(String.valueOf(value));
+  }
+
+  private Object parseOffsetDateTime(Object value) {
+    try {
+      return 
OffsetDateTime.parse(String.valueOf(value)).toInstant().toEpochMilli();
+    } catch (DateTimeParseException e) {
+      LOG.warn("Failed to parse DATETIMEOFFSET value '{}', forwarding raw 
value.", value);
+      return value;
+    }
+  }
+
+  private ZoneId getConfiguredZoneId() {
+    return configuredZoneId == null ? ZoneOffset.UTC : configuredZoneId;
+  }
+
+  private DebeziumEngine.ConnectorCallback 
createConnectorCallback(CountDownLatch startupLatch) {
+    return new DebeziumEngine.ConnectorCallback() {
+      @Override
+      public void connectorStarted() {
+        connectorStarted = true;
+        startupLatch.countDown();
+      }
+    };
+  }
+
+  void handleEngineCompletion(boolean success,
+                              String message,
+                              Throwable error,
+                              IExtensionsLogger logger,
+                              CountDownLatch startupLatch) {
+    if (error != null) {
+      this.engineFailure = error;
+    } else if (!success) {
+      this.engineFailure = new AdapterException(
+          "Debezium engine stopped unexpectedly" + (message == null || 
message.isBlank() ? "." : ": " + message)
+      );
+    }
+
+    if (startupLatch != null) {
+      startupLatch.countDown();
+    }
+
+    if (stopRequested) {
+      return;
+    }
+
+    if (engineFailure != null) {
+      logger.error("Debezium engine failed for MSSQL CDC adapter.", 
asException(engineFailure));
+    } else if (success) {
+      logger.warn("Debezium Engine Stopped", "The embedded Debezium engine 
stopped unexpectedly.");
+    } else {
+      logger.warn(
+          "Debezium Engine Stopped",
+          message == null || message.isBlank() ? "The embedded Debezium engine 
stopped unexpectedly." : message
+      );
+    }
+  }
+
+  private void awaitStartup(CountDownLatch startupLatch, IExtensionsLogger 
logger) throws AdapterException {
+    try {
+      boolean completed = startupLatch.await(ENGINE_STARTUP_TIMEOUT_SECONDS, 
TimeUnit.SECONDS);
+      if (engineFailure != null) {
+        throw new AdapterException("Failed to start Debezium engine: " + 
engineFailure.getMessage(), engineFailure);
+      }
+
+      if (!completed || !connectorStarted) {
+        logger.warn(
+            "Debezium Startup Delayed",
+            "The embedded Debezium engine did not confirm startup within "
+                + ENGINE_STARTUP_TIMEOUT_SECONDS + " seconds."
+        );
+      }
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw new AdapterException("Interrupted while waiting for Debezium 
engine startup.", e);
+    }
+  }
+
+  private Exception asException(Throwable throwable) {
+    if (throwable instanceof Exception exception) {
+      return exception;
+    }
+
+    return new AdapterException(throwable.getMessage(), throwable);
+  }
+}
diff --git 
a/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapterConfig.java
 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapterConfig.java
new file mode 100644
index 0000000000..3d1374067a
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapterConfig.java
@@ -0,0 +1,143 @@
+/*
+ * 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.extensions.connectors.cdc.adapter.mssql;
+
+import 
org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.model.staticproperty.Option;
+import 
org.apache.streampipes.model.staticproperty.RuntimeResolvableOneOfStaticProperty;
+
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
+public class MsSqlCdcAdapterConfig {
+
+  public static final String HOST_KEY = "host";
+  public static final String PORT_KEY = "port";
+  public static final String DATABASE_KEY = "database";
+  public static final String USERNAME_KEY = "username";
+  public static final String PASSWORD_KEY = "password";
+  public static final String TABLE_KEY = "table";
+  public static final String ENCRYPT_KEY = "encrypt";
+  public static final String TRUST_SERVER_CERTIFICATE_KEY = 
"trust-server-certificate";
+  public static final String TIMEZONE_KEY = "timezone";
+
+  private final String host;
+  private final Integer port;
+  private final String database;
+  private final String username;
+  private final String password;
+  private final String table;
+  private final Boolean encrypt;
+  private final Boolean trustServerCertificate;
+  private final String timezone;
+
+  public MsSqlCdcAdapterConfig(String host,
+                               Integer port,
+                               String database,
+                               String username,
+                               String password,
+                               String table,
+                               Boolean encrypt,
+                               Boolean trustServerCertificate,
+                               String timezone) {
+    this.host = host;
+    this.port = port;
+    this.database = database;
+    this.username = username;
+    this.password = password;
+    this.table = table;
+    this.encrypt = encrypt;
+    this.trustServerCertificate = trustServerCertificate;
+    this.timezone = timezone;
+  }
+
+  public static MsSqlCdcAdapterConfig from(IStaticPropertyExtractor extractor) 
{
+    return from(extractor, true);
+  }
+
+  public static MsSqlCdcAdapterConfig from(IStaticPropertyExtractor extractor, 
boolean requireTableSelection) {
+    return new MsSqlCdcAdapterConfig(
+        extractor.singleValueParameter(HOST_KEY, String.class),
+        extractor.singleValueParameter(PORT_KEY, Integer.class),
+        extractor.singleValueParameter(DATABASE_KEY, String.class),
+        extractor.singleValueParameter(USERNAME_KEY, String.class),
+        extractor.secretValue(PASSWORD_KEY),
+        extractTable(extractor, requireTableSelection),
+        extractor.slideToggleValue(ENCRYPT_KEY),
+        extractor.slideToggleValue(TRUST_SERVER_CERTIFICATE_KEY),
+        extractor.singleValueParameter(TIMEZONE_KEY, String.class)
+    );
+  }
+
+  private static String extractTable(IStaticPropertyExtractor extractor, 
boolean requireTableSelection) {
+    if (requireTableSelection) {
+      return extractor.selectedSingleValue(TABLE_KEY, String.class);
+    }
+
+    RuntimeResolvableOneOfStaticProperty tableProperty =
+        extractor.getStaticPropertyByName(TABLE_KEY, 
RuntimeResolvableOneOfStaticProperty.class);
+
+    return tableProperty.getOptions()
+        .stream()
+        .filter(Option::isSelected)
+        .findFirst()
+        .map(Option::getName)
+        .orElse(null);
+  }
+
+  public String getHost() {
+    return host;
+  }
+
+  public Integer getPort() {
+    return port;
+  }
+
+  public String getDatabase() {
+    return database;
+  }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public String getTable() {
+    return table;
+  }
+
+  public Boolean getEncrypt() {
+    return encrypt;
+  }
+
+  public Boolean getTrustServerCertificate() {
+    return trustServerCertificate;
+  }
+
+  public String getTimezone() {
+    return timezone;
+  }
+
+  public ZoneId getZoneId() {
+    return timezone == null || timezone.isBlank() ? ZoneOffset.UTC : 
ZoneId.of(timezone);
+  }
+}
diff --git 
a/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlMetadataClient.java
 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlMetadataClient.java
new file mode 100644
index 0000000000..c2d581a5f0
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlMetadataClient.java
@@ -0,0 +1,404 @@
+/*
+ * 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.extensions.connectors.cdc.adapter.mssql;
+
+import org.apache.streampipes.commons.exceptions.SpConfigurationException;
+import org.apache.streampipes.commons.exceptions.connect.AdapterException;
+import org.apache.streampipes.model.staticproperty.Option;
+
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Date;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class MsSqlMetadataClient {
+
+  private static final String CDC_ENABLED_QUERY =
+      "SELECT is_cdc_enabled FROM sys.databases WHERE name = DB_NAME()";
+
+  private static final String CDC_TABLES_QUERY =
+      "SELECT s.name AS schema_name, t.name AS table_name "
+          + "FROM sys.tables t "
+          + "JOIN sys.schemas s ON t.schema_id = s.schema_id "
+          + "WHERE t.is_ms_shipped = 0 AND t.is_tracked_by_cdc = 1 "
+          + "ORDER BY s.name, t.name";
+
+  private static final String SAMPLE_ROW_QUERY_TEMPLATE = "SELECT TOP 1 * FROM 
[%s].[%s]";
+
+  public List<Option> discoverTables(MsSqlCdcAdapterConfig config) throws 
SpConfigurationException {
+    try (Connection connection = openConnection(config)) {
+      validateCdcEnabled(connection);
+
+      List<Option> options = new ArrayList<>();
+      try (PreparedStatement statement = 
connection.prepareStatement(CDC_TABLES_QUERY);
+           ResultSet resultSet = statement.executeQuery()) {
+        while (resultSet.next()) {
+          options.add(new Option(resultSet.getString("schema_name") + "." + 
resultSet.getString("table_name")));
+        }
+      }
+
+      if (options.isEmpty()) {
+        throw new SpConfigurationException("No CDC-enabled tables found in 
database " + config.getDatabase());
+      }
+
+      return options;
+    } catch (SQLException e) {
+      throw new SpConfigurationException("Failed to resolve SQL Server tables: 
" + e.getMessage(), e);
+    }
+  }
+
+  public void validateSelectedTable(MsSqlCdcAdapterConfig config) throws 
AdapterException {
+    try (Connection connection = openConnection(config)) {
+      validateCdcEnabled(connection);
+
+      List<Option> options = discoverTables(config);
+      boolean containsTable = options.stream().anyMatch(option -> 
option.getName().equals(config.getTable()));
+      if (!containsTable) {
+        throw new AdapterException("Selected table is not CDC-enabled or does 
not exist: " + config.getTable());
+      }
+    } catch (SQLException e) {
+      throw new AdapterException("Failed to validate SQL Server configuration: 
" + e.getMessage(), e);
+    } catch (SpConfigurationException e) {
+      throw new AdapterException(e.getMessage(), e);
+    }
+  }
+
+  public Map<String, Object> generateSample(MsSqlCdcAdapterConfig config) 
throws AdapterException {
+    TableRef tableRef = parseTable(config.getTable());
+
+    try (Connection connection = openConnection(config)) {
+      validateCdcEnabled(connection);
+
+      Map<String, ColumnDescriptor> columns = describeColumns(connection, 
tableRef);
+      ZoneId zoneId = config.getZoneId();
+      Map<String, Object> sample = fetchRealSampleRow(connection, tableRef, 
columns, zoneId);
+
+      if (sample == null) {
+        sample = new LinkedHashMap<>();
+        for (Map.Entry<String, ColumnDescriptor> entry : columns.entrySet()) {
+          sample.put(entry.getKey(), placeholderForType(entry.getValue(), 
zoneId));
+        }
+      }
+
+      if (sample.isEmpty()) {
+        throw new AdapterException("No columns found for table " + 
config.getTable());
+      }
+
+      return sample;
+    } catch (SQLException | SpConfigurationException e) {
+      throw new AdapterException("Failed to generate sample data: " + 
e.getMessage(), e);
+    }
+  }
+
+  public Connection openConnection(MsSqlCdcAdapterConfig config) throws 
SQLException {
+    String url = "jdbc:sqlserver://" + config.getHost() + ":" + 
config.getPort()
+        + ";databaseName=" + config.getDatabase()
+        + ";encrypt=" + config.getEncrypt()
+        + ";trustServerCertificate=" + config.getTrustServerCertificate();
+
+    return DriverManager.getConnection(url, config.getUsername(), 
config.getPassword());
+  }
+
+  public Map<String, TemporalColumnMode> 
describeTemporalColumns(MsSqlCdcAdapterConfig config) throws AdapterException {
+    TableRef tableRef = parseTable(config.getTable());
+
+    try (Connection connection = openConnection(config)) {
+      validateCdcEnabled(connection);
+
+      Map<String, TemporalColumnMode> temporalColumns = new LinkedHashMap<>();
+      Map<String, ColumnDescriptor> columns = describeColumns(connection, 
tableRef);
+      columns.forEach((columnName, descriptor) -> {
+        TemporalColumnMode temporalMode = getTemporalColumnMode(descriptor);
+        if (temporalMode != null) {
+          temporalColumns.put(columnName, temporalMode);
+        }
+      });
+      return temporalColumns;
+    } catch (SQLException | SpConfigurationException e) {
+      throw new AdapterException("Failed to inspect SQL Server table metadata: 
" + e.getMessage(), e);
+    }
+  }
+
+  private void validateCdcEnabled(Connection connection) throws SQLException, 
SpConfigurationException {
+    try (PreparedStatement statement = 
connection.prepareStatement(CDC_ENABLED_QUERY);
+         ResultSet resultSet = statement.executeQuery()) {
+      if (!resultSet.next() || !resultSet.getBoolean(1)) {
+        throw new SpConfigurationException("CDC is not enabled on database " + 
connection.getCatalog());
+      }
+    }
+  }
+
+  private TableRef parseTable(String table) throws AdapterException {
+    if (table == null || table.isBlank()) {
+      throw new AdapterException("No table selected.");
+    }
+
+    String[] tokens = table.split("\\.", 2);
+    if (tokens.length != 2) {
+      throw new AdapterException("Selected table must be of the form 
schema.table: " + table);
+    }
+
+    return new TableRef(tokens[0], tokens[1]);
+  }
+
+  private Map<String, ColumnDescriptor> describeColumns(Connection connection,
+                                                        TableRef tableRef) 
throws SQLException {
+    DatabaseMetaData metaData = connection.getMetaData();
+    Map<String, ColumnDescriptor> columns = new LinkedHashMap<>();
+    try (ResultSet resultSet = metaData.getColumns(null, tableRef.schema(), 
tableRef.table(), null)) {
+      while (resultSet.next()) {
+        String columnName = resultSet.getString("COLUMN_NAME");
+        int jdbcType = resultSet.getInt("DATA_TYPE");
+        String typeName = resultSet.getString("TYPE_NAME");
+        int decimalDigits = resultSet.getInt("DECIMAL_DIGITS");
+        columns.put(columnName, new ColumnDescriptor(columnName, jdbcType, 
typeName, decimalDigits));
+      }
+    }
+    return columns;
+  }
+
+  private Map<String, Object> fetchRealSampleRow(Connection connection,
+                                                 TableRef tableRef,
+                                                 Map<String, ColumnDescriptor> 
columns,
+                                                 ZoneId zoneId) throws 
SQLException {
+    String query = SAMPLE_ROW_QUERY_TEMPLATE.formatted(
+        escapeIdentifier(tableRef.schema()),
+        escapeIdentifier(tableRef.table())
+    );
+
+    try (PreparedStatement statement = connection.prepareStatement(query);
+         ResultSet resultSet = statement.executeQuery()) {
+      if (!resultSet.next()) {
+        return null;
+      }
+
+      ResultSetMetaData metaData = resultSet.getMetaData();
+      Map<String, Object> sample = new LinkedHashMap<>();
+      for (int columnIndex = 1; columnIndex <= metaData.getColumnCount(); 
columnIndex++) {
+        String columnName = metaData.getColumnLabel(columnIndex);
+        ColumnDescriptor descriptor = columns.getOrDefault(
+            columnName,
+            new ColumnDescriptor(columnName, 
metaData.getColumnType(columnIndex), metaData.getColumnTypeName(columnIndex), 0)
+        );
+        Object rawValue = resultSet.getObject(columnIndex);
+        sample.put(columnName, sampleValue(rawValue, descriptor, zoneId));
+      }
+
+      return sample;
+    }
+  }
+
+  private Object placeholderForType(ColumnDescriptor descriptor, ZoneId 
zoneId) {
+    TemporalColumnMode temporalMode = getTemporalColumnMode(descriptor);
+    if (temporalMode != null) {
+      return (double) sampleTemporalValue(temporalMode, zoneId);
+    }
+
+    return switch (descriptor.jdbcType()) {
+      case Types.BIGINT, Types.INTEGER, Types.SMALLINT, Types.TINYINT -> 1;
+      case Types.DECIMAL, Types.DOUBLE, Types.FLOAT, Types.NUMERIC, Types.REAL 
-> 1.0;
+      case Types.BIT, Types.BOOLEAN -> true;
+      case Types.BINARY, Types.LONGVARBINARY, Types.VARBINARY -> "AQID";
+      default -> "example";
+    };
+  }
+
+  private Object sampleValue(Object rawValue, ColumnDescriptor descriptor, 
ZoneId zoneId) {
+    if (rawValue == null) {
+      return placeholderForType(descriptor, zoneId);
+    }
+
+    TemporalColumnMode temporalMode = getTemporalColumnMode(descriptor);
+    if (temporalMode != null) {
+      return (double) temporalValue(rawValue, temporalMode, zoneId);
+    }
+
+    if (rawValue instanceof BigDecimal decimalValue) {
+      return decimalValue.doubleValue();
+    }
+
+    if (rawValue instanceof byte[] bytes) {
+      return Base64.getEncoder().encodeToString(bytes);
+    }
+
+    return rawValue;
+  }
+
+  private TemporalColumnMode getTemporalColumnMode(ColumnDescriptor 
descriptor) {
+    String normalizedTypeName = descriptor.typeName() == null
+        ? ""
+        : descriptor.typeName().toLowerCase(Locale.ENGLISH);
+
+    return switch (normalizedTypeName) {
+      case "date" -> TemporalColumnMode.DATE_EPOCH_MILLIS;
+      case "time" -> {
+        if (descriptor.decimalDigits() <= 3) {
+          yield TemporalColumnMode.TIME_MILLIS;
+        } else if (descriptor.decimalDigits() <= 6) {
+          yield TemporalColumnMode.TIME_MICROS;
+        } else {
+          yield TemporalColumnMode.TIME_NANOS;
+        }
+      }
+      case "datetime", "smalldatetime" -> TemporalColumnMode.TIMESTAMP_MILLIS;
+      case "datetime2" -> {
+        if (descriptor.decimalDigits() <= 3) {
+          yield TemporalColumnMode.TIMESTAMP_MILLIS;
+        } else if (descriptor.decimalDigits() <= 6) {
+          yield TemporalColumnMode.TIMESTAMP_MICROS;
+        } else {
+          yield TemporalColumnMode.TIMESTAMP_NANOS;
+        }
+      }
+      case "datetimeoffset" -> TemporalColumnMode.DATETIMEOFFSET_MILLIS;
+      default -> {
+        if (descriptor.jdbcType() == Types.DATE) {
+          yield TemporalColumnMode.DATE_EPOCH_MILLIS;
+        } else if (descriptor.jdbcType() == Types.TIME || 
descriptor.jdbcType() == Types.TIME_WITH_TIMEZONE) {
+          yield TemporalColumnMode.TIME_MILLIS;
+        } else if (descriptor.jdbcType() == Types.TIMESTAMP
+            || descriptor.jdbcType() == Types.TIMESTAMP_WITH_TIMEZONE) {
+          yield TemporalColumnMode.TIMESTAMP_MILLIS;
+        }
+        yield null;
+      }
+    };
+  }
+
+  private Object sampleTemporalValue(TemporalColumnMode temporalMode, ZoneId 
zoneId) {
+    return switch (temporalMode) {
+      case DATE_EPOCH_MILLIS ->
+          LocalDate.of(2026, 5, 
7).atStartOfDay(zoneId).toInstant().toEpochMilli();
+      case TIME_MILLIS, TIME_MICROS, TIME_NANOS ->
+          LocalTime.of(10, 15, 30, 123_000_000).toNanoOfDay() / 1_000_000;
+      case TIMESTAMP_MILLIS, TIMESTAMP_MICROS, TIMESTAMP_NANOS, 
DATETIMEOFFSET_MILLIS ->
+          LocalDate.of(2026, 5, 7)
+              .atTime(10, 15, 30, 123_000_000)
+              .atZone(zoneId)
+              .toInstant()
+              .toEpochMilli();
+    };
+  }
+
+  private long temporalValue(Object rawValue, TemporalColumnMode temporalMode, 
ZoneId zoneId) {
+    return switch (temporalMode) {
+      case DATE_EPOCH_MILLIS -> toDateEpochMillis(rawValue, zoneId);
+      case TIME_MILLIS, TIME_MICROS, TIME_NANOS -> toTimeMillis(rawValue);
+      case TIMESTAMP_MILLIS, TIMESTAMP_MICROS, TIMESTAMP_NANOS -> 
toTimestampEpochMillis(rawValue, zoneId);
+      case DATETIMEOFFSET_MILLIS -> toDateTimeOffsetEpochMillis(rawValue, 
zoneId);
+    };
+  }
+
+  private long toDateEpochMillis(Object rawValue, ZoneId zoneId) {
+    if (rawValue instanceof Date dateValue) {
+      return 
dateValue.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli();
+    } else if (rawValue instanceof LocalDate localDate) {
+      return localDate.atStartOfDay(zoneId).toInstant().toEpochMilli();
+    }
+
+    return 
LocalDate.parse(rawValue.toString()).atStartOfDay(zoneId).toInstant().toEpochMilli();
+  }
+
+  private long toTimeMillis(Object rawValue) {
+    LocalTime localTime;
+    if (rawValue instanceof Time timeValue) {
+      localTime = timeValue.toLocalTime();
+    } else if (rawValue instanceof LocalTime parsedLocalTime) {
+      localTime = parsedLocalTime;
+    } else {
+      localTime = LocalTime.parse(rawValue.toString());
+    }
+
+    return localTime.toNanoOfDay() / 1_000_000;
+  }
+
+  private long toTimestampEpochMillis(Object rawValue, ZoneId zoneId) {
+    if (rawValue instanceof Timestamp timestampValue) {
+      return 
timestampValue.toLocalDateTime().atZone(zoneId).toInstant().toEpochMilli();
+    } else if (rawValue instanceof LocalDateTime localDateTime) {
+      return localDateTime.atZone(zoneId).toInstant().toEpochMilli();
+    } else if (rawValue instanceof Instant instant) {
+      return instant.toEpochMilli();
+    }
+
+    String stringValue = rawValue.toString();
+    try {
+      return Instant.parse(stringValue).toEpochMilli();
+    } catch (DateTimeParseException ignored) {
+      return LocalDateTime.parse(stringValue.replace(' ', 
'T')).atZone(zoneId).toInstant().toEpochMilli();
+    }
+  }
+
+  private long toDateTimeOffsetEpochMillis(Object rawValue, ZoneId zoneId) {
+    if (rawValue instanceof OffsetDateTime offsetDateTime) {
+      return offsetDateTime.toInstant().toEpochMilli();
+    } else if (rawValue instanceof Timestamp timestampValue) {
+      return timestampValue.toInstant().toEpochMilli();
+    }
+
+    String stringValue = rawValue.toString().replace(' ', 'T');
+    try {
+      return OffsetDateTime.parse(stringValue).toInstant().toEpochMilli();
+    } catch (DateTimeParseException ignored) {
+      return 
LocalDateTime.parse(stringValue).atZone(zoneId).toInstant().toEpochMilli();
+    }
+  }
+
+  private String escapeIdentifier(String identifier) {
+    return identifier.replace("]", "]]");
+  }
+
+  private record TableRef(String schema, String table) {
+  }
+
+  public enum TemporalColumnMode {
+    DATE_EPOCH_MILLIS,
+    TIME_MILLIS,
+    TIME_MICROS,
+    TIME_NANOS,
+    TIMESTAMP_MILLIS,
+    TIMESTAMP_MICROS,
+    TIMESTAMP_NANOS,
+    DATETIMEOFFSET_MILLIS
+  }
+
+  private record ColumnDescriptor(String columnName, int jdbcType, String 
typeName, int decimalDigits) {
+  }
+}
diff --git 
a/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/documentation.md
 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/documentation.md
new file mode 100644
index 0000000000..1566a3bea1
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/documentation.md
@@ -0,0 +1,142 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+## Microsoft SQL Server CDC Adapter
+
+***
+
+## Description
+
+This adapter uses the embedded Debezium Engine to capture change events from a 
CDC-enabled Microsoft SQL Server table.
+
+Current behavior:
+
+* only newly inserted rows are emitted
+* updates and deletes are ignored
+* the output event is flat and contains only the row fields
+* Debezium metadata is not included in emitted events
+* adapter state is kept in memory only
+
+This means the adapter starts at the current log position and does not read 
old rows when it starts.
+If the adapter is stopped, its position is lost and rows written while it is 
down can be missed.
+
+***
+
+## Required Input
+
+None.
+
+***
+
+## SQL Server Prerequisites
+
+The adapter expects the following SQL Server setup:
+
+* CDC is enabled on the selected database
+* CDC is enabled on the selected table
+* SQL Server Agent is running
+* the configured SQL Server user can access the database metadata and captured 
CDC data
+
+The configuration dialog only lists CDC-enabled tables from the selected 
database.
+
+***
+
+## Configuration
+
+### Host
+
+The SQL Server host name or IP address.
+
+### Port
+
+The SQL Server TCP port, for example `1433`.
+
+### Database
+
+The name of the CDC-enabled SQL Server database.
+
+### Username / Password
+
+Credentials used to connect to SQL Server.
+
+### Encrypt Connection
+
+Enable TLS for the SQL Server connection.
+
+### Trust Server Certificate
+
+Trust the SQL Server certificate without validating its chain.
+This is useful for local or test environments with self-signed certificates.
+
+### Timezone
+
+Timezone used for SQL Server temporal types without embedded timezone 
information, such as `DATETIME` and `DATETIME2`.
+
+Use an IANA timezone ID such as:
+
+* `UTC`
+* `Europe/Berlin`
+* `America/New_York`
+
+The default is `UTC`.
+
+### Table
+
+Select one CDC-enabled table from the configured database.
+
+***
+
+## Output Behavior
+
+Each insert event produces one StreamPipes event.
+
+Example output:
+
+```json
+{
+  "id": 42,
+  "temperature": 23.5,
+  "pressure": 1007.2,
+  "createdAt": 1778154600000
+}
+```
+
+Type handling:
+
+* `DECIMAL` and `NUMERIC` values are emitted as JSON numbers
+* `DATE`, `DATETIME`, `SMALLDATETIME`, `DATETIME2`, and `DATETIMEOFFSET` 
values are normalized to epoch milliseconds
+* `TIME` values are normalized to milliseconds past midnight
+* binary values are emitted as Base64 strings
+
+Runtime polling:
+
+* Debezium polling is controlled through the environment variable 
`SP_CDC_MSSQL_POLL_INTERVAL_MS`
+* the default polling interval is `1000` milliseconds
+
+***
+
+## Limitations
+
+Current limitations:
+
+* single-table capture only
+* insert events only
+* no snapshot of existing rows
+* no offset persistence across adapter restarts
+* no schema-history persistence across adapter restarts
+* timestamp semantics for downstream sinks still depend on the field selection 
in StreamPipes
diff --git 
a/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/icon.png
 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/icon.png
new file mode 100644
index 0000000000..0a6ff414b7
Binary files /dev/null and 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/icon.png
 differ
diff --git 
a/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/strings.en
 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/strings.en
new file mode 100644
index 0000000000..a54beb12af
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-cdc/src/main/resources/org.apache.streampipes.connect.cdc.adapter.mssql/strings.en
@@ -0,0 +1,47 @@
+#
+# 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.
+#
+
+
+org.apache.streampipes.connect.cdc.adapter.mssql.title=Microsoft SQL Server CDC
+org.apache.streampipes.connect.cdc.adapter.mssql.description=Captures newly 
inserted rows from a CDC-enabled Microsoft SQL Server table
+
+host.title=Host
+host.description=SQL Server hostname or IP address
+
+port.title=Port
+port.description=SQL Server TCP port
+
+database.title=Database
+database.description=Name of the CDC-enabled SQL Server database
+
+username.title=Username
+username.description=SQL Server user name
+
+password.title=Password
+password.description=SQL Server password
+
+encrypt.title=Encrypt Connection
+encrypt.description=Use TLS when connecting to SQL Server
+
+trust-server-certificate.title=Trust Server Certificate
+trust-server-certificate.description=Trust the SQL Server certificate without 
validating its chain
+
+timezone.title=Timezone
+timezone.description=Timezone to assume for SQL Server DATETIME and DATETIME2 
values without timezone information, for example Europe/Berlin. Defaults to UTC.
+
+table.title=Table
+table.description=Select a CDC-enabled table from the configured database
diff --git 
a/streampipes-extensions/streampipes-connectors-cdc/src/test/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapterTest.java
 
b/streampipes-extensions/streampipes-connectors-cdc/src/test/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapterTest.java
new file mode 100644
index 0000000000..90b2a015eb
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-cdc/src/test/java/org/apache/streampipes/extensions/connectors/cdc/adapter/mssql/MsSqlCdcAdapterTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.extensions.connectors.cdc.adapter.mssql;
+
+import org.apache.streampipes.extensions.api.monitoring.IExtensionsLogger;
+import org.apache.streampipes.model.monitoring.SpLogMessage;
+import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
+import org.apache.streampipes.model.staticproperty.Option;
+import 
org.apache.streampipes.model.staticproperty.RuntimeResolvableOneOfStaticProperty;
+import org.apache.streampipes.model.staticproperty.SecretStaticProperty;
+import org.apache.streampipes.model.staticproperty.SlideToggleStaticProperty;
+import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
+
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class MsSqlCdcAdapterTest {
+
+  @Test
+  void configExtractionAllowsMissingTableDuringRuntimeResolution() {
+    RuntimeResolvableOneOfStaticProperty tableProperty =
+        new 
RuntimeResolvableOneOfStaticProperty(MsSqlCdcAdapterConfig.TABLE_KEY, "", "");
+    tableProperty.setOptions(List.of(new Option("dbo.Measurements")));
+
+    MsSqlCdcAdapterConfig config = MsSqlCdcAdapterConfig.from(
+        StaticPropertyExtractor.from(List.of(
+            textProperty(MsSqlCdcAdapterConfig.HOST_KEY, "localhost"),
+            textProperty(MsSqlCdcAdapterConfig.PORT_KEY, "1433"),
+            textProperty(MsSqlCdcAdapterConfig.DATABASE_KEY, "testdb"),
+            textProperty(MsSqlCdcAdapterConfig.USERNAME_KEY, "sa"),
+            secretProperty(MsSqlCdcAdapterConfig.PASSWORD_KEY, "secret"),
+            toggleProperty(MsSqlCdcAdapterConfig.ENCRYPT_KEY, true),
+            toggleProperty(MsSqlCdcAdapterConfig.TRUST_SERVER_CERTIFICATE_KEY, 
false),
+            textProperty(MsSqlCdcAdapterConfig.TIMEZONE_KEY, "UTC"),
+            tableProperty
+        )),
+        false
+    );
+
+    assertNull(config.getTable());
+  }
+
+  @Test
+  void buildDebeziumPropertiesUsesDecimalHandlingAndElementId() {
+    MsSqlCdcAdapter adapter = new MsSqlCdcAdapter();
+    MsSqlCdcAdapterConfig config = new MsSqlCdcAdapterConfig(
+        "localhost",
+        1433,
+        "testdb",
+        "sa",
+        "secret",
+        "dbo.Measurements",
+        true,
+        false,
+        "UTC"
+    );
+
+    Properties properties = adapter.buildDebeziumProperties(config, 
"adapter-42");
+
+    assertEquals("double", properties.getProperty("decimal.handling.mode"));
+    assertEquals("1000", properties.getProperty("poll.interval.ms"));
+    assertEquals("testdb_dbo_measurements_adapter_42", 
properties.getProperty("topic.prefix"));
+    assertEquals("testdb_dbo_measurements_adapter_42-engine", 
properties.getProperty("name"));
+  }
+
+  @Test
+  void timestampNormalizationUsesConfiguredTimezone() throws Exception {
+    MsSqlCdcAdapter adapter = new MsSqlCdcAdapter();
+    Field configuredZoneId = 
MsSqlCdcAdapter.class.getDeclaredField("configuredZoneId");
+    configuredZoneId.setAccessible(true);
+    configuredZoneId.set(adapter, ZoneId.of("Europe/Berlin"));
+
+    Object normalized = adapter.normalizeTemporalValue(
+        1_778_161_800_000L,
+        MsSqlMetadataClient.TemporalColumnMode.TIMESTAMP_MILLIS
+    );
+
+    long expected = LocalDateTime.of(2026, 5, 7, 13, 50)
+        .atZone(ZoneId.of("Europe/Berlin"))
+        .toInstant()
+        .toEpochMilli();
+
+    assertEquals(expected, normalized);
+  }
+
+  @Test
+  void engineCompletionReportsFailuresToRuntimeLogger() throws Exception {
+    MsSqlCdcAdapter adapter = new MsSqlCdcAdapter();
+    TestLogger logger = new TestLogger();
+    RuntimeException cause = new RuntimeException("boom");
+
+    adapter.handleEngineCompletion(false, "connector failed", cause, logger, 
new CountDownLatch(1));
+
+    Field engineFailure = 
MsSqlCdcAdapter.class.getDeclaredField("engineFailure");
+    engineFailure.setAccessible(true);
+
+    assertNotNull(engineFailure.get(adapter));
+    assertEquals("Debezium engine failed for MSSQL CDC adapter.", 
logger.errorDetails);
+    assertEquals(cause, logger.errorException);
+  }
+
+  private static FreeTextStaticProperty textProperty(String internalName, 
String value) {
+    FreeTextStaticProperty property = FreeTextStaticProperty.of(internalName, 
value);
+    property.setInternalName(internalName);
+    return property;
+  }
+
+  private static SecretStaticProperty secretProperty(String internalName, 
String value) {
+    SecretStaticProperty property = new SecretStaticProperty(internalName, "", 
"");
+    property.setValue(value);
+    return property;
+  }
+
+  private static SlideToggleStaticProperty toggleProperty(String internalName, 
boolean selected) {
+    SlideToggleStaticProperty property = new 
SlideToggleStaticProperty(internalName, "", "", selected);
+    property.setSelected(selected);
+    return property;
+  }
+
+  private static class TestLogger implements IExtensionsLogger {
+    private String errorDetails;
+    private Exception errorException;
+
+    @Override
+    public void log(SpLogMessage logMessage) {
+    }
+
+    @Override
+    public void error(Exception e) {
+      this.errorException = e;
+    }
+
+    @Override
+    public void error(String details, Exception e) {
+      this.errorDetails = details;
+      this.errorException = e;
+    }
+
+    @Override
+    public void info(String title, String details) {
+    }
+
+    @Override
+    public void warn(String title, String details) {
+    }
+  }
+}
diff --git a/streampipes-extensions/streampipes-extensions-all-iiot/pom.xml 
b/streampipes-extensions/streampipes-extensions-all-iiot/pom.xml
index 47e559cda0..eddbe63a50 100644
--- a/streampipes-extensions/streampipes-extensions-all-iiot/pom.xml
+++ b/streampipes-extensions/streampipes-extensions-all-iiot/pom.xml
@@ -202,6 +202,12 @@
             <version>0.99.0-SNAPSHOT</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-connectors-cdc</artifactId>
+            <version>0.99.0-SNAPSHOT</version>
+        </dependency>
+
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk18on</artifactId>
diff --git 
a/streampipes-extensions/streampipes-extensions-all-iiot/src/main/java/org/apache/streampipes/extensions/all/iiot/AllExtensionsIIoTInit.java
 
b/streampipes-extensions/streampipes-extensions-all-iiot/src/main/java/org/apache/streampipes/extensions/all/iiot/AllExtensionsIIoTInit.java
index 179f9b4ede..b82ec7630e 100644
--- 
a/streampipes-extensions/streampipes-extensions-all-iiot/src/main/java/org/apache/streampipes/extensions/all/iiot/AllExtensionsIIoTInit.java
+++ 
b/streampipes-extensions/streampipes-extensions-all-iiot/src/main/java/org/apache/streampipes/extensions/all/iiot/AllExtensionsIIoTInit.java
@@ -20,6 +20,7 @@ package org.apache.streampipes.extensions.all.iiot;
 
 import org.apache.streampipes.connect.iiot.IIoTAdaptersExtensionModuleExport;
 import org.apache.streampipes.connectors.ros.RosConnectorsModuleExport;
+import 
org.apache.streampipes.extensions.connectors.cdc.CdcConnectorsModuleExport;
 import 
org.apache.streampipes.extensions.connectors.filewatcher.FileWatcherConnectorsModuleExport;
 import 
org.apache.streampipes.extensions.connectors.influx.InfluxConnectorsModuleExport;
 import 
org.apache.streampipes.extensions.connectors.kafka.KafkaConnectorsModuleExport;
@@ -87,7 +88,8 @@ public class AllExtensionsIIoTInit extends 
StreamPipesExtensionsServiceBase {
             new DatabaseSinksExtensionModuleExport(),
             new InternalSinksExtensionModuleExports(),
             new NotificationsExtensionModuleExport(),
-            new LlmExtensionModuleExport()
+            new LlmExtensionModuleExport(),
+            new CdcConnectorsModuleExport()
         )
         .registerRuntimeProvider(new StandaloneStreamPipesRuntimeProvider())
         .registerMessagingProtocols(
diff --git a/streampipes-extensions/streampipes-extensions-all-jvm/pom.xml 
b/streampipes-extensions/streampipes-extensions-all-jvm/pom.xml
index a165ea8b03..1368085007 100644
--- a/streampipes-extensions/streampipes-extensions-all-jvm/pom.xml
+++ b/streampipes-extensions/streampipes-extensions-all-jvm/pom.xml
@@ -79,6 +79,11 @@
             <artifactId>streampipes-connectors-filewatcher</artifactId>
             <version>0.99.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-connectors-cdc</artifactId>
+            <version>0.99.0-SNAPSHOT</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.streampipes</groupId>
             <artifactId>streampipes-connectors-influx</artifactId>
diff --git 
a/streampipes-extensions/streampipes-extensions-all-jvm/src/main/java/org/apache/streampipes/extensions/all/jvm/AllExtensionsInit.java
 
b/streampipes-extensions/streampipes-extensions-all-jvm/src/main/java/org/apache/streampipes/extensions/all/jvm/AllExtensionsInit.java
index ef031f35f3..689724dac2 100644
--- 
a/streampipes-extensions/streampipes-extensions-all-jvm/src/main/java/org/apache/streampipes/extensions/all/jvm/AllExtensionsInit.java
+++ 
b/streampipes-extensions/streampipes-extensions-all-jvm/src/main/java/org/apache/streampipes/extensions/all/jvm/AllExtensionsInit.java
@@ -21,6 +21,7 @@ import 
org.apache.streampipes.connect.GeneralAdaptersExtensionModuleExport;
 import org.apache.streampipes.connect.iiot.IIoTAdaptersExtensionModuleExport;
 import org.apache.streampipes.connectors.ros.RosConnectorsModuleExport;
 import 
org.apache.streampipes.extensions.connectors.camel.azure.CamelAzureConnectorsModuleExport;
+import 
org.apache.streampipes.extensions.connectors.cdc.CdcConnectorsModuleExport;
 import 
org.apache.streampipes.extensions.connectors.filewatcher.FileWatcherConnectorsModuleExport;
 import 
org.apache.streampipes.extensions.connectors.influx.InfluxConnectorsModuleExport;
 import 
org.apache.streampipes.extensions.connectors.kafka.KafkaConnectorsModuleExport;
@@ -71,6 +72,7 @@ public class AllExtensionsInit extends 
StreamPipesExtensionsServiceBase {
 
             new CamelAzureConnectorsModuleExport(),
             new FileWatcherConnectorsModuleExport(),
+            new CdcConnectorsModuleExport(),
             new InfluxConnectorsModuleExport(),
             new KafkaConnectorsModuleExport(),
             new MqttConnectorsModuleExport(),
diff --git 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.ts
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.ts
index 2dbb534d10..203439b179 100644
--- 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.ts
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.ts
@@ -79,6 +79,7 @@ export class StaticRuntimeResolvableOneOfInputComponent
             }
         }
         this.staticProperty.options = staticProperty.options;
+        this.performValidation();
     }
 
     isOptionSelected(): boolean {

Reply via email to