From c5e956f3988d8536e635f53dd49f3468799d3530 Mon Sep 17 00:00:00 2001 From: Niveathika Date: Fri, 11 Nov 2022 17:59:36 +0530 Subject: [PATCH] Add XA transaction testcases --- ballerina/build.gradle | 27 +-- ballerina/tests/15-xa-transaction.bal | 166 ++++++++++++++++++ .../resources/sql-scripts/run-sql-scripts.sh | 2 + .../xa-transaction-test-data-1.sql | 16 ++ .../xa-transaction-test-data-2.sql | 13 ++ 5 files changed, 212 insertions(+), 12 deletions(-) create mode 100644 ballerina/tests/15-xa-transaction.bal create mode 100644 ballerina/tests/resources/sql-scripts/transaction/xa-transaction-test-data-1.sql create mode 100644 ballerina/tests/resources/sql-scripts/transaction/xa-transaction-test-data-2.sql diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 526efac7..4fcdefe8 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -221,23 +221,26 @@ task startTestDockerContainers() { } } -task stopTestDockerContainers() { - doLast { - if (!Os.isFamily(Os.FAMILY_WINDOWS)) { - def containers = "ballerina-oracledb" - try { - def stdOut = new ByteArrayOutputStream() - exec { - commandLine 'sh', '-c', "docker stop ${containers}" - standardOutput = stdOut - } - } catch (ignore) { - println("Gradle process can safely ignore stopTestDockerContainers task") +def stopTestDockerContainer(containerName) { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + try { + def stdOut = new ByteArrayOutputStream() + exec { + commandLine 'sh', '-c', "docker stop ${containerName}" + standardOutput = stdOut } + } catch (ignore) { + println("Gradle process can safely ignore stopTestDockerContainers task") } } } +task stopTestDockerContainers() { + doLast { + stopTestDockerContainer("ballerina-oracledb") + } +} + updateTomlFiles.dependsOn copyStdlibs startTestDockerContainers.dependsOn createTestDockerImage diff --git a/ballerina/tests/15-xa-transaction.bal b/ballerina/tests/15-xa-transaction.bal new file mode 100644 index 00000000..ea8c4b8f --- /dev/null +++ b/ballerina/tests/15-xa-transaction.bal @@ -0,0 +1,166 @@ +// Copyright (c) 2022 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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; +import ballerina/sql; + +int DBCLIENT2_PORT = 1522; + +type XAResultCount record { + int COUNTVAL; +}; + +// Following test cases are disabled, since they need 2 db instances to run. +// Due to a constraint 2 Oracle Docker containers cannot be run on the same machine. +// Currently verified it manually with a local instance running on port 1522 and docker image running on port 1521 + +@test:Config { + enable: false, + groups: ["transaction", "xa-transaction"] +} +function testXATransactionSuccess() returns error? { + Client dbClient1 = check new (HOST, USER, PASSWORD, DATABASE, PORT, connectionPool = {maxOpenConnections: 1}, + options = { + useXADatasource: true + } + ); + Client dbClient2 = check new (HOST, USER, PASSWORD, DATABASE, DBCLIENT2_PORT, connectionPool = {maxOpenConnections: 1}, + options = { + useXADatasource: true + } + ); + + transaction { + _ = check dbClient1->execute(`insert into Customers (customerId, name, creditLimit, country) + values (1, 'Anne', 1000, 'UK')`); + _ = check dbClient2->execute(`insert into Salary (id, value) values (1, 1000)`); + check commit; + } on fail error e { + test:assertFail(msg = "Transaction failed" + e.message()); + } + + int count1 = check getCustomerCount(dbClient1, 1); + int count2 = check getSalaryCount(dbClient2, 1); + test:assertEquals(count1, 1, "First transaction failed"); + test:assertEquals(count2, 1, "Second transaction failed"); + + check dbClient1.close(); + check dbClient2.close(); +} + +@test:Config { + enable: false, + groups: ["transaction", "xa-transaction"] +} +function testXATransactionFailureWithDataSource() returns error? { + Client dbClient1 = check new (HOST, USER, PASSWORD, DATABASE, PORT, connectionPool = {maxOpenConnections: 1}, + options = { + useXADatasource: true + } + ); + Client dbClient2 = check new (HOST, USER, PASSWORD, DATABASE, DBCLIENT2_PORT, connectionPool = {maxOpenConnections: 1}, + options = { + useXADatasource: true + } + ); + + transaction { + // Intentionally fail first statement + _ = check dbClient1->execute(`insert into CustomersTrx (customerId, name, creditLimit, country) + values (30, 'Anne', 1000, 'UK')`); + _ = check dbClient2->execute(`insert into Salary (id, value) values (10, 1000)`); + check commit; + } on fail error e { + test:assertTrue(e.message().includes("Duplicate"), msg = "Transaction failed as expected"); + } + + int count1 = check getCustomerTrxCount(dbClient1, 30); + int count2 = check getSalaryCount(dbClient2, 20); + test:assertEquals(count1, 1, "First transaction should have failed"); + test:assertEquals(count2, 0, "Second transaction should not have been executed"); + + check dbClient1.close(); + check dbClient2.close(); +} + +@test:Config { + enable: false, + groups: ["transaction", "xa-transaction"] +} +function testXATransactionPartialSuccessWithDataSource() returns error? { + Client dbClient1 = check new (HOST, USER, PASSWORD, DATABASE, PORT, connectionPool = {maxOpenConnections: 1}, + options = { + useXADatasource: true + } + ); + Client dbClient2 = check new (HOST, USER, PASSWORD, DATABASE, DBCLIENT2_PORT, connectionPool = {maxOpenConnections: 1}, + options = { + useXADatasource: true + } + ); + + transaction { + _ = check dbClient1->execute(`insert into Customers (customerId, name, creditLimit, country) + values (30, 'Anne', 1000, 'UK')`); + // Intentionally fail second statement + _ = check dbClient2->execute(`insert into SalaryTrx (id, value) values (20, 1000)`); + check commit; + } on fail error e { + test:assertTrue(e.message().includes("Duplicate"), msg = "Transaction failed as expected"); + } + + int count1 = check getCustomerCount(dbClient1, 30); + int count2 = check getSalaryTrxCount(dbClient2, 20); + test:assertEquals(count1, 0, "First transaction is not rolledback"); + test:assertEquals(count2, 1, "Second transaction has succeeded"); + + check dbClient1.close(); + check dbClient2.close(); +} + +isolated function getCustomerCount(Client dbClient, int id) returns int|error { + stream streamData = dbClient->query(`Select COUNT(*) as + countVal from Customers where customerId = ${id}`); + return getResult(streamData); +} + +isolated function getCustomerTrxCount(Client dbClient, int id) returns int|error { + stream streamData = dbClient->query(`Select COUNT(*) as + countVal from CustomersTrx where customerId = ${id}`); + return getResult(streamData); +} + +isolated function getSalaryCount(Client dbClient, int id) returns int|error { + stream streamData = dbClient->query(`Select COUNT(*) as countval + from Salary where id = ${id}`); + return getResult(streamData); +} + +isolated function getSalaryTrxCount(Client dbClient, int id) returns int|error { + stream streamData = dbClient->query(`Select COUNT(*) as countval + from SalaryTrx where id = ${id}`); + return getResult(streamData); +} + +isolated function getResult(stream streamData) returns int|error { + record {|XAResultCount value;|}? data = check streamData.next(); + check streamData.close(); + XAResultCount? value = data?.value; + if value is XAResultCount { + return value.COUNTVAL; + } + return 0; +} diff --git a/ballerina/tests/resources/sql-scripts/run-sql-scripts.sh b/ballerina/tests/resources/sql-scripts/run-sql-scripts.sh index fb7108cb..6c17a93a 100755 --- a/ballerina/tests/resources/sql-scripts/run-sql-scripts.sh +++ b/ballerina/tests/resources/sql-scripts/run-sql-scripts.sh @@ -10,4 +10,6 @@ sqlplus -S admin/password@localhost/ORCLCDB.localdomain <<< @procedures/stored-p sqlplus -S admin/password@localhost/ORCLCDB.localdomain <<< @query/complex-params-test-data.sql sqlplus -S admin/password@localhost/ORCLCDB.localdomain <<< @query/simple-params-test-data.sql sqlplus -S admin/password@localhost/ORCLCDB.localdomain <<< @transaction/local-transaction-test-data.sql +sqlplus -S admin/password@localhost/ORCLCDB.localdomain <<< @transaction/xa-transaction-test-data-1.sql +sqlplus -S admin/password@localhost/ORCLCDB.localdomain <<< @transaction/xa-transaction-test-data-2.sql sqlplus -S admin/password@localhost/ORCLCDB.localdomain <<< @error/error-test-data.sql diff --git a/ballerina/tests/resources/sql-scripts/transaction/xa-transaction-test-data-1.sql b/ballerina/tests/resources/sql-scripts/transaction/xa-transaction-test-data-1.sql new file mode 100644 index 00000000..c083559e --- /dev/null +++ b/ballerina/tests/resources/sql-scripts/transaction/xa-transaction-test-data-1.sql @@ -0,0 +1,16 @@ +CREATE TABLE Customers( + customerId NUMBER, + name VARCHAR(300), + creditLimit DOUBLE PRECISION, + country VARCHAR(300) +); + +CREATE TABLE CustomersTrx( + customerId INTEGER, + name VARCHAR(300), + creditLimit DOUBLE PRECISION, + country VARCHAR(300), + PRIMARY KEY (customerId) +); + +INSERT INTO CustomersTrx VALUES (30, 'Oliver', 200000, 'UK'); diff --git a/ballerina/tests/resources/sql-scripts/transaction/xa-transaction-test-data-2.sql b/ballerina/tests/resources/sql-scripts/transaction/xa-transaction-test-data-2.sql new file mode 100644 index 00000000..ce5cc549 --- /dev/null +++ b/ballerina/tests/resources/sql-scripts/transaction/xa-transaction-test-data-2.sql @@ -0,0 +1,13 @@ +CREATE TABLE Salary ( + ID INTEGER, + VALUE DOUBLE PRECISION +); + +CREATE TABLE SalaryTrx ( + ID INTEGER, + VALUE DOUBLE PRECISION, + PRIMARY KEY (ID) +); + +INSERT INTO SalaryTrx VALUES (20, 30000); +