Skip to main content

OData V2/V4

ERPL-Web provides a universal OData reader that works seamlessly with both OData V2 and V4 services. Query any OData API like it's a native DuckDB table.

Overview

OData (Open Data Protocol) is a standard for building and consuming RESTful APIs. ERPL-Web makes OData services feel like regular databases.

What you get:

  • ✅ Automatic version detection (V2 or V4)
  • ✅ ATTACH services as databases
  • ✅ Predicate pushdown ($filter, $select, $top, $skip)
  • ✅ Expand navigation properties
  • ✅ Type mapping from EDM to DuckDB
  • ✅ Automatic pagination handling

Quick Start

-- Load the extension
LOAD erpl_web;

-- Attach an OData service
ATTACH 'https://services.odata.org/TripPinRESTierService'
AS trippin (TYPE odata);

-- Query it like a normal table
SELECT UserName, FirstName, LastName
FROM trippin.People
WHERE FirstName = 'Russell';

That's it! The OData service is now a queryable database.


Two Ways to Use OData

Attach the service once, query any entity set:

-- Attach OData V4 service
ATTACH 'https://services.odata.org/TripPinRESTierService'
AS trippin (TYPE odata);

-- List available tables
SHOW TABLES;

-- Query any entity
SELECT * FROM trippin.People LIMIT 10;
SELECT * FROM trippin.Airlines;
SELECT * FROM trippin.Airports;

2. Direct Read with odata_read

Query a specific entity set directly:

SELECT UserName, FirstName, LastName
FROM odata_read('https://services.odata.org/TripPinRESTierService/People')
WHERE UserName = 'russellwhyte';
tip

Use ATTACH for exploring services or querying multiple entities. Use odata_read for one-off queries or when you know exactly what you need.


Version Support

ERPL-Web automatically detects and handles both OData V2 and V4.

OData V4 Example

-- TripPin V4 Service
ATTACH 'https://services.odata.org/TripPinRESTierService'
AS trippin (TYPE odata);

SELECT
UserName,
FirstName,
LastName,
Gender
FROM trippin.People
WHERE Gender = 'Female'
LIMIT 5;

OData V2 Example

-- Northwind V2 Service
ATTACH 'https://services.odata.org/V2/Northwind/Northwind.svc'
AS northwind (TYPE odata);

SELECT
CustomerID,
CompanyName,
Country
FROM northwind.Customers
WHERE Country = 'Germany';

No configuration needed - ERPL-Web figures out the version automatically!


Predicate Pushdown

ERPL-Web translates SQL WHERE clauses into OData $filter queries, sending the filtering to the server. This dramatically improves performance.

Automatic $filter Translation

-- This SQL query...
SELECT * FROM trippin.People
WHERE FirstName = 'Russell' AND LastName = 'Whyte';

-- ...becomes this OData request:
-- GET /People?$filter=FirstName eq 'Russell' and LastName eq 'Whyte'

Supported Operators

SQL OperatorOData Translation
=eq
!= or <>ne
>gt
>=ge
<lt
<=le
ANDand
ORor
NOTnot

$top and $skip (LIMIT and OFFSET)

-- Get first 10 records
SELECT * FROM trippin.People LIMIT 10;
-- Translates to: $top=10

-- Skip first 20, get next 10
SELECT * FROM trippin.People LIMIT 10 OFFSET 20;
-- Translates to: $skip=20&$top=10

$select (Column Selection)

-- Only fetch specific columns
SELECT UserName, FirstName FROM trippin.People;
-- Translates to: $select=UserName,FirstName

$orderby (Sorting)

-- Sort by last name
SELECT * FROM trippin.People ORDER BY LastName;
-- Translates to: $orderby=LastName

-- Sort descending
SELECT * FROM trippin.People ORDER BY LastName DESC;
-- Translates to: $orderby=LastName desc

Expand Navigation Properties

OData services often have related entities. Use expand to fetch them in one query.

Basic Expand

-- Fetch people with their trips
SELECT
UserName,
FirstName,
Trips
FROM odata_read(
'https://services.odata.org/TripPinRESTierService/People',
expand := 'Trips'
);

Nested Expand

-- Expand multiple levels
SELECT UserName, Friends
FROM odata_read(
'https://services.odata.org/TripPinRESTierService/People',
expand := 'Friends($expand=Trips)'
);

Expand with Filter

-- Expand with filtering
SELECT UserName, Trips
FROM odata_read(
'https://services.odata.org/TripPinRESTierService/People',
expand := 'Trips($filter=Name eq ''Trip to France'')'
);

Authentication

Using Secrets

Most production OData services require authentication:

-- Create a secret for Basic Auth
CREATE SECRET sap_odata (
TYPE http_basic,
username 'your_username',
password 'your_password'
);

-- Attach with authentication (secret is auto-used)
ATTACH 'https://your-sap-server:port/sap/opu/odata/sap/SERVICE_NAME'
AS sap_service (TYPE odata);

SELECT * FROM sap_service.EntitySet;

Bearer Token

-- Create Bearer token secret
CREATE SECRET api_token (
TYPE http_bearer,
token 'your-bearer-token'
);

-- Use it automatically
ATTACH 'https://api.example.com/odata'
AS myservice (TYPE odata);

Explicit Secret Parameter

-- Pass secret explicitly in odata_read
SELECT *
FROM odata_read(
'https://your-server/odata/EntitySet',
secret := 'sap_odata'
);

Type Mapping

ERPL-Web maps OData EDM types to DuckDB types automatically.

EDM TypeDuckDB Type
Edm.StringVARCHAR
Edm.Int16SMALLINT
Edm.Int32INTEGER
Edm.Int64BIGINT
Edm.DecimalDECIMAL
Edm.DoubleDOUBLE
Edm.BooleanBOOLEAN
Edm.DateTimeTIMESTAMP
Edm.DateTimeOffsetTIMESTAMP WITH TIME ZONE
Edm.GuidVARCHAR
Edm.BinaryBLOB

Complex types and collections are mapped to DuckDB STRUCT and LIST types.


Pagination

ERPL-Web handles pagination automatically. If a service returns paginated results, the extension follows the @odata.nextLink to fetch all pages.

-- This might fetch multiple pages automatically
SELECT COUNT(*) FROM trippin.People;

Manual Pagination Control

-- Fetch specific page size
SELECT * FROM trippin.People LIMIT 100;

-- Skip to specific offset
SELECT * FROM trippin.People LIMIT 50 OFFSET 200;

Advanced Examples

SAP OData Services

-- Connect to SAP Gateway OData service
CREATE SECRET sap_gateway (
TYPE http_basic,
username 'SAP_USER',
password 'SAP_PASS'
);

ATTACH 'https://sap-server:port/sap/opu/odata/sap/ZSERVICE_SRV'
AS sap (TYPE odata);

-- Query SAP data
SELECT * FROM sap.Customers WHERE Country = 'US';

Complex Filters

-- Multiple conditions
SELECT * FROM northwind.Orders
WHERE
ShipCountry = 'USA'
AND OrderDate >= '1997-01-01'
AND Freight > 100
ORDER BY OrderDate DESC;

Joins Across Entity Sets

-- Join customers and orders
SELECT
c.CompanyName,
o.OrderID,
o.OrderDate
FROM northwind.Customers c
JOIN northwind.Orders o ON c.CustomerID = o.CustomerID
WHERE c.Country = 'Germany'
LIMIT 10;

Export to Parquet

-- Extract OData to Parquet file
COPY (
SELECT * FROM trippin.People
) TO 'people.parquet' (FORMAT PARQUET);

Use with DuckDB Features

-- Aggregate OData data
SELECT
Country,
COUNT(*) as customer_count,
AVG(Freight) as avg_freight
FROM northwind.Orders o
JOIN northwind.Customers c ON o.CustomerID = c.CustomerID
GROUP BY Country
ORDER BY customer_count DESC;

Performance Tips

1. Use Predicate Pushdown

-- Good: Filter is pushed to OData service
SELECT * FROM trippin.People WHERE Gender = 'Female';

-- Bad: Fetches all data, then filters locally
-- (Avoid if possible)

2. Select Only Needed Columns

-- Good: Only fetches 2 columns
SELECT UserName, FirstName FROM trippin.People;

-- Bad: Fetches all columns
SELECT * FROM trippin.People;

3. Use LIMIT for Exploration

-- Good: Quick data sample
SELECT * FROM trippin.People LIMIT 10;

-- Be careful with large datasets
-- SELECT * FROM large_entity_set; -- Could take a long time

4. Cache Results for Repeated Queries

-- Cache OData results in a table
CREATE TABLE people_cache AS
SELECT * FROM trippin.People;

-- Query the cache
SELECT * FROM people_cache WHERE FirstName = 'Russell';

Troubleshooting

Service Not Found

Error: OData service not found

Fixes:

  • Verify the service URL is correct
  • Check if service requires authentication
  • Ensure the service is accessible from your network

Entity Set Not Found

Error: Entity set 'XYZ' not found

Fixes:

  • Use SHOW TABLES to list available entity sets
  • Check capitalization (OData is case-sensitive)
  • Verify you're using the correct version (V2 vs V4)

Authentication Failed

Error: 401 Unauthorized

Fixes:

  • Verify credentials in your secret
  • Check if credentials have expired
  • Ensure user has permissions to access the service

Slow Queries

Solutions:

  • Use WHERE clauses to push filters to server
  • Select only needed columns
  • Use LIMIT for large datasets
  • Check OData service performance

Enable Tracing

-- Enable tracing to see what's happening
SET erpl_trace_enabled = TRUE;
SET erpl_trace_level = 'DEBUG';

-- Run your query
SELECT * FROM trippin.People LIMIT 5;

-- Check trace output for details

See Tracing & Diagnostics for more details.


Known Limitations

  • Write operations: ERPL-Web currently supports read-only operations (GET). POST/PUT/PATCH/DELETE are not yet supported.
  • Function imports: Custom OData functions may have limited support.
  • Batch requests: Not currently supported.

Examples by Use Case

Data Integration

-- Sync OData service to local DuckDB
CREATE TABLE local_customers AS
SELECT * FROM odata_read('https://api.example.com/odata/Customers');

-- Incremental updates
INSERT INTO local_customers
SELECT * FROM odata_read('https://api.example.com/odata/Customers')
WHERE ModifiedDate > (SELECT MAX(ModifiedDate) FROM local_customers);

Analytics

-- Analyze OData data with DuckDB
WITH monthly_sales AS (
SELECT
DATE_TRUNC('month', OrderDate) as month,
SUM(Freight) as total_freight
FROM northwind.Orders
GROUP BY month
)
SELECT * FROM monthly_sales
ORDER BY month;

Data Export

-- Export filtered data to CSV
COPY (
SELECT * FROM trippin.People
WHERE Gender = 'Female'
) TO 'female_travelers.csv' (HEADER, DELIMITER ',');

Next Steps


Summary

ERPL-Web's OData integration gives you:

Universal - Works with V2 and V4 automatically
Powerful - Predicate pushdown and expand support
Simple - ATTACH and query like normal SQL
Fast - Server-side filtering and pagination

Turn any OData service into a DuckDB table in seconds!