First of all, sorry for my poor english.
I want to change the following query to 'findBy~' method, but i don't know how to.
#Query(value = "SELECT t FROM Table t WHERE (b.num1 <= :variable or b.num1 IS NULL) AND (b.num2 >= :variable or b.num2 IS NULL)")
Or, is it impossible to get the result by using 'findby~' method name?
I would appreciate if anyone could reply.
Spring Data JPA does have support for all the conditions in your query and nesting of conditions. I'd argue that your query name will become unnecesarelly verbose. It would end up as
Table findByNum1LessThanEqualOrNum1IsNullAndNum2GreaterThanEqualOrNum2IsNull(Integer var0, Integer var1);
This should return the appropiate query, but you'd need to send the variable twice, once for each equals.
With #Query you have the freedom to call your query as you'd like and reuse the same variable.
Now, you CAN fix the downsides of using named methods by using a default method like
default Table myQuery (Integer var) {
return findByNum1LessThanEqualOrNum1IsNullAndNum2GreaterThanEqualOrNum2IsNull(var, var);
}
So you call this instead of the actual query, but then again, it would be much cleaner to use #Query with a proper, descriptive or even self-documenting name if you don't comment your code (you should comment your code). In any case, I suggest you use method names for simple queries and use #Query for anything more complex.
Please, refer to the following links for further reading:
Spring JPA Query Creation
Spring JPA Query Keyword Repository
LeafyJava article on Query Precedence Tricks, which also provides and example of how to change your query logic in case the conditions aren't arranged as you want.
This SO question also provides a bit of insight.
Related
I'm trying to make a N1QL based query on Spring Data Couchbase. The documentation says
#n1ql.fields will be replaced by the list of fields (eg. for a SELECT clause) necessary to reconstruct the entity.
My repository implementation is this one:
#Query("#{#n1ql.fields} WHERE #{#n1ql.filter}")
List<User> findAllByFields(String fields);
And I'm calling this query as follows:
this.userRepository.findAllByFields("SELECT firstName FROM default");
I'm getting this error:
Caused by: org.springframework.data.couchbase.core.CouchbaseQueryExecutionException: Unable to execute query due to the following n1ql errors:
{"msg":"syntax error - at AS","code":3000}
After a little bit of researching, I also tryed:
#Query("SELECT #{#n1ql.fields} FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
With this query, I don't get an error, I get all the documents stored but only the ID the other fields are set to null, when my query tries to get the firstName field.
this.userRepository.findAllByFields("firstName");
Anyone knows how to do such a query?
Thank you in advance.
You're misunderstanding the concept, I encourage you to give the documentation more time and see more examples. I'm not sure what exactly you're trying to achieve but I'll throw some examples.
Find all users (with all of their stored data)
#Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
List<User> findAllUsers();
This will basically generate SELECT meta().id,_cas,* FROM bucket WHERE type='com.example.User'
Notice findAllUsers() does not take any parameters because there are no param placeholders defined in the #Query above.
Find all users where firstName like
#Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND firstName like $1")
List<User> findByFirstNameLike(String keyword);
This will generate something like the above query but with an extra where condition firstName like
Notice this method takes a keyword because there is a param placeholder defined $1.
Notice in the documentation it says
#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1
is equivalent to
SELECT #{#n1ql.fields} FROM #{#n1ql.bucket} WHERE
#{#n1ql.filter} AND test = $1
Now if you don't want to fetch all the data for user(s), you'll need to specify the fields being selected, read following links for more info
How to fetch a field from document using n1ql with spring-data-couchbase
https://docs.spring.io/spring-data/couchbase/docs/2.2.4.RELEASE/reference/html/#_dto_projections
I think you should try below query, that should resolve the issue to get fields based parameter you have sent as arguments.
Please refer blow query.
#Query("SELECT $1 FROM #{#n1q1.bucket} WHERE #{#n1ql.filter}")
List findByFirstName(String fieldName);
Here, bucket name resolve to the User entity and and n1ql.filter would be a default filter.
I am working on a problem where I need to add an OR clause to a set of existing conditions. The current conditions are built in a hash in a method and at the end, they are used in the where clause. Here is a simplified example:
...
conds.merge!({:users => {:archived => false}})
Model.where(conds)
I am trying to add an OR clause to the current set of conditions so it would be something like '(conditions) OR new_condition'. I'd like to add the OR statement without converting each addition to the conds hash into a string. That would be my last option. I was hoping someone has done something like this before (without using Arel). I seem to recall in Rails 2 there was a way to parse a conditions hash using a method from the model (something like Model.some_method(conds) would produce the where clause string. Maybe that would be a good option to just add the OR clause on to that string. Any ideas are appreciated. Thank you for your help!
I found a way to do what I needed. Instead of changing all of the conditions that I am building, I am parsing the conditions to SQL using sanitize_sql_for_conditions. This is a private method in ActiveRecord, so I had to put a method on the model to allow me to access it. Here is my model method:
def self.convert_conditions_hash_to_sql(conditions)
self.sanitize_sql_for_conditions(conditions)
end
So, once I convert my conditions to text, I can add my OR clause (along with the appropriate parentheses) to the end of the original conditions. So, it would go something like this:
Model.where('(?) OR (model.type = ? AND model.id IN(?))', Model.convert_conditions_hash_to_sql(conds), model_type, model_id_array)
I'm attempting to write a stored procedure in MySql that will take a single parameter, and then check that parameter for any text that contains 'DROP','INSERT','UPDATE','TRUNCATE', etc., pretty much anything that isn't a SELECT statement. I know it's not ideal, but, unfortunately the SELECT statement is being built client-side, and to prevent some kind of man-in-the-middle change, it's just an added level of security from the server.
I've tried doing several means of accomplishing it, but, it's not working for me. I've come up with things similar to this:
CREATE PROCEDURE `myDatabase`.`execQuery` (in INC_query text)
BEGIN
#check to see if the incoming SQL query contains INSERT, DROP, TRUNCATE,
#or UPDATE as an added measure of security
IF (
SELECT LOCATE(LOWER(INC_query),'drop') OR
SELECT LOCATE(LOWER(INC_query),'truncate') OR
SELECT LOCATE(LOWER(INC_query),'insert') OR
SELECT LOCATE(LOWER(INC_query),'update') OR
SELECT LOCATE(LOWER(INC_query),'set')
>= 1)
THEN
SET #command = INC_query;
PREPARE statement FROM #command;
EXECUTE statement;
ELSE
SELECT * FROM database.otherTable; #just a generic output to know the procedure executed correctly, and will be removed later. Purely testing.
END IF;
END
Even if it contains any of my "filterable" words, it still executes the query. Any help would be appreciated, or if there's a better way of doing something, I'm all ears.
What if you have a column called updated_at or settings? You can't possibly expect this to work as you intend. This kind of technique is the reason there's so many references to clbuttic on the web.
You're really going to make a mess of things if you go down this road.
The only reasonable way to approach this is to send in the parameters for the kind of query you want to construct, then construct the query in your application using a vetted white list of allowed terms. An example expressed in JSON:
{
"select" : {
"table" : "users",
"columns" : [ "id", "name", "DROP TABLE users", "SUM(date)", "password_hash" ],
"joins" : {
"orders" : [ "users.id", "orders.user_id" ]
}
}
You just need to create a query constructor that emits this kind of thing, and another that converts it back into a valid query. You might want to list only particular columns for querying, as certain columns might be secret or internal only, not to be disclosed, like password_hash in this example.
You could also allow for patterns like (SUM|MIN|MAX|AVG)\((\w+)\) to capture specific grouping operations or JOIN conditions. It depends on how far you want to take this.
What is the best way, using ActiveRecord, to execute the following SQL:
SELECT parent.*
FROM sections AS node, sections AS parent
WHERE node.left BETWEEN parent.left AND parent.right
ORDER BY parent.left DESC
LIMIT 1
I know is possible to use .limit(), .where() and .order() but how do you deal with 'from'?
Or is it better just to execute the whole lot as a single statement?
Thanks for any help.
There's nothing wrong with using SQL in your application so long as you can verify it's working correctly and isn't exposing you to injection attacks. Since this statement is executed as-is you're okay in that regard.
ActiveRecord::Base.connection provides a method for executing arbitrary queries and retrieving the results in a variety of formats. select_all or select_rows may be what you're looking for.
The AREL query generator is not always as clever as we'd like, so when in doubt go with the simplest form of expression. In your case it seems to be that chunk of SQL.
To re-write it using AREL you'd have to use the join method to link it back on itself.
Parent.from('sections AS node, sections AS parent')
.where('node.left BETWEEN parent.left AND parent.right')
.order('parent.left DESC')
.limit(1)
And you get the benefit of chaining.
I need to take a MySQL query and insert a string before each table name. The solution doesn't need to be one line but obviously it's a regex problem. It will be implemented in PHP so having programming logic is also fine.
Rationale and Background:
I'm revamping my code base to allow for table prefixes (eg: 'nx_users' instead of 'users') and I'd like to have a function that will automate that for me so I don't need to find every query and modify it manually.
Example:
SELECT * FROM users, teams WHERE users.team_id = teams.team_id ORDER BY users.last_name
Using the prefix 'nx_', it should change to
SELECT * FROM nx_users, nx_ teams WHERE nx_ users.team_id = nx_ teams.team_id ORDER BY nx_ users.last_name
Obviously it should handle other cases such as table aliases, joins, and other common MySQL commands.
Has anybody done this?
How big of a code base are we talking about here? A regular expression for something like this is seriously flirting with disaster and I think you're probably better off looking for every mysql_query or whatever in your code and making the changes yourself. It shouldn't take more than the hour you'd spend implementing your regex and fixing all the edge cases that it will undoubtedly miss.
Using a regex to rewrite code is going to be problematic.
If you need to dynamically change this string, then you need to separate out your sql logic into one place, and have a $table_prefix variable that is appropriately placed in every sql query. The variable can then be set by the calling code.
$query = "SELECT foo from " . $table_prefix . "bar WHERE 1";
If you are encapsulating this in a class, all the better.
This example does not take into consideration any escaping or security concerns.
First off, regular expressions alone are not up to the task. Consider things like:
select sender from email where subject like "from users group by email"
To really do this you need something that will parse the SQL, produce a parse tree which you can modify, and then emit the modified SQL from the modified parse tree. With that, it's doable, but not advisable (for the reasons Paolo gave).
A better approach would be to grep through your source looking for either the table names, the function you use to sent SQL, the word from, or something like it at script something to throw you into an editor at those points.