keep column name variable in Java INSERT INTO command with PreparedStatement? - java

I have the following problem:
I have two tables in one data base which consist of the same columns besides the name of the last column. I want to write data into them using Java.
I want to use the same preparedStatement for both tables, where I check with an if-command whether it is table1 or table2. table2 has amount10 as the name for the last column, table1 has amount20 for it. This number is stored in a variable within my code.
Below you can see a (simplified) example and how I tried to let the column name variable but it doesn't work. Is there any way to fix this without copying the whole statement and manually changing the number variable?
String insertData = "INSERT INTO `database`.`"+table+"`
(`person_id`,`Date`,`amount`+"number") VALUES "+
"(?,?,?) ON DUPLICATE KEY UPDATE " +
"`person_id` = ? , " +
"`Date` = ? , " +
"`amount`+"number" = ? ; ";
PreparedStatement insertDataStmt;

This will not work since variables number and table are not going to be magically injected into your insertData string while you are changing them.
I'd to a method prepareInsertstatement(String table, String number) that would return correct PreparedStatement:
public void prepareInsertStatement(Connection conn, Strint table, String number) {
String insertData = "INSERT INTO `database`.`"+table+"`
(`person_id`,`Date`,`amount+"number"') VALUES "+
"(?,?,?) ON DUPLICATE KEY UPDATE " +
"`person_id` = ? , " +
"`Date` = ? , " +
"`amount+"number"' = ? ; ";
PreparedStatement insertDataStmt = conn.prepareStatement(insertData);
return insertDataStmt;
}
Just remember to close the PreparesStatement when you don't need it any more.

I suppose that reason for that is invalid syntax. When you concatenate string for last column name you use code 'amount' + number. If your number value is 20, than concat result will be
'amount'20 that cause invalid syntax exception. Just move one extra ' after number.
"'amount" + number + "'"
Note: log, or just error that appears during this statement execution would be very useful to find right answer for your question.

Related

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

error: The column index is out of range: 1, number of columns: 0

I'm trying to solve the problem of doing an insert into a Postgresql table
I looked at this similar question but it did not solve my problem
ERROR : The column index is out of range: 1, number of columns: 0
here is the part of code getting the error:
String query = "INSERT INTO reviews (nbstar, body, author, product_id) VALUES($1,$2,$3,$4)";
PreparedStatement prepareStatement = connection.prepareStatement(query);
prepareStatement.setInt(1, nbStar);
prepareStatement.setString(2, body);
prepareStatement.setString(3, author);
prepareStatement.setInt(4, productId);
boolean executed = prepareStatement.execute();
i tried several times to change the index number but still the same error
and here is the schema of the table:
table schema
can anyone give me an advice ?
thanks.
In the sql query, you want to insert the values for 5 fields (id, nbstar, body, author, product_id) but there are only 4 values VALUES($1,$2,$3,$4).
Update following your edited question, just modify your query as follows:
VALUES($1,$2,$3,$4)
to
VALUES(?,?,?,?)
My problem was that the question mark had single quotes around it and I copy pasted the query from straight sql so I just replaced the string with a ?. For example
Select * from datatable where id = '?'
and I had to change it to
Select * from datatable where id = ?
For me, I had added a comment which included a question mark.
Silly me!
I got that same error because I had the last \n missing in following query, I hope this helps somebody.
" order by mp.id desc\n" +
" limit 1\n" +
" ) as mge_mp\n" +
" )\n" +
"\n" +
"-- #pageable\n",
My last line was
"-- #pageable",
Incase you get this error, for my own issue, I was passing a field ending with a character next to a variable e.g.:
select * from my_schema."my_table" mt
where
mt.field_2 = variable_2
and mt.field_1 = 'variable_1'
[[and cast(mt.date as DATE) = {{date_picked}}]]
-> This failed with an error of:
The column index is out of range: 1, number of columns: 0
Changed to:
select * from my_schema."my_table" mt
where
mt.field_1 = 'variable_1'
and mt.field_2 = variable_2
[[and cast(mt.date as DATE) = {{date_picked}}]]
Please note the variable ending with a quote character has been moved.
-> This worked for me.
For me the issue was having a semicolon at the end of the query string. Something like:
String query = "INSERT INTO reviews (nbstar, body, author, product_id) VALUES(?,?,?,?);";
Notice the ; appended to the end of the query string. The fix is, well, to remove it:
String query = "INSERT INTO reviews (nbstar, body, author, product_id) VALUES(?,?,?,?)";

colown count does not match value count at row 1

i am a beginner in java Desktop application, and i was trying to insert some data into my table and i get the following reply "colown count does not match value count at row 1" here is my query
String sql = "insert into cataloguetb(title_statement,aurthurs_name,edition_statement,book_title,publisher_name"
+ "place_of_publication,year_of_publication,isbn_no,index_no,pagenRomannuem,pagneArabi,illuss,size_of_book"
+ "otherAurthurs,addEntries,length_in_cm,accessionNO,call_No1,call_No2,call_No,call_No4)values ('"+(titleStatement)+"','"+(aurthursName)+"'"
+ "'"+(editionStatement)+"','"+(bookTitle)+"','"+(publisherName)+"','"+(placeOfPublication)+"','"+(yearOfPublication)+"'"
+ "'"+(isbnNo)+"','"+(indexNo)+"','"+(pageRoman)+"','"+(pageArabic)+"','"+(illustration)+"','"+(size)+"','"+(otherAuthurs)+"'"
+ "'"+(addedEntries)+"','"+(lengthOfBook)+"','"+(accessionNo)+"','"+(calNo1)+"','"+(calNo2)+"','"+(calNo3)+"','"+(calNo4)+"')";
i have tried so many solutions even from stacflowoverflow there seem to no solution thanks for your help.
You need trailing commas on the fields at the end of the rows. Try this:
String sql = "insert into cataloguetb(title_statement,aurthurs_name,edition_statement,book_title,publisher_name,"
+ "place_of_publication,year_of_publication,isbn_no,index_no,pagenRomannuem,pagneArabi,illuss,size_of_book,"
+ "otherAurthurs,addEntries,length_in_cm,accessionNO,call_No1,call_No2,call_No,call_No4) values ('"+(titleStatement)+ "','"+(aurthursName)+"',"
+ "'"+(editionStatement)+"','"+(bookTitle)+"','"+(publisherName)+"','"+(placeOfPublication)+"','"+(yearOfPublication)+"',"
+ "'"+(isbnNo)+"','"+(indexNo)+"','"+(pageRoman)+"','"+(pageArabic)+"','"+(illustration)+"','"+(size)+"','"+(otherAuthurs)+"',"
+ "'"+(addedEntries)+"','"+(lengthOfBook)+"','"+(accessionNo)+"','"+(calNo1)+"','"+(calNo2)+"','"+(calNo3)+"','"+(calNo4)+"')";
This is because number of columns that you want to enter and datas(values for columns) you are entering are not same
the output of your code will be something like this
insert into cataloguetb(title_statement,aurthurs_name,edition_statement,book_title,publisher_nameplace_of_publication,year_of_publication,isbn_no,index_no,pagenRomannuem,pagneArabi,illuss,size_of_bookotherAurthurs,addEntries,length_in_cm,accessionNO,call_No1,call_No2,call_No,call_No4)values (.......)
That means you trying to insert in 19 columns but giving 21 values.So the error.
Well it is not the proper way of insertion.
Better would be to use PreparedStatement like this way
PreparedStatement pt=con.prepareStatement("insert into table (x,y) values(?,?");
pt.setString(1,value_for_x);
pt.setString(2,value_for_y);
pt.executeUpdate();

How to get meta data information of a sql query

I am using postgres 9.1 and java code for jdbc.
I may use a order by clause in my sql query string
I just want to get the meta data information of the query to find whether the query has order by clause or not. If it has then how many fields has been specified in the order by clause.
Ex:
order by age
order by age, name
order by age asc, name desc
In these example I just want to retrieve the number of parameters that are specified in the order by clause and their column names.
If your are getting your query as string you could simply parse it.
i.e. To figure out that ORDER BY is there
"SELECT * FROM MyTable ORDER BY SomeColumn".toLowerCase().indexOf("order by") // if it's return -1 query does not contains order by section otherwise it returns start index for first occurence "ORDER BY" in given string
For more complex searching in string you may need to use RegExp
You can do it by breaking an SQL query into part and then reassigning.
Like
String sql="SELECT NAME,COMPANY,FNAME,AGE FROM COMP_DATA JOIN PERSONAL_DATA WHERE (1=1) AND FNAME='Vaibs' ORDER BY AGE";
While writing in JAVA do as below.
Break Whole query into String parts and recombine it like this.
String strSQL = "SELECT " + "NAME"+",COMPANY"+",FNAME"+",AGE" + "FROM "
+ getTableName1(); //getTableName1() return tablename
strSQL+="JOIN "+ getTable2()+"";//getTable2() return tablename as well
String strWhere = " WHERE (1=1) " + " and FNAME='" + fname+ "';
String orderBySQL = " Order by " + i_will_return_string_to_order_by();
//return AGE in our case
String FinalString= strSQL +strWhere +orderBySQL ;
SOP order by to get what you want.
Hope that helped.

Using one query in another in JDBC programming

I understand how to do this on paper in SQL, but am having trouble implementing this in Java (this is the first time I am actually programming JDBC stuff)
For example, say my database consists of:
movie(code, title, publisher)
customer(custno, name)
borrowed(custno, code)
And I want to find the name of customers who borrowed every movie by pubisher ABC
string no_of_ABC_movies = "SELECT COUNT(publisher), publisher FROM movie, WHERE movie.publisher = 'ABC'";
string no_of_cust_ABC_movies = "SELECT COUNT(name), name FROM customer, borrowed, movie, WHERE customer.custno = borrowed.custno AND borrowed.code = movie.code AND movie.publisher = 'ABC'";
String query = "SELECT name" +
" name FROM customer, borrowed, movie" +
" WHERE customer.custno = borrowed.custno AND" +
" borrowed.code = movie.code AND" +
" movie.publisher = 'ABC' AND" + " "
no_of_cust_ABC_movies + " = " + no_of_ABC_movies;
This isn't the exact database I am working with, but query will work and print out the names of people who borrowed movies from ABC without the last line, but says I have an error in SQL syntax with the last line so I guess I don't know how to use one query within another.
It depends on your DBMS, but every SQL variant I've seen requires parens around subqueries.
Try something like:
...
" movie.publisher = 'ABC' AND ("
no_of_cust_ABC_movies + ") = (" + no_of_ABC_movies + ")";
You have problem with double name field without being separated by a comma in your query.
If your code is exactly as listed above, you have compilation error just above the last line-missing + to concatenate strings.
If that's a typo below is my suggestion.
Remove duplicate select (use only one name) or
Separate names by a comma ( I don't see a point of selecting name twice though)
And your last line is wrong.. you can not compare two select queries that way.. Just add the required where clauses.
(You should read database joins first, and then solve your problem)
I like to get my queries working in the query browser or workbench, then copy them over to Java. It keeps it to one new thing at a time...
You're query actually starts with
SELECT name name FROM customer ...
The name column is duplicated - maybe that the problem.

Categories

Resources