This is an automated email from the ASF dual-hosted git repository.
voonhous pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/master by this push:
new 365d0e41d5f7 fix(spark): throw proper ParseExceptions in the six
extended SQL AST... (#19460)
365d0e41d5f7 is described below
commit 365d0e41d5f7cf7fecc43e7837cc45789d1dec4f
Author: voonhous <[email protected]>
AuthorDate: Mon Aug 3 14:23:24 2026 +0800
fix(spark): throw proper ParseExceptions in the six extended SQL AST...
(#19460)
* fix(spark): throw proper ParseExceptions in the six extended SQL AST
builders
On Spark 3.4+ the (String, ParserRuleContext) ParseException constructor
treats the string as an error class, so the builders' 28 raw throw sites
surfaced at runtime as SparkException [INTERNAL_ERROR] (or a bare
AssertionError for messages containing dots), and the three
construct-then-setStackTrace sites discarded the original cause. Route
all sites through a per-version parseException helper (message-based
constructor on 3.3-3.5, _LEGACY_ERROR_TEMP_0035 on 4.x where the message
constructor is private), fix the unbraced ${nonRef.describe}
interpolation, and tighten the invalid-partition-transform test to
intercept[ParseException].
Fixes #19450
* test(spark): harden extended-parser negative coverage and polish the 4.x
helper
Add a shared interceptParse helper that asserts a clean ParseException plus
a message substring; the previous checkExceptionContain assertions catch any
Throwable, and the pre-fix [INTERNAL_ERROR] text embeds the original
message,
so they passed while every site was broken. Convert the negative parser
assertions in TestBlobDataType and TestCreateTable, add four cases for the
previously untested visitor arms (typed literals, invalid INTERVAL, the
out-of-range AssertionError mode, mixed interval fields), pin the
${nonRef.describe} rendering, strip the trailing period in the 4.x helper
to avoid double periods, document the cross-profile prefix and the
_LEGACY_ERROR_TEMP_0035 coupling, and mark the grammar-unreachable interval
arm for the next pruning pass.
* test(spark): cover remaining reachable builder throw sites and pin review
asserts
Address review comments: pin the interpolation fix by asserting the
absence of ".describe" in the column-reference message, and assert the
restored setStackTrace by checking for an IntervalUtils frame on the
invalid-INTERVAL case. Address the codecov patch-coverage gap with a new
negative test covering seven more converted throw sites (unsupported
typed literal, hex IllegalArgumentException fallback, both
single-from-to-unit arms, non-numeric unit value, non-string from-to
value, unsupported from-to pair); interceptParse now returns the
ParseException for follow-on assertions. The remaining uncovered sites
re-wrap Spark-utility exceptions whose types changed across 3.3-4.2
(e.g. over-precision decimals throw SparkArithmeticException on 4.x,
bypassing the AnalysisException catch), are grammar-unreachable, or need
ANTLR error-recovery trees.
---
.../hudi/common/ExtendedParserTestHelpers.scala | 15 +++++
.../spark/sql/hudi/ddl/TestCreateTable.scala | 8 +--
.../sql/hudi/dml/schema/TestBlobDataType.scala | 72 +++++++++++++++++-----
.../HoodieSpark3_3ExtendedSqlAstBuilder.scala | 69 ++++++++++++---------
.../HoodieSpark3_4ExtendedSqlAstBuilder.scala | 71 ++++++++++++---------
.../HoodieSpark3_5ExtendedSqlAstBuilder.scala | 71 ++++++++++++---------
.../HoodieSpark4_0ExtendedSqlAstBuilder.scala | 69 ++++++++++++---------
.../HoodieSpark4_1ExtendedSqlAstBuilder.scala | 69 ++++++++++++---------
.../HoodieSpark4_2ExtendedSqlAstBuilder.scala | 69 ++++++++++++---------
9 files changed, 322 insertions(+), 191 deletions(-)
diff --git
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/common/ExtendedParserTestHelpers.scala
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/common/ExtendedParserTestHelpers.scala
index d2f721440228..dfc1dd7c7a73 100644
---
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/common/ExtendedParserTestHelpers.scala
+++
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/common/ExtendedParserTestHelpers.scala
@@ -18,6 +18,7 @@
package org.apache.spark.sql.hudi.common
import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.catalyst.parser.ParseException
import org.apache.spark.sql.catalyst.plans.logical.CreateTable
import org.apache.spark.sql.connector.expressions.{FieldReference,
LiteralValue, Transform}
import org.scalatest.Assertions
@@ -36,6 +37,20 @@ trait ExtendedParserTestHelpers extends Assertions {
protected def parseCreateTable(sql: String): CreateTable =
spark.sessionState.sqlParser.parsePlan(sql).asInstanceOf[CreateTable]
+ /**
+ * Asserts that `sql` fails with a [[ParseException]] (not merely some
Exception) whose message
+ * contains `expected`, and returns the exception so callers can assert
further properties (e.g.
+ * stack-trace frames). Matching stays on a substring so the same assertion
holds on Spark 4.x,
+ * where the builders wrap the message in an "Operation not allowed: "
prefix.
+ */
+ protected def interceptParse(sql: String)(expected: String): ParseException
= {
+ val e = intercept[ParseException] {
+ spark.sql(sql)
+ }
+ assert(e.getMessage.contains(expected), s"actual: ${e.getMessage}")
+ e
+ }
+
protected def transformByName(plan: CreateTable, name: String): Transform =
plan.partitioning.find(_.name == name)
.getOrElse(fail(s"No partition transform named $name in
${plan.partitioning.mkString(", ")}"))
diff --git
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/ddl/TestCreateTable.scala
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/ddl/TestCreateTable.scala
index cf7135b07cba..7c87f5c034a6 100644
---
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/ddl/TestCreateTable.scala
+++
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/ddl/TestCreateTable.scala
@@ -2286,7 +2286,7 @@ class TestCreateTable extends HoodieSparkSqlTestBase with
ExtendedParserTestHelp
test("test create table with VECTOR without dimension fails") {
withTempDir { tmp =>
val tableName = generateTableName
- checkExceptionContain(
+ interceptParse(
s"""
|CREATE TABLE $tableName (
| id BIGINT,
@@ -2304,7 +2304,7 @@ class TestCreateTable extends HoodieSparkSqlTestBase with
ExtendedParserTestHelp
withTempDir { tmp =>
val tableName = generateTableName
// Unsupported element type
- checkExceptionContain(
+ interceptParse(
s"""
|CREATE TABLE $tableName (
| id BIGINT,
@@ -2403,14 +2403,14 @@ class TestCreateTable extends HoodieSparkSqlTestBase
with ExtendedParserTestHelp
assertEquals(Some("/tmp/vec_path_tbl"), pathPlan.tableSpec.location)
// A 'path' option colliding with LOCATION is rejected by the option
cleaner.
- checkExceptionContain(
+ interceptParse(
"CREATE TABLE vec_dup_path_tbl (id BIGINT, embedding VECTOR(4)) USING
hudi " +
"OPTIONS ('path' = '/tmp/a') LOCATION '/tmp/b'")(
"Duplicated table paths")
// Each reserved table property (provider, location, owner) is rejected by
the property cleaner.
Seq("provider", "location", "owner").foreach { reserved =>
- checkExceptionContain(
+ interceptParse(
s"CREATE TABLE vec_reserved_$reserved (id BIGINT, embedding VECTOR(4))
USING hudi " +
s"TBLPROPERTIES ('$reserved' = 'x')")(
"reserved table property")
diff --git
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestBlobDataType.scala
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestBlobDataType.scala
index 42d7bfcfba68..368506bfd076 100644
---
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestBlobDataType.scala
+++
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestBlobDataType.scala
@@ -382,10 +382,10 @@ class TestBlobDataType extends HoodieSparkSqlTestBase
with ExtendedParserTestHel
// Endpoints where the end field does not follow the start are rejected by
both interval
// data-type visitors. The grammar only allows YEAR/MONTH -> MONTH and
DAY/HOUR/MINUTE/SECOND
// -> HOUR/MINUTE/SECOND, so these stay grammatical yet still hit the
builder's end <= start guard.
- checkExceptionContain(
+ interceptParse(
"CREATE TABLE blob_bad_ym (id BIGINT, bad INTERVAL MONTH TO MONTH, data
BLOB) USING hudi")(
"are not supported")
- checkExceptionContain(
+ interceptParse(
"CREATE TABLE blob_bad_dt (id BIGINT, bad INTERVAL SECOND TO HOUR, data
BLOB) USING hudi")(
"are not supported")
}
@@ -502,23 +502,61 @@ class TestBlobDataType extends HoodieSparkSqlTestBase
with ExtendedParserTestHel
}
test("Test parse CREATE TABLE with BLOB column and invalid partition
transforms") {
- // Non-numeric number of buckets. The builders' raw `new
ParseException(message, ctx)` sites
- // surface on Spark 3.4+ as SparkException [INTERNAL_ERROR] wrapping the
message text
- // (#19450), so this case asserts the message via a plain intercept.
- // TODO(#19450): tighten to intercept[ParseException] once the builders
throw it cleanly.
- val e = intercept[Exception] {
- spark.sql("CREATE TABLE blob_e1 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (bucket('x', id))")
- }
- assert(e.getMessage.contains("Invalid number of buckets"))
- // A non-column-reference where a column is required.
- checkExceptionContain(
- "CREATE TABLE blob_e2 (id BIGINT, data BLOB) USING hudi PARTITIONED BY
(bucket(4, 5))")(
- "Expected a column reference")
+ // Each case pins a distinct visitor arm of the extended AST builders; all
must surface as a
+ // clean ParseException on every Spark profile (#19450). Assertions stay
substring-based
+ // because the Spark 4.x builders add an "Operation not allowed: " prefix.
+ // Non-numeric number of buckets.
+ interceptParse("CREATE TABLE blob_e1 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (bucket('x', id))")(
+ "Invalid number of buckets")
+ // A non-column-reference where a column is required. The buggy
interpolation rendered the
+ // literal text "5.describe" (a superstring of the expected message), so
pin its absence too.
+ val e2 = interceptParse("CREATE TABLE blob_e2 (id BIGINT, data BLOB) USING
hudi PARTITIONED BY (bucket(4, 5))")(
+ "Expected a column reference for transform bucket: 5")
+ assert(!e2.getMessage.contains(".describe"))
// A single-field transform given more than one argument.
- checkExceptionContain(
- "CREATE TABLE blob_e3 (id BIGINT, ts DATE, data BLOB) USING hudi " +
- "PARTITIONED BY (years(id, ts))")(
+ interceptParse("CREATE TABLE blob_e3 (id BIGINT, ts DATE, data BLOB) USING
hudi PARTITIONED BY (years(id, ts))")(
"Too many arguments")
+ // Typed literal that fails to parse (visitTypeConstructor arm).
+ interceptParse("CREATE TABLE blob_e4 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(DATE 'nope', id))")(
+ "Cannot parse the DATE value: nope")
+ // Invalid INTERVAL literal: the builders copy the triggering exception's
stack trace onto the
+ // ParseException (the construct-then-setStackTrace arm), so the thrower
must be visible.
+ val e5 = interceptParse("CREATE TABLE blob_e5 (id BIGINT, data BLOB) USING
hudi PARTITIONED BY (myfunc(INTERVAL 'x', id))")(
+ "Cannot parse the INTERVAL value: x")
+ assert(e5.getStackTrace.exists(_.getClassName.contains("IntervalUtils")))
+ // Out-of-range fractional literal; pre-fix the message's interior dots
broke Spark 3.4+
+ // error-class lookup and surfaced as a bare AssertionError.
+ interceptParse("CREATE TABLE blob_e6 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (bucket(1e40F, id))")(
+ "does not fit in range")
+ // Mixed year-month and day-time interval fields.
+ interceptParse("CREATE TABLE blob_e7 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(INTERVAL '1 year 2 hours', id))")(
+ "Cannot mix year-month and day-time fields")
+ }
+
+ test("Test parse CREATE TABLE with BLOB column and invalid literal transform
arguments") {
+ // Remaining error arms of the literal and interval visitors, one SQL per
throw site; all must
+ // surface as a clean ParseException on every Spark profile (#19450).
+ // A typed literal whose type keyword has no visitor arm.
+ interceptParse("CREATE TABLE blob_e8 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(FOO 'bar', id))")(
+ "Literals of type 'FOO' are currently not supported")
+ // A hex literal with a non-hex character (the IllegalArgumentException
fallback arm).
+ interceptParse("CREATE TABLE blob_e9 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(X'zz', id))")(
+ "hexBinary")
+ // Multi-unit interval combined with a from-to unit.
+ interceptParse("CREATE TABLE blob_e10 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(INTERVAL 1 DAY 2 HOUR TO MINUTE, id))")(
+ "Can only have a single from-to unit in the interval literal syntax")
+ // From-to unit combined with a trailing multi-unit interval (the
error-recovery arm).
+ interceptParse("CREATE TABLE blob_e11 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(INTERVAL '1' DAY TO HOUR '2' MINUTE, id))")(
+ "Can only have a single from-to unit in the interval literal syntax")
+ // A non-numeric value in a unit-value pair.
+ interceptParse("CREATE TABLE blob_e12 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(INTERVAL 'x' DAY, id))")(
+ "Can only use numbers in the interval value part")
+ // A from-to interval whose value is not a string literal.
+ interceptParse("CREATE TABLE blob_e13 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(INTERVAL 1 DAY TO HOUR, id))")(
+ "The value of from-to unit must be a string")
+ // A from-to unit pair outside the supported YEAR TO MONTH / DAY TO SECOND
family.
+ interceptParse("CREATE TABLE blob_e14 (id BIGINT, data BLOB) USING hudi
PARTITIONED BY (myfunc(INTERVAL '1' MONTH TO HOUR, id))")(
+ "Intervals FROM month TO hour are not supported")
}
test("Test parse CREATE TABLE with BLOB column and file-format / row-format
clauses") {
diff --git
a/hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_3ExtendedSqlAstBuilder.scala
b/hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_3ExtendedSqlAstBuilder.scala
index 06a51d02bf24..5c04157c4e35 100644
---
a/hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_3ExtendedSqlAstBuilder.scala
+++
b/hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_3ExtendedSqlAstBuilder.scala
@@ -57,6 +57,17 @@ import scala.collection.JavaConverters._
class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf, delegate:
ParserInterface)
extends HoodieSqlBaseBaseVisitor[AnyRef] with Logging {
+ /**
+ * Returns a [[ParseException]] carrying `message` as plain human-readable
text, positioned at
+ * `ctx`. Spark 3.3's (String, ParserRuleContext) constructor takes the
human message directly,
+ * unlike Spark 3.4+ where that constructor treats the string as an error
class (#19450).
+ * The Spark 4.x builders render the same messages behind an "Operation not
allowed: " prefix,
+ * so cross-profile test assertions must stay substring-based.
+ */
+ private def parseException(message: String, ctx: ParserRuleContext):
ParseException = {
+ new ParseException(message, ctx)
+ }
+
protected def typedVisit[T](ctx: ParseTree): T = {
ctx.accept(this).asInstanceOf[T]
}
@@ -130,7 +141,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
def toLiteral[T](f: UTF8String => Option[T], t: DataType): Literal = {
f(UTF8String.fromString(value)).map(Literal(_, t)).getOrElse {
- throw new ParseException(s"Cannot parse the $valueType value: $value",
ctx)
+ throw parseException(s"Cannot parse the $valueType value: $value", ctx)
}
}
@@ -179,7 +190,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.fromString(value))
} catch {
case e: IllegalArgumentException =>
- val ex = new ParseException(s"Cannot parse the INTERVAL value:
$value", ctx)
+ val ex = parseException(s"Cannot parse the INTERVAL value:
$value", ctx)
ex.setStackTrace(e.getStackTrace)
throw ex
}
@@ -196,12 +207,12 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val padding = if (value.length % 2 != 0) "0" else ""
Literal(DatatypeConverter.parseHexBinary(padding + value))
case other =>
- throw new ParseException(s"Literals of type '$other' are currently
not supported.", ctx)
+ throw parseException(s"Literals of type '$other' are currently not
supported.", ctx)
}
} catch {
case e: IllegalArgumentException =>
val message = Option(e.getMessage).getOrElse(s"Exception parsing
$valueType")
- throw new ParseException(message, ctx)
+ throw parseException(message, ctx)
}
}
@@ -274,13 +285,13 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
try {
val rawBigDecimal = BigDecimal(rawStrippedQualifier)
if (rawBigDecimal < minValue || rawBigDecimal > maxValue) {
- throw new ParseException(s"Numeric literal $rawStrippedQualifier does
not " +
+ throw parseException(s"Numeric literal $rawStrippedQualifier does not
" +
s"fit in range [$minValue, $maxValue] for type $typeName", ctx)
}
Literal(converter(rawStrippedQualifier))
} catch {
case e: NumberFormatException =>
- throw new ParseException(e.getMessage, ctx)
+ throw parseException(e.getMessage, ctx)
}
}
@@ -338,7 +349,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
Literal(BigDecimal(raw).underlying())
} catch {
case e: AnalysisException =>
- throw new ParseException(e.message, ctx)
+ throw parseException(e.message, ctx)
}
}
@@ -389,7 +400,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (yearMonthFields.nonEmpty) {
if (dayTimeFields.nonEmpty) {
val literalStr = source(ctx)
- throw new ParseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
+ throw parseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
}
Literal(
calendarInterval.months,
@@ -446,18 +457,20 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (ctx.errorCapturingMultiUnitsInterval != null) {
val innerCtx = ctx.errorCapturingMultiUnitsInterval
if (innerCtx.unitToUnitInterval != null) {
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
}
visitMultiUnitsInterval(innerCtx.multiUnitsInterval)
} else if (ctx.errorCapturingUnitToUnitInterval != null) {
val innerCtx = ctx.errorCapturingUnitToUnitInterval
if (innerCtx.error1 != null || innerCtx.error2 != null) {
val errorCtx = if (innerCtx.error1 != null) innerCtx.error1 else
innerCtx.error2
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
}
visitUnitToUnitInterval(innerCtx.body)
} else {
- throw new ParseException("at least one time unit should be given for
interval literal", ctx)
+ // Unreachable through Hudi's pruned grammar: a bare INTERVAL keyword
binds to
+ // transformArgument's qualifiedName alternative first. Kept for parity
with Spark.
+ throw parseException("at least one time unit should be given for
interval literal", ctx)
}
}
@@ -479,7 +492,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
// units and become valid ones, e.g. '1 day 2 hour'.
// Ideally, we only ensure the value parts don't contain any units
here.
if (value.exists(Character.isLetter)) {
- throw new ParseException("Can only use numbers in the interval
value part for" +
+ throw parseException("Can only use numbers in the interval value
part for" +
s" multiple unit value pairs interval form, but got invalid
value: $value", ctx)
}
if (values(i).MINUS() == null) {
@@ -498,7 +511,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.concat(kvs: _*))
} catch {
case i: IllegalArgumentException =>
- val e = new ParseException(i.getMessage, ctx)
+ val e = parseException(i.getMessage, ctx)
e.setStackTrace(i.getStackTrace)
throw e
}
@@ -520,7 +533,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
}
}
}.getOrElse {
- throw new ParseException("The value of from-to unit must be a string",
ctx.intervalValue)
+ throw parseException("The value of from-to unit must be a string",
ctx.intervalValue)
}
try {
val from = ctx.from.getText.toLowerCase(Locale.ROOT)
@@ -533,12 +546,12 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.fromDayTimeString(value,
DayTimeIntervalType.stringToField(from),
DayTimeIntervalType.stringToField(to))
case _ =>
- throw new ParseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
}
} catch {
// Handle Exceptions thrown by CalendarInterval
case e: IllegalArgumentException =>
- val pe = new ParseException(e.getMessage, ctx)
+ val pe = parseException(e.getMessage, ctx)
pe.setStackTrace(e.getStackTrace)
throw pe
}
@@ -579,7 +592,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
HoodieSchema.parseTypeDescriptor(ctx.getText).asInstanceOf[HoodieSchema.Vector]
} catch {
case e: IllegalArgumentException =>
- throw new ParseException(s"Invalid VECTOR type: ${e.getMessage}",
ctx)
+ throw parseException(s"Invalid VECTOR type: ${e.getMessage}", ctx)
}
val sparkElemType = vectorSchema.getVectorElementType match {
case HoodieSchema.Vector.VectorElementType.FLOAT => FloatType
@@ -596,7 +609,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ("interval", Nil) => CalendarIntervalType
case (dt, params) =>
val dtStr = if (params.nonEmpty) s"$dt(${params.mkString(",")})" else
dt
- throw new ParseException(s"DataType $dtStr is not supported.", ctx)
+ throw parseException(s"DataType $dtStr is not supported.", ctx)
}
}
@@ -607,7 +620,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = YearMonthIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
YearMonthIntervalType(start, end)
} else {
@@ -622,7 +635,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = DayTimeIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
DayTimeIntervalType(start, end)
} else {
@@ -890,7 +903,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ref: FieldReference =>
ref
case nonRef =>
- throw new ParseException(s"Expected a column reference for transform
$name: $nonRef.describe", ctx)
+ throw parseException(s"Expected a column reference for transform
$name: ${nonRef.describe}", ctx)
}
}
@@ -899,7 +912,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
arguments: Seq[V2Expression]): FieldReference
= {
lazy val name: String = ctx.identifier.getText
if (arguments.size > 1) {
- throw new ParseException(s"Too many arguments for transform $name",
ctx)
+ throw parseException(s"Too many arguments for transform $name", ctx)
} else {
getFieldReference(ctx, arguments.head)
}
@@ -922,7 +935,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case LiteralValue(longValue, LongType) =>
longValue.asInstanceOf[Long].toInt
case lit =>
- throw new ParseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
+ throw parseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
}
val fields = arguments.tail.map(arg => getFieldReference(applyCtx,
arg))
@@ -960,7 +973,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
.map(typedVisit[Literal])
.map(lit => LiteralValue(lit.value, lit.dataType))
reference.orElse(literal)
- .getOrElse(throw new ParseException("Invalid transform argument", ctx))
+ .getOrElse(throw parseException("Invalid transform argument", ctx))
}
}
@@ -970,13 +983,13 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val legacyOn = conf.getConf(SQLConf.LEGACY_PROPERTY_NON_RESERVED)
properties.filter {
case (PROP_PROVIDER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_PROVIDER is a reserved table
property, please use the USING clause to specify it.", ctx)
+ throw parseException(s"$PROP_PROVIDER is a reserved table property,
please use the USING clause to specify it.", ctx)
case (PROP_PROVIDER, _) => false
case (PROP_LOCATION, _) if !legacyOn =>
- throw new ParseException(s"$PROP_LOCATION is a reserved table
property, please use the LOCATION clause to specify it.", ctx)
+ throw parseException(s"$PROP_LOCATION is a reserved table property,
please use the LOCATION clause to specify it.", ctx)
case (PROP_LOCATION, _) => false
case (PROP_OWNER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_OWNER is a reserved table property,
it will be set to the current user.", ctx)
+ throw parseException(s"$PROP_OWNER is a reserved table property, it
will be set to the current user.", ctx)
case (PROP_OWNER, _) => false
case _ => true
}
@@ -989,7 +1002,7 @@ class HoodieSpark3_3ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
var path = location
val filtered = cleanTableProperties(ctx, options).filter {
case (k, v) if k.equalsIgnoreCase("path") && path.nonEmpty =>
- throw new ParseException(s"Duplicated table paths found: '${path.get}'
and '$v'. LOCATION" +
+ throw parseException(s"Duplicated table paths found: '${path.get}' and
'$v'. LOCATION" +
s" and the case insensitive key 'path' in OPTIONS are all used to
indicate the custom" +
s" table path, you can only specify one of them.", ctx)
case (k, v) if k.equalsIgnoreCase("path") =>
diff --git
a/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_4ExtendedSqlAstBuilder.scala
b/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_4ExtendedSqlAstBuilder.scala
index 695cf3fe1462..2bb46bbaa784 100644
---
a/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_4ExtendedSqlAstBuilder.scala
+++
b/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_4ExtendedSqlAstBuilder.scala
@@ -29,7 +29,7 @@ import org.apache.spark.sql.catalyst.analysis._
import org.apache.spark.sql.catalyst.catalog.BucketSpec
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.parser.{ParseException, ParserInterface}
-import
org.apache.spark.sql.catalyst.parser.ParserUtils.{checkDuplicateClauses,
checkDuplicateKeys, operationNotAllowed, source, string, stringWithoutUnescape,
validate, withOrigin}
+import
org.apache.spark.sql.catalyst.parser.ParserUtils.{checkDuplicateClauses,
checkDuplicateKeys, command, operationNotAllowed, position, source, string,
stringWithoutUnescape, validate, withOrigin}
import org.apache.spark.sql.catalyst.plans._
import org.apache.spark.sql.catalyst.plans.logical._
import org.apache.spark.sql.catalyst.util.{DateTimeUtils, IntervalUtils}
@@ -57,6 +57,17 @@ import scala.collection.JavaConverters._
class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf, delegate:
ParserInterface)
extends HoodieSqlBaseBaseVisitor[AnyRef] with Logging {
+ /**
+ * Returns a [[ParseException]] carrying `message` as plain human-readable
text, positioned at
+ * `ctx`. The (String, ParserRuleContext) constructor treats the string as
an error class on
+ * Spark 3.4+, so use the message-based primary constructor (errorClass
stays None) (#19450).
+ * The Spark 4.x builders render the same messages behind an "Operation not
allowed: " prefix,
+ * so cross-profile test assertions must stay substring-based.
+ */
+ private def parseException(message: String, ctx: ParserRuleContext):
ParseException = {
+ new ParseException(Option(command(ctx)), message, position(ctx.start),
position(ctx.stop))
+ }
+
protected def typedVisit[T](ctx: ParseTree): T = {
ctx.accept(this).asInstanceOf[T]
}
@@ -130,7 +141,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
def toLiteral[T](f: UTF8String => Option[T], t: DataType): Literal = {
f(UTF8String.fromString(value)).map(Literal(_, t)).getOrElse {
- throw new ParseException(s"Cannot parse the $valueType value: $value",
ctx)
+ throw parseException(s"Cannot parse the $valueType value: $value", ctx)
}
}
@@ -179,7 +190,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.fromString(value))
} catch {
case e: IllegalArgumentException =>
- val ex = new ParseException(s"Cannot parse the INTERVAL value:
$value", ctx)
+ val ex = parseException(s"Cannot parse the INTERVAL value:
$value", ctx)
ex.setStackTrace(e.getStackTrace)
throw ex
}
@@ -196,12 +207,12 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val padding = if (value.length % 2 != 0) "0" else ""
Literal(DatatypeConverter.parseHexBinary(padding + value))
case other =>
- throw new ParseException(s"Literals of type '$other' are currently
not supported.", ctx)
+ throw parseException(s"Literals of type '$other' are currently not
supported.", ctx)
}
} catch {
case e: IllegalArgumentException =>
val message = Option(e.getMessage).getOrElse(s"Exception parsing
$valueType")
- throw new ParseException(message, ctx)
+ throw parseException(message, ctx)
}
}
@@ -274,13 +285,13 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
try {
val rawBigDecimal = BigDecimal(rawStrippedQualifier)
if (rawBigDecimal < minValue || rawBigDecimal > maxValue) {
- throw new ParseException(s"Numeric literal $rawStrippedQualifier does
not " +
+ throw parseException(s"Numeric literal $rawStrippedQualifier does not
" +
s"fit in range [$minValue, $maxValue] for type $typeName", ctx)
}
Literal(converter(rawStrippedQualifier))
} catch {
case e: NumberFormatException =>
- throw new ParseException(e.getMessage, ctx)
+ throw parseException(e.getMessage, ctx)
}
}
@@ -338,7 +349,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
Literal(BigDecimal(raw).underlying())
} catch {
case e: AnalysisException =>
- throw new ParseException(e.message, ctx)
+ throw parseException(e.message, ctx)
}
}
@@ -389,7 +400,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (yearMonthFields.nonEmpty) {
if (dayTimeFields.nonEmpty) {
val literalStr = source(ctx)
- throw new ParseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
+ throw parseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
}
Literal(
calendarInterval.months,
@@ -446,18 +457,20 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (ctx.errorCapturingMultiUnitsInterval != null) {
val innerCtx = ctx.errorCapturingMultiUnitsInterval
if (innerCtx.unitToUnitInterval != null) {
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
}
visitMultiUnitsInterval(innerCtx.multiUnitsInterval)
} else if (ctx.errorCapturingUnitToUnitInterval != null) {
val innerCtx = ctx.errorCapturingUnitToUnitInterval
if (innerCtx.error1 != null || innerCtx.error2 != null) {
val errorCtx = if (innerCtx.error1 != null) innerCtx.error1 else
innerCtx.error2
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
}
visitUnitToUnitInterval(innerCtx.body)
} else {
- throw new ParseException("at least one time unit should be given for
interval literal", ctx)
+ // Unreachable through Hudi's pruned grammar: a bare INTERVAL keyword
binds to
+ // transformArgument's qualifiedName alternative first. Kept for parity
with Spark.
+ throw parseException("at least one time unit should be given for
interval literal", ctx)
}
}
@@ -479,7 +492,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
// units and become valid ones, e.g. '1 day 2 hour'.
// Ideally, we only ensure the value parts don't contain any units
here.
if (value.exists(Character.isLetter)) {
- throw new ParseException("Can only use numbers in the interval
value part for" +
+ throw parseException("Can only use numbers in the interval value
part for" +
s" multiple unit value pairs interval form, but got invalid
value: $value", ctx)
}
if (values(i).MINUS() == null) {
@@ -498,7 +511,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.concat(kvs: _*))
} catch {
case i: IllegalArgumentException =>
- val e = new ParseException(i.getMessage, ctx)
+ val e = parseException(i.getMessage, ctx)
e.setStackTrace(i.getStackTrace)
throw e
}
@@ -520,7 +533,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
}
}
}.getOrElse {
- throw new ParseException("The value of from-to unit must be a string",
ctx.intervalValue)
+ throw parseException("The value of from-to unit must be a string",
ctx.intervalValue)
}
try {
val from = ctx.from.getText.toLowerCase(Locale.ROOT)
@@ -533,12 +546,12 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.fromDayTimeString(value,
DayTimeIntervalType.stringToField(from),
DayTimeIntervalType.stringToField(to))
case _ =>
- throw new ParseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
}
} catch {
// Handle Exceptions thrown by CalendarInterval
case e: IllegalArgumentException =>
- val pe = new ParseException(e.getMessage, ctx)
+ val pe = parseException(e.getMessage, ctx)
pe.setStackTrace(e.getStackTrace)
throw pe
}
@@ -579,7 +592,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
HoodieSchema.parseTypeDescriptor(ctx.getText).asInstanceOf[HoodieSchema.Vector]
} catch {
case e: IllegalArgumentException =>
- throw new ParseException(s"Invalid VECTOR type: ${e.getMessage}",
ctx)
+ throw parseException(s"Invalid VECTOR type: ${e.getMessage}", ctx)
}
val sparkElemType = vectorSchema.getVectorElementType match {
case HoodieSchema.Vector.VectorElementType.FLOAT => FloatType
@@ -596,7 +609,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ("interval", Nil) => CalendarIntervalType
case (dt, params) =>
val dtStr = if (params.nonEmpty) s"$dt(${params.mkString(",")})" else
dt
- throw new ParseException(s"DataType $dtStr is not supported.", ctx)
+ throw parseException(s"DataType $dtStr is not supported.", ctx)
}
}
@@ -607,7 +620,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = YearMonthIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
YearMonthIntervalType(start, end)
} else {
@@ -622,7 +635,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = DayTimeIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
DayTimeIntervalType(start, end)
} else {
@@ -890,7 +903,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ref: FieldReference =>
ref
case nonRef =>
- throw new ParseException(s"Expected a column reference for transform
$name: $nonRef.describe", ctx)
+ throw parseException(s"Expected a column reference for transform
$name: ${nonRef.describe}", ctx)
}
}
@@ -899,7 +912,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
arguments: Seq[V2Expression]): FieldReference
= {
lazy val name: String = ctx.identifier.getText
if (arguments.size > 1) {
- throw new ParseException(s"Too many arguments for transform $name",
ctx)
+ throw parseException(s"Too many arguments for transform $name", ctx)
} else {
getFieldReference(ctx, arguments.head)
}
@@ -922,7 +935,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case LiteralValue(longValue, LongType) =>
longValue.asInstanceOf[Long].toInt
case lit =>
- throw new ParseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
+ throw parseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
}
val fields = arguments.tail.map(arg => getFieldReference(applyCtx,
arg))
@@ -960,7 +973,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
.map(typedVisit[Literal])
.map(lit => LiteralValue(lit.value, lit.dataType))
reference.orElse(literal)
- .getOrElse(throw new ParseException("Invalid transform argument", ctx))
+ .getOrElse(throw parseException("Invalid transform argument", ctx))
}
}
@@ -970,13 +983,13 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val legacyOn = conf.getConf(SQLConf.LEGACY_PROPERTY_NON_RESERVED)
properties.filter {
case (PROP_PROVIDER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_PROVIDER is a reserved table
property, please use the USING clause to specify it.", ctx)
+ throw parseException(s"$PROP_PROVIDER is a reserved table property,
please use the USING clause to specify it.", ctx)
case (PROP_PROVIDER, _) => false
case (PROP_LOCATION, _) if !legacyOn =>
- throw new ParseException(s"$PROP_LOCATION is a reserved table
property, please use the LOCATION clause to specify it.", ctx)
+ throw parseException(s"$PROP_LOCATION is a reserved table property,
please use the LOCATION clause to specify it.", ctx)
case (PROP_LOCATION, _) => false
case (PROP_OWNER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_OWNER is a reserved table property,
it will be set to the current user.", ctx)
+ throw parseException(s"$PROP_OWNER is a reserved table property, it
will be set to the current user.", ctx)
case (PROP_OWNER, _) => false
case _ => true
}
@@ -989,7 +1002,7 @@ class HoodieSpark3_4ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
var path = location
val filtered = cleanTableProperties(ctx, options).filter {
case (k, v) if k.equalsIgnoreCase("path") && path.nonEmpty =>
- throw new ParseException(s"Duplicated table paths found: '${path.get}'
and '$v'. LOCATION" +
+ throw parseException(s"Duplicated table paths found: '${path.get}' and
'$v'. LOCATION" +
s" and the case insensitive key 'path' in OPTIONS are all used to
indicate the custom" +
s" table path, you can only specify one of them.", ctx)
case (k, v) if k.equalsIgnoreCase("path") =>
diff --git
a/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_5ExtendedSqlAstBuilder.scala
b/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_5ExtendedSqlAstBuilder.scala
index 1cfa82c02136..eda95a8c09ec 100644
---
a/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_5ExtendedSqlAstBuilder.scala
+++
b/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark3_5ExtendedSqlAstBuilder.scala
@@ -29,7 +29,7 @@ import org.apache.spark.sql.catalyst.analysis._
import org.apache.spark.sql.catalyst.catalog.BucketSpec
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.parser.{ParseException, ParserInterface}
-import
org.apache.spark.sql.catalyst.parser.ParserUtils.{checkDuplicateClauses,
checkDuplicateKeys, operationNotAllowed, source, string, stringWithoutUnescape,
validate, withOrigin}
+import
org.apache.spark.sql.catalyst.parser.ParserUtils.{checkDuplicateClauses,
checkDuplicateKeys, command, operationNotAllowed, position, source, string,
stringWithoutUnescape, validate, withOrigin}
import org.apache.spark.sql.catalyst.plans._
import org.apache.spark.sql.catalyst.plans.logical._
import org.apache.spark.sql.catalyst.util.{DateTimeUtils, IntervalUtils}
@@ -57,6 +57,17 @@ import scala.collection.JavaConverters._
class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf, delegate:
ParserInterface)
extends HoodieSqlBaseBaseVisitor[AnyRef] with Logging {
+ /**
+ * Returns a [[ParseException]] carrying `message` as plain human-readable
text, positioned at
+ * `ctx`. The (String, ParserRuleContext) constructor treats the string as
an error class on
+ * Spark 3.4+, so use the message-based primary constructor (errorClass
stays None) (#19450).
+ * The Spark 4.x builders render the same messages behind an "Operation not
allowed: " prefix,
+ * so cross-profile test assertions must stay substring-based.
+ */
+ private def parseException(message: String, ctx: ParserRuleContext):
ParseException = {
+ new ParseException(Option(command(ctx)), message, position(ctx.start),
position(ctx.stop))
+ }
+
protected def typedVisit[T](ctx: ParseTree): T = {
ctx.accept(this).asInstanceOf[T]
}
@@ -130,7 +141,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
def toLiteral[T](f: UTF8String => Option[T], t: DataType): Literal = {
f(UTF8String.fromString(value)).map(Literal(_, t)).getOrElse {
- throw new ParseException(s"Cannot parse the $valueType value: $value",
ctx)
+ throw parseException(s"Cannot parse the $valueType value: $value", ctx)
}
}
@@ -179,7 +190,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.fromString(value))
} catch {
case e: IllegalArgumentException =>
- val ex = new ParseException(s"Cannot parse the INTERVAL value:
$value", ctx)
+ val ex = parseException(s"Cannot parse the INTERVAL value:
$value", ctx)
ex.setStackTrace(e.getStackTrace)
throw ex
}
@@ -196,12 +207,12 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val padding = if (value.length % 2 != 0) "0" else ""
Literal(DatatypeConverter.parseHexBinary(padding + value))
case other =>
- throw new ParseException(s"Literals of type '$other' are currently
not supported.", ctx)
+ throw parseException(s"Literals of type '$other' are currently not
supported.", ctx)
}
} catch {
case e: IllegalArgumentException =>
val message = Option(e.getMessage).getOrElse(s"Exception parsing
$valueType")
- throw new ParseException(message, ctx)
+ throw parseException(message, ctx)
}
}
@@ -274,13 +285,13 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
try {
val rawBigDecimal = BigDecimal(rawStrippedQualifier)
if (rawBigDecimal < minValue || rawBigDecimal > maxValue) {
- throw new ParseException(s"Numeric literal $rawStrippedQualifier does
not " +
+ throw parseException(s"Numeric literal $rawStrippedQualifier does not
" +
s"fit in range [$minValue, $maxValue] for type $typeName", ctx)
}
Literal(converter(rawStrippedQualifier))
} catch {
case e: NumberFormatException =>
- throw new ParseException(e.getMessage, ctx)
+ throw parseException(e.getMessage, ctx)
}
}
@@ -338,7 +349,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
Literal(BigDecimal(raw).underlying())
} catch {
case e: AnalysisException =>
- throw new ParseException(e.message, ctx)
+ throw parseException(e.message, ctx)
}
}
@@ -389,7 +400,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (yearMonthFields.nonEmpty) {
if (dayTimeFields.nonEmpty) {
val literalStr = source(ctx)
- throw new ParseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
+ throw parseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
}
Literal(
calendarInterval.months,
@@ -446,18 +457,20 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (ctx.errorCapturingMultiUnitsInterval != null) {
val innerCtx = ctx.errorCapturingMultiUnitsInterval
if (innerCtx.unitToUnitInterval != null) {
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
}
visitMultiUnitsInterval(innerCtx.multiUnitsInterval)
} else if (ctx.errorCapturingUnitToUnitInterval != null) {
val innerCtx = ctx.errorCapturingUnitToUnitInterval
if (innerCtx.error1 != null || innerCtx.error2 != null) {
val errorCtx = if (innerCtx.error1 != null) innerCtx.error1 else
innerCtx.error2
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
}
visitUnitToUnitInterval(innerCtx.body)
} else {
- throw new ParseException("at least one time unit should be given for
interval literal", ctx)
+ // Unreachable through Hudi's pruned grammar: a bare INTERVAL keyword
binds to
+ // transformArgument's qualifiedName alternative first. Kept for parity
with Spark.
+ throw parseException("at least one time unit should be given for
interval literal", ctx)
}
}
@@ -479,7 +492,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
// units and become valid ones, e.g. '1 day 2 hour'.
// Ideally, we only ensure the value parts don't contain any units
here.
if (value.exists(Character.isLetter)) {
- throw new ParseException("Can only use numbers in the interval
value part for" +
+ throw parseException("Can only use numbers in the interval value
part for" +
s" multiple unit value pairs interval form, but got invalid
value: $value", ctx)
}
if (values(i).MINUS() == null) {
@@ -498,7 +511,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.concat(kvs: _*))
} catch {
case i: IllegalArgumentException =>
- val e = new ParseException(i.getMessage, ctx)
+ val e = parseException(i.getMessage, ctx)
e.setStackTrace(i.getStackTrace)
throw e
}
@@ -520,7 +533,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
}
}
}.getOrElse {
- throw new ParseException("The value of from-to unit must be a string",
ctx.intervalValue)
+ throw parseException("The value of from-to unit must be a string",
ctx.intervalValue)
}
try {
val from = ctx.from.getText.toLowerCase(Locale.ROOT)
@@ -533,12 +546,12 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.fromDayTimeString(value,
DayTimeIntervalType.stringToField(from),
DayTimeIntervalType.stringToField(to))
case _ =>
- throw new ParseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
}
} catch {
// Handle Exceptions thrown by CalendarInterval
case e: IllegalArgumentException =>
- val pe = new ParseException(e.getMessage, ctx)
+ val pe = parseException(e.getMessage, ctx)
pe.setStackTrace(e.getStackTrace)
throw pe
}
@@ -579,7 +592,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
HoodieSchema.parseTypeDescriptor(ctx.getText).asInstanceOf[HoodieSchema.Vector]
} catch {
case e: IllegalArgumentException =>
- throw new ParseException(s"Invalid VECTOR type: ${e.getMessage}",
ctx)
+ throw parseException(s"Invalid VECTOR type: ${e.getMessage}", ctx)
}
val sparkElemType = vectorSchema.getVectorElementType match {
case HoodieSchema.Vector.VectorElementType.FLOAT => FloatType
@@ -596,7 +609,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ("interval", Nil) => CalendarIntervalType
case (dt, params) =>
val dtStr = if (params.nonEmpty) s"$dt(${params.mkString(",")})" else
dt
- throw new ParseException(s"DataType $dtStr is not supported.", ctx)
+ throw parseException(s"DataType $dtStr is not supported.", ctx)
}
}
@@ -607,7 +620,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = YearMonthIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
YearMonthIntervalType(start, end)
} else {
@@ -622,7 +635,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = DayTimeIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
DayTimeIntervalType(start, end)
} else {
@@ -890,7 +903,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ref: FieldReference =>
ref
case nonRef =>
- throw new ParseException(s"Expected a column reference for transform
$name: $nonRef.describe", ctx)
+ throw parseException(s"Expected a column reference for transform
$name: ${nonRef.describe}", ctx)
}
}
@@ -899,7 +912,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
arguments: Seq[V2Expression]): FieldReference
= {
lazy val name: String = ctx.identifier.getText
if (arguments.size > 1) {
- throw new ParseException(s"Too many arguments for transform $name",
ctx)
+ throw parseException(s"Too many arguments for transform $name", ctx)
} else {
getFieldReference(ctx, arguments.head)
}
@@ -922,7 +935,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case LiteralValue(longValue, LongType) =>
longValue.asInstanceOf[Long].toInt
case lit =>
- throw new ParseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
+ throw parseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
}
val fields = arguments.tail.map(arg => getFieldReference(applyCtx,
arg))
@@ -960,7 +973,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
.map(typedVisit[Literal])
.map(lit => LiteralValue(lit.value, lit.dataType))
reference.orElse(literal)
- .getOrElse(throw new ParseException("Invalid transform argument", ctx))
+ .getOrElse(throw parseException("Invalid transform argument", ctx))
}
}
@@ -970,13 +983,13 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val legacyOn = conf.getConf(SQLConf.LEGACY_PROPERTY_NON_RESERVED)
properties.filter {
case (PROP_PROVIDER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_PROVIDER is a reserved table
property, please use the USING clause to specify it.", ctx)
+ throw parseException(s"$PROP_PROVIDER is a reserved table property,
please use the USING clause to specify it.", ctx)
case (PROP_PROVIDER, _) => false
case (PROP_LOCATION, _) if !legacyOn =>
- throw new ParseException(s"$PROP_LOCATION is a reserved table
property, please use the LOCATION clause to specify it.", ctx)
+ throw parseException(s"$PROP_LOCATION is a reserved table property,
please use the LOCATION clause to specify it.", ctx)
case (PROP_LOCATION, _) => false
case (PROP_OWNER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_OWNER is a reserved table property,
it will be set to the current user.", ctx)
+ throw parseException(s"$PROP_OWNER is a reserved table property, it
will be set to the current user.", ctx)
case (PROP_OWNER, _) => false
case _ => true
}
@@ -989,7 +1002,7 @@ class HoodieSpark3_5ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
var path = location
val filtered = cleanTableProperties(ctx, options).filter {
case (k, v) if k.equalsIgnoreCase("path") && path.nonEmpty =>
- throw new ParseException(s"Duplicated table paths found: '${path.get}'
and '$v'. LOCATION" +
+ throw parseException(s"Duplicated table paths found: '${path.get}' and
'$v'. LOCATION" +
s" and the case insensitive key 'path' in OPTIONS are all used to
indicate the custom" +
s" table path, you can only specify one of them.", ctx)
case (k, v) if k.equalsIgnoreCase("path") =>
diff --git
a/hudi-spark-datasource/hudi-spark4.0.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_0ExtendedSqlAstBuilder.scala
b/hudi-spark-datasource/hudi-spark4.0.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_0ExtendedSqlAstBuilder.scala
index 08863dc81a33..81982a2e9857 100644
---
a/hudi-spark-datasource/hudi-spark4.0.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_0ExtendedSqlAstBuilder.scala
+++
b/hudi-spark-datasource/hudi-spark4.0.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_0ExtendedSqlAstBuilder.scala
@@ -57,6 +57,17 @@ import scala.collection.JavaConverters._
class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf, delegate:
ParserInterface)
extends HoodieSqlBaseBaseVisitor[AnyRef] with Logging {
+ /**
+ * Returns a [[ParseException]] carrying `message` as human-readable text,
positioned at `ctx`.
+ * Spark 4.x only exposes error-class constructors, so route the message
through the class that
+ * ParserUtils.operationNotAllowed uses; output gains an "Operation not
allowed: " prefix (the
+ * trailing period is stripped to avoid doubling). If Spark drops
_LEGACY_ERROR_TEMP_0035, the
+ * intercept[ParseException] cases in TestBlobDataType fail loudly on the
4.x profiles (#19450).
+ */
+ private def parseException(message: String, ctx: ParserRuleContext):
ParseException = {
+ new ParseException("_LEGACY_ERROR_TEMP_0035", Map("message" ->
message.stripSuffix(".")), ctx)
+ }
+
protected def typedVisit[T](ctx: ParseTree): T = {
ctx.accept(this).asInstanceOf[T]
}
@@ -130,7 +141,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
def toLiteral[T](f: UTF8String => Option[T], t: DataType): Literal = {
f(UTF8String.fromString(value)).map(Literal(_, t)).getOrElse {
- throw new ParseException(s"Cannot parse the $valueType value: $value",
ctx)
+ throw parseException(s"Cannot parse the $valueType value: $value", ctx)
}
}
@@ -179,7 +190,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.fromString(value))
} catch {
case e: IllegalArgumentException =>
- val ex = new ParseException(s"Cannot parse the INTERVAL value:
$value", ctx)
+ val ex = parseException(s"Cannot parse the INTERVAL value:
$value", ctx)
ex.setStackTrace(e.getStackTrace)
throw ex
}
@@ -196,12 +207,12 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val padding = if (value.length % 2 != 0) "0" else ""
Literal(DatatypeConverter.parseHexBinary(padding + value))
case other =>
- throw new ParseException(s"Literals of type '$other' are currently
not supported.", ctx)
+ throw parseException(s"Literals of type '$other' are currently not
supported.", ctx)
}
} catch {
case e: IllegalArgumentException =>
val message = Option(e.getMessage).getOrElse(s"Exception parsing
$valueType")
- throw new ParseException(message, ctx)
+ throw parseException(message, ctx)
}
}
@@ -274,13 +285,13 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
try {
val rawBigDecimal = BigDecimal(rawStrippedQualifier)
if (rawBigDecimal < minValue || rawBigDecimal > maxValue) {
- throw new ParseException(s"Numeric literal $rawStrippedQualifier does
not " +
+ throw parseException(s"Numeric literal $rawStrippedQualifier does not
" +
s"fit in range [$minValue, $maxValue] for type $typeName", ctx)
}
Literal(converter(rawStrippedQualifier))
} catch {
case e: NumberFormatException =>
- throw new ParseException(e.getMessage, ctx)
+ throw parseException(e.getMessage, ctx)
}
}
@@ -338,7 +349,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
Literal(BigDecimal(raw).underlying())
} catch {
case e: AnalysisException =>
- throw new ParseException(e.message, ctx)
+ throw parseException(e.message, ctx)
}
}
@@ -389,7 +400,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (yearMonthFields.nonEmpty) {
if (dayTimeFields.nonEmpty) {
val literalStr = source(ctx)
- throw new ParseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
+ throw parseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
}
Literal(
calendarInterval.months,
@@ -446,18 +457,20 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (ctx.errorCapturingMultiUnitsInterval != null) {
val innerCtx = ctx.errorCapturingMultiUnitsInterval
if (innerCtx.unitToUnitInterval != null) {
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
}
visitMultiUnitsInterval(innerCtx.multiUnitsInterval)
} else if (ctx.errorCapturingUnitToUnitInterval != null) {
val innerCtx = ctx.errorCapturingUnitToUnitInterval
if (innerCtx.error1 != null || innerCtx.error2 != null) {
val errorCtx = if (innerCtx.error1 != null) innerCtx.error1 else
innerCtx.error2
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
}
visitUnitToUnitInterval(innerCtx.body)
} else {
- throw new ParseException("at least one time unit should be given for
interval literal", ctx)
+ // Unreachable through Hudi's pruned grammar: a bare INTERVAL keyword
binds to
+ // transformArgument's qualifiedName alternative first. Kept for parity
with Spark.
+ throw parseException("at least one time unit should be given for
interval literal", ctx)
}
}
@@ -479,7 +492,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
// units and become valid ones, e.g. '1 day 2 hour'.
// Ideally, we only ensure the value parts don't contain any units
here.
if (value.exists(Character.isLetter)) {
- throw new ParseException("Can only use numbers in the interval
value part for" +
+ throw parseException("Can only use numbers in the interval value
part for" +
s" multiple unit value pairs interval form, but got invalid
value: $value", ctx)
}
if (values(i).MINUS() == null) {
@@ -498,7 +511,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.concat(kvs: _*))
} catch {
case i: IllegalArgumentException =>
- val e = new ParseException(i.getMessage, ctx)
+ val e = parseException(i.getMessage, ctx)
e.setStackTrace(i.getStackTrace)
throw e
}
@@ -520,7 +533,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
}
}
}.getOrElse {
- throw new ParseException("The value of from-to unit must be a string",
ctx.intervalValue)
+ throw parseException("The value of from-to unit must be a string",
ctx.intervalValue)
}
try {
val from = ctx.from.getText.toLowerCase(Locale.ROOT)
@@ -533,12 +546,12 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.fromDayTimeString(value,
DayTimeIntervalType.stringToField(from),
DayTimeIntervalType.stringToField(to))
case _ =>
- throw new ParseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
}
} catch {
// Handle Exceptions thrown by CalendarInterval
case e: IllegalArgumentException =>
- val pe = new ParseException(e.getMessage, ctx)
+ val pe = parseException(e.getMessage, ctx)
pe.setStackTrace(e.getStackTrace)
throw pe
}
@@ -579,7 +592,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
HoodieSchema.parseTypeDescriptor(ctx.getText).asInstanceOf[HoodieSchema.Vector]
} catch {
case e: IllegalArgumentException =>
- throw new ParseException(s"Invalid VECTOR type: ${e.getMessage}",
ctx)
+ throw parseException(s"Invalid VECTOR type: ${e.getMessage}", ctx)
}
val sparkElemType = vectorSchema.getVectorElementType match {
case HoodieSchema.Vector.VectorElementType.FLOAT => FloatType
@@ -596,7 +609,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ("interval", Nil) => CalendarIntervalType
case (dt, params) =>
val dtStr = if (params.nonEmpty) s"$dt(${params.mkString(",")})" else
dt
- throw new ParseException(s"DataType $dtStr is not supported.", ctx)
+ throw parseException(s"DataType $dtStr is not supported.", ctx)
}
}
@@ -607,7 +620,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = YearMonthIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
YearMonthIntervalType(start, end)
} else {
@@ -622,7 +635,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = DayTimeIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
DayTimeIntervalType(start, end)
} else {
@@ -890,7 +903,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ref: FieldReference =>
ref
case nonRef =>
- throw new ParseException(s"Expected a column reference for transform
$name: $nonRef.describe", ctx)
+ throw parseException(s"Expected a column reference for transform
$name: ${nonRef.describe}", ctx)
}
}
@@ -899,7 +912,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
arguments: Seq[V2Expression]): FieldReference
= {
lazy val name: String = ctx.identifier.getText
if (arguments.size > 1) {
- throw new ParseException(s"Too many arguments for transform $name",
ctx)
+ throw parseException(s"Too many arguments for transform $name", ctx)
} else {
getFieldReference(ctx, arguments.head)
}
@@ -922,7 +935,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case LiteralValue(longValue, LongType) =>
longValue.asInstanceOf[Long].toInt
case lit =>
- throw new ParseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
+ throw parseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
}
val fields = arguments.tail.map(arg => getFieldReference(applyCtx,
arg))
@@ -960,7 +973,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
.map(typedVisit[Literal])
.map(lit => LiteralValue(lit.value, lit.dataType))
reference.orElse(literal)
- .getOrElse(throw new ParseException("Invalid transform argument", ctx))
+ .getOrElse(throw parseException("Invalid transform argument", ctx))
}
}
@@ -970,13 +983,13 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val legacyOn = conf.getConf(SQLConf.LEGACY_PROPERTY_NON_RESERVED)
properties.filter {
case (PROP_PROVIDER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_PROVIDER is a reserved table
property, please use the USING clause to specify it.", ctx)
+ throw parseException(s"$PROP_PROVIDER is a reserved table property,
please use the USING clause to specify it.", ctx)
case (PROP_PROVIDER, _) => false
case (PROP_LOCATION, _) if !legacyOn =>
- throw new ParseException(s"$PROP_LOCATION is a reserved table
property, please use the LOCATION clause to specify it.", ctx)
+ throw parseException(s"$PROP_LOCATION is a reserved table property,
please use the LOCATION clause to specify it.", ctx)
case (PROP_LOCATION, _) => false
case (PROP_OWNER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_OWNER is a reserved table property,
it will be set to the current user.", ctx)
+ throw parseException(s"$PROP_OWNER is a reserved table property, it
will be set to the current user.", ctx)
case (PROP_OWNER, _) => false
case _ => true
}
@@ -989,7 +1002,7 @@ class HoodieSpark4_0ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
var path = location
val filtered = cleanTableProperties(ctx, options).filter {
case (k, v) if k.equalsIgnoreCase("path") && path.nonEmpty =>
- throw new ParseException(s"Duplicated table paths found: '${path.get}'
and '$v'. LOCATION" +
+ throw parseException(s"Duplicated table paths found: '${path.get}' and
'$v'. LOCATION" +
s" and the case insensitive key 'path' in OPTIONS are all used to
indicate the custom" +
s" table path, you can only specify one of them.", ctx)
case (k, v) if k.equalsIgnoreCase("path") =>
diff --git
a/hudi-spark-datasource/hudi-spark4.1.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_1ExtendedSqlAstBuilder.scala
b/hudi-spark-datasource/hudi-spark4.1.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_1ExtendedSqlAstBuilder.scala
index 74b1192bef6c..008ac8d92b6e 100644
---
a/hudi-spark-datasource/hudi-spark4.1.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_1ExtendedSqlAstBuilder.scala
+++
b/hudi-spark-datasource/hudi-spark4.1.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_1ExtendedSqlAstBuilder.scala
@@ -57,6 +57,17 @@ import scala.collection.JavaConverters._
class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf, delegate:
ParserInterface)
extends HoodieSqlBaseBaseVisitor[AnyRef] with Logging {
+ /**
+ * Returns a [[ParseException]] carrying `message` as human-readable text,
positioned at `ctx`.
+ * Spark 4.x only exposes error-class constructors, so route the message
through the class that
+ * ParserUtils.operationNotAllowed uses; output gains an "Operation not
allowed: " prefix (the
+ * trailing period is stripped to avoid doubling). If Spark drops
_LEGACY_ERROR_TEMP_0035, the
+ * intercept[ParseException] cases in TestBlobDataType fail loudly on the
4.x profiles (#19450).
+ */
+ private def parseException(message: String, ctx: ParserRuleContext):
ParseException = {
+ new ParseException("_LEGACY_ERROR_TEMP_0035", Map("message" ->
message.stripSuffix(".")), ctx)
+ }
+
protected def typedVisit[T](ctx: ParseTree): T = {
ctx.accept(this).asInstanceOf[T]
}
@@ -130,7 +141,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
def toLiteral[T](f: UTF8String => Option[T], t: DataType): Literal = {
f(UTF8String.fromString(value)).map(Literal(_, t)).getOrElse {
- throw new ParseException(s"Cannot parse the $valueType value: $value",
ctx)
+ throw parseException(s"Cannot parse the $valueType value: $value", ctx)
}
}
@@ -179,7 +190,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.fromString(value))
} catch {
case e: IllegalArgumentException =>
- val ex = new ParseException(s"Cannot parse the INTERVAL value:
$value", ctx)
+ val ex = parseException(s"Cannot parse the INTERVAL value:
$value", ctx)
ex.setStackTrace(e.getStackTrace)
throw ex
}
@@ -196,12 +207,12 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val padding = if (value.length % 2 != 0) "0" else ""
Literal(DatatypeConverter.parseHexBinary(padding + value))
case other =>
- throw new ParseException(s"Literals of type '$other' are currently
not supported.", ctx)
+ throw parseException(s"Literals of type '$other' are currently not
supported.", ctx)
}
} catch {
case e: IllegalArgumentException =>
val message = Option(e.getMessage).getOrElse(s"Exception parsing
$valueType")
- throw new ParseException(message, ctx)
+ throw parseException(message, ctx)
}
}
@@ -274,13 +285,13 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
try {
val rawBigDecimal = BigDecimal(rawStrippedQualifier)
if (rawBigDecimal < minValue || rawBigDecimal > maxValue) {
- throw new ParseException(s"Numeric literal $rawStrippedQualifier does
not " +
+ throw parseException(s"Numeric literal $rawStrippedQualifier does not
" +
s"fit in range [$minValue, $maxValue] for type $typeName", ctx)
}
Literal(converter(rawStrippedQualifier))
} catch {
case e: NumberFormatException =>
- throw new ParseException(e.getMessage, ctx)
+ throw parseException(e.getMessage, ctx)
}
}
@@ -338,7 +349,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
Literal(BigDecimal(raw).underlying())
} catch {
case e: AnalysisException =>
- throw new ParseException(e.message, ctx)
+ throw parseException(e.message, ctx)
}
}
@@ -389,7 +400,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (yearMonthFields.nonEmpty) {
if (dayTimeFields.nonEmpty) {
val literalStr = source(ctx)
- throw new ParseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
+ throw parseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
}
Literal(
calendarInterval.months,
@@ -446,18 +457,20 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (ctx.errorCapturingMultiUnitsInterval != null) {
val innerCtx = ctx.errorCapturingMultiUnitsInterval
if (innerCtx.unitToUnitInterval != null) {
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
}
visitMultiUnitsInterval(innerCtx.multiUnitsInterval)
} else if (ctx.errorCapturingUnitToUnitInterval != null) {
val innerCtx = ctx.errorCapturingUnitToUnitInterval
if (innerCtx.error1 != null || innerCtx.error2 != null) {
val errorCtx = if (innerCtx.error1 != null) innerCtx.error1 else
innerCtx.error2
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
}
visitUnitToUnitInterval(innerCtx.body)
} else {
- throw new ParseException("at least one time unit should be given for
interval literal", ctx)
+ // Unreachable through Hudi's pruned grammar: a bare INTERVAL keyword
binds to
+ // transformArgument's qualifiedName alternative first. Kept for parity
with Spark.
+ throw parseException("at least one time unit should be given for
interval literal", ctx)
}
}
@@ -479,7 +492,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
// units and become valid ones, e.g. '1 day 2 hour'.
// Ideally, we only ensure the value parts don't contain any units
here.
if (value.exists(Character.isLetter)) {
- throw new ParseException("Can only use numbers in the interval
value part for" +
+ throw parseException("Can only use numbers in the interval value
part for" +
s" multiple unit value pairs interval form, but got invalid
value: $value", ctx)
}
if (values(i).MINUS() == null) {
@@ -498,7 +511,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.concat(kvs: _*))
} catch {
case i: IllegalArgumentException =>
- val e = new ParseException(i.getMessage, ctx)
+ val e = parseException(i.getMessage, ctx)
e.setStackTrace(i.getStackTrace)
throw e
}
@@ -520,7 +533,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
}
}
}.getOrElse {
- throw new ParseException("The value of from-to unit must be a string",
ctx.intervalValue)
+ throw parseException("The value of from-to unit must be a string",
ctx.intervalValue)
}
try {
val from = ctx.from.getText.toLowerCase(Locale.ROOT)
@@ -533,12 +546,12 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.fromDayTimeString(value,
DayTimeIntervalType.stringToField(from),
DayTimeIntervalType.stringToField(to))
case _ =>
- throw new ParseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
}
} catch {
// Handle Exceptions thrown by CalendarInterval
case e: IllegalArgumentException =>
- val pe = new ParseException(e.getMessage, ctx)
+ val pe = parseException(e.getMessage, ctx)
pe.setStackTrace(e.getStackTrace)
throw pe
}
@@ -579,7 +592,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
HoodieSchema.parseTypeDescriptor(ctx.getText).asInstanceOf[HoodieSchema.Vector]
} catch {
case e: IllegalArgumentException =>
- throw new ParseException(s"Invalid VECTOR type: ${e.getMessage}",
ctx)
+ throw parseException(s"Invalid VECTOR type: ${e.getMessage}", ctx)
}
val sparkElemType = vectorSchema.getVectorElementType match {
case HoodieSchema.Vector.VectorElementType.FLOAT => FloatType
@@ -596,7 +609,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ("interval", Nil) => CalendarIntervalType
case (dt, params) =>
val dtStr = if (params.nonEmpty) s"$dt(${params.mkString(",")})" else
dt
- throw new ParseException(s"DataType $dtStr is not supported.", ctx)
+ throw parseException(s"DataType $dtStr is not supported.", ctx)
}
}
@@ -607,7 +620,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = YearMonthIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
YearMonthIntervalType(start, end)
} else {
@@ -622,7 +635,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = DayTimeIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
DayTimeIntervalType(start, end)
} else {
@@ -890,7 +903,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ref: FieldReference =>
ref
case nonRef =>
- throw new ParseException(s"Expected a column reference for transform
$name: $nonRef.describe", ctx)
+ throw parseException(s"Expected a column reference for transform
$name: ${nonRef.describe}", ctx)
}
}
@@ -899,7 +912,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
arguments: Seq[V2Expression]): FieldReference
= {
lazy val name: String = ctx.identifier.getText
if (arguments.size > 1) {
- throw new ParseException(s"Too many arguments for transform $name",
ctx)
+ throw parseException(s"Too many arguments for transform $name", ctx)
} else {
getFieldReference(ctx, arguments.head)
}
@@ -922,7 +935,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case LiteralValue(longValue, LongType) =>
longValue.asInstanceOf[Long].toInt
case lit =>
- throw new ParseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
+ throw parseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
}
val fields = arguments.tail.map(arg => getFieldReference(applyCtx,
arg))
@@ -960,7 +973,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
.map(typedVisit[Literal])
.map(lit => LiteralValue(lit.value, lit.dataType))
reference.orElse(literal)
- .getOrElse(throw new ParseException("Invalid transform argument", ctx))
+ .getOrElse(throw parseException("Invalid transform argument", ctx))
}
}
@@ -970,13 +983,13 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val legacyOn = conf.getConf(SQLConf.LEGACY_PROPERTY_NON_RESERVED)
properties.filter {
case (PROP_PROVIDER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_PROVIDER is a reserved table
property, please use the USING clause to specify it.", ctx)
+ throw parseException(s"$PROP_PROVIDER is a reserved table property,
please use the USING clause to specify it.", ctx)
case (PROP_PROVIDER, _) => false
case (PROP_LOCATION, _) if !legacyOn =>
- throw new ParseException(s"$PROP_LOCATION is a reserved table
property, please use the LOCATION clause to specify it.", ctx)
+ throw parseException(s"$PROP_LOCATION is a reserved table property,
please use the LOCATION clause to specify it.", ctx)
case (PROP_LOCATION, _) => false
case (PROP_OWNER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_OWNER is a reserved table property,
it will be set to the current user.", ctx)
+ throw parseException(s"$PROP_OWNER is a reserved table property, it
will be set to the current user.", ctx)
case (PROP_OWNER, _) => false
case _ => true
}
@@ -989,7 +1002,7 @@ class HoodieSpark4_1ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
var path = location
val filtered = cleanTableProperties(ctx, options).filter {
case (k, v) if k.equalsIgnoreCase("path") && path.nonEmpty =>
- throw new ParseException(s"Duplicated table paths found: '${path.get}'
and '$v'. LOCATION" +
+ throw parseException(s"Duplicated table paths found: '${path.get}' and
'$v'. LOCATION" +
s" and the case insensitive key 'path' in OPTIONS are all used to
indicate the custom" +
s" table path, you can only specify one of them.", ctx)
case (k, v) if k.equalsIgnoreCase("path") =>
diff --git
a/hudi-spark-datasource/hudi-spark4.2.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_2ExtendedSqlAstBuilder.scala
b/hudi-spark-datasource/hudi-spark4.2.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_2ExtendedSqlAstBuilder.scala
index 94066110b720..d6442fd01144 100644
---
a/hudi-spark-datasource/hudi-spark4.2.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_2ExtendedSqlAstBuilder.scala
+++
b/hudi-spark-datasource/hudi-spark4.2.x/src/main/scala/org/apache/spark/sql/parser/HoodieSpark4_2ExtendedSqlAstBuilder.scala
@@ -57,6 +57,17 @@ import scala.collection.JavaConverters._
class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf, delegate:
ParserInterface)
extends HoodieSqlBaseBaseVisitor[AnyRef] with Logging {
+ /**
+ * Returns a [[ParseException]] carrying `message` as human-readable text,
positioned at `ctx`.
+ * Spark 4.x only exposes error-class constructors, so route the message
through the class that
+ * ParserUtils.operationNotAllowed uses; output gains an "Operation not
allowed: " prefix (the
+ * trailing period is stripped to avoid doubling). If Spark drops
_LEGACY_ERROR_TEMP_0035, the
+ * intercept[ParseException] cases in TestBlobDataType fail loudly on the
4.x profiles (#19450).
+ */
+ private def parseException(message: String, ctx: ParserRuleContext):
ParseException = {
+ new ParseException("_LEGACY_ERROR_TEMP_0035", Map("message" ->
message.stripSuffix(".")), ctx)
+ }
+
protected def typedVisit[T](ctx: ParseTree): T = {
ctx.accept(this).asInstanceOf[T]
}
@@ -130,7 +141,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
def toLiteral[T](f: UTF8String => Option[T], t: DataType): Literal = {
f(UTF8String.fromString(value)).map(Literal(_, t)).getOrElse {
- throw new ParseException(s"Cannot parse the $valueType value: $value",
ctx)
+ throw parseException(s"Cannot parse the $valueType value: $value", ctx)
}
}
@@ -179,7 +190,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.fromString(value))
} catch {
case e: IllegalArgumentException =>
- val ex = new ParseException(s"Cannot parse the INTERVAL value:
$value", ctx)
+ val ex = parseException(s"Cannot parse the INTERVAL value:
$value", ctx)
ex.setStackTrace(e.getStackTrace)
throw ex
}
@@ -196,12 +207,12 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val padding = if (value.length % 2 != 0) "0" else ""
Literal(DatatypeConverter.parseHexBinary(padding + value))
case other =>
- throw new ParseException(s"Literals of type '$other' are currently
not supported.", ctx)
+ throw parseException(s"Literals of type '$other' are currently not
supported.", ctx)
}
} catch {
case e: IllegalArgumentException =>
val message = Option(e.getMessage).getOrElse(s"Exception parsing
$valueType")
- throw new ParseException(message, ctx)
+ throw parseException(message, ctx)
}
}
@@ -274,13 +285,13 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
try {
val rawBigDecimal = BigDecimal(rawStrippedQualifier)
if (rawBigDecimal < minValue || rawBigDecimal > maxValue) {
- throw new ParseException(s"Numeric literal $rawStrippedQualifier does
not " +
+ throw parseException(s"Numeric literal $rawStrippedQualifier does not
" +
s"fit in range [$minValue, $maxValue] for type $typeName", ctx)
}
Literal(converter(rawStrippedQualifier))
} catch {
case e: NumberFormatException =>
- throw new ParseException(e.getMessage, ctx)
+ throw parseException(e.getMessage, ctx)
}
}
@@ -338,7 +349,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
Literal(BigDecimal(raw).underlying())
} catch {
case e: AnalysisException =>
- throw new ParseException(e.message, ctx)
+ throw parseException(e.message, ctx)
}
}
@@ -389,7 +400,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (yearMonthFields.nonEmpty) {
if (dayTimeFields.nonEmpty) {
val literalStr = source(ctx)
- throw new ParseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
+ throw parseException(s"Cannot mix year-month and day-time fields:
$literalStr", ctx)
}
Literal(
calendarInterval.months,
@@ -446,18 +457,20 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
if (ctx.errorCapturingMultiUnitsInterval != null) {
val innerCtx = ctx.errorCapturingMultiUnitsInterval
if (innerCtx.unitToUnitInterval != null) {
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", innerCtx.unitToUnitInterval)
}
visitMultiUnitsInterval(innerCtx.multiUnitsInterval)
} else if (ctx.errorCapturingUnitToUnitInterval != null) {
val innerCtx = ctx.errorCapturingUnitToUnitInterval
if (innerCtx.error1 != null || innerCtx.error2 != null) {
val errorCtx = if (innerCtx.error1 != null) innerCtx.error1 else
innerCtx.error2
- throw new ParseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
+ throw parseException("Can only have a single from-to unit in the
interval literal syntax", errorCtx)
}
visitUnitToUnitInterval(innerCtx.body)
} else {
- throw new ParseException("at least one time unit should be given for
interval literal", ctx)
+ // Unreachable through Hudi's pruned grammar: a bare INTERVAL keyword
binds to
+ // transformArgument's qualifiedName alternative first. Kept for parity
with Spark.
+ throw parseException("at least one time unit should be given for
interval literal", ctx)
}
}
@@ -479,7 +492,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
// units and become valid ones, e.g. '1 day 2 hour'.
// Ideally, we only ensure the value parts don't contain any units
here.
if (value.exists(Character.isLetter)) {
- throw new ParseException("Can only use numbers in the interval
value part for" +
+ throw parseException("Can only use numbers in the interval value
part for" +
s" multiple unit value pairs interval form, but got invalid
value: $value", ctx)
}
if (values(i).MINUS() == null) {
@@ -498,7 +511,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.stringToInterval(UTF8String.concat(kvs: _*))
} catch {
case i: IllegalArgumentException =>
- val e = new ParseException(i.getMessage, ctx)
+ val e = parseException(i.getMessage, ctx)
e.setStackTrace(i.getStackTrace)
throw e
}
@@ -520,7 +533,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
}
}
}.getOrElse {
- throw new ParseException("The value of from-to unit must be a string",
ctx.intervalValue)
+ throw parseException("The value of from-to unit must be a string",
ctx.intervalValue)
}
try {
val from = ctx.from.getText.toLowerCase(Locale.ROOT)
@@ -533,12 +546,12 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
IntervalUtils.fromDayTimeString(value,
DayTimeIntervalType.stringToField(from),
DayTimeIntervalType.stringToField(to))
case _ =>
- throw new ParseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $from TO $to are not
supported.", ctx)
}
} catch {
// Handle Exceptions thrown by CalendarInterval
case e: IllegalArgumentException =>
- val pe = new ParseException(e.getMessage, ctx)
+ val pe = parseException(e.getMessage, ctx)
pe.setStackTrace(e.getStackTrace)
throw pe
}
@@ -579,7 +592,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
HoodieSchema.parseTypeDescriptor(ctx.getText).asInstanceOf[HoodieSchema.Vector]
} catch {
case e: IllegalArgumentException =>
- throw new ParseException(s"Invalid VECTOR type: ${e.getMessage}",
ctx)
+ throw parseException(s"Invalid VECTOR type: ${e.getMessage}", ctx)
}
val sparkElemType = vectorSchema.getVectorElementType match {
case HoodieSchema.Vector.VectorElementType.FLOAT => FloatType
@@ -596,7 +609,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ("interval", Nil) => CalendarIntervalType
case (dt, params) =>
val dtStr = if (params.nonEmpty) s"$dt(${params.mkString(",")})" else
dt
- throw new ParseException(s"DataType $dtStr is not supported.", ctx)
+ throw parseException(s"DataType $dtStr is not supported.", ctx)
}
}
@@ -607,7 +620,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = YearMonthIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
YearMonthIntervalType(start, end)
} else {
@@ -622,7 +635,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val endStr = ctx.to.getText.toLowerCase(Locale.ROOT)
val end = DayTimeIntervalType.stringToField(endStr)
if (end <= start) {
- throw new ParseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
+ throw parseException(s"Intervals FROM $startStr TO $endStr are not
supported.", ctx)
}
DayTimeIntervalType(start, end)
} else {
@@ -890,7 +903,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case ref: FieldReference =>
ref
case nonRef =>
- throw new ParseException(s"Expected a column reference for transform
$name: $nonRef.describe", ctx)
+ throw parseException(s"Expected a column reference for transform
$name: ${nonRef.describe}", ctx)
}
}
@@ -899,7 +912,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
arguments: Seq[V2Expression]): FieldReference
= {
lazy val name: String = ctx.identifier.getText
if (arguments.size > 1) {
- throw new ParseException(s"Too many arguments for transform $name",
ctx)
+ throw parseException(s"Too many arguments for transform $name", ctx)
} else {
getFieldReference(ctx, arguments.head)
}
@@ -922,7 +935,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
case LiteralValue(longValue, LongType) =>
longValue.asInstanceOf[Long].toInt
case lit =>
- throw new ParseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
+ throw parseException(s"Invalid number of buckets:
${lit.describe}", applyCtx)
}
val fields = arguments.tail.map(arg => getFieldReference(applyCtx,
arg))
@@ -960,7 +973,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
.map(typedVisit[Literal])
.map(lit => LiteralValue(lit.value, lit.dataType))
reference.orElse(literal)
- .getOrElse(throw new ParseException("Invalid transform argument", ctx))
+ .getOrElse(throw parseException("Invalid transform argument", ctx))
}
}
@@ -970,13 +983,13 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
val legacyOn = conf.getConf(SQLConf.LEGACY_PROPERTY_NON_RESERVED)
properties.filter {
case (PROP_PROVIDER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_PROVIDER is a reserved table
property, please use the USING clause to specify it.", ctx)
+ throw parseException(s"$PROP_PROVIDER is a reserved table property,
please use the USING clause to specify it.", ctx)
case (PROP_PROVIDER, _) => false
case (PROP_LOCATION, _) if !legacyOn =>
- throw new ParseException(s"$PROP_LOCATION is a reserved table
property, please use the LOCATION clause to specify it.", ctx)
+ throw parseException(s"$PROP_LOCATION is a reserved table property,
please use the LOCATION clause to specify it.", ctx)
case (PROP_LOCATION, _) => false
case (PROP_OWNER, _) if !legacyOn =>
- throw new ParseException(s"$PROP_OWNER is a reserved table property,
it will be set to the current user.", ctx)
+ throw parseException(s"$PROP_OWNER is a reserved table property, it
will be set to the current user.", ctx)
case (PROP_OWNER, _) => false
case _ => true
}
@@ -989,7 +1002,7 @@ class HoodieSpark4_2ExtendedSqlAstBuilder(conf: SQLConf,
delegate: ParserInterfa
var path = location
val filtered = cleanTableProperties(ctx, options).filter {
case (k, v) if k.equalsIgnoreCase("path") && path.nonEmpty =>
- throw new ParseException(s"Duplicated table paths found: '${path.get}'
and '$v'. LOCATION" +
+ throw parseException(s"Duplicated table paths found: '${path.get}' and
'$v'. LOCATION" +
s" and the case insensitive key 'path' in OPTIONS are all used to
indicate the custom" +
s" table path, you can only specify one of them.", ctx)
case (k, v) if k.equalsIgnoreCase("path") =>