All Products
Search
Document Center

Data Lake Formation:Run standalone SQL queries on DLF Paimon tables with PyPaimon

Last Updated:Jul 08, 2026

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

  1. Download the PyPaimon offline wheel package: pypaimon-1.5.dev20260608.tar.gz

  2. 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
  3. 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: rest.

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 when using AccessKey authentication.

dlf.region

The region ID where DLF is deployed (for example, cn-hangzhou). For details, see Endpoints and public network access.

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>
Note

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

PaimonCatalog + datafusion.SessionContext (covered in this document)

Three-part name: paimon.<database>.<table>

DataFusion DataFrame with conversion methods such as .to_pylist(), .to_arrow_table(), and .to_pandas()

Integrating with the DataFusion ecosystem, or converting results directly to Arrow, Pandas, or Polars

pypaimon.SQLContext

Single-part table name after calling set_current_catalog or set_current_database

list[pyarrow.RecordBatch] (manual conversion to Pandas/Polars required)

Lightweight scenarios where you only need to execute SQL and retrieve RecordBatch results