[
https://issues.apache.org/jira/browse/FLINK-40197?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18108250#comment-18108250
]
Federico Dolce commented on FLINK-40197:
----------------------------------------
Hi [~dianfu] . I have this implemented, but I've spent a couple of days looking
for other ways of doing the auto bindings.
I don't have a definitively better solution than what's described, but I want
to highlight a few issues with the implementation as it is.
{quote}Auto-bind (default, auto_bind=True): sql scans the caller's local and
global variables for DataFrame and UDF/UDTF objects and registers each under
its Python variable name. Auto-bind is best-effort — it warns and skips names
that are not valid SQL identifiers or that collide with an existing
table/view/function, and it never shadows permanent catalog objects or built-in
functions.
{quote}
So, this implies that every DataFrame object in the caller's scope is
registered and dropped at every pf.sql call.
I know this is intended, but it means that, for example, in a notebook/script
with 10 DataFrames declared, a pf.sql("SELECT 1") creates and drops 10
temporary views that are never used. The drops can also fail, which adds noise,
and if a DataFrame in scope is assigned to a variable whose name is not a valid
SQL identifier, users get warnings about it even if it's never used in a query.
All this to say that it would be nice to limit which DataFrames are registered
as temporary views instead of registering everything every time, especially
considering that passing explicit bindings doesn't skip the auto-binding scan.
I've tried a few things, none of which I found optimal, so I'd like your input
on whether you think these issues are relevant. I'm fine with the original
approach if you've already gone through this and think the trade-offs are
acceptable.
The first idea is to find a way to extract which table names are referenced in
the SQL string.
* Using regexps kind of works, but I think it's a bad idea because you have to
handle SQL syntax properly (string literals, comments, quoting) or you keep
hitting edge cases where things don't work.
* I tried hooking into the Java-side parser to extract references, but there's
no easy way to extract references from an unvalidated statement, and we'd need
to add methods to the Java API (the Parser interface is @PublicEvolving) that
only serve this Python implementation, which I'm not sure is OK in the context
of this FLIP.
* At that point, I tried to instead validate the statement and read back the
errors. This too works, but we end up relying on Calcite's error message
format, and since only the first error is reported, we have to resubmit the
statement in a loop until there are no more "Object 'x' not found" errors,
which is quite ugly. The situation might be improved by making changes in the
java side, but again might go out of scope.
Another possible approach would be to use explicit placeholders, so that we
only look at variable names the user actually mentions. A middle way between
explicit bindings and auto-binding: pf.sql("SELECT * FROM \{df}") or
pf.sql("SELECT * FROM \{table}", table=df). But that's not the auto-binding
this issue asks for, so I'm here to ask what you think about this.
> Add sql bindings to DataFrame API
> ---------------------------------
>
> Key: FLINK-40197
> URL: https://issues.apache.org/jira/browse/FLINK-40197
> Project: Flink
> Issue Type: Sub-task
> Components: API / Python
> Reporter: Dian Fu
> Assignee: Federico Dolce
> Priority: Major
> Fix For: 2.4.0
>
>
> def sql(
> query: str,
> *,
> auto_bind: bool = True,
> **bindings,
> ) -> DataFrame
> Run a SQL SELECT query and return its result as a DataFrame, so SQL and the
> DataFrame API mix freely. DataFrames referenced in the query are exposed as
> temporary views, and @pf.udf / @pf.udtf functions as temporary functions;
> both are registered only for the duration of the call and dropped afterwards.
> There are two ways to make Python objects visible to the query:
> * Auto-bind (default, auto_bind=True): sql scans the caller's local and
> global variables for DataFrame and UDF/UDTF objects and registers each under
> its Python variable name. Auto-bind is best-effort — it warns and skips names
> that are not valid SQL identifiers or that collide with an existing
> table/view/function, and it never shadows permanent catalog objects or
> built-in functions.
> * Explicit bindings: pass keyword arguments to choose the SQL name yourself,
> e.g. pf.sql("SELECT * FROM s", s=df). Explicit bindings are strict (conflicts
> raise ValueError), take precedence over auto-bind on name collisions, and are
> required to intentionally shadow a catalog table/view or a built-in function.
> Only SELECT queries are supported (no INSERT / DDL). The returned DataFrame
> can be further transformed with the DataFrame API.
> Example:
> {code:python}
> import pyflink.dataframe as pf
> df1 = pf.from_dict({"a": [1, 2, 3], "b": ["x", "y", "z"]})
> df2 = pf.from_dict({"a": [1, 2, 3], "c": ["p", "q", "r"]})
> # Auto-bind: df1 / df2 are registered under their variable names
> joined = pf.sql("SELECT df1.a, b, c FROM df1 JOIN df2 ON df1.a = df2.a")
> # UDFs are auto-bound too
> @pf.udf
> def add_one(x: int) -> int:
> return x + 1
> incremented = pf.sql("SELECT add_one(a) AS a1 FROM df1")
> # Explicit bindings: pick the SQL names, turn off scanning
> result = pf.sql(
> "SELECT * FROM src WHERE a > 1",
> auto_bind=False,
> src=df1,
> )
> # Mix SQL and the DataFrame API
> pf.sql("SELECT a, b FROM df1").filter(pf.col("a") > 1).to_pandas()
> {code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)