From b41be13df603f7eccdcdd0dd38c916c81ef0e84d Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Mon, 14 Oct 2024 12:40:41 +0530 Subject: [PATCH 1/2] Fix stored procedure call having output parameter failing with error --- ballerina/tests/call-procedures-test.bal | 161 ++++++++++++------ changelog.md | 2 + docs/spec/spec.md | 1 + .../io/ballerina/stdlib/sql/Constants.java | 3 + .../stdlib/sql/nativeimpl/CallProcessor.java | 127 ++------------ .../sql/nativeimpl/OutParameterProcessor.java | 143 ++++++++++++++++ 6 files changed, 269 insertions(+), 168 deletions(-) diff --git a/ballerina/tests/call-procedures-test.bal b/ballerina/tests/call-procedures-test.bal index bd51a717d..af52d359c 100644 --- a/ballerina/tests/call-procedures-test.bal +++ b/ballerina/tests/call-procedures-test.bal @@ -130,15 +130,16 @@ function testCallWithStringTypesOutParams() returns error? { ParameterizedCallQuery callProcedureQuery = `call SelectStringDataWithOutParams(${paraID}, ${paraVarchar}, ${paraCharmax}, ${paraChar}, ${paraCharactermax}, ${paraCharacter}, ${paraNvarcharmax})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); test:assertEquals(paraVarchar.get(string), "test0", "2nd out parameter of procedure did not match."); test:assertEquals(paraCharmax.get(string), "test1 ", "3rd out parameter of procedure did not match."); test:assertEquals(paraChar.get(string), "a", "4th out parameter of procedure did not match."); test:assertEquals(paraCharactermax.get(string), "test2 ", "5th out parameter of procedure did not match."); test:assertEquals(paraCharacter.get(string), "b", "6th out parameter of procedure did not match."); test:assertEquals(paraNvarcharmax.get(string), "test3", "7th out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -162,9 +163,8 @@ function testCallWithNumericTypesOutParams() returns error? { ${paraBigInt}, ${paraSmallInt}, ${paraTinyInt}, ${paraBit}, ${paraDecimal}, ${paraNumeric}, ${paraFloat}, ${paraReal}, ${paraDouble})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); decimal paraDecimalVal = 1234.56; test:assertEquals(paraInt.get(int), 2147483647, "2nd out parameter of procedure did not match."); @@ -177,6 +177,8 @@ function testCallWithNumericTypesOutParams() returns error? { test:assertTrue((check paraFloat.get(float)) > 1234.0, "9th out parameter of procedure did not match."); test:assertTrue((check paraReal.get(float)) > 1234.0, "10th out parameter of procedure did not match."); test:assertEquals(paraDouble.get(float), 1234.56, "11th out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -200,9 +202,8 @@ function testCallWithNumericTypesOutParamsForInvalidInValue() returns error? { ${paraBigInt}, ${paraSmallInt}, ${paraTinyInt}, ${paraBit}, ${paraDecimal}, ${paraNumeric}, ${paraFloat}, ${paraReal}, ${paraDouble})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); test:assertEquals(paraInt.get(int), 0, "2nd out parameter of procedure did not match."); test:assertEquals(paraBigInt.get(int), 0, "3rd out parameter of procedure did not match."); test:assertEquals(paraSmallInt.get(int), 0, "4th out parameter of procedure did not match."); @@ -213,6 +214,8 @@ function testCallWithNumericTypesOutParamsForInvalidInValue() returns error? { test:assertTrue((check paraFloat.get(float)) >= 0.0, "9th out parameter of procedure did not match."); test:assertTrue((check paraReal.get(float)) >= 0.0, "10th out parameter of procedure did not match."); test:assertEquals(paraDouble.get(float), 0.0, "11th out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -231,15 +234,16 @@ function testCallWithStringTypesInoutParams() returns error? { ParameterizedCallQuery callProcedureQuery = `call SelectStringDataWithInoutParams(${paraID}, ${paraVarchar}, ${paraCharmax}, ${paraChar}, ${paraCharactermax}, ${paraCharacter}, ${paraNvarcharmax})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); test:assertEquals(paraVarchar.get(string), "test0", "2nd out parameter of procedure did not match."); test:assertEquals(paraCharmax.get(string), "test1 ", "3rd out parameter of procedure did not match."); test:assertEquals(paraChar.get(string), "a", "4th out parameter of procedure did not match."); test:assertEquals(paraCharactermax.get(string), "test2 ", "5th out parameter of procedure did not match."); test:assertEquals(paraCharacter.get(string), "b", "6th out parameter of procedure did not match."); test:assertEquals(paraNvarcharmax.get(string), "test3", "7th out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -265,9 +269,8 @@ function testCallWithNumericTypesInoutParams() returns error? { ${paraSmallInt}, ${paraTinyInt}, ${paraBit}, ${paraDecimal}, ${paraNumeric}, ${paraFloat}, ${paraReal}, ${paraDouble})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); decimal paraDecimalVal = 1234.56; test:assertEquals(paraInt.get(int), 2147483647, "2nd out parameter of procedure did not match."); @@ -280,6 +283,8 @@ function testCallWithNumericTypesInoutParams() returns error? { test:assertTrue((check paraFloat.get(float)) > 1234.0, "9th out parameter of procedure did not match."); test:assertTrue((check paraReal.get(float)) > 1234.0, "10th out parameter of procedure did not match."); test:assertEquals(paraDouble.get(float), 1234.56, "11th out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -350,9 +355,8 @@ function testCallWithAllTypesInoutParamsAsObjectValues() returns error? { ${paraTimestamp}, ${paraIntArray}, ${paraStrArray}, ${paraFloArray}, ${paraDecArray}, ${paraBooArray}, ${paraByteArray}, ${paraEmptyArray})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); string clobType = "very long text"; byte[] varBinaryType = "77736f322062616c6c6572696e612062696e61727920746573742e".toBytes(); time:Civil dateTimeRecord = {year: 2017, month: 1, day: 25, hour: 16, minute: 33, second: 55}; @@ -360,6 +364,8 @@ function testCallWithAllTypesInoutParamsAsObjectValues() returns error? { test:assertEquals(paraClob.get(string), clobType, "Clob out parameter of procedure did not match."); test:assertEquals(paraVarBinary.get(byte), varBinaryType, "VarBinary out parameter of procedure did not match."); test:assertEquals(paraDateTime.get(time:Civil), dateTimeRecord, "DateTime out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -437,8 +443,8 @@ function testCallWithInoutParams() returns error? { ${paraTimestamp}, ${paraIntArray}, ${paraStrArray}, ${paraFloArray}, ${paraDecArray}, ${paraBooArray}, ${paraByteArray}, ${paraEmptyArray})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); decimal[] decimalArray = [245, 5559, 8796]; byte[][] byteArray = [[119, 115, 111, 50, 32, 98, 97, 108, 108, 101, 114, 105, 110, 97, 32, 98, 108, 111, 98, 32, 116, 101, 115, 116, 46]]; test:assertEquals(paraIntArray.get(IntArray), [1, 2, 3], "Int array out parameter of procedure did not match."); @@ -452,6 +458,8 @@ function testCallWithInoutParams() returns error? { "of procedure did not match."); test:assertEquals(paraByteArray.get(ByteArray), byteArray, "Byte array out parameter of " + "procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -462,7 +470,8 @@ function testErroneousCallWithNumericTypesInoutParams() returns error? { IntegerValue paraID = new (1); ParameterizedCallQuery callProcedureQuery = `call SelectNumericDataWithInoutParams(${paraID})`; - ProcedureCallResult|error ret = getProcedureCallResultFromMockClient(callProcedureQuery); + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult|error ret = dbClient->call(callProcedureQuery); test:assertTrue(ret is error); if ret is DatabaseError { @@ -490,13 +499,15 @@ function testCallWithDateTimeTypesWithOutParams() returns error? { ParameterizedCallQuery callProcedureQuery = `call SelectDateTimeDataWithOutParams(${paraID}, ${paraDate}, ${paraTime}, ${paraDateTime}, ${paraTimeWithTz}, ${paraTimestamp}, ${paraTimestampWithTz})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); test:assertEquals(paraDate.get(string), "2017-05-23", "Date out parameter of procedure did not match."); test:assertEquals(paraTime.get(string), "14:15:23", "Time out parameter of procedure did not match."); test:assertEquals(paraTimeWithTz.get(string), "16:33:55+06:30", "Time out parameter of procedure did not match."); test:assertEquals(paraTimestamp.get(string), "2017-01-25 16:33:55.0", "Timestamp out parameter of procedure did not match."); test:assertEquals(paraTimestampWithTz.get(string), "2017-01-25T16:33:55-08:00", "Date Time out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -515,9 +526,8 @@ function testCallWithDateTimeTypeRecordsWithOutParams() returns error? { ParameterizedCallQuery callProcedureQuery = `call SelectDateTimeDataWithOutParams(${paraID}, ${paraDate}, ${paraTime}, ${paraDateTime}, ${paraTimeWithTz}, ${paraTimestamp}, ${paraTimestampWithTz})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); time:Date dateRecord = {year: 2017, month: 5, day: 23}; time:TimeOfDay timeRecord = {hour: 14, minute: 15, second: 23}; time:Civil timestampRecord = {year: 2017, month: 1, day: 25, hour: 16, minute: 33, second: 55}; @@ -539,6 +549,8 @@ function testCallWithDateTimeTypeRecordsWithOutParams() returns error? { test:assertEquals(paraTimeWithTz.get(time:TimeOfDay), timeWithTzRecord, "Time with Timezone out parameter of procedure did not match."); test:assertEquals(paraTimestamp.get(time:Civil), timestampRecord, "Timestamp out parameter of procedure did not match."); test:assertEquals(paraTimestampWithTz.get(time:Civil), timestampWithTzRecord, "Timestamp with Timezone out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -551,9 +563,8 @@ function testCallWithTimestampTZRetrievalWithOutParams() returns error? { ParameterizedCallQuery callProcedureQuery = `call SelectTimestampTZWithOutParams(${paraID}, ${paraTimestampWithTz})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); string timestampWithTzRecordString = "2017-01-25T16:33:55-08:00"; time:Civil timestampWithTzRecordCivil = { utcOffset: {hours: -8, minutes: 0}, @@ -570,6 +581,8 @@ function testCallWithTimestampTZRetrievalWithOutParams() returns error? { test:assertEquals(paraTimestampWithTz.get(string), timestampWithTzRecordString, "Timestamp with Timezone out parameter of procedure did not match."); test:assertEquals(paraTimestampWithTz.get(time:Civil), timestampWithTzRecordCivil, "Timestamp with Timezone out parameter of procedure did not match."); test:assertEquals(paraTimestampWithTz.get(time:Utc), timestampWithTzRecordUtc, "Timestamp with Timezone out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -588,9 +601,8 @@ function testCallWithOtherDataTypesWithOutParams() returns error? { ParameterizedCallQuery callProcedureQuery = `call SelectOtherDataTypesWithOutParams(${paraID}, ${paraClob}, ${paraVarBinary}, ${paraIntArray}, ${paraStringArray}, ${paraBinary}, ${paraBoolean})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); string clobType = "very long text"; byte[] varBinaryType = "77736f322062616c6c6572696e612062696e61727920746573742e".toBytes(); int[] int_array = [1, 2, 3]; @@ -603,6 +615,8 @@ function testCallWithOtherDataTypesWithOutParams() returns error? { test:assertEquals(paraBoolean.get(boolean), true, "Boolean out parameter of procedure did not match."); test:assertEquals(paraIntArray.get(IntArray), int_array, "Int array out parameter of procedure did not match."); test:assertEquals(paraStringArray.get(StringArray), string_array, "String array out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } distinct class RandomOutParameter { @@ -629,7 +643,8 @@ function testCallWithOtherDataTypesWithInvalidOutParams() returns error? { ParameterizedCallQuery callProcedureQuery = `call SelectOtherDataTypesWithOutParams(${paraID}, ${paraClob}, ${paraVarBinary} , ${paraIntArray}, ${paraStringArray}, ${paraBinary}, ${paraBoolean})`; - ProcedureCallResult|error ret = getProcedureCallResultFromMockClient(callProcedureQuery); + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult|error ret = dbClient->call(callProcedureQuery); test:assertTrue(ret is error); } @@ -1059,15 +1074,13 @@ function testCallWithAllArrayTypesInoutParamsAsObjectValues() returns error? { END `; validateProcedureResult(check createSqlProcedure(createProcedure), 0, ()); - + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); ParameterizedCallQuery callProcedureQuery = `call SelectArrayDataWithInoutParams(${rowId}, ${smallint_array}, ${int_array}, ${real_array}, ${numeric_array}, ${nvarchar_array}, ${long_array}, ${float_array}, ${double_array}, ${decimal_array}, ${boolean_array}, ${char_array}, ${varchar_array}, ${string_array}, ${date_array}, ${time_array}, ${timestamp_array}, ${datetime_array})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); int[] smallIntArray = [12, 232]; int[] intArray = [1, 2, 3]; float[] floatArray = [199.33, 2399.1]; @@ -1080,6 +1093,8 @@ function testCallWithAllArrayTypesInoutParamsAsObjectValues() returns error? { test:assertEquals(numeric_array.get(FloatArray), numericArray, "Numeric array out parameter of procedure did not match."); test:assertEquals(nvarchar_array.get(StringArray), nVarcharArray, "Nvarchar array out parameter of procedure did not match."); test:assertEquals(datetime_array.get(CivilArray), civilArray, "Nvarchar array out parameter of procedure did not match."); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -1164,14 +1179,11 @@ function testCallWithAllArrayTypesOutParamsAsObjectValues() returns error? { ${char_array}, ${varchar_array}, ${string_array}, ${date_array}, ${time_array}, ${timestamp_array}, ${datetime_array}, ${bit_array}, ${time_tz_array}, ${timestamp_tz_array}, ${binary_array})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); - MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); stream streamData = dbClient->query(`SELECT * FROM ProArrayTypes WHERE row_id = 1`); _ = check streamData.next(); check streamData.close(); - check dbClient.close(); int[] smallIntArray = [12, 232]; int[] intArray = [1, 2, 3]; @@ -1276,6 +1288,8 @@ function testCallWithAllArrayTypesOutParamsAsObjectValues() returns error? { test:assertEquals(binary_array.get(ByteArray), binaryArray, "Timestamp with timezone array out parameter of " + "procedure did not match."); test:assertFalse((binary_array.get(StringArray) is Error)); + check ret.close(); + check dbClient.close(); } @test:Config { @@ -1311,8 +1325,8 @@ function negativeOutParamsTest() returns error? { ${char_array}, ${varchar_array}, ${string_array}, ${date_array}, ${time_array}, ${timestamp_array}, ${datetime_array}, ${bit_array}, ${time_tz_array}, ${timestamp_tz_array}, ${binary_array})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); byte[][]|Error result = smallint_array.get(ByteArray); if result is TypeMismatchError { test:assertEquals(result.message(), @@ -1472,7 +1486,8 @@ function negativeOutParamsTest() returns error? { } else { test:assertFail("Result is not mismatch"); } - + check ret.close(); + check dbClient.close(); } @test:Config { @@ -1508,8 +1523,9 @@ function unionTypeOutParamsTest() returns error? { ${char_array}, ${varchar_array}, ${string_array}, ${date_array}, ${time_array}, ${timestamp_array}, ${datetime_array}, ${bit_array}, ${time_tz_array}, ${timestamp_tz_array}, ${binary_array})`; - ProcedureCallResult ret = check getProcedureCallResultFromMockClient(callProcedureQuery); - check ret.close(); + + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); byte[][]|string|Error result = smallint_array.get(); if result is ApplicationError { test:assertEquals(result.message(), "OutParameter 'get' function does not support union return type.", @@ -1517,13 +1533,8 @@ function unionTypeOutParamsTest() returns error? { } else { test:assertFail("Result is not ApplicationError"); } -} - -function getProcedureCallResultFromMockClient(ParameterizedCallQuery sqlQuery) returns ProcedureCallResult|error { - MockClient dbClient = check new (url = proceduresDB, user = user, password = password); - ProcedureCallResult result = check dbClient->call(sqlQuery); + check ret.close(); check dbClient.close(); - return result; } function createSqlProcedure(ParameterizedQuery sqlQuery) returns ExecutionResult|Error { @@ -1547,3 +1558,49 @@ isolated function validateProcedureResult(ExecutionResult result, int rowCount, } } } + +@test:Config { + groups: ["procedures"] +} +function testOutParameterReturingErrorWhenResultIsClosed() returns error? { + IntegerValue paraID = new (1); + VarcharOutParameter paraVarchar = new; + CharOutParameter paraCharmax = new; + CharOutParameter paraChar = new; + CharOutParameter paraCharactermax = new; + CharOutParameter paraCharacter = new; + NVarcharOutParameter paraNvarcharmax = new; + + ParameterizedCallQuery callProcedureQuery = `call SelectStringDataWithOutParams(${paraID}, ${paraVarchar}, + ${paraCharmax}, ${paraChar}, ${paraCharactermax}, ${paraCharacter}, ${paraNvarcharmax})`; + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); + check ret.close(); + string|Error err = paraVarchar.get(string); + test:assertTrue(err is Error); + test:assertTrue((err).message().startsWith("Failed to read OUT parameter value.")); + check dbClient.close(); +} + +@test:Config { + groups: ["procedures"] +} +function testInOutParameterReturingErrorWhenResultIsClosed() returns error? { + IntegerValue paraID = new (1); + InOutParameter paraVarchar = new ("test varchar"); + InOutParameter paraCharmax = new ("test char"); + InOutParameter paraChar = new ("T"); + InOutParameter paraCharactermax = new ("test c_max"); + InOutParameter paraCharacter = new ("C"); + InOutParameter paraNvarcharmax = new ("test_nchar"); + + ParameterizedCallQuery callProcedureQuery = `call SelectStringDataWithInoutParams(${paraID}, ${paraVarchar}, + ${paraCharmax}, ${paraChar}, ${paraCharactermax}, ${paraCharacter}, ${paraNvarcharmax})`; + MockClient dbClient = check new (url = proceduresDB, user = user, password = password); + ProcedureCallResult ret = check dbClient->call(callProcedureQuery); + check ret.close(); + string|Error err = paraVarchar.get(string); + test:assertTrue(err is Error); + test:assertTrue((err).message().startsWith("Failed to read INOUT parameter value.")); + check dbClient.close(); +} \ No newline at end of file diff --git a/changelog.md b/changelog.md index 863db904e..6d23d3b06 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - [Fix strand hanging when strand count exceeds BALLERINA_SQL_MAX_POOL_SIZE](https://github.com/ballerina-platform/ballerina-library/issues/7244) +- [Fix stored procedure call having output parameter failing with result set closed error](https://github.com/ballerina-platform/ballerina-library/issues/7255) + ## [1.14.1] - 2024-08-29 diff --git a/docs/spec/spec.md b/docs/spec/spec.md index d68946f12..59b629535 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -257,6 +257,7 @@ In addition to the above parameters, it has `CursorOutParameter` to retrieve the stream resultStream = cursor.get(); ``` +> **_Note:_** In the case of a stored procedure query that returns a result set along with the mentioned parameters, the `get()` method of these parameters should only be invoked after consuming the result set. Otherwise, consuming the result set fails with a 'Error when iterating the SQL result. The result set is closed.' error. ## 3.3. Query concatenation diff --git a/native/src/main/java/io/ballerina/stdlib/sql/Constants.java b/native/src/main/java/io/ballerina/stdlib/sql/Constants.java index 033ba8f14..bec6a872a 100644 --- a/native/src/main/java/io/ballerina/stdlib/sql/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/sql/Constants.java @@ -75,6 +75,9 @@ private Constants() { public static final String READ_BYTE_CHANNEL_STRUCT = "ReadableByteChannel"; public static final String READ_CHAR_CHANNEL_STRUCT = "ReadableCharacterChannel"; + public static final String RESULT_PARAMETER_PROCESSOR = "ResultParameterProcessor"; + public static final String PARAMETER_INDEX_META_DATA = "parameterIndex"; + public static final String USERNAME = "user"; public static final String PASSWORD = "password"; diff --git a/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/CallProcessor.java b/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/CallProcessor.java index e7a49a88a..885a9e8f3 100644 --- a/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/CallProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/CallProcessor.java @@ -53,8 +53,10 @@ import static io.ballerina.stdlib.sql.Constants.CONNECTION_NATIVE_DATA_FIELD; import static io.ballerina.stdlib.sql.Constants.DATABASE_CLIENT; +import static io.ballerina.stdlib.sql.Constants.PARAMETER_INDEX_META_DATA; import static io.ballerina.stdlib.sql.Constants.PROCEDURE_CALL_RESULT; import static io.ballerina.stdlib.sql.Constants.QUERY_RESULT_FIELD; +import static io.ballerina.stdlib.sql.Constants.RESULT_PARAMETER_PROCESSOR; import static io.ballerina.stdlib.sql.Constants.RESULT_SET_COUNT_NATIVE_DATA_FIELD; import static io.ballerina.stdlib.sql.Constants.RESULT_SET_TOTAL_NATIVE_DATA_FIELD; import static io.ballerina.stdlib.sql.Constants.STATEMENT_NATIVE_DATA_FIELD; @@ -158,7 +160,7 @@ private static Object nativeCallExecutable(BObject client, BObject paramSQLStrin updateProcedureCallExecutionResult(statement, procedureCallResult); } - populateOutParameters(statement, paramSQLString, outputParamTypes, + populateOutParametersMetaData(statement, paramSQLString, outputParamTypes, resultParameterProcessor, procedureCallResult); procedureCallResult.addNativeData(STATEMENT_NATIVE_DATA_FIELD, statement); @@ -228,132 +230,25 @@ private static void setCallParameters(Connection connection, CallableStatement s } } - private static void populateOutParameters(CallableStatement statement, BObject paramSQLString, - HashMap outputParamTypes, - AbstractResultParameterProcessor resultParameterProcessor, - BObject procedureCallResult) + private static void populateOutParametersMetaData(CallableStatement statement, BObject paramSQLString, + HashMap outputParamTypes, + AbstractResultParameterProcessor resultParameterProcessor, + BObject procedureCallResult) throws SQLException, ApplicationError { if (outputParamTypes.size() == 0) { return; } BArray arrayValue = paramSQLString.getArrayValue(Constants.ParameterizedQueryFields.INSERTIONS); - for (Map.Entry entry : outputParamTypes.entrySet()) { int paramIndex = entry.getKey(); int sqlType = entry.getValue(); - BObject parameter = (BObject) arrayValue.get(paramIndex - 1); + parameter.addNativeData(PARAMETER_INDEX_META_DATA, paramIndex); parameter.addNativeData(Constants.ParameterObject.SQL_TYPE_NATIVE_DATA, sqlType); - Object result; - switch (sqlType) { - case Types.CHAR: - result = resultParameterProcessor.processChar(statement, paramIndex); - break; - case Types.VARCHAR: - result = resultParameterProcessor.processVarchar(statement, paramIndex); - break; - case Types.LONGVARCHAR: - result = resultParameterProcessor.processLongVarchar(statement, paramIndex); - break; - case Types.NCHAR: - result = resultParameterProcessor.processNChar(statement, paramIndex); - break; - case Types.NVARCHAR: - result = resultParameterProcessor.processNVarchar(statement, paramIndex); - break; - case Types.LONGNVARCHAR: - result = resultParameterProcessor.processLongNVarchar(statement, paramIndex); - break; - case Types.BINARY: - result = resultParameterProcessor.processBinary(statement, paramIndex); - break; - case Types.VARBINARY: - result = resultParameterProcessor.processVarBinary(statement, paramIndex); - break; - case Types.LONGVARBINARY: - result = resultParameterProcessor.processLongVarBinary(statement, paramIndex); - break; - case Types.BLOB: - result = resultParameterProcessor.processBlob(statement, paramIndex); - break; - case Types.CLOB: - result = resultParameterProcessor.processClob(statement, paramIndex); - break; - case Types.NCLOB: - result = resultParameterProcessor.processNClob(statement, paramIndex); - break; - case Types.DATE: - result = resultParameterProcessor.processDate(statement, paramIndex); - break; - case Types.TIME: - result = resultParameterProcessor.processTime(statement, paramIndex); - break; - case Types.TIME_WITH_TIMEZONE: - result = resultParameterProcessor.processTimeWithTimeZone(statement, paramIndex); - break; - case Types.TIMESTAMP: - result = resultParameterProcessor.processTimestamp(statement, paramIndex); - break; - case Types.TIMESTAMP_WITH_TIMEZONE: - result = resultParameterProcessor.processTimestampWithTimeZone(statement, paramIndex); - break; - case Types.ARRAY: - result = resultParameterProcessor.processArray(statement, paramIndex); - break; - case Types.ROWID: - result = resultParameterProcessor.processRowID(statement, paramIndex); - break; - case Types.TINYINT: - result = resultParameterProcessor.processTinyInt(statement, paramIndex); - break; - case Types.SMALLINT: - result = resultParameterProcessor.processSmallInt(statement, paramIndex); - break; - case Types.INTEGER: - result = resultParameterProcessor.processInteger(statement, paramIndex); - break; - case Types.BIGINT: - result = resultParameterProcessor.processBigInt(statement, paramIndex); - break; - case Types.REAL: - result = resultParameterProcessor.processReal(statement, paramIndex); - break; - case Types.FLOAT: - result = resultParameterProcessor.processFloat(statement, paramIndex); - break; - case Types.DOUBLE: - result = resultParameterProcessor.processDouble(statement, paramIndex); - break; - case Types.NUMERIC: - result = resultParameterProcessor.processNumeric(statement, paramIndex); - break; - case Types.DECIMAL: - result = resultParameterProcessor.processDecimal(statement, paramIndex); - break; - case Types.BIT: - result = resultParameterProcessor.processBit(statement, paramIndex); - break; - case Types.BOOLEAN: - result = resultParameterProcessor.processBoolean(statement, paramIndex); - break; - case Types.REF: - case Types.REF_CURSOR: - result = resultParameterProcessor.processRef(statement, paramIndex); - // This is to clean up the result set attached to the ref cursor out parameter - // when procedure call result is closed. - procedureCallResult.addNativeData(Constants.REF_CURSOR_VALUE_NATIVE_DATA, result); - break; - case Types.STRUCT: - result = resultParameterProcessor.processStruct(statement, paramIndex); - break; - case Types.SQLXML: - result = resultParameterProcessor.processXML(statement, paramIndex); - break; - default: - result = resultParameterProcessor.processCustomOutParameters(statement, paramIndex, sqlType); - } - parameter.addNativeData(Constants.ParameterObject.VALUE_NATIVE_DATA, result); + parameter.addNativeData(RESULT_PARAMETER_PROCESSOR, resultParameterProcessor); + parameter.addNativeData(STATEMENT_NATIVE_DATA_FIELD, statement); + parameter.addNativeData(PROCEDURE_CALL_RESULT, procedureCallResult); } } diff --git a/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/OutParameterProcessor.java b/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/OutParameterProcessor.java index 6871a8a8a..62c6544bc 100644 --- a/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/OutParameterProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/OutParameterProcessor.java @@ -34,6 +34,7 @@ import java.math.BigDecimal; import java.sql.Array; import java.sql.Blob; +import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Date; import java.sql.NClob; @@ -48,6 +49,13 @@ import java.time.OffsetDateTime; import java.time.OffsetTime; +import static io.ballerina.stdlib.sql.Constants.PARAMETER_INDEX_META_DATA; +import static io.ballerina.stdlib.sql.Constants.PROCEDURE_CALL_RESULT; +import static io.ballerina.stdlib.sql.Constants.ParameterObject; +import static io.ballerina.stdlib.sql.Constants.REF_CURSOR_VALUE_NATIVE_DATA; +import static io.ballerina.stdlib.sql.Constants.RESULT_PARAMETER_PROCESSOR; +import static io.ballerina.stdlib.sql.Constants.STATEMENT_NATIVE_DATA_FIELD; +import static io.ballerina.stdlib.sql.utils.Utils.getErrorStream; import static io.ballerina.stdlib.sql.utils.Utils.getString; /** @@ -61,17 +69,152 @@ private OutParameterProcessor() { } public static Object getOutParameterValue(BObject result, BTypedesc typeDesc) { + try { + populateOutParameter(result); + } catch (SQLException | ApplicationError e) { + return ErrorGenerator.getSQLError(e, "Failed to read OUT parameter value."); + } + return get(result, typeDesc, DefaultResultParameterProcessor.getInstance(), "OutParameter"); } public static BStream getOutCursorValue(BObject result, BTypedesc typeDesc) { + try { + populateOutParameter(result); + } catch (SQLException | ApplicationError e) { + return getErrorStream(typeDesc, + ErrorGenerator.getSQLError(e, "Failed to read parameter value.")); + } return get(result, typeDesc, DefaultResultParameterProcessor.getInstance()); } public static Object getInOutParameterValue(BObject result, BTypedesc typeDesc) { + try { + populateOutParameter(result); + } catch (SQLException | ApplicationError e) { + return ErrorGenerator.getSQLError(e, "Failed to read INOUT parameter value."); + } return get(result, typeDesc, DefaultResultParameterProcessor.getInstance(), "InOutParameter"); } + private static void populateOutParameter(BObject parameter) throws SQLException, ApplicationError { + int paramIndex = (int) parameter.getNativeData(PARAMETER_INDEX_META_DATA); + int sqlType = (int) parameter.getNativeData(Constants.ParameterObject.SQL_TYPE_NATIVE_DATA); + CallableStatement statement = (CallableStatement) parameter.getNativeData(STATEMENT_NATIVE_DATA_FIELD); + BObject procedureCallResult = (BObject) parameter.getNativeData(PROCEDURE_CALL_RESULT); + AbstractResultParameterProcessor resultParameterProcessor = (AbstractResultParameterProcessor) parameter + .getNativeData(RESULT_PARAMETER_PROCESSOR); + Object result; + switch (sqlType) { + case Types.CHAR: + result = resultParameterProcessor.processChar(statement, paramIndex); + break; + case Types.VARCHAR: + result = resultParameterProcessor.processVarchar(statement, paramIndex); + break; + case Types.LONGVARCHAR: + result = resultParameterProcessor.processLongVarchar(statement, paramIndex); + break; + case Types.NCHAR: + result = resultParameterProcessor.processNChar(statement, paramIndex); + break; + case Types.NVARCHAR: + result = resultParameterProcessor.processNVarchar(statement, paramIndex); + break; + case Types.LONGNVARCHAR: + result = resultParameterProcessor.processLongNVarchar(statement, paramIndex); + break; + case Types.BINARY: + result = resultParameterProcessor.processBinary(statement, paramIndex); + break; + case Types.VARBINARY: + result = resultParameterProcessor.processVarBinary(statement, paramIndex); + break; + case Types.LONGVARBINARY: + result = resultParameterProcessor.processLongVarBinary(statement, paramIndex); + break; + case Types.BLOB: + result = resultParameterProcessor.processBlob(statement, paramIndex); + break; + case Types.CLOB: + result = resultParameterProcessor.processClob(statement, paramIndex); + break; + case Types.NCLOB: + result = resultParameterProcessor.processNClob(statement, paramIndex); + break; + case Types.DATE: + result = resultParameterProcessor.processDate(statement, paramIndex); + break; + case Types.TIME: + result = resultParameterProcessor.processTime(statement, paramIndex); + break; + case Types.TIME_WITH_TIMEZONE: + result = resultParameterProcessor.processTimeWithTimeZone(statement, paramIndex); + break; + case Types.TIMESTAMP: + result = resultParameterProcessor.processTimestamp(statement, paramIndex); + break; + case Types.TIMESTAMP_WITH_TIMEZONE: + result = resultParameterProcessor.processTimestampWithTimeZone(statement, paramIndex); + break; + case Types.ARRAY: + result = resultParameterProcessor.processArray(statement, paramIndex); + break; + case Types.ROWID: + result = resultParameterProcessor.processRowID(statement, paramIndex); + break; + case Types.TINYINT: + result = resultParameterProcessor.processTinyInt(statement, paramIndex); + break; + case Types.SMALLINT: + result = resultParameterProcessor.processSmallInt(statement, paramIndex); + break; + case Types.INTEGER: + result = resultParameterProcessor.processInteger(statement, paramIndex); + break; + case Types.BIGINT: + result = resultParameterProcessor.processBigInt(statement, paramIndex); + break; + case Types.REAL: + result = resultParameterProcessor.processReal(statement, paramIndex); + break; + case Types.FLOAT: + result = resultParameterProcessor.processFloat(statement, paramIndex); + break; + case Types.DOUBLE: + result = resultParameterProcessor.processDouble(statement, paramIndex); + break; + case Types.NUMERIC: + result = resultParameterProcessor.processNumeric(statement, paramIndex); + break; + case Types.DECIMAL: + result = resultParameterProcessor.processDecimal(statement, paramIndex); + break; + case Types.BIT: + result = resultParameterProcessor.processBit(statement, paramIndex); + break; + case Types.BOOLEAN: + result = resultParameterProcessor.processBoolean(statement, paramIndex); + break; + case Types.REF: + case Types.REF_CURSOR: + result = resultParameterProcessor.processRef(statement, paramIndex); + // This is to clean up the result set attached to the ref cursor out parameter + // when procedure call result is closed. + procedureCallResult.addNativeData(REF_CURSOR_VALUE_NATIVE_DATA, result); + break; + case Types.STRUCT: + result = resultParameterProcessor.processStruct(statement, paramIndex); + break; + case Types.SQLXML: + result = resultParameterProcessor.processXML(statement, paramIndex); + break; + default: + result = resultParameterProcessor.processCustomOutParameters(statement, paramIndex, sqlType); + } + parameter.addNativeData(ParameterObject.VALUE_NATIVE_DATA, result); + } + public static BStream get(BObject result, Object recordType, AbstractResultParameterProcessor resultParameterProcessor) { Object value = result.getNativeData(Constants.ParameterObject.VALUE_NATIVE_DATA); From 49dc2338eacff202cfb7676a37577e3746510a43 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Mon, 14 Oct 2024 13:44:16 +0530 Subject: [PATCH 2/2] Refactor java code --- .../sql/nativeimpl/OutParameterProcessor.java | 147 +++++------------- 1 file changed, 41 insertions(+), 106 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/OutParameterProcessor.java b/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/OutParameterProcessor.java index 62c6544bc..b3564d907 100644 --- a/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/OutParameterProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/sql/nativeimpl/OutParameterProcessor.java @@ -104,114 +104,49 @@ private static void populateOutParameter(BObject parameter) throws SQLException, BObject procedureCallResult = (BObject) parameter.getNativeData(PROCEDURE_CALL_RESULT); AbstractResultParameterProcessor resultParameterProcessor = (AbstractResultParameterProcessor) parameter .getNativeData(RESULT_PARAMETER_PROCESSOR); - Object result; - switch (sqlType) { - case Types.CHAR: - result = resultParameterProcessor.processChar(statement, paramIndex); - break; - case Types.VARCHAR: - result = resultParameterProcessor.processVarchar(statement, paramIndex); - break; - case Types.LONGVARCHAR: - result = resultParameterProcessor.processLongVarchar(statement, paramIndex); - break; - case Types.NCHAR: - result = resultParameterProcessor.processNChar(statement, paramIndex); - break; - case Types.NVARCHAR: - result = resultParameterProcessor.processNVarchar(statement, paramIndex); - break; - case Types.LONGNVARCHAR: - result = resultParameterProcessor.processLongNVarchar(statement, paramIndex); - break; - case Types.BINARY: - result = resultParameterProcessor.processBinary(statement, paramIndex); - break; - case Types.VARBINARY: - result = resultParameterProcessor.processVarBinary(statement, paramIndex); - break; - case Types.LONGVARBINARY: - result = resultParameterProcessor.processLongVarBinary(statement, paramIndex); - break; - case Types.BLOB: - result = resultParameterProcessor.processBlob(statement, paramIndex); - break; - case Types.CLOB: - result = resultParameterProcessor.processClob(statement, paramIndex); - break; - case Types.NCLOB: - result = resultParameterProcessor.processNClob(statement, paramIndex); - break; - case Types.DATE: - result = resultParameterProcessor.processDate(statement, paramIndex); - break; - case Types.TIME: - result = resultParameterProcessor.processTime(statement, paramIndex); - break; - case Types.TIME_WITH_TIMEZONE: - result = resultParameterProcessor.processTimeWithTimeZone(statement, paramIndex); - break; - case Types.TIMESTAMP: - result = resultParameterProcessor.processTimestamp(statement, paramIndex); - break; - case Types.TIMESTAMP_WITH_TIMEZONE: - result = resultParameterProcessor.processTimestampWithTimeZone(statement, paramIndex); - break; - case Types.ARRAY: - result = resultParameterProcessor.processArray(statement, paramIndex); - break; - case Types.ROWID: - result = resultParameterProcessor.processRowID(statement, paramIndex); - break; - case Types.TINYINT: - result = resultParameterProcessor.processTinyInt(statement, paramIndex); - break; - case Types.SMALLINT: - result = resultParameterProcessor.processSmallInt(statement, paramIndex); - break; - case Types.INTEGER: - result = resultParameterProcessor.processInteger(statement, paramIndex); - break; - case Types.BIGINT: - result = resultParameterProcessor.processBigInt(statement, paramIndex); - break; - case Types.REAL: - result = resultParameterProcessor.processReal(statement, paramIndex); - break; - case Types.FLOAT: - result = resultParameterProcessor.processFloat(statement, paramIndex); - break; - case Types.DOUBLE: - result = resultParameterProcessor.processDouble(statement, paramIndex); - break; - case Types.NUMERIC: - result = resultParameterProcessor.processNumeric(statement, paramIndex); - break; - case Types.DECIMAL: - result = resultParameterProcessor.processDecimal(statement, paramIndex); - break; - case Types.BIT: - result = resultParameterProcessor.processBit(statement, paramIndex); - break; - case Types.BOOLEAN: - result = resultParameterProcessor.processBoolean(statement, paramIndex); - break; - case Types.REF: - case Types.REF_CURSOR: - result = resultParameterProcessor.processRef(statement, paramIndex); + Object result = switch (sqlType) { + case Types.CHAR -> resultParameterProcessor.processChar(statement, paramIndex); + case Types.VARCHAR -> resultParameterProcessor.processVarchar(statement, paramIndex); + case Types.LONGVARCHAR -> resultParameterProcessor.processLongVarchar(statement, paramIndex); + case Types.NCHAR -> resultParameterProcessor.processNChar(statement, paramIndex); + case Types.NVARCHAR -> resultParameterProcessor.processNVarchar(statement, paramIndex); + case Types.LONGNVARCHAR -> resultParameterProcessor.processLongNVarchar(statement, paramIndex); + case Types.BINARY -> resultParameterProcessor.processBinary(statement, paramIndex); + case Types.VARBINARY -> resultParameterProcessor.processVarBinary(statement, paramIndex); + case Types.LONGVARBINARY -> resultParameterProcessor.processLongVarBinary(statement, paramIndex); + case Types.BLOB -> resultParameterProcessor.processBlob(statement, paramIndex); + case Types.CLOB -> resultParameterProcessor.processClob(statement, paramIndex); + case Types.NCLOB -> resultParameterProcessor.processNClob(statement, paramIndex); + case Types.DATE -> resultParameterProcessor.processDate(statement, paramIndex); + case Types.TIME -> resultParameterProcessor.processTime(statement, paramIndex); + case Types.TIME_WITH_TIMEZONE -> resultParameterProcessor.processTimeWithTimeZone(statement, paramIndex); + case Types.TIMESTAMP -> resultParameterProcessor.processTimestamp(statement, paramIndex); + case Types.TIMESTAMP_WITH_TIMEZONE -> + resultParameterProcessor.processTimestampWithTimeZone(statement, paramIndex); + case Types.ARRAY -> resultParameterProcessor.processArray(statement, paramIndex); + case Types.ROWID -> resultParameterProcessor.processRowID(statement, paramIndex); + case Types.TINYINT -> resultParameterProcessor.processTinyInt(statement, paramIndex); + case Types.SMALLINT -> resultParameterProcessor.processSmallInt(statement, paramIndex); + case Types.INTEGER -> resultParameterProcessor.processInteger(statement, paramIndex); + case Types.BIGINT -> resultParameterProcessor.processBigInt(statement, paramIndex); + case Types.REAL -> resultParameterProcessor.processReal(statement, paramIndex); + case Types.FLOAT -> resultParameterProcessor.processFloat(statement, paramIndex); + case Types.DOUBLE -> resultParameterProcessor.processDouble(statement, paramIndex); + case Types.NUMERIC -> resultParameterProcessor.processNumeric(statement, paramIndex); + case Types.DECIMAL -> resultParameterProcessor.processDecimal(statement, paramIndex); + case Types.BIT -> resultParameterProcessor.processBit(statement, paramIndex); + case Types.BOOLEAN -> resultParameterProcessor.processBoolean(statement, paramIndex); + case Types.REF, Types.REF_CURSOR -> { + Object output = resultParameterProcessor.processRef(statement, paramIndex); // This is to clean up the result set attached to the ref cursor out parameter // when procedure call result is closed. - procedureCallResult.addNativeData(REF_CURSOR_VALUE_NATIVE_DATA, result); - break; - case Types.STRUCT: - result = resultParameterProcessor.processStruct(statement, paramIndex); - break; - case Types.SQLXML: - result = resultParameterProcessor.processXML(statement, paramIndex); - break; - default: - result = resultParameterProcessor.processCustomOutParameters(statement, paramIndex, sqlType); - } + procedureCallResult.addNativeData(REF_CURSOR_VALUE_NATIVE_DATA, output); + yield output; + } + case Types.STRUCT -> resultParameterProcessor.processStruct(statement, paramIndex); + case Types.SQLXML -> resultParameterProcessor.processXML(statement, paramIndex); + default -> resultParameterProcessor.processCustomOutParameters(statement, paramIndex, sqlType); + }; parameter.addNativeData(ParameterObject.VALUE_NATIVE_DATA, result); }