Query Paimon tables managed by DLF Catalog directly in a local Python environment using the PyPaimon built-in SQL engine. No Flink or Spark cluster is required, making this approach ideal for ad-hoc data exploration, model debugging, and report generation.
Prerequisites
Install dependencies
-
Download the PyPaimon offline wheel package: pypaimon-1.5.dev20260608.tar.gz
-
Install the PyPaimon package and other dependencies:
pip install \ pypaimon-1.5.dev20260608.tar.gz \ # PyPaimon offline package datafusion \ # SQL engine pypaimon-rust \ # DataFusion integration pyarrow pandas requests # data format & networking -
Run the following code to verify that datafusion and PaimonCatalog are available:
from datafusion import SessionContext from pypaimon_rust.datafusion import PaimonCatalog import datafusion print("datafusion:", datafusion.__version__) print("PaimonCatalog OK:", PaimonCatalog)
Configure the catalog connection
Define the DLF catalog connection parameters in your code:
CATALOG_OPTIONS = {
"metastore": "rest",
"uri": "http://<DLF-ENDPOINT>",
"warehouse": "<YOUR-CATALOG>",
"token.provider": "dlf",
"dlf.region": "<REGION-ID>",
"dlf.access-key-id": "<ACCESS-KEY-ID>",
"dlf.access-key-secret": "<ACCESS-KEY-SECRET>",
"dlf.oss-endpoint": "<OSS-ENDPOINT>",
}
The following table describes the parameters:
|
Parameter |
Description |
|
metastore |
Fixed value: |
|
uri |
The DLF Paimon REST endpoint. For details, see Endpoints and public network access. VPC access supports both HTTP and HTTPS. Public access requires HTTPS. |
|
warehouse |
The DLF catalog name. |
|
token.provider |
Fixed value: |
|
dlf.region |
The region ID where DLF is deployed (for example, |
|
dlf.access-key-id / dlf.access-key-secret |
The AccessKey pair for your Alibaba Cloud account or RAM user. |
|
dlf.oss-endpoint |
(Optional) The OSS public endpoint. Required for public access; can be omitted for VPC access. |
Permission requirements
When accessing DLF as a RAM user, ensure that the user has the required API and data permissions. For details, see Configure permissions.
Register the Paimon catalog with DataFusion
PaimonCatalog implements the DataFusion catalog provider protocol. After registration with a SessionContext, you can reference Paimon tables by the three-part name paimon.<database>.<table>.
from datafusion import SessionContext
from pypaimon_rust.datafusion import PaimonCatalog
ctx = SessionContext()
paimon = PaimonCatalog(CATALOG_OPTIONS)
ctx.register_catalog_provider("paimon", paimon)
print("catalogs:", ctx.catalog_names())
# -> {'paimon', 'datafusion'}
# List all databases (schemas) in the paimon catalog
print("databases:", ctx.catalog("paimon").names())
# List all tables in the default database
print("tables in default:", ctx.catalog("paimon").schema("default").names())
Query Paimon tables
The following examples assume that an employees table (with columns id, name, dept, and salary) and a departments table already exist. For table creation and data ingestion code, see the [Complete example](#complete-example) at the end of this document.
You can convert query results to different formats by using .to_pylist(), .to_arrow_table(), .to_pandas(), and other methods.
SELECT query
rows = ctx.sql("SELECT * FROM paimon.default.employees ORDER BY id").to_pylist()
print(rows[0])
# -> {'id': 1, 'name': 'Alice', 'dept': 'eng', 'salary': 9000}
WHERE filter, column projection, and ORDER BY
eng = ctx.sql("""
SELECT id, name, salary
FROM paimon.default.employees
WHERE dept = 'eng'
ORDER BY salary DESC
""").to_pylist()
print(eng)
# -> [{'id': 4, 'name': 'Dan', 'salary': 9500},
# {'id': 1, 'name': 'Alice', 'salary': 9000},
# {'id': 2, 'name': 'Bob', 'salary': 8500}]
GROUP BY aggregation
agg = ctx.sql("""
SELECT dept, COUNT(*) AS n, AVG(salary) AS avg_sal
FROM paimon.default.employees
GROUP BY dept
ORDER BY dept
""").to_pylist()
print(agg)
# -> [{'dept': 'eng', 'n': 3, 'avg_sal': 9000.0},
# {'dept': 'ops', 'n': 1, 'avg_sal': 6500.0},
# {'dept': 'sales', 'n': 2, 'avg_sal': 7100.0}]
JOIN
Self-join example:
result = ctx.sql("""
SELECT a.id AS id_a, b.id AS id_b, a.name
FROM paimon.default.employees a
JOIN paimon.default.employees b
ON a.id = b.id
WHERE a.dept = 'eng'
ORDER BY a.id
""").to_pylist()
Cross-table join example:
result = ctx.sql("""
SELECT e.id, e.name, e.dept, d.dept_full
FROM paimon.default.employees e
JOIN paimon.default.departments d
ON e.dept = d.dept
WHERE e.dept = 'eng'
ORDER BY e.id
""").to_pylist()
Convert query results
Convert query results to multiple formats for integration with different downstream processing frameworks.
Convert to an Arrow Table
arrow_tbl = ctx.sql(
"SELECT id, salary FROM paimon.default.employees ORDER BY id"
).to_arrow_table()
print(arrow_tbl.num_rows, "rows")
print(arrow_tbl.schema)
Convert to a Pandas DataFrame
df = ctx.sql(
"SELECT * FROM paimon.default.employees ORDER BY id"
).to_pandas()
print(df.head())
Integrate with Daft
If you use the Daft framework, convert the Arrow Table to a Daft DataFrame for further processing. For direct Daft-to-DLF Paimon integration, see Use Daft with DLF Paimon tables.
import daft
daft_df = daft.from_arrow(arrow_tbl)
daft_df.show()
Complete example
The following example demonstrates the end-to-end workflow: create a table, write data with PyPaimon, register the catalog with DataFusion, and run SQL queries.
from __future__ import annotations
import os
from datetime import datetime
import pyarrow as pa
from datafusion import SessionContext
from pypaimon_rust.datafusion import PaimonCatalog
from pypaimon import CatalogFactory, Schema
CATALOG_OPTIONS = {
"metastore": "rest",
"uri": "http://<DLF-ENDPOINT>",
"warehouse": "<YOUR-CATALOG>",
"token.provider": "dlf",
"dlf.region": "<REGION-ID>",
"dlf.access-key-id": os.environ["DLF_ACCESS_KEY_ID"],
"dlf.access-key-secret": os.environ["DLF_ACCESS_KEY_SECRET"],
"dlf.oss-endpoint": "<OSS-ENDPOINT>",
}
DATABASE = "default"
def main() -> None:
# 1) Create a table and write data using PyPaimon
pcat = CatalogFactory.create(CATALOG_OPTIONS)
pcat.create_database(DATABASE, True)
table_name = f"sql_demo_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
full_name = f"{DATABASE}.{table_name}"
pa_schema = pa.schema([
pa.field("id", pa.int32(), nullable=False),
pa.field("name", pa.string()),
pa.field("dept", pa.string()),
pa.field("salary", pa.int64()),
])
schema = Schema.from_pyarrow_schema(
pa_schema,
options={"bucket": "-1", "file.format": "parquet"},
)
pcat.create_table(full_name, schema, True)
tbl = pcat.get_table(full_name)
wb = tbl.new_batch_write_builder()
w = wb.new_write()
w.write_arrow(pa.Table.from_pydict({
"id": [1, 2, 3, 4, 5, 6],
"name": ["Alice", "Bob", "Carol", "Dan", "Eve", "Frank"],
"dept": ["eng", "eng", "sales", "eng", "sales", "ops"],
"salary": [9000, 8500, 7000, 9500, 7200, 6500],
}, schema=pa_schema))
wb.new_commit().commit(w.prepare_commit())
w.close()
print(f"created + wrote 6 rows to {full_name}")
# 2) Register the catalog with DataFusion
ctx = SessionContext()
ctx.register_catalog_provider("paimon", PaimonCatalog(CATALOG_OPTIONS))
# 3) Run SQL queries
rows = ctx.sql(
f"SELECT * FROM paimon.{DATABASE}.{table_name} ORDER BY id"
).to_pylist()
print("all rows:", rows)
eng = ctx.sql(
f"SELECT id, name, salary FROM paimon.{DATABASE}.{table_name} "
f"WHERE dept = 'eng' ORDER BY salary DESC"
).to_pylist()
print("eng:", eng)
agg = ctx.sql(
f"SELECT dept, COUNT(*) AS n, AVG(salary) AS avg_sal "
f"FROM paimon.{DATABASE}.{table_name} GROUP BY dept ORDER BY dept"
).to_pylist()
print("by dept:", agg)
if __name__ == "__main__":
main()
Use PyPaimon's SQL CLI
PyPaimon includes a built-in SQL CLI. After you specify the catalog connection in a YAML configuration file, you can start an interactive REPL or run a single SQL statement.
Save the catalog configuration as catalog.yaml:
metastore: rest
uri: http://<DLF-VPC-ENDPOINT>
warehouse: <YOUR-CATALOG>
token.provider: dlf
dlf.region: <REGION-ID>
dlf.access-key-id: <ACCESS-KEY-ID>
dlf.access-key-secret: <ACCESS-KEY-SECRET>
dlf.oss-endpoint: <OSS-ENDPOINT>
Security recommendation: Avoid storing AccessKey credentials in plain text in configuration files. Instead, inject credentials through environment variables. For example, set export DLF_ACCESS_KEY_ID=xxx and export DLF_ACCESS_KEY_SECRET=xxx, then render the YAML template with envsubst. In production environments, use STS temporary credentials.
Run queries using the paimon command:
# Start the interactive REPL
paimon --config /path/to/catalog.yaml sql
# Execute a single SQL statement
paimon --config /path/to/catalog.yaml sql "SELECT * FROM default.employees LIMIT 5"
# Specify the output format (default: table; also supports json)
paimon --config /path/to/catalog.yaml sql --format json "SELECT * FROM default.employees"
For additional options such as output format and result export, run paimon sql --help to view all available parameters.
SQL query interface comparison
PyPaimon provides two SQL query interfaces with different usage patterns. This document covers the PaimonCatalog + DataFusion approach. For the SQLContext approach, see the PyPaimon API documentation.
|
Interface |
Table reference |
Return type |
Use case |
|
|
Three-part name: |
DataFusion DataFrame with conversion methods such as |
Integrating with the DataFusion ecosystem, or converting results directly to Arrow, Pandas, or Polars |
|
|
Single-part table name after calling |
|
Lightweight scenarios where you only need to execute SQL and retrieve RecordBatch results |