Java dynamically generate SQL query - ATHENA - java

I am trying to generate sql query based on user input. There are 4 search fields on the UI:
FIRST_NAME, LAST_NAME, SUBJECT, MARKS
Based on user input I am planning to generate SQL query. Input can be of any combination.
eg: select * from TABLE where FIRST_NAME="some_value";
This query needs to be generated when FIRST_NAME is given and other fields are null
select * from TABLE where FIRST_NAME="some_value" and LAST_NAME="some_value";
This query needs to be generated when FIRST_NAME and LAST_NAME are given and other fields are null
Since there are 4 input fields, number of possible queries that can be generated are 24 (factorial of 4).
One idea is to write if condition for all 24 cases.
Java pseudo code:
String QUERY = "select * from TABLE where ";
if (FIRST_NAME!=null) {
QUERY = QUERY + "FIRST_NAME='use_input_value';"
}
if (LAST_NAME!=null) {
QUERY = QUERY + "LAST_NAME='use_input_value';"
}
if (SUBJECT!=null) {
QUERY = QUERY + "SUBJECT='use_input_value';"
}
if (MARKS!=null) {
QUERY = QUERY + "MARKS='use_input_value';"
}
I am not able to figure out how to generate SQL queries with AND coditions for multiple Input values.
I have been through concepts on dynamically generate sql query but couldn't process further.
Can someone help me on this.
FYI: I have been through How to dynamically generate SQL query based on user's selections?, still not able to generate query string based on user input.

Let's think about what would happen if you just ran the code you wrote and both FIRST_NAME and LAST_NAME are provided. You'll wind up with this:
select * from TABLE where FIRST_NAME='use_input_value';LAST_NAME='use_input_value';
There are two problems here:
The query is syntactically incorrect.
It contains the literals 'use_input_value' instead of the values you want.
To fix the first problem, let's first add and to the start of each expression, and remove the semicolons, something like this:
String QUERY = "select * from TABLE where";
if (FIRST_NAME!=null) {
QUERY = QUERY + " and FIRST_NAME='use_input_value'";
}
Notice the space before the and. We can also remove the space after where.
Now the query with both FIRST_NAME and LAST_NAME will look like this:
select * from TABLE where and FIRST_NAME='use_input_value' and LAST_NAME='use_input_value'
Better but now there's an extra and. We can fix that by adding a dummy always-true condition at the start of the query:
String QUERY = "select * from TABLE where 1=1";
Then we append a semicolon after all the conditions have been evaluated, and we have a valid query:
select * from TABLE where 1=1 and FIRST_NAME='use_input_value' and LAST_NAME='use_input_value';
(It may not be necessary to append the semicolon. Most databases don't require semicolons at the end of a single query like this.)
On to the string literals. You should add a placeholder instead, and simultaneously add the value you want to use to a List.
String QUERY = "select * from TABLE where";
List<String> args = new ArrayList<>();
if (FIRST_NAME!=null) {
QUERY = QUERY + " and FIRST_NAME=?";
args.add(FIRST_NAME);
}
After you've handled all the conditions you'll have a string with N '?' placeholders and a List with N values. At that point just prepare a query from the SQL string and add the placeholders.
PreparedStatement statement = conn.prepareStatement(QUERY);
for (int i = 0; i < args.size(); i++) {
statement.setString(i + 1, args[i]);
}
For some reason columns and parameters are indexed starting at 1 in the JDBC API, so we have to add 1 to i to produce the parameter index.
Then execute the PreparedStatement.

Related

How to prevent SQL injection when the statement has a dynamic table name?

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

error in jdbc code -> com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '#gmail.com' at line 1
I don't know where is the problem
public List<UserModel> listUser(String emailParam) throws SQLException {
List<UserModel> users = new ArrayList<>();
Connection conn = null;
PreparedStatement pre = null;
ResultSet rs = null;
// Get Connection
conn = dataSource.getConnection();
// fetch query
String fetchUser = "SELECT * FROM user WHERE email = " + emailParam;
pre = conn.prepareStatement(fetchUser);
// execute query
rs = pre.executeQuery();
// fetch data using resultSet interface;
while (rs.next()) {
Integer id = rs.getInt("id");
String firstName = rs.getString("firstName");
...
String email = rs.getString("email");
Boolean isActive = rs.getBoolean("isActive");
Boolean isLibrarian = rs.getBoolean("isLibrarian");
// insert into user constructor
UserModel theUser = new UserModel(id, firstName, lastName, gender,
department, idNo, contactNo, address, email, null,
isLibrarian, isActive);
// insert into ArrayList
users.add(theUser);
}
// close connection
close(conn, pre, rs);
return users;
}
where is the problem Thanks in advance.
The error is here, in listUser():
// fetch query
String fetchUser = "SELECT * FROM user WHERE email = " + emailParam;
pre = conn.prepareStatement(fetchUser);
You managed to use a prepared statement when inserting the user, and you need to do the same here when querying:
// fetch query
String fetchUser = "SELECT * FROM user WHERE email = ?";
pre = conn.prepareStatement(fetchUser);
pre.setString(1, emailParam);
As a general thought, preparing statements has two main usages:
1) Minimise preparation time when executing a query
2) For security - among other things "query rewriting"
I also have an observation about a syntax error in your select at the bottom of this post.
If you are going to prepare statements, then it is better to do it once, then "remember" the preparedStatement that you get back. Do not prepare the same query over and over.
Most, if not all, DBMS's work as follows for prepared query processing:
1) you send the template query to the DBMS for parsing and optimisation. The output of this is known by a few different names, but for the purposes of this we can call this the "executable plan". This is the PrepareXXX call.
2) The DBMS remembers all of those details for the second stage i.e. when you send the data up as a result of the prepdQuery.executeQuery() (or similar) call. this has the effect of sending up the data and plugging it into the prepared query's executable plan.
This will always involve two network trips (one to prepare and one to execute). However ....
... If you need to run the same query again with different data (e.g. a different email), just execute the second step - this bypasses the overheads associated with parsing and planning. Which will increase your throughput significantly - especially for single row operations such as the insert (and most likely the select) shown above.
The alternative is the string concatenation method which will always require parsing etc and execution - but at least it will be only one trip over the network. This works best for longer running queries (where parse time is insignificant compared to execution times) or where the query logic is dynamic (made up at run time based upon user input).
However, if you do send the query text concatenated with user input, make sure you address point 2 above (query rewriting).
Also, finally, your concatenated SQL is missing single quotes.
The query must look like this (the text must be quoted)
select ... from ... where email = 'email#domain.com';
Thus your concatenation must look like this:
String fetchUser = "SELECT * FROM user WHERE email = '" + emailParam + "'";
What is query rewriting? Imaging if the emailParam entered by the user looked like this:
emailParam = "'; delete from user all; select 'hello"
Try plugging that into your select BUT DO NOT RUN IT unless you have a backup copy of your users table (or you get lucky).
Also, note that you never put quote marks around the ? placeholders in prepared queries - even if the parameter is a text or date value.

Couchbase:(Java) Parameterized query on ORDER BY clause not working

I have a Parameterized query that goes
String stmt = "SELECT * FROM bucket ... ORDER BY $sortCategory DESC";
Then I go:
ParameterizedQuery query = ParameterizedQuery.parameterized(stmt, JsonObject.create().put("sortCategory", "dateUploaded"));
It's not sorting properly. I even printed out query.statementParameters() and it's printing my parameters properly. It only worked when I did a hardcode ("ORDER BY dateUploaded DESC"). Not sure why this is the case.
Why isn't this working?
The problem happens because the query gets translated into something like this:
SELECT * FROM bucket ... ORDER BY 'date' DESC;
Which is probably not making reference to the date column but to the 'date' value.
You can try using an index representing the column position instead of specifying the column name.
String stmt = "SELECT date, column2, column3 FROM bucket ... ORDER BY $sortCategory DESC";
ParameterizedQuery query = ParameterizedQuery.parameterized(stmt, JsonObject.create().put("sortCategory", 1));

List of columns in sql query

I have a query using various joins, and I just need the list of columns which are returned by this query. I done it in java, by asking only one row with rownum=1 and getting column name for value.The problem is if there is no data returned by that query.
For ex.
select * from something
and if there is any data returning by this query then it will return col1,col2,col3.
But if there is no data returned by this query, then it will throw error.
What I need is
Is there any way that I can run
desc (select * from something)
or similar to get list of columns returned by query.
It can be in sql or JAVA. Both methods are acceptable.
In my application, user passes the query, and I can add wrapper to the query but I cant modify it totally.
The flow of application is
query from user -> execute by java and get one row -> return list of columns in the result set.
you can use ResultSetMetaData of resultset
for example :
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM TABLE2");
ResultSetMetaData rsmd = rs.getMetaData();
int countOfColumns = rsmd.getColumnCount();
for(int i = 1; i <= countOfColumns ; i++ )
System.out.println(rsmd.getColumnName(i));
you could maybe convert your query to a view, you can then see the columns in the view by querying user_tab_columns
select * from user_tab_columns
The Oracle equivalent for information_schema.COLUMNS is USER_TAB_COLS for tables owned by the current user, ALL_TAB_COLS or DBA_TAB_COLS for tables owned by all users.
Tablespace is not equivalent to a schema, neither do you have to provide the tablespace name.
Providing the schema/username would be of use if you want to query ALL_TAB_COLS or DBA_TAB_COLS for columns OF tables owned by a specific user. in your case, I'd imagine the query would look something like:
String sqlStr= "
SELECT column_name
FROM all_tab_cols
WHERE table_name = 'users'
AND owner = ' || +_db+ || '
AND column_name NOT IN ( 'password', 'version', 'id' )
"
Note that with this approach, you risk SQL injection.

Is it possible to use "WHERE" clause to select all records in SQL Statement?

Good Evening, I am curious if it is possible to make a WHERE-clause in a SQL statement which can show all records?
Below some explanation:
Random SQL Statement (Java)-(JSP example), Normal Situation
String SqlStatement = "SELECT * FROM table_example WHERE First_Col = '<%=passVar%>' ";
db.query(SqlStatement );
//........
//........
What if the passVar is 'ALL', and we need to prompt all the records out when passVar = All? I know I can do it with if-else and check if the passVar is "ALL" then query the without-WHERE statement to make it work..
**without-WHERE statement (Java)-(JSP example)**
if(<%=passVar%> == "ALL") {
SqlStatement = "SELECT * FROM table_example";
} else {
SqlStatement = "SELECT * FROM table_example WHERE First_Col = '<%=passVar%>' ";
}
but can I just code one SQL statement to make all the records prompt? Something like below:
(Java)-(JSP example)
String ShowAll = "";
if(<%=passVar%> == "ALL") {
ShowAll = *;
} else {
ShowAll = <%=passVar%>;
}
SqlStatement = "SELECT * FROM table_example WHERE First_Col = ShowAll ";
Try with WHERE 1=1::
Select * from myTable WHERE 1=1
This also works:
WHERE columnname LIKE '%'
Except for NULL values.
where 1=1 worked for me, Although where clause was being used all records were selected.
You can also try
SELECT * FROM Customers
WHERE CustomerID=CustomerID; /* query */
or
[any_column_name]=[column_name_in_LHL]
(LHL=left hand side.)
copy the query and
click here to try code
It would be better to differ the 2 situations and make 2 queries out of it.
If there is no where condition then the DB does not need to evaluate it (potencially faster)
The source code/debugging output is clearer.
Consider moving the special case inside the query itself, e.g.
SELECT * FROM table_example WHERE '<%=passVar%>' IN ('ALL', First_Col)
Try with wildcard value '%' but I would recommend to use a Factory here to create the SQL statement, what you are trying to do smells a bit.
Something else you could do, is making that combination of code and SQL a single query. Which means the IF..ELSE will be in SQL language.
Check these links for some more info:
MySQL
Using If else in SQL Select statement
On sqlserver you can make proc:
create proc select_all_on_null
#a int
as
begin
select * from Records where (#a is null or Record_id=#a )
end
When you select be your program:
make #a in null will select all
if i is numder there will select row with this id

Categories