I am trying to implement the following SQL query in GORM:
select au.* from App_user au
inner join SEC_USER su on su.id=au.SEC_USER_ID
where
not su.id in (
select susr.SEC_USER_ID from SEC_USER_SEC_ROLE susr
inner join SEC_ROLE sr on susr.SEC_ROLE_ID=sr.id
where sr.authority like 'ROLE_INTERNAL%'
and susr.SEC_USER_ID=su.id
)
(get all the users who don't have a role matching the ROLE_INTERNAL.* pattern)
I have a working query, using GORM:
AppUser.createCriteria().list(args, {
secUser {
// def su = SecUser
notIn "id", SecUserSecRole.where {
secRole {
authority ==~ 'ROLE_INTERNAL%'
}
/*
secUser {
id == su.id
}
*/
}.property("secUser.id")
}
})
But this query is inefficient, due to the fact that I don't know how to add the SQL where clause and susr.SEC_USER_ID=su.id in the criteria DSL.
I have seen here and there mentions of using the commented code, to create an alias of the SecUser (su) at the root level, and then using it in the subquery, but I ger the following exception when trying to uncomment my additional statements:
No such property: id for class: SecUser
I feel that i am really close, but I can't figure it out by looking at the documentation. It is rather sparse in this aspect.
As the documentation shows under "7.4.8. More Advanced Subqueries in GORM",
DetachedCriteria<AppUser> query = AppUser.where {
secUser {
def u = SecUser
notIn "id", SecUserSecRole.where {
def u2 = secUser
secRole.authority ==~ 'ROLE_INTERNAL%' && u2.id == u.id
}.property("secUser.id")
}
}
query.list(args)
Related
I am using grails-2.5.6 version. I am using spring-security-core plugin. I have a criteria query on UserRole table. Where I want to find all distinct users by a role. It is working properly.
But the problem is the pagination effect. When I am counting on the list it is counting on UserRole list object. But I need the count on distinct projection items. Here is my attempt below:
def list(Integer max) {
def userInstanceList = UserRole.createCriteria().list(params) {
createAlias('user', 'au')
createAlias('role', 'ar')
projections {
distinct("user")
}
if (params.roleId) {
eq('ar.id', params.getLong("roleId"))
}
}
def totalCount = userInstanceList.totalCount
[userInstanceList: userInstanceList, totalCount: totalCount]
}
Here, totalCount is the number of UserRole list. But I want the distinct projection count.
I would tackle this slightly differently, you want to analyse the users, not the userroles.
So I'd do something like:
List<User> usersWithRole = UserRole.createCriteria().list(params) {
role {
eq('id', params.getLong("roleId"))
}
}*.user
int count = usersWithRole.size()
Unless of course there's hundreds or thousands of users, in which case I wouldn't want to load all of them each time and would revert to SQL.
Is this a custom version of spring security you're using? I've never seen Roles with a 'long' based ID, usually, the key is a String representing the Authority name.
Usually the DBAs see the use of distinct keyword as a code-smell.
In your case I would rather use the User as the main domain object to run the query against and a group by clause:
long id = params.getLong "roleId"
def list = User.createCriteria().list( params ) {
projections{
groupProperty 'userRole.role.id'
}
if( id )
userRole{
role{
eq 'id', id
}
}
}
I have the following domain classes
class EventA {
static belongsTo = [offer: Offer]
}
class EventB extends EventA {}
class EventC extends EventA {}
class Offer {
static hasMany [events: EventA]
}
I need to retrieve offers that are not associated with an EventC.
In SQL this can easily be performed as:
SELECT *
FROM OFFER O
LEFT JOIN EVENTC C ON O.event_id = C.id
WHERE C.ID IS NULL
Searching through the grails documentation I found instanceOf. Stating that once you have the result set you can perform a check of the instance type.
def offers = Offer.list()
for (Offer o in offers) {
for(Event e : o.events) {
if (e.instanceOf(EventC)) {
// no bueno
}
}
}
The above just feels wrong. I would prefer to have the database do such filtering for me. Is there a way to perform such a filter with searchCriteria?
You can accomplish this by querying the Event classes directly. That way you can specifically query the flavor of Event you care about. Then query the Offer table with the list of Id's
Offer.findAllByIdInList(EventC.list().offerId)
This actually ended up being easier then I expected. On my search criteria, I can build an expression to not include any Offer that has an event EventC.
Example:
Offer.with {
events {
ne('class', EventC)
}
}
Since I questioned this approach I enabled hibernate logging. Ironically, it generated SQL that was pretty similar to what I was after.
SELECT *
FROM OFFER O
LEFT JOIN EVENTB B ON O.ID == B.EVENT_ID
LEFT JOIN EVENTC C ON O.ID == C.EVENT_ID
WHERE
(
CASE
WHEN B.ID IS NOT NULL THEN 1
WHEN C.ID IS NOT NULL THEN 2
END <> ?
)
I have a domain model - where Org has many sites, and has many domains (these are two 'bag' collection attributes).
I want to write a query that retrieves a single, and preferably the sites and domain collections in one hit.
I tried this first
org = Org.findById (id, [fetch:[sites:"eager", domains:"eager"]]
which fails with
cannot simultaneously fetch multiple bags: [com.softwood.domain.OrgRoleInstance.sites, com.softwood.domain.OrgRoleInstance.domains]
(It will work with only one of the two collections).
I tried a criteria query like this
org = resource.withCriteria (uniqueResult: true) {
fetchMode 'sites', FetchMode.SELECT
fetchMode 'domains', FetchMode.SELECT
idEq(id)
sites {
org {
eq 'id', "$id"
}
}
} // didn't work
which errors with this
No signature of method: com.softwood.controller.OrgRoleInstanceController.fetchMode() is applicable for argument types: (java.lang.String, org.hibernate.annotations.FetchMode) values: [sites, SELECT]
This indicates it doesn't like the fetchMode function, (using either SELECT or JOIN).
How do you write a criteria query to return a single matched object by id, but returns sites, and domains collections at the same time?
The domain class looks like this. I don't want to use the static mapping ={} closure - as i want to control the loading through writing explicit queries as required
class Org {
enum OrgRoleType {
Supplier,
Customer,
Service_Provider,
Manufacturer,
Maintainer,
Indeterminate
}
String name
OrgRoleType role
Collection<NetworkDomain> domains = []
Collection<Site> sites = []
Collection<MaintenanceAgreement> mags //optional only set if role is service provider
//Collection<Device> ci
static hasMany = [domains : NetworkDomain, sites: Site, mags:MaintenanceAgreement]
static mappedBy = [mags: "maintainer"] //disambiguate column in mags
static constraints = {
name nullable:false
role nullable:true
domains nullable:true
sites nullable:true
mags nullable:true //optional
}
}
I had seen this [Grails GORM Criteria Query Eager Fetching
I'd tried to do similarly but the fetchMode (String, enum) just won't run.
Update
I changed query to this
org = resource.withCriteria (uniqueResult: true) {
join 'sites'
join 'domains'
idEq(id)
sites {
org {
eq 'id', "$id"
}
}
domains {
customer {
eq 'id', "$id"
}
}
}
This errors with
Cannot invoke method call() on null object
with the trace point to point where it access sites{org{ in the criteria.
First, testing has to be done as integration tests and not unit tests, however the result is you still can't do query finder with two eager loads. if you want to load collections you have to use criteria or possibly a where clause to do so
Here are two integration tests - the first using finders fails with exception, the second achieves the goal using criteria query:
void "query by finderById with multiple eager fetch in map conditions " () {
given :
when: "we print value from a collection "
def org = OrgRoleInstance.findById (7L, [fetch:[sites:"eager", domains:"eager"]])
println org.domains[0].name
then:
org.hibernate.loader.MultipleBagFetchException ex = thrown()
}
void "query withCriteria to do multiple eager fetch in map conditions " () {
given :
def org = OrgRoleInstance.withCriteria (uniqueResult: true) {
fetchMode 'sites', FetchMode.SELECT
fetchMode 'domains', FetchMode.SELECT
idEq (7L)
sites {}
domains {}
}
when: "we print value from a collection "
println org.domains[0].name
then:
org.domains.size() == 1
org.sites.size() == 2
}
I have Course domain,
Course has one teacher or null
I want to find all courses which either has no teacher or teacher.id != :loginId
How can I write query using GORM dynamic find* methods
Or write it using HSQL
- My teacher property is User domain
Appreciate your help
hasOne Class Structure
class Course {
User teacher
static hasOne = [
teacher: User
]
}
class User {
// implicit id field
}
Using HQL
def getCourses(def loginId) {
return Course.executeQuery("""
SELECT
c
FROM
Course c
LEFT OUTER JOIN c.teacher as t
WHERE
(t.id = NULL OR t.id != :logIn)
""", [loginId: loginId])
}
Using CriteriaBuilder
import org.hibernate.criterion.CriteriaSpecification
def getCourses(def loginId) {
return Course.createCriteria().list{
createAlias(
"teacher",
"t",
CriteriaSpecification.LEFT_JOIN
)
or {
ne("t.id", loginId)
isNull('t.id')
}
}
}
I'm going off of past experiences so I haven't tested your exact scenario, but I believe both options should work. I'm under the impression that a grails dynamic finder would not work in this case because of the nested condition you need (course.teacher.id != loginId).
Consider this query:
select user_id
from user_role a
where a.role_id = -1
and not exists (select 1
from user_role b
where b.role_id != a.role_id
and b.user_id = a.user_id);
I'm trying to recreate it using gorm criterias. Is it possible? How can I add the exists clause?
UPDATE
I've been trying to resolve this way:
UserRole.createCriteria().list{
eq('role', roleObj)
createAlias('user', 'u')
not{
inList('u.id', {
not {
eq('role', roleObj)
}
projections {
property 'user.id'
}
})
}
}
Still not working. I'm gettins this error when executing it:
DUMMY$_closure1_closure2_closure3_closure4 cannot be cast to java.lang.Long
I don't understand the error message. The inner criteria returns the list of ids and if I replace the inner criteria with a list of longs it works. Any clue?
Thanks in advance
Not tested : Try Sql restriction
UserRole.createCriteria().list {
and{
eq('roleId',-1)
sqlRestriction(" not exists(select 1
from UserRole b
where ............
)")
}
}
-- Hoping you have named your domain as UserRole and column name named as roleId.