Query Scripting
Learn about querying for models with chainable filters.
Stacklane uses method chaining to define the criteria of returned query results.
In general with method chaining order matters — for example, field filters must be defined
before calling limit
.
The results of a query are generally "used" outside of the script itself. For example, a Mustache endpoint may iterate over a query given by a supplier to display results as HTML.
Import
import {Article, Product} from '📦';
All Results
To query all models of a type, without any filters, use the all()
method. For example, Article.all()
Results will be returned in the model's natural ordering.
Field Filters
Queries besides all()
start with a field filter.
Multiple field filters may be chained together.
Keep in mind that field filters are effectively and conditions.
eq
Article.title('theTitle')
gt
Article.created_gt(new Date(2000))
gte
Product.price_gte(30)
lt
Article.created_gt(new Date(2000)).created_lt(new Date(2010))
lte
Product.price_gte(30).price_lte(40)
Ordering and Limits
Field filters are typically used to return a small number of specific results. By default they return unordered results. Unordered results are limited to 100 results by default, which may be explicitly raised to 500 results.
Methods
The following methods influence the query results.
asc()/desc()
All model types have a natural ordering.
It is recommended to minimize the use of asc()
and desc()
and to typically rely on the default ordering of various query types.
filter(function)
The callback function to filter
returns a boolean
that indicates whether the item should be included in the results (return false to exclude an entry).
This should only be used if another field filter (eq, gt, gte, lt, lte) is not sufficient.
limit(number)
Limits the results of a query.
Modifiers
Modifiers transform the original results.
map(function)
The callback function to map
transforms the current stream element
into a map or other value. This is often used to transform a model to a JSON object literal.
Article.all().map(article=>({title:article.title}));
flatMap(function)
The callback function to flatMap
transforms the current stream
element into another stream.
Return a single value, array, or other stream from the callback.
The results will be flattened into the resulting stream.
For example, given an options[]
field, get only distinct options:
Product.all().flatMap(product=>product.options).distinct();
insert(function)
Using the results from either map
or flatMap
, use insert
to grow or inflate those results.
The insert method transforms the modified original results by optionally inserting new elements of the same format.
It may also take otherwise empty results and fill them.
To ensure predictable results, keep in mind the default ordering of various query types.
insert
must only be used aftermap
, and the results inserted must be the same type of value as were originally returned frommap
. at the beginning, between two elements (previous and next), or at the very end.This method is particularly useful for inserting points along sparsely populated time series data, in conjunction with
Dates
to help generate the missing points.The callback function uses a single parameter as an "iterator". The iterator exposes the optional/nullable fields
next
andprevious
, which will be null in specific cases. Those 3 cases are exposed as booleans fieldsempty
(next/previous both null),first
(previous is null),last
(next is null).Returned values from the callback function are inserted into the results. Returnable values are null, a single value, arrays, or other streams.
Keep in mind that returned results must be the same type of value as were originally returned from
map
(on the source). In other words, if the originalmap
operation returned results like{date: value.created}
then values being returned from theinsert
callback but also be in the format{date: /* value */}
.
distinct()
Using the results from map
, flatMap
, or insert
,
creates new results which contain only unique values.
let distinctTitleCount = Article.all()
.map(article=>({title:article.title}))
.distinct()
.count();
Execution
Once a query is built up with various methods, the query results are executed and used by including the query in JSON output, or Mustache HTML output. The following methods also execute and use the results from a query:
count()
Return the total number of results, after considering all other methods, such as limit
.
get()
Returns a single result (effectively limit(1)
).
If there is no result, then a $ModelNotFound
exception is thrown, similar to loading a model by its ID.
optional()
Behaves similarly to get()
, but returns null
instead of throwing $ModelNotFound
.
exists()
Returns true
if there is at least 1 result for the query.
sum(field)
Must be called before a modifier.
Returns the sum of a field (integer
or double
).
Product.all().sum(Product.price);
The sum will be zero if there are no results (results are empty).
avg(field)
Must be called before a modifier.
Product.all().avg(Product.rating);
avg
may return null if there are no results to average (results are empty).
modify(function)
The callback function to modify
receives a Model instance as its parameter, and does not expect any return value.
This should only be used to update fields, or remove()
models in bulk.
It is only available during POST
, PUT
, DELETE
, and is limited to the quota on batch size for the request.
It is not required that every Model be modified, for example if it doesn't satisfy some condition. However consider using
filter(...)
in the case where there are well defined conditions that must be met before updating a model. This will also keep the modify function simpler.
Contained Models
Querying a model contained by a parent model is performed in much the same way as any other model query. The main difference is that these queries must first specify a parent selector.
Specific Parent
To query for models within a specific container, use the method by its lowercase name.
Given a container named List
and its contained model named Note
,
then a query for every Note
in a specific List
would be:
Note.list(theListVar)
Any Parent
To query contained models across all of its containers, use the any
prefix, followed by the name of the parent type:
Note.anyList()
Additional Filters
After a parent container selector, any other field filters and query methods may be specified as usual.
Grouping
When querying across any parent, it's also possible to group by each distinct parent.
List
.grouping(Note.anyList().status('active'))
.map(group=>({
listName: group.list.name,
noteNames: group.note.map(note=>note.name)
}))
For each parent group, the children returned will be limited.
By default each parent is limited to 100 child results.
This may be decreased using the limit(n)
on the child's query.
Embedded Models
Queries for embedded models are similar to equality queries.
The following assumes Article
has a field named metadata
.
The metadata
field is an embedded model name ArticleMetadata
with the field title
.
Article.metadata({title: 'The Title'}).get();
This will return an Article
where Article.metadata.title == 'The Title'
,
regardless of whether ArticleMetadata
has other defined fields.
Queries for values in an embedded model list are identical.
Unique Value Queries
Query unique fields and
UID fields
as you would any other type of equality query.
Assuming a model named Article
and a UID field named "slug":
let slugId = '....';
let found = Article.slug(slugId).get();
// Because of error handling, 'found' is always defined at this point
Mustache Use
The "all" query on a model type is available directly to Mustache.
/index.html
<!--TEMPLATE mustache-->
{{% import {List} from '📦' }}
{{#List.all as list}}
{{list.title}}
{{/List.all}}
Simple contained model queries are also available directly to Mustache:
/index.html
<!--TEMPLATE mustache-->
{{% import {Note} from '📦' }}
{{% import {list} from '🔗' }}
{{#Note.list list as note}}
{{note.title}}
{{/Note.list}}
Any other queries must be defined and built within a supplier before importing them into Mustache.