SDM query language

CloudBees SDM is a preview, with early access for select preview members. Product features and documentation are frequently updated. If you find an issue or have a suggestion, please contact CloudBees Support. Learn more about the preview program.

The SDM query language (SDM QL) is used to define queries and rules used in reports and policies. SDM QL uses a limited, simplified structure that pulls information defined in data types.

Query syntax

The same expression syntax is for both the target filter and criteria. The most basic expression is a comparison of the following form:


On the left-hand side, the <PATH> is made up of field names, joined with periods. The path starts at the selected data type. For example, if the type is GithubPullRequest, then the path could be state or author.login. The path can also traverse relationships to other data types, for example:, where the . indicates that name is a child value of repository.

On the right-hand side, <VALUE> may be a string in single (') or double quotes ("), an integer or floating-point number, a boolean (true or false), or null. The type of the value should match the type of the field at the end of the path. For fields of type DateTime, the value may be a string that represents a time, encoded as per '2007-12-03T10:15:30'. For fields of type Timestamp, you need to compare against an integer. The value can also use the function now() that represents the point in time at which the policy is evaluated. The function takes an optional string argument that represents a delta from the current time in days and/or hours, for example, now('-1 day 6 hours') or now('+1 hour').

In the center, the <OPERATOR> compares the field at the end of the path with the given value. For a numeric or date/time field, the valid operators are <, , =, !=, >, or >=. For string fields, the ~ operator can be used to compare against a POSIX regular expression, for example, title ~ '.WIP.'. For boolean fields and comparison with null, only the = and != operators are valid.


In a comparison, the path must only include single-valued fields. For list fields, use the size(), average(), any(), or none() functions.

  • The size() function returns the number of items in the list. The size can then be used for a numeric comparison, for example, reviewRequests.size() > 0.

  • The average() function takes the name of a numeric field as an argument and returns the average of the values for that field in the list. The average can then be used for numeric comparison, for example, builds.average(duration) > 20.

  • The any() function evaluates to true if any of the items in the list match the expression passed as an argument to the function, for example, reviewRequests.any(requestedReviewer.login = 'xxx'). The <PATH> expressions in the any argument are evaluated relative to the items in the list

  • The none() function is the inverse of any(): it evaluates to true if none of the items in the list match the expression.

Lists and relationships to other types

For lists that represent relationships to other types, the orderBy(), first(), and last() functions may also be used.

  • The orderBy() function takes one or more arguments, separated by commas, with each being the name of a field used to order the list. The default order is ascending. The field name can be wrapped by the desc() function to sort in descending order. For example, pullRequestReviews.orderBy(author.login, desc(submittedAt)) would sort the pull request reviews firstly by author login in ascending alphabetical order and then, for each login, in descending order of submission time, i.e., with the most recent submission first.

  • The first() function returns the first value in a list.

  • The last() function returns the last value in a list.

After a call to orderBy(), the first() and last() functions can be used to retrieve the first and last item from the ordered list respectively. For example, pullRequestReviews.orderBy(submittedAt).last().state = 'APPROVED' checks that the state of the last submitted pull request review is APPROVED.

The slice() function can also be used after orderBy() to return a subset of a list. slice() takes one or two numeric arguments that represent zero-based indexes:

  • slice(5) will return a new list from the start of the original list (index 0) up to, but excluding, the entity at index 5, i.e. the first five entities in the list.

  • slice(2, 5) will return a list inclusive of the first argument and exclusive of the second argument, i.e. the three entities at index positions 2, 3, and 4.

The example orders the pullRequestReviews by the submittedAt field and returns a subset of the list containing the first four entities before checking if any has a state of 'APPROVED'.

pullRequestReviews.orderBy(submittedAt).slice(4).any(state = 'APPROVED')

You can use the contains() function for lists of scalar types. Unlike the functions mentioned above, the argument to contains() must be a simple value, not an expression.

Syntax errors

The following will cause an error:

  • If none or more than two arguments are provided

  • The first argument is greater than the second

  • Using negative arguments

  • Using more than one slice() statement

  • Using slice() before orderBy() or using slice() after first()/last()

Logical operators

The logical operators not, and, and or can be used to combine boolean expressions (in order of precedence), with parentheses used to override the default order of precedence. For example:

state = 'OPEN' and (changedFiles > 10 or not author.login = 'xxx')

The example below provides a more complex expression that selects any repository associated with the ACME Rockets product that has any items with a label of wip or do not merge. The labels are case sensitive.

repository.products.any(name='ACME Rockets') and state = 'OPEN' and labels.none(name = 'wip' or name = 'do not merge')