CipherStash
CipherStash Documentation

Querying Records

Having a collection full of records isn’t much use unless you can retrieve the data when you need it. For this section of the tutorial, we’ll introduce our “examples” codebase and look at some of those examples to show you how to perform queries against a collection.

The StashJS Examples

We’ve collected a bunch of common code snippets into the StashJS examples git repository. These provide examples of how to do most common operations with CipherStash, using StashJS, our TypeScript/JavaScript client library. Most of these things you can also do using the stash CLI.

As querying is a complex operation, we’ve put several examples of how to do that in the stashjs-examples repo. We’ll download that repo, build the examples, and then run the querying examples so you can get a feel for how they work.

Downloading the stashjs-examples repo

The easiest way to get the stashjs-examples repo is to clone it using git:

git clone https://github.com/cipherstash/stashjs-examples

This shouldn’t take very long, as the repository isn’t very large.

From there, it should just be a matter of installing the dependencies, and then running the TypeScript compiler:

npm install
npm run build

Running the StashJS examples

All of the pure-JavaScript example code gets written into the dist/ subdirectory, with a .js extension. To run the simplest query example (an exact match on title), you can run the following command:

node dist/query-exact.js

This should output a single record from the movies collection, for the 2008 indie film “Lifelines” (originally titled “Wherever You Are”). All the other examples are run the same way, just change the filename.

Querying Fundamentals

If you examine the various querying examples (query-exact.ts, query-range.ts, query-match.ts, query-count.ts), you’ll notice that most of the code is TypeScript boilerplate. All that really changes in each script is the exact way that movies.query() is called.

In each case, the first argument is an inline function that describes the index and operator to apply to each record. If that function returns a match, then the record will be included in the result. For example:

movies.query(movie => movie.exactTitle.eq("Lifelines"))

Since all querying in CipherStash is against an index, not against a record field, we need to specify the name of a previously-defined index (in this case, exactTitle) to query. Valid operators depend on the index type. The available index types, and what operators are valid for each type, are listed in the reference documentation.

For an exact index, you can only use an eq (”exact equality”) operator, and is typically used on boolean and string fields. If you want to search for any string that matches another, you’ll want the match index type. Numeric types are best indexed with the range index type, which supports both equality and relational operators (such as less than, greater than, etc).

Examples of how to use each of these index types are provided in the examples repository.

In the snippet above, an array of matching records will be in the documents key of the object returned by movies.query().

Ordering, Limits and Aggregates

The second argument to stash.query() allows you to specify keyword arguments that specify ordering, limits, and aggregation operations. A simple example, to find the 10 oldest movies whose title matches the string “Life”, looks like this:

movies.query(
  movie => movie.title.match("Life"), {
    limit: 10,
    order: [{byIndex: "year", direction: "ASC"}]
  }
)

The limit option takes a positive integer which specifies the maximum number of records to return. If you don’t specify any ordering, any record(s) that match may end up being returned.

Sorting results is controlled by the order option, which takes a list of ordering specifiers. These are simply objects containing byIndex and direction keywords. As the name suggests, ordering in CipherStash is controlled by indexes; any range index can be used for sorting results, in either "ASC" (ascending, ie “smallest value first”) or "DESC" (descending, ie “largest value first”) order.

Aggregation -- the process of combining records together -- is specified with the aggregation option. At present, the only available aggregation operation is count, and can be applied to any index like this:

movies.query(
  movie => movie.title.match("Life"), {
    aggregation: [{ofIndex: "runningTime", aggregate: "count"}]
  }
)

Aggregation results are returned in the aggregates key of the object returned by movies.query(), like this:

{
  documents: [...],
  aggregates: [
    { name: "count", value: 1234n }
  ]
}

Since when you’re aggregating you often don’t want the actual records themselves to be returned, you can specify the skipResults: true option, which will speed up the query and save sending back the record data that you won’t care about anyway.

Go Wild

Now you’ve been introduced to all the nuances of querying, feel free to edit the examples and do some querying of your own.

FIN!

This is the end of our “Getting Started” tutorial. It’s time to start using CipherStash to build your first secured application. If you’d like some help, we encourage you to sign up to our public discussion forum and post your questions.