Accessors
Learn how to access properties and attributes in CQL.
When writing queries, you will often use property access expressions to extract values from your data.
CQL provides several ways to access properties that you can choose depending on your use case and preferred style.
Ownership notation
The ownership notation is a more natural way to access properties in CQL. It is complementary to the possessive notation and can be used interchangeably or in combination with it.
The syntax follows the same pattern as the possessive form in English. It consists of the variable name followed by an apostrophe and an "s" ('s) and the property name.
Here is a basic example that accesses the interests property of the user variable:
user's interests
You can also chain multiple property accesses together to access nested properties:
user's address's city
Alternatively, you can use the possessive form for an even more natural sentence:
property city of user's address
To combine the ownership notation with other technical notations, such as the bracket notation, you can use parentheses:
(user's interests)[0]
In English, it is common to drop the "s" after the apostrophe when the word ends with an "s". You can do the same in CQL to make the queries more natural:
let status = ["code": "active"];status' code
Keep in mind that dropping the extra "s" after an apostrophe is just syntax sugar. It is not a requirement, so you can keep the "s" if you prefer.
Consider using the ownership notation when:
- The property name is a valid identifier and is known in advance.
- The property is not deeply nested.
- You prefer writing queries using a more natural language style.
- You want to combine it with the possessive notation for a more natural sentence.
Possessive notation
The possessive notation is another way to access properties in CQL that emphasizes natural language. It is complementary to the ownership notation and can be used interchangeably or in combination with it.
The syntax for the possessive notation consists of the keyword property followed by the property name, the keyword of, and the value or variable name.
Here is an example that accesses the interests property of the user variable:
property interests of user
You can access nested properties by chaining multiple property accesses together.
property city of property address of user
As mentioned earlier, you can combine the possessive notation with the ownership notation for an even more natural sentence:
property city of user's address
You can optionally add the word the before the property name to make the sentence more readable depending on the context:
the property city of user's address is equal to 'New York'
To combine the possessive notation with other technical notations, such as the bracket notation, you can use parentheses:
(property interests of user)[0]
While the possessive notation can provide a more natural way to access properties in some scenarios, it may not always be the most suitable choice for all situations.
Consider using the possessive notation when:
- The property name is a valid identifier and is known in advance.
- The property is not deeply nested.
- You prefer writing queries using a more natural language style.
- You want to combine it with the ownership notation for a more natural sentence.
Dot notation
This is the simplest and most concise way to access properties.
It follows the same syntax as some programming languages, such as JavaScript and Python. As a result, it may be more familiar to developers than to those who are not involved in programming.
The syntax consists of the variable name followed by a dot (.) and the property name.
Here is a basic example that accesses the interests property of the user variable:
user.interests
You can also chain multiple property accesses together to access nested properties:
user.address.city
Or even combine them with other notations, such as the bracket notation:
user.interests[0]
Each notation has its own advantages and tradeoffs, so you should choose the one that best fits your scenario.
Consider using the dot notation when:
- The property name is a valid identifier and is known in advance.
- You are accessing a deeply nested property.
- Anyone reading the queries is familiar with this notation.
Bracket notation
The bracket notation is another way to access properties in CQL, and it is particularly useful when the property name is not a valid identifier or when you want to use selectors.
Unlike the dot notation, the bracket notation supports any property name, including those that are not a valid identifier.
The syntax consists of the variable name followed by a selector enclosed in square brackets ([ and ]):
subject[/*<selector>*/]
Here is a basic example that accesses the interests property of the user variable:
user['interests']
You can also use numeric indexes to access items from a list:
user.interests[0]
Although this notation supports arbitrary property names and selectors, it may still not be the best choice for all scenarios.
Consider using the bracket notation when:
- The property name is not a valid identifier or is not known in advance.
- You need to access items from a list using numeric indexes.
- You want to leverage the power of selectors.
- Anyone reading the queries is familiar with this notation.
- You prefer to stick to a single notation throughout your queries.
Selectors
We call selectors the different specifiers you can use inside brackets to access properties. They are shorthands for common operations that you can use to access values in a more concise way.
Here is a summary of the currently available selectors:
Selector | Description | Example |
---|---|---|
First selector | Get the first item of a list. | cart.items[:first] |
Last selector | Get the last item of a list. | cart.items[:last] |
Ordinal selector | Get the item at a specific position. | cart.items[1st] |
Expression selector | Get a value using an expression. | cart.items['price'] |
Wildcard selector | Get multiple values at once. | cart[*].name |
Scan selector | Get values recursively. | cart[...].name |
First selector
The first selector provides a convenient, more intuitive way to access the first items of a list.
For example, to access the first item in a list, you can use:
let transport = ["🚀", "🚗", "🚆"];transport[:first] // 🚀
You can also use it to access a specific number of items from the beginning of a list:
transport[:first(/*<count>*/)]
For example, to access the first two items of a list, you can use:
transport[:first(2)] // ["🚀", "🚗"]
In the real world, you can use it to access the first item a user added to their shopping cart, for example:
cart.items[:first]
And you can also use it to get the first items of a map:
let beings = ["aliens": "👽", "robots": "🤖", "humans": "👨🚀"];beings[:first] // 👽
This works because the :first selector does not use the index or key to find the first element, but the order in which they appear in the collection.
Last selector
The last selector is the counterpart of the first selector. It allows you to access the last elements of a collection without knowing the index or key.
For example, to access the last item in a list, you can use:
let transport = ["🚀", "🚗", "🚆️"];transport[:last] // 🚆
You can also use it to access a specific number of items from the end of a list:
transport[:last(/*<count>*/)]
For example, to access the last two items of a list, you can use:
transport[:last(2)] // ["🚗", "🚆"]
In the real world, you can use it to access the last item a user added to their shopping cart:
cart.items[:last]
And you can also use it to access the last element of a map:
let beings = ["aliens": "👽", "robots": "🤖", "humans": "👨🚀"];beings[:last] // 👨🚀
Similar to the :first selector, :last does not use the index or key to find the last element but the order in which they appear in the collection.
Ordinal selector
The ordinal selector allows you to access an element at a specific position in a list using its ordinal number instead of its index or key.
That's right! With the ordinal selector, you can use natural counting, starting at 1, instead of the typical 0-based indexing in programming.
For example, to access the second item in a list, you can simply use:
let transport = ["🚀", "🚗", "🚆"];transport[2nd] // 🚗
For a real-world example, let's say you want to access the second item added to a shopping cart:
cart.items[2nd] or else null
Here, we use the or else operator to return null if the cart is empty. Otherwise, the query would fail with an error.
Expression selector
The expression selector is the most versatile, as it allows you to use any expression to access a property or an item in a list.
This selector is especially useful when the property name is not a valid identifier or when you want to dynamically compute the index or key.
For example, say you want to get the value of a query string parameter named referral code. Because the property name contains spaces, you can only access it using the expression selector:
page.query['referral code']
Because you can use any expression, you could store the property name in a variable and use it in the expression:
let param = 'referral code';page.query[param]
You can also use numeric indexes to access items in a list:
let transport = ["🚀", "🚗", "🚆"];transport[0] // 🚀
Notice that the index is zero-based, so the first item has an index of 0, the second item has an index of 1, and so on. For a more natural way to access items in a list, see the ordinal selector.
Wildcard selector
The wildcard selector allows you to access multiple properties or elements at once, which can be useful when you want to retrieve or modify several items in a list, for example.
It iterates over all the items in a list and applies the selector to each item, much like a for loop in programming languages.
Before we explore its use, let's first define a wishlist of must-have items:
let whishlist = [["name": "🔮 Crystal ball", "price": 50],["name": "🎩 Invisible hat", "price": 75],["name": "👻 Ghost repellent spray", "price": 30],["name": "🚀 DIY rocket ship kit", "price": 900],["name": "🦖 Dinosaur egg incubator", "price": 200],];
Now, let's say you want to calculate the total price of all the items on your wishlist. In this scenario, the wildcard selector comes in handy:
sum of whishlist[*].price
Here the wildcard selector iterates over all the items in the list and creates a new list containing the price of each item. Then, the sum macro calculates and returns the total by summing all the prices in the list.
This also comes in handy when you want to check if a list satisfies a condition. For example, suppose you want to check if at least one item in the user's shopping cart has a price greater than $100. You can use the wildcard selector and the some quantifier to answer this question:
some price in cart.items[*] satisfies price > 100
This will check if any price in the cart.items list satisfies the given condition. And because the some quantifier returns a boolean, you have your answer.
Scan selector
The scan selector allows you to scan and retrieve values from nested data structures. It recursively iterates through the data structure and returns a list of all corresponding values.
For example, let's say you have the following data structure representing an e-commerce store's inventory:
let inventory = [["category": "📱 Electronics","items": [["name": "Smartphone", "price": 500],["name": "Tablet", "price": 400],]],["category": "👗 Fashion","items": [["name": "T-shirt", "price": 25],["name": "Jeans", "price": 60],],],["category": "🍳 Home & Kitchen","items": [["name": "Toaster", "price": 30],["name": "Blender", "price": 75]],],];
Using the scan selector, you easily find the cheapest item in the inventory:
min of inventory[...].price
Here, the scan selector iterates through the inventory and creates a list of all the prices. Then, the min macro returns the lowest price in the list, which is $25.
You can use the same idea to check whether the user has added an Apple product to their cart:
some brand in cart[...].brand satisfies brand is "Apple"
This time, the scan selector iterates through the cart and creates a list of all the brands. Then, the some quantifier checks if Apple is in the list.
The scan selector also supports an optional maximum depth parameter, which allows you to limit the depth of the search and potentially speed up the query.
For example, let's update the inventory to include subcategories and see how this works:
let inventory = [ // Level 1[ // Level 2"category": "📱 Electronics","items": [ // Level 3["name": "Desktop computer", "price": 900], // Level 4],"subcategories": [["category": "🎮 Gaming","items": [["name": "Gaming console", "price": 500],["name": "Gaming headset", "price": 100]],],],],];
Now, let's say you want to sum the prices, but only for the root categories. You can use the scan selector with a maximum depth of 4 to achieve this:
sum of inventory[...4].price
Since the desktop computer is the only item in a root category, the result is $900 instead of $2500.