Enhancing Jira with PocketKnife QueryDSL: Solving Complex SQL Queries Beyond ActiveObjects
com.atlassian.confluence.content.render.xhtml.migration.exceptions.UnknownMacroMigrationException: The macro 'html' is unknown.

Enhancing Jira with PocketKnife QueryDSL: Solving Complex SQL Queries Beyond ActiveObjects

Introduction

As Atlassian App developers, we are well-versed in using ActiveObjects, which is the go-to ORM for handling most database operations in Jira plugin development. ActiveObjects makes it easy to perform basic CRUD operations, create tables etc. However, when it comes to more advanced querying, such as aggregating data with groupBy or performing complex SQL operations like joining multiple tables, ActiveObjects falls short. For example, if you want to retrieve a specific count of grouped results, ActiveObjects doesn’t offer the flexibility needed, often returning only the count of the first row or limiting you to entity-specific operations.

To overcome these limitations, Atlassian provides a powerful library called PocketKnife QueryDSL. This library enables us to perform complex queries that ActiveObjects can't handle, such as joining multiple tables, aggregating data, and retrieving specific column counts with groupBy. PocketKnife QueryDSL offers type-safe queries and provides greater control and flexibility over how data is retrieved and manipulated in Jira, opening up possibilities for more sophisticated database operations. Below is step by step tutorial of how to integrate PocketKnife QueryDSL to your plugin -


Integration Steps

To integrate Atlassian PocketKnife QueryDSL into your Maven project, follow these steps:

  1. Add Maven Dependency

    Include the following dependency in your pom.xml:

<dependency> <groupId>com.atlassian.pocketknife</groupId> <artifactId>atlassian-pocketknife-querydsl</artifactId> <version>5.2.1</version> </dependency>
  1. OSGi Integration

    Include the following import package:

<Import-Package> <!-- exclude pocketknife bits we don't need --> !com.infradna.tool*, !net.sf.cglib.proxy, !org.jvnet.hudson.annotation_indexer, <!-- import (alltheotherthings) --> *;resolution='optional' </Import-Package>

The line *;resolution='optional' imports all other packages, which may include unnecessary components, potentially increasing build times and introducing unused dependencies. For optimal performance and maintainability, we recommend explicitly specifying only the packages your plugin requires rather than using a wildcard. This approach ensures your code remains efficient and minimizes dependency issues.

  1. Add Scanned Dependencies

    Ensure that the scanned dependencies include:

<dependency> <groupId>com.atlassian.pocketknife</groupId> <artifactId>atlassian-pocketknife-querydsl</artifactId> </dependency> //dependency management <dependencyManagement> <dependencies> <dependency> <groupId>com.atlassian.jira</groupId> <artifactId>jira-project</artifactId> <version>${jira.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> //jira version <jira.version>9.17.3</jira.version>

Now you are all set to use PocketKnife QueryDSL in your project!


Practical Examples

Example 1: Running a Simple GROUP BY Query with Count

Problem Statement: Retrieve the URLs and their respective counts from the TICKETS_DB table, where the request-raised field is set to true, within a specific date range.

image-20241106-094718.png

Query-

SELECT "URL",count("URL") FROM "AO_8FF32E_TICKETS_DB" WHERE "REQUEST_RAISED" = true AND "TYPE" = 'TICKET' AND "CREATED" BETWEEN '2024-08-11' AND '2024-09-23' group by "URL";

Query result-

image-20241106-100848.png

To execute a simple GROUP BY query that returns counts, implement the following code:

//imports import com.atlassian.pocketknife.api.querydsl.DatabaseAccessor; import com.querydsl.core.Tuple; import com.querydsl.core.types.Expression; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.sql.SQLQuery;
//Define you variables Expression<Long> countExpression Expression<String> urlExpression BooleanExpression commonCondition BooleanExpression dateRangeCondition
SQLQuery<Tuple> query = databaseConnection .select(urlExpression, countExpression) .from(Expressions.template(String.class, tableName)) .where(commonCondition.and(dateRangeCondition)) .groupBy(urlExpression);
image-20240926-121556.png

Example 2: Running a Join Query

Problem Statement: Retrieve search queries and their respective counts from the SEARCH_DB table, based on the session_state field in the SETTINGS_DB table, for a specified date range.

image-20241106-095317.png

Query-

SELECT "AO_8FF32E_SEARCH_DB"."QUERY", COUNT("AO_8FF32E_SEARCH_DB"."QUERY") ,"AO_8FF32E_SETTING_DB"."SESSION_STATE" FROM "AO_8FF32E_SEARCH_DB" JOIN "AO_8FF32E_SETTING_DB" ON "AO_8FF32E_SEARCH_DB"."TRACKER_DB_ID" = "AO_8FF32E_SETTING_DB"."ID" WHERE public."AO_8FF32E_SEARCH_DB"."CREATED" BETWEEN '2024-09-12' AND '2024-09-19' GROUP BY "AO_8FF32E_SEARCH_DB"."QUERY", "AO_8FF32E_SETTING_DB"."SESSION_STATE";

Query results-

image-20241106-100529.png

To execute a join query, use the following code:

SQLQuery<Tuple> query = databaseConnection .select(queryColumn, countQuery, sessionStateColumn) .from(fromJoinTableName) .on(Expressions.booleanTemplate(joinCondition1).eq(Expressions.booleanTemplate(joinCondition2))) .where(dateRangeCondition) .groupBy(queryColumn, sessionStateColumn)
image-20240926-122201.png

Testing and Debugging

For testing and debugging purposes, you can refer to our blog post: “How to start Atlassian applications in standalone debug mode?” for comprehensive guidelines.

Conclusion

PocketKnife QueryDSL unlocks advanced querying capabilities in Jira plugin development, overcoming the limitations of ActiveObjects. By following this guide, you can integrate complex queries, including grouped counts and joins, into your projects with ease. This powerful tool enhances data handling flexibility and efficiency, enabling more sophisticated and scalable database operations within Jira.


References:

We have tried and tested this on Jira versions 9.12.13 and 9.17.3. Please reach out to us if this no longer works using the Contact Us section; we may have a better solution by then.