This document gives an overview of the Query API.
Contents
See also
The general query is an object implementing the Query
interface.
The interface is targeted at relational queries, ie. queries that can be
expressed by SQL or some similar query language (like Hibernate Query Language, HQL).
These two query langauages are reflected in the subinterfaces HqlQuery
and SqlQuery
and in the QueryType
enumeration.
The general template for of a relational query is:
SELECT [DISTINCT] <selection-list> FROM <root-entity> [JOIN <joined-entity>] [WHERE <restrictions>] [GROUP BY <groupby-list] [HAVING <restrictions>] [ORDER BY <orderby-list>]
The Query
interface has methods for manipulating all aspects of the query
except the root-entity
. The root entity should be specified by the code that
creates an actual instance of a query. For example:
// News.getQuery() SELECT nws FROM NewsData nws
Here the root entity is the "NewsData" entity and has been given an alias "nws".
This alias is the string returned by the Query.getRootAlias()
method.
All Hibernate queries must have a root alias.
An actual implementation are not required to support manipulation of all aspects of
a query. For example the EntityQuery
represents a query for Hibernate
entities. In this case the selection-list
is fixed to return only
items of the specified type. See the "news" example above. If an implementation
doesn't support a method it should throw a UnsupportedOperationException
.
// AbstractEntityQuery.java public void select(Select s) { throw new UnsupportedOperationException(); }
Currently, we have three implementations of the Query
interface:
ItemQuery
: Use HQL to query for items. This query doesn't support
modifying the select, group by or order by part of the query.
This type of query is usually created by the static
getQuery()
method in each item class.
DataQuery
: Use HQL to query for items, but returns disconnected data
objects. This type of query is only used with batchable items and is described
in the batch processing documentation.
DynamicQuery
: Use SQL to query the dynamic part of the database.
This type of query is described in the Dynamic API
documentation.
Runtime query filters are used by Hibernate queries for two purposes:
PermissionDeniedException
is thrown while trying to access
that item.
EntityQuery.include()
and
EntityQuery.exclude()
methods. The default is to
return only items which are owned by the logged in user and not
flagged as removed. A client application may add other options
to the query, for example:
// Also, include removed items ItemQuery query = ... query.include(Include.REMOVED);
The namespace for runtime filters are global. The filter-def.hbm.xml
defines the name and parameters of a filter. The actual SQL code for a filter
is defined on a per-item basis. We ahve choosen to define the actual filters
programmatically in the HibernateUtil.addFilterConditions()
method.
The drawback is that it is not possible to change the filter without compiling.
The advantage is that a hacker cannot change the filters either.
// filter-def.hbm.xml <!-- Filter Ownable items on the owner to only return items owned by the logged in user. --> <filter-def name="ownedBy"> <filter-param name="owner" type="int" /> </filter-def> // HibernateUtil.addFilterConditions() PersistentClass pc = ... Class dataClass = pc.getMappedClass(); if (OwnableData.class.isAssignableFrom(dataClass)) { pc.addFilter("ownedBy", ":owner = owner"); }
The PersistentClass
object is a Hibernate object representing
the mapping for the dataClass
object which is one of the data layer
classes. If the class is ownable (implements the OwnableData
interface)
we create a filter condition which checks the owner
column.
Enabling and using the filter is then as easy as:
int loggedInUserId = ... org.hibernate.Session session = ... org.hibernate.Filter filter = session.enableFilter("ownedBy"); filter.setParameter("owner", loggedInUserId);
Now, all queries executed in that session will have the filter applied to them:
SELECT Samples.* FROM Samples s WHERE :owner = s.owner
One important note is that the filter are applied in the SQL layer of Hibernate, not at the HQL layer. It means that the filter must use the real database column names and not the entity properties. This limits the use of filters to a single table, since it is not possible to know which other tables are joined and what alias they may have.
Runtime filters are enabled/disabled on a per-query basis by instances
of the QueryRuntimeFilter
interface. The actual enabling/disabling
of filters is done by a QueryRuntimeFilterManager
object. It
keeps track of all filters that has been enabled, and disallows
a filter to be enabled twice. It is also used to disable all filters after the
query has been executed.
The QueryRuntimFilterFactory
class has already created instances for
the most commonly used filters. For example all removable items
has a RemovableFilter
which checks the Include.REMOVED
and Include.NOT_REMOVED
options and applies a filter if
neccessary.
Most items have more than one filter. They are collected by the
ChainedFilter
class which calls enableFilters
on each one of them.
The DenyAllFilter
filter is important. It checks if the logged
in user has been denied access to a certain type of item. If so, it
applies a filter that cause all queryes not to return any items.
The OwnableFilter
and ShareableFilter
are used for items implementing the OwnableData
and
SharableData
interfaces. The check options like:
Include.MINE
, Include.SHARED
and
Include.IN_PROJECT
.
The BasicFilter
is assigned to all other items. It
just checks if the logged in user has generic read permission or not.
If not, no items are returned by a query, otherwise all items are returned.
This filter may not be what is wanted by many items. For example, news can
be read by any user if the today is between the start date and end date.
Therfore this filter is optional and can be replaced by a query with
another filter. The News
class does exactly this:
// News.java private static final QueryRuntimeFilter RUNTIME_FILTER = new QueryRuntimeFilterImpl(); ... other code public static ItemQuery<News> getQuery() { return new ItemQuery<News>(News.class, RUNTIME_FILTER); } ... other code private static class QueryRuntimeFilterImpl implements QueryRuntimeFilter { public void enableFilters(QueryRuntimeFilterManager manager, EntityQuery query, DbControl dc) { SessionControl sc = dc.getSessionControl(); if (!sc.hasPermission(Permission.READ, Item.NEWS)) { org.hibernate.Filter filter = manager.enableFilter("todaysNews"); if (filter != null) { filter.setParameter("today", new Date()); } } } } // HibernateUtil.addFilterConditions() PersistentClass pc = ... Class dataClass = pc.getMappedClass(); if (NewsData.class.isAssignableFrom(dataClass)) { pc.addFilter("todaysNews", ":today >= start_date AND (:today <= end_date OR end_date IS NULL)"); } // filter-def.hbm.xml <filter-def name="todaysNews"> <filter-param name="today" type="date" /> </filter-def>
As you can see, it requires a long chain to get a filter to work.
Most replacements of the optional filter is done by child items
which needs to check the logged in user's permission on the parent
item type instead of the actual item type. For example Help
items which are children to Client
.
// Help.java private static final QueryRuntimeFilter RUNTIME_FILTER = new QueryRuntimeFilterFactory.ChildFilter(Item.HELP, Item.CLIENT); ... other code public static ItemQuery<Help> getQuery(Client client) { ItemQuery<Help> query = null; if (client != null) { query = new ItemQuery<Help>(Help.class, null); query.restrictPermanent( Restrictions.eq( Hql.property("client"), Hql.entity(client) ) ); } else { query = new ItemQuery<Help>(Help.class, RUNTIME_FILTER); } return query; }
A query for child items usually take a parameter of the parent type.
If the logged in user had read access to that item the user should
also have read access to any child items. Therefore we can disable
the optional filter by passing in null
as a paremter
to the ItemQuery
constructor.
If the parent item is null we must check if the logged in user has
generic read permission for all parents. This is done by
the ChildFilter
implementation, and we pass an
instance of that class that to the ItemQuery
constructor.