ExOdata4

An OData v4 query parser and Ecto query builder for Elixir. Parse OData query strings or full URIs and get back Ecto queries ready to execute against your own repo. Designed for exposing internal Elixir services to BI tools like Power BI and Excel.

Installation

Add ex_odata4 to your dependencies in mix.exs:

def deps do
  [
    {:ex_odata4, "~> 0.2.1"}
  ]
end

Configuration

Register your Ecto schemas in config/config.exs:

config :ex_odata4, schemas: %{
  "Orders" => MyApp.Orders,
  "Products" => MyApp.Products
}

OData field names are automatically derived from your schema — Ecto's snake_case atoms become PascalCase OData names. So :first_name is exposed as FirstName, :amount as Amount, and so on. No additional mapping is required.

Usage

Parsing a full OData URI

ExOdata4.parse_uri("/Orders?$filter=Amount gt 1000&$top=25&$skip=0")
|> MyApp.Repo.all()

Parsing a query string directly

ExOdata4.get("Orders", "$filter=Status eq 'active'&$orderby=Amount desc&$top=10")
|> MyApp.Repo.all()

Both functions return an %Ecto.Query{} ready to pipe into your repo.

$metadata

For Power BI and Excel, serve the $metadata document from your router:

# In your Phoenix router or Plug
get "/$metadata", fn conn, _ ->
  xml = ExOdata4.Metadata.generate(MyApp.Orders, namespace: "MyApp")
  conn
  |> put_resp_content_type("application/xml")
  |> send_resp(200, xml)
end

generate/2 accepts an optional :namespace (defaults to "Default").

Supported OData query options

Option Example
$filter$filter=Name eq 'John'
$top$top=25
$skip$skip=50
$orderby$orderby=Amount desc,Name asc
$metadataGET /$metadata

Filter operators

Operator Meaning
eq Equal
ne Not equal
gt Greater than
ge Greater than or equal
lt Less than
le Less than or equal
and Logical and
or Logical or

Filter functions

Function Example SQL
containscontains(Name, 'Jo')LIKE '%Jo%'
startswithstartswith(Name, 'Jo')LIKE 'Jo%'
endswithendswith(Email, '.com')LIKE '%.com'
tolowertolower(Name) eq 'john'lower(name) = 'john'
touppertoupper(Name) eq 'JOHN'upper(name) = 'JOHN'
yearyear(Date) eq 2024extract(year from date) = 2024
monthmonth(Date) eq 1extract(month from date) = 1
dayday(Date) eq 15extract(day from date) = 15
hourhour(Timestamp) gt 8extract(hour from timestamp) > 8

Functions can be combined with logical operators:

$filter=contains(Name, 'John') and Amount gt 100
$filter=tolower(Status) eq 'active' or year(Date) gt 2023

Supported literal types

EDM type mapping

$metadata automatically maps Ecto types to OData EDM types:

Ecto type EDM type
:stringEdm.String
:integerEdm.Int32
:floatEdm.Double
:decimalEdm.Decimal
:booleanEdm.Boolean
:dateEdm.Date
:utc_datetime / :naive_datetimeEdm.DateTimeOffset
:binary_idEdm.Guid

Not yet supported

Contributions are welcome. Open an issue or PR on GitHub.

License

MIT