Filtering results in a JPQL query - java

I am trying to make "filter" search for all questions in my database. Now I get a exception telling me that I can't compare enum values with string. Is it because I don't use the fully qualified package name of wher the enum type is declared? If so, is it better ways than hard-coding the package name?
Exception Description: Error compiling the query [SELECT q FROM
Question q WHERE q.status = 'APPROVED'], line 1, column 40: invalid
enum equal expression, cannot compare enum value of type
[app.utility.Status} with a non enum value of type
[java.lang.String].
public List<Question> all(Status status, ViewOption viewOption) {
String jpql = "SELECT q FROM Question q ";
boolean isWhereClauseAdded = false;
if (status != Status.ALL) {
if (!isWhereClauseAdded) {
jpql += "WHERE ";
}
jpql += "q.status = '" + status + "'";
}
if (viewOption != ViewOption.ALL) {
if (!isWhereClauseAdded) {
jpql += "WHERE ";
}
// Check if 'AND' operator is needed.
if (status != Status.ALL) {
jpql += " AND ";
}
switch (viewOption) {
case ONLY_IMAGES:
jpql += "q.image != ''";
break;
case NO_IMAGES:
jpql += "q.image = '' ";
break;
}
}
TypedQuery<Question> query = entityManager.createQuery(jpql,
Question.class);
return query.getResultList();
}

The right thing to do would be to use a query parameter:
String jpql = "select ... where q.status = :status";
Query query = em.createQuery(jpql).setParameter("status", status);
Rather than creating your query dynamically be concatenating query parts, you should also use the Criteria API, which has been designed with this goal in mind.

Can you try changing:
jpql += "q.status = '" + status + "'";
To:
jpql += "q.status = app.utility.Status." + status;

Related

How to find condition in SQL query and replace it 1=1 with Java

I have sql query which has a conditions(in where clause, having clause, or in join). I need to find condition which contains value with brackets like this: {some name} and replace that condition with 1=1(in some cases) with java.
E.g.
Select *
From customer c inner join address a on c.id = a.customer_id
where c.id > {var1} AND (c.name LIKE {var2} OR a.city = {var3}) AND ... (there could be written all posible operaters and conditions which are allowed by sql)
if var1 = 2, var2 = *ALL, var3 = 'aa' then query should looks like
Select *
From customer c inner join address a on c.id = a.customer_id
where c.id > 2 AND (1=1 OR a.city = 'aa')
I thought to split where condition with AND|OR then check if it contains {var2} then replace with 1=1, but this will not work. (in above described example after the split it will be (c.name LIKE {var2} so ( also will be replaced).
Does somebody came across with this situation and how solved.
Is there any open source libraray which will find and replace or how can I do that with regex ?
First I assume you are not able to dynamically build your sql. For this you would go the trivial way and could build your sql using string concats, e.g.
"select ... where c.id > " + myvalue + " ... "
The use of regular expressions for sqls will work, if you can guarantee that your expression will only find the specific parts of your sql and not more. This sounds a bit trivial but is hard to achieve for specific sqls, e.g.:
select '{var2}' from mytable
Should {var2} replaced here or not? I think not, because it is part of a string literal and is not part of a where statement.
Therefore you need a more structured look at your sql which is provided by a sql parser.
Using e.g. JSqlParser (https://github.com/JSQLParser/JSqlParser) you would be able to identify the parts of your where statement and replace the parts of your SQL in a controlled manner.
But to get this parsing going, you have to replace your {val} with something that is SQL conform, e.g. __val__.
So your replacement could be achieved with something like the following code. It assumes, that the replacement is only done within the right expression, but you could easily change that.
String sqlTxt = "Select * from customer c inner join address a on c.id = a.customer_id where c.id > {var1} AND (c.name LIKE {var2} OR a.city = {var3})";
//replace macro constructs
String sql = sqlTxt.replace("{", "__").replace("}", "__");
//build replacement data
Map<String, String> data = new HashMap<>();
data.put("__var1__", "2");
data.put("__var2__", "*ALL");
data.put("__var3__", "'aa'");
//parse sql
Select select = (Select) CCJSqlParserUtil.parse(sql);
StringBuilder b = new StringBuilder(sql);
//rewrite sql to fit your needs
((PlainSelect) select.getSelectBody()).getWhere().accept(new ExpressionVisitorAdapter() {
int delta = 0; //to correct the position due to former replacements
#Override
protected void visitBinaryExpression(BinaryExpression expr) {
if (expr instanceof ASTNodeAccess) {
if (expr.getRightExpression() instanceof Column) {
Column c = ((Column) expr.getRightExpression());
if (data.containsKey(c.getColumnName())) {
if ("__var2__".equals(c.getColumnName())) {
delta = replaceASTNodeWith(b, delta, (ASTNodeAccess) expr, "1=1");
} else {
delta = replaceASTNodeWith(b, delta, (ASTNodeAccess) expr,
expr.getLeftExpression() + expr.getStringExpression() + data.get(c.getColumnName()));
}
}
}
}
super.visitBinaryExpression(expr);
}
});
System.out.println("parsed sql = " + select.toString());
System.out.println("changed sql = " + b.toString());
The replacement in your sql is somewhat tricky due to the position change of former replacements.
//do the text replacement within the sql
private static int replaceASTNodeWith(StringBuilder sql, int delta, ASTNodeAccess node, String expr) {
sql.replace(
node.getASTNode().jjtGetFirstToken().absoluteBegin + delta - 1,
node.getASTNode().jjtGetLastToken().absoluteEnd + delta - 1,
expr);
return delta + expr.length()
- (node.getASTNode().jjtGetLastToken().absoluteEnd - node.getASTNode().jjtGetFirstToken().absoluteBegin);
}
This will not answer your question, but just more a suggestion on how I used the 1=1 approach.
I think you might want to write something like this
String query = "select * from customer c inner join address a on c.id = a.customer_id where 1=1"
if(var1 != null)
query += "and c.id > " + var1
if(var2 != null)
query += "and c.name LIKE " + var2
if(var3 != null)
query += "and a.city = " + var3
for example you want to had that logic, it will be a little tricky that it will look like this.
//you should realize that 1=1 here to make sure that we add "AND " command
String query = "select * from customer c inner join address a on c.id = a.customer_id where 1=1"
if(var1 != null)
query += "and c.id > " + var1
if(var2 != null && var3 != null) { //because if var2 or var3 is all, then return all.
query += "and ( c.name LIKE " + var2;
query += "or a.city = " + var3 + ")";
}
Hope this help
use the following:-
In case of single word variable between {singleWord}
queryString = queryString.replaceAll("\\{[A-Za-z0-9]+\\}", "1=1");
In case of multi word variable between {multi word}
queryString = queryString.replaceAll("\\{[A-Za-z0-9\\s]+\\}", "1=1");
Refer for more details:- https://docs.oracle.com/javase/1.5.0/docs/api/java/util/regex/Pattern.html#sum

HQL query throws QueryException with working SQL query

I'm working with HSQLDB and Hibernate, and I want to perform search requests from my REST API.
For example, for a REST request like localhost:8080/search?token=a%20e, my method should create the following query: FROM Course WHERE Course.description LIKE '%a%' OR Course.description LIKE '%e%' and I get this exception:
javax.servlet.ServletException: java.lang.IllegalArgumentException: org.hibernate.QueryException: Unable to resolve path [Course.description], unexpected token [Course] [FROM model.Course WHERE Course.description LIKE '%a%' OR Course.description LIKE '%e%']
This is the code in SearchService's method for searching Course by description or name.
public List<Course> searchCourses(String token, MatchIn column) {
// Prepare and clean token, leaving only key words
String[] keyWords = token.split(" ");
// Build query and ask database to retrieve relevant courses
StringBuilder sb = new StringBuilder("FROM Course WHERE ");
String colName = "Course.";
if(column.equals(MatchIn.DESCRIPTION)) colName += "description";
else if(column.equals(MatchIn.NAME)) colName += "name";
sb.append(colName);
int i = 0;
sb.append(" LIKE \'");
sb.append("%");
sb.append(keyWords[i]);
sb.append("%\'");
if(keyWords.length != 1){
i++;
for (; i < keyWords.length; i++) {
sb.append(" OR " + colName +
" LIKE \'");
sb.append("%");
sb.append(keyWords[i]);
sb.append("%\'");
}
}
Query query = session.createQuery(sb.toString());
return query.list();
}
Note that in the exception I'm receiving, it says that my method is actually creating the following query: FROM *model.*Course WHERE Course.description LIKE '%a%' OR Course.description LIKE '%e%'
When I try SELECT * FROM Course WHERE c.description LIKE '%a%' OR c.DESCRIPTION LIKE '%e%'; in IDEA's SQL console, it runs successfully. (I'm not using SELECT * in the query I'm creating because HQL doesn't use it)
I'm new to HQL and SQL, so I don't know where the problem is.
EDIT:
In Debugger mode I found the exact place where the exception is being called. There seems to be a problem with Hibernate:
I don't know what's causing this issue.
HQL works with binding parameters, so adding directly LIKE '%key%' won't work. Hibernate will convert the HQL to a SQL so to achieve that you may do this:
for (; i < keyWords.length; i++) {
sb.append(" OR " + colName + " LIKE " + "key" + String.valueOf(i));// notice that I'm not adding the '%%'
}
then you have to bind the parameters:
Query query = session.createQuery(sb.toString());
for (int j = 0; j < keyWords.length; j++) {
query.setParameter("key" + String.valueOf(j), "%" + keyWords[j] + "%")
}
As you can see it's a lot of code for a simple query.
So basically you have 2 options:
Create a native SQL. session.createSQLQuery(...)
Use Criteria.
String colName = "";
if(column.equals(MatchIn.DESCRIPTION)) {
colName = "description";
} else if(column.equals(MatchIn.NAME)) {
colName = "name";
}
Criteria criteria = session.createCriteria(Course.class)
for(String key : keyWords) {
criteria.add(Restrictions.or(Restrictions.like( colName, "%" + key + "%"));
}
return criteria.list();
TIPS:
DO NOT CONCAT YOUR PARAMS. USE query.setParameter(..)

SQL error or missing database (near “?”: syntax error)

private static final String QUERY = "SELECT * FROM " + TABLE_SONG_DETAILS + " WHERE " + TABLE_SONG_DETAILS + "." + "artist" + "=? ORDER BY track ?";
private PreparedStatement queryAllSongsInfo = conn.prepareStatement(QUERY);
// the user inputs the artist_name and ORDER
queryAllSongsInfo.setString(1, artist_name);
if (order == ORDER_BY_DESC) {
queryAllSongsInfo.setString(2, "DESC");
} else {
queryAllSongsInfo.setString(2, "ASC");
}
Shows the error: SQL error or missing database (near “?”: syntax error)
If i only include the first placeholder then it works fine.
queryAllSongsInfo.setString(1, artist_name);
Why cant i use multiple placeholders ?? Why the second placeholder doesn't consider the second input from user?
You can use placeholders only for column values. You can't use them for table names, column names or (as you tried in this example) reserved words.
You can create two SQL strings, one for ascending order and the other for descending order:
private static final String QUERY_ASC = "SELECT * FROM "+TABLE_SONG_DETAILS +" WHERE "+TABLE_SONG_DETAILS+"."+"artist"+"=? ORDER BY track ASC";
private static final String QUERY_DESC = "SELECT * FROM "+TABLE_SONG_DETAILS +" WHERE "+TABLE_SONG_DETAILS+"."+"artist"+"=? ORDER BY track DESC";
private PreparedStatement queryAllSongsInfo = conn.prepareStatement(order==ORDER_BY_DESC?QUERY_DESC:QUERY_ASC);
// the user inputs the artist_name and ORDER
queryAllSongsInfo.setString(1, artist_name);
No when you use :
queryAllSongsInfo.setString(2, "DESC");
This will put DESC or ASC keywords between two quotes like this ORDER BY track 'DESC' and this is not correct.
Instead use concatenation directly with the query for example :
String QUERY = "SELECT * FROM "+TABLE_SONG_DETAILS +" WHERE "+TABLE_SONG_DETAILS+"."+"artist"+"=? ORDER BY track ";
if(order==ORDER_BY_DESC) {
QUERY += "DESC";
}else {
QUERY += "ASC";
}
PreparedStatement queryAllSongsInfo = conn.prepareStatement(QUERY);
queryAllSongsInfo.setString(1, artist_name);

how to make filter between two datebox zk

I have 2 datebox to make a filter. Their value will determine the 'from' and 'to' in my query (I'm using Oracle now), here is the code.
#Listen("onClick=#btnSaveFilter")
public void saveFilter() throws Exception {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
c_main_filter.detach();
cc.refreshFilter(""
+"[Date of Birth] between '" + formatter.format(dbDateBirthFrom.getValue()).toString() + "' "
+ "and '" + formatter.format(dbDateBirthto.getValue()).toString() + "'");
}
when both datebox has value, the query work. but when they have no value the query is giving no data.
when datebox has value, it's giving data
"[Registration Date] between '2010-09-23' and '2010-09-23' "
when datebox has no value, it's giving no data
"[Registration Date] between '' and '' "
like another filter I expect if the value is '' then all data will appear, but not :D hahaha. the condition is more than this actually, the filter has a lot of parameter one of them is this condition, and some of them use date format so there will be more condition like this.
do you know how to elegantly fix this problem, I've been thinking to use 'if' to determine the datebox has value or no then I will append the text to query text if both of them has value, but then I found another problem how I can add 'and' in query to give another condition,
let say I have 5 conditions so then
"select * from xxxx where 1stcondition and 2ndcondition and
3rdcondition and 4thcondition and 5th condition"
so when the dateboxes of the 5thcondition has no value the query will be wrong like this
"select * from xxxx where 1stcondition and 2ndcondition and
3rdcondition and 4thcondition and"
if I want to use 'if' how can I play with the 'and'? but if you have alternative it will be great cause I don't have to deal with 'if' :D
You can use String.isEmpty() to determine whether you need to put an and:
String where = "";
if (from != null && to != null) {
where += <yourDateCondition>;
}
if (<needToAddSecondCondtion>) {
if (!where.isEmpty()) {
where += " and ";
}
where += <secondCondition>;
}
// continue with other conditions
String query = "select * from xxxx where " + where;
I don't know if you use plain JDBC or ORM framework like hibernate to querying to the database, but you can try something like this :
public void saveFileter(){
StringBuilder sb = new StringBuilder();
sb.append("select * from table ");
if(dbDateBirthFrom.getValue() != null{
sb.append(determineFilterWord(sb.toString()));
sb.append("your condition ");
}
if(dbDateBirthTo.getValue() != null{
sb.append(determineFilterWord(sb.toString()));
sb.append("your condition ")
}
session.createQuery(sb.toString()); //if you using hibernate
}
private string determineFilterWord(String query){
if(query.toLowerCase().indexOf("where") != -1){
return "and ";
}else{
return "where ";
}
}

Conditional AND using hibernate

I have the following query:
#NamedQuery(name = "User.findByParams", query=
"SELECT *
FROM User user
WHERE user.name type = :inputType")
And I wish to add AND statement, that will take place only if the inputs are supplied:
#NamedQuery(name = "User.findByParams", query=
"SELECT *
FROM User user
WHERE user.name type = :inputType AND (:ageInput != null AND user.age > :ageInput")
It means that if the ageInput is supplied, filter by it as well. If not- ignore this param. Any ideas?
Any ideas?
As the previous speakers wrote, you can use Criteria
Criteria criteria = createCriteria()
.add(Restrictions.eq("type", type));
if (ageInput != null) {
criteria.add(Restrictions.eq("ageInput", ageInput));
}
List<User> list = criteria.list();
or SQLQuery
String sql = "SELECT * " +
"FROM User user " +
"WHERE user.type = :inputType ";
sql += (ageInput != null) ? "AND ageInput = :ageInput " : "";
Query query = sessionFactory.getCurrentSession().createSQLQuery(sql)
.setParameter("inputType", inputType);
if(ageInput != null) {
query.setParameter("ageInput", ageInput);
}
return (List<User>) query.list();
You will have to check if ageInput is supplied or not in code and will have to call different methods accordingly.
Means if ageInput is supplied then you will have to call a method having ageInput constraint o/w call method which do not have ageInput constraint.
Alternatively, you can use predicates and execute a query.

Categories