ODBC: Universal Database Connector
This guide uses the nanodbc extension by the DuckDB community. Thanks to the contributors who made universal database connectivity possible through ODBC!
Connect flAPI to any database that provides an ODBC driver - including Oracle, Teradata, DB2, Informix, SAP HANA, and proprietary enterprise systems. Perfect for legacy system integration and databases without native DuckDB extensions.
Why Use ODBC with flAPI?
"If it has an ODBC driver, flAPI can connect to it."
Many enterprise systems don't have modern APIs:
- 🏢 Legacy databases - Oracle 9i, DB2, Informix
- 🏭 Proprietary systems - Vendor-specific databases
- 💼 Enterprise software - SAP HANA, Teradata, Vertica
- 🖥️ Mainframe data - AS/400, z/OS DB2
- 📊 BI tools - Microsoft Access, FoxPro
flAPI + ODBC provides:
- ✅ Universal compatibility - 1000s of ODBC drivers available
- ✅ No custom code - Standard ODBC interface
- ✅ Modern REST APIs - Legacy data, modern access
- ✅ Caching layer - Speed up slow legacy systems
- ✅ SQL abstraction - Hide complex vendor SQL
Supported Databases (via ODBC)
Enterprise Databases
- Oracle - All versions (8i through 23c)
- IBM DB2 - z/OS, LUW, iSeries (AS/400)
- IBM Informix - All versions
- Teradata - Data warehouse platform
- SAP HANA - In-memory database
- Vertica - Columnar analytics database
Proprietary/Legacy
- Microsoft Access - .mdb and .accdb files
- FileMaker Pro - Custom business applications
- FoxPro - Legacy desktop databases
- Sybase ASE - Enterprise database
- Progress OpenEdge - Business application platform
Cloud/Modern (when no native driver)
- Amazon Redshift - Via ODBC driver
- Azure SQL Database - Alternative to native
- Google BigQuery - Alternative to native extension
Architecture
Installation
Step 1: Install ODBC Driver
On Linux (Ubuntu/Debian):
# Install ODBC manager
sudo apt-get install unixodbc unixodbc-dev
# Example: Install Oracle driver
sudo apt-get install oracle-instantclient-odbc
# Example: Install PostgreSQL driver (for testing)
sudo apt-get install odbc-postgresql
On macOS:
brew install unixodbc
# Example: PostgreSQL driver
brew install psqlodbc
On Windows:
Drivers usually come with database client software or download from vendor website.
Step 2: Configure ODBC DSN
# Define driver location
[Oracle ODBC]
Description = Oracle ODBC Driver
Driver = /usr/lib/oracle/19.3/client64/lib/libsqora.so.19.1
[PostgreSQL]
Description = PostgreSQL ODBC Driver
Driver = /usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so
# Define data sources
[OracleProd]
Driver = Oracle ODBC
ServerName = oracle-prod.company.com
Port = 1521
Database = PRODDB
UID = flapi_reader
PWD = ${ORACLE_PASSWORD}
[TeradataDW]
Driver = Teradata
DBCName = teradata-dw.company.com
Database = ANALYTICS
UID = api_user
PWD = ${TERADATA_PASSWORD}
Step 3: Configure flAPI
connections:
oracle-legacy:
init: |
-- Install nanodbc extension
INSTALL nanodbc FROM community;
LOAD nanodbc;
properties:
dsn: 'OracleProd' # Matches /etc/odbc.ini
connection_string: 'DSN=OracleProd;UID=flapi_reader;PWD=${ORACLE_PASSWORD}'
template:
environment-whitelist:
- '^ORACLE_.*'
- '^TERADATA_.*'
- '^DB2_.*'
Example 1: Oracle Database
Configuration
connections:
oracle-erp:
init: |
INSTALL nanodbc FROM community;
LOAD nanodbc;
properties:
dsn: 'OracleERP'
schema: 'HR'
Query Oracle Table
SELECT
employee_id,
first_name,
last_name,
email,
hire_date,
job_title,
salary,
department_name
FROM odbc_scan(
'{{{conn.dsn}}}',
'HR',
'EMPLOYEES'
)
WHERE 1=1
{{#params.department}}
AND department_name = '{{{params.department}}}'
{{/params.department}}
{{#params.min_salary}}
AND salary >= {{{params.min_salary}}}
{{/params.min_salary}}
ORDER BY hire_date DESC
{{#params.limit}}
LIMIT {{{params.limit}}}
{{/params.limit}}
url-path: /hr/employees/
template-source: employees.sql
connection:
- oracle-erp
cache-template: employees_cache.sql
cache-schedule: "0 */6 * * *" # Refresh every 6 hours
parameters:
department:
type: string
description: Filter by department
min_salary:
type: number
description: Minimum salary
limit:
type: integer
description: Max results
maximum: 1000
Example 2: IBM DB2 (Mainframe)
AS/400 Connection
connections:
db2-mainframe:
init: |
INSTALL nanodbc FROM community;
LOAD nanodbc;
properties:
connection_string: >
Driver={IBM i Access ODBC Driver};
System=as400.company.com;
UID=${DB2_USER};
PWD=${DB2_PASSWORD};
DefaultLibraries=PRODLIB;
Query Legacy System
-- Query AS/400 inventory system
SELECT
item_code,
item_description,
quantity_on_hand,
quantity_on_order,
reorder_point,
last_order_date
FROM odbc_query(
'{{{conn.connection_string}}}',
'SELECT * FROM PRODLIB.INVENTORY WHERE STATUS = ''A'''
)
{{#params.low_stock}}
WHERE quantity_on_hand < reorder_point
{{/params.low_stock}}
ORDER BY item_code
Example 3: Teradata Data Warehouse
connections:
teradata-dw:
init: |
INSTALL nanodbc FROM community;
LOAD nanodbc;
properties:
connection_string: >
Driver={Teradata};
DBCName=teradata.company.com;
Database=ANALYTICS;
UID=${TERADATA_USER};
PWD=${TERADATA_PASSWORD};
CharSet=UTF8;
-- Query Teradata warehouse
SELECT
sale_date,
product_category,
region,
SUM(revenue) as total_revenue,
SUM(quantity) as units_sold,
COUNT(DISTINCT customer_id) as unique_customers
FROM odbc_scan(
'{{{conn.dsn}}}',
'ANALYTICS',
'FACT_SALES'
)
WHERE sale_date >= CURRENT_DATE - INTERVAL '90' DAY
{{#params.region}}
AND region = '{{{params.region}}}'
{{/params.region}}
GROUP BY sale_date, product_category, region
ORDER BY sale_date DESC
Performance Optimization
Problem: ODBC Queries are Slow
Legacy databases can be very slow (5-30 seconds per query).
Solution: Aggressive Caching
# CRITICAL: Cache ODBC data locally
cache-template: oracle_cache.sql
cache-schedule: "0 */4 * * *" # Every 4 hours
cache-strategy: full-refresh
-- Pull data from Oracle into DuckDB cache
SELECT *
FROM odbc_scan('{{{conn.dsn}}}', 'HR', 'EMPLOYEES')
WHERE hire_date >= CURRENT_DATE - INTERVAL '5 year'
Performance improvement:
- ❌ Direct ODBC: 5-30 seconds
- ✅ Cached: 1-10ms (500-3000x faster!)
Incremental Refresh
-- Only fetch new/updated records
SELECT *
FROM odbc_scan('{{{conn.dsn}}}', 'HR', 'EMPLOYEES')
WHERE last_updated_date > (
SELECT COALESCE(MAX(last_updated_date), '1970-01-01')
FROM cached_employees
)
Vendor-Specific SQL Handling
Oracle-Specific Features
-- Oracle syntax: ROWNUM, SYSDATE, NVL
SELECT
employee_id,
NVL(manager_id, 0) as manager_id,
TO_CHAR(hire_date, 'YYYY-MM-DD') as hire_date
FROM odbc_query(
'{{{conn.dsn}}}',
'SELECT * FROM HR.EMPLOYEES WHERE ROWNUM <= 100'
)
DB2-Specific Features
-- DB2 syntax: FETCH FIRST, CURRENT DATE
SELECT *
FROM odbc_query(
'{{{conn.connection_string}}}',
'SELECT * FROM PRODLIB.ITEMS WHERE CREATED >= CURRENT DATE - 30 DAYS FETCH FIRST 1000 ROWS ONLY'
)
Teradata-Specific Features
-- Teradata syntax: TOP, CAST
SELECT *
FROM odbc_query(
'{{{conn.dsn}}}',
'SELECT TOP 1000 * FROM ANALYTICS.SALES WHERE SALE_DATE >= CAST(CURRENT_DATE - 90 AS DATE)'
)
Best Practices
1. Use Read-Only Credentials
-- In Oracle: Create read-only user
CREATE USER flapi_reader IDENTIFIED BY secure_password;
GRANT CONNECT, SELECT ANY TABLE TO flapi_reader;
-- Revoke write permissions
REVOKE INSERT, UPDATE, DELETE FROM flapi_reader;
2. Limit Data Volume
-- ❌ BAD: Pull entire table (millions of rows)
SELECT * FROM odbc_scan(dsn, 'PROD', 'SALES')
-- ✅ GOOD: Filter at source (only recent data)
SELECT * FROM odbc_scan(dsn, 'PROD', 'SALES')
WHERE sale_date >= CURRENT_DATE - INTERVAL '90' DAY
3. Connection Pooling
connections:
oracle-erp:
properties:
connection_string: >
DSN=Oracle;
UID=${USER};
PWD=${PASS};
Pooling=true;
Max Pool Size=10;
4. Timeout Configuration
properties:
connection_string: >
DSN=Oracle;
ConnectionTimeout=30;
CommandTimeout=60;
5. Test ODBC Connection First
# Test ODBC connection before configuring flAPI
isql OracleProd flapi_reader password
# Should connect successfully
SQL> SELECT COUNT(*) FROM HR.EMPLOYEES;
Troubleshooting
Issue: "Data source not found"
Error: [unixODBC][Driver Manager]Data source name not found
Solutions:
- Verify DSN exists in
/etc/odbc.ini
- Check DSN name matches exactly (case-sensitive)
- Test with
isql DSN_NAME username password
Issue: "Driver not found"
Error: [unixODBC][Driver Manager]Can't open lib
Solutions:
- Install ODBC driver package
- Update
/etc/odbcinst.ini
with correct driver path - Verify driver file exists:
ls -l /path/to/driver.so
Issue: Slow queries
Problem: Every query takes 10+ seconds
Solutions:
- Enable caching (most important!)
- Add indexes in source database
- Limit data pulled (WHERE clauses)
- Use scheduled batch loads instead of real-time
Issue: Character encoding problems
Error: Garbled text or special characters
Solution: Set charset in connection string:
connection_string: >
DSN=Oracle;
CharSet=UTF8;
NCharSet=UTF8;
Issue: Connection timeout
Error: Connection timeout expired
Solutions:
- Increase timeout:
ConnectionTimeout=60
- Check firewall rules
- Verify network connectivity to database
- Test direct connection (not through flAPI)
Security Considerations
1. Never Hardcode Credentials
# ❌ BAD
properties:
connection_string: 'DSN=Oracle;UID=admin;PWD=password123'
# ✅ GOOD
properties:
connection_string: 'DSN=Oracle;UID=${DB_USER};PWD=${DB_PASSWORD}'
2. Use Separate ODBC User
Don't use production/admin accounts:
-- Create dedicated read-only user
CREATE USER flapi_api_reader IDENTIFIED BY secure_password;
GRANT SELECT ON schema.table TO flapi_api_reader;
3. Encrypt Connections
connection_string: >
DSN=Oracle;
Encrypt=yes;
TrustServerCertificate=no;
4. IP Whitelisting
Configure database firewall to only allow flAPI server IPs.
Cost Optimization
1. Reduce Database Load
# Infrequent refreshes for static data
cache-schedule: "0 2 * * *" # Once daily at 2am
2. Off-Peak Queries
# Query during off-peak hours (cheaper on some systems)
cache-schedule: "0 2,14 * * *" # 2am and 2pm
3. Minimize Data Transfer
-- Only fetch required columns
SELECT id, name, status -- Not SELECT *
FROM odbc_scan(...)
Migration Examples
From Oracle to PostgreSQL
# Phase 1: ODBC to Oracle
connections:
legacy:
init: LOAD nanodbc;
properties:
dsn: 'Oracle'
# Phase 2: Migrate to PostgreSQL
connections:
modern:
init: LOAD postgres;
properties:
host: postgres.company.com
SQL stays mostly the same - flAPI provides abstraction!
Next Steps
- PostgreSQL: Modern alternative to legacy databases
- Caching Setup: Essential for ODBC performance
- Examples: See caching patterns
- Deployment: Deploy to production
Additional Resources
- DuckDB nanodbc Extension: Official extension docs
- unixODBC Documentation: ODBC manager docs
- ODBC Driver List: Database-specific connection strings
ODBC is your bridge from legacy to modern. Connect flAPI to that 20-year-old Oracle system, add caching, and suddenly it feels fast and modern. No database migration required!