Peter Bell

5 minute read

My name is Peter Bell. I have been a software developer and tester for the past 16 years for various Seattle area companies. I enjoy cycling, rooting for the Seattle Mariners (not easy), and good beer. My passion for API design and functional programming led me to develop GlideQuery, which we initially used in the ITAM (IT Asset Management) team at ServiceNow. We have expanded its use, both within ServiceNow and to our customers. I gave a talk at K20 last year, which I recommend viewing.

I designed GlideQuery to prevent common pain points our team has experienced during development. For example, we had a performance bug using GlideRecord because an addQuery call had a misspelled field, causing it to query far more records than intended. If we had GlideQuery back then, we could have caught that bug before our customers did!

This is the first in a series of blog posts showing practical examples and patterns using GlideQuery. I believe one of the quickest ways to learn is by diving straight into examples. We’ll start with simple read queries and move on to more advanced topics with each post. Let’s begin!

GlideQuery

GlideQuery is a modern, expressive, and safer interface to the Now platform. You use it where you might typically use server-side GlideRecord. As a wrapper over GlideRecord, it can catch mistakes earlier in the development process and simplify many queries. You can find the documentation for GlideQuery.

GlideQuery has adopted the following three design principles:

Fail fast: Catch mistakes before they become bugs Native JavaScript: Welcome JS developers to the Now platform with an inviting API Be expressive: Do more with less code

These three principles should become clear as you use and learn the API better.

Reading Multiple Rows

new GlideQuery('task')
    .where('priority', 1)
    .select('short_description', 'opened_at')
    .forEach(function (task) {
        gs.info('Task "' + task.short_description + '" was opened at ' + task.opened_at);
   });
// Task "Reset my password" was opened at 2020-09-17 22:50:23
// Task "Install new Cisco" was opened at 2020-03-29 23:14:14
// ...

You’ll notice that instead of using a while loop, as you would with GlideRecord, we use the forEach function and pass in a function to be called on each record. forEach is useful when you want to perform operations like logging or other actions using each record in a query. Another thing to note is that the record task passed to the function is not a GlideRecord, but just a regular JavaScript object. In this case, the object contains the keys short_description, opened_at, and sys_id (the primary key is always included).

One advantage of using GlideQuery here is that it performs a set of validation checks before executing the query. For example, if the priority, short_description, or opened_at fields didn’t exist, GlideQuery would throw an error message (with a readable stack trace!) explaining the problem.

Using select and Stream

The select method is used when reading multiple rows by returning a Stream which allows the reading of multiple rows as if they were a single collection. GlideQuery’s Stream is similar to JavaScript Array in this case, and they contain several of the same methods. For example, just as you can use forEach on a JavaScript array like this:

var users = [{ first_name: 'Bob' }, { first_name: 'Sue' }];
users.forEach(function (user) {
    gs.info(user.first_name);
});

b& you can also use forEach on a GlideQuery Stream:


new GlideQuery('sys_user')
    .select('first_name')
    .forEach(function (user) {
       gs.info(user.first_name);
   });
// Jimmie
// Melinda
// ...

Each object within a Stream is a regular JavaScript object containing only the fields requested as a parameter (first_name in the example above) when calling select. The primary key of a table (usually sys_id) is also returned whether you asked for it or not:

new GlideQuery('sys_user')
    .select()
    .forEach(function (user) {
       gs.info(user.sys_id);
   });
// 005d500b536073005e0addeeff7b12f4
// 02826bf03710200044e0bfc8bcbe5d3f

Reading a Single Record

It’s common to want a single record instead of multiple, and for this, we turn to selectOne.

new GlideQuery('sys_user')
    .where('last_name', 'Luddy')
    .selectOne('first_name')
    .ifPresent(function (user) {
       gs.info(user.first_name);
   });
// Fred

Using selectOne, we’re telling GlideQuery we only want it to return a single record (if it exists). But what if no record is found that matches a given query? While select returns a Stream, selectOne returns an Optional. An Optional is a container that may contain the single record requested (or may not.). When no record can be found that matches a given query, selectOne returns an empty Optional; otherwise, it returns an Optional containing the first record matching the query. selectOne will always return an Optional, regardless of whether a record was found.

Using selectOne and Optional

Optionals have several methods that are useful for handling the scenario where a record may or may not exist. Some examples:

// Find user if they exist, otherwise return user with first_name 'Nobody'
var user = new GlideQuery('sys_user')
    .where('last_name', 'Luddy')
    .selectOne('first_name')
    .orElse({ first_name: 'Nobody' });

// Find user, otherwise throw an Error that the user couldn't be found
var user = new GlideQuery('sys_user')
    .where('last_name', 'Luddy')
    .selectOne('first_name')
    .get(); // this method can throw if no record is found

// Does a given user exist? Assign boolean to userExists
var userExists = new GlideQuery('sys_user')
   .where('last_name', 'Luddy')
    .selectOne('first_name')
    .isPresent();

There are more methods available (see documentation), but this shows some idiomatic ways of reading a single record. Additionally, selectOne has a performance advantage over select because behind the scenes, GlideQuery will use GlideRecord’s setLimit not to process any more records than necessary.

Method Chaining

GlideQuery has a fluent interface, where many of the methods return a new GlideQuery object. You may notice the above examples used the where method, which behaves similarly to GlideRecord’s addQuery. The difference, however, is that where returns a new GlideQuery object, allowing you to chain calls together.

Scoped Apps

One final important sidenote: GlideQuery is a script include (global scope), therefore when used within a scoped app, it (as well as Stream and Optional) must be prefixed with the “global” scope. For example:

new global.GlideQuery('sys_user')
// ...

Conclusion

GlideQuery is designed to catch mistakes early in the development cycle and behave more like a regular JavaScript API. GlideQuery has a fluent interface, which requires you to chain your method calls together.

Select: return a stream of multiple rows selectOne: return an Optional containing one record if found

If you have any questions or comments, please share them below!


Comments