You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add first-class Spark SQL support on Hop’s native Spark pipeline engine (see #7486).
Operators should be able to express multi-input joins, filters, windows, projections, and aggregations as SQL compiled by Spark’s Catalyst optimizer against intermediate pipeline Datasets — without chaining Hop Join/Filter/Calculator transforms or pushing SQL through JDBC / mapPartitions.
This builds on the dedicated Spark 4.1.x engine (no Beam). Classic Hop SQL transforms (ExecSql, Table Input, Dynamic SQL row) target RDBMS connections and are the wrong abstraction for Dataset-scoped SQL.
Motivation
Gap today
Why Spark SQL helps
Complex multi-table logic needs many Hop transforms
A single spark.sql statement gets Catalyst (joins, predicates, codegen)
Classic SQL transforms use JDBC
They do not compile SQL against hop intermediate Datasets
File I/O already established as Spark-only transforms
Same product model (supportedEngines = SparkPipelineEngine)
Reference: Apache Spark SQL — structured data processing via SQL and the Dataset API.
Goals
Dedicated Spark SQL transform (SparkSql), visible only for the native Spark engine.
Multi-input: N enabled incoming hops registered as temp views; SQL can JOIN / UNION them. Zero-input catalog / VALUES SQL allowed when the transform is on the active graph.
Driver-side Hop variable substitution (${...}) in SQL text before spark.sql(...) (string concat, not bind parameters).
Correct Hop IRowMeta for design-time field resolution and following generic mapPartitions handlers — v1 requires an explicit non-empty output field list.
Metrics, logging, and engine compatibility consistent with other native handlers.
Works under hop-run, GUI local[*], and spark-submit via MainSpark (no new deploy mode).
Unit + integration tests with local SparkSession; README + user-manual docs.
Non-goals (v1)
Streaming / Structured Streaming SQL (engine is batch-only)
Making classic JDBC SQL transforms “work natively” without a new transform
New canvas transform: Spark SQL (plugin id SparkSql).
Category: Big Data (same as Spark File Input/Output)
Engine restriction: native Spark only
Local engine: metadata-only stub (same as Spark File Input)
Module: plugins/engines/spark (no new Maven module)
Runtime flow
Hop graph (topo-sort)
→ for each predecessor: lookupPreviousDataset → createOrReplaceTempView(alias)
→ resolve variables in SQL on the driver
→ enforce single-statement + default-deny non-SELECT (isQuery heuristic)
→ spark.sql(resolvedSql)
→ project/cast via required SparkField list (TYPED_SOURCE — no string round-trip)
→ SparkNativeMetrics.track(Role.TRANSFORM)
→ put result Dataset in transformDatasetMap
→ drop input temp views after analyze (merge-gated)
Composition is hop-only: temp views exist only for hop predecessors of the current transform. No registerOutputView in v1.
flowchart LR
O[Spark File Input: orders] --> S[Spark SQL]
L[Spark File Input: lines] --> S
S --> OUT[Spark File Output]
Loading
Example SQL (views mapped from hop predecessors):
SELECTo.order_id,
sum(l.amount) AS total,
rank() OVER (ORDER BYsum(l.amount) DESC) AS rnk
FROM orders o
JOIN lines l ONo.order_id=l.order_idGROUP BYo.order_id
Metadata (proposed)
Property
Description
sql
Single Spark SQL query (SELECT / WITH … SELECT / VALUES / TABLE)
viewMappings
Optional hop transform name → SQL view alias (supports ${vars})
fields
Required non-empty output schema (SparkField list) for Hop row meta
allowNonSelect
Default false. Trusted-operator escape hatch for DDL/DML
Default view naming: sanitized predecessor transform name; auto-suffix _2, _3 on collision; fail hard on user-alias clash. Recommend explicit mappings for multi-input SQL.
Multi-input: native handler path (no auto-union). Mandatory use of lookupPreviousDataset for every predecessor so Filter/Switch target streams work (do not copy Merge Join’s direct map get).
Schema / projection
v1 requires output fields so pipelineMeta.getTransformFields(...) (used by SparkGenericTransformHandler) is never empty.
TYPED_SOURCE — Spark SQL: cast without string intermediate
Add HopSparkRowConverter.toRowMeta(StructType) for tooling / optional Get Fields (not a v1 empty-fields fallback).
Security
A Spark SQL transform runs with full authority of the job’s SparkSession (catalog, paths, UDFs per cluster config) — same trust level as Spark File Input / any Spark job.
Threat
Mitigation
Untrusted values expanded into SQL via variables
Document: variables are not binds; never inject untrusted strings
Basic: fingerprint / length; Detailed: full resolved SQL
Observability
Metrics: existing SparkNativeMetrics with Role.TRANSFORM (same mapPartitions boundary after SQL as other native handlers; Catalyst still optimizes inside the SQL plan)
What would you like to happen?
Add first-class Spark SQL support on Hop’s native Spark pipeline engine (see #7486).
Operators should be able to express multi-input joins, filters, windows, projections, and aggregations as SQL compiled by Spark’s Catalyst optimizer against intermediate pipeline
Datasets — without chaining Hop Join/Filter/Calculator transforms or pushing SQL through JDBC /mapPartitions.This builds on the dedicated Spark 4.1.x engine (no Beam). Classic Hop SQL transforms (
ExecSql,Table Input,Dynamic SQL row) target RDBMS connections and are the wrong abstraction for Dataset-scoped SQL.Motivation
spark.sqlstatement gets Catalyst (joins, predicates, codegen)supportedEngines = SparkPipelineEngine)Reference: Apache Spark SQL — structured data processing via SQL and the Dataset API.
Goals
SparkSql), visible only for the native Spark engine.JOIN/UNIONthem. Zero-input catalog /VALUESSQL allowed when the transform is on the active graph.${...}) in SQL text beforespark.sql(...)(string concat, not bind parameters).IRowMetafor design-time field resolution and following generic mapPartitions handlers — v1 requires an explicit non-empty output field list.local[*], andspark-submitviaMainSpark(no new deploy mode).SparkSession; README + user-manual docs.Non-goals (v1)
Proposed design
Product shape
New canvas transform: Spark SQL (plugin id
SparkSql).plugins/engines/spark(no new Maven module)Runtime flow
Composition is hop-only: temp views exist only for hop predecessors of the current transform. No
registerOutputViewin v1.Example SQL (views mapped from hop predecessors):
Metadata (proposed)
sqlSELECT/WITH … SELECT/VALUES/TABLE)viewMappings${vars})fieldsSparkFieldlist) for Hop row metaallowNonSelectfalse. Trusted-operator escape hatch for DDL/DMLDefault view naming: sanitized predecessor transform name; auto-suffix
_2,_3on collision; fail hard on user-alias clash. Recommend explicit mappings for multi-input SQL.Multi-input: native handler path (no auto-union). Mandatory use of
lookupPreviousDatasetfor every predecessor so Filter/Switch target streams work (do not copy Merge Join’s direct map get).Schema / projection
pipelineMeta.getTransformFields(...)(used bySparkGenericTransformHandler) is never empty.STRING_SOURCE— preserve CSV/text File Input cast behaviorTYPED_SOURCE— Spark SQL: cast without string intermediateHopSparkRowConverter.toRowMeta(StructType)for tooling / optional Get Fields (not a v1 empty-fields fallback).Security
A Spark SQL transform runs with full authority of the job’s
SparkSession(catalog, paths, UDFs per cluster config) — same trust level as Spark File Input / any Spark job.allowNonSelect=false+ strengthenedisQuery(CTE peel, multi-statement reject, main-verb allowlist)Observability
SparkNativeMetricswithRole.TRANSFORM(same mapPartitions boundary after SQL as other native handlers; Catalyst still optimizes inside the SQL plan)Key decisions
SparkSqltransform (not classic JDBC SQL)spark.sqlduring graph build (Dataset lineage)allowNonSelectescape hatch onlySparkFieldProjection:STRING_SOURCE(File I/O) vsTYPED_SOURCE(SQL)plugins/engines/sparkregisterOutputViewin v1 — hops onlyinput)lookupPreviousDatasetfor predecessorsImplementation plan (PRs)
SparkFieldProjection(STRING_SOURCE/TYPED_SOURCE) +StructType → IRowMeta; File I/O stays behavior-preservingspark-sql.adocSparkSessionPackage layout (sketch)
Test highlights
isQuery/ multi-statement (incl. CTE-wrapped DML), type matrix, meta check errorsVALUES/SELECTOpen questions (non-blocking for v1)
allowNonSelectis not a productized sink UX)spark.sql.caseSensitivebehaviorRelated
plugins/engines/spark/Issue Priority
Priority: 3
Issue Component
Component: Pipelines
Component: Transforms
Component: Documentation