columnDefinitions;
72 | StructureType streamConstraint;
73 | resultSet = statement.getResultSet();
74 | int totalRecordDescriptions = (int) procedureCallResult
75 | .getNativeData(RESULT_SET_TOTAL_NATIVE_DATA_FIELD);
76 | if (totalRecordDescriptions == 0) {
77 | streamConstraint = getDefaultStreamConstraint();
78 | columnDefinitions = getColumnDefinitions(resultSet, streamConstraint);
79 | } else {
80 | Object[] recordDescriptions = (Object[]) procedureCallResult
81 | .getNativeData(TYPE_DESCRIPTIONS_NATIVE_DATA_FIELD);
82 | int recordDescription = (int) procedureCallResult.getNativeData(RESULT_SET_COUNT_NATIVE_DATA_FIELD);
83 | if (recordDescription <= totalRecordDescriptions) {
84 | streamConstraint = (StructureType) TypeUtils.getReferredType(
85 | ((BTypedesc) recordDescriptions[recordDescription]).getDescribingType());
86 | columnDefinitions = getColumnDefinitions(resultSet, streamConstraint);
87 | procedureCallResult.addNativeData(RESULT_SET_COUNT_NATIVE_DATA_FIELD, recordDescription + 1);
88 | } else {
89 | throw new ApplicationError("The record description array count does not match with the " +
90 | "returned result sets count.");
91 | }
92 | }
93 | BStream streamValue = ValueCreator.createStreamValue(
94 | TypeCreator.createStreamType(streamConstraint, PredefinedTypes.TYPE_NULL),
95 | resultParameterProcessor.createRecordIterator(resultSet, null, null,
96 | columnDefinitions, streamConstraint));
97 | procedureCallResult.set(QUERY_RESULT_FIELD, streamValue);
98 | procedureCallResult.set(EXECUTION_RESULT_FIELD, null);
99 | } else {
100 | updateProcedureCallExecutionResult(statement, procedureCallResult);
101 | }
102 | return moreResults;
103 | } catch (SQLException e) {
104 | return ErrorGenerator.getSQLDatabaseError(e, "Error when accessing the next query result.");
105 | } catch (ApplicationError e) {
106 | return ErrorGenerator.getSQLApplicationError("Error when accessing the next query result. "
107 | + e.getMessage());
108 | } catch (Throwable throwable) {
109 | return ErrorGenerator.getSQLApplicationError("Error when accessing the next SQL result. "
110 | + throwable.getMessage());
111 | }
112 | }
113 |
114 | public static Object closeCallResult(BObject procedureCallResult) {
115 | Statement statement = (Statement) procedureCallResult.getNativeData(Constants.STATEMENT_NATIVE_DATA_FIELD);
116 | Connection connection = (Connection) procedureCallResult.getNativeData(Constants.CONNECTION_NATIVE_DATA_FIELD);
117 | // This is to clean up the result set attached to the ref cursor out parameter.
118 | // This is to avoid the result set to be open after the call result is closed.
119 | Object resultSet = procedureCallResult.getNativeData(Constants.REF_CURSOR_VALUE_NATIVE_DATA);
120 | if (resultSet instanceof ResultSet) {
121 | try {
122 | ((ResultSet) resultSet).close();
123 | procedureCallResult.addNativeData(Constants.REF_CURSOR_VALUE_NATIVE_DATA, null);
124 | } catch (SQLException e) {
125 | return ErrorGenerator.getSQLDatabaseError(e, "Error when closing the result set.");
126 | }
127 | }
128 | return cleanUpConnection(procedureCallResult, null, statement, connection);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/ballerina/tests/resources/sql/procedures/call-procedures-test-data.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS StringTypes (
2 | id INT IDENTITY,
3 | varchar_type VARCHAR(255),
4 | charmax_type CHAR(10),
5 | char_type CHAR,
6 | charactermax_type CHARACTER(10),
7 | character_type CHARACTER,
8 | nvarcharmax_type NVARCHAR(255),
9 | PRIMARY KEY (id)
10 | );
11 |
12 | INSERT INTO StringTypes(id, varchar_type, charmax_type, char_type, charactermax_type, character_type, nvarcharmax_type)
13 | VALUES (1, 'test0', 'test1', 'a', 'test2', 'b', 'test3');
14 |
15 | CREATE TABLE IF NOT EXISTS OtherTypes (
16 | id INT IDENTITY,
17 | clob_type CLOB,
18 | blob_type BLOB,
19 | var_binary_type VARBINARY(27),
20 | int_array_type INT ARRAY,
21 | string_array_type VARCHAR(50) ARRAY,
22 | binary_type BINARY(27),
23 | boolean_type BOOLEAN,
24 | PRIMARY KEY (id)
25 | );
26 |
27 | INSERT INTO OtherTypes(id, clob_type, blob_type, var_binary_type, int_array_type, string_array_type, binary_type, boolean_type)
28 | VALUES (1, CONVERT('very long text', CLOB), X'77736F322062616C6C6572696E6120626C6F6220746573742E',
29 | X'77736F322062616C6C6572696E612062696E61727920746573742E', ARRAY [1, 2, 3], ARRAY['Hello', 'Ballerina'],
30 | X'77736F322062616C6C6572696E612062696E61727920746573742E', TRUE);
31 |
32 | CREATE TABLE IF NOT EXISTS NumericTypes (
33 | id INT IDENTITY,
34 | int_type INT,
35 | bigint_type BIGINT,
36 | smallint_type SMALLINT,
37 | tinyint_type TINYINT,
38 | bit_type BIT,
39 | decimal_type DECIMAL(10,2),
40 | numeric_type NUMERIC(10,2),
41 | float_type FLOAT,
42 | real_type REAL,
43 | double_type DOUBLE,
44 | PRIMARY KEY (id)
45 | );
46 |
47 |
48 | INSERT INTO NumericTypes (id, int_type, bigint_type, smallint_type, tinyint_type, bit_type, decimal_type, numeric_type,
49 | float_type, real_type, double_type)
50 | VALUES (1, 2147483647, 9223372036854774807, 32767, 127, 1, 1234.56, 1234.56,1234.56, 1234.56, 1234.56);
51 |
52 | CREATE TABLE IF NOT EXISTS StringTypesSecond (
53 | id INT IDENTITY,
54 | varchar_type VARCHAR(255),
55 | charmax_type CHAR(10),
56 | char_type CHAR,
57 | charactermax_type CHARACTER(10),
58 | character_type CHARACTER,
59 | nvarcharmax_type NVARCHAR(255),
60 | PRIMARY KEY (id)
61 | );
62 | CREATE TABLE IF NOT EXISTS DateTimeTypes (
63 | id INT IDENTITY,
64 | date_type DATE,
65 | time_type TIME,
66 | datetime_type DATETIME,
67 | timewithtz_type TIME WITH TIME ZONE,
68 | timestamp_type TIMESTAMP,
69 | timestampwithtz_type TIMESTAMP WITH TIME ZONE,
70 | PRIMARY KEY (id)
71 | );
72 |
73 | INSERT INTO DateTimeTypes (id, date_type, time_type, datetime_type, timestamp_type, timewithtz_type, timestampwithtz_type)
74 | VALUES (1, '2017-05-23', '14:15:23', '2017-01-25 16:33:55', '2017-01-25 16:33:55', '16:33:55+6:30', '2017-01-25 16:33:55-8:00');
75 |
76 | CREATE TABLE IF NOT EXISTS MultipleRecords (
77 | id INT IDENTITY,
78 | name VARCHAR(255),
79 | age INT,
80 | birthday DATE,
81 | country_code VARCHAR(10),
82 | PRIMARY KEY (id)
83 | );
84 |
85 | INSERT INTO MultipleRecords (id, name, age, birthday, country_code)
86 | VALUES(1, 'Bob', 20, '2017-05-23', 'US');
87 | INSERT INTO MultipleRecords (id, name, age, birthday, country_code)
88 | VALUES(2, 'John', 25, '2012-10-12', 'US');
89 |
90 | CREATE TABLE IF NOT EXISTS ArrayTypes(
91 | row_id INTEGER NOT NULL,
92 | int_array INTEGER ARRAY,
93 | long_array BIGINT ARRAY,
94 | float_array FLOAT ARRAY,
95 | double_array DOUBLE ARRAY,
96 | decimal_array DECIMAL ARRAY,
97 | boolean_array BOOLEAN ARRAY,
98 | string_array VARCHAR(20) ARRAY,
99 | blob_array VARBINARY(27) ARRAY,
100 | PRIMARY KEY (row_id)
101 | );
102 |
103 | INSERT INTO ArrayTypes (row_id, int_array, long_array, float_array, double_array, decimal_array, boolean_array, string_array, blob_array)
104 | VALUES (1, ARRAY [1, 2, 3], ARRAY [10000, 20000, 30000], ARRAY[245.23, 5559.49, 8796.123],
105 | ARRAY[245.23, 5559.49, 8796.123], ARRAY[245, 5559, 8796], ARRAY[TRUE, FALSE, TRUE], ARRAY['Hello', 'Ballerina'],
106 | ARRAY[X'77736F322062616C6C6572696E6120626C6F6220746573742E']);
107 |
108 | CREATE TABLE IF NOT EXISTS ProArrayTypes (
109 | row_id INTEGER NOT NULL,
110 | smallint_array SMALLINT ARRAY,
111 | int_array INTEGER ARRAY,
112 | long_array BIGINT ARRAY,
113 | float_array FLOAT ARRAY,
114 | double_array DOUBLE ARRAY,
115 | real_array REAL ARRAY,
116 | decimal_array DECIMAL(6,2) ARRAY,
117 | numeric_array NUMERIC(6,2) ARRAY,
118 | boolean_array BOOLEAN ARRAY,
119 | bit_array BIT ARRAY,
120 | binary_array BINARY(27) ARRAY,
121 | char_array CHAR(15) ARRAY,
122 | varchar_array VARCHAR(100) ARRAY,
123 | nvarchar_array NVARCHAR(15) ARRAY,
124 | string_array VARCHAR(20) ARRAY,
125 | blob_array VARBINARY(27) ARRAY,
126 | date_array DATE ARRAY,
127 | time_array TIME ARRAY,
128 | datetime_array DATETIME ARRAY,
129 | timestamp_array timestamp ARRAY,
130 | time_tz_array TIME WITH TIME ZONE ARRAY,
131 | timestamp_tz_array TIMESTAMP WITH TIME ZONE ARRAY,
132 | PRIMARY KEY (row_id)
133 | );
134 |
135 | INSERT INTO ProArrayTypes (row_id, int_array, long_array, float_array, double_array, decimal_array, boolean_array,
136 | bit_array, string_array, blob_array, smallint_array, numeric_array, real_array, char_array, varchar_array,
137 | nvarchar_array, date_array, time_array, datetime_array, timestamp_array, time_tz_array, timestamp_tz_array, binary_array)
138 | VALUES (1, ARRAY [1, 2, 3], ARRAY [100000000, 200000000, 300000000], ARRAY[245.23, 5559.49, 8796.123],
139 | ARRAY[245.23, 5559.49, 8796.123], ARRAY[245.12, 5559.12, 8796.92], ARRAY[TRUE, FALSE, TRUE],
140 | ARRAY[1, 1, 0], ARRAY['Hello', 'Ballerina'],
141 | ARRAY[X'77736F322062616C6C6572696E6120626C6F6220746573742E', X'77736F322062616C6C6572696E6120626C6F6220746573742E'],
142 | ARRAY[12, 232], ARRAY[11.11, 23.23], ARRAY[199.33, 2399.1], ARRAY['Hello', 'Ballerina'], ARRAY['Hello', 'Ballerina'],
143 | ARRAY['Hello', 'Ballerina'], ARRAY['2017-02-03', '2017-02-03'], ARRAY['11:22:42', '12:23:45'],
144 | ARRAY['2017-02-03 11:53:00', '2019-04-05 12:33:10'], ARRAY['2017-02-03 11:53:00', '2019-04-05 12:33:10'],
145 | ARRAY['16:33:55+6:30', '16:33:55+4:30'], ARRAY['2017-01-25 16:33:55-8:00', '2017-01-25 16:33:55-5:00'],
146 | ARRAY[X'77736F322062616C6C6572696E612062696E61727920746573747R']);
147 |
--------------------------------------------------------------------------------
/native/src/main/java/io/ballerina/stdlib/sql/utils/ErrorGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, WSO2 Inc. (http://wso2.com) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 | package io.ballerina.stdlib.sql.utils;
19 |
20 | import io.ballerina.runtime.api.creators.ErrorCreator;
21 | import io.ballerina.runtime.api.creators.TypeCreator;
22 | import io.ballerina.runtime.api.creators.ValueCreator;
23 | import io.ballerina.runtime.api.utils.StringUtils;
24 | import io.ballerina.runtime.api.values.BError;
25 | import io.ballerina.runtime.api.values.BMap;
26 | import io.ballerina.runtime.api.values.BString;
27 | import io.ballerina.stdlib.sql.Constants;
28 | import io.ballerina.stdlib.sql.exception.ApplicationError;
29 | import io.ballerina.stdlib.sql.exception.ConversionError;
30 | import io.ballerina.stdlib.sql.exception.DataError;
31 | import io.ballerina.stdlib.sql.exception.FieldMismatchError;
32 | import io.ballerina.stdlib.sql.exception.TypeMismatchError;
33 | import io.ballerina.stdlib.sql.exception.UnsupportedTypeError;
34 |
35 | import java.sql.SQLException;
36 | import java.util.HashMap;
37 | import java.util.List;
38 | import java.util.Map;
39 |
40 | /**
41 | * Utility class for generating SQL Client errors.
42 | *
43 | * @since 1.2.0
44 | */
45 | public class ErrorGenerator {
46 |
47 | // This regex matches all strings in the pattern a.b.c.d:
48 | public static final String JAVA_CLASS_NAME_REGEX = "([\\p{L}_$][\\p{L}\\p{N}_$]*\\.)+[\\p{L}_$][\\p{L}\\p{N}_$]*: ";
49 |
50 | private ErrorGenerator() {
51 | }
52 |
53 | public static BError getSQLBatchExecuteError(SQLException exception,
54 | List> executionResults,
55 | String messagePrefix) {
56 | String sqlErrorMessage =
57 | exception.getMessage() != null ? exception.getMessage() : exception.getClass().getSimpleName();
58 | int vendorCode = exception.getErrorCode();
59 | String sqlState = exception.getSQLState();
60 | String errorMessage = messagePrefix + sqlErrorMessage + ".";
61 | return getSQLBatchExecuteError(errorMessage, vendorCode, sqlState, executionResults);
62 | }
63 |
64 | public static BError getSQLDatabaseError(SQLException exception, String messagePrefix) {
65 | String sqlErrorMessage =
66 | exception.getMessage() != null ? exception.getMessage() : exception.getClass().getSimpleName();
67 | int vendorCode = exception.getErrorCode();
68 | String sqlState = exception.getSQLState();
69 | String errorMessage = messagePrefix + sqlErrorMessage + ".";
70 | return getSQLDatabaseError(errorMessage, vendorCode, sqlState);
71 | }
72 |
73 | public static BError getSQLApplicationError(String errorMessage) {
74 | errorMessage = removeJavaClassNames(errorMessage);
75 | return ErrorCreator.createError(ModuleUtils.getModule(), Constants.APPLICATION_ERROR,
76 | StringUtils.fromString(errorMessage), null, null);
77 | }
78 |
79 | public static BError getSQLApplicationError(ApplicationError error) {
80 | return getSQLApplicationError(error, "");
81 | }
82 |
83 | public static BError getSQLApplicationError(ApplicationError error, String messagePrefix) {
84 | String message = error.getMessage() != null ? error.getMessage() : error.getClass().getSimpleName();
85 | message = messagePrefix + message;
86 | String errorName;
87 | if (error instanceof ConversionError) {
88 | errorName = Constants.CONVERSION_ERROR;
89 | } else if (error instanceof TypeMismatchError) {
90 | errorName = Constants.TYPE_MISMATCH_ERROR;
91 | } else if (error instanceof FieldMismatchError) {
92 | errorName = Constants.FIELD_MISMATCH_ERROR;
93 | } else if (error instanceof UnsupportedTypeError) {
94 | errorName = Constants.UNSUPPORTED_TYPE_ERROR;
95 | } else if (error instanceof DataError) {
96 | errorName = Constants.DATA_ERROR;
97 | } else {
98 | errorName = Constants.APPLICATION_ERROR;
99 | }
100 | message = removeJavaClassNames(message);
101 | return ErrorCreator.createError(ModuleUtils.getModule(), errorName,
102 | StringUtils.fromString(message), null, null);
103 | }
104 |
105 | public static BError getSQLError(Throwable th, String thMessage) {
106 | String message = th.getMessage() != null ? th.getMessage() : th.getClass().getSimpleName();
107 | thMessage = removeJavaClassNames(thMessage);
108 | message = removeJavaClassNames(message);
109 | return ErrorCreator.createError(ModuleUtils.getModule(), Constants.SQL_ERROR,
110 | StringUtils.fromString(thMessage + message), null, null);
111 | }
112 |
113 | public static BError getNoRowsError(String message) {
114 | message = removeJavaClassNames(message);
115 | return ErrorCreator.createError(ModuleUtils.getModule(), Constants.NO_ROWS_ERROR,
116 | StringUtils.fromString(message), null, null);
117 | }
118 |
119 | private static BError getSQLBatchExecuteError(String message, int vendorCode, String sqlState,
120 | List> executionResults) {
121 | Map valueMap = new HashMap<>();
122 | valueMap.put(Constants.ErrorRecordFields.ERROR_CODE, vendorCode);
123 | valueMap.put(Constants.ErrorRecordFields.SQL_STATE, sqlState);
124 | valueMap.put(Constants.ErrorRecordFields.EXECUTION_RESULTS,
125 | ValueCreator.createArrayValue(executionResults.toArray(), TypeCreator.createArrayType(
126 | TypeCreator.createRecordType(
127 | Constants.EXECUTION_RESULT_RECORD, ModuleUtils.getModule(),
128 | 0, false, 0))));
129 |
130 | BMap sqlClientErrorDetailRecord = ValueCreator.
131 | createRecordValue(ModuleUtils.getModule(), Constants.BATCH_EXECUTE_ERROR_DETAIL, valueMap);
132 | message = removeJavaClassNames(message);
133 | return ErrorCreator.createError(ModuleUtils.getModule(), Constants.BATCH_EXECUTE_ERROR,
134 | StringUtils.fromString(message), null, sqlClientErrorDetailRecord);
135 | }
136 |
137 | private static BError getSQLDatabaseError(String message, int vendorCode, String sqlState) {
138 | Map valueMap = new HashMap<>();
139 | valueMap.put(Constants.ErrorRecordFields.ERROR_CODE, vendorCode);
140 | valueMap.put(Constants.ErrorRecordFields.SQL_STATE, sqlState);
141 | BMap sqlClientErrorDetailRecord = ValueCreator.
142 | createRecordValue(ModuleUtils.getModule(), Constants.DATABASE_ERROR_DETAILS, valueMap);
143 | message = removeJavaClassNames(message);
144 | return ErrorCreator.createError(ModuleUtils.getModule(), Constants.DATABASE_ERROR,
145 | StringUtils.fromString(message), null, sqlClientErrorDetailRecord);
146 | }
147 |
148 | private static String removeJavaClassNames(String message) {
149 | return message.replaceAll(JAVA_CLASS_NAME_REGEX, "");
150 | }
151 | }
152 |
--------------------------------------------------------------------------------