From 48495fc086074c79182170c02303ce333a1b0033 Mon Sep 17 00:00:00 2001 From: Niveathika Date: Tue, 20 May 2025 11:34:55 +0530 Subject: [PATCH 01/11] Move listener to database specific modules --- README.md | 166 +---------- ballerina/Dependencies.toml | 87 +++++- ballerina/README.md | 166 +---------- ballerina/extern_functions.bal | 43 ++- ballerina/listener.bal | 47 +++ ballerina/mssql_listener.bal | 97 ------ ballerina/mysql_listener.bal | 96 ------ ballerina/oracledb_listener.bal | 97 ------ ballerina/postgresql_listener.bal | 97 ------ ballerina/tests/dynamic_attachment.bal | 38 ++- ballerina/tests/mock_listener.bal | 128 ++++++++ .../service.bal => ballerina/tests/utils.bal | 15 +- ballerina/tests/utils_test.bal | 107 +++++++ ballerina/types.bal | 129 -------- ballerina/utils.bal | 146 ++------- .../lib/cdc/compiler/CodeActionTest.java | 10 +- .../lib/cdc/compiler/CompilerPluginTest.java | 24 +- .../lib/cdc/compiler/CompletionTest.java | 4 +- .../code-action-expected/service_1/result.bal | 2 +- .../code-action-expected/service_2/result.bal | 2 +- .../code-action-expected/service_3/result.bal | 2 +- .../code-action-expected/service_4/result.bal | 2 +- .../code-action-expected/service_7/result.bal | 2 +- .../code-action-expected/service_8/result.bal | 2 +- .../code-action-expected/service_9/result.bal | 2 +- .../snippet_gen_service_1/mock_listener.bal | 51 ++++ .../snippet_gen_service_1/service.bal | 2 +- .../snippet_gen_service_2/mock_listener.bal | 51 ++++ .../snippet_gen_service_2/service.bal | 2 +- .../snippet_gen_service_3/mock_listener.bal | 51 ++++ .../snippet_gen_service_4/mock_listener.bal | 51 ++++ .../snippet_gen_service_4/service.bal | 2 +- .../completions-source/sample_1/main.bal | 36 ++- .../completions-source/sample_2/main.bal | 40 ++- .../invalid_service_1/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_1/service.bal | 17 +- .../invalid_service_10/mock_listener.bal | 51 ++++ .../invalid_service_10/service.bal | 2 +- .../invalid_service_11/mock_listener.bal | 51 ++++ .../invalid_service_11/service.bal | 2 +- .../invalid_service_12/mock_listener.bal | 51 ++++ .../invalid_service_12/service.bal | 2 +- .../invalid_service_14/mock_listener.bal | 51 ++++ .../invalid_service_14/service.bal | 2 +- .../invalid_service_15/mock_listener.bal | 51 ++++ .../invalid_service_15/service.bal | 2 +- .../invalid_service_17/mock_listener.bal | 51 ++++ .../invalid_service_17/service.bal | 2 +- .../invalid_service_18/mock_listener.bal | 51 ++++ .../invalid_service_18/service.bal | 2 +- .../invalid_service_19/mock_listener.bal | 51 ++++ .../invalid_service_19/service.bal | 2 +- .../invalid_service_2/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_2/service.bal | 2 +- .../invalid_service_20/mock_listener.bal | 51 ++++ .../invalid_service_20/service.bal | 4 +- .../invalid_service_3/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_3/service.bal | 2 +- .../invalid_service_4/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_4/service.bal | 2 +- .../invalid_service_5/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_5/service.bal | 2 +- .../invalid_service_6/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_6/service.bal | 2 +- .../invalid_service_7/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_7/service.bal | 2 +- .../invalid_service_8/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_8/service.bal | 2 +- .../invalid_service_9/mock_listener.bal | 51 ++++ .../diagnostics/invalid_service_9/service.bal | 4 +- .../valid_service_1/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_1/service.bal | 2 +- .../valid_service_10/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_10/service.bal | 2 +- .../valid_service_11/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_11/service.bal | 2 +- .../valid_service_2/Ballerina.toml | 4 - .../valid_service_3/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_3/service.bal | 2 +- .../valid_service_4/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_4/service.bal | 2 +- .../valid_service_5/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_5/service.bal | 2 +- .../valid_service_6/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_6/service.bal | 2 +- .../valid_service_7/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_7/service.bal | 2 +- .../valid_service_8/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_8/service.bal | 2 +- .../valid_service_9/mock_listener.bal | 51 ++++ .../diagnostics/valid_service_9/service.bal | 2 +- .../ballerina/lib/cdc/compiler/Constants.java | 3 +- .../validator/CdcServiceAnalysisTask.java | 53 ++-- .../validator/CdcServiceValidator.java | 34 ++- docs/spec/spec.md | 98 +------ .../java/io/ballerina/lib/cdc/Listener.java | 277 ++++++++++++------ 96 files changed, 2519 insertions(+), 1250 deletions(-) create mode 100644 ballerina/listener.bal delete mode 100644 ballerina/mssql_listener.bal delete mode 100644 ballerina/mysql_listener.bal delete mode 100644 ballerina/oracledb_listener.bal delete mode 100644 ballerina/postgresql_listener.bal create mode 100644 ballerina/tests/mock_listener.bal rename compiler-plugin-tests/src/test/resources/diagnostics/valid_service_2/service.bal => ballerina/tests/utils.bal (72%) create mode 100644 ballerina/tests/utils_test.bal create mode 100644 compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_1/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_2/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_3/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_4/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_1/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_10/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_11/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_12/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_14/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_15/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_17/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_18/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_19/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_2/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_20/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_3/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_4/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_5/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_6/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_7/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_8/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_9/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_1/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_10/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_11/mock_listener.bal delete mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_2/Ballerina.toml create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_3/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_4/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_5/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_6/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_7/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_8/mock_listener.bal create mode 100644 compiler-plugin-tests/src/test/resources/diagnostics/valid_service_9/mock_listener.bal diff --git a/README.md b/README.md index c97a9a1..17a62cf 100644 --- a/README.md +++ b/README.md @@ -15,162 +15,28 @@ With the CDC module, you can: - Process and react to database events programmatically. - Build event-driven applications with ease. -## Setup guide - -### 1. Enable CDC for MySQL - -1. **Verify Binary Logging**: - - Run the following command to ensure binary logging is enabled: - ```sql - SHOW VARIABLES LIKE 'log_bin'; - ``` - -2. **Enable Binary Logging**: - - Add the following lines to the MySQL configuration file (`my.cnf` or `my.ini`): - ```ini - [mysqld] - log-bin=mysql-bin - binlog-format=ROW - server-id=1 - ``` - - Restart the MySQL server to apply the changes: - ```bash - sudo service mysql restart - ``` - Or, if you are using Homebrew on macOS: - ```bash - brew services restart mysql - ``` - -### 2. Enable CDC for Microsoft SQL Server - -1. **Ensure SQL Server Agent is Enabled**: - - The SQL Server Agent must be running to use CDC. Start the agent if it is not already running. - -2. **Enable CDC for the Database**: - - Run the following command to enable CDC for the database: - ```sql - USE ; - EXEC sys.sp_cdc_enable_db; - ``` - -3. **Enable CDC for Specific Tables**: - - Enable CDC for the required tables by specifying the schema and table name: - ```sql - EXEC sys.sp_cdc_enable_table - @source_schema = 'your_schema_name', - @source_name = 'your_table_name', - @role_name = NULL; - ``` - -4. **Verify CDC Configuration**: - - Run the following query to verify that CDC is enabled for the database: - ```sql - SELECT name, is_cdc_enabled FROM sys.databases WHERE name = 'your_database_name'; - ``` - -### 3. Enable CDC for PostgreSQL Server - -1. **Enable Logical Replication**: - - Add the following lines to the PostgreSQL configuration file (`postgresql.conf`): - ```ini - wal_level = logical - max_replication_slots = 4 - max_wal_senders = 4 - ``` - - Restart the PostgreSQL server to apply the changes: - ```bash - sudo service postgresql restart - ``` - -### 4. Enable CDC for Oracle Database - -To enable CDC for Oracle Database, follow these steps: - -1. **Enable Supplemental Logging**: - - Supplemental logging must be enabled to capture changes in the database. Run the following SQL command: - ```sql - ALTER DATABASE ADD SUPPLEMENTAL LOG DATA; - ``` - -2. **Create a Change Table**: - - Use the `DBMS_LOGMNR_CDC_PUBLISH.CREATE_CHANGE_TABLE` procedure to create a change table for capturing changes. Replace `` and `` with your schema and table names: - ```sql - BEGIN - DBMS_LOGMNR_CDC_PUBLISH.CREATE_CHANGE_TABLE( - owner_name => '', - change_table_name => 'cdc_', - source_schema_name => '', - source_table_name => '', - column_type_list => 'id NUMBER, name VARCHAR2(100), updated_at DATE', - capture_values => 'ALL', - rs_id => 'Y', - row_id => 'Y', - user_id => 'Y', - timestamp => 'Y', - object_id => 'Y', - source_colmap => 'Y' - ); - END; - ``` - -3. **Start Change Data Capture**: - - Use the `DBMS_LOGMNR_CDC_SUBSCRIBE.START_SUBSCRIPTION` procedure to start capturing changes: - ```sql - BEGIN - DBMS_LOGMNR_CDC_SUBSCRIBE.START_SUBSCRIPTION( - subscription_name => 'cdc_subscription' - ); - END; - ``` - -4. **Grant Necessary Permissions**: - - Ensure the user has the necessary permissions to use CDC: - ```sql - GRANT EXECUTE ON DBMS_LOGMNR TO ; - GRANT SELECT ON V$LOGMNR_CONTENTS TO ; - ``` - -5. **Verify CDC Configuration**: - - Run the following query to verify that CDC is enabled for the database: - ```sql - SELECT * FROM DBA_LOGMNR_CDC_PUBLISH; - ``` - -6. **Stop Change Data Capture (Optional)**: - - To stop CDC, use the `DBMS_LOGMNR_CDC_SUBSCRIBE.STOP_SUBSCRIPTION` procedure: - ```sql - BEGIN - DBMS_LOGMNR_CDC_SUBSCRIBE.STOP_SUBSCRIPTION( - subscription_name => 'cdc_subscription' - ); - END; - ``` - ## Quickstart -### Step 1: Import the Module - -Import the CDC module into your Ballerina program: - -```ballerina -import ballerinax/cdc; -``` +### Step 1: Import the Required Modules -### Step 2: Import the CDC MySQL Driver +Add the following imports to your Ballerina program: -Import the CDC MySQL Driver module into your Ballerina program: +- `ballerinax/cdc`: Core module that provides APIs to capture and process database change events. +- `ballerinax/mysql.cdc.driver as _`: Debezium-based driver for MySQL CDC. Use the appropriate driver for your database (e.g., `mssql.cdc.driver`, `postgresql.cdc.driver`, or `oracledb.cdc.driver`). +- `ballerinax/mysql`: Provides MySQL-specific listener and types for CDC. Replace with the corresponding module for your database if needed. ```ballerina -import ballerinax/cdc.mysql.driver as _; +import ballerinax/cdc; +import ballerinax/mysql.cdc.driver as _; +import ballerinax/mysql; ``` -### Step 3: Configure the Listener +### Step 2: Configure the CDC Listener -Create a CDC listener for your database. For example, to create a MySQL listener: +Create a CDC listener for your MySQL database by specifying the connection details: ```ballerina -listener cdc:MySqlListener mysqlListener = new ({ +listener mysql:CdcListener mysqlListener = new ({ hostname: "localhost", port: 3306, username: "username", @@ -179,12 +45,12 @@ listener cdc:MySqlListener mysqlListener = new ({ }); ``` -### Step 4: Define the Service +### Step 3: Define the CDC Service -Define a CDC service to handle database change events: +Implement a `cdc:Service` to handle database change events: ```ballerina -service cdcService on mysqlListener { +service on mysqlListener { remote function onRead(record {} after) returns error? { // Handle the read event @@ -208,11 +74,11 @@ service cdcService on mysqlListener { } ``` -### Step 5: Run the Application +### Step 4: Run the Application Run your Ballerina application: -```ballerina +```bash bal run ``` diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 26594f2..2590a80 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.12.3" +distribution-version = "2201.12.0" [[package]] org = "ballerina" @@ -31,6 +31,16 @@ modules = [ {org = "ballerina", packageName = "data.jsondata", moduleName = "data.jsondata"} ] +[[package]] +org = "ballerina" +name = "io" +version = "1.8.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -73,6 +83,27 @@ org = "ballerina" name = "lang.object" version = "0.0.0" +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "observe" @@ -85,6 +116,7 @@ dependencies = [ org = "ballerina" name = "random" version = "1.7.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "time"} @@ -93,6 +125,18 @@ modules = [ {org = "ballerina", packageName = "random", moduleName = "random"} ] +[[package]] +org = "ballerina" +name = "sql" +version = "1.16.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"}, + {org = "ballerina", name = "time"} +] + [[package]] org = "ballerina" name = "test" @@ -135,11 +179,48 @@ dependencies = [ {org = "ballerina", name = "crypto"}, {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.runtime"}, {org = "ballerina", name = "random"}, {org = "ballerina", name = "test"}, - {org = "ballerinai", name = "observe"} + {org = "ballerinai", name = "observe"}, + {org = "ballerinax", name = "mysql"}, + {org = "ballerinax", name = "mysql.cdc.driver"} ] modules = [ {org = "ballerinax", packageName = "cdc", moduleName = "cdc"} ] - + +[[package]] +org = "ballerinax" +name = "mysql" +version = "1.15.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "sql"}, + {org = "ballerina", name = "time"} +] +modules = [ + {org = "ballerinax", packageName = "mysql", moduleName = "mysql"} +] + +[[package]] +org = "ballerinax" +name = "mysql.cdc.driver" +version = "0.1.0" +scope = "testOnly" +dependencies = [ + {org = "ballerinax", name = "mysql.driver"} +] +modules = [ + {org = "ballerinax", packageName = "mysql.cdc.driver", moduleName = "mysql.cdc.driver"} +] + +[[package]] +org = "ballerinax" +name = "mysql.driver" +version = "1.8.0" +scope = "testOnly" + diff --git a/ballerina/README.md b/ballerina/README.md index c3577b4..eac1f47 100644 --- a/ballerina/README.md +++ b/ballerina/README.md @@ -7,162 +7,28 @@ With the CDC module, you can: - Process and react to database events programmatically. - Build event-driven applications with ease. -## Setup guide - -### 1. Enable CDC for MySQL - -1. **Verify Binary Logging**: - - Run the following command to ensure binary logging is enabled: - ```sql - SHOW VARIABLES LIKE 'log_bin'; - ``` - -2. **Enable Binary Logging**: - - Add the following lines to the MySQL configuration file (`my.cnf` or `my.ini`): - ```ini - [mysqld] - log-bin=mysql-bin - binlog-format=ROW - server-id=1 - ``` - - Restart the MySQL server to apply the changes: - ```bash - sudo service mysql restart - ``` - Or, if you are using Homebrew on macOS: - ```bash - brew services restart mysql - ``` - -### 2. Enable CDC for Microsoft SQL Server - -1. **Ensure SQL Server Agent is Enabled**: - - The SQL Server Agent must be running to use CDC. Start the agent if it is not already running. - -2. **Enable CDC for the Database**: - - Run the following command to enable CDC for the database: - ```sql - USE ; - EXEC sys.sp_cdc_enable_db; - ``` - -3. **Enable CDC for Specific Tables**: - - Enable CDC for the required tables by specifying the schema and table name: - ```sql - EXEC sys.sp_cdc_enable_table - @source_schema = 'your_schema_name', - @source_name = 'your_table_name', - @role_name = NULL; - ``` - -4. **Verify CDC Configuration**: - - Run the following query to verify that CDC is enabled for the database: - ```sql - SELECT name, is_cdc_enabled FROM sys.databases WHERE name = 'your_database_name'; - ``` - -### 3. Enable CDC for PostgreSQL Server - -1. **Enable Logical Replication**: - - Add the following lines to the PostgreSQL configuration file (`postgresql.conf`): - ```ini - wal_level = logical - max_replication_slots = 4 - max_wal_senders = 4 - ``` - - Restart the PostgreSQL server to apply the changes: - ```bash - sudo service postgresql restart - ``` - -### 4. Enable CDC for Oracle Database - -To enable CDC for Oracle Database, follow these steps: - -1. **Enable Supplemental Logging**: - - Supplemental logging must be enabled to capture changes in the database. Run the following SQL command: - ```sql - ALTER DATABASE ADD SUPPLEMENTAL LOG DATA; - ``` - -2. **Create a Change Table**: - - Use the `DBMS_LOGMNR_CDC_PUBLISH.CREATE_CHANGE_TABLE` procedure to create a change table for capturing changes. Replace `` and `` with your schema and table names: - ```sql - BEGIN - DBMS_LOGMNR_CDC_PUBLISH.CREATE_CHANGE_TABLE( - owner_name => '', - change_table_name => 'cdc_', - source_schema_name => '', - source_table_name => '', - column_type_list => 'id NUMBER, name VARCHAR2(100), updated_at DATE', - capture_values => 'ALL', - rs_id => 'Y', - row_id => 'Y', - user_id => 'Y', - timestamp => 'Y', - object_id => 'Y', - source_colmap => 'Y' - ); - END; - ``` - -3. **Start Change Data Capture**: - - Use the `DBMS_LOGMNR_CDC_SUBSCRIBE.START_SUBSCRIPTION` procedure to start capturing changes: - ```sql - BEGIN - DBMS_LOGMNR_CDC_SUBSCRIBE.START_SUBSCRIPTION( - subscription_name => 'cdc_subscription' - ); - END; - ``` - -4. **Grant Necessary Permissions**: - - Ensure the user has the necessary permissions to use CDC: - ```sql - GRANT EXECUTE ON DBMS_LOGMNR TO ; - GRANT SELECT ON V$LOGMNR_CONTENTS TO ; - ``` - -5. **Verify CDC Configuration**: - - Run the following query to verify that CDC is enabled for the database: - ```sql - SELECT * FROM DBA_LOGMNR_CDC_PUBLISH; - ``` - -6. **Stop Change Data Capture (Optional)**: - - To stop CDC, use the `DBMS_LOGMNR_CDC_SUBSCRIBE.STOP_SUBSCRIPTION` procedure: - ```sql - BEGIN - DBMS_LOGMNR_CDC_SUBSCRIBE.STOP_SUBSCRIPTION( - subscription_name => 'cdc_subscription' - ); - END; - ``` - ## Quickstart -### Step 1: Import the Module - -Import the CDC module into your Ballerina program: - -```ballerina -import ballerinax/cdc; -``` +### Step 1: Import the Required Modules -### Step 2: Import the CDC MySQL Driver +Add the following imports to your Ballerina program: -Import the CDC MySQL Driver module into your Ballerina program: +- `ballerinax/cdc`: Core module that provides APIs to capture and process database change events. +- `ballerinax/mysql.cdc.driver as _`: Debezium-based driver for MySQL CDC. Use the appropriate driver for your database (e.g., `mssql.cdc.driver`, `postgresql.cdc.driver`, or `oracledb.cdc.driver`). +- `ballerinax/mysql`: Provides MySQL-specific listener and types for CDC. Replace with the corresponding module for your database if needed. ```ballerina -import ballerinax/cdc.mysql.driver as _; +import ballerinax/cdc; +import ballerinax/mysql.cdc.driver as _; +import ballerinax/mysql; ``` -### Step 3: Configure the Listener +### Step 2: Configure the CDC Listener -Create a CDC listener for your database. For example, to create a MySQL listener: +Create a CDC listener for your MySQL database by specifying the connection details: ```ballerina -listener cdc:MySqlListener mysqlListener = new ({ +listener mysql:CdcListener mysqlListener = new ({ hostname: "localhost", port: 3306, username: "username", @@ -171,12 +37,12 @@ listener cdc:MySqlListener mysqlListener = new ({ }); ``` -### Step 4: Define the Service +### Step 3: Define the CDC Service -Define a CDC service to handle database change events: +Implement a `cdc:Service` to handle database change events: ```ballerina -service cdcService on mysqlListener { +service on mysqlListener { remote function onRead(record {} after) returns error? { // Handle the read event @@ -200,11 +66,11 @@ service cdcService on mysqlListener { } ``` -### Step 5: Run the Application +### Step 4: Run the Application Run your Ballerina application: -```ballerina +```bash bal run ``` diff --git a/ballerina/extern_functions.bal b/ballerina/extern_functions.bal index 75186c3..2b652d7 100644 --- a/ballerina/extern_functions.bal +++ b/ballerina/extern_functions.bal @@ -16,17 +16,50 @@ import ballerina/jballerina.java; -isolated function externDetach(MySqlListener|MsSqlListener|PostgreSqlListener|OracleListener self, Service o) returns boolean|Error = @java:Method { - name: "detach", +# Attach point to call the native attach method of the CDC listener. +# +# + cdcListener - the cdc listener object +# + cdcService - the cdc service object +# + return - an error if the service cannot be attached, or `()` if successful +public isolated function externAttach(Listener cdcListener, Service cdcService) returns Error? = @java:Method { + name: "attach", 'class: "io.ballerina.lib.cdc.Listener" } external; -isolated function externAttach(MySqlListener|MsSqlListener|PostgreSqlListener|OracleListener self, Service o) returns Error? = @java:Method { - name: "attach", +# Attach point to call the detach start method of the CDC listener. +# +# + cdcListener - the cdc listener object +# + cdcService - the configuration map +# + return - an error if the service cannot be detached, or `()` if successful +public isolated function externDetach(Listener cdcListener, Service cdcService) returns Error? = @java:Method { + name: "detach", 'class: "io.ballerina.lib.cdc.Listener" } external; -isolated function externStart(MySqlListener|MsSqlListener|PostgreSqlListener|OracleListener self, map config) returns Error? = @java:Method { +# Attach point to call the start method of the CDC listener. +# +# + cdcListener - the cdc listener object +# + config - the configuration map containing debezium properties +# + return - an error if the listener cannot be started, or `()` if successful +public isolated function externStart(Listener cdcListener, map config) returns Error? = @java:Method { name: "start", 'class: "io.ballerina.lib.cdc.Listener" } external; + +# Attach point to call the gracefulStop method of the CDC listener. +# +# + cdcListener - the cdc listener object +# + return - an error if the listener cannot be stopped, or `()` if successful +public isolated function externGracefulStop(Listener cdcListener) returns Error? = @java:Method { + name: "gracefulStop", + 'class: "io.ballerina.lib.cdc.Listener" +} external; + +# Attach point to call the immediateStop method of the CDC listener. +# +# + cdcListener - the cdc listener object +# + return - an error if the listener cannot be stopped, or `()` if successful +public isolated function externImmediateStop(Listener cdcListener) returns Error? = @java:Method { + name: "immediateStop", + 'class: "io.ballerina.lib.cdc.Listener" +} external; diff --git a/ballerina/listener.bal b/ballerina/listener.bal new file mode 100644 index 0000000..4ae5da6 --- /dev/null +++ b/ballerina/listener.bal @@ -0,0 +1,47 @@ +// Copyright (c) 2025, 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. + +# Represents a Ballerina CDC MySQL Listener. +public type Listener isolated object { + + # Attaches a CDC service to the listener. + # + # + s - The CDC service to attach + # + name - Attachment points + # + return - An error if the service cannot be attached, or `()` if successful + public isolated function attach(Service s, string[]|string? name = ()) returns Error?; + + # Starts the CDC listener. + # + # + return - An error if the listener cannot be started, or `()` if successful + public isolated function 'start() returns Error?; + + # Detaches a CDC service from the listener. + # + # + s - The CDC service to detach + # + return - An error if the service cannot be detached, or `()` if successful + public isolated function detach(Service s) returns Error?; + + # Stops the listener gracefully. + # + # + return - An error if the listener cannot be stopped, or `()` if successful + public isolated function gracefulStop() returns Error?; + + # Stops the listener immediately. + # + # + return - An error if the listener cannot be stopped, or `()` if successful + public isolated function immediateStop() returns Error?; +}; diff --git a/ballerina/mssql_listener.bal b/ballerina/mssql_listener.bal deleted file mode 100644 index 31066e2..0000000 --- a/ballerina/mssql_listener.bal +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2025, 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/jballerina.java as java; - -# Represents a Ballerina CDC MSSQL Listener. -public isolated class MsSqlListener { - - private final map & 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) { - self.config = getDebeziumProperties(config); - } - - # Attaches a CDC service to the MSSQL listener. - # - # + s - The CDC service to attach - # + name - Attachment points - # + return - An error if the service cannot be attached, or `()` if successful - public isolated function attach(Service s, string[]|string? name = ()) returns Error? { - lock { - if self.isStarted { - return error OperationNotPermittedError("Cannot attach CDC service to the listener once it is running."); - } - } - check externAttach(self, s); - lock { - self.hasAttachedService = true; - } - } - - # Starts the MSSQL listener. - # - # + return - An error if the listener cannot be started, or `()` if successful - public isolated function 'start() returns Error? { - lock { - if !self.hasAttachedService { - return error OperationNotPermittedError("Cannot start the listener without at least one attached service."); - } - } - check externStart(self, self.config); - lock { - self.isStarted = true; - } - } - - # Detaches a CDC service from the MSSQL listener. - # - # + s - The CDC service to detach - # + return - An error if the service cannot be detached, or `()` if successful - public isolated function detach(Service s) returns Error? { - lock { - if self.isStarted { - return error OperationNotPermittedError("Cannot detach a service from the listener once it is running."); - } - if !self.hasAttachedService { - return; - } - } - boolean result = check externDetach(self, s); - lock { - self.hasAttachedService = result; - } - } - - # Stops the MSSQL listener gracefully. - # - # + return - An error if the listener cannot be stopped, or `()` if successful - public isolated function gracefulStop() returns Error? = @java:Method { - 'class: "io.ballerina.lib.cdc.Listener" - } external; - - # Stops the MSSQL listener immediately. - # - # + return - An error if the listener cannot be stopped, or `()` if successful - public isolated function immediateStop() returns Error? = @java:Method { - 'class: "io.ballerina.lib.cdc.Listener" - } external; -} diff --git a/ballerina/mysql_listener.bal b/ballerina/mysql_listener.bal deleted file mode 100644 index 8c459ec..0000000 --- a/ballerina/mysql_listener.bal +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2025, 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/jballerina.java as java; - -# Represents a Ballerina CDC MySQL Listener. -public isolated class MySqlListener { - - private final map & readonly config; - private boolean isStarted = false; - private boolean hasAttachedService = false; - - # Initializes the MySQL listener with the given configuration. - # - # + config - The configuration for the MySQL connector - public isolated function init(*MySqlListenerConfiguration config) { - self.config = getDebeziumProperties(config); - } - - # Attaches a CDC service to the MySQL listener. - # - # + s - The CDC service to attach - # + name - Attachment points - # + return - An error if the service cannot be attached, or `()` if successful - public isolated function attach(Service s, string[]|string? name = ()) returns Error? { - lock { - if self.isStarted { - return error OperationNotPermittedError("Cannot attach CDC service to the listener once it is running."); - } - } - check externAttach(self, s); - lock { - self.hasAttachedService = true; - } - } - - # Starts the MySQL listener. - # - # + return - An error if the listener cannot be started, or `()` if successful - public isolated function 'start() returns Error? { - lock { - if !self.hasAttachedService { - return error OperationNotPermittedError("Cannot start the listener without at least one attached service."); - } - } - check externStart(self, self.config); - lock { - self.isStarted = true; - } - } - - # Detaches a CDC service from the MySQL listener. - # - # + s - The CDC service to detach - # + return - An error if the service cannot be detached, or `()` if successful - public isolated function detach(Service s) returns Error? { - lock { - if self.isStarted { - return error OperationNotPermittedError("Cannot detach a service from the listener once it is running."); - } - if !self.hasAttachedService { - return; - } - } - boolean result = check externDetach(self, s); - lock { - self.hasAttachedService = result; - } - } - - # Stops the MySQL listener gracefully. - # - # + return - An error if the listener cannot be stopped, or `()` if successful - public isolated function gracefulStop() returns Error? = @java:Method { - 'class: "io.ballerina.lib.cdc.Listener" - } external; - - # Stops the MySQL listener immediately. - # - # + return - An error if the listener cannot be stopped, or `()` if successful - public isolated function immediateStop() returns Error? = @java:Method { - 'class: "io.ballerina.lib.cdc.Listener" - } external; -} diff --git a/ballerina/oracledb_listener.bal b/ballerina/oracledb_listener.bal deleted file mode 100644 index 0350f7a..0000000 --- a/ballerina/oracledb_listener.bal +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2025, 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/jballerina.java as java; - -# Represents a Ballerina CDC Oracle DB Listener. -public isolated class OracleListener { - - private final map & readonly config; - private boolean isStarted = false; - private boolean hasAttachedService = false; - - # Initializes the Oracle DB listener with the given configuration. - # - # + config - The configuration for the Oracle DB connector - public isolated function init(*OracleListenerConfiguration config) { - self.config = getDebeziumProperties(config); - } - - # Attaches a CDC service to the Oracle listener. - # - # + s - The CDC service to attach - # + name - Attachment points - # + return - An error if the service cannot be attached, or `()` if successful - public isolated function attach(Service s, string[]|string? name = ()) returns Error? { - lock { - if self.isStarted { - return error OperationNotPermittedError("Cannot attach CDC service to the listener once it is running."); - } - } - check externAttach(self, s); - lock { - self.hasAttachedService = true; - } - } - - # Starts the Oracle DB listener. - # - # + return - An error if the listener cannot be started, or `()` if successful - public isolated function 'start() returns Error? { - lock { - if !self.hasAttachedService { - return error OperationNotPermittedError("Cannot start the listener without at least one attached service."); - } - } - check externStart(self, self.config); - lock { - self.isStarted = true; - } - } - - # Detaches a CDC service from the Oracle listener. - # - # + s - The CDC service to detach - # + return - An error if the service cannot be detached, or `()` if successful - public isolated function detach(Service s) returns Error? { - lock { - if self.isStarted { - return error OperationNotPermittedError("Cannot detach a service from the listener once it is running."); - } - if !self.hasAttachedService { - return; - } - } - boolean result = check externDetach(self, s); - lock { - self.hasAttachedService = result; - } - } - - # Stops the Oracle DB listener gracefully. - # - # + return - An error if the listener cannot be stopped, or `()` if successful - public isolated function gracefulStop() returns Error? = @java:Method { - 'class: "io.ballerina.lib.cdc.Listener" - } external; - - # Stops the Oracle DB listener immediately. - # - # + return - An error if the listener cannot be stopped, or `()` if successful - public isolated function immediateStop() returns Error? = @java:Method { - 'class: "io.ballerina.lib.cdc.Listener" - } external; -} diff --git a/ballerina/postgresql_listener.bal b/ballerina/postgresql_listener.bal deleted file mode 100644 index c3d2f71..0000000 --- a/ballerina/postgresql_listener.bal +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2025, 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/jballerina.java as java; - -# Represents a Ballerina CDC PostgreSql Listener. -public isolated class PostgreSqlListener { - - private final map & readonly config; - private boolean isStarted = false; - private boolean hasAttachedService = false; - - # Initializes the PostgreSql listener with the given configuration. - # - # + config - The configuration for the PostgreSql connector - public isolated function init(*PostgresListenerConfiguration config) { - self.config = getDebeziumProperties(config); - } - - # Attaches a CDC service to the PostgreSql listener. - # - # + s - The CDC service to attach - # + name - Attachment points - # + return - An error if the service cannot be attached, or `()` if successful - public isolated function attach(Service s, string[]|string? name = ()) returns Error? { - lock { - if self.isStarted { - return error OperationNotPermittedError("Cannot attach CDC service to the listener once it is running."); - } - } - check externAttach(self, s); - lock { - self.hasAttachedService = true; - } - } - - # Starts the PostgreSql listener. - # - # + return - An error if the listener cannot be started, or `()` if successful - public isolated function 'start() returns Error? { - lock { - if !self.hasAttachedService { - return error OperationNotPermittedError("Cannot start the listener without at least one attached service."); - } - } - check externStart(self, self.config); - lock { - self.isStarted = true; - } - } - - # Detaches a CDC service from the PostgreSql listener. - # - # + s - The CDC service to detach - # + return - An error if the service cannot be detached, or `()` if successful - public isolated function detach(Service s) returns Error? { - lock { - if self.isStarted { - return error OperationNotPermittedError("Cannot detach a service from the listener once it is running."); - } - if !self.hasAttachedService { - return; - } - } - boolean result = check externDetach(self, s); - lock { - self.hasAttachedService = result; - } - } - - # Stops the PostgreSql listener gracefully. - # - # + return - An error if the listener cannot be stopped, or `()` if successful - public isolated function gracefulStop() returns Error? = @java:Method { - 'class: "io.ballerina.lib.cdc.Listener" - } external; - - # Stops the PostgreSql listener immediately. - # - # + return - An error if the listener cannot be stopped, or `()` if successful - public isolated function immediateStop() returns Error? = @java:Method { - 'class: "io.ballerina.lib.cdc.Listener" - } external; -} diff --git a/ballerina/tests/dynamic_attachment.bal b/ballerina/tests/dynamic_attachment.bal index efcecde..02be7f5 100644 --- a/ballerina/tests/dynamic_attachment.bal +++ b/ballerina/tests/dynamic_attachment.bal @@ -21,7 +21,7 @@ Service testService = service object { } }; -function getDummyMySqlListener() returns MySqlListener { +function getDummyMySqlListener() returns MockListener { return new ({ database: { username: "testUser", @@ -32,21 +32,21 @@ function getDummyMySqlListener() returns MySqlListener { @test:Config {} function testStartingWithoutAService() returns error? { - MySqlListener mysqlListener = getDummyMySqlListener(); + MockListener mysqlListener = getDummyMySqlListener(); Error? result = mysqlListener.'start(); test:assertEquals(result is () ? "" : result.message(), "Cannot start the listener without at least one attached service."); } @test:Config {} function testStopWithoutStart() returns error? { - MySqlListener mysqlListener = getDummyMySqlListener(); + MockListener mysqlListener = getDummyMySqlListener(); error? result = mysqlListener.gracefulStop(); test:assertTrue(result is ()); } @test:Config {} function testStartWithConflictingServices() returns error? { - MySqlListener mysqlListener = getDummyMySqlListener(); + MockListener mysqlListener = getDummyMySqlListener(); Service service1 = service object { remote function onCreate(record {} after, string tableName) returns error? { @@ -62,11 +62,12 @@ function testStartWithConflictingServices() returns error? { Error? result = mysqlListener.attach(service2); test:assertEquals(result is () ? "" : result.message(), "The 'cdc:ServiceConfig' annotation is mandatory when attaching multiple services to the 'cdc:Listener'."); + check mysqlListener.detach(service1); } @test:Config {} function testStartWithServicesWithSameAnnotation() returns error? { - MySqlListener mysqlListener = getDummyMySqlListener(); + MockListener mysqlListener = getDummyMySqlListener(); Service service1 = @ServiceConfig { tables: "table1" @@ -86,33 +87,43 @@ function testStartWithServicesWithSameAnnotation() returns error? { error? result = mysqlListener.attach(service2); test:assertEquals(result is () ? "" : result.message(), "Multiple services cannot be used to receive events from the same table 'table1'."); + check mysqlListener.detach(service1); } @test:Config { enable: false } function testAttachAfterStart() returns error? { - MySqlListener mysqlListener = new ({ + MockListener mysqlListener = new ({ database: { - username: "testUser", - password: "testPassword" + username, + password, + port + }, + options: { + snapshotMode: NO_DATA } }); check mysqlListener.attach(testService); check mysqlListener.'start(); error? result = mysqlListener.attach(testService); test:assertEquals(result is () ? "" : result.message(), - "Cannot attach CDC service to the listener once it is running."); + "Cannot attach a CDC service to the listener once it is running."); + check mysqlListener.immediateStop(); } @test:Config { enable: false } function testDetachAfterStart() returns error? { - MySqlListener mysqlListener = new ({ + MockListener mysqlListener = new ({ database: { - username: "testUser", - password: "testPassword" + username, + password, + port + }, + options: { + snapshotMode: NO_DATA } }); @@ -120,5 +131,6 @@ function testDetachAfterStart() returns error? { check mysqlListener.'start(); error? result = mysqlListener.detach(testService); test:assertEquals(result is () ? "" : result.message(), - "Cannot detach a service from the listener once it is running."); + "Cannot detach a CDC service from the listener once it is running."); + check mysqlListener.gracefulStop(); } diff --git a/ballerina/tests/mock_listener.bal b/ballerina/tests/mock_listener.bal new file mode 100644 index 0000000..fc0f8a3 --- /dev/null +++ b/ballerina/tests/mock_listener.bal @@ -0,0 +1,128 @@ +// Copyright (c) 2025, 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/random; + +# Represents a Ballerina CDC MySQL Listener. +public isolated class MockListener { + *Listener; + + private final map & readonly config; + private boolean isStarted = false; + private boolean hasAttachedService = false; + + # Initializes the MySQL listener with the given configuration. + # + # + config - The configuration for the MySQL connector + public isolated function init(*MySqlListenerConfiguration config) { + map configMap = {}; + populateDebeziumProperties({ + engineName: config.engineName, + offsetStorage: config.offsetStorage, + internalSchemaStorage: config.internalSchemaStorage, + options: config.options + }, configMap); + populateDatabaseConfigurations({ + connectorClass: config.database.connectorClass, + hostname: config.database.hostname, + port: config.database.port, + username: config.database.username, + password: config.database.password, + connectTimeout: config.database.connectTimeout, + tasksMax: config.database.tasksMax, + secure: config.database.secure, + includedTables: config.database.includedTables, + excludedTables: config.database.excludedTables, + includedColumns: config.database.includedColumns, + excludedColumns: config.database.excludedColumns + }, configMap); + configMap["database.server.id"] = "100000"; + self.config = configMap.cloneReadOnly(); + } + + # Attaches a CDC service to the MySQL listener. + # + # + s - The CDC service to attach + # + name - Attachment points + # + return - An error if the service cannot be attached, or `()` if successful + public isolated function attach(Service s, string[]|string? name = ()) returns Error? { + check externAttach(self, s); + } + + # Starts the MySQL listener. + # + # + return - An error if the listener cannot be started, or `()` if successful + public isolated function 'start() returns Error? { + check externStart(self, self.config); + } + + # Detaches a CDC service from the MySQL listener. + # + # + s - The CDC service to detach + # + return - An error if the service cannot be detached, or `()` if successful + public isolated function detach(Service s) returns Error? { + check externDetach(self, s); + } + + # Stops the MySQL listener gracefully. + # + # + return - An error if the listener cannot be stopped, or `()` if successful + public isolated function gracefulStop() returns Error? { + check externGracefulStop(self); + } + + # Stops the MySQL listener immediately. + # + # + return - An error if the listener cannot be stopped, or `()` if successful + public isolated function immediateStop() returns Error? { + check externImmediateStop(self); + } +} + +const string MYSQL_DATABASE_SERVER_ID = "database.server.id"; +const string MYSQL_DATABASE_INCLUDE_LIST = "database.include.list"; +const string MYSQL_DATABASE_EXCLUDE_LIST = "database.exclude.list"; + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; + string databaseServerId = (checkpanic random:createIntInRange(0, 100000)).toString(); + string|string[] includedDatabases?; + string|string[] excludedDatabases?; + int tasksMax = 1; + SecureDatabaseConnection secure = {}; +|}; + +// Populates MySQL-specific configurations +isolated function populateMySqlConfigurations(MySqlDatabaseConnection connection, map configMap) { + configMap[MYSQL_DATABASE_SERVER_ID] = connection.databaseServerId.toString(); + + string|string[]? includedDatabases = connection.includedDatabases; + if includedDatabases !is () { + configMap[MYSQL_DATABASE_INCLUDE_LIST] = includedDatabases is string ? includedDatabases : string:'join(",", ...includedDatabases); + } + + string|string[]? excludedDatabases = connection.excludedDatabases; + if excludedDatabases !is () { + configMap[MYSQL_DATABASE_EXCLUDE_LIST] = excludedDatabases is string ? excludedDatabases : string:'join(",", ...excludedDatabases); + } +} diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_2/service.bal b/ballerina/tests/utils.bal similarity index 72% rename from compiler-plugin-tests/src/test/resources/diagnostics/valid_service_2/service.bal rename to ballerina/tests/utils.bal index 04a9c2e..8c268e3 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_2/service.bal +++ b/ballerina/tests/utils.bal @@ -14,14 +14,9 @@ // specific language governing permissions and limitations // under the License. -import ballerinax/cdc as foo; +import ballerinax/mysql.cdc.driver as _; -listener foo:MySqlListener cdcListener = new (database = { - username: "root", - password: "root" -}); - -service foo:Service on cdcListener { - remote function onRead(record {|anydata...;|} after) { - } -} +string username = "root"; +string password = "root"; +string database = "store_db"; +int port = 3307; diff --git a/ballerina/tests/utils_test.bal b/ballerina/tests/utils_test.bal new file mode 100644 index 0000000..80edea9 --- /dev/null +++ b/ballerina/tests/utils_test.bal @@ -0,0 +1,107 @@ +// Copyright (c) 2025, 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/test; + +@test:Config {} +function testGetDebeziumProperties() { + // Expected properties map + map expectedProperties = { + "name": "ballerina-cdc-connector", + "max.queue.size": "8192", + "max.batch.size": "2048", + "event.processing.failure.handling.mode": "warn", + "snapshot.mode": "initial", + "skipped.operations": "t", + "skip.messages.without.change": "false", + "tombstones.on.delete": "false", + "decimal.handling.mode": "double", + "schema.history.internal": "io.debezium.storage.kafka.history.KafkaSchemaHistory", + "topic.prefix": "bal_cdc_schema_history", + "schema.history.internal.kafka.bootstrap.servers": "", + "schema.history.internal.kafka.topic": "bal_cdc_internal_schema_history", + "offset.storage": "org.apache.kafka.connect.storage.KafkaOffsetBackingStore", + "offset.flush.interval.ms": "60000", + "offset.flush.timeout.ms": "5000", + "bootstrap.servers": "", + "offset.storage.topic": "bal_cdc_offsets", + "offset.storage.partitions": "1", + "offset.storage.replication.factor": "2", + "include.schema.changes": "false", + "database.query.timeout.ms": "60000" + }; + + ListenerConfiguration config = { + offsetStorage: { + bootstrapServers: "" + }, + internalSchemaStorage: { + bootstrapServers: "" + } + }; + + map actualProperties = {}; + // Call the function to test + populateDebeziumProperties(config, actualProperties); + + // Validate the returned properties + test:assertEquals(actualProperties, expectedProperties, msg = "Debezium properties do not match the expected values."); +} + +@test:Config {} +function testGetDatabaseDebeziumProperties() { + // Expected properties map + map expectedProperties = { + "connector.class": "", + "database.hostname": "localhost", + "database.port": "3307", + "database.user": "root", + "database.password": "root", + "tasks.max": "1", + "connect.timeout.ms": "600000", + "database.ssl.mode": "disabled", + "database.ssl.keystore": "", + "database.ssl.keystore.password": "", + "database.ssl.truststore": "", + "database.ssl.truststore.password": "", + "table.include.list": "", + "column.include.list": "ya,tan" + }; + + DatabaseConnection config = { + username: "root", + password: "root", + port: 3307, + hostname: "localhost", + connectorClass: "", + connectTimeout: 600, + tasksMax: 1, + secure: { + sslMode: DISABLED, + keyStore: {path: "", password: ""}, + trustStore: {path: "", password: ""} + }, + includedTables: "", + includedColumns: ["ya", "tan"] + }; + + map actualProperties = {}; + // Call the function to test + populateDatabaseConfigurations(config, actualProperties); + + // Validate the returned properties + test:assertEquals(actualProperties, expectedProperties, msg = "Debezium properties do not match the expected values."); +} diff --git a/ballerina/types.bal b/ballerina/types.bal index 996233b..d8f1610 100644 --- a/ballerina/types.bal +++ b/ballerina/types.bal @@ -14,7 +14,6 @@ // specific language governing permissions and limitations // under the License. import ballerina/crypto; -import ballerina/random; # Represents the SSL modes for secure database connections. public enum SslMode { @@ -196,102 +195,6 @@ public type DatabaseConnection record {| string|string[] excludedColumns?; |}; -# Represents the configuration for a MySQL database connection. -# -# + connectorClass - The class name of the MySQL connector implementation to use -# + hostname - The hostname of the MySQL server -# + port - The port number of the MySQL server -# + databaseServerId - The unique identifier for the MySQL server -# + includedDatabases - A list of regular expressions matching fully-qualified database identifiers to capture changes from (should not be used alongside databaseExclude) -# + excludedDatabases - A list of regular expressions matching fully-qualified database identifiers to exclude from change capture (should not be used alongside databaseInclude) -# + tasksMax - The maximum number of tasks to create for this connector. Because the MySQL connector always uses a single task, changing the default value has no effect -# + secure - The connector establishes an encrypted connection if the server supports secure connections -public type MySqlDatabaseConnection record {| - *DatabaseConnection; - string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; - string hostname = "localhost"; - int port = 3306; - string databaseServerId = (checkpanic random:createIntInRange(0, 100000)).toString(); - string|string[] includedDatabases?; - string|string[] excludedDatabases?; - int tasksMax = 1; - SecureDatabaseConnection secure = {}; -|}; - -# Represents the configuration for an MSSQL database connection. -# -# + connectorClass - The class name of the MSSQL connector implementation to use -# + hostname - The hostname of the MSSQL server -# + port - The port number of the MSSQL server -# + databaseInstance - The name of the database instance -# + databaseNames - A list of database names to capture changes from -# + includedSchemas - A list of regular expressions matching fully-qualified schema identifiers to capture changes from -# + excludedSchemas - A list of regular expressions matching fully-qualified schema identifiers to exclude from change capture -# + tasksMax - The maximum number of tasks to create for this connector. If the `databaseNames` contains more than one element, you can increase the value of this property to a number less than or equal to the number of elements in the list -public type MsSqlDatabaseConnection record {| - *DatabaseConnection; - string connectorClass = "io.debezium.connector.sqlserver.SqlServerConnector"; - string hostname = "localhost"; - int port = 1433; - string databaseInstance?; - string|string[] databaseNames; - string|string[] includedSchemas?; - string|string[] excludedSchemas?; - int tasksMax = 1; -|}; - -# Represents the configuration for a PostgreSQL CDC connector. -# -# + connectorClass - The class name of the PostgreSQL connector implementation to use -# + hostname - The hostname of the PostgreSQL server -# + port - The port number of the PostgreSQL server -# + databaseName - The name of the PostgreSQL database from which to stream the changes. -# + includedSchemas - A list of regular expressions matching fully-qualified schema identifiers to capture changes from -# + excludedSchemas - A list of regular expressions matching fully-qualified schema identifiers to exclude from change capture -# + tasksMax - The PostgreSQL connector always uses a single task and therefore does not use this value, so the default is always acceptable -# + pluginName - The name of the PostgreSQL logical decoding plug-in installed on the server -# + slotName - The name of the PostgreSQL logical decoding slot -# + publicationName - The name of the PostgreSQL publication created for streaming changes when using pgoutput. -public type PostgresDatabaseConnection record {| - *DatabaseConnection; - string connectorClass = "io.debezium.connector.postgresql.PostgresConnector"; - string hostname = "localhost"; - int port = 5432; - string databaseName; - string|string[] includedSchemas?; - string|string[] excludedSchemas?; - int tasksMax = 1; - PostgreSQLLogicalDecodingPlugin pluginName = PGOUTPUT; - string slotName = "debezium"; - string publicationName = "dbz_publication"; -|}; - -# Represents the configuration for a Oracle CDC connector. -# -# + connectorClass - The class name of the Oracle connector implementation to use -# + hostname - The hostname of the Oracle server -# + port - The port number of the Oracle server -# + url - JDBC url -# + pdbName - Name of the Oracle pluggable database to connect to. Use this property with container database (CDB) installations only -# + databaseName - The name of the Oracle database from which to stream the changes -# + connectionAdopter - The adapter implementation that the connector uses when it streams database changes -# + includedSchemas - A list of regular expressions matching fully-qualified schema identifiers to capture changes from -# + excludedSchemas - A list of regular expressions matching fully-qualified schema identifiers to exclude from change capture -# + tasksMax - The Oracle connector always uses a single task and therefore does not use this value, so the default is always acceptable -public type OracleDatabaseConnection record {| - *DatabaseConnection; - string connectorClass = "io.debezium.connector.oracle.OracleConnector"; - string hostname = "localhost"; - int port = 1521; - string url?; - string pdbName?; - string databaseName; - OracleConnectionAdopter connectionAdopter = LOGMINER; - string|string[] includedSchemas?; - string|string[] excludedSchemas?; - int tasksMax = 1; -|}; - # Provides a set of additional configurations related to the cdc connection. # # + snapshotMode - The mode for capturing snapshots @@ -325,35 +228,3 @@ public type ListenerConfiguration record {| FileOffsetStorage|KafkaOffsetStorage offsetStorage = {}; Options options = {}; |}; - -# Represents the configuration for a MySQL CDC connector. -# -# + database - The MySQL database connection configuration -public type MySqlListenerConfiguration record {| - MySqlDatabaseConnection database; - *ListenerConfiguration; -|}; - -# Represents the configuration for an MSSQL CDC connector. -# -# + database - The MSSQL database connection configuration -public type MsSqlListenerConfiguration record {| - MsSqlDatabaseConnection database; - *ListenerConfiguration; -|}; - -# Represents the configuration for a Postgres CDC connector. -# -# + database - The Postgres database connection configuration -public type PostgresListenerConfiguration record {| - PostgresDatabaseConnection database; - *ListenerConfiguration; -|}; - -# Represents the configuration for an Oracle CDC connector. -# -# + database - The Oracle database connection configuration -public type OracleListenerConfiguration record {| - OracleDatabaseConnection database; - *ListenerConfiguration; -|}; diff --git a/ballerina/utils.bal b/ballerina/utils.bal index b0de5e6..14f3192 100644 --- a/ballerina/utils.bal +++ b/ballerina/utils.bal @@ -56,59 +56,25 @@ const string OFFSET_STORAGE_REPLICATION_FACTOR = "offset.storage.replication.fac const string INCLUDE_SCHEMA_CHANGES = "include.schema.changes"; const string TOMBSTONES_ON_DELETE = "tombstones.on.delete"; -const string SCHEMA_INCLUDE_LIST = "schema.include.list"; -const string SCHEMA_EXCLUDE_LIST = "schema.exclude.list"; +# Processes the given configuration and populates the map with the necessary debezium properties. +# +# + config - listener configuration +# + configMap - map to populate with debezium properties +public isolated function populateDebeziumProperties(ListenerConfiguration config, map configMap) { -const string MYSQL_DATABASE_SERVER_ID = "database.server.id"; -const string MYSQL_DATABASE_INCLUDE_LIST = "database.include.list"; -const string MYSQL_DATABASE_EXCLUDE_LIST = "database.exclude.list"; - -const string MSSQL_DATABASE_NAMES = "database.names"; -const string MSSQL_DATABASE_INSTANCE = "database.instance"; -const string MSSQL_DATABASE_ENCRYPT = "database.encrypt"; - -const string POSTGRESQL_DATABASE_NAME = "database.dbname"; -const string POSTGRESQL_PLUGIN_NAME = "plugin.name"; -const string POSTGRESQL_SLOT_NAME = "slot.name"; -const string POSTGRESQL_PUBLICATION_NAME = "publication.name"; - -const string ORACLE_DATABASE_NAME = "database.dbname"; -const string ORACLE_URL = "database.url"; -const string ORACLE_PDB_NAME = "database.dbname"; -const string ORACLE_CONNECTION_ADAPTER = "database.connection.adapter"; - -isolated function getDebeziumProperties(MySqlListenerConfiguration|MsSqlListenerConfiguration|PostgresListenerConfiguration|OracleListenerConfiguration config) returns map & readonly { - map configMap = {}; configMap[NAME] = config.engineName; populateSchemaHistoryConfigurations(config.internalSchemaStorage, configMap); populateOffsetStorageConfigurations(config.offsetStorage, configMap); - populateDatabaseConfigurations(config.database, configMap); - populateOptions(config.options, configMap); // The following values cannot be overridden by the user configMap[TOMBSTONES_ON_DELETE] = "false"; configMap[INCLUDE_SCHEMA_CHANGES] = "false"; - - return configMap.cloneReadOnly(); } -// Populates common configurations shared across all databases -isolated function populateOptions(Options options, map configMap) { - configMap[MAX_QUEUE_SIZE] = options.maxQueueSize.toString(); - configMap[MAX_BATCH_SIZE] = options.maxBatchSize.toString(); - configMap[EVENT_PROCESSING_FAILURE_HANDLING_MODE] = options.eventProcessingFailureHandlingMode; - configMap[SNAPSHOT_MODE] = options.snapshotMode; - configMap[SKIPPED_OPERATIONS] = string:'join(",", ...options.skippedOperations); - configMap[SKIP_MESSAGES_WITHOUT_CHANGE] = options.skipMessagesWithoutChange.toString(); - configMap[DECIMAL_HANDLING_MODE] = options.decimalHandlingMode; - configMap[DATABASE_QUERY_TIMEOUTS_MS] = getMillisecondValueOf(options.queryTimeout); -} - -// Populates schema history storage configurations isolated function populateSchemaHistoryConfigurations(FileInternalSchemaStorage|KafkaInternalSchemaStorage schemaHistoryInternal, map configMap) { configMap[SCHEMA_HISTORY_INTERNAL] = schemaHistoryInternal.className; configMap[TOPIC_PREFIX] = schemaHistoryInternal.topicPrefix; @@ -122,7 +88,6 @@ isolated function populateSchemaHistoryConfigurations(FileInternalSchemaStorage| } } -// Populates offset storage configurations isolated function populateOffsetStorageConfigurations(FileOffsetStorage|KafkaOffsetStorage offsetStorage, map configMap) { configMap[OFFSET_STORAGE] = offsetStorage.className; configMap[OFFSET_FLUSH_INTERVAL_MS] = getMillisecondValueOf(offsetStorage.flushInterval); @@ -139,8 +104,22 @@ isolated function populateOffsetStorageConfigurations(FileOffsetStorage|KafkaOff } } -// Populates database-specific configurations -isolated function populateDatabaseConfigurations(MySqlDatabaseConnection|MsSqlDatabaseConnection|PostgresDatabaseConnection|OracleDatabaseConnection connection, map configMap) { +isolated function populateOptions(Options options, map configMap) { + configMap[MAX_QUEUE_SIZE] = options.maxQueueSize.toString(); + configMap[MAX_BATCH_SIZE] = options.maxBatchSize.toString(); + configMap[EVENT_PROCESSING_FAILURE_HANDLING_MODE] = options.eventProcessingFailureHandlingMode; + configMap[SNAPSHOT_MODE] = options.snapshotMode; + configMap[SKIPPED_OPERATIONS] = string:'join(",", ...options.skippedOperations); + configMap[SKIP_MESSAGES_WITHOUT_CHANGE] = options.skipMessagesWithoutChange.toString(); + configMap[DECIMAL_HANDLING_MODE] = options.decimalHandlingMode; + configMap[DATABASE_QUERY_TIMEOUTS_MS] = getMillisecondValueOf(options.queryTimeout); +} + +# Populates the database configurations in the given map. +# +# + connection - database connection configuration +# + configMap - map to populate with database configurations +public isolated function populateDatabaseConfigurations(DatabaseConnection connection, map configMap) { configMap[CONNECTOR_CLASS] = connection.connectorClass; configMap[DATABASE_HOSTNAME] = connection.hostname; configMap[DATABASE_PORT] = connection.port.toString(); @@ -154,19 +133,9 @@ isolated function populateDatabaseConfigurations(MySqlDatabaseConnection|MsSqlDa populateSslConfigurations(connection, configMap); populateTableAndColumnConfigurations(connection, configMap); - - if connection is MySqlDatabaseConnection { - populateMySqlConfigurations(connection, configMap); - } else if connection is MsSqlDatabaseConnection { - populateMsSqlConfigurations(connection, configMap); - } else if connection is PostgresDatabaseConnection { - populatePostgresConfigurations(connection, configMap); - } else { - populateOracleConfigurations(connection, configMap); - } } -isolated function populateSslConfigurations(MySqlDatabaseConnection|MsSqlDatabaseConnection|PostgresDatabaseConnection|OracleDatabaseConnection connection, map configMap) { +isolated function populateSslConfigurations(DatabaseConnection connection, map configMap) { SecureDatabaseConnection? secure = connection.secure; if secure !is () { configMap[DATABASE_SSL_MODE] = secure.sslMode.toString(); @@ -186,7 +155,7 @@ isolated function populateSslConfigurations(MySqlDatabaseConnection|MsSqlDatabas } // Populates table and column inclusion/exclusion configurations -isolated function populateTableAndColumnConfigurations(MySqlDatabaseConnection|MsSqlDatabaseConnection|PostgresDatabaseConnection|OracleDatabaseConnection connection, map configMap) { +isolated function populateTableAndColumnConfigurations(DatabaseConnection connection, map configMap) { string|string[]? includedTables = connection.includedTables; if includedTables !is () { configMap[TABLE_INCLUDE_LIST] = includedTables is string ? includedTables : string:'join(",", ...includedTables); @@ -208,75 +177,6 @@ isolated function populateTableAndColumnConfigurations(MySqlDatabaseConnection|M } } -// Populates MySQL-specific configurations -isolated function populateMySqlConfigurations(MySqlDatabaseConnection connection, map configMap) { - configMap[MYSQL_DATABASE_SERVER_ID] = connection.databaseServerId.toString(); - - string|string[]? includedDatabases = connection.includedDatabases; - if includedDatabases !is () { - configMap[MYSQL_DATABASE_INCLUDE_LIST] = includedDatabases is string ? includedDatabases : string:'join(",", ...includedDatabases); - } - - string|string[]? excludedDatabases = connection.excludedDatabases; - if excludedDatabases !is () { - configMap[MYSQL_DATABASE_EXCLUDE_LIST] = excludedDatabases is string ? excludedDatabases : string:'join(",", ...excludedDatabases); - } -} - -// Populates MSSQL-specific configurations -isolated function populateMsSqlConfigurations(MsSqlDatabaseConnection connection, map configMap) { - if connection.databaseInstance !is () { - configMap[MSSQL_DATABASE_INSTANCE] = connection.databaseInstance ?: ""; - } - - string|string[] databaseNames = connection.databaseNames; - configMap[MSSQL_DATABASE_NAMES] = databaseNames is string ? databaseNames : string:'join(",", ...databaseNames); - - populateSchemaConfigurations(connection, configMap); - - if connection.secure is () { - configMap[MSSQL_DATABASE_ENCRYPT] = "false"; - } -} - -// Populates PostgreSQL-specific configurations -isolated function populatePostgresConfigurations(PostgresDatabaseConnection connection, map configMap) { - configMap[POSTGRESQL_DATABASE_NAME] = connection.databaseName; - populateSchemaConfigurations(connection, configMap); - configMap[POSTGRESQL_PLUGIN_NAME] = connection.pluginName; - configMap[POSTGRESQL_SLOT_NAME] = connection.slotName; - configMap[POSTGRESQL_PUBLICATION_NAME] = connection.publicationName; -} - -// Populates Oracle-specific configurations -isolated function populateOracleConfigurations(OracleDatabaseConnection connection, map configMap) { - configMap[ORACLE_DATABASE_NAME] = connection.databaseName; - - if connection.url !is () { - configMap[ORACLE_URL] = connection.url ?: ""; - } - - if connection.pdbName !is () { - configMap[ORACLE_PDB_NAME] = connection.pdbName ?: ""; - } - - configMap[ORACLE_CONNECTION_ADAPTER] = connection.connectionAdopter; - populateSchemaConfigurations(connection, configMap); -} - -// Populates schema inclusion/exclusion configurations -isolated function populateSchemaConfigurations(MsSqlDatabaseConnection|PostgresDatabaseConnection|OracleDatabaseConnection connection, map configMap) { - string|string[]? includedSchemas = connection.includedSchemas; - if includedSchemas !is () { - configMap[SCHEMA_INCLUDE_LIST] = includedSchemas is string ? includedSchemas : string:'join(",", ...includedSchemas); - } - - string|string[]? excludedSchemas = connection.excludedSchemas; - if excludedSchemas !is () { - configMap[SCHEMA_EXCLUDE_LIST] = excludedSchemas is string ? excludedSchemas : string:'join(",", ...excludedSchemas); - } -} - isolated function getMillisecondValueOf(decimal value) returns string { string milliSecondVal = (value * 1000).toBalString(); return milliSecondVal.substring(0, milliSecondVal.indexOf(".") ?: milliSecondVal.length()); diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CodeActionTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CodeActionTest.java index 35c912f..3ecd5da 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CodeActionTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CodeActionTest.java @@ -146,7 +146,7 @@ public void testServiceWithVariablesCodeActionWithTableName() throws IOException ); } - @Test + @Test(enabled = false) public void testPostgresEmptyServiceCodeAction() throws IOException { CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION, @@ -165,7 +165,7 @@ public void testPostgresEmptyServiceCodeAction() throws IOException { ); } - @Test + @Test(enabled = false) public void testPostgresEmptyServiceCodeActionWithTableName() throws IOException { CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION, @@ -187,7 +187,7 @@ public void testPostgresEmptyServiceCodeActionWithTableName() throws IOException @Test public void testMakeFunctionRemote() throws IOException { - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION, 224); + CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION, 231); CodeActionInfo expectedCodeAction = CodeActionInfo.from("Make the function remote", List.of(locationArg)); expectedCodeAction.setProviderName( FUNCTION_SHOULD_BE_REMOTE.getCode() + "/ballerinax/cdc/" + MAKE_FUNCTION_REMOTE); @@ -203,7 +203,7 @@ public void testMakeFunctionRemote() throws IOException { @Test public void testChangeReturnTypeToError() throws IOException { - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION, TextRange.from(173, 7)); + CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION, TextRange.from(180, 7)); CodeActionInfo expectedCodeAction = CodeActionInfo.from("Change return type to error?", List.of(locationArg)); expectedCodeAction.setProviderName( @@ -219,7 +219,7 @@ public void testChangeReturnTypeToError() throws IOException { @Test public void testChangeReturnTypeToCdcError() throws IOException { - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION, TextRange.from(173, 7)); + CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION, TextRange.from(180, 7)); CodeActionInfo expectedCodeAction = CodeActionInfo.from("Change return type to cdc:Error?", List.of(locationArg)); expectedCodeAction.setProviderName( diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CompilerPluginTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CompilerPluginTest.java index a67e32e..9d327e7 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CompilerPluginTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CompilerPluginTest.java @@ -75,13 +75,6 @@ public void testValidService1() { Assert.assertEquals(diagnosticResult.errors().size(), 0); } - @Test(description = "Validating import as") - public void testValidService2() { - PackageCompilation currentPackage = loadAndCompilePackage("valid_service_2"); - DiagnosticResult diagnosticResult = currentPackage.diagnosticResult(); - Assert.assertEquals(diagnosticResult.errors().size(), 0); - } - @Test(description = "Validate basic service") public void testValidService3() { PackageCompilation currentPackage = loadAndCompilePackage("valid_service_3"); @@ -159,8 +152,14 @@ public void testInvalidService1() { { NO_VALID_FUNCTION, "missing valid remote function: expected at least one of " + - "''onRead'', ''onCreate'', ''onUpdate'', ''onDelete'' or ''onTruncate'' functions", - "(32:0,33:1)" + "''onRead'', ''onCreate'', ''onUpdate'' or ''onDelete'' functions", + "(26:0,27:1)" + }, + { + NO_VALID_FUNCTION, + "missing valid remote function: expected at least one of " + + "''onRead'', ''onCreate'', ''onUpdate'' or ''onDelete'' functions", + "(29:0,34:1)" } }); } @@ -173,12 +172,12 @@ public void testInvalidService2() { { "BCE2063", "missing.required.parameter", - "(18:41,21:2)" + "(18:36,21:2)" }, { "BCE2039", "undefined.parameter", - "(18:46,21:1)" + "(18:41,21:1)" }}); } @@ -442,7 +441,8 @@ public void testInvalidService19() { }); } - @Test(description = "Validate parameters for onTruncate") + // Need postgresql module as test dependency + @Test(enabled = false, description = "Validate parameters for onTruncate") public void testInvalidService20() { PackageCompilation currentPackage = loadAndCompilePackage("invalid_service_20"); DiagnosticResult diagnosticResult = currentPackage.diagnosticResult(); diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CompletionTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CompletionTest.java index 6c5e8e1..1840421 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CompletionTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/lib/cdc/compiler/CompletionTest.java @@ -58,8 +58,8 @@ public Object[][] dataProvider() { }; } - @Test(dataProvider = "completion-data-provider") - protected void test(String sourceFile, int line, int offset, String expectedFile) throws IOException { + @Test(enabled = false, dataProvider = "completion-data-provider") + public void test(String sourceFile, int line, int offset, String expectedFile) throws IOException { Path sourceFilePath = RESOURCE_DIRECTORY.resolve("completions-source").resolve(sourceFile); Path expectedFilePath = RESOURCE_DIRECTORY.resolve("completions-expected").resolve(expectedFile); TestConfig expectedList = GSON.fromJson(Files.newBufferedReader(expectedFilePath), TestConfig.class); diff --git a/compiler-plugin-tests/src/test/resources/code-action-expected/service_1/result.bal b/compiler-plugin-tests/src/test/resources/code-action-expected/service_1/result.bal index a01d427..ff6d7cb 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-expected/service_1/result.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-expected/service_1/result.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service cdc:Service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-expected/service_2/result.bal b/compiler-plugin-tests/src/test/resources/code-action-expected/service_2/result.bal index 1b779a3..6809bd3 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-expected/service_2/result.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-expected/service_2/result.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service cdc:Service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-expected/service_3/result.bal b/compiler-plugin-tests/src/test/resources/code-action-expected/service_3/result.bal index 4a3060b..92add59 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-expected/service_3/result.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-expected/service_3/result.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service cdc:Service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-expected/service_4/result.bal b/compiler-plugin-tests/src/test/resources/code-action-expected/service_4/result.bal index d716daa..400bb3b 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-expected/service_4/result.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-expected/service_4/result.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service cdc:Service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-expected/service_7/result.bal b/compiler-plugin-tests/src/test/resources/code-action-expected/service_7/result.bal index 78e6f33..a027d40 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-expected/service_7/result.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-expected/service_7/result.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-expected/service_8/result.bal b/compiler-plugin-tests/src/test/resources/code-action-expected/service_8/result.bal index e0a0700..da66629 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-expected/service_8/result.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-expected/service_8/result.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-expected/service_9/result.bal b/compiler-plugin-tests/src/test/resources/code-action-expected/service_9/result.bal index bbdd601..5138474 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-expected/service_9/result.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-expected/service_9/result.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_1/mock_listener.bal b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_1/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_1/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_1/service.bal b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_1/service.bal index 7a80ed8..2e73e4d 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_1/service.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_1/service.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service cdc:Service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_2/mock_listener.bal b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_2/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_2/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_2/service.bal b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_2/service.bal index 6699d86..b78aedd 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_2/service.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_2/service.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service cdc:Service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_3/mock_listener.bal b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_3/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_3/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_4/mock_listener.bal b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_4/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_4/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_4/service.bal b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_4/service.bal index e2e5812..9c362b9 100644 --- a/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_4/service.bal +++ b/compiler-plugin-tests/src/test/resources/code-action-source/snippet_gen_service_4/service.bal @@ -1,6 +1,6 @@ import ballerinax/cdc; -service on new cdc:MySqlListener(database = { +service cdc:Service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/completions-source/sample_1/main.bal b/compiler-plugin-tests/src/test/resources/completions-source/sample_1/main.bal index 9457148..74dc8b1 100644 --- a/compiler-plugin-tests/src/test/resources/completions-source/sample_1/main.bal +++ b/compiler-plugin-tests/src/test/resources/completions-source/sample_1/main.bal @@ -3,9 +3,43 @@ import ballerinax/cdc; // This is added to test some auto generated code segments. // Please ignore the indentation. -service cdc:Service on new cdc:MySqlListener(database = { +service on new MockListener(database = { username: "root", password: "root" }) { r } + +# Represents a Ballerina CDC MySQL Listener. +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; diff --git a/compiler-plugin-tests/src/test/resources/completions-source/sample_2/main.bal b/compiler-plugin-tests/src/test/resources/completions-source/sample_2/main.bal index ba56538..c20a610 100644 --- a/compiler-plugin-tests/src/test/resources/completions-source/sample_2/main.bal +++ b/compiler-plugin-tests/src/test/resources/completions-source/sample_2/main.bal @@ -3,7 +3,43 @@ import ballerinax/cdc; // This is added to test some auto generated code segments. // Please ignore the indentation. -service cdc:Service on new cdc:MySqlListener(database = { + + +# Represents a Ballerina CDC MySQL Listener. +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + +service on new MockListener(database = { username: "root", password: "root" }) { @@ -15,7 +51,7 @@ service cdc:Service on new cdc:MySqlListener(database = { } } -service cdc:Service on new cdc:MySqlListener(database = { +service on new MockListener(database = { username: "root", password: "root" }) { diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_1/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_1/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_1/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_1/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_1/service.bal index 0d9cfa6..81e1cfa 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_1/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_1/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); @@ -24,11 +24,12 @@ listener cdc:MySqlListener cdcListener = new (database = { service cdc:Service on cdcListener { } -listener cdc:PostgreSqlListener postgresListener = new (database = { - username: "root", - password: "root", - databaseName: "testdb" -}); - -service cdc:Service on postgresListener { +service on cdcListener { } + +service on new MockListener(database = { + username: "root", + password: "root" + }) { + +} \ No newline at end of file diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_10/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_10/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_10/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_10/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_10/service.bal index 7ba362f..2d8b8b1 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_10/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_10/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_11/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_11/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_11/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_11/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_11/service.bal index 0c0bd35..a0b900a 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_11/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_11/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_12/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_12/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_12/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_12/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_12/service.bal index 7b11fc5..3f97e54 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_12/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_12/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_14/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_14/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_14/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_14/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_14/service.bal index 3d3a4f4..ebd4c41 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_14/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_14/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_15/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_15/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_15/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_15/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_15/service.bal index 9a7ce07..787529f 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_15/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_15/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_17/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_17/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_17/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_17/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_17/service.bal index e9a1be6..23c158d 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_17/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_17/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_18/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_18/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_18/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_18/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_18/service.bal index aa86913..3da8e78 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_18/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_18/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_19/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_19/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_19/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_19/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_19/service.bal index 60f3a1b..82b3ece 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_19/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_19/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_2/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_2/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_2/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_2/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_2/service.bal index 8c59618..8b803fc 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_2/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_2/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (datbase = { +listener MockListener cdcListener = new (datbase = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_20/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_20/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_20/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_20/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_20/service.bal index d79d1e7..ab82c6b 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_20/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_20/service.bal @@ -15,9 +15,9 @@ // under the License. import ballerinax/cdc; +import ballerinax/postgresql; - -listener cdc:PostgreSqlListener postgresListener = new (database = { +listener postgresql:Listener postgresListener = new (database = { username: "root", password: "root", databaseName: "testdb" diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_3/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_3/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_3/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_3/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_3/service.bal index 457d02d..a24e82c 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_3/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_3/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_4/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_4/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_4/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_4/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_4/service.bal index 9fcc5fb..789054a 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_4/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_4/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_5/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_5/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_5/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_5/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_5/service.bal index 61db884..f23e62f 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_5/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_5/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_6/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_6/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_6/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_6/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_6/service.bal index 20fc979..b5348f6 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_6/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_6/service.bal @@ -15,7 +15,7 @@ // under the License. import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_7/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_7/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_7/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_7/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_7/service.bal index 1a214c1..e6855d1 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_7/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_7/service.bal @@ -15,7 +15,7 @@ // under the License. import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_8/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_8/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_8/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_8/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_8/service.bal index 5956219..6ef7117 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_8/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_8/service.bal @@ -15,7 +15,7 @@ // under the License. import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_9/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_9/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_9/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_9/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_9/service.bal index 360bd46..e03f87d 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_9/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/invalid_service_9/service.bal @@ -16,12 +16,12 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); -listener cdc:MySqlListener cdcListener2 = new (database = { +listener MockListener cdcListener2 = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_1/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_1/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_1/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_1/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_1/service.bal index b1de8a1..f7f65a9 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_1/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_1/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_10/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_10/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_10/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_10/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_10/service.bal index 3dda00e..d2e9659 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_10/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_10/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_11/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_11/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_11/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_11/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_11/service.bal index 84794cd..07055eb 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_11/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_11/service.bal @@ -39,6 +39,6 @@ public isolated class MySqlListener { listener MySqlListener cdcListener = new (); -service cdc:Service on cdcListener { +service on cdcListener { } diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_2/Ballerina.toml b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_2/Ballerina.toml deleted file mode 100644 index 8ccdabd..0000000 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_2/Ballerina.toml +++ /dev/null @@ -1,4 +0,0 @@ -[package] -org = "cdc_test" -name = "valid_service_2" -version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_3/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_3/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_3/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_3/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_3/service.bal index e231fd4..15dc038 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_3/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_3/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_4/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_4/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_4/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_4/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_4/service.bal index d236dfe..3fdb1ad 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_4/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_4/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_5/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_5/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_5/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_5/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_5/service.bal index fcc8a61..342eb1d 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_5/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_5/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_6/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_6/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_6/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_6/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_6/service.bal index 3c78f03..aaff0b9 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_6/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_6/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_7/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_7/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_7/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_7/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_7/service.bal index 37dcb08..b1c0ed9 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_7/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_7/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_8/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_8/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_8/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_8/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_8/service.bal index a8dd9c2..b672080 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_8/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_8/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_9/mock_listener.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_9/mock_listener.bal new file mode 100644 index 0000000..33b1f20 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_9/mock_listener.bal @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 ballerinax/cdc; + +public isolated class MockListener { + *cdc:Listener; + public isolated function init(*MySqlListenerConfiguration config) { + } + + public isolated function attach(cdc:Service s, string[]|string? name = ()) returns cdc:Error? { + } + + public isolated function 'start() returns cdc:Error? { + } + + public isolated function detach(cdc:Service s) returns cdc:Error? { + } + + public isolated function gracefulStop() returns cdc:Error? { + } + + public isolated function immediateStop() returns cdc:Error? { + } +} + +public type MySqlListenerConfiguration record {| + MySqlDatabaseConnection database; + *cdc:ListenerConfiguration; +|}; + +public type MySqlDatabaseConnection record {| + *cdc:DatabaseConnection; + string connectorClass = "io.debezium.connector.mysql.MySqlConnector"; + string hostname = "localhost"; + int port = 3306; +|}; + diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_9/service.bal b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_9/service.bal index fa33e82..0aa7a03 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_9/service.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/valid_service_9/service.bal @@ -16,7 +16,7 @@ import ballerinax/cdc; -listener cdc:MySqlListener cdcListener = new (database = { +listener MockListener cdcListener = new (database = { username: "root", password: "root" }); diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/Constants.java index 5d50689..0e8c01c 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/Constants.java @@ -22,11 +22,12 @@ public final class Constants { public static final String PACKAGE_ORG = "ballerinax"; public static final String PACKAGE_PREFIX = "cdc"; + public static final String POSTGRESQL_PACKAGE_PREFIX = "postgres"; // Parameters public static final String ERROR_PARAM = "Error"; - public static final String POSTGRES_LISTENER_NAME = "PostgreSqlListener"; + public static final String CDC_LISTENER_NAME = "Listener"; public static final List VALID_FUNCTIONS = List.of( ServiceMethodNames.ON_READ, ServiceMethodNames.ON_CREATE, diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/validator/CdcServiceAnalysisTask.java b/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/validator/CdcServiceAnalysisTask.java index 9b71c9a..628bc8f 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/validator/CdcServiceAnalysisTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/validator/CdcServiceAnalysisTask.java @@ -18,8 +18,11 @@ package io.ballerina.lib.cdc.compiler.validator; import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ClassSymbol; import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.TypeDescKind; +import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.lib.cdc.compiler.Utils; @@ -31,7 +34,7 @@ import java.util.List; import java.util.Optional; -import static io.ballerina.lib.cdc.compiler.DiagnosticCodes.INVALID_MULTIPLE_LISTENERS; +import static io.ballerina.lib.cdc.compiler.Constants.CDC_LISTENER_NAME; public class CdcServiceAnalysisTask implements AnalysisTask { @@ -47,36 +50,50 @@ public void perform(SyntaxNodeAnalysisContext context) { return; } // Created inner class to keep context as class param - new CdcServiceValidator(context).validate(); + new CdcServiceValidator(context, (ServiceDeclarationNode) context.node()).validate(); } private boolean isCdcService(SyntaxNodeAnalysisContext context) { SemanticModel semanticModel = context.semanticModel(); ServiceDeclarationNode serviceDeclarationNode = (ServiceDeclarationNode) context.node(); - Optional symbol = semanticModel.symbol(serviceDeclarationNode); - if (symbol.isEmpty()) { + Optional symbolOpt = semanticModel.symbol(serviceDeclarationNode); + if (symbolOpt.isEmpty() || !(symbolOpt.get() instanceof ServiceDeclarationSymbol serviceSymbol)) { return false; } - ServiceDeclarationSymbol serviceDeclarationSymbol = (ServiceDeclarationSymbol) symbol.get(); - Optional serviceTypeSymbol = serviceDeclarationSymbol.typeDescriptor(); - if (serviceTypeSymbol.isPresent() && serviceTypeSymbol.get().getModule().isEmpty()) { - if (!Utils.isCdcModule(serviceTypeSymbol.get().getModule().get())) { - return false; + // Prefer checking the service type descriptor if available + Optional serviceTypeOpt = serviceSymbol.typeDescriptor(); + if (serviceTypeOpt.isPresent()) { + TypeSymbol serviceType = serviceTypeOpt.get(); + if (serviceType.getModule().isPresent() && Utils.isCdcModule(serviceType.getModule().get())) { + return true; } } - List listeners = serviceDeclarationSymbol.listenerTypes(); - if (listeners.size() > 1) { - context.reportDiagnostic(Utils.createDiagnostic(INVALID_MULTIPLE_LISTENERS, - serviceDeclarationNode.location())); + // Fallback: check listener types + List listeners = serviceSymbol.listenerTypes(); + if (listeners.size() != 1) { + return false; + } + TypeSymbol listener = listeners.getFirst(); + if (listener.typeKind() != TypeDescKind.TYPE_REFERENCE) { + return false; + } + + TypeSymbol refType = ((TypeReferenceTypeSymbol) listener).typeDescriptor(); + if (refType.typeKind() != TypeDescKind.OBJECT || !(refType instanceof ClassSymbol)) { + return false; + } + + List inclusions = ((ClassSymbol) refType).typeInclusions(); + if (inclusions.size() != 1) { + return false; + } + TypeSymbol inclusion = inclusions.getFirst(); + if (inclusion.typeKind() != TypeDescKind.TYPE_REFERENCE || inclusion.getModule().isEmpty()) { return false; } - return listeners.stream() - .map(TypeSymbol::getModule) - .filter(Optional::isPresent) - .map(Optional::get) - .allMatch(Utils::isCdcModule); + return Utils.isCdcModule(inclusion.getModule().get()) && inclusion.nameEquals(CDC_LISTENER_NAME); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/validator/CdcServiceValidator.java b/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/validator/CdcServiceValidator.java index 7ae5a12..c0073e1 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/validator/CdcServiceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/cdc/compiler/validator/CdcServiceValidator.java @@ -18,6 +18,7 @@ package io.ballerina.lib.cdc.compiler.validator; import io.ballerina.compiler.api.symbols.MethodSymbol; +import io.ballerina.compiler.api.symbols.ModuleSymbol; import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; @@ -28,25 +29,27 @@ import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.tools.diagnostics.DiagnosticInfo; +import java.util.List; import java.util.Optional; import static io.ballerina.compiler.syntax.tree.SyntaxKind.OBJECT_METHOD_DEFINITION; import static io.ballerina.compiler.syntax.tree.SyntaxKind.RESOURCE_ACCESSOR_DEFINITION; -import static io.ballerina.lib.cdc.compiler.Constants.POSTGRES_LISTENER_NAME; +import static io.ballerina.lib.cdc.compiler.Constants.PACKAGE_ORG; +import static io.ballerina.lib.cdc.compiler.Constants.POSTGRESQL_PACKAGE_PREFIX; import static io.ballerina.lib.cdc.compiler.Constants.VALID_FUNCTIONS; import static io.ballerina.lib.cdc.compiler.Constants.VALID_FUNCTIONS_NON_POSTGRES; import static io.ballerina.lib.cdc.compiler.DiagnosticCodes.EMPTY_SERVICE; import static io.ballerina.lib.cdc.compiler.DiagnosticCodes.EMPTY_SERVICE_POSTGRESQL; +import static io.ballerina.lib.cdc.compiler.DiagnosticCodes.INVALID_MULTIPLE_LISTENERS; import static io.ballerina.lib.cdc.compiler.DiagnosticCodes.INVALID_RESOURCE_FUNCTION; import static io.ballerina.lib.cdc.compiler.DiagnosticCodes.NO_VALID_FUNCTION; import static io.ballerina.lib.cdc.compiler.Utils.getMethodSymbol; import static io.ballerina.tools.diagnostics.DiagnosticFactory.createDiagnostic; import static io.ballerina.tools.diagnostics.DiagnosticSeverity.INTERNAL; -public record CdcServiceValidator(SyntaxNodeAnalysisContext context) { +public record CdcServiceValidator(SyntaxNodeAnalysisContext context, ServiceDeclarationNode serviceDeclarationNode) { public void validate() { - ServiceDeclarationNode serviceDeclarationNode = (ServiceDeclarationNode) this.context.node(); NodeList memberNodes = serviceDeclarationNode.members(); Optional serviceSymbolOpt = getServiceSymbol(serviceDeclarationNode); @@ -55,13 +58,19 @@ public void validate() { } ServiceDeclarationSymbol serviceSymbol = serviceSymbolOpt.get(); + + validateAttachedListeners(serviceSymbol); + Optional listenerOpt = serviceSymbol.listenerTypes().stream().findFirst(); if (listenerOpt.isEmpty()) { return; } TypeSymbol listener = listenerOpt.get(); - boolean isPostgresListener = isPostgresListener(listener); + + boolean isPostgresListener = listener.getModule() + .map(this::isPostgresListener) + .orElse(false); boolean hasValidFunction = serviceDeclarationNode.members().stream() .filter(node -> node.kind() == OBJECT_METHOD_DEFINITION) @@ -75,14 +84,27 @@ public void validate() { validateServiceMembers(memberNodes); } + private void validateAttachedListeners(ServiceDeclarationSymbol serviceDeclarationSymbol) { + List listeners = serviceDeclarationSymbol.listenerTypes(); + if (listeners.size() > 1) { + context.reportDiagnostic(Utils.createDiagnostic(INVALID_MULTIPLE_LISTENERS, + serviceDeclarationNode.location())); + } + } + private Optional getServiceSymbol(ServiceDeclarationNode serviceDeclarationNode) { return context.semanticModel().symbol(serviceDeclarationNode) .filter(ServiceDeclarationSymbol.class::isInstance) .map(ServiceDeclarationSymbol.class::cast); } - private boolean isPostgresListener(TypeSymbol listener) { - return listener.getName().map(POSTGRES_LISTENER_NAME::equals).orElse(false); + private boolean isPostgresListener(ModuleSymbol moduleSymbol) { + if (moduleSymbol == null || moduleSymbol.id() == null) { + return false; + } + String moduleName = moduleSymbol.id().moduleName(); + String orgName = moduleSymbol.id().orgName(); + return POSTGRESQL_PACKAGE_PREFIX.equals(moduleName) && PACKAGE_ORG.equals(orgName); } private boolean isValidFunction(FunctionDefinitionNode functionNode, boolean isPostgresListener) { diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 74b339d..11ca63a 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -3,7 +3,7 @@ _Authors_: [@niveathika](https://github.com/niveathika) \ _Reviewers_: [@daneshk](https://github.com/daneshk) [@ThisaruGuruge](https://github.com/ThisaruGuruge) \ _Created_: 2025/04/23 \ -_Updated_: 2025/04/27 \ +_Updated_: 2025/05/19 \ _Edition_: Swan Lake ## Introduction @@ -24,21 +24,7 @@ The conforming implementation of the specification is released and included in t - [1. Overview](#1-overview) - [2. Components](#2-components) - [Example: Importing the CDC Package](#example-importing-the-cdc-package) - - [2.1 Listeners](#21-listeners) - - [2.1.1 MySqlListener](#211-mysqllistener) - - [2.1.1.1 Initializing `MySqlListener` Using Username and Password](#2111-initializing-mysqllistener-using-username-and-password) - - [Example: Initializing the MySQL Listener with Username and Password](#example-initializing-the-mysql-listener-with-username-and-password) - - [2.1.2 MSSQL Listener](#212-mssql-listener) - - [2.1.2.1 Initializing `MsSqlListener` Using Username, Password, and Database Names](#2121-initializing-mssqllistener-using-username-password-and-database-names) - - [Example: Initializing the `MsSqlListener` with Username, Password, and Database Names](#example-initializing-the-mssqllistener-with-username-password-and-database-names) - - [2.1.3 PostgeSQL Listener](#213-postgesql-listener) - - [2.1.3.1 Initializing `PostgeSqlListener` Using Username, Password, and Database Name](#2131-initializing-postgesqllistener-using-username-password-and-database-name) - - [Example: Initializing the `PostgeSqlListener` with Username, Password, and Database Name](#example-initializing-the-postgesqllistener-with-username-password-and-database-name) - - [2.1.4 Oracle Listener](#214-oracle-listener) - - [2.1.3.1 Initializing `OracleListener` Using Username, Password, and Database Name](#2131-initializing-oraclelistener-using-username-password-and-database-name) - - [Example: Initializing the `OracleListener` with Username, Password, and Database Name](#example-initializing-the-oraclelistener-with-username-password-and-database-name) - - [2.1.5 Listener Configuration](#215-listener-configuration) - - [Example: Listener Configuration](#example-listener-configuration) + - [2.1 Listener](#21-listener) - [2.2 Service](#22-service) - [Example: Service](#example-service) - [2.2.1 Service Type](#221-service-type) @@ -88,85 +74,15 @@ This section describes the components of the Ballerina CDC package. To use the B import ballerinax/cdc; ``` -### 2.1 Listeners +### 2.1 Listener -The Ballerina CDC module provides two types of listeners: `MySqlListener` and `MsSqlListener`. These listeners are specifically designed to capture real-time changes from MySQL and MSSQL databases, respectively. They enable developers to build applications that respond to database events such as inserts, updates, and deletes. +The Ballerina CDC package provides a generic `Listener` object, which serves as the foundation for capturing change data events. Each supported database (such as MySQL, MSSQL, PostgreSQL, etc.) implements its own specific listener by extending this base `Listener` object. -Both listeners can be configured with the necessary database connection details and event processing options to suit specific application requirements. +These database-specific listeners internally call the publicly available extern functions (`externAttach()`, `externDetach()`, `externStart()`, `externGracefulStop()`, `externImmediateStop()`) provided by the CDC module to interact with the underlying change data capture mechanisms. -#### 2.1.1 MySqlListener +Common configuration records such as `ListenerConfiguration` and `DatabaseConnection` are available in the CDC module. Utility methods are also provided to convert these configurations into Debezium-compatible properties maps, making integration with Debezium seamless. Any additional properties or configurations that are specific to a particular database must be implemented within the respective database modules. -The `MySqlListener` listens to changes in a MySQL database and streams the captured events to the application. It uses the MySQL binary log to detect and process changes. - -##### 2.1.1.1 Initializing `MySqlListener` Using Username and Password - -The `MySqlListener` requires a username and password to connect to the database. These credentials must be provided during the initialization of the listener. - -###### Example: Initializing the MySQL Listener with Username and Password - -```ballerina -listener cdc:MySqlListener mysqlListener = new (database = { username = "root", password = "password" }); -``` - -#### 2.1.2 MSSQL Listener - -The `MsSqlListener` listens to changes in an MSSQL database and streams the captured events to the application. It leverages the SQL Server Change Data Capture (CDC) feature to track and process changes. - -##### 2.1.2.1 Initializing `MsSqlListener` Using Username, Password, and Database Names - -The `MsSqlListener` requires a username, password and at least one database name to connect to the database. These credentials must be provided during the initialization of the listener. - -###### Example: Initializing the `MsSqlListener` with Username, Password, and Database Names - -```ballerina -listener cdc:MsSqlListener mssqlListener = new (database = { username = "root", password = "password", databaseNames: "finance_db",}); -``` - -#### 2.1.3 PostgeSQL Listener - -The `PostgeSqlListener` listens to changes in a PostgreSQL database and streams the captured events to the application. - -##### 2.1.3.1 Initializing `PostgeSqlListener` Using Username, Password, and Database Name - -The `PostgeSqlListener` requires a username, password and one database name to connect to the server. These credentials must be provided during the initialization of the listener. - -###### Example: Initializing the `PostgeSqlListener` with Username, Password, and Database Name - -```ballerina -listener cdc:PostgeSqlListener postgreListener = new (database = { username = "root", password = "password", databaseNames: "finance_db",}); -``` - -#### 2.1.4 Oracle Listener - -The `OracleListener` listens to changes in an Oracle database and streams the captured events to the application. - -##### 2.1.3.1 Initializing `OracleListener` Using Username, Password, and Database Name - -The `OracleListener` requires a username, password and one database name to connect to the server. These credentials must be provided during the initialization of the listener. - -###### Example: Initializing the `OracleListener` with Username, Password, and Database Name - -```ballerina -listener cdc:OracleListener oracleListener = new (database = { username = "root", password = "password", databaseNames: "finance_db",}); -``` - -#### 2.1.5 Listener Configuration - -The CDC listeners allows additional configurations to be passed when creating a `cdc:MySqlListener` or `cdc:MsSqlListener`. These configurations are defined in the `cdc:MySqlConnectorConfiguration` record and `cdc:MsSqlConnectorConfiguration`, enabling developers to customize the behavior of the listener based on their requirements. - -###### Example: Listener Configuration - -```ballerina -listener cdc:MySqlListener mysqlListener = new ({ - hostname: "localhost", - port: 3306, - username: "cdc_user", - password: "cdc_pass", - databaseInclude: ["mydb"], - tableInclude: ["mydb.customers", "mydb.orders"], - snapshotMode: "INITIAL" -}); -``` +This design allows the CDC package to support multiple databases while maintaining a consistent and extensible API for users. ### 2.2 Service diff --git a/native/src/main/java/io/ballerina/lib/cdc/Listener.java b/native/src/main/java/io/ballerina/lib/cdc/Listener.java index 8acd1db..961b1a6 100644 --- a/native/src/main/java/io/ballerina/lib/cdc/Listener.java +++ b/native/src/main/java/io/ballerina/lib/cdc/Listener.java @@ -19,13 +19,13 @@ import io.ballerina.lib.cdc.models.Service; import io.ballerina.lib.cdc.utils.Constants.BallerinaErrors; +import io.ballerina.lib.cdc.utils.ErrorUtils; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.types.ObjectType; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; -import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; @@ -36,10 +36,12 @@ import java.io.IOException; import java.util.Map; import java.util.Properties; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.locks.ReentrantLock; import static io.ballerina.lib.cdc.utils.Constants.ANN_CONFIG_TABLES; import static io.ballerina.lib.cdc.utils.Constants.ANN_NAME_EVENTS_FROM; @@ -58,24 +60,191 @@ public class Listener { public static final String TABLE_TO_SERVICE_MAP_KEY = "TABLE_TO_SERVICE_MAP"; public static final String DEBEZIUM_ENGINE_KEY = "DEB_ENGINE"; public static final String EXECUTOR_SERVICE_KEY = "ExecutorService"; + public static final String IS_STARTED_KEY = "isStarted"; + public static final String HAS_ATTACHED_SERVICE_KEY = "hasAttachedService"; + public static final String LISTENER_ID = "Id"; + private static final ConcurrentHashMap lockMap = new ConcurrentHashMap<>(); public static Object attach(BObject listener, BObject service) { - Object serviceMap = listener.getNativeData(TABLE_TO_SERVICE_MAP_KEY); - Object serviceConfigAnn = getServiceConfigAnnotation(service); - - Map updatedServiceMap = initializeServiceMap(serviceMap); + String id = getListenerId(listener); + ReentrantLock lock = lockMap.computeIfAbsent(id, k -> new ReentrantLock()); + lock.lock(); try { + Object isStartedKey = listener.getNativeData(IS_STARTED_KEY); + boolean isStarted = isStartedKey != null && ((Boolean) isStartedKey); + if (isStarted) { + return ErrorUtils.createError(BallerinaErrors.OPERATION_NOT_PERMITTED_ERROR, + "Cannot attach a CDC service to the listener once it is running."); + } + + Object serviceMap = listener.getNativeData(TABLE_TO_SERVICE_MAP_KEY); + Object serviceConfigAnn = getServiceConfigAnnotation(service); + + Map updatedServiceMap = initializeServiceMap(serviceMap); if (serviceConfigAnn == null) { handleUnAnnotatedServiceAttachment(serviceMap, updatedServiceMap, service); } else { handleUnAnnotatedServiceAttachment(serviceConfigAnn, service, updatedServiceMap); } - } catch (BError e) { + listener.addNativeData(TABLE_TO_SERVICE_MAP_KEY, updatedServiceMap); + listener.addNativeData(HAS_ATTACHED_SERVICE_KEY, true); + return null; + } catch (Exception e) { + return e; + } finally { + lock.unlock(); + } + } + + public static Object detach(BObject listener, BObject service) { + String id = getListenerId(listener); + ReentrantLock lock = lockMap.computeIfAbsent(id, k -> new ReentrantLock()); + + lock.lock(); + try { + Object isStartedKey = listener.getNativeData(IS_STARTED_KEY); + boolean isStarted = isStartedKey != null && ((Boolean) isStartedKey); + if (isStarted) { + return ErrorUtils.createError(BallerinaErrors.OPERATION_NOT_PERMITTED_ERROR, + "Cannot detach a CDC service from the listener once it is running."); + } + + Object hasAttachedServiceObj = listener.getNativeData(HAS_ATTACHED_SERVICE_KEY); + boolean hasAttachedService = hasAttachedServiceObj != null && ((Boolean) hasAttachedServiceObj); + if (hasAttachedService) { + return null; + } + Object serviceMap = listener.getNativeData(TABLE_TO_SERVICE_MAP_KEY); + Object serviceConfigAnn = getServiceConfigAnnotation(service); + + if (serviceConfigAnn == null) { + hasAttachedService = removeSingleServiceFromMap(listener, serviceMap); + } else { + hasAttachedService = removeServiceFromMap(listener, serviceMap, serviceConfigAnn); + } + listener.addNativeData(HAS_ATTACHED_SERVICE_KEY, hasAttachedService); + return null; + } catch (Exception e) { return e; + } finally { + lock.unlock(); + } + } + + public static Object start(Environment environment, BObject listener, BMap config) { + String id = getListenerId(listener); + ReentrantLock lock = lockMap.computeIfAbsent(id, k -> new ReentrantLock()); + + lock.lock(); + try { + Object isStartedKey = listener.getNativeData(IS_STARTED_KEY); + boolean isStarted = isStartedKey != null && ((Boolean) isStartedKey); + if (isStarted) { + return null; + } + + Object hasAttachedServiceObj = listener.getNativeData(HAS_ATTACHED_SERVICE_KEY); + boolean hasAttachedService = hasAttachedServiceObj != null && ((Boolean) hasAttachedServiceObj); + if (!hasAttachedService) { + return ErrorUtils.createError(BallerinaErrors.OPERATION_NOT_PERMITTED_ERROR, + "Cannot start the listener without at least one attached service."); + } + + Properties engineProperties = populateEngineProperties(config); + @SuppressWarnings("unchecked") + ConcurrentHashMap serviceMap = (ConcurrentHashMap) listener + .getNativeData(TABLE_TO_SERVICE_MAP_KEY); + + CompletableFuture comFuture = new CompletableFuture<>(); + ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + DebeziumEngine> engine = create(Json.class) + .using(engineProperties) + .notifying(new BalChangeConsumer(serviceMap, environment.getRuntime())) + .using(new DebeziumEngine.ConnectorCallback() { + @Override + public void taskStarted() { + EngineResult result = new EngineResult(); + result.success = true; + comFuture.complete(result); + } + }) + .using((success, message, error) -> { + EngineResult result = new EngineResult(); + result.success = success; + result.message = message; + result.error = error; + comFuture.complete(result); + }) + .build(); + executor.submit(engine); + + EngineResult engineResult = comFuture.get(); + if (engineResult.success) { + listener.addNativeData(DEBEZIUM_ENGINE_KEY, engine); + listener.addNativeData(EXECUTOR_SERVICE_KEY, executor); + } else { + String errorMessage = engineResult.message != null ? engineResult.message + : (engineResult.error != null ? engineResult.error.getMessage() : "Unknown error"); + return createCdcError("Failed to start the Debezium engine: " + errorMessage); + } + listener.addNativeData(IS_STARTED_KEY, true); + return null; + } catch (Throwable t) { + return createCdcError("Failed to start the Debezium engine: " + t.getMessage()); + } finally { + lock.unlock(); + } + } + + public static Object gracefulStop(BObject listener) { + String id = getListenerId(listener); + ReentrantLock lock = lockMap.computeIfAbsent(id, k -> new ReentrantLock()); + + lock.lock(); + try { + Object debEngine = listener.getNativeData(DEBEZIUM_ENGINE_KEY); + if (debEngine != null) { + ((DebeziumEngine) debEngine).close(); + listener.addNativeData(DEBEZIUM_ENGINE_KEY, null); + } + + Object executor = listener.getNativeData(EXECUTOR_SERVICE_KEY); + if (executor != null) { + ((ExecutorService) executor).shutdown(); + listener.addNativeData(EXECUTOR_SERVICE_KEY, null); + } + + listener.addNativeData(IS_STARTED_KEY, false); + return null; + } catch (IOException e) { + return createCdcError("Failed to stop the Debezium engine: " + e.getMessage()); + } finally { + lock.unlock(); + } + } + + public static Object immediateStop(BObject listener) { + String id = getListenerId(listener); + ReentrantLock lock = lockMap.computeIfAbsent(id, k -> new ReentrantLock()); + + lock.lock(); + try { + Object executor = listener.getNativeData(EXECUTOR_SERVICE_KEY); + + if (executor != null) { + ((ExecutorService) executor).shutdownNow(); + listener.addNativeData(EXECUTOR_SERVICE_KEY, null); + } + + listener.addNativeData(DEBEZIUM_ENGINE_KEY, null); + listener.addNativeData(IS_STARTED_KEY, false); + return null; + } catch (Exception e) { + return createCdcError("Failed to stop the Debezium engine: " + e.getMessage()); + } finally { + lock.unlock(); } - listener.addNativeData(TABLE_TO_SERVICE_MAP_KEY, updatedServiceMap); - return null; } private static Object getServiceConfigAnnotation(BObject service) { @@ -121,21 +290,6 @@ private static void addServiceToMap(BObject service, String table, Map config) { - Properties engineProperties = populateEngineProperties(config); - @SuppressWarnings("unchecked") - ConcurrentHashMap serviceMap = (ConcurrentHashMap) listener - .getNativeData(TABLE_TO_SERVICE_MAP_KEY); - - try { - CompletableFuture comFuture = new CompletableFuture<>(); - ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); - DebeziumEngine> engine = create(Json.class) - .using(engineProperties) - .notifying(new BalChangeConsumer(serviceMap, environment.getRuntime())) - .using(new DebeziumEngine.ConnectorCallback() { - @Override - public void taskStarted() { - EngineResult result = new EngineResult(); - result.success = true; - comFuture.complete(result); - } - }) - .using((success, message, error) -> { - EngineResult result = new EngineResult(); - result.success = success; - result.message = message; - result.error = error; - comFuture.complete(result); - }) - .build(); - executor.submit(engine); - - EngineResult engineResult = comFuture.get(); - if (engineResult.success) { - listener.addNativeData(DEBEZIUM_ENGINE_KEY, engine); - listener.addNativeData(EXECUTOR_SERVICE_KEY, executor); - } else { - String errorMessage = engineResult.message != null ? engineResult.message - : (engineResult.error != null ? engineResult.error.getMessage() : "Unknown error"); - return createCdcError("Failed to start the Debezium engine: " + errorMessage); - } - return null; - } catch (Throwable t) { - return createCdcError("Failed to start the Debezium engine: " + t.getMessage()); - } - } - private static Properties populateEngineProperties(BMap config) { Properties engineProperties = new Properties(); for (Map.Entry configEntry : config.entrySet()) { @@ -219,30 +328,16 @@ private static Properties populateEngineProperties(BMap config) return engineProperties; } - public static Object gracefulStop(BObject listener) { - Object debEngine = listener.getNativeData(DEBEZIUM_ENGINE_KEY); - if (debEngine != null) { - try { - ((DebeziumEngine) debEngine).close(); - listener.addNativeData(DEBEZIUM_ENGINE_KEY, null); - } catch (IOException e) { - return createCdcError("Failed to stop the Debezium engine: " + e.getMessage()); - } - } - return null; - } - - public static Object immediateStop(BObject listener) { - Object executor = listener.getNativeData(EXECUTOR_SERVICE_KEY); - if (executor != null) { - try { - ((ExecutorService) executor).shutdownNow(); - listener.addNativeData(EXECUTOR_SERVICE_KEY, null); - } catch (Exception e) { - return createCdcError("Failed to stop the Debezium engine: " + e.getMessage()); - } + private static String getListenerId(BObject listener) { + Object idObj = listener.getNativeData(LISTENER_ID); + String id; + if (idObj == null) { + id = UUID.randomUUID().toString(); + listener.addNativeData(LISTENER_ID, id); + } else { + id = (String) idObj; } - return null; + return id; } // Helper class to store result From 03c1026718917f1a1719bd46fbd44820f73d88ac Mon Sep 17 00:00:00 2001 From: Niveathika Date: Tue, 20 May 2025 11:42:44 +0530 Subject: [PATCH 02/11] Add test cases --- ballerina/Dependencies.toml | 2 +- ballerina/build.gradle | 47 ++++++++ ballerina/tests/environment/compose.yaml | 20 ++++ ballerina/tests/environment/mysql-setup.sql | 38 +++++++ ballerina/tests/listener_tests.bal | 120 ++++++++++++++++++++ build.gradle | 5 + gradle.properties | 6 + 7 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 ballerina/tests/environment/compose.yaml create mode 100644 ballerina/tests/environment/mysql-setup.sql create mode 100644 ballerina/tests/listener_tests.bal diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 2590a80..1e31cf6 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -209,7 +209,7 @@ modules = [ [[package]] org = "ballerinax" name = "mysql.cdc.driver" -version = "0.1.0" +version = "1.0.0" scope = "testOnly" dependencies = [ {org = "ballerinax", name = "mysql.driver"} diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 51e558e..d29a17a 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -97,6 +97,53 @@ tasks.register('commitTomlFiles') { } } +tasks.register('startMySQLServer') { + doLast { + def stdOut = new ByteArrayOutputStream() + def cmd = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c'] : ['sh', '-c'] + exec { + commandLine cmd + ["docker ps --filter name=mysql-cdc"] + standardOutput = stdOut + } + if (!stdOut.toString().contains("mysql-cdc")) { + println "Starting MySQL server." + exec { + commandLine cmd + ["docker compose -f tests/environment/compose.yaml up -d"] + standardOutput = stdOut + } + println stdOut.toString() + sleep(20 * 1000) + } else { + println "MySQL server is already running." + } + } +} + +tasks.register('stopMySQLServer') { + doLast { + def stdOut = new ByteArrayOutputStream() + def cmd = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c'] : ['sh', '-c'] + exec { + commandLine cmd + ["docker ps --filter name=mysql-cdc"] + standardOutput = stdOut + } + if (stdOut.toString().contains("mysql-cdc")) { + println "Stopping MySQL server." + exec { + commandLine cmd + ["docker compose -f tests/environment/compose.yaml rm -svf"] + standardOutput = stdOut + } + println stdOut.toString() + sleep(5 * 1000) + } else { + println "MySQL server is not started." + } + } +} + +test.dependsOn "startMySQLServer" +test.finalizedBy "stopMySQLServer" + updateTomlFiles.dependsOn copyStdlibs build.dependsOn ":${packageName}-native:build" build.dependsOn ":${packageName}-compiler-plugin:build" diff --git a/ballerina/tests/environment/compose.yaml b/ballerina/tests/environment/compose.yaml new file mode 100644 index 0000000..6068cff --- /dev/null +++ b/ballerina/tests/environment/compose.yaml @@ -0,0 +1,20 @@ +name: cdc-test-cases + +services: + mysql: + image: mysql:8.0 + container_name: mysql-cdc + ports: + - "3307:3306" + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: store_db + MYSQL_USER: cdc_user + MYSQL_PASSWORD: cdc_password + volumes: + - ./mysql-setup.sql:/docker-entrypoint-initdb.d/mysql-setup.sql:ro + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] + interval: 10s + timeout: 5s + retries: 5 diff --git a/ballerina/tests/environment/mysql-setup.sql b/ballerina/tests/environment/mysql-setup.sql new file mode 100644 index 0000000..1093cb2 --- /dev/null +++ b/ballerina/tests/environment/mysql-setup.sql @@ -0,0 +1,38 @@ +CREATE DATABASE IF NOT EXISTS store_db; +USE store_db; + +CREATE TABLE vendors ( + id INT PRIMARY KEY, + name VARCHAR(255), + contact_info TEXT +); + +INSERT INTO vendors VALUES +(1, 'Samsung', 'contact@samsung.com'), +(2, 'Apple', 'contact@apple.com'); + +CREATE TABLE products ( + id INT PRIMARY KEY, + name VARCHAR(255), + price DECIMAL(10,2), + description TEXT, + vendor_id INT, + FOREIGN KEY (vendor_id) REFERENCES vendors(id) +); + +INSERT INTO products VALUES +(1001, 'Samsung Galaxy S24', 999.99, 'Flagship phone with AI camera', 1), +(1002, 'Apple iPhone 15 Pro', 1099.00, 'New titanium design', 2); + +CREATE TABLE product_reviews ( + review_id INT PRIMARY KEY, + product_id INT, + rating INT CHECK (rating BETWEEN 1 AND 5), + comment TEXT, + FOREIGN KEY (product_id) REFERENCES products(id) +); + +INSERT INTO product_reviews VALUES +(1, 1001, 5, 'Amazing camera'), +(2, 1001, 4, 'Great battery life'), +(3, 1002, 5, 'Best iPhone yet'); diff --git a/ballerina/tests/listener_tests.bal b/ballerina/tests/listener_tests.bal new file mode 100644 index 0000000..d7ab88d --- /dev/null +++ b/ballerina/tests/listener_tests.bal @@ -0,0 +1,120 @@ +// Copyright (c) 2025, 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/mysql; + +Service mysqlTestService = +@ServiceConfig {tables: "store_db.products"} +service object { + remote function onCreate(record {} after, string tableName = "") returns error? { + createEventCount = createEventCount + 1; + } + + remote function onUpdate(record {} before, record {} after, string tableName = "") returns error? { + updateEventCount = updateEventCount + 1; + } + + remote function onDelete(record {} before, string tableName = "") returns error? { + deleteEventCount = deleteEventCount + 1; + } + + remote function onRead(record {} before, string tableName = "") returns error? { + readEventCount = readEventCount + 1; + } +}; + +Service mysqlDataBindingFailService = +@ServiceConfig {tables: "store_db.vendors"} +service object { + + remote function onCreate(WrongVendor after, string tableName = "") returns error? { + createEventCount = createEventCount + 1; + } + + remote function onError(Error e) returns error? { + onErrorCount = onErrorCount + 1; + } +}; + +type WrongVendor record {| + int test; +|}; + +final mysql:Client mysqlClient = check new (host = "localhost", + port = port, + user = username, + password = password, + database = database +); + +int createEventCount = 0; +int updateEventCount = 0; +int deleteEventCount = 0; +int readEventCount = 0; +int onErrorCount = 0; + +@test:Config { +} +function testMockListenerEvents() returns error? { + MockListener testListener = new ({ + database: { + username, + password, + port, + includedDatabases: database, + includedTables: ["store_db.products", "store_db.vendors"] + } + }); + + check testListener.attach(mysqlTestService); + check testListener.attach(mysqlDataBindingFailService); + check testListener.start(); + runtime:sleep(5); + + test:assertEquals(readEventCount, 2, msg = "READ event count mismatch."); + + // Test CREATE event + _ = check mysqlClient->execute( + `INSERT INTO products (id, name, price, description, vendor_id) + VALUES (1103, 'Product A', 10.0, 'testProduct', 1)`); + runtime:sleep(3); + test:assertEquals(createEventCount, 1, msg = "CREATE event count mismatch."); + + // Test UPDATE event + _ = check mysqlClient->execute( + `UPDATE products SET price = 15.0 WHERE id = 1103`); + runtime:sleep(3); + test:assertEquals(updateEventCount, 1, msg = "UPDATE event count mismatch."); + + // Test DELETE event + _ = check mysqlClient->execute( + `DELETE FROM products WHERE id = 1103`); + + runtime:sleep(3); + test:assertEquals(deleteEventCount, 1, msg = "DELETE event count mismatch."); + + // Test CREATE event for vendors table + _ = check mysqlClient->execute( + `INSERT INTO vendors (id, name, contact_info) + VALUES (201, 'Vendor A', 'contact@vendora.com')`); + runtime:sleep(3); + test:assertEquals(onErrorCount, 3, msg = "Error count mismatch."); + // 1,2 for onRead method not present, 3 for payload binding failure + + check testListener.gracefulStop(); +} diff --git a/build.gradle b/build.gradle index b012c61..c3da43e 100644 --- a/build.gradle +++ b/build.gradle @@ -85,6 +85,11 @@ subprojects { ballerinaStdLibs "io.ballerina.lib:data.jsondata-ballerina:${stdlibDataJsonDataVersion}" + ballerinaStdLibs "io.ballerina.stdlib:sql-ballerina:${stdlibSqlVersion}" + ballerinaStdLibs "io.ballerina.stdlib:mysql-ballerina:${stdlibMySqlVersion}" + + ballerinaStdLibs "io.ballerina.lib:mysql.cdc.driver-ballerina:${stdlibMysqlCdcDriverVersion}" + ballerinaStdLibs "io.ballerina.stdlib:observe-ballerina:${observeVersion}" ballerinaStdLibs "io.ballerina:observe-ballerina:${observeInternalVersion}" } diff --git a/gradle.properties b/gradle.properties index 3c952c5..376ae8a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,6 +26,12 @@ stdlibTimeVersion=2.7.0 stdlibCryptoVersion=2.9.0 stdlibRandomVersion=1.7.0 +stdlibSqlVersion=1.16.0 +stdlibMySqlVersion=1.15.0 + # Ballerinax ObseObserver observeVersion=1.5.0 observeInternalVersion=1.5.0 + +# Ballerina library +stdlibMysqlCdcDriverVersion=1.0.0 From 95d9a1e15efdd7598dd0fdcc49a3f0fe20051716 Mon Sep 17 00:00:00 2001 From: Niveathika Date: Tue, 20 May 2025 12:28:16 +0530 Subject: [PATCH 03/11] Move to stable version --- ballerina/Ballerina.toml | 8 ++++---- ballerina/CompilerPlugin.toml | 2 +- ballerina/Dependencies.toml | 2 +- gradle.properties | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 9fa350e..7e95292 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerinax" name = "cdc" -version = "0.1.0" +version = "1.0.0" distribution = "2201.12.0" authors = ["Ballerina"] repository = "https://github.com/ballerina-platform/module-ballerinax-cdc" @@ -15,9 +15,9 @@ graalvmCompatible=true [[platform.java21.dependency]] groupId = "io.ballerina.lib.cdc" -artifactId = "cdc-native-0.1.0-SNAPSHOT" -version = "0.1.0-SNAPSHOT" -path = "../native/build/libs/cdc-native-0.1.0-SNAPSHOT.jar" +artifactId = "cdc-native-1.0.0-SNAPSHOT" +version = "1.0.0-SNAPSHOT" +path = "../native/build/libs/cdc-native-1.0.0-SNAPSHOT.jar" [[platform.java21.dependency]] groupId = "io.debezium" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 6294b34..7a5b77c 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,4 +3,4 @@ id = "cdc-compiler-plugin" class = "io.ballerina.lib.cdc.compiler.CdcCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/cdc-compiler-plugin-0.1.0-SNAPSHOT.jar" +path = "../compiler-plugin/build/libs/cdc-compiler-plugin-1.0.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1e31cf6..3361197 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -174,7 +174,7 @@ modules = [ [[package]] org = "ballerinax" name = "cdc" -version = "0.1.0" +version = "1.0.0" dependencies = [ {org = "ballerina", name = "crypto"}, {org = "ballerina", name = "data.jsondata"}, diff --git a/gradle.properties b/gradle.properties index 376ae8a..76452bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.lib -version=0.1.0-SNAPSHOT +version=1.0.0-SNAPSHOT checkstylePluginVersion=10.12.0 spotbugsPluginVersion=6.0.18 From eb2fea21548116d603f16b9d26d66e0497aa347c Mon Sep 17 00:00:00 2001 From: Niveathika Date: Tue, 20 May 2025 12:28:28 +0530 Subject: [PATCH 04/11] Add maven publishing configs --- ballerina/build.gradle | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index d29a17a..98da8a7 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -141,10 +141,29 @@ tasks.register('stopMySQLServer') { } } +publishing { + publications { + maven(MavenPublication) { + artifact source: createArtifactZip, extension: 'zip' + } + } + repositories {maven { + name = 'GitHubPackages' + url = uri("https://maven.pkg.github.com/ballerina-platform/module-${packageOrg}-${packageName}") + credentials { + username = System.getenv('publishUser') + password = System.getenv('publishPAT') + } + } + } +} + +updateTomlFiles.dependsOn copyStdlibs +build.dependsOn "generatePomFileForMavenPublication" + test.dependsOn "startMySQLServer" test.finalizedBy "stopMySQLServer" -updateTomlFiles.dependsOn copyStdlibs build.dependsOn ":${packageName}-native:build" build.dependsOn ":${packageName}-compiler-plugin:build" build.finalizedBy ":${packageName}-compiler-plugin-tests:build" From 05674952427b50e54d6850c3d00d1ef6d71e0b40 Mon Sep 17 00:00:00 2001 From: Niveathika Date: Wed, 21 May 2025 11:29:03 +0530 Subject: [PATCH 05/11] Enable test cases --- ballerina/tests/dynamic_attachment.bal | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ballerina/tests/dynamic_attachment.bal b/ballerina/tests/dynamic_attachment.bal index 02be7f5..baa16ee 100644 --- a/ballerina/tests/dynamic_attachment.bal +++ b/ballerina/tests/dynamic_attachment.bal @@ -90,9 +90,7 @@ function testStartWithServicesWithSameAnnotation() returns error? { check mysqlListener.detach(service1); } -@test:Config { - enable: false -} +@test:Config {} function testAttachAfterStart() returns error? { MockListener mysqlListener = new ({ database: { @@ -113,7 +111,6 @@ function testAttachAfterStart() returns error? { } @test:Config { - enable: false } function testDetachAfterStart() returns error? { MockListener mysqlListener = new ({ From 2dbdf25281e254a7b7281d9cb24d6ac994e3ecd7 Mon Sep 17 00:00:00 2001 From: Niveathika Date: Mon, 28 Apr 2025 00:00:06 +0530 Subject: [PATCH 06/11] Add examples --- .gitignore | 2 + README.md | 5 + ballerina/README.md | 6 + examples/README.md | 41 +++++ examples/cache-management/.github/README.md | 1 + examples/cache-management/Ballerina.toml | 5 + examples/cache-management/Cache Management.md | 78 ++++++++++ .../cache-management/db-scripts/setup.sql | 38 +++++ examples/cache-management/db-scripts/test.sql | 14 ++ examples/cache-management/docker-compose.yml | 31 ++++ examples/cache-management/main.bal | 147 ++++++++++++++++++ examples/fraud-detection/.github/README.md | 1 + examples/fraud-detection/Ballerina.toml | 5 + examples/fraud-detection/Fraud Detection.md | 90 +++++++++++ examples/fraud-detection/db-scripts/setup.sql | 17 ++ examples/fraud-detection/db-scripts/test.sql | 7 + examples/fraud-detection/docker-compose.yml | 20 +++ examples/fraud-detection/main.bal | 81 ++++++++++ 18 files changed, 589 insertions(+) create mode 100644 examples/README.md create mode 120000 examples/cache-management/.github/README.md create mode 100644 examples/cache-management/Ballerina.toml create mode 100644 examples/cache-management/Cache Management.md create mode 100644 examples/cache-management/db-scripts/setup.sql create mode 100644 examples/cache-management/db-scripts/test.sql create mode 100644 examples/cache-management/docker-compose.yml create mode 100644 examples/cache-management/main.bal create mode 120000 examples/fraud-detection/.github/README.md create mode 100644 examples/fraud-detection/Ballerina.toml create mode 100644 examples/fraud-detection/Fraud Detection.md create mode 100644 examples/fraud-detection/db-scripts/setup.sql create mode 100644 examples/fraud-detection/db-scripts/test.sql create mode 100644 examples/fraud-detection/docker-compose.yml create mode 100644 examples/fraud-detection/main.bal diff --git a/.gitignore b/.gitignore index 0f2eb75..9aabdb1 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,5 @@ examples/**/Config.toml # Environment files *.env + +examples/*/tmp diff --git a/README.md b/README.md index 17a62cf..ff8df20 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,11 @@ bal run ## Examples +The `cdc` module provides practical examples illustrating its usage in various real-world scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples) to understand how to capture and process database change events effectively. + +1. [Fraud Detection](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples/fraud-detection) - Detect suspicious transactions in a financial database and send fraud alerts via email. This example showcases how to integrate the CDC module with the Gmail connector to notify stakeholders of potential fraud. + +2. [Cache Management](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples/cache-management) - Synchronize a Redis cache with changes in a MySQL database. It listens to changes in the `products`, `vendors`, and `product_reviews` tables and updates the Redis cache accordingly. ## Issues and projects diff --git a/ballerina/README.md b/ballerina/README.md index eac1f47..0f2f37a 100644 --- a/ballerina/README.md +++ b/ballerina/README.md @@ -75,3 +75,9 @@ bal run ``` ## Examples + +The `cdc` module provides practical examples illustrating its usage in various real-world scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples) to understand how to capture and process database change events effectively. + +1. [Fraud Detection](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples/fraud-detection) - Detect suspicious transactions in a financial database and send fraud alerts via email. This example showcases how to integrate the CDC module with the Gmail connector to notify stakeholders of potential fraud. + +2. [Cache Management](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples/cache-management) - Synchronize a Redis cache with changes in a MySQL database. It listens to changes in the `products`, `vendors`, and `product_reviews` tables and updates the Redis cache accordingly. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..bd51766 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,41 @@ +# Examples + +The `cdc` module provides practical examples illustrating its usage in various real-world scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples) to understand how to capture and process database change events effectively. + +1. [Fraud Detection](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples/fraud-detection) - Detect suspicious transactions in a financial database and send fraud alerts via email. This example showcases how to integrate the CDC module with the Gmail connector to notify stakeholders of potential fraud. + +2. [Cache Management](https://github.com/ballerina-platform/module-ballerinax-cdc/tree/main/examples/cache-management) - Synchronize a Redis cache with changes in a MySQL database. It listens to changes in the `products`, `vendors`, and `product_reviews` tables and updates the Redis cache accordingly. + +## Running an Example + +Execute the following commands to build an example from the source: + +* To build an example: + + ```bash + bal build + ``` + +* To run an example: + + ```bash + bal run + ``` + +## Building the Examples with the Local Module + +**Warning**: Due to the absence of support for reading local repositories for single Ballerina files, the Bala of the module is manually written to the central repository as a workaround. Consequently, the bash script may modify your local Ballerina repositories. + +Execute the following commands to build all the examples against the changes you have made to the module locally: + +* To build all the examples: + + ```bash + ./build.sh build + ``` + +* To run all the examples: + + ```bash + ./build.sh run + ``` diff --git a/examples/cache-management/.github/README.md b/examples/cache-management/.github/README.md new file mode 120000 index 0000000..7a8e27c --- /dev/null +++ b/examples/cache-management/.github/README.md @@ -0,0 +1 @@ +../Cache Management.md \ No newline at end of file diff --git a/examples/cache-management/Ballerina.toml b/examples/cache-management/Ballerina.toml new file mode 100644 index 0000000..08753ed --- /dev/null +++ b/examples/cache-management/Ballerina.toml @@ -0,0 +1,5 @@ +[package] +org = "wso2" +name = "cache_management" +version = "0.1.0" +distribution = "2201.12.2" diff --git a/examples/cache-management/Cache Management.md b/examples/cache-management/Cache Management.md new file mode 100644 index 0000000..ddacf98 --- /dev/null +++ b/examples/cache-management/Cache Management.md @@ -0,0 +1,78 @@ +# Cache Management + +This example demonstrates how to use the Ballerina Change Data Capture (CDC) module to synchronize a Redis cache with changes in a MySQL database. It listens to changes in the `products`, `vendors`, and `product_reviews` tables and updates the Redis cache accordingly. + +## Setup Guide + +### 1. MySQL Database + +1. Refer to the [Setup Guide](https://central.ballerina.io/ballerinax/mysql/latest#setup-guide) for the necessary steps to enable CDC in the MySQL server. + +2. Add the necessary schema and data using the `setup.sql` script: + ```bash + mysql -u -p < db_scripts/setup.sql + ``` + +### 2. Redis Server + +Ensure a Redis server is running on `localhost:6379`. + +### 3. Configuration + +Configure MySQL database credentials in the `Config.toml` file located in the example directory: + +```toml +username = "" +password = "" +``` + +Replace `` and `` with your MySQL database credentials. + +## Setup Guide: Using Docker Compose + +You can use Docker Compose to set up both MySQL and Redis services for this example. Follow these steps: + +### 1. Start the services + +Run the following command to start both MySQL and Redis services: + +```bash +docker-compose up -d +``` + +### 2. Verify the services + +Ensure both `mysql` and `redis` services are in a healthy state: + +```bash +docker-compose ps +``` + +### 3. Configuration + +Ensure the `Config.toml` file is updated with the following credentials: + +```toml +username = "cdc_user" +password = "cdc_password" +``` + +## Run the Example + +1. Execute the following command to run the example: + + ```bash + bal run + ``` + +2. Use the provided `test.sql` script to insert sample transactions into the `products`, `vendors`, and `product_reviews` tables to test the synchronization. Run the following command: + + ```bash + mysql -u -p < db_scripts/test.sql + ``` + +If using docker services, + + ```bash + docker exec -i mysql-cdc mysql -u cdc_user -pcdc_password < db-scripts/test.sql + ``` diff --git a/examples/cache-management/db-scripts/setup.sql b/examples/cache-management/db-scripts/setup.sql new file mode 100644 index 0000000..1093cb2 --- /dev/null +++ b/examples/cache-management/db-scripts/setup.sql @@ -0,0 +1,38 @@ +CREATE DATABASE IF NOT EXISTS store_db; +USE store_db; + +CREATE TABLE vendors ( + id INT PRIMARY KEY, + name VARCHAR(255), + contact_info TEXT +); + +INSERT INTO vendors VALUES +(1, 'Samsung', 'contact@samsung.com'), +(2, 'Apple', 'contact@apple.com'); + +CREATE TABLE products ( + id INT PRIMARY KEY, + name VARCHAR(255), + price DECIMAL(10,2), + description TEXT, + vendor_id INT, + FOREIGN KEY (vendor_id) REFERENCES vendors(id) +); + +INSERT INTO products VALUES +(1001, 'Samsung Galaxy S24', 999.99, 'Flagship phone with AI camera', 1), +(1002, 'Apple iPhone 15 Pro', 1099.00, 'New titanium design', 2); + +CREATE TABLE product_reviews ( + review_id INT PRIMARY KEY, + product_id INT, + rating INT CHECK (rating BETWEEN 1 AND 5), + comment TEXT, + FOREIGN KEY (product_id) REFERENCES products(id) +); + +INSERT INTO product_reviews VALUES +(1, 1001, 5, 'Amazing camera'), +(2, 1001, 4, 'Great battery life'), +(3, 1002, 5, 'Best iPhone yet'); diff --git a/examples/cache-management/db-scripts/test.sql b/examples/cache-management/db-scripts/test.sql new file mode 100644 index 0000000..c693ef8 --- /dev/null +++ b/examples/cache-management/db-scripts/test.sql @@ -0,0 +1,14 @@ +USE store_db; + +UPDATE products +SET price = price * 0.9 +WHERE id = 1002; + +UPDATE product_reviews +SET rating = rating - 1 +WHERE product_id = 1002; + +INSERT products VALUES (1003, "Samsung Galaxy S20", 499.99, "Old Smartphone", 2); + +DELETE FROM products +WHERE id = 1003; \ No newline at end of file diff --git a/examples/cache-management/docker-compose.yml b/examples/cache-management/docker-compose.yml new file mode 100644 index 0000000..fff59ad --- /dev/null +++ b/examples/cache-management/docker-compose.yml @@ -0,0 +1,31 @@ +name: cache-management-example + +services: + mysql: + image: mysql:8.0 + container_name: mysql-cdc + ports: + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: store_db + MYSQL_USER: cdc_user + MYSQL_PASSWORD: cdc_password + volumes: + - ./db-scripts/setup.sql:/docker-entrypoint-initdb.d/setup.sql + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:latest + container_name: redis-cache + ports: + - "6379:6379" + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: 10s + timeout: 5s + retries: 5 diff --git a/examples/cache-management/main.bal b/examples/cache-management/main.bal new file mode 100644 index 0000000..7f383c7 --- /dev/null +++ b/examples/cache-management/main.bal @@ -0,0 +1,147 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org). +// +// 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/log; +import ballerina/os; +import ballerinax/cdc; +import ballerinax/mysql; +import ballerinax/mysql.cdc.driver as _; +import ballerinax/redis; + +configurable string username = os:getEnv("DB_USERNAME"); +configurable string password = os:getEnv("DB_PASSWORD"); + +listener mysql:CdcListener mysqlListener = new ( + database = { + username, + password, + includedDatabases: "store_db", + includedTables: ["store_db.products", "store_db.vendors", "store_db.product_reviews"] + }, + options = { + snapshotMode: cdc:NO_DATA + } +); + +final redis:Client redis = check new ( + connection = { + host: "localhost", + port: 6379 + } +); + +type Entity record { + int id; +}; + +type ProductReviews record { + int product_id; + int rating; +}; + +@cdc:ServiceConfig { + tables: ["store_db.products", "store_db.vendors"] +} +service cdc:Service on mysqlListener { + + remote function onRead(Entity after, string tableName) returns error? { + _ = check redis->set(string `${tableName}:${after.id}`, after.toJsonString()); + log:printInfo(`'${tableName}' cache entry created for Id: ${after.id}`); + } + + remote function onCreate(Entity after, string tableName) returns error? { + _ = check redis->set(string `product:${after.id}`, after.toJsonString()); + log:printInfo(`'${tableName}' cache entry created for Id: ${after.id}`); + } + + remote function onUpdate(Entity before, Entity after, string tableName) returns error? { + _ = check redis->set(string `product:${after.id}`, after.toJsonString()); + log:printInfo(`'${tableName}' cache entry updated for Id: ${after.id}.`); + } + + remote function onDelete(Entity before, string tableName) returns error? { + int delVal = check redis->del([ + string `${tableName}:${before.id}` + ]); + if tableName == "products" { + _ = check redis->del([ + string `product_tot_rating:${before.id}`, + string `product_reviews:${before.id}` + ]); + log:printInfo(`'products' cache entry deleted for Id: ${before.id}. Redis delete count: ${delVal}`); + } else { + log:printInfo(`'vendors' cache entry deleted for Id: ${before.id}. Redis delete count: ${delVal}`); + } + } + + remote function onError(cdc:Error 'error) returns error? { + log:printInfo(`Error occurred while processing events. Error: ${'error.message()}`); + if 'error is cdc:PayloadBindingError { + log:printInfo(`Error occurred while processing events. Error: ${'error.detail().payload.toBalString()}`); + } + } +} + +@cdc:ServiceConfig { + tables: ["store_db.product_reviews"] +} +service cdc:Service on mysqlListener { + + remote function onRead(ProductReviews after, string tableName) returns error? { + int totalRating = check redis->incrBy(string `product_tot_rating:${after.product_id}`, after.rating); + log:printInfo(`'product_tot_rating' cache added for Product Id: ${after.product_id}. Current total rating: ${totalRating}`); + + int reviews = check redis->incr(string `product_reviews:${after.product_id}`); + log:printInfo(`'product_reviews' cache entry added for Product Id: ${after.product_id}. Current total reviews: ${reviews}`); + } + + remote function onCreate(ProductReviews after, string tableName) returns error? { + int totalRating = check redis->incrBy(string `product_tot_rating:${after.product_id}`, after.rating); + log:printInfo(`'product_tot_rating' cache added for Product Id: ${after.product_id}. Current total rating: ${totalRating}`); + + int reviews = check redis->incr(string `product_reviews:${after.product_id}`); + log:printInfo(`'product_reviews' cache entry added for Product Id: ${after.product_id}. Current total reviews: ${reviews}`); + } + + remote function onUpdate(ProductReviews before, ProductReviews after, string tableName) returns error? { + int ratingDiff = after.rating - before.rating; + + if ratingDiff > 0 { + int updatedRating = check redis->incrBy(string `product_tot_rating:${after.product_id}`, ratingDiff); + log:printInfo(`'product_tot_rating' cache updated for Product Id: ${after.product_id}. Current total rating: ${updatedRating}`); + return; + } + + if ratingDiff < 0 { + int updatedRating = check redis->decrBy(string `product_tot_rating:${after.product_id}`, ratingDiff); + log:printInfo(`'product_tot_rating' cache updated for Product Id: ${after.product_id}. Current total rating: ${updatedRating}`); + return; + } + log:printInfo(`No change in rating for Product ID: ${after.product_id} from table '${tableName}'`); + + } + + remote function onDelete(ProductReviews before, string tableName) returns error? { + int deletedRating = check redis->decrBy(string `product_tot_rating:${before.product_id}`, before.rating); + log:printInfo(`'product_tot_rating' cache deleted for Product Id: ${before.product_id}. Current total rating: ${deletedRating}`); + int reviews = check redis->decr(string `product_reviews:${before.product_id}`); + log:printInfo(`'product_reviews' cache entry deleted for Product Id: ${before.product_id}. Current total reviews: ${reviews}`); + } + + remote function onError(cdc:Error 'error) returns error? { + log:printInfo(`Error occurred while processing events. Error: ${'error.message()}`); + } +} diff --git a/examples/fraud-detection/.github/README.md b/examples/fraud-detection/.github/README.md new file mode 120000 index 0000000..01fef8d --- /dev/null +++ b/examples/fraud-detection/.github/README.md @@ -0,0 +1 @@ +../Fraud Detection.md \ No newline at end of file diff --git a/examples/fraud-detection/Ballerina.toml b/examples/fraud-detection/Ballerina.toml new file mode 100644 index 0000000..a6c9681 --- /dev/null +++ b/examples/fraud-detection/Ballerina.toml @@ -0,0 +1,5 @@ +[package] +org = "wso2" +name = "fraud_detection" +version = "0.1.0" +distribution = "2201.12.2" diff --git a/examples/fraud-detection/Fraud Detection.md b/examples/fraud-detection/Fraud Detection.md new file mode 100644 index 0000000..204c093 --- /dev/null +++ b/examples/fraud-detection/Fraud Detection.md @@ -0,0 +1,90 @@ +# Fraud Detection + +This example demonstrates how to use the Ballerina CDC module to implement a fraud detection system. The system listens to table changes and processes them to identify potential fraudulent activities. + +## Setup Guide + +### 1. MySQL Database + +1. Refer to the [Setup Guide](https://central.ballerina.io/ballerinax/mysql/latest#setup-guide) for the necessary steps to enable CDC in the MySQL server. + +2. Add the necessary schema and data using the `setup.sql` script: + ```bash + mysql -u -p < db_scripts/setup.sql + ``` + +### 2. Configuration + +Configure MySQL Database and Gmail API credentials in the `Config.toml` file located in the example directory: + +```toml +username = "" +password = "" + +refreshToken = "" +clientId = "" +clientSecret = "" +recipient = "" +sender = "" +``` + +Replace `` and `` with your MySQL database credentials. + +Replace the Gmail API placeholders (``, ``, ``, ``, ``) with your Gmail API credentials and email addresses. + +## Setup Guide: Using Docker Compose + +You can use Docker Compose to set up MySQL for this example. Follow these steps: + +### 1. Start the service + +Run the following command to start the MySQL service: + +```bash +docker-compose up -d +``` + +### 2. Verify the service + +Ensure `mysql` service is in a healthy state: + +```bash +docker-compose ps +``` + +### 3. Configuration + +Ensure the `Config.toml` file is updated with the following credentials: + +```toml +username = "cdc_user" +password = "cdc_password" + +refreshToken = "" +clientId = "" +clientSecret = "" +recipient = "" +sender = "" +``` + +Replace the Gmail API placeholders (``, ``, ``, ``, ``) with your Gmail API credentials and email addresses. + +## Run the Example + +1. Execute the following command to run the example: + + ```bash + bal run + ``` + +2. Use the provided `test.sql` script to insert a sample transactions into the `trx` table to test the fraud detection system. Use the following SQL command: + + ```bash + mysql -u -p < db_scripts/test.sql + ``` + +If using docker services, + + ```bash + docker exec -i mysql-cdc mysql -u cdc_user -pcdc_password < db-scripts/test.sql + ``` diff --git a/examples/fraud-detection/db-scripts/setup.sql b/examples/fraud-detection/db-scripts/setup.sql new file mode 100644 index 0000000..6110253 --- /dev/null +++ b/examples/fraud-detection/db-scripts/setup.sql @@ -0,0 +1,17 @@ +CREATE DATABASE IF NOT EXISTS finance_db; +USE finance_db; + +-- transactions table +CREATE TABLE transactions ( + tx_id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT, + amount DECIMAL(10,2), + status VARCHAR(50), + created_at DATETIME +); + +-- Sample data +INSERT INTO transactions (user_id, amount, status, created_at) VALUES +(10, 9000.00, 'COMPLETED', '2025-04-01 08:00:00'), +(11, 12000.00, 'COMPLETED', '2025-04-01 08:10:00'), -- this one should trigger fraud logic +(12, 4500.00, 'PENDING', '2025-04-01 08:30:00'); diff --git a/examples/fraud-detection/db-scripts/test.sql b/examples/fraud-detection/db-scripts/test.sql new file mode 100644 index 0000000..8fb542b --- /dev/null +++ b/examples/fraud-detection/db-scripts/test.sql @@ -0,0 +1,7 @@ +USE finance_db; + +INSERT INTO transactions (user_id, amount, status, created_at) VALUES +(11, 2000.00, 'COMPLETED', '2025-04-01 08:10:00'); + +INSERT INTO transactions (user_id, amount, status, created_at) VALUES +(11, 12000.00, 'COMPLETED', '2025-04-01 08:10:00'); diff --git a/examples/fraud-detection/docker-compose.yml b/examples/fraud-detection/docker-compose.yml new file mode 100644 index 0000000..4ddbd36 --- /dev/null +++ b/examples/fraud-detection/docker-compose.yml @@ -0,0 +1,20 @@ +name: fraud-detection-example + +services: + mysql: + image: mysql:8.0 + container_name: mysql-cdc + ports: + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: finance_db + MYSQL_USER: cdc_user + MYSQL_PASSWORD: cdc_password + volumes: + - ./db-scripts/setup.sql:/docker-entrypoint-initdb.d/setup.sql + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] + interval: 10s + timeout: 5s + retries: 5 diff --git a/examples/fraud-detection/main.bal b/examples/fraud-detection/main.bal new file mode 100644 index 0000000..2c69626 --- /dev/null +++ b/examples/fraud-detection/main.bal @@ -0,0 +1,81 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org). +// +// 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/log; +import ballerina/os; +import ballerinax/cdc; +import ballerinax/mysql; +import ballerinax/mysql.cdc.driver as _; +import ballerinax/googleapis.gmail; + +configurable string refreshToken = os:getEnv("REFRESH_TOKEN"); +configurable string clientId = os:getEnv("CLIENT_ID"); +configurable string clientSecret = os:getEnv("CLIENT_SECRET"); +configurable string recipient = os:getEnv("RECIPIENT"); +configurable string sender = os:getEnv("SENDER"); + +configurable string username = os:getEnv("DB_USERNAME"); +configurable string password = os:getEnv("DB_PASSWORD"); + +listener mysql:CdcListener financeDBListener = new ( + database = { + username, + password, + includedDatabases: "finance_db", + includedTables: "finance_db.transactions" + }, + options = { + snapshotMode: cdc:NO_DATA, + skippedOperations: [cdc:TRUNCATE, cdc:UPDATE, cdc:DELETE] + } +); + +final gmail:Client gmail = check new ({ + auth: { + refreshToken, + clientId, + clientSecret + } +}); + +service cdc:Service on financeDBListener { + isolated remote function onCreate(Transactions trx) returns error? { + log:printInfo(`Create trx event received Transaction Id: ${trx.tx_id}`); + if trx.amount > 10000.00 { + string fraudAlert = string `Fraud detected! Transaction Id: ${trx.tx_id}, User Id: ${trx.user_id}, Amount: $${trx.amount}`; + + gmail:MessageRequest message = { + to: [recipient], + subject: "Fraud Alert: Suspicious Transaction Detected", + bodyInText: fraudAlert + }; + + gmail:Message sendResult = check gmail->/users/me/messages/send.post(message); + log:printInfo(`Email sent. Message ID: ${sendResult.id}`); + } + } + + isolated remote function onError(cdc:Error e) { + log:printInfo(`Error occurred: ${e.message()}`); + } +} + +type Transactions record {| + int tx_id; + int user_id; + float amount; + string status; + int created_at; +|}; From c830ae8f30a5a152f93ebaf9a15e228b233a8ce0 Mon Sep 17 00:00:00 2001 From: Niveathika Date: Wed, 21 May 2025 15:51:13 +0530 Subject: [PATCH 07/11] Remove example build from the workflows --- .github/workflows/build-with-bal-test-graalvm.yml | 2 ++ .github/workflows/ci.yml | 1 + .github/workflows/daily-build.yml | 1 + .github/workflows/dev-stage-release.yml | 1 + .github/workflows/pull-request.yml | 2 ++ .github/workflows/release.yml | 1 + 6 files changed, 8 insertions(+) diff --git a/.github/workflows/build-with-bal-test-graalvm.yml b/.github/workflows/build-with-bal-test-graalvm.yml index 4f69d4d..bb549e9 100644 --- a/.github/workflows/build-with-bal-test-graalvm.yml +++ b/.github/workflows/build-with-bal-test-graalvm.yml @@ -15,3 +15,5 @@ jobs: if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-connector-template.yml@main secrets: inherit + with: + additional-build-flags: -x :cdc-examples:build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc81c05..8053818 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,3 +16,4 @@ jobs: secrets: inherit with: repo-name: module-ballerinax-cdc + additional-build-flags: -x :cdc-examples:build \ No newline at end of file diff --git a/.github/workflows/daily-build.yml b/.github/workflows/daily-build.yml index a9217cd..30665e3 100644 --- a/.github/workflows/daily-build.yml +++ b/.github/workflows/daily-build.yml @@ -12,3 +12,4 @@ jobs: secrets: inherit with: repo-name: module-ballerinax-cdc + additional-build-flags: -x :cdc-examples:build \ No newline at end of file diff --git a/.github/workflows/dev-stage-release.yml b/.github/workflows/dev-stage-release.yml index b85c07c..a564c4c 100644 --- a/.github/workflows/dev-stage-release.yml +++ b/.github/workflows/dev-stage-release.yml @@ -19,3 +19,4 @@ jobs: secrets: inherit with: environment: ${{ github.event.inputs.environment }} + additional-build-flags: -x :cdc-examples:build \ No newline at end of file diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 47662b5..114b1d0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,3 +12,5 @@ jobs: if: ${{ github.repository_owner == 'ballerina-platform' }} uses: ballerina-platform/ballerina-library/.github/workflows/pr-build-connector-template.yml@main secrets: inherit + with: + additional-build-flags: -x :cdc-examples:build \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3b4b9c..6c456f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,3 +14,4 @@ jobs: with: package-name: cdc package-org: ballerinax + additional-build-flags: -x :cdc-examples:build \ No newline at end of file From ff29ef2b87017b6212949f5def05c36b5f63af0c Mon Sep 17 00:00:00 2001 From: Niveathika Date: Wed, 21 May 2025 20:21:14 +0530 Subject: [PATCH 08/11] Add Eof new line Co-authored-by: Danesh Kuruppu --- .github/workflows/ci.yml | 2 +- .github/workflows/daily-build.yml | 3 ++- .github/workflows/dev-stage-release.yml | 3 ++- .github/workflows/pull-request.yml | 3 ++- .github/workflows/release.yml | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8053818..dd6df4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,4 +16,4 @@ jobs: secrets: inherit with: repo-name: module-ballerinax-cdc - additional-build-flags: -x :cdc-examples:build \ No newline at end of file + additional-build-flags: -x :cdc-examples:build diff --git a/.github/workflows/daily-build.yml b/.github/workflows/daily-build.yml index 30665e3..2ddc187 100644 --- a/.github/workflows/daily-build.yml +++ b/.github/workflows/daily-build.yml @@ -12,4 +12,5 @@ jobs: secrets: inherit with: repo-name: module-ballerinax-cdc - additional-build-flags: -x :cdc-examples:build \ No newline at end of file + additional-build-flags: -x :cdc-examples:build + \ No newline at end of file diff --git a/.github/workflows/dev-stage-release.yml b/.github/workflows/dev-stage-release.yml index a564c4c..0763ca4 100644 --- a/.github/workflows/dev-stage-release.yml +++ b/.github/workflows/dev-stage-release.yml @@ -19,4 +19,5 @@ jobs: secrets: inherit with: environment: ${{ github.event.inputs.environment }} - additional-build-flags: -x :cdc-examples:build \ No newline at end of file + additional-build-flags: -x :cdc-examples:build + \ No newline at end of file diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 114b1d0..4a6bfb0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -13,4 +13,5 @@ jobs: uses: ballerina-platform/ballerina-library/.github/workflows/pr-build-connector-template.yml@main secrets: inherit with: - additional-build-flags: -x :cdc-examples:build \ No newline at end of file + additional-build-flags: -x :cdc-examples:build + \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6c456f0..c51eb40 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,4 +14,5 @@ jobs: with: package-name: cdc package-org: ballerinax - additional-build-flags: -x :cdc-examples:build \ No newline at end of file + additional-build-flags: -x :cdc-examples:build + \ No newline at end of file From 421c8c059b15ea3b10827079ed8d276b6ebc63e0 Mon Sep 17 00:00:00 2001 From: Niveathika Date: Wed, 21 May 2025 20:42:21 +0530 Subject: [PATCH 09/11] Format bal code --- ballerina/tests/mock_listener.bal | 36 +++++++++++++++---------------- ballerina/tests/utils_test.bal | 2 +- examples/fraud-detection/main.bal | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ballerina/tests/mock_listener.bal b/ballerina/tests/mock_listener.bal index fc0f8a3..282fe61 100644 --- a/ballerina/tests/mock_listener.bal +++ b/ballerina/tests/mock_listener.bal @@ -29,25 +29,25 @@ public isolated class MockListener { public isolated function init(*MySqlListenerConfiguration config) { map configMap = {}; populateDebeziumProperties({ - engineName: config.engineName, - offsetStorage: config.offsetStorage, - internalSchemaStorage: config.internalSchemaStorage, - options: config.options - }, configMap); + engineName: config.engineName, + offsetStorage: config.offsetStorage, + internalSchemaStorage: config.internalSchemaStorage, + options: config.options + }, configMap); populateDatabaseConfigurations({ - connectorClass: config.database.connectorClass, - hostname: config.database.hostname, - port: config.database.port, - username: config.database.username, - password: config.database.password, - connectTimeout: config.database.connectTimeout, - tasksMax: config.database.tasksMax, - secure: config.database.secure, - includedTables: config.database.includedTables, - excludedTables: config.database.excludedTables, - includedColumns: config.database.includedColumns, - excludedColumns: config.database.excludedColumns - }, configMap); + connectorClass: config.database.connectorClass, + hostname: config.database.hostname, + port: config.database.port, + username: config.database.username, + password: config.database.password, + connectTimeout: config.database.connectTimeout, + tasksMax: config.database.tasksMax, + secure: config.database.secure, + includedTables: config.database.includedTables, + excludedTables: config.database.excludedTables, + includedColumns: config.database.includedColumns, + excludedColumns: config.database.excludedColumns + }, configMap); configMap["database.server.id"] = "100000"; self.config = configMap.cloneReadOnly(); } diff --git a/ballerina/tests/utils_test.bal b/ballerina/tests/utils_test.bal index 80edea9..e4e9bdd 100644 --- a/ballerina/tests/utils_test.bal +++ b/ballerina/tests/utils_test.bal @@ -79,7 +79,7 @@ function testGetDatabaseDebeziumProperties() { "database.ssl.truststore.password": "", "table.include.list": "", "column.include.list": "ya,tan" - }; + }; DatabaseConnection config = { username: "root", diff --git a/examples/fraud-detection/main.bal b/examples/fraud-detection/main.bal index 2c69626..b242e75 100644 --- a/examples/fraud-detection/main.bal +++ b/examples/fraud-detection/main.bal @@ -16,9 +16,9 @@ import ballerina/log; import ballerina/os; import ballerinax/cdc; +import ballerinax/googleapis.gmail; import ballerinax/mysql; import ballerinax/mysql.cdc.driver as _; -import ballerinax/googleapis.gmail; configurable string refreshToken = os:getEnv("REFRESH_TOKEN"); configurable string clientId = os:getEnv("CLIENT_ID"); From 308c50dd2060f5a68d4bef6d600e5da1bd5af3a4 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Thu, 22 May 2025 12:08:28 +0530 Subject: [PATCH 10/11] Update examples/cache-management/db-scripts/test.sql --- examples/cache-management/db-scripts/test.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cache-management/db-scripts/test.sql b/examples/cache-management/db-scripts/test.sql index c693ef8..14ec9bd 100644 --- a/examples/cache-management/db-scripts/test.sql +++ b/examples/cache-management/db-scripts/test.sql @@ -11,4 +11,4 @@ WHERE product_id = 1002; INSERT products VALUES (1003, "Samsung Galaxy S20", 499.99, "Old Smartphone", 2); DELETE FROM products -WHERE id = 1003; \ No newline at end of file +WHERE id = 1003; From a52d4af4f5bb83d50f2c5bc54f997284d6aef18d Mon Sep 17 00:00:00 2001 From: Niveathika Date: Thu, 22 May 2025 15:07:11 +0530 Subject: [PATCH 11/11] Add mysql driver to build pack --- build.gradle | 1 + gradle.properties | 1 + 2 files changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index c3da43e..6327c86 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,7 @@ subprojects { ballerinaStdLibs "io.ballerina.stdlib:mysql-ballerina:${stdlibMySqlVersion}" ballerinaStdLibs "io.ballerina.lib:mysql.cdc.driver-ballerina:${stdlibMysqlCdcDriverVersion}" + ballerinaStdLibs "io.ballerina.stdlib:mysql.driver-ballerina:${stdlibMysqlDriverVersion}" ballerinaStdLibs "io.ballerina.stdlib:observe-ballerina:${observeVersion}" ballerinaStdLibs "io.ballerina:observe-ballerina:${observeInternalVersion}" diff --git a/gradle.properties b/gradle.properties index 76452bc..f2a4344 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,3 +35,4 @@ observeInternalVersion=1.5.0 # Ballerina library stdlibMysqlCdcDriverVersion=1.0.0 +stdlibMysqlDriverVersion=1.8.0