11 OData Query Reference
The OData query DSL is provided by the main bcnav module.
11.1 Query Construction
procedure
(make-query [ #:filter filter #:select select #:expand expand #:orderby orderby #:top top #:skip skip]) → query? filter : filter-expr/c = #f select : (or/c #f (listof symbol?)) = #f expand : (or/c #f (listof symbol?)) = #f
orderby : (or/c #f symbol? (cons/c symbol? (or/c 'asc 'desc))) = #f top : (or/c #f exact-nonnegative-integer?) = #f skip : (or/c #f exact-nonnegative-integer?) = #f
#:filter —
A filter expression limiting which records are returned. See Filter Expressions. #:select —
A list of field names to include in the response. Reduces response size. #:expand —
A list of navigation properties to include as nested data. #:orderby —
Sort order. Either a field name (ascending) or a pair of (field . direction) where direction is 'asc or 'desc. #:top —
Maximum number of records to return. #:skip —
Number of records to skip (for pagination).
> (make-query #:filter (eq 'status "Open") #:select '(id displayName amount) #:orderby '(amount . desc) #:top 10) (query status eq 'Open' '(id displayName amount) #f '(amount . desc) 10 #f)
value
11.2 Query Accessors
These functions retrieve individual components of a query.
procedure
(query-filter q) → filter-expr/c
q : query?
procedure
(query-select q) → (or/c #f (listof symbol?))
q : query?
procedure
(query-expand q) → (or/c #f (listof symbol?))
q : query?
procedure
(query-top q) → (or/c #f exact-nonnegative-integer?)
q : query?
procedure
(query-skip q) → (or/c #f exact-nonnegative-integer?)
q : query?
11.3 Query Composition
These functions create modified copies of queries. The original query is unchanged.
procedure
(query-with-filter q filter) → query?
q : query? filter : filter-expr/c
procedure
(query-with-orderby q orderby) → query?
q : query? orderby : (or/c #f symbol? (cons/c symbol? (or/c 'asc 'desc)))
procedure
(query-with-top q top) → query?
q : query? top : (or/c #f exact-nonnegative-integer?)
procedure
(query-with-skip q skip) → query?
q : query? skip : (or/c #f exact-nonnegative-integer?)
11.4 Query Conversion
procedure
(query->params q) → (listof (cons/c symbol? string?))
q : odata-query/c
> (query->params (make-query #:filter (eq 'status "Open") #:select '(id name) #:top 5)) '(($filter . "status eq 'Open'") ($select . "id,name") ($top . "5"))
11.5 Filter Expressions
Filter expressions are built using comparison and logical functions. They represent OData $filter query conditions.
Comparison operators use OData names (eq, ne, lt, etc.) while logical operators use a : suffix (and:, or:, not:) to avoid shadowing Racket’s built-in forms.
value
11.5.1 Comparison Operators
Each comparison operator takes a field name (as a symbol) and a value. The function names match the OData operator names.
> (filter->string (eq 'status "Open")) "status eq 'Open'"
> (filter->string (eq 'blocked #f)) "blocked eq false"
> (filter->string (ne 'type "Person")) "type ne 'Person'"
> (filter->string (lt 'amount 1000)) "amount lt 1000"
> (filter->string (gt 'quantity 0)) "quantity gt 0"
11.5.2 String Functions
These functions search within text fields.
> (filter->string (contains 'displayName "Contoso")) "contains(displayName,'Contoso')"
procedure
(startswith field prefix) → fexp?
field : symbol? prefix : string?
> (filter->string (startswith 'number "C00")) "startswith(number,'C00')"
> (filter->string (endswith 'email ".com")) "endswith(email,'.com')"
11.5.3 Logical Operators
Combine multiple filter expressions. These use a : suffix to avoid shadowing Racket’s built-in and, or, and not forms.
With zero arguments, returns #f. With one argument, returns that argument unchanged. With multiple arguments, combines them with and.
> (and:) #f
> (and: (eq 'status "Open")) status eq 'Open'
> (filter->string (and: (eq 'status "Open") (gt 'amount 100))) "(status eq 'Open' and amount gt 100)"
> (filter->string (and: (eq 'a 1) (eq 'b 2) (eq 'c 3))) "((a eq 1 and b eq 2) and c eq 3)"
With zero arguments, returns #f. With one argument, returns that argument unchanged. With multiple arguments, combines them with or.
> (filter->string (or: (eq 'type "Company") (eq 'type "Person"))) "(type eq 'Company' or type eq 'Person')"
> (filter->string (not: (eq 'blocked #t))) "not (blocked eq true)"
11.5.4 Type Wrappers
OData uses different value types that require specific formatting. Strings are automatically quoted, but dates, datetimes, and GUIDs must be sent unquoted. These wrapper functions mark values so they are formatted correctly.
procedure
(odata-date value) → any/c
value : string?
> (filter->string (eq 'postingDate (odata-date "2025-07-01"))) "postingDate eq 2025-07-01"
> (filter->string (ge 'orderDate (odata-date "2025-01-01"))) "orderDate ge 2025-01-01"
A binary operator with incompatible types was detected. |
Found operand types 'Edm.Date' and 'Edm.String' |
procedure
(odata-datetime value) → any/c
value : string?
> (filter->string (ge 'lastModifiedDateTime (odata-datetime "2025-07-01T00:00:00Z"))) "lastModifiedDateTime ge 2025-07-01T00:00:00Z"
procedure
(odata-guid value) → any/c
value : string?
> (filter->string (eq 'customerId (odata-guid "12345678-1234-1234-1234-123456789abc"))) "customerId eq 12345678-1234-1234-1234-123456789abc"
> (filter->string (eq 'customField (odata-raw "someUnquotedValue"))) "customField eq someUnquotedValue"
11.5.5 Date/Time Range Filters
Convenience functions for filtering by date ranges.
procedure
(date-between field start-date end-date) → fexp?
field : symbol? start-date : string? end-date : string?
The dates should be in ISO 8601 format (YYYY-MM-DD).
> (filter->string (date-between 'postingDate "2025-07-01" "2025-07-31")) "(postingDate ge 2025-07-01 and postingDate le 2025-07-31)"
This is the recommended way to filter by date ranges instead of manually constructing the filter with odata-date wrappers.
procedure
(datetime-between field start-datetime end-datetime) → fexp? field : symbol? start-datetime : string? end-datetime : string?
The datetimes should be in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ).
> (filter->string (datetime-between 'lastModifiedDateTime "2025-07-01T00:00:00Z" "2025-07-31T23:59:59Z")) "(lastModifiedDateTime ge 2025-07-01T00:00:00Z and lastModifiedDateTime le 2025-07-31T23:59:59Z)"
11.5.6 Filter Utilities
procedure
(filter->string expr) → string?
expr : fexp?
> (filter->string (and: (eq 'status "Open") (or: (gt 'amount 1000) (contains 'displayName "VIP")))) "(status eq 'Open' and (amount gt 1000 or contains(displayName,'VIP')))"