BadSqlGrammarException with namedParameterJdbcTemplate but not psql - java

I'm altering a Postgres column's type from UUID to TEXT with
ALTER TABLE my_table ALTER COLUMN id TYPE TEXT;
But I want to ensure my code works with both column types. i.e. As it's difficult to sync up the alteration of the db with the code which runs on a server.
I'm testing this locally by switching between the types. This can be done by moving back to UUID (while the data is in the correct format) with
ALTER TABLE my_table ALTER COLUMN id TYPE UUID USING id::uuid;
This works. But when switched to a TEXT and running my Java code I noticed some failures in the tests. Specifically whenever a WHERE statement makes use of the switched id. e.g.
UPDATE my_table SET name = 'new_name' WHERE id = '00000000-0000-0000-0001-000000000000'::uuid;
generates
ERROR: operator does not exist: text = uuid
That makes sense to me as the namedParameterJdbcTemplate class I'm using is adding a ::uuid to the end of the id in the SQL query. I'm not certain why it bothers doing that but it can be worked around by converting the passed in parameter from a UUID to a string.
However if I switch the column back to UUID I get the following error with
UPDATE my_table SET name = 'new_name' WHERE id = '00000000-0000-0000-0001-000000000000';
generating
ERROR: operator does not exist: text = uuid
I can accept the first update query causing issues because I can run it on the psql command line and get the same error. However this does not happen with the second command in psql.
Furthermore if I stop using the namedParameterJdbcTemplate's parameters and bake the values into the sql string it works.
String sqlStatement = "UPDATE my_table SET name = :v_new_name WHERE id = :v_id";
MapSqlParameterSource sqlParameterMap = new MapSqlParameterSource();
sqlParameterMap.addValue("v_new_name", "New Name");
sqlParameterMap.addValue("v_id", id.toString());
namedParameterJdbcTemplate.update(sqlStatement, sqlParameterMap);
Generates
ERROR: operator does not exist: uuid = character varying
thrown from SQLStateSQLExceptionTranslator.java.
versus
String sqlStatement = "UPDATE my_table SET name = 'New Name' WHERE id = '" + id.toString() + "'";
MapSqlParameterSource sqlParameterMap = new MapSqlParameterSource();
namedParameterJdbcTemplate.update(sqlStatement, sqlParameterMap);
which works.
Why is that the case? It feels like the namedParameterJdbcTemplate is doing some extra type checking that I cannot find.

Frustrated with the idea of having to bake the variables into the SQL statement I took a guess and tried the following
String sqlStatement = "UPDATE my_table SET name = :v_new_name WHERE id = :v_id";
MapSqlParameterSource sqlParameterMap = new MapSqlParameterSource();
sqlParameterMap.addValue("v_new_name", "New Name");
sqlParameterMap.addValue("v_id", id.toString(), java.sql.Types.OTHER);
namedParameterJdbcTemplate.update(sqlStatement, sqlParameterMap);
To my surprised it worked. Before I had experimented with the differences between Types.BINARY and Types.VARCHAR. But I guess I needed to be less specific. The definition of OTHER states
The constant in the Java programming language that indicates that the SQL type is database-specific and gets mapped to a Java object that can be accessed via the methods getObject and setObject.
Which sounds like an appropriate default value but it seems the library does not use it as such.
I do have to note that this only works with id.toString(). Using id by itself leads to a type error.
Thanks to some insights from #a_horse_with_no_name which helped.

Related

Query returning sql string with wrong parameters

I am currently working on fixing some SQL injection bugs in my project.
Here is my current sql string:
String sql = "select * from :table order by storenum";
Here is how I am setting the parameters:
SQLQuery query = sess.createSQLQuery(sql).setParameter("table", table);
(table is a string that is passed in through a method)
Whenever I run the program I get something like this:
select * from ? order by storenum
You can't dynamically bind table names, only values, so you'll have to resort to string manipulation/concatenation to get the table name dynamically. However, you would probably want to escape it to avoid SQL Injections.

JDBC prepared statement to query JSON using json_exists

I'm facing trouble transforming the below query to jdbc prepared statement and setting the parameters.
oracle query:
select * from TRANSACTION_DUMMY where ID = 'aa'
and JSON_EXISTS(TRANSACTION_DUMMY_INDEX FORMAT JSON,
'$.header.lineItems[*].status?(#=="complete")')
translated query:
select * from TRANSACTION_DUMMY where ID = ?
and JSON_EXISTS(TRANSACTION_DUMMY_INDEX FORMAT JSON,
'$.header.lineItems[*].status?(#==?)')
the issue is how to set parameters in the query.
tried playing around with indexes but always getting the error, invalid column index.
any pointers how to handle the above scenario using java jdbc prepared statement?
thanks
According to the documentation, the second argument to JSON_EXISTS is a special string literal called JSON_path_expression.
If the value of the expression should change dynamically, it will be easiest to create it on the client (Java) side and then concatenate it into the query. You cannot pass the path expression as a bind variable because Oracle expects it to be a literal, i.e. a "parse-time constant". As you noticed, you'll get an ORA-40454: path expression not a literal error message if you try to pass the expression as a bind value.
The following code uses Java's String.format() for injecting the expression into the SQL template:
String sql = "select * from TRANSACTION_DUMMY where ID = 'aa' "
+ "and JSON_EXISTS(TRANSACTION_DUMMY_INDEX_FORMAT_JSON, %s)";
// here you could have some code for modifying jsonPathExpression dynamically,
// e.g. changing the status based on some criteria
String jsonPathExpression = "'$.header.lineItems[*].status?(#==\"complete\")'";
try (Statement st = myConnection.createStatement(String.format(sql, jsonPathExpression))) {
ResultSet st = ps.executeQuery();
// Process result set
}

Java SQL PreparedStatement UPDATE with optional attributes

I'm writing a Java GUI application which uses a MySQL database. In this application the users can see a JTable with the rows of DB's table and modify the attributes of a selected row with a form. I'd like to update only the modified attributes of the row. I know I have to specify every SQL table's column in the String command if I use PreparedStatement and placeholders
String command = "UPDATE table SET attr0 = ?, attr1 = ? WHERE id = ?";
PreparedStatement statement = connection.prepareStatement(command);
but It's not what I'm looking for. Moreover, my DB's table has many columns, so I can't use different String commands for every combination of attributes.
Can someone help me? Thanks.
Unfortunately with straight JDBC, the best you can do is build the SET clause dynamically (see rough code sample below). JDBC can't handle optional parameters and will throw an Exception if not all parameters are bound before executing.
`
// 'columns' assumed a Map
// 'id' to be a String
List<String> setClauses = new ArrayList<String>();
for (String key : columns.keySet()) {
setClauses.add(String.format("%s=?", key));
}
// StringUtils is from Apache Commons Lang
// although it's pretty easy to build your own join routine.
String command = String.format("UPDATE table SET %s WHERE id=?"
, StringUtils.join(setClauses, ",")
);
PreparedStatement statement = connection.prepareStatement(command);
int p = 1;
for (String key : columns.keySet()) {
statement.setString(p++, columns.get(key));
}
statement.setString(p++, id);
`
JDBC also doesn't have named parameters either so this is why you have to do the incrementing. If you are able to do it, I would recommend investigating Hibernate (which allows to work with JavaBeans) or Spring JDBCTemplate (which does have named parameters).

Error with simple Parameterized Query - Java/ SQL

Following on from one of my previous questions to do with method design I was advised to implemented my SQL queries as a parameterized query as opposed to a simple string.
I've never used parameterized queries before so I decided to start with something simple, take the following Select statement:
String select = "SELECT * FROM ? ";
PreparedStatement ps = connection.prepareStatement(select);
ps.setString(1, "person");
This gives me the following error: "[SQLITE_ERROR] SQL error or missing database (near "?": syntax error)"
I then tried a modified version which has additional criteria;
String select = "SELECT id FROM person WHERE name = ? ";
PreparedStatement ps = connection.prepareStatement(select);
ps.setString(1, "Yui");
This version works fine, in the my first example am I missing the point of parameterized queries or am I constructing them incorrectly?
Thanks!
Simply put, SQL binds can't bind tables, only where clause values. There are some under-the-hood technical reasons for this related to "compiling" prepared SQL statements. In general, parameterized queries was designed to make SQL more secure by preventing SQL injection and it had a side benefit of making queries more "modular" as well but not to the extent of being able to dynamically set a table name (since it's assumed you already know what the table is going to be).
If you want all rows from PERSON table, here is what you should do:
String select = "SELECT * FROM person";
PreparedStatement ps = connection.prepareStatement(select);
Variable binding does not dynamically bind table names as others mentioned above.
If you have the table name coming in to your method as a variable, you may construct the whole query as below:
String select = "SELECT * FROM " + varTableName;
PreparedStatement ps = connection.prepareStatement(select);
Parameterized queries are for querying field names - not the table name!
Prepared statements are still SQL and need to be constructed with the appropriate where clause; i.e. where x = y. One of their advantages is they are parsed by the RDMS when first seen, rather than every time they are sent, which speeds up subsequent executions of the same query with different bind values.

How do I sanitize SQL without using prepared statements

For some sql statements I can't use a prepared statment, for instance:
SELECT MAX(AGE) FROM ?
For instance when I want to vary the table. Is there a utility that sanitizes sql in Java? There is one in ruby.
Right, prepared statement query parameters can be used only where you would use a single literal value. You can't use a parameter for a table name, a column name, a list of values, or any other SQL syntax.
So you have to interpolate your application variable into the SQL string and quote the string appropriately. Do use quoting to delimit your table name identifier, and escape the quote string by doubling it:
java.sql.DatabaseMetaData md = conn.getMetaData();
String q = md.getIdentifierQuoteString();
String sql = "SELECT MAX(AGE) FROM %s%s%s";
sql = String.format(sql, q, tablename.replaceAll(q, q+q), q);
For example, if your table name is literally table"name, and your RDBMS identifier quote character is ", then sql should contain a string like:
SELECT MAX(AGE) FROM "table""name"
I also agree with #ChssPly76's comment -- it's best if your user input is actually not the literal table name, but a signifier that your code maps into a table name, which you then interpolate into the SQL query. This gives you more assurance that no SQL injection can occur.
HashMap h = new HashMap<String,String>();
/* user-friendly table name maps to actual, ugly table name */
h.put("accounts", "tbl_accounts123");
userTablename = ... /* user input */
if (h.containsKey(userTablename)) {
tablename = h.get(userTablename);
} else {
throw ... /* Exception that user input is invalid */
}
String sql = "SELECT MAX(AGE) FROM %s";
/* we know the table names are safe because we wrote them */
sql = String.format(sql, tablename);
Not possible. Best what you can do is to use String#format().
String sql = "SELECT MAX(AGE) FROM %s";
sql = String.format(sql, tablename);
Note that this doesn't avoid SQL injection risks. If the tablename is a user/client-controlled value, you'd need to sanitize it using String#replaceAll().
tablename = tablename.replaceAll("[^\\w]", "");
Hope this helps.
[Edit] I should add: do NOT use this for column values where you can use PreparedStatement for. Just continue using it the usual way for any column values.
[Edit2] Best would be to not let the user/client be able to enter the tablename the way it want, but better present a dropdown containing all valid tablenames (which you can obtain by DatabaseMetaData#getCatalogs()) in the UI so that the user/client can select it. Don't forget to check in the server side if the selection is valid because one could spoof the request parameters.
In this case you could validate the table name against the list of available tables, by getting the table listing from the DatabaseMetaData. In reality it would probably just be easier to use a regex to strip spaces, perhaps also some sql reserved words, ";", etc from the string prior to using something liek String.format to build your complete sql statement.
The reason you can't use preparedStatement is because it is probably encasing the table name in ''s and escaping it like a string.

Categories

Resources