How to get cursor from Oracle using Groovy? - sql

I'm using a Groovy script in Mule ESB to get output parameters from Oracle stored procedure (including cursor) and getting an exception.
Minimal example:
import groovy.sql.Sql
import oracle.jdbc.pool.OracleDataSource
import oracle.jdbc.driver.OracleTypes
def ds = new OracleDataSource()
// setting data source parameters here
def sql = new Sql(ds)
def data = []
sql.call("""declare
result_table sys_refcursor;
begin
open result_table for select 1 as a from dual;
insert into CURSOR_TEST (ID) values (1);
commit;
${Sql.resultSet OracleTypes.CURSOR} := result_table;
insert into CURSOR_TEST (ID) values (2);
commit;
end;
"""
){ table ->
throw new RuntimeException("Never getting this exception.")
table.eachRow {
data << it.toRowResult()
}
}
sql.close()
return data
Error:
Message : java.sql.SQLException: Closed Statement (javax.script.ScriptException)
Code : MULE_ERROR--2
--------------------------------------------------------------------------------
Exception stack is:
1. Closed Statement(SQL Code: 17009, SQL State: + 99999) (java.sql.SQLException)
oracle.jdbc.driver.SQLStateMapping:70 (null)
2. java.sql.SQLException: Closed Statement (javax.script.ScriptException)
org.codehaus.groovy.jsr223.GroovyScriptEngineImpl:323 (http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/script/ScriptException.html)
3. java.sql.SQLException: Closed Statement (javax.script.ScriptException)
(org.mule.api.transformer.TransformerException)
org.mule.module.scripting.transformer.ScriptTransformer:39 (http://www.mulesoft.org/docs/site/current3/apidocs/org/mule/api/transformer/TransformerException.html)
--------------------------------------------------------------------------------
Root Exception stack trace:
java.sql.SQLException: Closed Statement
at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:70)
at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:133)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:199)
+ 3 more (set debug level logging or '-Dmule.verbose.exceptions=true' for everything)
********************************************************************************
Select from CURSOR_TEST returns 1 and 2.
Oracle server version: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production.
Mule version: 3.5.0.
I'm using jdbc\lib\ojdbc6.jar from oracle client version 11.1.0.7.0.
What am I doing wrong?

The following code can help you get variable of SYS_REFCURSOR from Oracle anonymous block.
We should focus on a few key details:
Class groovy.sql.Sql doesn't have corresponding OutParameter and we make it manually as CURSOR_PARAMETER and pass it to sql.call method
Consider that the block starts with {call DECLARE and ends with END } without semicolon after END. Otherwise we can get a poorly recognizable SQLException in the face.
The question marks ? inside the sqlString are places for parameter bindings. Bindings are made in the natural order. In this example:
the first ? binds with the first element in parametersList: "abc", treating the value as IN parameter ;
the second ? binds with CURSOR_PARAMETER treating the value as OUT parameter of passed type;
There is only one enter into closure after sql.call and ResultSet rs provide rows of cursor my_cur declared in anonymous block.
import groovy.sql.OutParameter
import groovy.sql.Sql
import oracle.jdbc.OracleTypes
import java.sql.ResultSet
def driver = 'oracle.jdbc.driver.OracleDriver'
def sql = Sql.newInstance('jdbc:oracle:thin:#MY-SERVER:1521:XXX', 'usr', 'psw', driver)
// special OutParameter for cursor type
OutParameter CURSOR_PARAMETER = new OutParameter() {
public int getType() {
return OracleTypes.CURSOR;
}
};
// look at some ceremonial wrappers around anonymous block
String sqlString = """{call
DECLARE
my_cur SYS_REFCURSOR;
x VARCHAR2(32767) := ?;
BEGIN
OPEN my_cur
FOR
SELECT x || level AS my_column FROM dual CONNECT BY level < 10;
? := my_cur;
END
}
""";
// the order of elements matches the order of bindings
def parametersList = ["abc", CURSOR_PARAMETER];
// rs contains the result set of cursor my_cur
sql.call(sqlString, parametersList) { ResultSet rs ->
while (rs.next()) {
println rs.getString("my_column")
}
};

Related

ORA-01036: illegal variable name/number while running PL SQL block from nodejs

I have following pl/sql block:
DECLARE
user_name varchar(255);
custom_exception EXCEPTION;
PRAGMA exception_init( custom_exception, -20001);
BEGIN
SELECT name
INTO user_name
FROM data_table where ID = '1';
IF user_name = 'temp' THEN
RAISE custom_exception;
END IF;
END;
When I run it from Oracle SQL Developer, it works fine.
But I am getting ORA-01036: illegal variable name/number while running it from nodejs.
Call from Nodejs code:
var output = await connection.execute(fs.readFileSync('path_to_pl_sql_file', 'utf8'), {
message: {
dir: oracledb.BIND_OUT,
type: oracledb.STRING,
maxSize: 100
}
}, {
autoCommit: true
});
Can someone point out what going wrong here?
The error was not with the PL/SQL block.
I was passing in bind variables while executing the PL/SQL from nodejs and that bind variable was not present in the SQL.
Hence the error!

Spring Data Jpa call to Stored Procedure

I am trying to create a method that call Stored Procedure to do a very easy insert and a select,
I created a stored procedure on an Oracle database as below:
CREATE OR REPLACE PROCEDURE schema.insertNewTable(p_test IN VARCHAR2, p_out OUT VARCHAR2)
IS
BEGIN
INSERT INTO NEWTABLE("TEST") VALUES(p_test);
SELECT '1' INTO p_out FROM DUAL;
COMMIT;
END;
Then on an Entity:
#Procedure(procedureName = "insertNewTable", outputParameterName="p_out")
String insertNewTable(#Param("p_test") String p_test);
but everytime i call this method i get this:
2018-06-23 20:04:43,047|ORA-06550: row 1, colonna 7:
PLS-00306: wrong number or types of arguments in call to 'INSERTNEWTABLE'
ORA-06550: riga 1, column 7:
PL/SQL: Statement ignored
2018-06-23 20:04:43,100|org.springframework.dao.InvalidDataAccessResourceUsageException: Error calling CallableStatement.getMoreResults; SQL [insertNewTable]; nested exception is org.hibernate.exception.SQLGrammarException: Error calling CallableStatement.getMoreResults
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:261)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:503)
After a couple of tries (and exceptions), I modified the Entity and the call to avoid "OUT" types and, in this way, I have no errors:
CREATE OR REPLACE PROCEDURE schema.insertNewTable(p_test IN VARCHAR2)
IS
BEGIN
INSERT INTO NEWTABLE("TEST") VALUES(p_test);
COMMIT;
END;
Then on an Entity:
#Procedure(procedureName = "insertNewTable")
void insertNewTable(#Param("p_test") String p_test);
How can i fix this to get a return value?
with Spring DATA
in your repository:
1) calling with native SQL :
#Query(value = "select Store-procedure-name(:param) from dual", nativeQuery = true)
BigDecimal method-name(#Param("param") Long param);
2)
#Procedure(procedureName = "store-procedure-name", outputParameterName = "output-param-of-store-procedure")
return-type-of-store-procedure method-name(input-params-of-store-procedure);
you can use NamedStoredProcedureQuery
#NamedStoredProcedureQuery(
name = "insertNewTable",
procedureName = "insertNewTable",
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, type = String.class, name
= "p_test"),
#StoredProcedureParameter(mode = ParameterMode.OUT, type = String.class, name
= "p_out"),
)
}
)
method call:
StoredProcedureQuery query =
this.em.createNamedStoredProcedureQuery("insertNewTable");
query.setParameter("p_test", ""TEST"");
query.execute();
String out = query.getOutputParameterValue("p_out");

Build a query on the selected record , position of the dataset pointer

to display the content of a geometry/spatial field I made a small component. This component does it's job when I provide all the information to query the table correctly.
tablename
fieldname of the gemoetry field
fieldname of a field to select the correct element by a query
a connection to connect the query to the correct database on my server
a datasource to display other elements of my table for reference
as my component should be similliar to all the other dbmemo, dbedit ... VCL components, I do not want to share
a) fieldname of a field to select the correct element by a query
and if possible also remove from parameter transfer to my component
b) the connection
here come my current working solution and need help on a) and b)
unit Unit_DBmemoSpatial;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, DB, ADODB ;
type
TDBmemoSpatial = class(TMemo)
private
{ Private-Deklarationen }
Fdatasource : Tdatasource ;
FConnection : TADOConnection;
FdataField : String;
procedure Setdatasource(const Value: Tdatasource);
procedure SetdataField(const Value: String);
procedure AfterSCROLL(DataSet: TDataSet);
protected
{ Protected-Deklarationen }
public
{ Public-Deklarationen }
published
{ Published-Deklarationen }
property Connection : TADOConnection read FConnection write FConnection ;
property datasoure : Tdatasource read Fdatasource write Setdatasource ;
property dataField : String read FdataField write SetdataField ;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('TOOLS', [ TDBmemoSpatial]);
end;
{ TDBmemoSpatial }
procedure TDBmemoSpatial.AfterSCROLL(DataSet: TDataSet);
var aQuery : TADOQuery ;
SQLSTR : String ;
RIndex : Integer ;
begin
/// this will only work on my table , not for others !!!!!
RIndex := Fdatasource.DataSet.FieldByName('MyrecordIndex').AsInteger;
self.Lines.Clear;
self.Lines.Add( 'DBCOMP' + TimetoStr(now) );
aQuery := TADOQuery.Create(nil);
try
aQuery.Connection := FConnection;
SQLSTR := 'SELECT ' + FdataField+'.STAsText() FROM ' + 'MyTable' + ' where MyrecordIndex=' + IntToStr(RIndex);
aQuery.SQL.Add(SQLSTR);
aQuery.Open;
self.Lines.Add( 'record affected ' + IntToStr(aQuery.RecordCount) );
self.Lines.Add( 'Text ' + (aQuery.Fields[0].asString) );
finally
aQuery.Free;
end;
end;
procedure TDBmemoSpatial.SetdataField(const Value: String);
begin
FdataField := Value;
end;
procedure TDBmemoSpatial.Setdatasource(const Value: Tdatasource);
begin
Fdatasource := Value;
Fdatasource.DataSet.AfterScroll := AfterSCROLL;
end;
end.
the calling code for my component goes like this
dbmsptl1.Connection := FCon;
dbmsptl1.datasoure := Fdatasource;
dbmsptl1.dataField :='geometry_by_Fieldname';

Can not use method void java.sql.CallableStatement.setObject(String parameterName, Object x, int targetSqlType)

This is my Procedure code:
Specification:
create or replace PACKAGE PKG_NALD AS
PROCEDURE PROC_KETQUA(res OUT SYS_REFCURSOR ,maMH STRING);
END PKG_NALD;
Body:
create or replace PACKAGE BODY PKG_NALD AS
PROCEDURE PROC_KETQUA(res OUT SYS_REFCURSOR ,maMH STRING) AS
BEGIN
OPEN res FOR
select MA_MH as maMH123, TEN_MH as tenMH, TIN_CHI as tinChi, KHOA as khoa from nald_mon_hoc where upper(ma_mh) = upper(maMH);
END PROC_KETQUA;
END PKG_NALD;
And this's my code in order to call Procedure through CallableStatement:
String sqlQuery = "call PKG_NALD.PROC_KETQUA(?, :maMH)";
Connection connection = ((SessionImpl)session).connection();
CallableStatement callable = connection.prepareCall(sqlQuery);
callable.setObject (2, "COSC1310", Types.VARCHAR); // This method belongs to java.sql.PreparedStatement
callable.registerOutParameter(1, OracleTypes.CURSOR);
callable.execute();
The program can run correctly. However, if I replace callable.setObject (2, "COSC1310", Types.VARCHAR) with
callable.setObject("maMH", "COSC1310", Types.VARCHAR); // This method belongs to java.sql.CallableStatement
I received the error:
Exception in thread "main" java.sql.SQLException: The number of parameter names does not match the number of registered praremeters
at oracle.jdbc.driver.OracleSql.setNamedParameters(OracleSql.java:196)
at oracle.jdbc.driver.OracleCallableStatement.execute(OracleCallableStatement.java:4708)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.execute(OraclePreparedStatementWrapper.java:1374)
at com.mkyong.util.QueryUtil.getListFromPackage(QueryUtil.java:71)
at com.mkyong.App.main(App.java:35)
Despite of the document here, something went wrong. What should I do ?
Try changing this
String sqlQuery = "call PKG_NALD.PROC_KETQUA(?, :maMH)";
To this
String sqlQuery = "call PKG_NALD.PROC_KETQUA(?, ?)";

Return cursor results from anonymous block

I have the following SELECT which I want to change into an anonymous block (need to use an anonymous block as doing it within Java and do not have access to created stored functionality) in order to remove the use of the literal into a bind variable:
SELECT IL.LAT_NUM, IL.LNGTD_NUM
FROM ITEM_LOCATION IL
WHERE IL.ITEM_ID = 294341;
I have two anonymous blocks created but am unable to find how to return the values created in both:
1)
DECLARE
itemID number;
latitude number;
longitude number;
BEGIN
itemID := 294341;
SELECT
IL.LAT_NUM,
IL.LNGTD_NUM,
INTO
latitude,
longitude,
FROM
ITEM_LOCATION IL
WHERE
IL.ITEM_ID = itemID ;
END;
2)
DECLARE
TYPE t_ref_cursor IS REF CURSOR;
c_cursor t_ref_cursor;
itemID number;
latitude ITEM_LOCATION.LAT_NUM%TYPE;
longitude ITEM_LOCATION.LNGTD_NUM%TYPE;
BEGIN
itemID := 294341;
OPEN c_cursor FOR
SELECT
IL.LAT_NUM,
IL.LNGTD_NUM,
FROM
ITEM_LOCATION IL
WHERE
IL.ITEM_ID = itemID ;
CLOSE c_cursor;
END;
Does anyone know how either/both of these blocks can return as if it were the SELECT above?
I want to change into an anonymous block ... in order to remove the
use of the literal into a bind variable
Why do you think you need to use an anonymous block to use a bind variable? And both of your blocks still have the value 294341 hard-coded anyway; your select is using a bind variable within the block but is generating a new block every time really much of an improvement on what you had?
As #haki said many hours ago, you cam just use a prepared statement with a bind variable:
PreparedStatement pStmt = conn.prepareStatement(
"SELECT IL.LAT_NUM, IL.LNGTD_NUM :
+ "FROM ITEM_LOCATION IL "
+ "WHERE IL.ITEM_ID = ?");
pStmt.setInt(1, 294341);
... and then execute the query and process the result set as you presumably are already. (From your reply to haki's comment you seem to be confusing a prepared statement - which is a Java/JDBC construct - with a stored procedure in the database).
Here's a simple standalone example against the demo HR schema's EMP table:
import java.sql.;
import java.text.;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;
public class JamesGallagher
{
public static void main(String args[]) throws SQLException
{
Connection conn;
OracleDataSource ds = new OracleDataSource();
ds.setURL("jdbc:oracle:thin:scott/oracle#127.0.0.1:1521:orcl");
conn = ds.getConnection();
PreparedStatement pStmt = conn.prepareStatement(
"select ename, sal from emp where sal > ?");
pStmt.setInt(1, Integer.parseInt(args[0]));
ResultSet rs = pStmt.executeQuery();
while (rs.next())
{
System.out.println(rs.getString(1) + ": " + rs.getInt(2));
}
try { rs.close(); } catch ( Exception ex ) {}
try { pStmt.close(); } catch ( Exception ex ) {}
try { conn.close(); } catch ( Exception ex ) {}
conn = null;
}
}
I can compile that with javac JamesGallagher.java and execute with java JamesGallagher 1500 and it prints the results based on the bound value:
ALLEN: 1600
JONES: 2975
BLAKE: 2850
CLARK: 2450
SCOTT: 3000
KING: 5000
FORD: 3000