grails dynamically add gorm subquery to existing query - grails

I would like to have a util for building queries, so that I can add specificity to a common query rather than hard coding similar queries over and over again.
For instance:
DetachedCriteria query = DeviceConfiguration.where { ... }
while(query.list(max: 2).size() > 1) QueryUtil.addConstraint(query, newConstraint)
But I'm having trouble with queries that involve many-to-many relationships.
If my domain classes are:
class StringDescriptor {
String name
String stringValue
static hasMany = [ deviceConfigurations: DeviceConfiguration ]
static belongsTo = DeviceConfiguration
}
class DeviceConfiguration {
Integer setting1
Integer setting2
static hasMany = [ stringDescriptors: StringDescriptor ]
}
And my device configurations look like this:
DeviceConfiguration hondaAccord = new DeviceConfiguration(setting1: 1, setting2: 1)
DeviceConfiguration hondaCivic = new DeviceConfiguration(setting1: 2, setting2: 2)
DeviceConfiguration accord = new DeviceConfiguration(setting1: 3, setting2: 3)
StringDescriptor hondaDescriptor = new StringDescriptor(name: "make", stringValue: "honda")
StringDescriptor civicDescriptor = new StringDescriptor(name: "model", stringValue: "civic")
StringDescriptor accordDescriptor = new StringDescriptor(name: "model", stringValue: "accord")
hondaAccord.addToStringDescriptors(hondaDescriptor)
hondaAccord.addToStringDescriptors(accordDescriptor)
hondaCivic.addToStringDescriptors(hondaDescriptor)
hondaCivic.addToStringDescriptors(civicDescriptor)
accord.addToStringDescriptors(accordDescriptor)
hondaAccord.save(failOnError: true)
hondaCivic.save(failOnError: true)
accord.save(failOnError: true, flush: true)
I would like to be able to do this:
def query = DeviceCollector.where{ stringDescriptors {name =~ "make" & stringValue =~ "honda"} }
if(query.list(max: 2)?.size() > 1)
def query2 = query.where { stringDescriptors {name =~ "model" & stringValue =~ "civic"} }
if(query2.list(max: 2)?.size() > 1)
//...
But that doesn't work - query2 gives the same results as the first query. And yet when I do THIS, it works perfectly:
def query = DeviceCollector.where{ stringDescriptors {name =~ "make" & stringValue =~ "honda"} }
if(query.list(max: 2)?.size() > 1)
def query2 = query.where { eq('setting1', 1) }
if(query.list(max: 2)?.size() > 1)
def query3 = query.build { eq('setting2', 1) }
Please advise :(
EDIT thanks to injecteer
Now my domain includes this:
class DeviceConfiguration {
//...
static namedQueries = {
byStringDescriptor { String name, String value ->
stringDescriptors {
ilike 'name', name
ilike 'stringValue', value
}
}
}
}
And my attempt to string the queries together looks like this:
//Lists hondaAccord and hondaCivic
DeviceConfiguration.byStringDescriptor("make", "honda").list()
//Lists hondaAccord and accord
DeviceConfiguration.byStringDescriptor("model", "accord").list()
// LISTS NOTHING... BUT WHYYYYY?
DeviceConfiguration.byStringDescriptor("make", "honda").byStringDescriptor("model", "accord").list()
I am confused. Yet again.
EDIT thanks to injecteer's updated answer
Yay, here is the named query that worked for me:
class DeviceConfiguration {
//...
static namedQueries = {
byStringDescriptor { List<StringDescriptor> descriptors ->
sizeEq('stringDescriptors', descriptors.size())
stringDescriptors {
or {
for(descriptor in descriptors) {
and {
ilike 'name', descriptor.name
ilike 'stringValue', descriptor.stringValue
}
}
}
}
}
}
}
The results (YAYYY) :) ...
StringDescriptor hondaDescriptor = new StringDescriptor(name: "make", stringValue: "honda")
StringDescriptor accordDescriptor = new StringDescriptor(name: "model", stringValue: "accord")
//returns nothing - **check**
def hondaQuery = DeviceConfiguration.byStringDescriptor([hondaDescriptor]).list()
//returns accord configuration - **check**
def accordQuery = DeviceConfiguration.byStringDescriptor([accordDescriptor]).list()
//returns just the hondaAccord configuration - **YESSSSSS**
def hondaAccordQuery = DeviceConfiguration.byStringDescriptorUsingOr([hondaDescriptor, accordDescriptor]).listDistinct()
injecteer is my favorite person ever.

Use criteria query or named queries. they both allow for better chaining
class DeviceConfiguration {
static namedQueries = {
byDescriptors { List vals ->
stringDescriptors {
or{
for( def tuple in vals ){
and{
ilike 'name', "%${tuple[ 0 ]}%"
ilike 'stringValue', "%${tuple[ 1 ]}%"
}
}
}
}
}
}
}
so you can call:
DeviceConfiguration.byDescriptors( [ [ 'make', 'honda' ], [ 'model', 'accord' ] ] ).findAllBySetting1( 10 )
you should know, what conjunction is appropriate and or or
UPDATE 2
with so many of ands you won't find anything...
if you fire up a query like blah( honda, accord ).list() it would try find stringDescriptors with name='honda' AND name='accord' which is not possble, so it returns no results!
That's why I tend to think, that your domain model does NOT allow such queries at all - even at SQL-level.
Your attributes shall be clearly distinguishable, so that you can find by honda (type 'make') and accord (type 'model') it shouldn't look for "honda" in "model".
Can a single DeviceConfiguration instance contain several StringDescriptors of the same type?

Related

How to save addTo in order by given date/id?

I need some help on my API, when I'm on web, the order is saving correct, but when its on API, it goes all wrong:
def test = parseJSON.sort { a, b -> a.ID <=> b.ID } //or dateTime, will print the same
//order when I print each of them
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:1, ID:1, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:0, ID:2, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:0, ID:3, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:4, ID:4, ph:0, redoxPotential:0, temperature:0]
test.each{
def sample = new SampleWater()
sample.levelWater = it.levelWater
sample.conductivity = it.conductivity
sample.dissolvedOxygen = it.dissolvedOxygen
sample.redoxPotential = it.redoxPotential
sample.ph = it.ph
sample.temperature = it.temperature
water.addToSamples(sample)
}
return water
My problem is that addTo is not saving in order. How can I solve this?
Make sure you have defined the type of samples as a List in your Water domain class so that we can maintain the insertion order:
class Water {
static hasMany = [samples: Sample]
List<Sample> samples = []
}
class Sample {
def levelWater
}
By default implementation of hasMany is of type Set which does not maintain the insertion order but is responsible for uniqueness.
Since, now you samples will be saved in the same order as they are inserted.
You have to specify with order you want to apply to the list of SampleWater in the "water" domain class.
i.e:
class BlogCategory {
static hasMany = [
entries : BlogEntry
]
static mapping = {
entries: sort:'dateCreated', order:'desc'
}
}
In this example BlogEntry will be ordered respect dateCreated.

Grails - createCriteria: associations + not + ilike

some how a Criteria: associations + not + ilike does not give a good result.
I still get cases with actions that have the status I don't want in the result.
Any clue or suggestion for other approaches?
I have this in controller:
def pgp = [:]
pgp.max = params.max?.toInteger() ?: 20;
pgp.offset = params.offset?.toInteger() ?: 0
pgp.max = 20;
def result = Case.createCriteria().list(pgp) {
actions {
not {
and {
ilike("status","%CLOSED")
ilike("status","%Installed in PRD")
}
}
}
}
This is the relevant Domain snipped:
class Case {
String caseCode
String caseName
String caseType
static hasMany = [ actions : Action ]
I'm on Grails 2.4.4
Your Boolean logic is faulty - the and should be an or. Your current tests will be true for every possible value of status, as any value that passes ilike("status","%CLOSED") will fail ilike("status","%Installed in PRD") and vice versa.

How to use "Grouped BY" with Grails criteria among three tables

If I have three domain classes like this:
class Candidate {
static belongsto=[application:Application]
}
class Vote {
String name;
static belongsto=[application:Application]
}
class Application {
Date datedemand;
Candidate candidates;
Vote votes
static hasMany = [candidates:Candidate,votes:Vote]
}
I want to retrieve all candidates grouped by Vote's Name .
I started ​​the following attempt, and I remain blocked :
def criteria = Application.createCriteria()
def results = criteria.list {
votes {
}
candidates{
}
}
In decent grails (2.x+) you could try something similar:
Application.withCriteria {
createAlias("votes", votes)
projections {
groupProperty("votes.name")
}
}
In this scenario I would go for a simple vanilla HQL instead of Criteria, something like this or more (use join the way you need):
String hql = "Select V.name, C.name " +
"from Candidate C, Vote V " +
"where C.application = V.application " +
"group by V.name, C.name"
Query query = session.createQuery(hql)
List results = query.list()

Sorting list or getAll in Grails

I wanted to implement a default sort order in my domain class and immediately found it didn't work with the getAll method. No biggie, I just used list instead. The thing is that the default sort order in a domain class does not allow you specify multiple sort fields (as seen here).
My goal is to sort all Foo objects first by the name of their Bar object, then by their own name.
class Foo {
String name
String Bar
}
class Bar {
String name
}
How can I implement this in the domain class so I don't have to specify a long/nasty comparator every time I call .list()?
One of my attempts:
static Comparator getComparator() {
def c = { a, b ->
def result = a.bar.name.compareTo( b.bar.name );
if ( result == 0 ) {
result = a.name.compareTo( b.name );
}
}
return c as Comparator
}
Then I could just call Foo.list(Foo.getComparator())... if I could get it to work.
Update:
I think I am really close here, just having trouble with implementing two comparisons in the same sort closure.
Foo.list().sort{ a, b ->
def result = a.bar.name <=> b.bar.name;
// Things mess up when I put this if statement in.
if( result == 0 ) {
a.name <=> b.name
}
}
Disco!
class Foo { // My domain class
// ...
static Comparator getComparator() {
def c =[
compare: { a, b ->
def result = a.bar.name <=> b.bar.name;
if( result == 0 ) {
result = a.name <=> b.name
}
return result
}
] as Comparator
}
// ...
}
And implemented like this in my controller:
Foo.list().sort( Foo.getComparator() )
PS:
The above works, but Jeff Storey posted some code in his answer after I discoed, and his code works and is much nicer than mine so use it :)
In your case, would it make sense to have Foo implement Comparable and the implementation could do the comparison as you described? Then when you sort the objects in a list, because they are Comparable, they will sort properly.
If it does not make sense for you to implement Comparable though, you will need to specify a comparator to sort by.
Here's some sample code based on your comments:
edit:
class Person implements Comparable<Person> {
String firstName
String lastName
int compareTo(Person other) {
int lastNameCompare = lastName <=> other.lastName
return lastNameCompare != 0 ? lastNameCompare : firstName <=> other.firstName
}
String toString() {
"${lastName},${firstName}"
}
}
def people = [new Person(firstName:"John",lastName:"Smith"), new Person(firstName:"Bill",lastName:"Jones"), new Person(firstName:"Adam",lastName:"Smith")]
println "unsorted = ${people}"
println "sorted = ${people.sort()}"
This prints:
unsorted = [Smith,John, Jones,Bill, Smith,Adam]
sorted = [Jones,Bill, Smith,Adam, Smith,John]
To further simplify the above post (I would have commented on it but I don't have the rep yet), you can chain the groovy compare operators using the elvis operator:
class Person implements Comparable<Person> {
String firstName
String lastName
int compareTo(Person other) {
return lastName <=> other.lastName ?: firstName <=> other.firstName
}
String toString() {
"${lastName},${firstName}"
}
}
def people = [new Person(firstName:"John",lastName:"Smith"), new Person(firstName:"Bill",lastName:"Jones"), new Person(firstName:"Adam",lastName:"Smith")]
println "unsorted = ${people}"
println "sorted = ${people.sort()}"
This will give you the same result because 0 is considered false in groovy's eyes, which will make it look at the next conditional in the chain.

Grails : from HQL to DetachedCriteria Query

Let say I have the following domain model :
class Book {
String title
Set authors
static hasMany = {authors: Author}
}
class Author {
String name
}
The HQL query to retrieve a collection of Author given a query on a title Book:
Author.executeQuery("select distinct author from Book as book join book.authors as author where book.name like ?", ["%groovy%"])
But I would to be able to have the same result a with DetachedCriteria or alike (but is it possible ... ?) and without adding a relationship from Author to Book (otherwise it would pretty obvious)
thanks
This can be done a few ways with criteria or detached Criteria, but a bit easier with regular GORM criteria since it implements the createAlias command that detachedCriteria doesn't as of Grails 2.2.2:
Create Alias In Detached Criteria
Here are both ways:
package bookauthor
import grails.gorm.DetachedCriteria
import grails.orm.HibernateCriteriaBuilder
class MyController {
def index() {
HibernateCriteriaBuilder ac2 = Author.createCriteria()
HibernateCriteriaBuilder criteria2 = Author.createCriteria()
HibernateCriteriaBuilder criteria = Book.createCriteria()
def bookResults = criteria {
projections {
property 'aut.id'
}
createAlias('authors', 'aut')
like('title', '%Groovy%')
}
def dc = new DetachedCriteria(Book).build {
authors {}
like('title', '%Groovy%')
}
def myList = dc.list().collect { it.authors.collect { author -> author.id} }.flatten()
def resultsDetached = criteria2 {
'in'('id', myList )
}
def results = ac2 {
'in'('id', bookResults )
}
log.info("RESULTS: " + results)
log.info("Detached RESULTS: " + resultsDetached)
}
}
You'll see in the logs:
bookauthor.MyController - RESULTS: [bookauthor.Author : 1, bookauthor.Author : 3]
bookauthor.MyController - Detached RESULTS: [bookauthor.Author : 1, bookauthor.Author : 3]
Unfortunately, AFAIK, this is not possible with this query. It's possible with the following ugly query, though:
select author from Author author
where author.id in (select author2.id from Book book
join book.authors as author2
where book.name like :bookName)
For such a simple, non-dynamically composed query, I would stick with your HQL query. If you really need to use Criteria, then this is the corresponding code:
Criteria c = session.createCriteria(Author.class, "author");
DetachedCriteria dc = DetachedCriteria.forClass(Book.class, "book");
dc.createAlias("book.authors", "author2");
dc.add(Restrictions.like("book.name", bookName));
dc.setProjection(Projections.property("author.id"));
c.add(Subqueries.propertyIn("author.id", dc);
List<Author> authors = (List<Author>) c.list();

Resources