On this page:
11.1 Query Construction
make-query
query?
odata-query/  c
11.2 Query Accessors
query-filter
query-select
query-expand
query-orderby
query-top
query-skip
11.3 Query Composition
query-with-filter
query-with-select
query-with-expand
query-with-orderby
query-with-top
query-with-skip
11.4 Query Conversion
query->params
11.5 Filter Expressions
filter-expr/  c
11.5.1 Comparison Operators
eq
ne
lt
gt
le
ge
11.5.2 String Functions
contains
startswith
endswith
11.5.3 Logical Operators
and:
or:
not:
11.5.4 Type Wrappers
odata-date
odata-datetime
odata-guid
odata-raw
11.5.5 Date/  Time Range Filters
date-between
datetime-between
11.5.6 Filter Utilities
filter->string
fexp?
9.3

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
Constructs a query object that can be passed to entity functions via the #:query keyword argument.

  • #: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)

procedure

(query? v)  boolean?

  v : any/c
Returns #t if v is a query object created by make-query.

A contract for query values: (or/c #f query?).

11.2 Query Accessors🔗

These functions retrieve individual components of a query.

procedure

(query-filter q)  filter-expr/c

  q : query?
Returns the filter expression, or #f if none.

procedure

(query-select q)  (or/c #f (listof symbol?))

  q : query?
Returns the select field list, or #f if none.

procedure

(query-expand q)  (or/c #f (listof symbol?))

  q : query?
Returns the expand field list, or #f if none.

procedure

(query-orderby q)

  (or/c #f symbol? (cons/c symbol? (or/c 'asc 'desc)))
  q : query?
Returns the orderby specification, or #f if none.

procedure

(query-top q)  (or/c #f exact-nonnegative-integer?)

  q : query?
Returns the top limit, or #f if none.

procedure

(query-skip q)  (or/c #f exact-nonnegative-integer?)

  q : query?
Returns the skip count, or #f if none.

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
Returns a new query with the given filter, keeping other fields from q.

procedure

(query-with-select q select)  query?

  q : query?
  select : (or/c #f (listof symbol?))
Returns a new query with the given select list.

procedure

(query-with-expand q expand)  query?

  q : query?
  expand : (or/c #f (listof symbol?))
Returns a new query with the given expand list.

procedure

(query-with-orderby q orderby)  query?

  q : query?
  orderby : (or/c #f symbol? (cons/c symbol? (or/c 'asc 'desc)))
Returns a new query with the given orderby specification.

procedure

(query-with-top q top)  query?

  q : query?
  top : (or/c #f exact-nonnegative-integer?)
Returns a new query with the given top limit.

procedure

(query-with-skip q skip)  query?

  q : query?
  skip : (or/c #f exact-nonnegative-integer?)
Returns a new query with the given skip count.

11.4 Query Conversion🔗

procedure

(query->params q)  (listof (cons/c symbol? string?))

  q : odata-query/c
Converts a query to a list of URL parameter pairs suitable for HTTP requests.

> (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.

A contract for filter expressions: (or/c #f fexp?).

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.

procedure

(eq field value)  fexp?

  field : symbol?
  value : any/c
Equality comparison (eq in OData).
> (filter->string (eq 'status "Open"))

"status eq 'Open'"

> (filter->string (eq 'blocked #f))

"blocked eq false"

procedure

(ne field value)  fexp?

  field : symbol?
  value : any/c
Inequality comparison (ne in OData).
> (filter->string (ne 'type "Person"))

"type ne 'Person'"

procedure

(lt field value)  fexp?

  field : symbol?
  value : any/c
Less than comparison (lt in OData).
> (filter->string (lt 'amount 1000))

"amount lt 1000"

procedure

(gt field value)  fexp?

  field : symbol?
  value : any/c
Greater than comparison (gt in OData).
> (filter->string (gt 'quantity 0))

"quantity gt 0"

procedure

(le field value)  fexp?

  field : symbol?
  value : any/c
Less than or equal comparison (le in OData).

procedure

(ge field value)  fexp?

  field : symbol?
  value : any/c
Greater than or equal comparison (ge in OData).

11.5.2 String Functions🔗

These functions search within text fields.

procedure

(contains field substring)  fexp?

  field : symbol?
  substring : string?
True if field contains substring.
> (filter->string (contains 'displayName "Contoso"))

"contains(displayName,'Contoso')"

procedure

(startswith field prefix)  fexp?

  field : symbol?
  prefix : string?
True if field starts with prefix.
> (filter->string (startswith 'number "C00"))

"startswith(number,'C00')"

procedure

(endswith field suffix)  fexp?

  field : symbol?
  suffix : string?
True if field ends with suffix.
> (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.

procedure

(and: expr ...)  (or/c #f fexp?)

  expr : fexp?
Logical AND of all expressions.

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)"

procedure

(or: expr ...)  (or/c #f fexp?)

  expr : fexp?
Logical OR of all expressions.

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')"

procedure

(not: expr)  fexp?

  expr : fexp?
Logical negation of expr.

> (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?
Wraps a date string (ISO 8601 format: YYYY-MM-DD) so it is not quoted in the filter expression. Required for Edm.Date fields.

> (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"

Without the wrapper, dates are treated as strings and cause a type mismatch error:

A binary operator with incompatible types was detected.

Found operand types 'Edm.Date' and 'Edm.String'

procedure

(odata-datetime value)  any/c

  value : string?
Wraps a datetime string (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ) so it is not quoted. Required for Edm.DateTimeOffset fields.

> (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?
Wraps a GUID string so it is not quoted. Required for Edm.Guid fields.

> (filter->string (eq 'customerId (odata-guid "12345678-1234-1234-1234-123456789abc")))

"customerId eq 12345678-1234-1234-1234-123456789abc"

procedure

(odata-raw value)  any/c

  value : any/c
Wraps any value to emit it without quoting. Use for special cases not covered by the other wrappers.

> (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?
Creates an inclusive date range filter. Equivalent to (and: (ge field (odata-date start-date)) (le field (odata-date end-date))).

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?
Creates an inclusive datetime range filter. Equivalent to (and: (ge field (odata-datetime start-datetime)) (le field (odata-datetime end-datetime))).

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?
Converts a filter expression to its OData string representation. Useful for debugging.

> (filter->string (and: (eq 'status "Open")
                        (or: (gt 'amount 1000)
                             (contains 'displayName "VIP"))))

"(status eq 'Open' and (amount gt 1000 or contains(displayName,'VIP')))"

procedure

(fexp? v)  boolean?

  v : any/c
Returns #t if v is a filter expression.