Query reference
Plato queries read records: pick a model, filter and order it, and select the fields and relations you want back. A query never changes anything; changes go through the actions your schema declares.
Start from your schema
A query names the models, fields and relations of your database's schema, exactly as the schema spells them: names are case-sensitive.
The examples on this page query this schema:
model Customer {
fields {
name String
}
}
model Invoice {
fields {
number String
status String
customer Customer? @relation("invoices")
}
}
model Item {
fields {
description String
amount Int
invoice Invoice @relation("items")
}
}
model CreditNote extends Invoice {
fields {
reason String
}
}Select records and fields
Name a model, then the fields to return in braces. A model can be queried directly; no view needs declaring first.
Invoice { number status }The result is a list of rows. Every row, and every record nested in one, carries its id whether or not you select it, so you can name the record in a later action.
Filter and order
@where(…) keeps the records its condition holds for, and @orderBy(field) sorts them, ascending unless you add : desc.
Invoice @where(status == "Open") @orderBy(number: desc) {
number
status
}Conditions compare with ==, !=, <, <=, > and >=, and combine with && and ||. Strings are quoted; numbers, true, false and null are not. A condition can follow a reference to a field of the record it points at:
Invoice @where(customer.name == "Acme" && status != "Paid") {
number
}Follow relations
Braces after a reference or a relation select from the records it leads to: one record for a reference, a list for a relation.
Invoice @orderBy(number) {
number
customer? { name }
items { description amount }
}A relation with no records reads as an empty list, and never drops its row. A referenced record you cannot read comes back as its id and title alone.
Empty references: ? and !
customer is optional in the schema, so an invoice may have none. A query must say what it wants then, or it is refused with a message naming both choices. The ? above includes the invoice, with customer as null. A ! leaves it out, returning only the invoices that have a customer:
Invoice @orderBy(number) {
number
customer! { name }
}- A selected reference needs
?or!when the schema lets it be empty, or when it carries its own@where, which empties it when the record it points at does not match; a@wherefilters the record, so it goes on a braced reference. A reference the schema requires needs neither marker, though it may take either. - This holds for a bare reference (
customer!, its id), a braced one, and a document field. A bare reference is empty only when it holds no id; a braced one also when the record it names no longer exists. - A reference you cannot read is not empty: under
!its row is kept, with the reference'sidand title. - Emptiness passes outward only through
!: a reference marked!whose own reference marked!is empty is empty in turn. - A relation to many records, a value, and an aggregate take no marker and are never left out for being empty.
@first(n)counts only the rows returned, so a page is not cut short by the rows left out.- A view declared in a schema is checked the same way when the schema is installed, so a change that makes a field it selects optional is refused until the view says which it means. A refusal names every selection to fix at once.
Subtypes
Braces after a subtype's name select its own fields, on a record of a model it extends. A record may or may not be of that type, so a subtype selection is optional like a reference and takes ? or !. With !, only credit notes are returned; with ?, every invoice, with CreditNote as null for the others:
Invoice @orderBy(number) {
number
CreditNote! { reason }
}- A
@whereon a subtype selection filters the subtype record:CreditNote! @where(…)keeps only the credit notes it admits, and with?the selection isnullfor the others. With a@where, even a selection of the model being read or one it extends needs a marker. - Braces after a model's name select only a subtype of the model being read, or that model or one it extends, which need no marker. Any other model, and any model selected on a derived model, is refused: query it directly.
- Whoever can read a record can read every type it is, so a subtype selection decides only on records you can read. Under a reference you cannot read it never leaves a row out: the reference comes back as its
idand title.
Aliases and aggregates
name: field returns a field under another name. An aggregate folds a relation into one value per row: @count, @sum(field), @avg(field), @min(field) and @max(field). A @where before the aggregate folds only the records it keeps.
Invoice @orderBy(number) {
invoiceNumber: number
itemCount: items @count
total: items @sum(amount)
large: items @where(amount >= 1000) @count
}Page through long results
@first(n) returns at most n rows, with a pageInfo beside them:
Invoice @orderBy(number) @first(2) { number }When pageInfo.hasNextPage is true, repeat the same query with @after and the pageInfo.endCursor it returned to read the next page:
Invoice @orderBy(number) @first(2) @after("<endCursor>") { number }A cursor is refused by a query of another model or order than the one that returned it. Only the top level of a query pages; nested relations do not.
Limits
A query Plato runs on its server reads within a fixed budget. A query that needs more is refused whole, with a message naming the limit it passed; it never returns part of a result.
- At most 100,000 stored entries and 16,000,000 characters of stored data per query, counted beneath sorting, joins and nested selections.
- A query that selects only aggregates, such as
n: items @count, may read up to 1,000,000 entries within the same characters.
Coming under the limit
Add a @where filter, page with @first(n), or select fewer fields and nested relations. @orderBy sorts every matching row before the first page is cut, so paging a sorted query does not bring it under the limit: narrow it with a filter instead.
Errors
A query that cannot run is refused with a message saying what went wrong. It never returns an empty result in its place.
- The text does not parse. Plato queries are not SQL; check the syntax above.
- A name is unknown, or types do not match. The query names a model or field the schema lacks, or compares values of different types. Check the names against the schema.
- The query read too much. See Limits; the same query will be refused again.