|
| 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)] |
0 commit comments