Skip to content

[FLINK-40197][python] Add sql() to the DataFrame API - #29036

Open
fdolce wants to merge 2 commits into
apache:masterfrom
fdolce:pyflink-dataframe-sql
Open

[FLINK-40197][python] Add sql() to the DataFrame API#29036
fdolce wants to merge 2 commits into
apache:masterfrom
fdolce:pyflink-dataframe-sql

Conversation

@fdolce

@fdolce fdolce commented Aug 28, 2026

Copy link
Copy Markdown

What is the purpose of the change

Adds pyflink.dataframe.sql() (FLINK-40197), which executes a SQL SELECT query and returns the result as a DataFrame, so SQL and DataFrame operations can be mixed freely. Referenced DataFrames are registered as temporary views only for the duration of the call and dropped afterwards. By default the caller's variables are scanned and DataFrames are registered under their Python variable names (auto-bind); explicit keyword bindings choose the SQL names directly.

UDF/UDTF bindings are intentionally out of scope and will be added in a separate PR once the DataFrame API gains UDF support in general.

Brief change log

  • Added pyflink/dataframe/sql.py implementing sql(query, *, auto_bind=True, **bindings), exported as pyflink.dataframe.sql
  • Auto-bind is best-effort: it warns and skips names that are not valid SQL identifiers, collide with existing tables/views, or belong to a different TableEnvironment; it never shadows permanent catalog objects
  • Explicit bindings are strict (ValueError on temporary-view collisions, TypeError for non-DataFrame values), take precedence over auto-bind, and are the intentional way to shadow a permanent catalog table
  • Only SELECT-style queries are accepted; other statements raise ValueError pointing to TableEnvironment.execute_sql()
  • Registered views are always dropped after the call, including on failure and on partially-completed registration
  • Added the Sphinx API reference page (docs/reference/pyflink.dataframe/sql.rst)

Verifying this change

This change added tests and can be verified as follows:

  • Added pyflink/dataframe/tests/test_sql.py (27 tests) covering: SELECT-only enforcement and error translation, auto-bind over locals/globals (including locals-over-globals precedence), joins across auto-bound DataFrames, explicit bindings with and without auto-bind, precedence on name collisions, strict collision errors for explicit bindings, warn-and-skip on collisions with existing temporary views and permanent tables, identifier validation (invalid names, unicode names, SQL keywords via backtick quoting), cleanup after success / query failure / partial registration failure, rejection of DataFrames from a foreign TableEnvironment, rejection of UDF bindings, and composition of the result with the DataFrame API
  • Run with: cd flink-python && python -m pytest pyflink/dataframe/tests/test_sql.py

Does this pull request potentially affect one of the following parts:

  • Dependencies (does it add or upgrade a dependency): no
  • The public API, i.e., is any changed class annotated with @Public(Evolving): yes, the new sql() function is annotated @PublicEvolving
  • The serializers: no
  • The runtime per-record code paths (performance sensitive): no (view registration happens once per call at query compilation time)
  • Anything that affects deployment or recovery: no
  • The S3 file system connector: no

Documentation

  • Does this pull request introduce a new feature? yes
  • If yes, how is the feature documented? docs (Sphinx API reference page) and the sql() docstring with examples

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code 2.1.250 (Claude Fable 5)

@flinkbot

flinkbot commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands The @flinkbot bot supports the following commands:
  • @flinkbot run azure re-run the last Azure build

@dianfu dianfu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fdolce Thanks for the PR! LGTM overall. Have left just a few minor comments.

Comment thread flink-python/pyflink/dataframe/sql.py Outdated
}
finally:
del frame, caller
t_env = get_or_create_table_environment()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It always obtains the global TableEnvironment. This prevents a DataFrame created through the public from_table() API from being used unless the caller also mutates the global DataFrame context. This is a simple example to reproduce it:

custom_env = TableEnvironment.create(EnvironmentSettings.in_batch_mode())
source = pf.from_table(
    custom_env.from_elements([(1,), (2,)], ["a"])
)

pf.sql(
    "SELECT * FROM source",
    auto_bind=False,
    source=source,
)

We may need to resolve the environment from explicit bindings first, then auto-bound candidates, and fall back to the global environment if none is available.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I guess we need to check all the explicit bindings, and raise an error if they come from different environments too right?

f"cannot bind '{name}': a temporary table or view with this name "
"already exists"
)
t_env.create_temporary_view(name, value.to_table())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-bound names are validated before registration, while explicit names are passed directly to create_temporary_view().

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread flink-python/pyflink/dataframe/sql.py Outdated
@PublicEvolving()
def sql(query: str, *, auto_bind: bool = True, **bindings: DataFrame) -> DataFrame:
"""
Execute a SQL SELECT query and return the result as a :class:`DataFrame`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation and error message say that only SELECT queries are supported, but actually it also accepts query operations such as VALUES(1), UNION, INTERSECT, EXCEPT, and ORDER BY, etc. I guess we could just describe it simply as Execute a SQL query

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mh right. If it's ok to allow those too, then yes I'll make the comment less specific.

Add pyflink.dataframe.sql(), which executes a SQL SELECT query and
returns the result as a DataFrame. Referenced DataFrames are registered
as temporary views for the duration of the call and dropped afterwards:
auto-binding registers DataFrames from the caller's scope under their
variable names (best-effort, with warnings on invalid identifiers and
collisions, never shadowing permanent catalog objects), while explicit
keyword bindings are strict, take precedence, and may intentionally
shadow permanent catalog tables.

UDF/UDTF bindings are intentionally out of scope and will be added in a
separate PR once the DataFrame API gains UDF support in general.
@fdolce
fdolce force-pushed the pyflink-dataframe-sql branch from c71b1e2 to c948c3c Compare September 1, 2026 09:53
@fdolce
fdolce requested a review from dianfu September 1, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants