Retrieving data for your Skyve application can be done using queries declared in the module.xml, directly in a Skyve view and in code extensions.

Queries declared in modules and views use Skyve’s Persistence management to issue SQL to retrieve data for basic Create, Read, Update and Delete operations, according to the privileges and scoping declared in your module.xml, even where you have declared complex relationships with references across several documents.

For more control, developers can create creates using a range of approaches and languages using Skyve’s DocumentQuery class. Developers can also extend Skyve’s ListModel class to drive listGrids for more complex queries and other kinds of data sources. For more information on this see List models.

Queries can use expressions, values from the session and conversation stash, parameters and can be extended to maximise re-use throughout your Skyve project.

No-code queries

When retrieving data for no-code views and lists, if a query name is not supplied, Skyve will generate a default query which will include all columns for all document attributes.

However, in some cases it is useful to declare the query in the module so that it can be more tightly customised or parameterised for your application. Queries are declared in the module to maximise re-use throughout the application.

As described in Modules, the module.xml file can include definitions of queries used in the application. Queries declared in the module.xml are called metadata queries to distinguish them from other queries which may exist as views on the database server or as unsecured SQL strings within developer code.

Each document can specify a defaultQueryName - which is the name of the metadata query to use by default wherever lists of document instances may be required (e.g. lists and lookups for document references).

Declaring queries

Skyve metadata queries use object references, rather than SQL, and queries can include content columns (for images or attachments) as well as simple data values.

In Skyve, a query is a definition of a projected tabular set of results along with filter criteria - where the expressions are relative to a defined driving document. So Metadata queries must specify the documentName; the name of the driving document.

If the query is the basis of a listGrid, then double-clicking in the listGrid will zoom to the driving document.

Query definition

Query based list

The evaluation engine knows when to inner and outer join based on the requiredness as specified in the document metadata.

To improve performance, Skyve will defer instantiation of domain objects unless necessary - producing a list of mapped objects that implement the given domain interface.

Non-persistent values can be projected (as well as persisted or database values) and the evaluation engine will load the domain objects behind the scenes only if necessary.

However you can also declare queries using bizQL (derived from Hibernate Query Language and SQL.

Query column definition

Query column attributes Description
binding The document value to be shown in the query column.
A compound binding can be used where the value to be shown is in a related document.
displayName An alias for the query column.
If no displayName is specified in the query, the list column title will be the displayName specified for the document attribute.
editable Whether the column is editable in the list view inline.
By default editable is set to true and must be set false if the column binding points to a non-persistent attribute.
escape Whether the value of the column (which might contain HTML, etc) should be escaped
expression A valid HQL expression which defines the value to be shown in the list column.
filterable Whether a filter can be applied to this column in the list view. This must be set false if the column binding points to a non-persistent attribute.
filterExpression A literal value or one of a number of implicit parameter expressions (shown below).
filterOperator The operation to use with the filterExpression (operators shown below).
hidden Whether the query column will be hidden by default in a list view.
Hidden columns are hidden by default, but can be un-hidden by the user unless the column has projected=false.
name You can include calculated or derived values in a query however you must create a non-persistent field in the driving document to hold the value. The name is the name of the non-persistent document field which holds the calculated value.
Note that the name must correspond to a transient (i.e. non persistent) field in the document which describes other aspects of the expression result (such as type, length, display format etc.).
projected Whether the column will exist in the result set.
By default all query columns are projected unless this attribute is false.
sanitise Whether the value of the column (which might contain HTML, etc) should be sanitised.
sortable Whether the query can be sorted by this column in the list view. This must be set false if the column binding points to a non-persistent attribute.
sortOrder The sorting order (ascending or descending) to use by default when this query is displayed.
If the column is sortable, the user will be able to re-sort the list results.

Implicit parameter expressions

Skyve provides a number of implicit parameter expressions to be used for filtering:

  • {CONTACTID} - the id of the contact who is the current user
  • {CUSTOMER} - the name of the customer context in which the current user operates
  • {DATAGROUPID} - the id of the data group of the current user<li>{DATE} - current date
  • {DATE} - current date
  • {DATETIME} - current date and time
  • {USERID} - bizId of the current user
  • {USER} - the userName of current user
  • {USERNAME} - the name of the current user contact

Filter operators

  • equal
  • notEqual
  • greater
  • less
  • greaterEqual
  • lessEqual
  • like
  • notLike
  • notNull
  • isNull
  • nullOrEqual
  • nullOrNotEqual
  • nullOrGreater
  • nullOrLess
  • nullOrGreaterEqual
  • nullOrLessEqual
  • nullOrLike
  • nullOrNotLike

Developers can of course use their own parameters and provide parameter values using the DocumentQuery interface.

Query column definitions

Driving documents can be the subject of many queries.

Content query columns

Skyve also provides a content column type for content items (images and file attachments). The content column type has the following attributes:

Content column attributes Description
alignment either left, right or centre
display either thumbnail or link - displays either a thumbnail of the image, or file type icon, or a download link for the content item
displayName the column name for the column
emptyThumbnailRelativeFile the relative file to display if the content item produces an empty thumbnail
hidden whether the column is hidden by default
pixelHeight the height of the thumbnail in pixels
pixelWidth the width of the thumbnail in pixels
sortOrder The sorting order (ascending or descending) to use by default when this query is displayed.

Thumbnail image in list

Filter parameters

Remember that in many cases, you do not need to declare any query in the module and Skyve will automatically determine a “default” query for a document to drive lists, grids and lookupDescription selection.

You can also then apply filterParameters to listGrid and lookupDescription widgets in views without having to create specific queries for this purpose.

<listGrid continueConversation="true" query="qCoach">
	<filterParameter operator="equal" filterBinding="team" valueBinding="selectedTeam"/>
</listGrid>

filterBinding is the binding expression relative to the driving document of qCoach and valueBinding is the binding expression relative to the bean on which the view is based.

Here, the filterParameter is applied in addition to any filtering declared in the query (or to the default query inferred by Skyve where no query is explicitly declared).

Note that the filterParameter can refer to document attributes (or database columns) that are not included in the projected columns explicitly declared in the query. So, in the above example, the filterBinding team can be used even if the selectedTeam attribute is not one of the columns declared in the query.

Note also that in the above example, project may be an association type attribute.

To filter the listGrid in a view using the bean on which the view is based, use the bizId attribute as the valueBinding. Using the above example, if the listGrid appeared in a view based on a Team document, to filter the list of Coach for those related to the current Team would be as follows:

<listGrid continueConversation="true" query="qCoach">
	<filterParameter operator="equal" filterBinding="team" valueBinding="bizId"/>
</listGrid>

Basic query example

The following query is used to retrieve details of Users.

<query documentName="User" name="qUser">
	<description><![CDATA[Users]]></description>
	<columns>
		<content display="thumbnail" binding="contact.image" escape="false" />
		<column binding="username" />
		<column binding="contact.name" sortOrder="ascending" />
		<column binding="createdDateTime" hidden="true"/>
		<column binding="inactive" />
	</columns>
</query>

The above query is declared for the User document - by convention we suggest naming the query beginning with q - qUser or qUsers etc.

If this query is used as the basis of a module menu item or a listGrid, the resulting list will enable navigation to User records via the double-click/zoom gesture.

The <content> column will show a thumbnail of the user’s contact image, from the thumbnail cache managed automatically by Skyve.

Because the query includes columns with bindings like contact.name, Skyve will generate SQL (via Hibernate) to join the Contact database table.

The column for createdDateTime is hidden, meaning that by default it will not appear in a list based on this query, but in desktop mode, users can opt to include this column at run-time if required.

Basic metadata filtering

To filter the query to only include users that are not inactive, you can change the column declaration as follows:

<column binding="inactive">
	<filterOperator>nullOrEqual</filterOperator>
	<filterExpression>false</filterExpression>
	<projected>false</projected>
</column>

The filterOperator nullOrEqual handles the case that the boolean value may not yet be set.

The filterExpression is the expression used by the filterOperator and in this case is the boolean value false

The projected element may be optionally included so that the column (which because of the filter will always be either null or false) is not included in the projected columns returned by the query - whereas the hidden attribute means that the column is not included in lists by default but can be selected for inclusion by the user at run-time.

Metadata declaration versus other methods

Using Skyve’s metadata approach to declaring queries offers a number of advantages.

  • Metadata queries are non-dialect specific, and so applications can be ported across different SQL implementations.
  • Metadata queries are subject to XML validation as well as Skyve’s domain validation to prevent simple inadvertent transcription and syntax errors.

However, there are circumstances where more direct query methods are convenient to take advatage of more sophisticated filter expressions and even dialect-specific performance or capability advantages.

Including a filter element

You can also include a filter element in the query definition and express your filter directly.

Filtering expressions and bizQL are based on Hibernate Query Language.

Using the filter element can be useful for convenience and is added to the where clause as is, in addition to any implicit filtering performed automatically by Skyve or expressed using other filter operators.

The following queries are equivalent:

<query documentName="User" name="qActiveUser">
	<description><![CDATA[Users]]></description>
	<columns>
		<content display="thumbnail" binding="contact.image" escape="false" />
		<column binding="username" />
		<column binding="contact.name" sortOrder="ascending" />
		<column binding="createdDateTime" hidden="true"/>
		<column binding="inactive">
			<filterOperator>nullOrEqual</filterOperator>
			<filterExpression>false</filterExpression>
			<projected>false</projected>
		</column>
	</columns>
</query>
<query documentName="User" name="qActiveUser">
	<description><![CDATA[Users]]></description>
	<filter>
		<![CDATA[
			bean.inactive is null or bean.inactive = false
		]]>
	</filter>
	<columns>
		<content display="thumbnail" binding="contact.image" escape="false" />
		<column binding="username" />
		<column binding="contact.name" sortOrder="ascending" />
		<column binding="createdDateTime" hidden="true"/>
	</columns>
</query>

Here, the driving document is aliased as bean by Skyve, and so filter expressions must include that alias (hence bean.inactive).

Note that the filter element can be used in combination with other filter operators, for example in combination with the column filterOperator and filterExpression.

For comparison, the following is an equivalent way of retrieving active users using the DocumentQuery interface.

DocumentQuery qActiveUser = CORE.getPersistence().newDocumentQuery(User.MODULE_NAME, User.DOCUMENT_NAME);
qActiveUser.getFilter().addNullOrEquals(User.inactivePropertyName, Boolean.FALSE);

List<User> activeUsers = qActiveUser.beanResults();

However using the DocumentQuery method loads beans, not MapBeans.

Including a from element

You can override the usual query behaviour to include other documents that may or not be directly related to the driving document by including a from element in the query declaration, and aliasing the documents. The documents must be declared using the {module.Document} naming convention.

Whereas the filter element will be in addition to any implicit filtering by Skyve (i.e. added to the implied where clause), the from element replaces the implied from clause of the underlying query.

The following example returns the support tickets associated to the current user (using the {USERID} implicit parameter). The from clause is used to include the associated CustomerAgreement document so that the filter element can check if the current user is a member of the supportUsers collection under the agreement.

<query name="qClosedClientSupportTicket" documentName="SupportTicket">
	<description>Closed Support Tickets</description>
	<from>
		<![CDATA[
			{support.SupportTicket} as bean
			, {crm.CustomerAgreement} as ca
			, {admin.User} as u
		]]>
	</from>
	<filter>
		<![CDATA[
			bean.status in ('closed') 
			and bean.agreement.bizId = ca.bizId
			and u MEMBER OF ca.supportUsers
			and u.bizId = {USERID}
		]]>
	</filter>
	<columns>
		<column binding="ticketNumber" sortOrder="descending"/>
		<column binding="user.contact.email1" editable="false" />
		<column binding="priority" pixelWidth="200"/>
		<column binding="title" />
		<column binding="toDoStatus" editable="false" pixelWidth="150"/>
	</columns>
</query>

In the above example, SupportTicket document in the support module is the driving document and is aliased as bean.

Ignoring a filter if the user has a specific role

This example shows how to filter data that is relevant to the current user, or show all data if the if the user has a specific role.

In this example WeeklyTimesheet has an association to Employee called employee and Employee has an association to User called employeeUser. Normally the query would only show timesheets that belong to the current user, but if the user has been assigned the role admin.SecurityAdministrator then they should have visibility to all user timesheets.

The complexity arises from the fact that the User may have the role assigned directly to the UserRole collection, or because the role is a GroupRole for a Group that the User has.

<query documentName="WeeklyTimesheet" name="qTimesheets">
	<description>Timesheets</description>
	<from><![CDATA[{time.WeeklyTimesheet} as bean]]></from>
	<filter>
		<![CDATA[
		    bean.employee.employeeUser.bizId = {USERID}
		    or EXISTS (select ur 
		    		from {admin.User} as u, {admin.UserRole} as ur
		    		where ur MEMBER OF u.roles
		    		and ur.roleName = 'admin.SecurityAdministrator'
		    		)
			or EXISTS (select gr 
					from {admin.User} as u, {admin.Group} as g, {admin.GroupRole} as gr
					where gr MEMBER OF g.roles
					and gr.roleName = 'admin.SecurityAdministrator'
					and g MEMBER OF u.groups
					and u.bizId = {USERID}
					)
		]]>
	</filter>
	<columns>
		<column binding="weekEndingDate" sortOrder="descending" />
		<column binding="employee.employeeCode" />
	</columns>
</query>

The above query will show:

  • all timesheets where the Timesheet.employee.employeeUser is the current user, OR
  • all timesheets if the user has the UserRole = ‘admin.SecurityAdministrator’, OR
  • all timesheets if the user has the Role ‘admin.SecurityAdministrator’ as one of the roles in a Group that they have been assigned.

Note that the nomenclature {admin.UserRole} reflects the module name and role name as declared in the module XML, and is not the table name in the database.

Using bizQL

In some cases, it may be convenient to specify the query using bizQL directly, rather than XML metadata.

In this example, the query retrieves ContentAudit beans where a specified instanceId is not one of the items in the syncedInstances collection.

<bizQL name="bizqlGetNextUnsychronisedContentAudit">
	<description>Gets all unsynchronised content audits.</description>
	<query>
		<![CDATA[
			SELECT bean
			FROM {admin.ContentAudit} AS bean
			WHERE NOT EXISTS (
				SELECT li
				FROM {fsp.LocalInstance} AS li
				WHERE li IN ELEMENTS(bean.syncedInstances) 
					and li.idinstance = :instanceId
			)
			ORDER BY bean.millis
		]]>
	</query>
</bizQL>

The above query can then be referenced by developers as follows:

BizQL bizQL = CORE.getPersistence().newNamedBizQL(ContentAudit.MODULE_NAME, "bizqlGetNextUnsychronisedContentAudit");
bizQL.putParameter("instanceId", instanceId);
bizQL.setMaxResults(1);

Another alternative approach:

BizQL bql = CORE.getPersistence().newBizQL("Select bean from {myModule.Customer} as bean "
				+ "where bean.systemChampion = :loggedInUser or :loggedInUser MEMBER OF bean.relatedUsers");
		bql.putParameter("loggedInUser", loggedInUser);

		// q.getFilter().addOr(sysChampFilter).addOr(relUserFilter);

		return bql.beanResult();	

Using Session And Stash Values

Developers can use values from the current Bean, Conversation Stash and Session to influence query behaviour.

In this example, the project allows users to “change context” within the application with menu lists etc responding the context setting.

To achieve this, elsewhere in the project, the developer has stored a value for the current ContextProgramId in the session and wants to use this value to filter query results for various list in the menu.

The value is originally set in the session as follows:

CORE.getUser().getAttributes().put("ContextProgramId", <some bizId value>);

NOTE: When storing values in the user session, to reduce performance issues, storing id values rather than entire objects is recommended!

Queries can then use this value for filtering, for example:

 <query documentName="Grant" name="qGrants">
            <description><![CDATA[Grants]]></description>
            <from>
            	<![CDATA[{myModule.Grant} as bean]]>
            </from>
            <filter><![CDATA[bean.program.bizId = {ContextProgramId}]]></filter>
            <columns>
                <column binding="grantNumber" sortOrder="ascending"/>
                <column binding="title"/>
                ...
            </columns>
        </query>

Using SQL

If required, you can also declare queries using SQL (and a specific SQL dialect). In this case the SQL may be dialect-specific and your application may not function when used on other database types.

<sql name="qInspectionReportModel">
	<description>This query backs the FoodAct InspectionReportModel. If this query changes, the
		model class will need to also be updated.</description>
	<query>
		<![CDATA[
			SELECT 'Number of Businesses' as type
			    , sum(case when foodActPriority = 'P1' then 1 else 0 end) as P1 
			    , sum(case when foodActPriority = 'P2' then 1 else 0 end) as P2 
			    , sum(case when foodActPriority = 'P3' then 1 else 0 end) as P3 
			    , sum(case when foodActPriority = 'P4' then 1 else 0 end) as P4 
			    , sum(case when foodActPriority is not null then 1 else 0 end) as Total
				, 1 as ordinal
			FROM FSP_Audit a
			INNER JOIN FSP_AuditType t
			    ON a.auditType_id = t.bizId
			WHERE t.auditTypeName IN ('Vehicle', 'Surveillance', 'Unannounced', 'Initial')
				AND inspectionDate >= :inspectionDateFrom
				AND inspectionDate <= :inspectionDateTo
			
			UNION
			
			SELECT 'Inspections Conducted' as type
			    , sum(case when foodActPriority = 'P1' then 1 else 0 end) as P1 
			    , sum(case when foodActPriority = 'P2' then 1 else 0 end) as P2 
			    , sum(case when foodActPriority = 'P3' then 1 else 0 end) as P3 
			    , sum(case when foodActPriority = 'P4' then 1 else 0 end) as P4 
			    , sum(case when foodActPriority is not null then 1 else 0 end) as Total
				, 2 as ordinal
			FROM FSP_Audit a
			INNER JOIN FSP_AuditType t
			    ON a.auditType_id = t.bizId
			WHERE t.auditTypeName IN ('Vehicle', 'Surveillance', 'Unannounced', 'Initial')
				AND inspectionDate >= :inspectionDateFrom
				AND inspectionDate <= :inspectionDateTo
			
			ORDER BY ordinal
		]]>
	</query>
</sql>

In this example, the query depends on parameters inspectionDateFrom and inspectionDateTo, supplied when the query is used as follows:

SQL sql = CORE.getPersistence().newNamedSQL(FoodAct.MODULE_NAME, SQL_QUERY_NAME);
sql.putParameter("inspectionDateFrom", dateFrom);
sql.putParameter("inspectionDateTo", dateTo);

Using MEMBER OF

In this example, Provider has a collection of ServiceType called servicesProvided.

The query below filters JobRequest items by serviceType, so that any of the service types that are in the Provider’s collection are included, filtered for Providers where the providerUser is the current user.

<query documentName="JobRequest" name="qAvailableJobRequests">
<description><![CDATA[Show only Job Requests that match the Services provider by this Provider.]]></description>
	<from>
        <![CDATA[
           {tradie.JobRequest} as bean, {tradie.Provider} as p 
        ]]>
     </from>
     <filter>
		<![CDATA[
			bean.serviceType MEMBER OF p.servicesProvided 
            and p.providerUser.bizId = {USERID}
		]]>
	</filter>
	<columns>
		<column binding="urgency" sortOrder="ascending" />
		<column binding="address" />
		<column binding="description" />
		<column binding="serviceType" />
	</columns>
</query>

The following example return users with the selfRegistrationGroup

<query documentName="User" name="qPendingApprovalUsers">
	<description>Users not yet approved</description>
	<from>
		<![CDATA[
			{admin.User} as bean, {admin.Group} as g, {admin.Configuration} as config
		]]>
	</from>
	<filter>
		<![CDATA[
			g MEMBER OF bean.groups
			and config.userSelfRegistrationGroup.bizId = g.bizId
		]]>
	</filter>
	<columns>
		<column binding="bizKey" />
		<column binding="createdDateTime" />
	</columns>
</query>		

Other examples

This example filters the list of People for those who do not have any Schedule in the currentTerm, where currentTerm belongs to the General document.

<filter>
	<![CDATA[
	bean not in (select schedule.person 
					from {swimSchool.Schedule} as schedule
					, {swimSchool.SwimClass} as swimClass
					, {swimSchool.General} as general
					where swimClass = schedule.parent
					and swimClass.term = general.currentTerm 
					)
	]]>
</filter>

In this example, Agreement has a child collection of Task.

The filter element below filters for Agreement_s that are current (_endDate is null or greater than or equal to the current date), and where there are active Task_s (_inactive is null or equals false) that are members of the child collection (parent = bean).

<filter>
	<![CDATA[
	(bean.endDate is null or bean.endDate >= {DATE})
	and exists (select bizId 
					from {business.Task} as task
					where (task.inactive is null or task.inactive=false)
					and task.parent = bean
					) 				
	]]>
</filter>	

Users with a role

This example shows how to retrieve all users with a specific role in the application. (Users may have a Role assigned directly, or by being assigned a Group which has the Role.)

<query documentName="User" name="qUserWithRole">
	<description>Users with Basic User role</description>
	<from>
		<![CDATA[
			{admin.User} as bean
		]]>
	</from>
	 <filter>
    	<![CDATA[
    		bean.bizId in (select ur.parent.bizId 
    				from {admin.UserRole} as ur 
					where ur.roleName = 'admin.BasicUser')
			or bean.bizId in (select u.bizId
					from {admin.User} as u, {admin.Group} as g, {admin.GroupRole} as r
					where r.roleName = 'admin.BasicUser'
    				AND g MEMBER OF u.groups
					AND r.parent = g.bizId)
		]]>
	</filter>
	<columns>
		<column binding="bizKey" sortOrder="ascending" />
	</columns>
</query>         

Debugging

Where direct expressions like bizQL or SQL are used for queries, Skyve offers trace options to assist developers debug their application queries.

The project .json file includes tracing options for query and sql.

// Trace settings
"trace": {
	// XML metadata parsing
	"xml": false,
	// HTTP request attributes and parameters
	"http": false,
	// queries performed
	"query": true,
	// MVC command debug
	"command": false,
	// JSF inner workings
	"faces": false,
	// generated SQL statements
	"sql": true,
	// content manipulation
	"content": false,
	// application security denials
	"security": false,
	// bizlet callbacks
	"bizlet": false,
	// mutations in domain objects
	"dirty": false
},

query tracing will log the HQL generated by Skyve to the server log or developer console, for example:

SELECT count(bean.bizId) as bizId, min(bean.bizFlagComment) as bizFlagComment FROM adminUser as bean 

sql tracing will log the SQL generated by Hibernate to the server log or developer console, for example:

select count(adminuser0_.bizId) as col_0_0_, min(adminuser0_.bizFlagComment) as col_1_0_ from apps.ADM_SecurityUser adminuser0_

If query tracing was not specified at deploy-time, a user with DevOps role can turn it on at run-time via the deskop mode. This will turn on both query and sql tracing.

Query logging


Next Inheritance
Previous Views