-
Notifications
You must be signed in to change notification settings - Fork 29.4k
[SPARK-59068][SQL] Restore support for nested runtime filter attributes #58370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4ac63be
6019cec
467323b
1983e15
24ac1aa
ec4fa1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,26 +20,27 @@ package org.apache.spark.sql.execution.datasources.v2 | |
| import java.util.{Collections, Optional, OptionalLong} | ||
|
|
||
| import org.apache.spark.SparkException | ||
| import org.apache.spark.sql.AnalysisException | ||
| import org.apache.spark.sql.catalyst.analysis.{MultiInstanceRelation, NamedRelation, TimeTravelSpec} | ||
| import org.apache.spark.sql.catalyst.catalog.{CatalogColumnStat, CatalogStatistics} | ||
| import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeMap, AttributeReference, AttributeSet, Expression, SortOrder, V2ExpressionUtils} | ||
| import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeMap, AttributeReference, AttributeSet, Expression, NamedExpression, SortOrder, V2ExpressionUtils} | ||
| import org.apache.spark.sql.catalyst.plans.QueryPlan | ||
| import org.apache.spark.sql.catalyst.plans.logical.{ColumnStat, ExposesMetadataColumns, Histogram, HistogramBin, LeafNode, LogicalPlan, Statistics} | ||
| import org.apache.spark.sql.catalyst.plans.logical.statsEstimation.EstimationUtils | ||
| import org.apache.spark.sql.catalyst.streaming.{StreamingSourceIdentifyingName, Unassigned} | ||
| import org.apache.spark.sql.catalyst.trees.TreePattern.{DATA_SOURCE_V2_RELATION, DATA_SOURCE_V2_SCAN_RELATION, TreePattern} | ||
| import org.apache.spark.sql.catalyst.types.DataTypeUtils.toAttributes | ||
| import org.apache.spark.sql.catalyst.types.DataTypeUtils.{fromAttributes, toAttributes} | ||
| import org.apache.spark.sql.catalyst.util.{removeInternalMetadata, truncatedString, CharVarcharUtils} | ||
| import org.apache.spark.sql.connector.catalog.{CatalogPlugin, FunctionCatalog, Identifier, SupportsMetadataColumns, Table, TableCapability, TableCatalog, V2TableUtil} | ||
| import org.apache.spark.sql.connector.catalog.CatalogV2Implicits.CatalogHelper | ||
| import org.apache.spark.sql.connector.expressions.{FieldReference, NamedReference} | ||
| import org.apache.spark.sql.connector.read.{Scan, Statistics => V2Statistics, SupportsReportStatistics, SupportsRuntimeV2Filtering} | ||
| import org.apache.spark.sql.connector.read.colstats.{ColumnStatistics, Histogram => V2Histogram, HistogramBin => V2HistogramBin} | ||
| import org.apache.spark.sql.connector.read.streaming.{Offset, SparkDataStream} | ||
| import org.apache.spark.sql.errors.QueryCompilationErrors | ||
| import org.apache.spark.sql.internal.connector.{SupportsRuntimeCatalystFiltering, V2StatisticsUtils} | ||
| import org.apache.spark.sql.types.{DataType, StructType} | ||
| import org.apache.spark.sql.util.CaseInsensitiveStringMap | ||
| import org.apache.spark.util.ArrayImplicits._ | ||
| import org.apache.spark.util.Utils | ||
|
|
||
| /** | ||
|
|
@@ -201,16 +202,23 @@ case class DataSourceV2ScanRelation( | |
| * Resolved attributes that the scan declares for runtime filtering via | ||
| * [[SupportsRuntimeV2Filtering.filterAttributes]] or | ||
| * [[SupportsRuntimeCatalystFiltering.filterAttributes]]. Empty when the scan | ||
| * implements neither interface or exposes no attributes. | ||
| * implements neither interface or exposes no attributes. Accessing this value also validates | ||
| * attributes returned by [[SupportsRuntimeCatalystFiltering.fullyPushedFilterAttributes]]. | ||
| */ | ||
| lazy val runtimeFilterAttrs: AttributeSet = { | ||
| checkRuntimeFilteringInterfaces() | ||
| checkFullyPushedFilterAttrs() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking (P2): [P2] Resolve fully-pushed attributes on this entry path This helper only rejects nested references. A one-part value returned by |
||
| val filterAttrs = scan match { | ||
| case s: SupportsRuntimeV2Filtering => s.filterAttributes | ||
| case s: SupportsRuntimeCatalystFiltering => s.filterAttributes() | ||
| case _ => Array.empty[NamedReference] | ||
| } | ||
| resolveTopLevelFilterAttrs(filterAttrs) | ||
| resolveFilterAttrs(filterAttrs, "filterAttributes()") | ||
| } | ||
|
|
||
| private lazy val declaredFullyPushedRuntimeFilterAttrs: Array[NamedReference] = scan match { | ||
| case s: SupportsRuntimeCatalystFiltering => s.fullyPushedFilterAttributes() | ||
| case _ => Array.empty | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -219,27 +227,34 @@ case class DataSourceV2ScanRelation( | |
| */ | ||
| lazy val fullyPushedRuntimeFilterAttrs: AttributeSet = { | ||
| checkRuntimeFilteringInterfaces() | ||
| val filterAttrs = scan match { | ||
| case s: SupportsRuntimeCatalystFiltering => s.fullyPushedFilterAttributes() | ||
| case _ => Array.empty[NamedReference] | ||
| } | ||
| resolveTopLevelFilterAttrs(filterAttrs) | ||
| checkFullyPushedFilterAttrs() | ||
| resolveFilterAttrs( | ||
| declaredFullyPushedRuntimeFilterAttrs, "fullyPushedFilterAttributes()") | ||
| } | ||
|
|
||
| /** | ||
| * Resolves the given runtime-filter references against this relation's output. Both runtime | ||
| * filtering interfaces require each reference to be a top-level attribute of the read schema, | ||
| * so a nested reference is rejected. | ||
| * Resolves runtime-filter references against this relation's output. | ||
| * | ||
| * [[AttributeSet]] reduces nested references to their root attributes. This is sufficient for | ||
| * ordinary runtime-filter eligibility because Spark retains the post-scan predicate. | ||
| */ | ||
| private def resolveTopLevelFilterAttrs(filterAttrs: Array[NamedReference]): AttributeSet = { | ||
| filterAttrs.find(_.fieldNames.length > 1).foreach { ref => | ||
| throw SparkException.internalError( | ||
| s"Runtime filter attribute '${ref.fieldNames.mkString(".")}' declared by " + | ||
| s"${scan.getClass.getName} must be a top-level attribute of the scan read schema, " + | ||
| "but it is a nested reference.") | ||
| private def resolveFilterAttrs( | ||
| filterAttrs: Array[NamedReference], | ||
| method: String): AttributeSet = { | ||
| val resolvedAttrs = filterAttrs.map { ref => | ||
| try { | ||
| V2ExpressionUtils.resolveRef[NamedExpression](ref, this) | ||
| } catch { | ||
| case e: AnalysisException => | ||
| throw QueryCompilationErrors.cannotResolveDataSourceRuntimeFilterAttributeError( | ||
| attribute = ref.fieldNames, | ||
| method = method, | ||
| scanClass = scan.getClass.getName, | ||
| relationOutput = fromAttributes(output), | ||
| cause = e) | ||
| } | ||
| } | ||
| AttributeSet(V2ExpressionUtils.resolveRefs[Attribute]( | ||
| filterAttrs.toImmutableArraySeq, this)) | ||
| AttributeSet(resolvedAttrs) | ||
| } | ||
|
|
||
| override val nodePatterns: Seq[TreePattern] = Seq(DATA_SOURCE_V2_SCAN_RELATION) | ||
|
|
@@ -290,12 +305,23 @@ case class DataSourceV2ScanRelation( | |
| Statistics(sizeInBytes = conf.defaultSizeInBytes) | ||
| } | ||
|
|
||
| private def checkRuntimeFilteringInterfaces(): Unit = scan match { | ||
| case _: SupportsRuntimeV2Filtering with SupportsRuntimeCatalystFiltering => | ||
| throw SparkException.internalError( | ||
| "A scan must not implement both SupportsRuntimeV2Filtering and " + | ||
| s"SupportsRuntimeCatalystFiltering, but ${scan.getClass.getName} implements both.") | ||
| case _ => | ||
| private def checkRuntimeFilteringInterfaces(): Unit = { | ||
| scan match { | ||
| case _: SupportsRuntimeV2Filtering with SupportsRuntimeCatalystFiltering => | ||
| throw SparkException.internalError( | ||
| "A scan must not implement both SupportsRuntimeV2Filtering and " + | ||
| s"SupportsRuntimeCatalystFiltering, but ${scan.getClass.getName} implements both.") | ||
| case _ => | ||
| } | ||
| } | ||
|
|
||
| private def checkFullyPushedFilterAttrs(): Unit = { | ||
| declaredFullyPushedRuntimeFilterAttrs.find(_.fieldNames.length > 1).foreach { ref => | ||
| throw QueryCompilationErrors.nestedDataSourceFullyPushedRuntimeFilterAttributeError( | ||
| attribute = ref.fieldNames, | ||
| scanClass = scan.getClass.getName, | ||
| relationOutput = fromAttributes(output)) | ||
| } | ||
| } | ||
|
|
||
| override def doCanonicalize(): DataSourceV2ScanRelation = { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking (P2): [P2] Describe per-part quoting in V1 filter names
PredicateUtils.toV1usesNamedReference.toString, andFieldReference.toStringappliesquoteIfNeededto each path part. ThusSeq("parent", "child.with.dot")reaches this callback as parent.child.with.dot, not as an entirely unquoted name. A connector following this text can split or bind a legal nested name incorrectly. Please say that parts are dot-separated and individually quoted as needed, with parent.child.with.dotas an example.