dongjoon-hyun opened a new pull request, #59735:
URL: https://github.com/apache/spark/pull/59735

   ### What changes were proposed in this pull request?
   
   When `PipelinesHandler` handles a `DefineFlow` command, it transforms the 
relation of the flow with `SparkConnectPlanner.transformRelation`. 
`spark.sql(query, df=...)` with DataFrame arguments sends a SQL-with-references 
`WITH_RELATIONS` relation, and `transformRelation` handles it with 
`executeSQLWithRefs`. That method analyzes each reference, registers it as a 
temporary view, and runs the root SQL with `session.sql`, all at flow 
registration.
   
   This PR adds `SparkConnectPlanner.transformPipelineFlowRelation` and passes 
it to `PipelinesHandler` instead of `transformRelation`. While it transforms 
the relation of a flow, `transformSqlWithRefs` does not execute a valid 
SQL-with-references `WITH_RELATIONS`. It parses the root SQL and binds each 
reference to its alias as a CTE relation (`UnresolvedWith`). `FlowAnalysis` 
already substitutes CTEs before it resolves `UnresolvedRelation`s as pipeline 
inputs, so it handles a dataset that a DataFrame argument reads in the same way 
as a dataset in the SQL text.
   
   `handleSqlCommand` and all other Spark Connect requests still use 
`executeSQLWithRefs`, so `spark.sql` with DataFrame arguments outside of a flow 
definition is unchanged.
   
   ### Why are the changes needed?
   
   Inside a flow function, `spark.sql` does not run the SQL. The server returns 
the relation as is (SPARK-54452), so that the flow analysis can resolve it 
later. With DataFrame arguments, however, `DefineFlow` analyzes it right away:
   
   1. The flow fails to register when a DataFrame argument reads a dataset of 
the same pipeline that does not exist in the catalog: a table or materialized 
view on the first run, or a temporary view on every run.
   
       ```python
       @dp.materialized_view
       def upstream():
           return spark.range(5)
   
       @dp.materialized_view
       def downstream():
           return spark.sql("SELECT * FROM {df}", 
df=spark.read.table("upstream"))
       ```
   
       ```
       pyspark.errors.exceptions.connect.AnalysisException: 
[TABLE_OR_VIEW_NOT_FOUND] The table or view `upstream` cannot be found. ...
       ```
   
   2. When the table already exists, for example from a previous run, the flow 
registers an analyzed plan. The flow analysis then does not see the read of 
`upstream` and does not record it as an input of `downstream`. Without the 
dependency, `downstream` can run before `upstream` is refreshed and read stale 
data, without an error.
   
   3. A command at the root that the SDP guard allows, such as `SHOW TABLES`, 
is executed at flow registration.
   
   The same query without DataFrame arguments, `spark.sql("SELECT * FROM 
upstream")`, works because its relation is only parsed at flow registration.
   
   In Spark 4.1.0 and 4.2.0, the SDP guard in `handleSqlCommand` also analyzed 
the relation, so the error in (1) was raised at the `spark.sql` call in the 
flow function. SPARK-57189 (#59425) moved it to flow registration.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. In a flow function, `spark.sql` with DataFrame arguments can read 
datasets of the same pipeline, and the flow analysis records them as inputs of 
the flow. The SQL is analyzed during graph resolution instead of flow 
registration, so an analysis error is reported as a flow resolution failure, as 
for `spark.sql` without DataFrame arguments.
   
   In a flow definition, DataFrame arguments are now bound as CTE relations 
instead of temporary views. A statement that refers to a DataFrame argument 
other than as a table in a query, such as `DESCRIBE TABLE {df}`, can no longer 
resolve it when a flow function returns the statement.
   
   ### How was this patch tested?
   
   Added two tests to `PythonPipelineSuite`. Both fail without this fix.
   
   - `SPARK-60494: referencing internal datasets through DataFrame arguments of 
spark.sql`: a new pipeline reads a materialized view with batch and streaming 
reads, and a temporary view with a batch read, all through DataFrame arguments. 
The test checks the inputs and streaming-ness of the flows, runs the pipeline, 
and checks the results. Without the fix, registration of the first flow fails 
with `TABLE_OR_VIEW_NOT_FOUND`.
   - `SPARK-60494: referencing an internal dataset through DataFrame arguments 
of spark.sql tracks the dependency when its table already exists`: Without the 
fix, the inputs of the flow are empty.
   
   Also passed the existing suites in `org.apache.spark.sql.connect.pipelines`, 
including `PythonPipelineSuite` and `SparkDeclarativePipelinesServerSuite`, as 
well as `SparkConnectPlannerSuite` and `SparkConnectProtoSuite`:
   
   ```
   build/sbt 'connect/testOnly org.apache.spark.sql.connect.pipelines.* 
*SparkConnectPlannerSuite *SparkConnectProtoSuite'
   ```
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Opus 5.5


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to