Skip to content

Commit 67c31c0

Browse files
authored
Merge pull request #217889 from vicancy/lianwei/filter
Adding filter syntax
2 parents 61dbbe0 + 364c741 commit 67c31c0

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
---
2+
title: OData filter syntax in Azure Web PubSub service
3+
description: OData language reference and full syntax used for creating filter expressions in Azure Web PubSub service queries.
4+
author: vicancy
5+
ms.author: lianwei
6+
ms.service: azure-web-pubsub
7+
ms.topic: reference
8+
ms.date: 11/11/2022
9+
---
10+
11+
# OData filter syntax in Azure Web PubSub service
12+
13+
In Azure Web PubSub service, the **filter** parameter specifies inclusion or exclusion criteria for the connections to send messages to. This article describes the OData syntax of **filter** and provides examples.
14+
15+
The complete syntax is described in the [formal grammar](#formal-grammar).
16+
17+
There is also a browsable [syntax diagram](https://aka.ms/awps/filter-syntax-diagram) that allows you to interactively explore the grammar and the relationships between its rules.
18+
19+
## Syntax
20+
21+
A filter in the OData language is a Boolean expression, which in turn can be one of several types of expression, as shown by the following EBNF ([Extended Backus-Naur Form](https://en.wikipedia.org/wiki/Extended_Backus–Naur_form)):
22+
23+
```
24+
/* Identifiers */
25+
string_identifier ::= 'connectionId' | 'userId'
26+
collection_identifier ::= 'groups'
27+
28+
/* Rules for $filter */
29+
30+
boolean_expression ::= logical_expression
31+
| comparison_expression
32+
| in_expression
33+
| boolean_literal
34+
| boolean_function_call
35+
| '(' boolean_expression ')'
36+
```
37+
38+
An interactive syntax diagram is also available:
39+
40+
> [!div class="nextstepaction"]
41+
> [OData syntax diagram for Azure Web PubSub service](https://aka.ms/awps/filter-syntax-diagram)
42+
43+
> [!NOTE]
44+
> See [formal grammar section](#formal-grammar) for the complete EBNF.
45+
46+
### Identifiers
47+
48+
The filter syntax is used to filter out the connections matching the filter expression to send messages to.
49+
50+
Azure Web PubSub supports below identifiers:
51+
52+
| Identifier | Description | Note | Examples
53+
| --- | --- | -- | --
54+
| `userId` | The userId of the connection. | Case insensitive. It can be used in [string operations](#supported-operations). | `userId eq 'user1'`
55+
| `connectionId` | The connectionId of the connection. | Case insensitive. It can be used in [string operations](#supported-operations). | `connectionId ne '123'`
56+
| `groups` | The collection of groups the connection is currently in. | Case insensitive. It can be used in [collection operations](#supported-operations). | `'group1' in groups`
57+
58+
Identifiers are used to refer to the property value of a connection. Azure Web PubSub supports 3 identifiers matching the property name of the connection model. and supports identifiers `userId` and `connectionId` in string operations, supports identifier `groups` in [collection operations](#supported-operations). For example, to filter out connections with userId `user1`, we specify the filter as `userId eq 'user1'`. Read through the below sections for more samples using the filter.
59+
60+
### Boolean expressions
61+
62+
The expression for a filter is a boolean expression. When sending messages to connections, Azure Web PubSub sends messages to connections with filter expression evaluated to `true`.
63+
64+
The types of Boolean expressions include:
65+
66+
- Logical expressions that combine other Boolean expressions using the operators `and`, `or`, and `not`.
67+
- Comparison expressions, which compare fields or range variables to constant values using the operators `eq`, `ne`, `gt`, `lt`, `ge`, and `le`.
68+
- The Boolean literals `true` and `false`. These constants can be useful sometimes when programmatically generating filters, but otherwise don't tend to be used in practice.
69+
- Boolean expressions in parentheses. Using parentheses can help to explicitly determine the order of operations in a filter. For more information on the default precedence of the OData operators, see [operator precedence section](#operator-precedence).
70+
71+
### Supported operations
72+
| Operator | Description | Example
73+
| --- | --- | ---
74+
| **Logical Operators**
75+
| `and` | Logical and | `length(userId) le 10 and length(userId) gt 3`
76+
| `or` | Logical or | `length(userId) gt 10 or length(userId) le 3`
77+
| `not` | Logical negation | `not endswith(userId, 'milk')`
78+
| **Comparison Operators**
79+
| `eq` | Equal | `userId eq 'user1'`, </br> `userId eq null`
80+
| `ne` | Not equal | `userId ne 'user1'`, </br> `userId ne null`
81+
| `gt` | Greater than | `length(userId) gt 10`
82+
| `ge` | Greater than or equal | `length(userId) ge 10`
83+
| `lt` | Less than | `length(userId) lt 3`
84+
| `le` | Less than or equal | `'group1' in groups`, </br> `user in ('user1','user2')`
85+
| **In Operator**
86+
| `in` | The right operand MUST be either a comma-separated list of primitive values, enclosed in parentheses, or a single expression that resolves to a collection.| `userId ne 'user1'`
87+
| **Grouping Operator**
88+
| `()` | Controls the evaluation order of an expression | `userId eq 'user1' or (not (startswith(userId,'user2'))`
89+
| **String Functions**
90+
| `string tolower(string p)` | Get the lower case for the string value | `tolower(userId) eq 'user1'` can match connections for user `USER1`
91+
| `string toupper(string p)` | Get the upper case for the string value | `toupper(userId) eq 'USER1'` can match connections for user `user1`
92+
| `string trim(string p)` | Trim the string value | `trim(userId) eq 'user1'` can match connections for user ` user1 `
93+
| `string substring(string p, int startIndex)`,</br>`string substring(string p, int startIndex, int length)` | Substring of the string | `substring(userId,5,2) eq 'ab'` can match connections for user `user-ab-de`
94+
| `bool endswith(string p0, string p1)` | Check if `p0` ends with `p1` | `endswith(userId,'de')` can match connections for user `user-ab-de`
95+
| `bool startswith(string p0, string p1)` | Check if `p0` starts with `p1` | `startswith(userId,'user')` can match connections for user `user-ab-de`
96+
| `int indexof(string p0, string p1)` | Get the index of `p1` in `p0`. Returns `-1` if `p0` does not contain `p1`. | `indexof(userId,'-ab-') ge 0` can match connections for user `user-ab-de`
97+
| `int length(string p)` | Get the length of the input string | `length(userId) gt 1` can match connections for user `user-ab-de`
98+
| **Collection Functions**
99+
| `int length(collection p)` | Get the length of the collection | `length(groups) gt 1` can match connections in 2 groups
100+
101+
### Operator precedence
102+
103+
If you write a filter expression with no parentheses around its sub-expressions, Azure Web PubSub service will evaluate it according to a set of operator precedence rules. These rules are based on which operators are used to combine sub-expressions. The following table lists groups of operators in order from highest to lowest precedence:
104+
105+
| Group | Operator(s) |
106+
| --- | --- |
107+
| Logical operators | `not` |
108+
| Comparison operators | `eq`, `ne`, `gt`, `lt`, `ge`, `le` |
109+
| Logical operators | `and` |
110+
| Logical operators | `or` |
111+
112+
An operator that is higher in the above table will "bind more tightly" to its operands than other operators. For example, `and` is of higher precedence than `or`, and comparison operators are of higher precedence than either of them, so the following two expressions are equivalent:
113+
114+
```odata-filter-expr
115+
length(userId) gt 0 and length(userId) lt 3 or length(userId) gt 7 and length(userId) lt 10
116+
((length(userId) gt 0) and (length(userId) lt 3)) or ((length(userId) gt 7) and (length(userId) lt 10))
117+
```
118+
119+
The `not` operator has the highest precedence of all -- even higher than the comparison operators. That's why if you try to write a filter like this:
120+
121+
```odata-filter-expr
122+
not length(userId) gt 5
123+
```
124+
125+
You'll get this error message:
126+
127+
```text
128+
Invalid syntax for 'not length(userId)': Type 'null', expect 'bool'. (Parameter 'filter')
129+
```
130+
131+
This error happens because the operator is associated with just the `length(userId)` expression, which is of type `null` when `userId` is `null`, and not with the entire comparison expression. The fix is to put the operand of `not` in parentheses:
132+
133+
```odata-filter-expr
134+
not (length(userId) gt 5)
135+
```
136+
137+
### Filter size limitations
138+
139+
There are limits to the size and complexity of filter expressions that you can send to Azure Web PubSub service. The limits are based roughly on the number of clauses in your filter expression. A good guideline is that if you have over 100 clauses, you are at risk of exceeding the limit. We recommend designing your application in such a way that it doesn't generate filters of unbounded size.
140+
141+
## Examples
142+
143+
1. Send to multiple groups
144+
145+
```odata-filter-expr
146+
filter='group1' in groups or 'group2' in groups or 'group3' in groups
147+
```
148+
2. Send to multiple users in some specific group
149+
```odata-filter-expr
150+
filter=userId in ('user1', 'user2', 'user3') and 'group1' in groups
151+
```
152+
3. Send to some user but not some specific connectionId
153+
```odata-filter-expr
154+
filter=userId eq 'user1' and connectionId ne '123'
155+
```
156+
4. Send to some user not in some specific group
157+
```odata-filter-expr
158+
filter=userId eq 'user1' and (not ('group1' in groups))
159+
```
160+
5. Escape `'` when userId contains `'`
161+
```odata-filter-expr
162+
filter=userId eq 'user''1'
163+
```
164+
165+
## Formal grammar
166+
167+
We can describe the subset of the OData language supported by Azure Web PubSub service using an EBNF ([Extended Backus-Naur Form](https://en.wikipedia.org/wiki/Extended_Backus–Naur_form)) grammar. Rules are listed "top-down", starting with the most complex expressions, and breaking them down into more primitive expressions. At the top is the grammar rule for `$filter` that correspond to specific parameter `filter` of the Azure Azure Web PubSub service `Send*` REST APIs:
168+
169+
170+
```
171+
/* Top-level rule */
172+
173+
filter_expression ::= boolean_expression
174+
175+
/* Identifiers */
176+
string_identifier ::= 'connectionId' | 'userId'
177+
collection_identifier ::= 'groups'
178+
179+
/* Rules for $filter */
180+
181+
boolean_expression ::= logical_expression
182+
| comparison_expression
183+
| in_expression
184+
| boolean_literal
185+
| boolean_function_call
186+
| '(' boolean_expression ')'
187+
188+
logical_expression ::= boolean_expression ('and' | 'or') boolean_expression
189+
| 'not' boolean_expression
190+
191+
comparison_expression ::= primary_expression comparison_operator primary_expression
192+
193+
in_expression ::= primary_expression 'in' ( '(' primary_expression (',' primary_expression)* ')' ) | collection_expression
194+
195+
collection_expression ::= collection_variable
196+
| '(' collection_expression ')'
197+
198+
primary_expression ::= primary_variable
199+
| function_call
200+
| constant
201+
| '(' primary_expression ')'
202+
203+
string_expression ::= string_literal
204+
| 'null'
205+
| string_identifier
206+
| string_function_call
207+
| '(' string_expression ')'
208+
209+
primary_variable ::= string_identifier
210+
collection_variable ::= collection_identifier
211+
212+
comparison_operator ::= 'gt' | 'lt' | 'ge' | 'le' | 'eq' | 'ne'
213+
214+
/* Rules for constants and literals */
215+
constant ::= string_literal
216+
| integer_literal
217+
| boolean_literal
218+
| 'null'
219+
220+
boolean_literal ::= 'true' | 'false'
221+
222+
string_literal ::= "'"([^'] | "''")*"'"
223+
224+
digit ::= [0-9]
225+
sign ::= '+' | '-'
226+
integer_literal ::= sign? digit+
227+
228+
boolean_literal ::= 'true' | 'false'
229+
230+
/* Rules for functions */
231+
232+
function_call ::= indexof_function_call
233+
| length_function_call
234+
| string_function_call
235+
| boolean_function_call
236+
237+
boolean_function_call ::= endsWith_function_call
238+
| startsWith_function_call
239+
| contains_function_call
240+
string_function_call ::= tolower_function_call
241+
| toupper_function_call
242+
| trim_function_call
243+
| substring_function_call
244+
| concat_function_call
245+
246+
/* Rules for string functions */
247+
indexof_function_call ::= "indexof" '(' string_expression ',' string_expression ')'
248+
concat_function_call ::= "concat" '(' string_expression ',' string_expression ')'
249+
contains_function_call ::= "contains" '(' string_expression ',' string_expression ')'
250+
endsWith_function_call ::= "endswith" '(' string_expression ',' string_expression ')'
251+
startsWith_function_call ::= "startswith" '(' string_expression ',' string_expression ')'
252+
substring_function_call ::= "substring" '(' string_expression ',' integer_literal (',' integer_literal)? ')'
253+
tolower_function_call ::= "tolower" '(' string_expression ')'
254+
toupper_function_call ::= "toupper" '(' string_expression ')'
255+
trim_function_call ::= "trim" '(' string_expression ')'
256+
257+
/* Rules for string and collection functions */
258+
length_function_call ::= "length" '(' string_expression | collection_expression ')'
259+
```
260+
261+
## Next steps
262+
263+
[!INCLUDE [next step](includes/include-next-step.md)]

articles/azure-web-pubsub/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@
168168
href: reference-server-sdk-js.md
169169
- name: Server SDK - Python
170170
href: reference-server-sdk-python.md
171+
- name: OData filter syntax reference
172+
href: reference-odata-filter.md
171173
- name: Control plane
172174
href: /rest/api/webpubsub/
173175
- name: Azure CLI

0 commit comments

Comments
 (0)