Skip to content

Commit c8606b9

Browse files
authored
21 support for stored procedures (#22)
* Creating action to call stored procedure * Adding integration test. * Improving docs and changelog with procedure call action description * Improving docs.
1 parent 6ab0753 commit c8606b9

19 files changed

+1638
-31
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,26 @@ Checkbox ``Don't throw Error on an Empty Result`` allows to emit an empty respon
206206
![image](https://user-images.githubusercontent.com/40201204/43644579-f593d1c8-9737-11e8-9b97-ee9e575a19f7.png)
207207
As an input metadata you will get a Primary Key field to provide the data inside as a clause value.
208208

209+
### Execute stored procedure
210+
This action calls stored procedure from selected `DB Schema` and `Stored procedure` name
211+
#### Input fields description
212+
- **DB Schema** - a schema that contains a procedure to call. Must be selected from the dropdown list before `Stored procedure` name
213+
- **Stored procedure** - a name of a procedure to call, can be selected from the dropdown list
214+
215+
Metadata generates automatically using `IN` & `IN OUT` procedure parameters for input, and `OUT` & `IN OUT` procedure parameters for output.
216+
217+
As array fields this action now support ONLY:
218+
- CURSOR (as SQL type)
219+
- REF CURSOR (as ORACLE type)
220+
The result for this type of fields would be returned as an array of JSON objects.
221+
222+
This action DOES NOT processing MSSql @RETURN_VALUE.
223+
224+
For MySQL component same to DATABASE is same to SCHEMA by it's
225+
[definition](https://dev.mysql.com/doc/refman/8.0/en/getting-information.html), so DB Schema dropdown is empty for MySQL.
226+
227+
[MSSQL DB](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-procedure-transact-sql?view=sql-server-2017) stored procedures has only IN and INOUT fields.
228+
209229
### Create or update record action (Deprecated)
210230
This action exists in JDBC component only for backward compatibility. [**Upsert row by primary key**](#upsert-row-by-primary-key-action) Action is recommended to use.
211231

@@ -219,6 +239,7 @@ This action exists in JDBC component only for backward compatibility. [**Upsert
219239
- ``Oracle`` - compatible with Oracle Database 8.1.7 - 12.1.0.2
220240
- ``MSSQL`` - compatible with Microsoft SQL Server 2008 R2 and higher
221241
3. The current implementation of the action ``Upsert By Primary Key`` doesn't mark non-nullable fields as required fields at a dynamic metadata. In case of updating such fields with an empty value you will get SQL Exception ``Cannot insert the value NULL into...``. You should manually fill in all non-nullable fields with previous data, if you want to update part of columns in a row, even if data in that fields doesn't change.
242+
4. The current implementation of the action ``Execute stored procedure`` doesn't support ResultSet response type parameters for stored procedure output.
222243

223244
## License
224245
Apache-2.0 © [elastic.io GmbH](https://www.elastic.io "elastic.io GmbH")

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group = 'io.elastic'
2-
version = '2.0.1'
2+
version = '2.1.0'
33
apply plugin: 'java'
44
apply plugin: 'idea'
55
apply plugin: 'eclipse'

changelog.md

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,25 @@
11
# jdbc Component Change Log
22

3+
## 2.1.0 (2019-07-22)
34

4-
## [Unrelased]
5-
### Added
6-
### Change
7-
### Deprecated
8-
### Removed
9-
### Fixed
10-
### Security
5+
* Add Execute stored procedure action
116

12-
## [V2.0.1] 2019-06-24 elastic.io
7+
## 2.0.1 (2019-06-24)
138

14-
### Fixed
15-
- Improvement error logging for generating metadata of `Select` action
9+
* Fix error logging for generating metadata of `Select` action
1610

17-
## [V2.0.0] 2018-09-19 elastic.io
11+
## 2.0.0 (2018-09-19)
1812

19-
### Added
13+
* Add Select trigger
14+
* Add Get rows polling trigger
2015

21-
Triggers
22-
- SELECT
23-
- GET ROWS POLLING
16+
* Add Select action
17+
* Add Lookup by primary key action
18+
* Add Upsert by primary key action (for migration)
19+
* Add Delete by primary key action
2420

25-
Actions
26-
- SELECT
27-
- LOOKUP BY PRIMARY KEY
28-
- UPSERT BY PRIMARY KEY (for migration)
29-
- DELETE BY PRIMARY KEY
21+
* Remove CreateOrUpdateRecord action
3022

31-
### Removed
32-
Actions
33-
- CreateOrUpdateRecord
34-
35-
### Fixed
36-
- fix issue in postgresql - getDate(null)
37-
- fix null values as input for select
23+
* Fix issue in postgresql - getDate(null)
24+
* Fix null values as input for select
3825

component.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@
132132
}
133133
},
134134
"actions": {
135-
136135
"lookupRowByPrimaryKey": {
137136
"main": "io.elastic.jdbc.actions.LookupRowByPrimaryKey",
138137
"title": "Lookup Row By Primary Key",
@@ -230,6 +229,31 @@
230229
}
231230
},
232231
"dynamicMetadata": "io.elastic.jdbc.ColumnNamesProviderOld"
232+
},
233+
"executeStoredProcedure": {
234+
"main": "io.elastic.jdbc.actions.ExecuteStoredProcedure",
235+
"title": "Execute stored procedure",
236+
"description": "Executing selected stored procedure from selected DB schema",
237+
"fields": {
238+
"schemaName": {
239+
"viewClass": "SelectView",
240+
"prompt": "Select a Schema",
241+
"label": "DB Schema",
242+
"required": true,
243+
"model": "io.elastic.jdbc.SchemasProvider"
244+
},
245+
"procedureName": {
246+
"viewClass": "SelectView",
247+
"prompt": "Select a stored procedure name",
248+
"label": "Stored procedure",
249+
"model": "io.elastic.jdbc.ProcedureFieldsNameProvider",
250+
"required": true,
251+
"require": [
252+
"schemaName"
253+
]
254+
}
255+
},
256+
"dynamicMetadata": "io.elastic.jdbc.ProcedureFieldsNameProvider"
233257
}
234258
}
235259
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package io.elastic.jdbc;
2+
3+
import io.elastic.api.DynamicMetadataProvider;
4+
import io.elastic.api.SelectModelProvider;
5+
import io.elastic.jdbc.ProcedureParameter.Direction;
6+
import java.sql.Connection;
7+
import java.sql.DatabaseMetaData;
8+
import java.sql.ResultSet;
9+
import java.util.ArrayList;
10+
import java.util.LinkedList;
11+
import java.util.List;
12+
import javax.json.Json;
13+
import javax.json.JsonObject;
14+
import javax.json.JsonObjectBuilder;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
18+
public class ProcedureFieldsNameProvider implements DynamicMetadataProvider, SelectModelProvider {
19+
20+
private static final Logger LOGGER = LoggerFactory.getLogger(ProcedureFieldsNameProvider.class);
21+
22+
@Override
23+
public JsonObject getSelectModel(JsonObject configuration) {
24+
JsonObjectBuilder result = Json.createObjectBuilder();
25+
List<String> proceduresNames = getProceduresList(configuration);
26+
proceduresNames.forEach(procedure -> result.add(procedure, procedure));
27+
return result.build();
28+
}
29+
30+
@Override
31+
public JsonObject getMetaModel(JsonObject configuration) {
32+
List<ProcedureParameter> parameters = getProcedureMetadata(configuration);
33+
return paramsListToMetadata(parameters);
34+
}
35+
36+
public static JsonObject paramsListToMetadata(List<ProcedureParameter> parameters) {
37+
JsonObjectBuilder result = Json.createObjectBuilder();
38+
JsonObjectBuilder inFields = Json.createObjectBuilder();
39+
JsonObjectBuilder outFields = Json.createObjectBuilder();
40+
41+
parameters.stream()
42+
.filter(p -> p.getDirection() == Direction.IN || p.getDirection() == Direction.INOUT)
43+
.forEach(p -> {
44+
JsonObjectBuilder valueContent = Json.createObjectBuilder()
45+
.add("type", Utils.cleanJsonType(Utils.detectColumnType(p.getType(), "")))
46+
.add("name", p.getName())
47+
.add("required", true);
48+
inFields.add(p.getName(), valueContent.build());
49+
});
50+
51+
JsonObjectBuilder inMetadata = Json.createObjectBuilder()
52+
.add("type", "object")
53+
.add("properties", inFields.build());
54+
55+
parameters.stream()
56+
.filter(p -> p.getDirection() == Direction.OUT || p.getDirection() == Direction.INOUT)
57+
.forEach(p -> {
58+
JsonObjectBuilder valueContent = Json.createObjectBuilder()
59+
.add("type", Utils.cleanJsonType(Utils.detectColumnType(p.getType(), "")))
60+
.add("name", p.getName())
61+
.add("required", true);
62+
outFields.add(p.getName(), valueContent.build());
63+
});
64+
65+
JsonObjectBuilder outMetadata = Json.createObjectBuilder()
66+
.add("type", "object")
67+
.add("properties", outFields.build());
68+
69+
result.add("in", inMetadata.build()).add("out", outMetadata.build());
70+
71+
JsonObject metadataResponse = result.build();
72+
LOGGER.trace(metadataResponse.toString());
73+
return metadataResponse;
74+
}
75+
76+
public List<String> getProceduresList(JsonObject config) {
77+
List<String> result = new ArrayList<>();
78+
try (Connection conn = Utils.getConnection(config)) {
79+
DatabaseMetaData meta = conn.getMetaData();
80+
ResultSet res = meta.getProcedures(null, config.getString("schemaName"), null);
81+
while (res.next()) {
82+
String cat = res.getString("PROCEDURE_CAT");
83+
String schem = res.getString("PROCEDURE_SCHEM");
84+
String name = res.getString("PROCEDURE_NAME");
85+
86+
name = name.indexOf(';') == -1 ? name : name.substring(0, name.indexOf(';'));
87+
88+
result.add(name);
89+
}
90+
res.close();
91+
} catch (Exception e) {
92+
throw new RuntimeException(e);
93+
}
94+
95+
return result;
96+
}
97+
98+
public static List<ProcedureParameter> getProcedureMetadata(JsonObject config) {
99+
List<ProcedureParameter> parameters = new LinkedList<>();
100+
101+
try (Connection conn = Utils.getConnection(config)) {
102+
DatabaseMetaData dbMetaData = conn.getMetaData();
103+
ResultSet rs = dbMetaData.getProcedureColumns(conn.getCatalog(),
104+
config.getString("schemaName"),
105+
config.getString("procedureName"),
106+
null);
107+
108+
int order = 1;
109+
while (rs.next()) {
110+
// get stored procedure metadata
111+
String procedureCatalog = rs.getString(1);
112+
String procedureSchema = rs.getString(2);
113+
String procedureName = rs.getString(3);
114+
String columnName = rs.getString(4);
115+
short columnReturn = rs.getShort(5);
116+
int columnDataType = rs.getInt(6);
117+
String columnReturnTypeName = rs.getString(7);
118+
int columnPrecision = rs.getInt(8);
119+
int columnByteLength = rs.getInt(9);
120+
short columnScale = rs.getShort(10);
121+
short columnRadix = rs.getShort(11);
122+
short columnNullable = rs.getShort(12);
123+
String columnRemarks = rs.getString(13);
124+
125+
if (columnReturnTypeName.equals("REF CURSOR")) {
126+
columnDataType = -10;
127+
}
128+
129+
if (columnName.equals("@RETURN_VALUE")) {
130+
continue;
131+
}
132+
133+
parameters.add(new ProcedureParameter(columnName, columnReturn, columnDataType, order++));
134+
}
135+
} catch (Exception e) {
136+
throw new RuntimeException(e);
137+
}
138+
139+
return parameters;
140+
}
141+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package io.elastic.jdbc;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
6+
public class ProcedureParameter {
7+
8+
private String name;
9+
private Direction direction;
10+
private int type;
11+
private int order;
12+
13+
public ProcedureParameter(String name, Direction direction, int type) {
14+
this.name = name;
15+
this.direction = direction;
16+
this.type = type;
17+
}
18+
19+
public ProcedureParameter(String name, short direction, int type) {
20+
this.name = name;
21+
this.direction = jdbcColTypeToDirection(direction);
22+
this.type = type;
23+
}
24+
25+
public ProcedureParameter(String name, Direction direction, int type, int order) {
26+
this.name = name;
27+
this.direction = direction;
28+
this.type = type;
29+
this.order = order;
30+
}
31+
32+
public ProcedureParameter(String name, short direction, int type, int order) {
33+
this.name = name;
34+
this.direction = jdbcColTypeToDirection(direction);
35+
this.type = type;
36+
this.order = order;
37+
}
38+
39+
private Direction jdbcColTypeToDirection(short direction) {
40+
List<Short> inputTypes = Arrays.asList((short) 1);
41+
List<Short> outputTypes = Arrays.asList((short) 3, (short) 4, (short) 5);
42+
List<Short> inOutputTypes = Arrays.asList((short) 2);
43+
44+
return inputTypes.contains(direction) ? Direction.IN
45+
: outputTypes.contains(direction) ? Direction.OUT
46+
: inOutputTypes.contains(direction) ? Direction.INOUT
47+
: Direction.UNDEFINED;
48+
}
49+
50+
public enum Direction {
51+
IN("in"),
52+
OUT("out"),
53+
INOUT("inout"),
54+
UNDEFINED("undefined");
55+
56+
private String dirName;
57+
58+
Direction(String dirName) {
59+
this.dirName = dirName;
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return this.dirName;
65+
}
66+
}
67+
68+
public String getName() {
69+
return name;
70+
}
71+
72+
public Direction getDirection() {
73+
return direction;
74+
}
75+
76+
public int getType() {
77+
return type;
78+
}
79+
80+
public int getOrder() {
81+
return order;
82+
}
83+
}

0 commit comments

Comments
 (0)