I'm developing a web app with Java 6 EE and DB2. I created a table function that receives 3 parameters and returns a table.
CREATE FUNCTION MY_FUNCTION (PARAM1 VARCHAR(5), PARAM2 VARCHAR(10), PARAM3 INTEGER)
RETURNS TABLE (
FIELD1 VARHCHAR(5),
FIELD2 VARCHAR(10),
FIELD3 INTEGER
)
RETURN
SELECT FIELD1, FIELD2, FIELD3
FROM TABLE_1 WHERE FIELD1 = PARAM1 || '_MAIN'
AND FIELD2 = PARAM2 || '_MAIL' AND FIELD3 = PARAM3 + 47
I'm trying to execute a function in Java with prepared statement as follows (using wildcards):
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM TABLE(MY_FUNCTION(?, ?, ?)) AS TABLE");
But when I run my code, I get an SQLSyntaxErrorException in the prepared statement:
java.sql.SQLSyntaxErrorException: [SQL0418] A statement contains a use of a parameter marker that is not valid
at com.ibm.as400.access.JDError.createSQLExceptionSubClass(JDError.java:828)
at com.ibm.as400.access.JDError.throwSQLException(JDError.java:699)
at com.ibm.as400.access.JDError.throwSQLException(JDError.java:669)
at com.ibm.as400.access.AS400JDBCStatement.commonPrepare(AS400JDBCStatement.java:1660)
at com.ibm.as400.access.AS400JDBCPreparedStatement.<init>(AS400JDBCPreparedStatement.java:248)
at com.ibm.as400.access.AS400JDBCCallableStatement.<init>(AS400JDBCCallableStatement.java:120)
at com.ibm.as400.access.AS400JDBCConnection.prepareCall(AS400JDBCConnection.java:1840)
at com.ibm.as400.access.AS400JDBCConnection.prepareCall(AS400JDBCConnection.java:1741)
Note: If I hardcode the parameters like this (without wilcards) works:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM TABLE(MY_FUNCTION('" + var1 + "', '" + var2 + "', '" + var3 + "')) AS TABLE");
What I want to achieve is to call the functions with the wildcards to improve the processing of the function.
Thanks in advance
Solution with #user384842 answer
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM TABLE(MY_FUNCTION(cast(? as VARCHAR(5)), cast(? as VARCHAR(10)), cast(? as INTEGER))) AS TABLE");
After hunting a bit on google, looks like maybe you need to cast them to the appropriate type? I found this documentation:
requiresCastingOfParametersInSelectClause()
DB2 in fact does require that parameters appearing in the select clause be wrapped in cast() calls to tell the DB parser the type of the select value.
here:
https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/dialect/DB2Dialect.html
Not sure if it's relevant, but might be worth a go? I guess it would look something like cast(? as varchar(30))
Link on casting here http://www.dbatodba.com/db2/how-to-do/how-to-convert-data-types-on-db2/
Related
This is my database:
dragons
id, key, name, age, creation_date
users
id, name, user, pass
users_dragons
user_id, dragon_id
So this is my code for deleting dragons from the database that have a bigger key that the passed and belongs to a determination user. The SQL query works perfectly for deleting them but not for returning the array of keys from the deleted elements.
I tried using PreparedStatement but later I checked, as far as I know, that this class doesn't return arrays, and the CallableStatement is only for executing processes in the db, and I don't know how they return arrays.
String query = "" +
"DELETE FROM dragons " +
"WHERE id IN (SELECT d.id FROM dragons d, users u, users_dragons ud" +
" WHERE d.key > ?" +
" AND ud.dragon_id = d.iD" +
" AND ud.user_id in (select id from users where id = ?)) RETURNING key INTO ?";
CallableStatement callableStatement = connection.prepareCall(query);
int pointer = 0;
callableStatement.setInt(++pointer, key);
callableStatement.setInt(++pointer, credentials.id);
callableStatement.registerOutParameter(++pointer, Types.INTEGER);
callableStatement.executeUpdate();
return (int []) callableStatement.getArray(1).getArray();
The code is giving me the error, but is obvious because the CallableStatement needs a postgres function to run and not a simple SQL query
org.postgresql.util.PSQLException: This statement does not declare an OUT parameter.
Use { ?= call ... } to declare one.
at org.postgresql.jdbc.PgCallableStatement.registerOutParameter
.......
It would be really helpful how would be the correct JDBC algorithm to delete the elements from the database and return the array of keys of the deleted items.
You treat such a statement like a normal SELECT statement: use java.sql.PreparedStatement.executeQuery() or java.sql.Statement.executeQuery(String sql) to execute the statement and get a result set.
java.sql.CallableStatement is for calling Procedures (but you don't need it in PostgreSQL).
I have connected Java to SSMS and can call data from the server no problems using something like this:
String cell = "SELECT [Close] FROM ExcelData WHERE id_num = 3";
Statement st4 = con.createStatement();
ResultSet rs4 = st4.executeQuery(cell);
while (rs4.next())
{
Float close = rs4.getFloat("close");
System.out.format("%s\n", close);
}
When I replace the "SELECT [Close] FROM ExcelData WHERE id_num = 3" to "SELECT #SMA" I get the much questioned "Must declare the scalar variable #SMA.
I do not know how to do that.
Answering my own question to help others who may need to know this...
It cannot be done. The work around is to create a table in SQL and insert the variable into the table.
DROP TABLE IF EXISTS SMA
CREATE TABLE SMA (
SMA_Price DECIMAL (6,5),
)
INSERT INTO SMA VALUES (#SMA);
SELECT * FROM SMA;
You can then call "SELECT * FROM SMA" from Java.
I am having code something like this.
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
Calculation of fullTableName is something like:
public String getFullTableName(final String table) {
if (this.schemaDB != null) {
return this.schemaDB + "." + table;
}
return table;
}
Here schemaDB is the name of the environment(which can be changed over time) and table is the table name(which will be fixed).
Value for schemaDB is coming from an XML file which makes the query vulnerable to SQL injection.
Query: I am not sure how the table name can be used as a prepared statement(like the name used in this example), which is the 100% security measure against SQL injection.
Could anyone please suggest me, what could be the possible approach to deal with this?
Note: We can be migrated to DB2 in future so the solution should compatible with both Oracle and DB2(and if possible database independent).
JDBC, sort of unfortunately, does not allow you to make the table name a bound variable inside statements. (It has its reasons for this).
So you can not write, or achieve this kind of functionnality :
connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);
And have TUSER be bound to the table name of the statement.
Therefore, your only safe way forward is to validate the user input. The safest way, though, is not to validate it and allow user-input go through the DB, because from a security point of view, you can always count on a user being smarter than your validation.
Never trust a dynamic, user generated String, concatenated inside your statement.
So what is a safe validation pattern ?
Pattern 1 : prebuild safe queries
1) Create all your valid statements once and for all, in code.
Map<String, String> statementByTableName = new HashMap<>();
statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?");
statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?");
If need be, this creation itself can be made dynamic, with a select * from ALL_TABLES; statement. ALL_TABLES will return all the tables your SQL user has access to, and you can also get the table name, and schema name from this.
2) Select the statement inside the map
String unsafeUserContent = ...
String safeStatement = statementByTableName.get(usafeUserContent);
conn.prepareStatement(safeStatement, name);
See how the unsafeUserContent variable never reaches the DB.
3) Make some kind of policy, or unit test, that checks that all you statementByTableName are valid against your schemas for future evolutions of it, and that no table is missing.
Pattern 2 : double check
You can 1) validate that the user input is indeed a table name, using an injection free query (I'm typing pseudo sql code here, you'd have to adapt it to make it work cause I have no Oracle instance to actually check it works) :
select * FROM
(select schema_name || '.' || table_name as fullName FROM all_tables)
WHERE fullName = ?
And bind your fullName as a prepared statement variable here. If you have a result, then it is a valid table name. Then you can use this result to build a safe query.
Pattern 3
It's sort of a mix between 1 and 2.
You create a table that is named, e.g., "TABLES_ALLOWED_FOR_DELETION", and you statically populate it with all tables that are fit for deletion.
Then you make your validation step be
conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);
If this has a result, then you execute the safe_table_name. For extra safety, this table should not be writable by the standard application user.
I somehow feel the first pattern is better.
You can avoid attack by checking your table name using regular expression:
if (fullTableName.matches("[_a-zA-Z0-9\\.]+")) {
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
}
It's impossible to inject SQL using such a restricted set of characters.
Also, we can escape any quotes from table name, and safely add it to our query:
fullTableName = StringEscapeUtils.escapeSql(fullTableName);
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
StringEscapeUtils comes with Apache's commons-lang library.
I think that the best approach is to create a set of possible table names and check for existance in this set before creating query.
Set<String> validTables=.... // prepare this set yourself
if(validTables.contains(fullTableName))
{
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
//and so on
}else{
// ooooh you nasty haker!
}
create table MYTAB(n number);
insert into MYTAB values(10);
commit;
select * from mytab;
N
10
create table TABS2DEL(tname varchar2(32));
insert into TABS2DEL values('MYTAB');
commit;
select * from TABS2DEL;
TNAME
MYTAB
create or replace procedure deltab(v in varchar2)
is
LvSQL varchar2(32767);
LvChk number;
begin
LvChk := 0;
begin
select count(1)
into LvChk
from TABS2DEL
where tname = v;
if LvChk = 0 then
raise_application_error(-20001, 'Input table name '||v||' is not a valid table name');
end if;
exception when others
then raise;
end;
LvSQL := 'delete from '||v||' where n = 10';
execute immediate LvSQL;
commit;
end deltab;
begin
deltab('MYTAB');
end;
select * from mytab;
no rows found
begin
deltab('InvalidTableName');
end;
ORA-20001: Input table name InvalidTableName is not a valid table name ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 21
ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 16
ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721
I have a Java exception when calling a function that returns nothing:
org.postgresql.util.PSQLException: A CallableStatement was executed with nothing returned.
The Java code is similar to this:
// Procedure call.
CallableStatement proc = con.prepareCall("{ ? = call doquery ( ? ) }");
proc.registerOutParameter(1, Types.Other);
proc.setInt(2, 33434);
proc.execute();
ResultSet results = (ResultSet) proc.getObject(1);
while (results.next()) {
// do something with the results...
}
results.close();
proc.close();
The query is very simple:
select * from table where idTable = 33434;
The query does not return any value because what I'm looking for in postgresql DB does not exist. A sql query is like that, not always we get something in return.
How do you deal with this situations?
PS.- The Postgresql function:
CREATE OR REPLACE FUNCTION doquery(_idTable bigint)
RETURNS TABLE(idTable bigint, name varchar) AS
$BODY$
DECLARE
searchsql text := '';
BEGIN
searchsql := 'SELECT * FROM table
WHERE idTable = ' || _idTable;
RETURN QUERY EXECUTE searchsql;
END
$BODY$
LANGUAGE plpgsql;
Don't use a CallableStatement. They are intended for stored procedures not functions.
As your function returns a resultset, you need to use a select statement:
PreparedStatement pstmt = con.prepareStatement("select * from doquery(?)");
pstmt.setInt(1, 33434);
ResultSet results = pstmt.executeQuery();
while (results.next()) {
// do something with the results...
}
results.close();
proc.close();
Note that the use of dynamic SQL or even PL/pgSQL is not needed. You should also not append parameters to queries (the same way you shouldn't do it in Java as well). Use parameter placeholders:
CREATE OR REPLACE FUNCTION doquery(_idTable bigint)
RETURNS TABLE(idTable bigint, name varchar) AS
$BODY$
BEGIN
RETURN QUERY
SELECT *
FROM table
WHERE idTable = _idTable;
END
$BODY$
LANGUAGE plpgsql;
Or even simpler as a pure SQL function:
CREATE OR REPLACE FUNCTION doquery(_idTable bigint)
RETURNS TABLE(idTable bigint, name varchar) AS
$BODY$
SELECT idtable, name
FROM table
WHERE idTable = _idTable;
$BODY$
LANGUAGE sql;
If you do need dynamic SQL then use placeholders inside the string and pass the parameters to the execute function. Do not concatenate values:
CREATE OR REPLACE FUNCTION doquery(_idTable bigint)
RETURNS TABLE(idTable bigint, name varchar) AS
$BODY$
BEGIN
RETURN QUERY EXECUTE '
SELECT *
FROM table
WHERE idTable = $1'
USING _idTable;
END
$BODY$
LANGUAGE plpgsql;
Suppose I have the query:
SELECT * FROM CUSTOMERS WHERE CUSTOMER_ID = ?
With PreparedStatement, I can bind the variable:
pstmt.setString(1, custID);
However, I cannot obtain the correct results with the following binding:
pstmt.setString(1, null);
As this results in:
SELECT * FROM CUSTOMERS WHERE CUSTOMER_ID = NULL
which does not give any result. The correct query should be:
SELECT * FROM CUSTOMERS WHERE CUSTOMER_ID IS NULL
The usual solutions are:
Solution 1
Dynamically generate query:
String sql = "SELECT * FROM CUSTOMERS WHERE CUSTOMER_ID "
+ (custID==null ? "IS NULL" : "= ?");
if (custID!=null)
pstmt.setString(1, custID);
Solution 2
Use NVL to convert null value to a gibberish value:
SELECT * FROM CUSTOMERS WHERE NVL(CUSTOMER_ID, 'GIBBERISH') = NVL(?, 'GIBBERISH');
But you need to be 100% sure that the value 'GIBBERISH' will never be stored.
Question
Is there a way to use a static query and avoid depending on gibberish value conversions? I am looking for something like:
SELECT * FROM CUSTOMERS
WHERE /** IF ? IS NULL THEN CUSTOMER_ID IS NULL ELSE CUSTOMER_ID = ? **/
I think I may have a working solution:
SELECT * FROM CUSTOMERS
WHERE ((? IS NULL AND CUSTOMER_ID IS NULL) OR CUSTOMER_ID = ?)
pstmt.setString(1, custID);
pstmt.setString(2, custID);
Will the above work reliably? Is there a better way (possibly one that requires setting the parameter only once)? Or is there no way to do this reliably at all?
Your working solution is fine (and similar to what I've used before). If you only want to bind once you can use a CTE or inline view to provide the value to the real query:
WITH CTE AS (
SELECT ? AS REAL_VALUE FROM DUAL
)
SELECT C.* -- but not * really, list all the columns
FROM CTE
JOIN CUSTOMERS C
ON (CTE.REAL_VALUE IS NULL AND C.CUSTOMER_ID IS NULL)
OR C.CUSTOMER_ID = CTE.REAL_VALUE
So there is only one placeholder to bind.
I don't really see a problem with a branch on the Java side though, unless your actual query is much more complicated and would lead to significant duplication.
WHERE
DECODE(CUSTOMER_ID, NULL, 'NULL', CUSTOMER_ID || 'NOT NULL') =
DECODE(?, NULL, 'NULL', CUSTOMER_ID, CUSTOMER_ID || 'NOT NULL')
This works, I believe
SQLFiddle
Note that in order to test it on sqlfiddle I have had to replace the parameter with a value for each case [NULL, 'NULL', 'SMITH']
Dynamically creating the query works on all JDBCs, so you are not bound to platform-specific SQL.
It wouldn't be that hard to read to create an if-branch, would it?
PreparedStatement pst = null; // Avoid initialisation warnings
if (custID == null)
pst = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE CUSTOMER_ID IS NULL");
else {
pst = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE CUSTOMER_ID = ?");
pst.setString(1, custID);
}
ResultSet rs = pst.executeQuery();