Skip to main content

Database Engines

IterableData supports reading from SQL and NoSQL databases as iterable data sources. Database engines provide streaming, memory-efficient access to database tables and queries, enabling seamless integration with existing IterableData workflows.

Overview​

Database engines allow you to use databases as data sources in IterableData, treating database queries as iterable collections. This enables:

  • Streaming access: Large result sets are streamed in batches, avoiding memory issues
  • Unified interface: Database sources work with open_iterable(), convert(), and pipeline() just like file sources
  • Read-only safety: All database operations are read-only by default, preventing accidental data modification
  • Memory efficiency: Server-side cursors and batch processing minimize memory usage

Supported Database Engines​

PostgreSQL​

PostgreSQL support is available via the postgres or postgresql engine.

Status: ✅ Available
Driver: psycopg2
Optional dependency: psycopg2-binary

ClickHouse​

ClickHouse support is available via the clickhouse engine.

Status: ✅ Available
Driver: clickhouse-connect (official ClickHouse driver)
Optional dependency: clickhouse-connect

ClickHouse is a high-performance columnar OLAP database optimized for analytical workloads, real-time analytics, and data warehousing.

MySQL/MariaDB​

Status: ✅ Available
Driver: pymysql
Optional dependency: pymysql

Microsoft SQL Server​

Status: ✅ Available
Driver: pyodbc
Optional dependency: pyodbc

SQLite​

Status: ✅ Available
Driver: sqlite3 (standard library)

MongoDB​

Status: ✅ Available
Driver: pymongo
Optional dependency: pymongo

Elasticsearch/OpenSearch​

Status: ✅ Available
Driver: elasticsearch
Optional dependency: elasticsearch

Installation​

Database engines require optional dependencies. Install them based on your database:

PostgreSQL​

pip install psycopg2-binary

ClickHouse​

pip install clickhouse-connect

Or install all database dependencies:

pip install iterabledata[db]

Or install SQL databases only:

pip install iterabledata[db-sql]

Or install NoSQL databases only:

pip install iterabledata[db-nosql]

Basic Usage​

Opening a Database Source​

Use open_iterable() with a database engine and connection string:

from iterable import open_iterable

# Connect to PostgreSQL database
with open_iterable(
"postgresql://user:password@localhost:5432/mydb",
engine="postgres",
iterableargs={"query": "users"}
) as source:
for row in source:
print(row)

Using with Context Manager​

Always use the context manager (with statement) to ensure proper connection cleanup:

from iterable import open_iterable

with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "SELECT * FROM users WHERE active = TRUE"}
) as source:
# Process rows
for row in source:
process(row)
# Connection automatically closed

PostgreSQL​

Connection String Format​

PostgreSQL connection strings follow the standard PostgreSQL URI format:

postgresql://[user[:password]@][host][:port][/database][?param1=value1&...]

Examples:

# Basic connection
"postgresql://localhost/mydb"

# With authentication
"postgresql://user:password@localhost:5432/mydb"

# With SSL
"postgresql://user:password@localhost/mydb?sslmode=require"

# Using existing connection object
import psycopg2
conn = psycopg2.connect("postgresql://localhost/mydb")
# Pass connection object directly
with open_iterable(conn, engine="postgres", iterableargs={"query": "users"}) as source:
...

Query Parameters​

The iterableargs parameter accepts database-specific options:

query (required)​

SQL query string or table name:

# Table name (auto-builds SELECT * FROM table)
iterableargs={"query": "users"}

# SQL query string
iterableargs={"query": "SELECT id, name FROM users WHERE age > 18"}

# Complex query
iterableargs={"query": """
SELECT u.id, u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5
"""}

schema (optional)​

Schema name for table references:

iterableargs={
"query": "users",
"schema": "public"
}
# Builds: SELECT * FROM "public"."users"

columns (optional)​

List of column names for projection (only read specified columns):

iterableargs={
"query": "users",
"columns": ["id", "name", "email"]
}
# Builds: SELECT "id", "name", "email" FROM "users"

filter (optional)​

WHERE clause fragment (simple filtering):

iterableargs={
"query": "users",
"filter": "active = TRUE AND age > 18"
}
# Builds: SELECT * FROM "users" WHERE active = TRUE AND age > 18

Note: When using a full SQL query string, columns and filter parameters are ignored.

batch_size (optional)​

Number of rows per batch for streaming (default: 10000):

iterableargs={
"query": "users",
"batch_size": 5000
}

read_only (optional)​

Use read-only transaction (default: True):

iterableargs={
"query": "users",
"read_only": True # Default, prevents accidental writes
}

server_side_cursor (optional)​

Use server-side cursor for streaming (default: True):

iterableargs={
"query": "users",
"server_side_cursor": True # Recommended for large result sets
}

connect_args (optional)​

Additional arguments passed to psycopg2.connect():

iterableargs={
"query": "users",
"connect_args": {
"sslmode": "require",
"connect_timeout": 10
}
}

pool (optional)​

Connection pooling configuration. Connection pooling improves performance when processing multiple queries to the same database by reusing connections:

iterableargs={
"query": "users",
"pool": {
"enabled": True, # Enable pooling (default: True)
"min_size": 1, # Minimum pool size (default: 1)
"max_size": 10, # Maximum pool size (default: 10)
"timeout": 30.0, # Connection acquisition timeout in seconds (default: 30.0)
"max_idle": 300.0, # Maximum idle time before connection closed (default: 300.0 = 5 minutes)
}
}

Benefits of Connection Pooling:

  • ✅ Reduced overhead: Reuse connections instead of creating new ones
  • ✅ Better performance: Faster query execution for multiple operations
  • ✅ Concurrent access: Pool handles multiple concurrent requests
  • ✅ Automatic cleanup: Stale connections are automatically validated and replaced

When Pooling Helps:

  • Multiple queries to the same database
  • High-frequency queries
  • Concurrent database access
  • Long-running processes with many database operations

Disable Pooling:

iterableargs={
"query": "users",
"pool": {"enabled": False} # Disable pooling for this connection
}

Note: Connection pooling is enabled by default. Connections are automatically returned to the pool when the iterable is closed, allowing reuse across multiple operations.

Examples​

Reading a Table​

from iterable import open_iterable

# Read entire table
with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "users"}
) as source:
for row in source:
print(row)

Reading Specific Columns​

# Only read id, name, and email columns
with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={
"query": "users",
"columns": ["id", "name", "email"]
}
) as source:
for row in source:
print(row) # Only contains id, name, email keys

Filtering Rows​

# Filter active users
with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={
"query": "users",
"filter": "active = TRUE AND created_at > '2024-01-01'"
}
) as source:
for row in source:
print(row)

Complex SQL Query​

# Use full SQL for complex queries
with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={
"query": """
SELECT
u.id,
u.name,
COUNT(o.id) as order_count,
SUM(o.total) as total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.active = TRUE
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5
ORDER BY total_spent DESC
LIMIT 100
"""
}
) as source:
for row in source:
print(row)

Schema-Specific Table​

# Access table in specific schema
with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={
"query": "users",
"schema": "analytics"
}
) as source:
for row in source:
print(row)

Helper Functions​

List Tables​

The list_tables() function lists all tables in a PostgreSQL database:

from iterable.db.postgres import PostgresDriver

# List all tables
tables = PostgresDriver.list_tables("postgresql://localhost/mydb")
for table in tables:
print(f"{table['schema']}.{table['table']}: {table['row_count']} rows")

# List tables in specific schema
tables = PostgresDriver.list_tables(
"postgresql://localhost/mydb",
schema="public"
)

Returns a list of dictionaries with keys:

  • schema: Schema name
  • table: Table name
  • row_count: Estimated row count (may be None if statistics unavailable)

ClickHouse​

Connection String Format​

ClickHouse connection strings follow the standard URL format:

clickhouse://[user[:password]@][host][:port][/database][?param1=value1&...]

Examples:

# Basic connection
"clickhouse://localhost:9000/mydb"

# With authentication
"clickhouse://user:password@localhost:9000/mydb"

# With SSL (via connect_args)
import clickhouse_connect
client = clickhouse_connect.get_client(
host="localhost",
port=8443,
username="user",
password="password",
secure=True
)
# Pass client object directly
with open_iterable(client, engine="clickhouse", iterableargs={"query": "events"}) as source:
...

Query Parameters​

The iterableargs parameter accepts ClickHouse-specific options:

query (required) or table (alternative)​

SQL query string or table name:

# Table name (auto-builds SELECT * FROM table)
iterableargs={"query": "events"}

# Or use table parameter
iterableargs={"table": "events"}

# SQL query string
iterableargs={"query": "SELECT id, name, timestamp FROM events WHERE active = 1"}

# Complex query
iterableargs={"query": """
SELECT
toDate(timestamp) as date,
count() as event_count,
uniq(user_id) as unique_users
FROM events
WHERE timestamp >= now() - INTERVAL 7 DAY
GROUP BY date
ORDER BY date DESC
"""}

database (optional)​

Database name (if not in connection string):

iterableargs={
"query": "events",
"database": "analytics"
}
# Builds: SELECT * FROM `analytics`.`events`

columns (optional)​

List of column names for projection:

iterableargs={
"query": "events",
"columns": ["id", "name", "timestamp"]
}
# Builds: SELECT `id`, `name`, `timestamp` FROM `events`

filter (optional)​

WHERE clause fragment:

iterableargs={
"query": "events",
"filter": "active = 1 AND timestamp > '2024-01-01'"
}
# Builds: SELECT * FROM `events` WHERE active = 1 AND timestamp > '2024-01-01'

settings (optional)​

ClickHouse query settings dictionary:

iterableargs={
"query": "SELECT * FROM large_table",
"settings": {
"max_threads": 4,
"max_memory_usage": 10000000000,
"max_execution_time": 300
}
}

format (optional)​

Result format: "native" (default, more efficient) or "JSONEachRow" (text-based):

# Native format (default, binary, faster)
iterableargs={"query": "events", "format": "native"}

# JSONEachRow format (text-based, more compatible)
iterableargs={"query": "events", "format": "JSONEachRow"}

batch_size (optional)​

Number of rows per batch for streaming (default: 10000). Maps to ClickHouse's max_block_size:

iterableargs={
"query": "events",
"batch_size": 5000
}

read_only (optional)​

Validate queries are read-only (default: True):

iterableargs={
"query": "events",
"read_only": True # Default, prevents non-SELECT queries
}

Note: ClickHouse doesn't support explicit read-only transactions like PostgreSQL. Instead, queries are validated to ensure they only contain SELECT statements.

Examples​

Basic Table Query​

from iterable import open_iterable

# Query entire table
with open_iterable(
"clickhouse://user:password@localhost:9000/analytics",
engine="clickhouse",
iterableargs={"query": "events"}
) as source:
for row in source:
print(row)

Column Projection​

# Only read specific columns
with open_iterable(
"clickhouse://localhost:9000/analytics",
engine="clickhouse",
iterableargs={
"query": "events",
"columns": ["id", "name", "timestamp"]
}
) as source:
for row in source:
print(row) # Only contains id, name, timestamp keys

Filtering Rows​

# Filter active events
with open_iterable(
"clickhouse://localhost:9000/analytics",
engine="clickhouse",
iterableargs={
"query": "events",
"filter": "active = 1 AND timestamp > '2024-01-01'"
}
) as source:
for row in source:
print(row)

Using Query Settings​

# Optimize query with ClickHouse settings
with open_iterable(
"clickhouse://localhost:9000/analytics",
engine="clickhouse",
iterableargs={
"query": "SELECT * FROM large_table",
"settings": {
"max_threads": 8,
"max_memory_usage": 20000000000
}
}
) as source:
for row in source:
process(row)

Database Selection​

# Query different database without changing connection string
with open_iterable(
"clickhouse://localhost:9000",
engine="clickhouse",
iterableargs={
"query": "events",
"database": "analytics"
}
) as source:
for row in source:
print(row)

Helper Functions​

List Tables​

The list_tables() function lists all tables in a ClickHouse database:

from iterable.db.clickhouse import ClickHouseDriver

# List all tables
tables = ClickHouseDriver.list_tables("clickhouse://localhost:9000")
for table in tables:
print(f"{table['database']}.{table['table']}: {table['row_count']} rows")

# List tables in specific database
tables = ClickHouseDriver.list_tables(
"clickhouse://localhost:9000",
database="analytics"
)

Returns a list of dictionaries with keys:

  • database: Database name
  • table: Table name
  • row_count: Estimated row count (may be None if statistics unavailable)

Connection Pooling​

Connection pooling is enabled by default for all database engines. It improves performance by reusing database connections across multiple queries to the same database.

Basic Usage​

Connection pooling works automatically - no configuration needed:

from iterable import open_iterable

# First query - creates connection and pool
with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "SELECT * FROM users"}
) as source:
for row in source:
process(row)
# Connection returned to pool

# Second query - reuses connection from pool
with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "SELECT * FROM orders"}
) as source:
for row in source:
process(row)
# Connection returned to pool again

Custom Pool Configuration​

Configure pool size and behavior:

with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={
"query": "users",
"pool": {
"min_size": 2, # Pre-create 2 connections
"max_size": 20, # Allow up to 20 connections
"timeout": 60.0, # Wait up to 60s for connection
"max_idle": 600.0, # Close idle connections after 10 minutes
}
}
) as source:
for row in source:
process(row)

Disable Pooling​

Disable pooling for specific connections:

with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={
"query": "users",
"pool": {"enabled": False} # Use direct connection, no pooling
}
) as source:
for row in source:
process(row)

Pool Statistics​

Get pool statistics for monitoring:

from iterable.db.pooling import get_pool_stats

stats = get_pool_stats()
for pool_key, pool_info in stats.items():
print(f"{pool_key}: {pool_info['created']} connections created, "
f"{pool_info['available']} available")

Cleanup​

Close all pools when done (usually not necessary, but useful for testing):

from iterable.db.pooling import close_all_pools

# Close all connection pools
close_all_pools()

Integration with convert()​

Database sources work seamlessly with the convert() function:

from iterable.convert import convert

# Convert database table to Parquet file
convert(
fromfile="postgresql://localhost/mydb",
tofile="users.parquet",
iterableargs={"engine": "postgres", "query": "users"}
)

# Convert database query to JSONL
convert(
fromfile="postgresql://localhost/mydb",
tofile="active_users.jsonl",
iterableargs={
"engine": "postgres",
"query": "users",
"filter": "active = TRUE"
}
)

Integration with pipeline()​

Database sources can be used in data pipelines:

from iterable import open_iterable
from iterable.pipeline import Pipeline

# Create pipeline with database source
source = open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "users"}
)

destination = open_iterable("output.jsonl", mode="w")

pipeline = Pipeline(source, destination)
result = pipeline.run()

print(f"Processed {result.rows_processed} rows")

Integration with DataFrame Bridges​

Database sources support DataFrame conversion methods:

from iterable import open_iterable

with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "users"}
) as source:
# Convert to pandas DataFrame
df = source.to_pandas()

# Convert to pandas with chunking (for large datasets)
for chunk_df in source.to_pandas(chunksize=10000):
process_chunk(chunk_df)

# Convert to Polars DataFrame
df = source.to_polars()

# Convert to Dask DataFrame
df = source.to_dask()

Read-Only Behavior and Safety​

All database operations are read-only by default. This ensures:

  • ✅ Safety: No accidental data modification
  • ✅ ETL-friendly: Safe for analytical workloads
  • ✅ Transaction isolation: Read-only transactions prevent locks

The read_only parameter defaults to True and sets the database transaction to read-only mode. This prevents:

  • INSERT, UPDATE, DELETE statements
  • CREATE, DROP, ALTER statements
  • Other write operations

Note: Write support is planned for future releases.

Error Handling​

Database operations support error handling policies:

# Raise exceptions (default)
iterableargs={
"query": "users",
"on_error": "raise" # Default
}

# Skip errors and continue
iterableargs={
"query": "users",
"on_error": "skip"
}

# Warn and continue
iterableargs={
"query": "users",
"on_error": "warn"
}

Handling Import Errors​

If the required database driver is not installed:

from iterable import open_iterable

try:
with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "users"}
) as source:
for row in source:
print(row)
except ImportError as e:
print(f"Database driver not installed: {e}")
print("Install with: pip install psycopg2-binary")
except ConnectionError as e:
print(f"Connection failed: {e}")
except Exception as e:
print(f"Error: {e}")

Metrics Tracking​

Database sources track metrics during iteration:

from iterable import open_iterable

with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "users"}
) as source:
for row in source:
process(row)

# Get metrics after iteration
metrics = source.metrics
print(f"Rows read: {metrics['rows_read']}")
print(f"Elapsed time: {metrics['elapsed_seconds']:.2f} seconds")

Available metrics:

  • rows_read: Number of rows read
  • bytes_read: Bytes read (may be None for database sources)
  • elapsed_seconds: Time elapsed during iteration
  • start_time: Timestamp when iteration started

Limitations​

Reset Not Supported​

Database queries cannot be reset after iteration starts:

with open_iterable(
"postgresql://localhost/mydb",
engine="postgres",
iterableargs={"query": "users"}
) as source:
# First iteration works
for row in source:
print(row)

# Reset not supported - will raise NotImplementedError
try:
source.reset()
except NotImplementedError:
# Recreate the iterable if needed
pass

Write Operations Not Supported​

Database write operations (INSERT, UPDATE, DELETE) are not yet supported. This is planned for future releases.

Troubleshooting​

Database Driver Not Available​

Error: Database engine 'postgres' is not available

Solution: Install the required driver:

pip install psycopg2-binary

Or install all database dependencies:

pip install iterabledata[db]

Connection Failed​

Error: Failed to connect to PostgreSQL: ...

Solutions:

  • Verify connection string format
  • Check database server is running
  • Verify network connectivity
  • Check authentication credentials
  • Review firewall settings

Query Execution Failed​

Error: SQL syntax errors or permission issues

Solutions:

  • Verify SQL query syntax
  • Check table/schema names are correct
  • Verify database user has SELECT permissions
  • Test query directly in database client

Memory Issues with Large Result Sets​

Solutions:

  • Use batch_size parameter to control batch size
  • Ensure server_side_cursor=True (default)
  • Process data in chunks using DataFrame bridges with chunksize
  • Use filtering to reduce result set size

Import Errors​

Error: psycopg2-binary is required for PostgreSQL support

Solution: Install the missing dependency:

pip install psycopg2-binary