CQL, the Card Query Language, is a syntax for finding Decko cards. There's a brief introduction on the CQL card that explains why and where to use it. This card is for folks who want to know how to write CQL.
A CQL statement defines a list of cards by their properties, their relationships to other cards, or both.
CQL is employed both by Sharks (in JSON) and by Monkeys (in Ruby).
Monkeys should have no problem translating the examples from JSON to Ruby, because CQL's structure is the same in both. For example, in Ruby, we might express a simple CQL statement as follows:
{ :name => 'Help' }
while the same in JSON would be
{ "name" : "Help" }
The above example is about as different as the two representations get. Both surround hashes with curly brackets and arrays with square brackets, but Ruby CQL uses symbols which start with colons, like :name, while JSON CQL puts both keys and values in double quotes. And those keys and values are separated by => in Ruby and : in JSON. In all other cases, whether you're writing in Ruby or JSON your CQL will use the same basic representation for the same basic concepts.
Building Blocks
Decko's basic building block is the card. CQL's basic building block is the card query.
Card queries identify a list of one or more cards. In the same way that one card can include other cards, one card query can include other card queries.
To conceptualize this, it may help to think of card queries as a noun phrase in a sentence. For example:
Image cards.
That phrase can be nested inside another noun phrase:
Cards that link to Image cards.
You can extend this idea ad nauseam:
Cards that include cards for editors of cards that link to Image cards...
Those phrases give a general sense of what CQL is querying for, but unlike SQL, CQL is not a plaintext phrase. Instead it is expressed as a compound object built from three simple types.
Note that these simple object types are common to a great variety of languages and platforms, and so CQL queries can be easily translated into any such language.
Card queries are "hashes", or unordered lists of key/value pairs. These hashes use five kinds of keys :
property - name, content...
relationship - link to X, included by Y...
arrangement - sort by name, limit to 10...
operators - (shortcut for property queries), all the results matching "grantmaker"
modifier - add "+address" to the end of each result...
Card query values can be any of the three object types:
simple: for simple matches, card names, etc
array: for operators
hash: for nested card queries
A +cheat sheet lists these more concisely, but without the explanation given below here:
Properties
Each card query can describe the following card properties:
name
type
content
key - canonical name permutation
id - unique numerical id that remains the same even when the name is changed
Simple values
Each of these properties can be used as the key in a card query. For example, here is a search for the card named Lewis Hoffman.
The above examples represent simple searches where the property equals the value. For example, the type equals "Fruit", or the name equals "Lewis Hoffman". CQL allows more flexible ways to constrain properties, via the following operators:
match / ~
gt / >
lt / <
eq / = (an explicit form of the default)
ne / !=
in
In the previous CQL examples, all the card query values are simple strings. With operators, the values are arrays, as follows:
PROPERTY:[OPERATOR, VALUE]
So, the above example of cards with content equalling Kiwi:
{"content": "Kiwi"}
is actually the short form of the following:
{"content": ["eq", "Kiwi"] }
or
{"content": ["=", "Kiwi"] }
The other operators are more interesting. The "in" operator lets you search on multiple values for a given property. This example finds all the cards whose type is either Releases or Point Releases:
Here's where it gets interesting. Cards in Decko are related to other cards in two main ways: references (links and nesting), and compound names). Since these relationships are the source of most structure in Decko, this is the the element of CQL that adds the most depth.
All of these relationships are specified in CQL by nesting card queries. With the exception of certain "plus" relationships, they all take this form:
RELATIONSHIP: CARD_QUERY
References
Cards linking to or including other cards comprise references. (note -- insert links to feature cards inline here)
CQL supports the following reference keys:
link_to - web links
linked_to_by
include - embedded cards
included_by
refer_to - web links or embedded cards
referred_to_by
The simplest reference relationship is the link_to. Here's how you can use CQL to find all the cards linked to the "John Abbe" card:
Notice that in this case our nested card query is just the card name, "John Abbe". This is a short form of the more explicit expression:
{"link_to": {"name":"John Abbe"} }
Written that way, it looks much more like a nested card query: curly brackets inside curly brackets.
Plus cards
Frankly, if CQL ever gets a little mind-flexing, it's in using plus cards. But that's partly because plus cards are such a flexible way of doing things. If you haven't gotten the basic ideas behind compound names, continuing to read here will likely be very confusing. If you get the gist of plus cards, this will all make sense. But don't feel bad if you have to peek again later.
CQL supports two main plus cards keys:
part - value defines cards that are part of a given plus card
plus - value defines plus cards of which a given card is part
For each of those, it also supports two side-specific keys:
left, right
left_plus, right_plus
Part queries are generally pretty simple. Remember, if it has a part, it is always a plus card! The following finds all the cards that have "website" as a part.
Plus queries can get a little more involved. This is because, while a card can only ever have two part cards, it can be plussed to countless other cards. And you often want to refer to them in pairs - the other part and the plus card. For example, you might want to find all of the cards connected to "status" (the other part) where the content of X+status (the plus card) is "closed". So the plus card actually takes two queries. Here's the syntax:
In the example "type": "Image" and "and": {...} are two 'pairs' that are the parameters for "or".
Nesting searches
Often you want to specify a search once, then narrow it or arrange it in different ways. This could lead to a multiplication of search cards to maintain, but you can avoid this by using found_by, a key which takes a single Search card as a value, and returns all of the cards that Search returns. Note that the order and view of those cards is lost, and you can only nest once this way. A typical use is *right forms, e.g.:
create — in ascending order from the card earliest created to the latest
name — in ascending order, alphabetically (this used to be alpha, which still works)
content — in ascending order, alphabetically. This works, but only in very simple cases because Basic cards always have HTML and can have Decko markup, both of which do unexpected things to the order. Even Phrase and Plaintext cards can have HTML or Decko markup, but this should be usable on those cardtypes if no one is pasting in HTML. (Numbers are treated as text, and Dates are not stored in a consistent form, so neither is generally usable with sort by content.)
Dir can be used to override the default direction:
Append and prepend let you convert search result cards into new cards. For example, here's a simple way to find all the cards in a deck grouped by their type.
Notice that each card returned is a virtual card, which means that it's not actually stored in the database. There is a RichText card, and a *type, but there is no actual RichText+*type -- it's formed via a search pattern established in *type+*right+*structure. Since most CQL can only return cards in the database, something like this would not work:
Append works because the appending is done after the results are returned.
In addition to virtual cards, you can use append/prepend to return "missing" cards — cards that haven't been created yet. For example, if you're using Decko for an event and you want to invite people to fill in missing contact information, you might do something like this:
{"type": "User", "sort": "name", "append": "how to reach me"}
In general, whenever all the results you want are actual cards, you should not use append/prepend, but rather use left / right.
Returning card count
You can have CQL return the number of cards found instead of the cards themselves. For example, if you wanted to include the number of current users, you'd do this:
{"type": "User", "return": "count"}
By default, Decko provides a card *count you can append to any Search card to instead return a count, like so:
"in" doesn't work with "plus", but if you want to search on two plus cards, you can use "plus" for one and "right_plus" for the other.
When searching on the content of Toggles, search for 0 to find the nos and 1 to find the yeses.
You can put comments in CQL with:
/* commented text */
Needs
To date, all operators and sorts are treat numeric card values as text, which means, for example, that 11 comes before 2 (as it would alphabetically). We plan to support better number handling soon.