Skip to content

Commit 614ff7a

Browse files
committed
Adding filter syntax
1 parent bd06240 commit 614ff7a

File tree

3 files changed

+327
-0
lines changed

3 files changed

+327
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
2+
/* Top-level rule */
3+
4+
filter_expression ::= boolean_expression
5+
6+
/* Identifiers */
7+
string_identifier ::= 'connectionId' | 'userId'
8+
collection_identifier ::= 'groups'
9+
10+
/* Rules for $filter */
11+
12+
boolean_expression ::= logical_expression
13+
| comparison_expression
14+
| in_expression
15+
| boolean_literal
16+
| boolean_function_call
17+
| '(' boolean_expression ')'
18+
19+
logical_expression ::= boolean_expression ('and' | 'or') boolean_expression
20+
| 'not' boolean_expression
21+
22+
comparison_expression ::= primary_expression comparison_operator primary_expression
23+
24+
in_expression ::= primary_expression 'in' '(' (primary_expression (',' primary_expression)* | collection_expression ) ')'
25+
26+
collection_expression ::= collection_variable
27+
| '(' collection_expression ')'
28+
29+
primary_expression ::= primary_variable
30+
| function_call
31+
| constant
32+
| '(' primary_expression ')'
33+
34+
string_expression ::= string_literal
35+
| 'null'
36+
| string_identifier
37+
| string_function_call
38+
| '(' string_expression ')'
39+
40+
primary_variable ::= string_identifier
41+
collection_variable ::= collection_identifier
42+
43+
comparison_operator ::= 'gt' | 'lt' | 'ge' | 'le' | 'eq' | 'ne'
44+
45+
/* Rules for constants and literals */
46+
constant ::= string_literal
47+
| integer_literal
48+
| boolean_literal
49+
| 'null'
50+
51+
boolean_literal ::= 'true' | 'false'
52+
53+
string_literal ::= "'"([^'] | "''")*"'"
54+
55+
digit ::= [0-9]
56+
sign ::= '+' | '-'
57+
integer_literal ::= sign? digit+
58+
59+
boolean_literal ::= 'true' | 'false'
60+
61+
/* Rules for functions */
62+
63+
function_call ::= indexof_function_call
64+
| length_function_call
65+
| string_function_call
66+
| boolean_function_call
67+
68+
boolean_function_call ::= endsWith_function_call
69+
| startsWith_function_call
70+
| contains_function_call
71+
string_function_call ::= tolower_function_call
72+
| toupper_function_call
73+
| trim_function_call
74+
| substring_function_call
75+
| concat_function_call
76+
77+
/* Rules for string functions */
78+
indexof_function_call ::= "indexof" '(' string_expression ',' string_expression ')'
79+
concat_function_call ::= "concat" '(' string_expression ',' string_expression ')'
80+
contains_function_call ::= "contains" '(' string_expression ',' string_expression ')'
81+
endsWith_function_call ::= "endswith" '(' string_expression ',' string_expression ')'
82+
startsWith_function_call ::= "startswith" '(' string_expression ',' string_expression ')'
83+
substring_function_call ::= "substring" '(' string_expression ',' integer_literal (',' integer_literal)? ')'
84+
tolower_function_call ::= "tolower" '(' string_expression ')'
85+
toupper_function_call ::= "toupper" '(' string_expression ')'
86+
trim_function_call ::= "trim" '(' string_expression ')'
87+
88+
/* Rules for string and collection functions */
89+
length_function_call ::= "length" '(' string_expression | collection_expression ')'
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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](#complete-syntax).
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](#complete-syntax) 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+
The model for a connection is defined as below:
51+
52+
```ts
53+
{
54+
connectionId : string,
55+
userId: string,
56+
groups: string[]
57+
}
58+
```
59+
60+
Identifiers are used to refer to the property value of a connection. 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.
61+
62+
### Boolean expressions
63+
The types of Boolean expressions include:
64+
65+
- Logical expressions that combine other Boolean expressions using the operators `and`, `or`, and `not`.
66+
- Comparison expressions, which compare fields or range variables to constant values using the operators `eq`, `ne`, `gt`, `lt`, `ge`, and `le`.
67+
- 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.
68+
- 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 the next section.
69+
70+
### Operator precedence in filters
71+
72+
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:
73+
74+
| Group | Operator(s) |
75+
| --- | --- |
76+
| Logical operators | `not` |
77+
| Comparison operators | `eq`, `ne`, `gt`, `lt`, `ge`, `le` |
78+
| Logical operators | `and` |
79+
| Logical operators | `or` |
80+
81+
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:
82+
83+
```odata-filter-expr
84+
length(userId) gt 0 and length(userId) lt 3 or length(userId) gt 7 and length(userId) lt 10
85+
((length(userId) gt 0) and (length(userId) lt 3)) or ((length(userId) gt 7) and (length(userId) lt 10))
86+
```
87+
88+
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:
89+
90+
```odata-filter-expr
91+
not length(userId) gt 5
92+
```
93+
94+
You'll get this error message:
95+
96+
```text
97+
Invalid syntax for 'not length(userId)': Type 'null', expect 'bool'. (Parameter 'filter')
98+
```
99+
100+
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:
101+
102+
```odata-filter-expr
103+
not (length(userId) gt 5)
104+
```
105+
106+
### Filter size limitations
107+
108+
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 hundreds of 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.
109+
110+
## Examples
111+
112+
1. Send to multiple groups
113+
114+
```odata-filter-expr
115+
filter='group1' in groups or 'group2' in groups or 'group3' in groups
116+
```
117+
2. Send to multiple users in some specific group
118+
```odata-filter-expr
119+
filter=userId in ('user1', 'user2', 'user3') and 'group1' in groups
120+
```
121+
3. Send to some user but not some specific connectionId
122+
```odata-filter-expr
123+
filter=userId eq 'user1' and connectionId ne '123'
124+
```
125+
4. Send to some user not in some specific group
126+
```odata-filter-expr
127+
filter=userId eq 'user1' and (not ('group1' in groups))
128+
```
129+
5. Escape `'` when userId contains `'`
130+
```odata-filter-expr
131+
filter=userId eq 'user''1'
132+
```
133+
134+
## Formal grammar
135+
136+
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:
137+
138+
139+
```
140+
/* Top-level rule */
141+
142+
filter_expression ::= boolean_expression
143+
144+
/* Identifiers */
145+
string_identifier ::= 'connectionId' | 'userId'
146+
collection_identifier ::= 'groups'
147+
148+
/* Rules for $filter */
149+
150+
boolean_expression ::= logical_expression
151+
| comparison_expression
152+
| in_expression
153+
| boolean_literal
154+
| boolean_function_call
155+
| '(' boolean_expression ')'
156+
157+
logical_expression ::= boolean_expression ('and' | 'or') boolean_expression
158+
| 'not' boolean_expression
159+
160+
comparison_expression ::= primary_expression comparison_operator primary_expression
161+
162+
in_expression ::= primary_expression 'in' ( '(' primary_expression (',' primary_expression)* ')' ) | collection_expression
163+
164+
collection_expression ::= collection_variable
165+
| '(' collection_expression ')'
166+
167+
primary_expression ::= primary_variable
168+
| function_call
169+
| constant
170+
| '(' primary_expression ')'
171+
172+
string_expression ::= string_literal
173+
| 'null'
174+
| string_identifier
175+
| string_function_call
176+
| '(' string_expression ')'
177+
178+
primary_variable ::= string_identifier
179+
collection_variable ::= collection_identifier
180+
181+
comparison_operator ::= 'gt' | 'lt' | 'ge' | 'le' | 'eq' | 'ne'
182+
183+
/* Rules for constants and literals */
184+
constant ::= string_literal
185+
| integer_literal
186+
| boolean_literal
187+
| 'null'
188+
189+
boolean_literal ::= 'true' | 'false'
190+
191+
string_literal ::= "'"([^'] | "''")*"'"
192+
193+
digit ::= [0-9]
194+
sign ::= '+' | '-'
195+
integer_literal ::= sign? digit+
196+
197+
boolean_literal ::= 'true' | 'false'
198+
199+
/* Rules for functions */
200+
201+
function_call ::= indexof_function_call
202+
| length_function_call
203+
| string_function_call
204+
| boolean_function_call
205+
206+
boolean_function_call ::= endsWith_function_call
207+
| startsWith_function_call
208+
| contains_function_call
209+
string_function_call ::= tolower_function_call
210+
| toupper_function_call
211+
| trim_function_call
212+
| substring_function_call
213+
| concat_function_call
214+
215+
/* Rules for string functions */
216+
indexof_function_call ::= "indexof" '(' string_expression ',' string_expression ')'
217+
concat_function_call ::= "concat" '(' string_expression ',' string_expression ')'
218+
contains_function_call ::= "contains" '(' string_expression ',' string_expression ')'
219+
endsWith_function_call ::= "endswith" '(' string_expression ',' string_expression ')'
220+
startsWith_function_call ::= "startswith" '(' string_expression ',' string_expression ')'
221+
substring_function_call ::= "substring" '(' string_expression ',' integer_literal (',' integer_literal)? ')'
222+
tolower_function_call ::= "tolower" '(' string_expression ')'
223+
toupper_function_call ::= "toupper" '(' string_expression ')'
224+
trim_function_call ::= "trim" '(' string_expression ')'
225+
226+
/* Rules for string and collection functions */
227+
length_function_call ::= "length" '(' string_expression | collection_expression ')'
228+
```
229+
230+
231+
[!INCLUDE [syntax](includes/filter.ebnf)]
232+
233+
234+
## Next steps
235+
236+
[!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+
name: reference-odata-filter.md
171173
- name: Control plane
172174
href: /rest/api/webpubsub/
173175
- name: Azure CLI

0 commit comments

Comments
 (0)