one Field for 2 parameters in JAVA - java

public ArrayList searchCustomer(String cid) throws SQLException {
ArrayList searchCustList = new ArrayList();
PreparedStatement pStmt = connection.prepareStatement("select * from customer where (custID = ? OR firstName LIKE ?)");
pStmt.setString(1, cid);
pStmt.setString(2, "%" + cid + "%");
please explain last command i used one text field for search customer by name or ID can any body explain last line

Your question is unclear, but if you want to understand :pStmt.setString(2, "%" + cid + "%");
Then it set the second parameter in sql query to the value of cid variable, and add % around
Adding % around, mean in an SQL Like 'any character', so having %cid% mean anything containing cid in it.
As the actual query use cid for either custId or firstName, it mean that it look for user having a specific id, or having in its firstname the id.
Which is strange, and looks like more a bug, than a logical query, but maybe it come from old legacy having some id in firstname, who knows

Related

JDBC with Oracle DB - Using parameter marker with like condition [duplicate]

This question already has answers here:
Invalid column index using PreparedStatement
(2 answers)
Closed 4 years ago.
In JDBC with Oracle DB, I want to retrieve Employees whose first name starts with a specific letter. How can I use parameter marker "?" in a like condition? setXXX() method doesn't see it when i place it in a single quotation.
ex:
PreparedStatement ps = null;
String sql="SELECT FIRST_NAME, LAST_NAME, SALARY FROM EMPLOYEES WHERE FIRST_NAME like '?%'";
ps = conn.prepareStatement(sql);
ps.setString(1, firstName);
Concatenate the bound value with the wildcard:
"SELECT FIRST_NAME, LAST_NAME, SALARY FROM EMPLOYEES WHERE FIRST_NAME like ? || '%'"
You could also check the first letter explicitly with something like:
"SELECT FIRST_NAME, LAST_NAME, SALARY FROM EMPLOYEES WHERE SUBSTR(FIRST_NAME, 1, 1) = ?"
but that may be less efficient (unless you add a function-based index).
The normal route is to set the % in the 'setX' call, so your query becomes SELECT FIRST_NAME, LAST_NAME, SALARY FROM EMPLOYEES WHERE FIRST_NAME like ? and then .setString(1, firstChar + "%");
The alternative is as #Alex Poole answered: use WHERE FIRST_NAME like (? || '%')
NB: This answer was edited: The query included an erroneous % at the end; it has been removed.

sql-injection finding when using a prepared-statement in java [duplicate]

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

PreparedStatement "is null" in Where clause without if conditional (dynamic query) or gibberish values

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();

resultSet obtained if parameter containing whitespace concatenated, but not using setString

I have this piece of code, with a prepared statement. I know the query is redundant. the parameter id is a string <space>413530 (" 413530"). Please note the preceding whitespace character.
String query = "SELECT RSCode as id FROM Customer WHERE RSCode=?";
PreparedStatement newPrepStatement = connection
.prepareStatement(query);
newPrepStatement.setString(1, id);
resultSet1 = newPrepStatement.executeQuery();
while (resultSet1.next()) {
System.out.println("Got a result set.");
logindata.add(resultSet1.getString("id"));
}
I do not get any results after executing this query.
Now, if I use the same statements and append the parameter as part of the string as follows:
String query = "SELECT RSCode as id FROM Customer WHERE RSCode=" + id;
PreparedStatement newPrepStatement = connection
.prepareStatement(query);
resultSet1 = newPrepStatement.executeQuery();
while (resultSet1.next()) {
System.out.println("Got a result set.");
logindata.add(resultSet1.getString("id"));
}
I get a result as after executing this prepared statement. Same also works with a java.sql.statement
I wish to know why the driver ignores the whitespace in the second piece of code, but has a problem in the first part.
If you use setString the parameter will be bound as a string resulting in this SQL (considering the bound parameter an SQL string):
SELECT RSCode as id FROM Customer WHERE RSCode=' 0123';
If you use concatenation the SQL used will be (considering the concatenated value as an integer, since space will be ignored as part of the SQL syntax):
SELECT RSCode as id FROM Customer WHERE RSCode=<space>0123;
In this case I would advise to convert it to int or long or whatever it is and bind it with the right type. With setInt() or setLong().
And if you field is a string you could normalize it first using for example:
String normalizedValue = String.trim(value);
newPrepStatement.setString(1, normalizedValue);
or even direct in SQL like:
SELECT RSCode as id FROM Customer WHERE RSCode=TRIM(?);
In scenario - 1, the query will look like this
"SELECT RSCode as id FROM Customer WHERE RSCode=' 413530'"
In scenario - 2, the query will look like this
"SELECT RSCode as id FROM Customer WHERE RSCode= 413530"

How to select a column in with a CLOB datatype

I have a table on my jsp page that will have a column populated by a database column with type CLOB. I am running into some trouble doing this, and have seen other questions asked about this, but the answers have not worked for me. Here is my statement where comments is a CLOB.
stmt = conn.prepareStatement("SELECT DISTINCT restriction, person, start_date, end_date, comments "
+ " FROM restrictions WHERE person = ? "
+ " AND (start_date BETWEEN TO_DATE (? , 'yyyy/mm/dd') AND TO_DATE (? , 'yyyy/mm/dd') "
+ " OR start_date < TO_DATE (? , 'yyyy/mm/dd') AND end_date IS NULL) " );
stmt.setString(1, Id);
stmt.setString(2, StartRest);
stmt.setString(3, EndRest);
stmt.setString(4, EndRest);
result = stmt.executeQuery();
And then I will have the columns in a while loop:
while (result.next()) {
restrictions = StringUtils.defaultString(result.getString("str_restriction"));
.......
// here is where I would get my Clob data from the query.
So, basically, I was wondering if there is a way to translate the CLOB in the query, or even in the java code, so it would be usable in my page.
The problem comes from the distint clause of the query, which can't be applied to a CLOB.
Check if the distinct keyword is really needed. Or maybe you could rewrite your query as
select restriction, person, start_date, end_date, comments from restrictions
where id in (select distinct id from restrictions where <original where clause>)
PS: next time, include the error message and your database in the question. I've been able to find the problem with a simple google search on "ora-00932 clob".

Categories

Resources