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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerinax"
name = "mssql"
version = "1.16.2"
version = "1.16.3"
authors = ["Ballerina"]
keywords = ["client", "network", "SQL", "RDBMS", "SQLServer", "MSSQL", "Vendor/Microsoft", "Area/Database", "Type/Connector"]
repository = "https://github.com/ballerina-platform/module-ballerinax-mssql"
Expand All @@ -15,8 +15,8 @@ graalvmCompatible = true
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "mssql-native"
version = "1.16.2"
path = "../native/build/libs/mssql-native-1.16.2.jar"
version = "1.16.3"
path = "../native/build/libs/mssql-native-1.16.3-SNAPSHOT.jar"

[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
Expand Down
2 changes: 1 addition & 1 deletion ballerina/CompilerPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ id = "mssql-compiler-plugin"
class = "io.ballerina.stdlib.mssql.compiler.MSSQLCompilerPlugin"

[[dependency]]
path = "../compiler-plugin/build/libs/mssql-compiler-plugin-1.16.2.jar"
path = "../compiler-plugin/build/libs/mssql-compiler-plugin-1.16.3-SNAPSHOT.jar"
74 changes: 57 additions & 17 deletions ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ dependencies = [
{org = "ballerina", name = "log"}
]

[[package]]
org = "ballerina"
name = "avro"
version = "1.2.0"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]

[[package]]
org = "ballerina"
name = "cache"
Expand All @@ -44,7 +52,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "crypto"
version = "2.9.0"
version = "2.9.3"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "time"}
Expand All @@ -56,7 +64,7 @@ modules = [
[[package]]
org = "ballerina"
name = "data.jsondata"
version = "1.1.0"
version = "1.1.3"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.object"}
Expand All @@ -80,7 +88,7 @@ modules = [
[[package]]
org = "ballerina"
name = "http"
version = "2.14.0"
version = "2.14.9"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "auth"},
Expand Down Expand Up @@ -127,7 +135,7 @@ modules = [
[[package]]
org = "ballerina"
name = "jwt"
version = "2.15.0"
version = "2.15.1"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "cache"},
Expand All @@ -144,7 +152,6 @@ dependencies = [
org = "ballerina"
name = "lang.__internal"
version = "0.0.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.object"}
Expand Down Expand Up @@ -182,7 +189,6 @@ dependencies = [
org = "ballerina"
name = "lang.int"
version = "0.0.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.__internal"},
Expand Down Expand Up @@ -249,7 +255,6 @@ dependencies = [
org = "ballerina"
name = "log"
version = "2.12.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
Expand All @@ -260,7 +265,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "mime"
version = "2.12.0"
version = "2.12.1"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "io"},
Expand All @@ -272,7 +277,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "oauth2"
version = "2.14.0"
version = "2.14.1"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "cache"},
Expand All @@ -286,15 +291,15 @@ dependencies = [
[[package]]
org = "ballerina"
name = "observe"
version = "1.5.0"
version = "1.5.1"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]

[[package]]
org = "ballerina"
name = "os"
version = "1.10.0"
version = "1.10.1"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "io"},
Expand Down Expand Up @@ -342,7 +347,7 @@ modules = [
[[package]]
org = "ballerina"
name = "time"
version = "2.7.0"
version = "2.8.0"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
Expand All @@ -353,7 +358,7 @@ modules = [
[[package]]
org = "ballerina"
name = "url"
version = "2.6.0"
version = "2.6.1"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
Expand All @@ -363,7 +368,6 @@ dependencies = [
org = "ballerina"
name = "uuid"
version = "1.10.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "crypto"},
{org = "ballerina", name = "jballerina.java"},
Expand Down Expand Up @@ -405,21 +409,57 @@ modules = [
[[package]]
org = "ballerinax"
name = "cdc"
version = "1.0.2"
version = "1.2.0"
dependencies = [
{org = "ballerina", name = "crypto"},
{org = "ballerina", name = "data.jsondata"},
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
{org = "ballerinai", name = "observe"}
{org = "ballerina", name = "log"},
{org = "ballerinai", name = "observe"},
{org = "ballerinax", name = "kafka"}
]
modules = [
{org = "ballerinax", packageName = "cdc", moduleName = "cdc"}
]

[[package]]
org = "ballerinax"
name = "confluent.cavroserdes"
version = "1.0.2"
dependencies = [
{org = "ballerina", name = "avro"},
{org = "ballerina", name = "jballerina.java"},
{org = "ballerinai", name = "observe"},
{org = "ballerinax", name = "confluent.cregistry"}
]

[[package]]
org = "ballerinax"
name = "confluent.cregistry"
version = "0.4.3"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]

[[package]]
org = "ballerinax"
name = "kafka"
version = "4.6.3"
dependencies = [
{org = "ballerina", name = "crypto"},
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "time"},
{org = "ballerina", name = "uuid"},
{org = "ballerinai", name = "observe"},
{org = "ballerinax", name = "confluent.cavroserdes"},
{org = "ballerinax", name = "confluent.cregistry"}
]

[[package]]
org = "ballerinax"
name = "mssql"
version = "1.16.2"
version = "1.16.3"
dependencies = [
{org = "ballerina", name = "crypto"},
{org = "ballerina", name = "file"},
Expand Down
16 changes: 10 additions & 6 deletions ballerina/cdc_listener.bal
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ import ballerinax/cdc;
public isolated class CdcListener {
*cdc:Listener;

private final map<string> & readonly config;
private final map<anydata> & readonly config;
private boolean isStarted = false;
private boolean hasAttachedService = false;

# Initializes the MSSQL listener with the given configuration.
#
# + config - The configuration for the MSSQL connector
public isolated function init(*MsSqlListenerConfiguration config) {
map<string> configMap = {};
map<string> debeziumConfigs = {};
cdc:populateDebeziumProperties({
engineName: config.engineName,
offsetStorage: config.offsetStorage,
internalSchemaStorage: config.internalSchemaStorage,
options: config.options
}, configMap);
}, debeziumConfigs);
cdc:populateDatabaseConfigurations({
connectorClass: config.database.connectorClass,
hostname: config.database.hostname,
Expand All @@ -47,9 +47,13 @@ public isolated class CdcListener {
excludedTables: config.database.excludedTables,
includedColumns: config.database.includedColumns,
excludedColumns: config.database.excludedColumns
}, configMap);
populateMsSqlConfigurations(config.database, configMap);
self.config = configMap.cloneReadOnly();
}, debeziumConfigs);
populateMsSqlConfigurations(config.database, debeziumConfigs);
map<anydata> listenerConfigs = {
...debeziumConfigs
};
listenerConfigs["livenessInterval"] = config.livenessInterval;
self.config = listenerConfigs.cloneReadOnly();
Comment on lines +52 to +56
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find MsSqlListenerConfiguration definition and livenessInterval usage
rg -n "MsSqlListenerConfiguration" -g '*.bal' -C 5

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 1585


🏁 Script executed:

#!/bin/bash
# Search for livenessInterval declarations and usages
rg -n "livenessInterval" -g '*.bal' -B 2 -A 2

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 409


🏁 Script executed:

#!/bin/bash
# Check the cdc_listener.bal file around the target lines
cat -n ballerina/cdc_listener.bal | sed -n '40,70p'

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 2016


🏁 Script executed:

#!/bin/bash
# Search for cdc:ListenerConfiguration in the codebase
rg -n "ListenerConfiguration" -g '*.bal' -B 2 -A 8

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 1862


🏁 Script executed:

#!/bin/bash
# Check types.bal for the full type definitions
cat -n ballerina/types.bal

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 10105


🏁 Script executed:

#!/bin/bash
# Search for any cdc-related files in the repository
find . -name "*cdc*" -type f 2>/dev/null | head -20

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 162


🏁 Script executed:

#!/bin/bash
# Search for imports and usage of livenessInterval across the codebase
rg -n "livenessInterval" -g '*.bal' -B 3 -A 3

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 511


🏁 Script executed:

#!/bin/bash
# Check if there are any Ballerina lock files or dependencies
fd -e "lock" -e "Ballerina.lock" 2>/dev/null

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Find and read Ballerina.toml
fd -name "Ballerina.toml" -o -name "*.toml" 2>/dev/null | head -5

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Read the Ballerina.toml file
cat Ballerina.toml 2>/dev/null || cat ballerina/Ballerina.toml 2>/dev/null

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 809


🏁 Script executed:

#!/bin/bash
# Check how config parameter is passed and used - look for calls to init function
rg -n "init\(" -g '*.bal' -B 2 -A 5

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 6113


🏁 Script executed:

#!/bin/bash
# Search for test files that use MsSqlListener initialization
find . -name "*test*.bal" -o -name "*Test.bal" | head -10

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 510


🏁 Script executed:

#!/bin/bash
# Look for test files and examples
fd -name "*test*" -type f -g "*.bal" 2>/dev/null | head -20

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Search for how the listener is instantiated in tests
rg -n "new MsSqlListener" -g '*.bal' -B 3 -A 5

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Check the listener test file
cat -n ballerina/tests/listener_tests.bal | head -100

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 3957


🏁 Script executed:

#!/bin/bash
# Search for MsSqlListener in test files
rg -n "MsSqlListener" -g '*.bal' -B 2 -A 5

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 4091


🏁 Script executed:

#!/bin/bash
# Get the full cdc_listener.bal file to see complete context
cat -n ballerina/cdc_listener.bal

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 5188


🏁 Script executed:

#!/antml:thinking
Looking at the test file, I can see that the `getDummyMsSqlListener()` function creates a listener without passing `livenessInterval`. This suggests it's optional. Let me check the full init function to understand the context better.

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 154


🏁 Script executed:

#!/bin/bash
# Search for any comments or documentation about config handling
rg -n "config\[" -g '*.bal' -B 2 -A 2

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Look for patterns of guarding assignments in similar codebases
rg -n "if .* != \(\)" -g '*.bal' -B 1 -A 1

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 68


🌐 Web query:

cdc:ListenerConfiguration livenessInterval ballerina optional

💡 Result:

In the current Ballerina CDC module (ballerinax/cdc), cdc:ListenerConfiguration does not have a livenessInterval field (so there’s nothing to mark “optional” vs “required”). The base record only includes:

  • engineName (has a default)
  • internalSchemaStorage (has a default)
  • offsetStorage (has a default)
  • options (has a default)

So livenessInterval is simply not a supported config property for cdc:ListenerConfiguration (as of ballerinax/cdc latest docs). [1]

Source: Ballerina Central API docs for cdc:ListenerConfiguration. [1]

[1] (central.ballerina.io)

Citations:


🌐 Web query:

Ballerina cdc module ListenerConfiguration field definitions

💡 Result:

In ballerinax/cdc, ListenerConfiguration is a closed record with these fields:

  • engineName: string (default "ballerina-cdc-connector") — Name of the CDC engine. [1]
  • internalSchemaStorage: FileInternalSchemaStorage|KafkaInternalSchemaStorage (default {}) — Internal schema history configuration. [1]
  • offsetStorage: FileOffsetStorage|KafkaOffsetStorage (default {}) — Offset storage configuration. [1]
  • options: Options (default {}) — Additional CDC engine options. [1]

Source:
[1] Ballerina Central – ballerinax/cdc module docs (ListenerConfiguration record): (central.ballerina.io)

Citations:


🏁 Script executed:

#!/bin/bash
# Check if the code compiles and get more context around config handling
rg -n "config\." -g 'cdc_listener.bal' -B 1 -A 1

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 2673


🏁 Script executed:

#!/antml:thinking
Based on the web search results, cdc:ListenerConfiguration does NOT have a livenessInterval field. But the code is accessing config.livenessInterval. This is strange. Let me check if maybe the record allows additional fields (open record) or if there's a recent addition.

Actually, looking at the record definition again:

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 444


🏁 Script executed:

#!/an/bash
# Check if there are type definitions or imports that might add livenessInterval
cat -n ballerina/cdc_listener.bal | grep -E "import|type"

Repository: ballerina-platform/module-ballerinax-mssql

Length of output: 115


Guard optional livenessInterval before insertion.

Since livenessInterval is optional in the configuration, when not provided it defaults to (). Unconditionally inserting () into the config map before passing to native config parsing may cause issues. Guard the assignment:

Suggested fix
-        listenerConfigs["livenessInterval"] = config.livenessInterval;
+        if config.livenessInterval != () {
+            listenerConfigs["livenessInterval"] = config.livenessInterval;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina/cdc_listener.bal` around lines 52 - 56, The code unconditionally
sets listenerConfigs["livenessInterval"] = config.livenessInterval which may
insert the empty tuple () when livenessInterval is absent; update the logic to
guard the assignment by checking that config.livenessInterval is not the empty
value (e.g., if config.livenessInterval != () {
listenerConfigs["livenessInterval"] = config.livenessInterval; }) before calling
self.config = listenerConfigs.cloneReadOnly(), so only present livenessInterval
values are added.

}

# Attaches a CDC service to the MSSQL listener.
Expand Down
107 changes: 107 additions & 0 deletions ballerina/tests/listener_liveness_tests.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. 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.

import ballerina/lang.runtime;
import ballerina/test;
import ballerinax/cdc;

@test:Config {
groups: ["liveness"]
}
function testLivenessBeforeListenerStart() returns error? {
CdcListener mssqlListener = new ({
database: {
username: cdcUsername,
password: cdcPassword,
port: cdcPort,
databaseNames: cdcDatabase
},
options: {
snapshotMode: cdc:NO_DATA
}
});
check mssqlListener.attach(testService);
boolean liveness = check cdc:isLive(mssqlListener);
test:assertFalse(liveness, "Liveness check passes even before listener starts");
}

@test:Config {
groups: ["liveness"]
}
function testLivenessWithStartedListener() returns error? {
CdcListener mssqlListener = new ({
database: {
username: cdcUsername,
password: cdcPassword,
port: cdcPort,
databaseNames: cdcDatabase
},
options: {
snapshotMode: cdc:NO_DATA
}
});
check mssqlListener.attach(testService);
check mssqlListener.'start();
boolean liveness = check cdc:isLive(mssqlListener);
test:assertTrue(liveness, "Liveness fails for a started listener");
check mssqlListener.gracefulStop();
}

@test:Config {
groups: ["liveness"]
}
function testLivenessAfterListenerStop() returns error? {
CdcListener mssqlListener = new ({
database: {
username: cdcUsername,
password: cdcPassword,
port: cdcPort,
databaseNames: cdcDatabase
},
options: {
snapshotMode: cdc:NO_DATA
}
});
check mssqlListener.attach(testService);
check mssqlListener.'start();
check mssqlListener.gracefulStop();
boolean liveness = check cdc:isLive(mssqlListener);
test:assertFalse(liveness, "Liveness check passes after the listener has stopped");
}

@test:Config {
groups: ["liveness"]
}
function testLivenessWithoutReceivingEvents() returns error? {
CdcListener mssqlListener = new ({
database: {
username: cdcUsername,
password: cdcPassword,
port: cdcPort,
databaseNames: cdcDatabase
},
options: {
snapshotMode: cdc:NO_DATA
},
livenessInterval: 5.0
});
check mssqlListener.attach(testService);
check mssqlListener.'start();
runtime:sleep(10);
boolean liveness = check cdc:isLive(mssqlListener);
test:assertFalse(liveness, "Liveness check passes even after not receiving events within the liveness interval");
check mssqlListener.gracefulStop();
}
Loading