You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by we...@apache.org on 2026/06/14 23:00:50 UTC
(spark) branch master updated: [SPARK-57440][SQL] Gate BIN BY behind spark.sql.binByRelationOperator.enabled
This is an automated email from the ASF dual-hosted git repository.
cloud-fan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 875982c4a65b [SPARK-57440][SQL] Gate BIN BY behind spark.sql.binByRelationOperator.enabled
875982c4a65b is described below
commit 875982c4a65bc703bce88fb17683b423d8f88255
Author: Nikolina Vraneš <ni...@databricks.com>
AuthorDate: Sun Jun 14 16:00:38 2026 -0700
[SPARK-57440][SQL] Gate BIN BY behind spark.sql.binByRelationOperator.enabled
### What changes were proposed in this pull request?
Follows up #56426 (BIN BY parsing and resolution). Tracked under SPARK-57440.
Adds an internal session config `spark.sql.binByRelationOperator.enabled` (default `false`) that gates the `BIN BY` relation operator at analysis time.
- New `BIN_BY_ENABLED` conf in `SQLConf` (config key `spark.sql.binByRelationOperator.enabled`).
- `ResolveBinBy` short-circuits to `QueryCompilationErrors.binByDisabledError()` when the conf is off, before resolving the operator.
- The gate reuses the existing `UNSUPPORTED_FEATURE.BIN_BY` condition (already raised by the execution stub) rather than introducing a new one. Its message is generalized to "The BIN BY relation operator is not yet supported." so both the analysis-time gate (flag off) and the execution stub (flag on) surface the same condition.
### Why are the changes needed?
`BIN BY` resolves today but has no physical execution yet (#56426 stubs it). A config gate keeps the incomplete operator disabled by default and lets it be enabled for development and testing, instead of exposing a half-implemented operator. Defaulting off is honest while execution is unimplemented.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
- `ResolveBinBySuite`: existing cases run with the flag enabled (via a `test()` override); a new `super.test` case asserts the operator is rejected with `UNSUPPORTED_FEATURE.BIN_BY` when the flag is off.
- `BinBySuite`: the stub test runs with the flag on; a new case asserts the flag-off gate fails at analysis.
- `SparkThrowableSuite` passes (error-conditions.json golden).
- `catalyst/Test/compile` + `sql/Test/compile` and scalastyle on the touched modules are clean.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Fable 5)
Closes #56487 from vranes/bin-by-oss-config.
Authored-by: Nikolina Vraneš <ni...@databricks.com>
Signed-off-by: Wenchen Fan <we...@databricks.com>
---
.../src/main/resources/error/error-conditions.json | 2 +-
.../spark/sql/catalyst/analysis/ResolveBinBy.scala | 2 +
.../spark/sql/errors/QueryCompilationErrors.scala | 6 +++
.../org/apache/spark/sql/internal/SQLConf.scala | 10 ++++
.../sql/catalyst/analysis/ResolveBinBySuite.scala | 20 +++++++
.../scala/org/apache/spark/sql/BinBySuite.scala | 61 +++++++++++++++-------
6 files changed, 81 insertions(+), 20 deletions(-)
diff --git a/common/utils/src/main/resources/error/error-conditions.json b/common/utils/src/main/resources/error/error-conditions.json
index 3d1ca002719b..e0bea26dc960 100644
--- a/common/utils/src/main/resources/error/error-conditions.json
+++ b/common/utils/src/main/resources/error/error-conditions.json
@@ -8094,7 +8094,7 @@
},
"BIN_BY" : {
"message" : [
- "Physical execution of BIN BY is not yet implemented."
+ "The BIN BY relation operator is not yet supported."
]
},
"CATALOG_INTERFACE_METHOD" : {
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveBinBy.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveBinBy.scala
index e2d1d41dfcb0..1f7d48787d78 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveBinBy.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveBinBy.scala
@@ -39,6 +39,8 @@ object ResolveBinBy extends Rule[LogicalPlan] {
override def apply(plan: LogicalPlan): LogicalPlan = plan.resolveOperatorsWithPruning(
_.containsPattern(UNRESOLVED_BIN_BY), ruleId) {
+ case _: UnresolvedBinBy if !SQLConf.get.getConf(SQLConf.BIN_BY_ENABLED) =>
+ throw QueryCompilationErrors.binByDisabledError()
case b: UnresolvedBinBy if !readyToResolve(b) => b
case b: UnresolvedBinBy => resolve(b)
}
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
index ff2fa1684202..7f76f48cc7ef 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
@@ -313,6 +313,12 @@ private[sql] object QueryCompilationErrors extends QueryErrorsBase with Compilat
messageParameters = Map("columnName" -> toSQLId(columnName)))
}
+ def binByDisabledError(): Throwable = {
+ new AnalysisException(
+ errorClass = "UNSUPPORTED_FEATURE.BIN_BY",
+ messageParameters = Map.empty)
+ }
+
def binByRequiresTopLevelColumnError(columnName: String): Throwable = {
new AnalysisException(
errorClass = "BIN_BY_REQUIRES_TOP_LEVEL_COLUMN",
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
index 83e28f6a28df..4f76dec323ef 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
@@ -5193,6 +5193,16 @@ object SQLConf {
.booleanConf
.createWithDefault(true)
+ val BIN_BY_ENABLED =
+ buildConf("spark.sql.binByRelationOperator.enabled")
+ .doc("Enable the BIN BY relation operator for aligning range-typed rows to " +
+ "fixed-width bin boundaries.")
+ .internal()
+ .version("4.3.0")
+ .withBindingPolicy(ConfigBindingPolicy.SESSION)
+ .booleanConf
+ .createWithDefault(false)
+
object StoreAssignmentPolicy extends Enumeration {
val ANSI, LEGACY, STRICT = Value
}
diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/ResolveBinBySuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/ResolveBinBySuite.scala
index 5ff103e8288e..5b182cb7c38c 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/ResolveBinBySuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/ResolveBinBySuite.scala
@@ -17,6 +17,9 @@
package org.apache.spark.sql.catalyst.analysis
+import org.scalactic.source.Position
+import org.scalatest.Tag
+
import org.apache.spark.SparkThrowable
import org.apache.spark.sql.catalyst.QueryPlanningTracker
import org.apache.spark.sql.catalyst.dsl.expressions._
@@ -28,6 +31,17 @@ import org.apache.spark.sql.types._
class ResolveBinBySuite extends AnalysisTest {
+ // BIN BY is gated off by default; run the resolution tests with it enabled. The dedicated
+ // gate test below uses `super.test` to observe the default-off behavior.
+ override protected def test(testName: String, testTags: Tag*)(testFun: => Any)
+ (implicit pos: Position): Unit = {
+ super.test(testName, testTags: _*) {
+ withSQLConf(SQLConf.BIN_BY_ENABLED.key -> "true") {
+ testFun
+ }
+ }
+ }
+
private val tsStart = $"ts_start".timestamp
private val tsEnd = $"ts_end".timestamp
private val tsStartNtz = $"ts_start".timestampNTZ
@@ -304,4 +318,10 @@ class ResolveBinBySuite extends AnalysisTest {
assert(appendedExprIds.distinct.size == appendedExprIds.size,
"appended BinBy attributes must have distinct exprIds across the two join sides")
}
+
+ // `super.test` escapes the suite-wide flag-on wrapper so this runs with the default (off).
+ super.test("BIN BY is gated off by default") {
+ assert(!SQLConf.get.getConf(SQLConf.BIN_BY_ENABLED))
+ expectError(unresolved(), "UNSUPPORTED_FEATURE.BIN_BY")
+ }
}
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/BinBySuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/BinBySuite.scala
index 36ea5c3ba08f..8ec0a1a44a8c 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/BinBySuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/BinBySuite.scala
@@ -17,32 +17,55 @@
package org.apache.spark.sql
-import org.apache.spark.SparkUnsupportedOperationException
+import org.apache.spark.{SparkThrowable, SparkUnsupportedOperationException}
+import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.test.SharedSparkSession
class BinBySuite extends QueryTest with SharedSparkSession {
+ private def createMetricsView(): Unit = {
+ spark.sql(
+ """SELECT TIMESTAMP '2024-01-01 00:00:00' AS ts_start,
+ | TIMESTAMP '2024-01-01 01:00:00' AS ts_end,
+ | CAST(1 AS DOUBLE) AS value""".stripMargin).createOrReplaceTempView("metrics")
+ }
+
+ private val binByQuery =
+ """SELECT * FROM metrics BIN BY (
+ | RANGE ts_start TO ts_end
+ | BIN WIDTH INTERVAL '5' MINUTE
+ | DISTRIBUTE UNIFORM (value)
+ |)""".stripMargin
+
test("BIN BY analyzes but physical execution is not yet implemented") {
+ withSQLConf(SQLConf.BIN_BY_ENABLED.key -> "true") {
+ withTempView("metrics") {
+ createMetricsView()
+ val df = spark.sql(binByQuery)
+
+ // Analysis is fully functional when the operator is enabled.
+ df.queryExecution.assertAnalyzed()
+
+ // Physical execution is stubbed until the follow-up PR; planning surfaces a clean
+ // UNSUPPORTED_FEATURE error rather than an internal error.
+ checkError(
+ exception = intercept[SparkUnsupportedOperationException] {
+ df.collect()
+ },
+ condition = "UNSUPPORTED_FEATURE.BIN_BY",
+ parameters = Map.empty[String, String])
+ }
+ }
+ }
+
+ test("BIN BY is gated off by default") {
withTempView("metrics") {
- spark.sql(
- """SELECT TIMESTAMP '2024-01-01 00:00:00' AS ts_start,
- | TIMESTAMP '2024-01-01 01:00:00' AS ts_end,
- | CAST(1 AS DOUBLE) AS value""".stripMargin).createOrReplaceTempView("metrics")
- val df = spark.sql(
- """SELECT * FROM metrics BIN BY (
- | RANGE ts_start TO ts_end
- | BIN WIDTH INTERVAL '5' MINUTE
- | DISTRIBUTE UNIFORM (value)
- |)""".stripMargin)
-
- // Analysis is fully functional in this PR.
- df.queryExecution.assertAnalyzed()
-
- // Physical execution is stubbed until the follow-up PR; planning surfaces a clean
- // UNSUPPORTED_FEATURE error rather than an internal error.
+ createMetricsView()
+ // Gated off, the operator is rejected at analysis with the same UNSUPPORTED_FEATURE.BIN_BY
+ // condition the execution stub raises when enabled.
checkError(
- exception = intercept[SparkUnsupportedOperationException] {
- df.collect()
+ exception = intercept[SparkThrowable] {
+ spark.sql(binByQuery).queryExecution.assertAnalyzed()
},
condition = "UNSUPPORTED_FEATURE.BIN_BY",
parameters = Map.empty[String, String])
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org