Skip to content

Commit

Permalink
Merge pull request #743 from MohamedSabthar/mstr
Browse files Browse the repository at this point in the history
Fix stored procedure call having output parameter failing with error
  • Loading branch information
MohamedSabthar authored Oct 14, 2024
2 parents 99a0d7a + 49dc233 commit fbe911b
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 168 deletions.
161 changes: 109 additions & 52 deletions ballerina/tests/call-procedures-test.bal

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ In addition to the above parameters, it has `CursorOutParameter` to retrieve the
stream<record{}, sql:Error?> 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

Expand Down
3 changes: 3 additions & 0 deletions native/src/main/java/io/ballerina/stdlib/sql/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -228,132 +230,25 @@ private static void setCallParameters(Connection connection, CallableStatement s
}
}

private static void populateOutParameters(CallableStatement statement, BObject paramSQLString,
HashMap<Integer, Integer> outputParamTypes,
AbstractResultParameterProcessor resultParameterProcessor,
BObject procedureCallResult)
private static void populateOutParametersMetaData(CallableStatement statement, BObject paramSQLString,
HashMap<Integer, Integer> outputParamTypes,
AbstractResultParameterProcessor resultParameterProcessor,
BObject procedureCallResult)
throws SQLException, ApplicationError {
if (outputParamTypes.size() == 0) {
return;
}
BArray arrayValue = paramSQLString.getArrayValue(Constants.ParameterizedQueryFields.INSERTIONS);

for (Map.Entry<Integer, Integer> 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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -61,17 +69,87 @@ 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 -> 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, 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);
}

public static BStream get(BObject result, Object recordType,
AbstractResultParameterProcessor resultParameterProcessor) {
Object value = result.getNativeData(Constants.ParameterObject.VALUE_NATIVE_DATA);
Expand Down

0 comments on commit fbe911b

Please sign in to comment.