Oracle & java dynamic 'Order by' clause - java

I am trying to build a dynamic sql query in java (shown below)
sqlStr = "Select * " +
"from " + tableName
if(tableName!=null){
if(tableName.equals("Table1"){
sqlStr = sqlStr.concat("order by city desc");
}else if(tableName.equals("Table2"){
sqlStr = sqlStr.concat("order by country desc");
}else if(tableName.equals("Table3"){
sqlStr = sqlStr.concat("order by price desc");
}
}
Now what i would like to do is to add a final 'else' statement which would order the query based on whether the table contains a column named 'custID'. There will be several tables with that column so i want to sort the ones that have that column by custID. (Rather than having hundreds of additional if statements for each table that does have that column name.) Is this possible? i have seen people using the 'decode' function but i cant work out how to use it here.

Use DatabaseMetaData to get the table information.
You can use the getTablexxx() and getColumnxx() methods to get the table information.
Connection conn = DriverManager.getConnection(.....);
DatabaseMetaData dbmd = conn.getMetaData();
dbmd.getxxxx();
Note: you forgot space in your code before ORDER BY clause.

If you are happy with hardcoding things, a way to avoid multiple conditionals would be to store a list of all the tables that include custID.
private final static String tablesWithCustID = "/TableX/TableY/TableZ/";
...
if (tablesWithCustID.contains( tableName )) {
sqlStr = sqlStr.concat("order by custID")
}
You could use a List instead of a simple delimited string if you like.
Perhaps better, you could store a map with table names as the key, and the sort string as the value. Load it up once, then you don't need any conditional at all.

The most straight-forward way to do it is to read the column definitions from USER_TAB_COLUMNS or ALL_TAB_COLUMNS and check for the existence of a custID column. Without crazy PL/SQL tricks, you won't be able to solve this in SQL alone.
BTW, there is a " " missing between tableName and the order by clauses.

I understand that you're looking for a solution that can do this in one query, i.e. without running a separate metadata query beforehand.
Unfortunately, this won't be possible. The decode function can do some dynamic things with column values, but not with column name. And you're looking for a solution dynamically derive the column name.
An alternative might be to just add ORDER BY 1, 2. This is an old syntax that means order by the first and than by the second column. It might be a good solution if the custID column is the first column. Otherwise it at least gives you some sorting.

How about ArrayList.contains()?
You can create a list of tables which have that column, and just check for tables.contains(tablename) in the final if condition.

Related

preparestatement with name [duplicate]

I was wondering if there was any way to specify returned column names using prepared statements.
I am using MySQL and Java.
When I try it:
String columnNames="d,e,f"; //Actually from the user...
String name = "some_table"; //From user...
String query = "SELECT a,b,c,? FROM " + name + " WHERE d=?";//...
stmt = conn.prepareStatement(query);
stmt.setString(1, columnNames);
stmt.setString(2, "x");
I get this type of statement (printing right before execution).
SELECT a,b,c,'d,e,f' FROM some_table WHERE d='x'
I would, however, like to see:
SELECT a,b,c,d,e,f FROM some_table WHERE d='x'
I know that I cannot do this for table names, as discussed
here, but was wondering if there was some way to do it for column names.
If there is not, then I will just have to try and make sure that I sanitize the input so it doesn't lead to SQL injection vulnerabilities.
This indicates a bad DB design. The user shouldn't need to know about the column names. Create a real DB column which holds those "column names" and store the data along it instead.
And any way, no, you cannot set column names as PreparedStatement values. You can only set column values as PreparedStatement values
If you'd like to continue in this direction, you need to sanitize the column names (to avoid SQL Injection) and concatenate/build the SQL string yourself. Quote the separate column names and use String#replace() to escape the same quote inside the column name.
Prepare a whitelist of allowed column names. Use the 'query' to look up in the whitelist to see if the column name is there. If not, reject the query.
For MySQL prepared statements with NodeJS (mysqljs/mysql), what you need to know is that ? is for values, but if you need to escape column names, table names etc, use ?? instead.
Something like this will work:
SELECT ??, ??, ?? FROM ?? WHERE ?? < ?
Set values to ['id', 'name', 'address', 'user', 'id', 100]
I think this case can't work because the whole point of the prepared statement is to prevent the user from putting in unescaped query bits - so you're always going to have the text quoted or escaped.
You'll need to sanitize this input in Java if you want to affect the query structure safely.
Use sql injection disadvantage of Statement Interface as advantage.
Ex:
st=conn.createStatement();
String columnName="name";
rs=st.executeQuery("select "+ columnName+" from ad_org ");
public void MethodName(String strFieldName1, String strFieldName2, String strTableName)
{
//Code to connect with database
String strSQLQuery=String.format("select %s, %s from %s", strFieldName, strFieldName2, strTableName);
st=conn.createStatement();
rs=st.executeQuery(strSQLQuery);
//rest code
}
Below is the solution in java.
String strSelectString = String.format("select %s, %s from %s", strFieldName, strFieldName2, strTableName);

Dynamic column name using prepared statement + sql query with variable containing 's

My query
attributes.replace(" ' ", "");
//also used SET "+attributes+"
String sql;
sql = "UPDATE diseaseinfo"
+ " SET ?=?"
+ "WHERE companyname = 'mycom' && diseaseName =?";
PreparedStatement preparedStmt = connects.prepareStatement(sql);
preparedStmt.setString(1, attributes);
preparedStmt.setString(2, attrData);
preparedStmt.setString(3, medname);
System.out.println(preparedStmt);
it is giving me an error because query set the column name in string so it become like this on causes
UPDATE diseaseinfo SET 'causes'='abc' WHERE companyname = 'mycom' and diseaseName ='fever'
and through this question I get to know that I can't add dynamic column by prepared statement: https://stackoverflow.com/a/3136049/7794329
Now, the real question comes up: suppose if I will use a simple update query like in this question: jdbc dymanic sql query with variable containg 's
It says you can't enter value with 's in your simple sql query because it will again make the query syntactical error for example :
SELECT * FROM diseaseinfo WHERE diseaseName = 'Adult Still's disease' AND name = 'add';
Here it wont execute because of ' 's on 'Adult Still's
Then it won't work with simple query. What should I do now? What to use? To set dynamic column with taking care of 's in the query.
I am not worried about SQL injection because i am working on local. And I just want my query to be executed.
Right. We can't supply identifiers as bind parameters. The name of the column has to be part of the SQL text.
We can dynamically incorporate the name of the column into the SQL text with something like this:
sql = "UPDATE diseaseinfo"
+ " SET `" + colname + "` = ?"
+ " WHERE companyname = 'mycom' AND diseaseName = ?";
And supply values for the two remaining bind parameters
preparedStmt.setString(1, attrData);
preparedStmt.setString(2, medname);
And you are absolutely correct about being concerned about SQL Injection.
Supplied as bind values, single quotes in the values of attrData and medname won't be an issue, in terms of SQL Injection.
But the example I've provided is vulnerable through incorporating the colname variable into the SQL text, if we don't have some guaranteed that colname is "safe" to include in the statement.
So we need to make the assignment of a value to colname "safe".
Several approaches we can use do that. The most secure would be a "whitelist" approach. The code can ensure that only specific allowed "safe" values get assigned to colname, before colname gets included into the SQL text.
As a simple example:
String colname;
if (attributes.equals("someexpectedvalue") {
colname = "columnname_to_be_used";
} else if (attributes.equals("someothervalid") {
colname = "valid_columname";
} else {
// unexpected/unsupported attributes value so
// handle condition or throw an exception
}
A more flexible approach is to ensure that a backtick character doesn't appear in colname. In the example, the value of colname is being escaped by enclosing it in backticks. So, as long as a backtick character doesn't appear in colname, we will prevent a supplied value from being interpreted as anything other than as an identifier.
For a more generic (and complicated) approach to using hardcoded backtick characters, we could consider making use the supportsQuotedIdentifiers and getIdentifierQuoteString methods of java.sql.DatabaseMetaData class.
(In the OP code, we don't see the datatype of contents of attributes. We see a call to a method named replace, and the arguments that are supplied to that. Assuming that attributes is a String, and that's supposed to be a column name, it's not at all clear why we would have "space single quote space" in the string, or why we need to remove that. Other than this mention, this answer doesn't address that.)

How to prevent SQL injection when dealing with dynamic table/column names?

I am using jdbc PreparedStatement for data insertion.
Statement stmt = conn.prepareStatement(
"INESRT INTO" + tablename+ "("+columnString+") VALUES (?,?,?)");
tablename and columnString are something that is dynamically generated.
I've tried to parameterise tablename and columnString but they will just resolve to something like 'tablename' which will violate the syntax.
I've found somewhere online that suggest me to lookup the database to check for valid tablename/columnString, and cache it somewhere(a Hashset perhaps) for another query, but I'm looking for better performance/ quick hack that will solve the issue, perhaps a string validator/ regex that will do the trick.
Have anyone came across this issue and how do you solve it?
I am not a java-guy, so, only a theory.
You can either format dynamically added identifiers or white-list them.
Second option is way better. Because
most developers aren't familiar enough with identifiers to format them correctly. Say, to quote an identifier, which is offered in the first comment, won't make it protected at all.
there could be another attack vector, not entirely an injection, but similar: imagine there is a column in your table, an ordinary user isn't allowed to - say, called "admin". With dynamically built columnString using data coming from the client side, it's piece of cake to forge a privilege escalation.
Thus, to list all the possible (and allowed) variants in your code beforehand, and then to verify entered value against it, would be the best.
As of columnString - is consists of separate column names. Thus, to protect it, one have to verify each separate column name against a white list, and then assemble a final columnString from them.
Create a method that generates the sql string for you:
private static final String template = "insert into %s (%s) values (%s)";
private String buildStmt(String tblName, String ... colNames) {
StringJoiner colNamesJoiner = new StringJoiner(",");
StringJoiner paramsJoiner = new StringJoiner(",");
Arrays.stream(colNames).forEach(colName -> {
colNamesJoiner.add(colName);
paramsJoiner.add("?");
});
return String.format(template, tblName, colNamesJoiner.toString(), paramsJoiner.toString());
}
Then use it...
Statement stmt = conn.prepareStatement(buildStmt(tablename, [your column names]));
As an elaboration on #Anders' answer, don't use the input parameter as the name directly, but keep a properties file (or database table) that maps a set of allowed inputs to actual table names.
That way any invalid name will not lead to valid SQL (and can be caught before any SQL is generated) AND the actual names are never known outside the application, thus making it far harder to guess what would be valid SQL statements.
I think, the best approach is to get table and columns names from database or other non user input, and use parameters in prepared statement for the rest.
There are multiple solutions we can apply.
1) White List Input Validation
String tableName;
switch(PARAM):
case "Value1": tableName = "fooTable";
break;
case "Value2": tableName = "barTable";
break;
...
default : throw new InputValidationException("unexpected value provided for table name");
By doing this input validation on tableName, will allows only specified tables in the query, so it will prevents sql injection attack.
2) Bind your dynamic columnName(s) or tableName(s) with special characters as shown below
eg:
For Mysql : use back codes (`)
Select `columnName ` from `tableName `;
For MSSQL : Use double codes(" or [ ] )
select "columnName" from "tableName"; or
select [columnName] from [tableName];
Note: Before doing this you should sanitize your data with this special characters ( `, " , [ , ] )

java looping through multiple sql queries

I'm trying to loop through multiple sql queries that are executed. I want to first get all the question information for a certain task and then get the keywords for that question. I have three records in my Questions table, but when the while loop at the end of list.add(keyword); is done, it jumps to the SELECT Questions.Question loop (as it should) and then just jumps out and gives me only one record and not the other 2.
What am I doing wrong? Can someone maybe help me fix my code? I've thought of doing batch sql executes (maybe that is the solution), but within each while loop, I need information from the previous sql statement, so I can't just do it all at the end of the batch.
SQL Code:
String TaskTopic = eElement.getElementsByTagName("TaskTopic").item(0).getTextContent();
// perform query on database and retrieve results
String sql = "SELECT Tasks.TaskNo FROM Tasks WHERE Tasks.TaskTopic = '" + TaskTopic + "';";
System.out.println(" Performing query, sql = " + sql);
result = stmt.executeQuery(sql);
Document doc2 = x.createDoc();
Element feedback = doc2.createElement("Results");
while (result.next())
{
String TaskNo = result.getString("TaskNo");
// perform query on database and retrieve results
String sqlquery = "SELECT Questions.Question, Questions.Answer, Questions.AverageRating, Questions.AverageRating\n" +
"FROM Questions\n" +
"INNER JOIN TaskQuestions ON TaskQuestions.QuestionID = Questions.QuestionID \n" +
"INNER JOIN Tasks ON Tasks.TaskNo = '" + TaskNo + "';";
result = stmt.executeQuery(sqlquery);
while (result.next())
{
String Question = result.getString("Question");
String Answer = result.getString("Answer");
String AverageRating = result.getString("AverageRating");
String sqlID = "SELECT QuestionID FROM Questions WHERE Question = '" + Question + "';";
result = stmt.executeQuery(sqlID);
while (result.next())
{
String ID = result.getString("QuestionID");
String sqlKeywords = "SELECT Keyword FROM LinkedTo WHERE QuestionID = '" + ID + "';";
result = stmt.executeQuery(sqlKeywords);
while (result.next())
{
String keyword = result.getString("Keyword");
list.add(keyword);
}
}
feedback.appendChild(x.CreateQuestionKeyword(doc2, Question, Answer, AverageRating, list));
}
}
Why this should be done in SQL
Creating loops is exponentially less efficient than writing a sql query. Sql is built to pull back this type of data and can plan out how it is going to get this data from the database (called an execution plan).
Allowing Sql to do its job and determine the best way to pull back the data instead of explicitly determining what tables you are going to use first and then calling them one at a time is better in terms of the amount of resources you will use, how much time it will take to get the results, code readability, and maintainability in the future.
What information you are looking for
In the psuedocode you provided, you are using the Keyword, Question, Answer, and AnswerRating values. Finding these values should be the focus of the sql query. Based on the code you have written, Question, Answer, and AnswerRating are coming from the Questions table and Keyword is coming from the LinkedTo table, so both of these tables should be available to have data pulled from them.
You can note at this point that we have essentially just mapped out what the Select and From portions of your query should look like.
It also looks like you have a parameter called TaskTopic so we need to include the table Tasks to make sure the correct data is returned. Lastly, the TaskQuestions table is the link between the tasks and the questions. Now that we know what the query should look like, let's see what the results are using sql syntax.
The Code
You did not include the declaration of stmt, but I assume that it is a PreparedStatement. You can add parameters to a prepared statement. Notice the ? in the sql code? The parameters you provide will be added in place of the ?. To do this, you should use stmt.setString(1, TaskTopic);. Note that if there were more than one parameter, you would need to add them in the order that they exists in the sql query (using 1, 2, ...)
SELECT l.Keyword,
q.Question,
q.Answer,
q.AverageRating
FROM LinkedTo l Inner Join
Questions q
on l.questionID = q.QuestionID
Where exists ( Select 1
From TaskQuestions tq INNER JOIN
Tasks t
on tq.TaskNo = t.TaskNo
Where t.TaskTopic = ?
and tq.QuestionID = q.QuestionID)
This is one way that you can write the query to return the same results. There are other ways to write this to get what you are looking for.
What's Going On?
There are a few things in this query you may not be familiar with. First are table aliases. Instead of writing the table name over and over again, you can alias your tables. I used the letter q to represent the Questions table. Any time you see q. you should recognize that I am referring to a column from Questions. The q after Questions is what gives the table its alias.
Exists Instead of doing a bunch of inner joins with tables that you are not selecting information from, you can use an exists to check if what you are looking for is in those tables. You can continue to do inner joins if you need data from the tables, but if you don't, Exists is more efficient.
I suspect you had issues with the query before (and probably the one you provided) because you did not provide any information to join TaskQuestions and Tasks together. That most likely resulted in the duplicates. I joined on TaskNo but this may not be the correct column depending on how the tables are set up.

Variable column names using prepared statements

I was wondering if there was any way to specify returned column names using prepared statements.
I am using MySQL and Java.
When I try it:
String columnNames="d,e,f"; //Actually from the user...
String name = "some_table"; //From user...
String query = "SELECT a,b,c,? FROM " + name + " WHERE d=?";//...
stmt = conn.prepareStatement(query);
stmt.setString(1, columnNames);
stmt.setString(2, "x");
I get this type of statement (printing right before execution).
SELECT a,b,c,'d,e,f' FROM some_table WHERE d='x'
I would, however, like to see:
SELECT a,b,c,d,e,f FROM some_table WHERE d='x'
I know that I cannot do this for table names, as discussed
here, but was wondering if there was some way to do it for column names.
If there is not, then I will just have to try and make sure that I sanitize the input so it doesn't lead to SQL injection vulnerabilities.
This indicates a bad DB design. The user shouldn't need to know about the column names. Create a real DB column which holds those "column names" and store the data along it instead.
And any way, no, you cannot set column names as PreparedStatement values. You can only set column values as PreparedStatement values
If you'd like to continue in this direction, you need to sanitize the column names (to avoid SQL Injection) and concatenate/build the SQL string yourself. Quote the separate column names and use String#replace() to escape the same quote inside the column name.
Prepare a whitelist of allowed column names. Use the 'query' to look up in the whitelist to see if the column name is there. If not, reject the query.
For MySQL prepared statements with NodeJS (mysqljs/mysql), what you need to know is that ? is for values, but if you need to escape column names, table names etc, use ?? instead.
Something like this will work:
SELECT ??, ??, ?? FROM ?? WHERE ?? < ?
Set values to ['id', 'name', 'address', 'user', 'id', 100]
I think this case can't work because the whole point of the prepared statement is to prevent the user from putting in unescaped query bits - so you're always going to have the text quoted or escaped.
You'll need to sanitize this input in Java if you want to affect the query structure safely.
Use sql injection disadvantage of Statement Interface as advantage.
Ex:
st=conn.createStatement();
String columnName="name";
rs=st.executeQuery("select "+ columnName+" from ad_org ");
public void MethodName(String strFieldName1, String strFieldName2, String strTableName)
{
//Code to connect with database
String strSQLQuery=String.format("select %s, %s from %s", strFieldName, strFieldName2, strTableName);
st=conn.createStatement();
rs=st.executeQuery(strSQLQuery);
//rest code
}
Below is the solution in java.
String strSelectString = String.format("select %s, %s from %s", strFieldName, strFieldName2, strTableName);

Categories