CallableStatement with parameter names on PostgreSQL - java

I've tried to call a stored procedure with parameter names specified, but the JDBC failed to accept the parameters. It says:
Method org.postgresql.jdbc4.Jdbc4CallableStatement.setObject(String,Object) is not yet implemented.
I use postgresql-9.2-1003.jdbc4
I there any other way to do this?
I know that I can just specify the sequence number. But I want to specify the parameter names as it is more convenient for me to do so.
My code:
String call_statement = "{ ? = call procedure_name(?, ?, ?) }";
CallableStatement proc = connection.prepareCall(call_statement);
proc.registerOutParameter(1, Types.OTHER);
proc.setObject("param1", 1);
proc.setObject("param2", "hello");
proc.setObject("param3", true);
proc.execute();
ResultSet result = (ResultSet)proc.getObject(1);

Unfortunately, using the parameter names is not a feature supported by the implementation of the JDBC 4 driver for the PostgreSQL database. See the code of this JDBC 4 implementation in GrepCode.
However, you can still continue to use an integer (variable or literal) to indicate the position of the parameter.

It's 2020 here and the standard open source JDBC driver for Postgres still doesn't support named parameter notation for CallableStatement.
Interestingly, EnterpriseDB driver does support it (with that said - I tried to use EDB JDBC driver - it indeed supports named parameters but it does so many things differently, if at all, that we ruled out this option entirly, for those other reasons)
A solution which worked for us - is to use this "hack" (pseudo-code, YMMV):
String sql = "SELECT * FROM PROC(IN_PARAM1 => ?, IN_PARAM2 => ?, IN_PARAM => ?)";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setObject("IN_PARAM1", 1);
ps.setObject("IN_PARAM2", "hello");
ps.setObject("IN_PARAM3", true);
ps.execute();
ResultSet result = (ResultSet)ps.getObject(1);
The killer feature of this notation - is the ability to call SPs with optional params (it could be achieved by having optional ordinal params, but if you have more than a few of them - it becomes a nightmare, as one needs to pass so many nulls, that it's too easy to miscount, and those are very hard to spot)
There are also additional benefits, like ability to return multiple ResultSets (refcursors), ability to use maps as params, etc.
P.S.: we also use the same trick for Node.js with node-postgres - works well for years.

Related

"x is a procedure, use "call"" when I am already using call

I'm using Postgres 12 and have written this procedure:
CREATE OR REPLACE PROCEDURE reduceStock(id INTEGER, soldQuantity INTEGER)
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE inventory SET ProductStockAmount = ProductStockAmount - soldQuantity WHERE ProductID = id;
END;
$$;
It works perfectly if I open up psql on the command line and run call reduceStock(1,1);
However, calling it from my Java program as follows:
CallableStatement stmt = conn.prepareCall("{call reduceStock(?, ?)}");
stmt.setInt(1, productID);
stmt.setInt(2, quantity);
stmt.execute();
Gives me the following error:
What I've Tried
running call reduceStock(1,1); from the psql client - works perfectly
dropping the database and starting over to see if some old definition was cached - didn't work
different capitalisations, spacings of call
Any ideas would be appreciated
You need to remove the curly braces, which are the JDBC escape for calling a procedure. But because Postgres has it's own call command, they are not needed (and collides with the JDBC escape).
CallableStatement stmt = conn.prepareCall("call reducestock(?, ?)");
The curly braces around the procedure inocation ({call reduceStock(?, ?)}) mean that this is not native SQL, but rather JDBC syntax. You can read more about it here: Why do JDBC calls to stored procedures wrap the call in curly brackets?.
So calls like this still have to get translated to the native SQL by the JDBC driver. It happens that the Postgres driver, by default, treats such statements as function calls and translates them to SELECT reduceStock(?, ?) SQL query. This is not how stored procedures shall be called in Postgres. In Postgres a stored procedure call SQL is call reduceStock(?, ?).
One way to make it work would be, like #a_horse_with_no_name wrote in his answer, to remove the curly braces. This makes the statement a native call and because it's a valid Postgres SQL this is going to work. The downside is that it makes it less cross-platform as it will not work for DBs that don't support the call procname() syntax. For example this won't work for Oracle, so if you have to support multiple JDBC drivers, this is the less-preferable way to go.
A better fix would be to hint Postgres JDBC driver to treat this syntax like a stored procedure call rather than a function call and translate it to SQL accordingly. For this purpose the Postgres driver exposes a escapeSyntaxCallMode configuration property (check out the EscapeSyntaxCallMode enum as well):
Specifies how the driver transforms JDBC escape call syntax into underlying SQL, for invoking procedures or functions. (backend >= 11) In escapeSyntaxCallMode=select mode (the default), the driver always uses a SELECT statement (allowing function invocation only). In escapeSyntaxCallMode=callIfNoReturn mode, the driver uses a CALL statement (allowing procedure invocation) if there is no return parameter specified, otherwise the driver uses a SELECT statement. In escapeSyntaxCallMode=call mode, the driver always uses a CALL statement (allowing procedure invocation only).
As you can see all {call something()} statements are treated like function calls by default and always translated to SELECTs. Setting escapeSyntaxCallMode to call will make the driver translate them to call SQL statements instead. The callIfNoReturn option seems most reasonable for most use-cases as it will transform JDBC calls to stored procedure calls if no return parameter has been specified and as function calls otherwise.
You can find an example of using this setting in Postgres docs (Chapter 6. Calling Stored Functions and Procedures):
// set up a connection
String url = "jdbc:postgresql://localhost/test";
Properties props = new Properties();
// ... other properties ...
// Ensure EscapeSyntaxCallmode property set to support procedures if no return value
props.setProperty("escapeSyntaxCallMode", "callIfNoReturn");
Connection con = DriverManager.getConnection(url, props);
// Setup procedure to call.
Statement stmt = con.createStatement();
stmt.execute("CREATE TEMP TABLE temp_val ( some_val bigint )");
stmt.execute("CREATE OR REPLACE PROCEDURE commitproc(a INOUT bigint) AS '"
+ " BEGIN "
+ " INSERT INTO temp_val values(a); "
+ " COMMIT; "
+ " END;' LANGUAGE plpgsql");
stmt.close();
// As of v11, we must be outside a transaction for procedures with transactions to work.
con.setAutoCommit(true);
// Procedure call with transaction
CallableStatement proc = con.prepareCall("{call commitproc( ? )}");
proc.setInt(1, 100);
proc.execute(); proc.close();>
-- https://jdbc.postgresql.org/documentation/head/callproc.html#call-procedure-example

how to print Statement (CallableStatement) in Java?

How can I print this OracleCallableStatement ?
ocstmt = (OracleCallableStatement) connection.prepareCall("{?= call
package.method(id => ?, name=>?)}");
ocstmt.registerOutParameter(1, OracleTypes.CURSOR);
ocstmt.setInt(2, obj.getId());
ocstmt.setString(3, obj.getName());
ocstmt.execute();
resultSet = ocstmt.getCursor(1);
What I mean is how can I know that what query goes to the database, how can I print the query? because sometimes it gives me error like "Wrong type" that is why I want to look at this query
Are you using log4j?
If so, add loggers for sql like below.
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
If you are using a ORM framework such as ibatis, you could add additional logger like below.
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
Yes, you can do this. You can either wrap your callable statement in a proxy that can substitute the actual values when you print it (and show the sql), or hunt around for a driver that has a meaningful toString. javaworld article There is also p6spy, and others.
Stored procedures are harder, but still doable.
You can't get the SQL by printing the Statement.
Is the example you posted one of the "sometimes" that triggers the error?
Why do you have to case this to an OracleCallableStatement? What part of the call is not the standard CallableStatement?
In general, use myObject.toString() to see what it prints. You may or may not be able to see the full query though. If you can't get it to go, the first thing that I would look at is the API documentation(javadocs for that Oracle library or driver that you're using)
I'm not sure if I understand the question, but it seems like you want to see this:
String sql = "{?= call package.method(id => ?, name=>?)}";
System.out.println(sql);
ocstmt = (OracleCallableStatement) connection.prepareCall(sql);
...
It's possible to use proxy jdbc driver to log all jdbc database actions.
this driver can prints all statements with values and all results.
My solution is use ProxyDataSourceBuilder(use it in Spring Boot project).
#Bean
public DataSource dataSource() {
SLF4JQueryLoggingListener loggingListener = new SLF4JQueryLoggingListener();
return ProxyDataSourceBuilder
.create(datasource)
.listener(loggingListener)
.build();
}
...
#Autowired
private DataSource dataSource;
Connection connection = dataSource.getConnection();
ocstmt = (OracleCallableStatement) connection.prepareCall("{?= call
package.method(id => ?, name=>?)}");
And just turn on logging in application.yml:
logging:
level:
net.ttddyy.dsproxy.listener.logging: debug
Try
> java -classpath ojdbc8.jar oracle.jdbc.driver.OracleSql false false "<your sql here>"
That will print the SQL that the driver sends to the database, among other things. This is not documented nor supported, but it's been around forever.
I thought it might be useful if you are looking whether executed query has value or not
System.out.println("value : "+CallableStatement.execute());
i.e The "false" returned by "CallableStatement.execute()" means that the JDBC statement didn't read any rows, (so there's no ResultSet to read). This is what you'd expect, as stored procedures don't directly return ResultSets, they only return values for any OUT or INOUT parameters.

What does this mean? (java,database)

StringBuffer sql = new StringBuffer("{ call ? := mailmerge_package.getLetters(?, ?, ?)}");
I know it's like an sql statement but theres no such thing as 'call' in SQL.
Can someone explain to me what it means and how does it come to be understood by Java
EDIT:
import oracle.jdbc.driver.OracleTypes;
//omitted code
CallableStatement cs = null;
ResultSet rs = null;
StringBuffer sql = new StringBuffer("{ call ? := mailmerge_package.getLetters(?, ?, ?)}");
try {
cs = conn.prepareCall(sql.toString());
cs.registerOutParameter(1, OracleTypes.CURSOR);
DAOUtils.setLong(cs, 3, checklistAnsMastId);
DAOUtils.setLong(cs, 2, workEntityId);
cs.setLong(4, patientId);
DAOUtils.setLong(cs, 5, encounterId);
cs.setString(6, encounterType);
cs.execute();
rs = (ResultSet)cs.getObject(1);
This looks like SQL you can pass to Oracle.
If so then this could be used to call an Oracle function mailmerge_package.getLetters which returns a value. That value is parsed by JDBC and the Db layer to replace the first ?, this can be read into a Java variable. the Oracle function takes 3 parameters (the 3 ? in parentheses)
{call <<procedure name>>}
is a SQL escape sequence. Basically, since different databases have different syntax for how to call a user-defined procedure and different databases have different built-in functions for various things like common date/ time functions, JDBC drivers implement a number of escape sequences where the driver translates a generic specification (i.e. {call <<procedure name>>}) and expands that to be the database-specific syntax. There are various other escape sequences for things like outer joins, date literals, and string functions that can be useful if you're trying to write database agnostic code.
FYI, these escape sequences were originally defined in the ODBC API and then adopted by JDBC so you may find more documentation related to ODBC than JDBC.
It is call to an stored procedure of a database. But which database, I can't tell from this code.

How do I get the return value from a T-SQL stored procedure in Java?

I have a stored procedure that adds an object to the database and returns the generated ID number.
For reference, its basic structure goes something like this:
ALTER PROCEDURE [dbo].[myProc]
#Name nvarchar(50),
#Creator nvarchar(50),
#Text nvarchar(200),
#Lat float,
#Lon float,
#myID int OUTPUT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO myTable --# blah blah blah
SELECT #myID = scope_identity(); --# grab the auto-inc key from myTable
INSERT INTO anotherTable --# blah blah blah
END
I ran this in SQL Server Management Studio and verified that it worked correctly.
Now I want to call that stored procedure from Java. I wrote this code to do it:
CallableStatement cs = con.prepareCall("EXEC myProc "
+ "#Name = ?, #Creator = ?, #Text = ?, #Lat = ?, #Lon = ?, #myID = ? OUTPUT");
cs.setString(1, aString);
cs.setString(2, anotherString);
cs.setString(3, yetAnotherString);
cs.setFloat(4, aFloat);
cs.setFloat(5, anotherFloat);
cs.registerOutParameter(6, java.sql.Types.INTEGER);
cs.execute();
But the execute() call throws an exception:
java.sql.SQLException: [Microsoft][ODBC SQL Server Driver][SQL Server]Error converting data type nvarchar to int.
What is going wrong here? Why is it even trying to convert an nvarchar to an int? I'm not even trying to fetch the return value via getInt() yet (that comes on the next line).
What I've tried:
Building the query by string manipulation to check whether the problem could possibly be in the input parameters. Same exception. At least that narrows the problem down.
Changing the type of the output parameter to Types.NVARCHAR, just in case. But that's not even supported by the JdbcOdbcDriver which I am using.
Messing around with the call syntax (I haven't used SQL Server with Java before). Always ended up with syntax errors. This included trying to make myID a return value instead of an output parameter.
Searching Google. A lot. Ended up with a bunch of unhelpful forum threads and EE "answers".
Now I'm stumped. Is it really this hard or am I just missing the obvious?
Your code looks ok. The only thing that is wrong is that "float" in SQL Server maps to double in Java. "real" in MS SQL maps to float in Java. But this does not produce the problem.
The problem is the buggy ODBC Bridge. You should use a type 4 JDBC driver for MS SQL. If you want to use the ODBC Bridge then you can test setting a value for the 6th parameter:
cs.setInt(6, 0);
But I does not know if this will work. The message means that the driver is using the data type NVARCHAR for the last parameter. It look like the ODBC Bridge does not forward the type with registerOutParameter.
Can't test your code since I don't have SQL Server, but you can try changing to String since this is Java equivalent for NVARCHAR.

Using Oracle ref cursor in Java without Oracle dependency

According to google and some other sources (e.g., http://www.enterprisedt.com/publications/oracle/result_set.html), if I want to call a stored-function that returns a ref cursor, I need to write something like this in order to access the ResultSet:
String query = "begin ? := sp_get_stocks(?); end;";
CallableStatement stmt = conn.prepareCall(query);
// register the type of the out param - an Oracle specific type
stmt.registerOutParameter(1, OracleTypes.CURSOR);
// set the in param
stmt.setFloat(2, price);
// execute and retrieve the result set
stmt.execute();
ResultSet rs = (ResultSet)stmt.getObject(1);
Is there anyway to do it without introducing the compile-time dependency on Oracle. Is there a generic alternative to OracleTypes.CURSOR?
Constant OracleTypes.CURSOR is -10. Quite ugly solution but you can just write -10 there or create your own constant which value is -10.
Have you tried java.sql.Types.OTHER? It might work. API says, it's for database specific types.

Categories