This is an automated email from the ASF dual-hosted git repository.

dtenedor 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 84bb88952f33 [SPARK-58122][SQL] Add SQL ASOF JOIN parser and error 
conditions
84bb88952f33 is described below

commit 84bb88952f33955deb1aa6bbd4dff729973452f2
Author: Serge Rielau <[email protected]>
AuthorDate: Wed Jul 15 08:55:27 2026 -0700

    [SPARK-58122][SQL] Add SQL ASOF JOIN parser and error conditions
    
    ## What changes were proposed in this pull request?
    
    First stacked PR for 
[SPARK-58092](https://issues.apache.org/jira/browse/SPARK-58092) / 
[SPARK-58122](https://issues.apache.org/jira/browse/SPARK-58122): SQL surface 
area for ASOF JOIN only (parser, keywords, errors, feature flag, minimal 
logical plan).
    
    ### SQL syntax (introduced in this PR)
    
    ```sql
    left_table [ AS alias ]
      [ INNER | LEFT OUTER ] ASOF JOIN right_table [ AS alias ]
      MATCH_CONDITION ( left_expr comparison_op right_expr )
      [ ON boolean_expression | USING ( column_name [, ...] ) ]
    ```
    
    - **`ASOF JOIN`** is a dedicated join form (not a modifier on `INNER JOIN`).
    - **`MATCH_CONDITION`** is required and must be a single comparison using 
one of `>=`, `>`, `<=`, `<`. Compound predicates (`AND` / `OR`), equality 
(`=`), and other operators are rejected at parse time.
    - **Join type**: `INNER` (default) or `LEFT OUTER` only.
    - **Optional `ON` / `USING`**: same role as in a regular join — additional 
equi-join (or general boolean) correlation between the two inputs. `ON` and 
`USING` are mutually exclusive.
    
    Example:
    
    ```sql
    SELECT *
    FROM trades t
    ASOF JOIN quotes q
      MATCH_CONDITION (t.trade_time >= q.quote_time)
      ON t.symbol = q.symbol
    ```
    
    ### Intended semantics (full behavior lands in follow-up PRs)
    
    ASOF join finds, for each row on the left, the best-matching row on the 
right according to the `MATCH_CONDITION` ordering predicate (e.g. latest quote 
at or before each trade time when using `>=`). Optional `ON` / `USING` 
restricts candidates to rows that also satisfy the equi-join keys.
    
    - **`INNER ASOF JOIN`**: left rows with no qualifying right match are 
dropped.
    - **`LEFT OUTER ASOF JOIN`**: left rows with no match are kept; right-side 
columns are null-padded.
    
    This PR parses SQL into an unresolved `AsOfJoin` logical node carrying 
`matchComparison`, optional `ON`/`USING`, and join type. Analysis 
(`ResolveAsOfJoin`), type/reference validation, and execution are **not** in 
this PR.
    
    ### PySpark relationship
    
    PySpark already exposes ASOF join via `DataFrame.joinAsOf` (as-of keys, 
optional tolerance, direction, and join condition). This PR adds the SQL 
counterpart and generalizes the join condition through an explicit 
`MATCH_CONDITION` comparison (`left_expr op right_expr`) instead of tying 
semantics to a single pair of as-of column arguments.
    
    ### In scope (this PR)
    
    - ANTLR grammar and parser (`AstBuilder`)
    - Keywords `ASOF` and `MATCH_CONDITION` (lexer, docs, golden files, JDBC 
metadata test)
    - `spark.sql.join.asofJoin.enabled` conf (default off)
    - Parse-time errors for disabled feature and invalid `MATCH_CONDITION` 
operators
    - Minimal `AsOfJoin` extensions (`usingColumns`, `matchComparison`, 
`fromMatchCondition`)
    - Tests: `PlanParserSuite` (asof join), `AsOfJoinSQLSuite` (parse error)
    
    ### Out of scope (follow-up PRs)
    
    - `ResolveAsOfJoin` / analysis validation (`TABLE_REFERENCE`, types, 
operand normalization)
    - Sort-merge execution (`SortMergeAsOfJoinExec`, planner wiring for SQL 
`MATCH_CONDITION` plans)
    - End-to-end SQL integration tests (`AsOfJoinSortMergeSQLSuite`)
    
    ## Why is this needed?
    
    ASOF join is already supported in PySpark for temporal/event-style joins. 
Adding SQL syntax completes API parity and makes the feature usable from 
SQL-only workloads (notebooks, views, JDBC). The `MATCH_CONDITION` form also 
generalizes beyond the PySpark API’s fixed left/right as-of column pair.
    
    This is the first PR in a stacked series so reviewers can sign off on 
grammar and error surface before analysis and execution land.
    
    ## Does this PR introduce any user-facing change?
    
    No when `spark.sql.join.asofJoin.enabled` is false (default). When enabled, 
SQL parses to an unresolved `AsOfJoin` logical node; analysis and execution are 
not wired yet in this PR.
    
    ## How was this patch tested?
    
    ```
    build/sbt "catalyst/testOnly 
org.apache.spark.sql.catalyst.parser.PlanParserSuite -- -z \"asof join\"" 
"sql/testOnly org.apache.spark.sql.AsOfJoinSQLSuite"
    ```
    
    ## Was this patch authored or co-authored using generative AI tooling?
    
    Co-authored with Cursor.
    
    ### SQL vs DataFrame API asymmetry
    
    The SQL surface is **not** a strict superset of the DataFrame joinAsOf API:
    
    - **SQL generalizes** the match predicate via MATCH_CONDITION (left_expr op 
right_expr) instead of a fixed left/right as-of column pair.
    - **SQL does not yet express** two DataFrame features in this stacked 
series:
      - tolerance (fromMatchCondition hard-codes toleranceAssertion = None)
      - Nearest direction (only >=, >, <=, < are accepted, mapping to 
Backward/Forward with exact-match; Nearest has no operator)
    
    Follow-up PRs cover analysis/execution; tolerance/Nearest parity may be 
separate follow-ups if we extend the grammar.
    
    Closes #57251 from srielau/SPARK-58122.
    
    Authored-by: Serge Rielau <[email protected]>
    Signed-off-by: Daniel Tenedorio <[email protected]>
---
 .../src/main/resources/error/error-conditions.json |  34 +++++++
 docs/sql-ref-ansi-compliance.md                    |   2 +
 .../spark/sql/catalyst/parser/SqlBaseLexer.g4      |   2 +
 .../spark/sql/catalyst/parser/SqlBaseParser.g4     |  24 ++++-
 .../spark/sql/errors/QueryParsingErrors.scala      |  16 ++++
 .../sql/catalyst/analysis/CheckAnalysis.scala      |   4 +-
 .../catalyst/analysis/DeduplicateRelations.scala   |   2 +-
 .../sql/catalyst/optimizer/RewriteAsOfJoin.scala   |   3 +-
 .../spark/sql/catalyst/parser/AstBuilder.scala     |  67 ++++++++++++-
 .../spark/sql/catalyst/plans/joinTypes.scala       |  30 ++++++
 .../plans/logical/basicLogicalOperators.scala      |  38 +++++++-
 .../org/apache/spark/sql/internal/SQLConf.scala    |  11 +++
 .../sql/catalyst/parser/PlanParserSuite.scala      | 105 ++++++++++++++++++++-
 .../jdbc/SparkConnectDatabaseMetaDataSuite.scala   |   2 +-
 .../spark/sql/execution/SparkStrategies.scala      |   2 +-
 .../sql-tests/results/keywords-enforced.sql.out    |   2 +
 .../resources/sql-tests/results/keywords.sql.out   |   2 +
 .../sql-tests/results/nonansi/keywords.sql.out     |   2 +
 .../org/apache/spark/sql/AsOfJoinSQLSuite.scala    |  72 ++++++++++++++
 .../spark/sql/errors/QueryParsingErrorsSuite.scala |   2 +-
 .../ThriftServerWithSparkContextSuite.scala        |   2 +-
 21 files changed, 403 insertions(+), 21 deletions(-)

diff --git a/common/utils/src/main/resources/error/error-conditions.json 
b/common/utils/src/main/resources/error/error-conditions.json
index a6150f5e9500..83ace844cfaa 100644
--- a/common/utils/src/main/resources/error/error-conditions.json
+++ b/common/utils/src/main/resources/error/error-conditions.json
@@ -156,6 +156,30 @@
     ],
     "sqlState" : "42713"
   },
+  "ASOF_JOIN_MATCH_CONDITION_INVALID_EXPRESSION" : {
+    "message" : [
+      "The MATCH_CONDITION operand contains an invalid expression: <expr>. 
Rewrite the query to avoid subqueries, aggregate functions, window functions, 
generator functions, or non-deterministic functions in MATCH_CONDITION 
operands."
+    ],
+    "sqlState" : "42903"
+  },
+  "ASOF_JOIN_MATCH_CONDITION_INVALID_OPERATOR" : {
+    "message" : [
+      "The MATCH_CONDITION operator must be one of >=, >, <=, <. Got: 
<operator>."
+    ],
+    "sqlState" : "42K0E"
+  },
+  "ASOF_JOIN_MATCH_CONDITION_INVALID_TYPE" : {
+    "message" : [
+      "The MATCH_CONDITION operands must be of an orderable, mutually 
comparable type. Got: <type1> and <type2>."
+    ],
+    "sqlState" : "42K09"
+  },
+  "ASOF_JOIN_MATCH_CONDITION_TABLE_REFERENCE" : {
+    "message" : [
+      "Each operand of the MATCH_CONDITION comparison must reference only 
columns of one join input. Got: <refs1> and <refs2>."
+    ],
+    "sqlState" : "42K0E"
+  },
   "ASSIGNMENT_ARITY_MISMATCH" : {
     "message" : [
       "The number of columns or variables assigned or aliased: <numTarget> 
does not match the number of source expressions: <numExpr>."
@@ -167,6 +191,11 @@
       "Invalid as-of join."
     ],
     "subClass" : {
+      "SORT_MERGE_REQUIRED" : {
+        "message" : [
+          "SQL ASOF JOIN requires the sort-merge physical operator. Set 
<config> to true."
+        ]
+      },
       "TOLERANCE_IS_NON_NEGATIVE" : {
         "message" : [
           "The input argument `tolerance` must be non-negative."
@@ -8261,6 +8290,11 @@
           "The ANALYZE TABLE command does not support views."
         ]
       },
+      "ASOF_JOIN" : {
+        "message" : [
+          "SQL ASOF JOIN syntax. Set <config> to true to enable it."
+        ]
+      },
       "BIN_BY" : {
         "message" : [
           "The BIN BY relation operator is not yet supported."
diff --git a/docs/sql-ref-ansi-compliance.md b/docs/sql-ref-ansi-compliance.md
index c3eec6cc5ce3..367abce6cb8d 100644
--- a/docs/sql-ref-ansi-compliance.md
+++ b/docs/sql-ref-ansi-compliance.md
@@ -430,6 +430,7 @@ Below is a list of all the keywords in Spark SQL.
 |AS|reserved|non-reserved|reserved|
 |ASC|non-reserved|non-reserved|non-reserved|
 |ASENSITIVE|non-reserved|non-reserved|non-reserved|
+|ASOF|non-reserved|non-reserved|non-reserved|
 |AT|non-reserved|non-reserved|reserved|
 |ATOMIC|non-reserved|non-reserved|non-reserved|
 |AUTHORIZATION|reserved|non-reserved|reserved|
@@ -641,6 +642,7 @@ Below is a list of all the keywords in Spark SQL.
 |MACRO|non-reserved|non-reserved|non-reserved|
 |MAP|non-reserved|non-reserved|non-reserved|
 |MATCHED|non-reserved|non-reserved|non-reserved|
+|MATCH_CONDITION|non-reserved|non-reserved|non-reserved|
 |MATERIALIZED|non-reserved|non-reserved|non-reserved|
 |MAX|non-reserved|non-reserved|non-reserved|
 |MEASURE|non-reserved|non-reserved|non-reserved|
diff --git 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
index 3e5c79613f19..64a99e82053b 100644
--- 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
+++ 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
@@ -145,6 +145,7 @@ APPROX: 'APPROX';
 ARCHIVE: 'ARCHIVE';
 ARRAY: 'ARRAY' {incComplexTypeLevelCounter();};
 AS: 'AS';
+ASOF: 'ASOF';
 ASC: 'ASC';
 ASENSITIVE: 'ASENSITIVE';
 AT: 'AT';
@@ -358,6 +359,7 @@ LOOP: 'LOOP';
 MACRO: 'MACRO';
 MAP: 'MAP' {incComplexTypeLevelCounter();};
 MATCHED: 'MATCHED';
+MATCH_CONDITION: 'MATCH_CONDITION';
 MATERIALIZED: 'MATERIALIZED';
 MAX: 'MAX';
 MEASURE: 'MEASURE';
diff --git 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
index 6b2237f15cc4..fd0bba2f9684 100644
--- 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
+++ 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
@@ -75,7 +75,7 @@ options { tokenVocab = SqlBaseLexer; }
            la == AS || la == WHERE || la == PIVOT || la == UNPIVOT ||
            la == TABLESAMPLE || la == INNER || la == CROSS || la == LEFT ||
            la == RIGHT || la == FULL || la == NATURAL || la == SEMI ||
-           la == ANTI || la == JOIN || la == UNION || la == EXCEPT ||
+           la == ANTI || la == ASOF || la == JOIN || la == UNION || la == 
EXCEPT ||
            la == SETMINUS || la == INTERSECT || la == ORDER || la == CLUSTER ||
            la == DISTRIBUTE || la == SORT || la == LIMIT || la == OFFSET ||
            la == AGGREGATE || la == WINDOW || la == LATERAL || la == BIN;
@@ -1104,8 +1104,24 @@ relationExtension
     ;
 
 joinRelation
-    : (joinType) JOIN LATERAL? right=relationPrimary (joinCriteria | 
nearestByClause)?
+    : (joinType) JOIN LATERAL? right=relationPrimary joinPostfix?
     | NATURAL joinType JOIN LATERAL? right=relationPrimary
+    | asofJoinType ASOF JOIN right=relationPrimary asofJoinCriteria
+    ;
+
+asofJoinType
+    : INNER?
+    | LEFT OUTER?
+    ;
+
+joinPostfix
+    : joinCriteria
+    | nearestByClause
+    ;
+
+asofJoinCriteria
+    : MATCH_CONDITION LEFT_PAREN matchExpr=booleanExpression RIGHT_PAREN
+      ( ON onExpr=booleanExpression | USING identifierList )?
     ;
 
 joinType
@@ -2013,6 +2029,7 @@ ansiNonReserved
     | ARRAY
     | ASC
     | ASENSITIVE
+    | ASOF
     | AT
     | ATOMIC
     | AUTO
@@ -2180,6 +2197,7 @@ ansiNonReserved
     | MACRO
     | MAP
     | MATCHED
+    | MATCH_CONDITION
     | MATERIALIZED
     | MAX
     | MEASURE
@@ -2405,6 +2423,7 @@ nonReserved
     | AS
     | ASC
     | ASENSITIVE
+    | ASOF
     | AT
     | ATOMIC
     | AUTHORIZATION
@@ -2610,6 +2629,7 @@ nonReserved
     | MACRO
     | MAP
     | MATCHED
+    | MATCH_CONDITION
     | MATERIALIZED
     | MAX
     | MEASURE
diff --git 
a/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala 
b/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala
index 9c7eaadedd78..d7554838ead1 100644
--- 
a/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala
+++ 
b/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala
@@ -237,6 +237,22 @@ private[sql] object QueryParsingErrors extends 
DataTypeErrorsBase {
       ctx)
   }
 
+  def sqlAsofJoinDisabled(configKey: String, ctx: ParserRuleContext): 
Throwable = {
+    new ParseException(
+      errorClass = "UNSUPPORTED_FEATURE.ASOF_JOIN",
+      messageParameters = Map("config" -> toSQLConf(configKey)),
+      ctx)
+  }
+
+  def sqlAsOfJoinMatchConditionInvalidOperator(
+      operator: String,
+      ctx: ParserRuleContext): Throwable = {
+    new ParseException(
+      errorClass = "ASOF_JOIN_MATCH_CONDITION_INVALID_OPERATOR",
+      messageParameters = Map("operator" -> operator),
+      ctx)
+  }
+
   def repetitiveWindowDefinitionError(name: String, ctx: WindowClauseContext): 
Throwable = {
     new ParseException(
       errorClass = "INVALID_SQL_SYNTAX.REPETITIVE_WINDOW_DEFINITION",
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala
index ccc5da5ed8c3..f457ac1ba852 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala
@@ -676,7 +676,7 @@ trait CheckAnalysis extends LookupCatalog with 
QueryErrorsBase with PlanToString
                 "joinCondition" -> toSQLExpr(condition),
                 "conditionType" -> toSQLType(condition.dataType)))
 
-          case j @ AsOfJoin(_, _, _, Some(condition), _, _, _)
+          case j @ AsOfJoin(_, _, _, Some(condition), _, _, _, _, _, _, _)
               if condition.dataType != BooleanType =>
             throw SparkException.internalError(
               msg = s"join condition '${toSQLExpr(condition)}' " +
@@ -684,7 +684,7 @@ trait CheckAnalysis extends LookupCatalog with 
QueryErrorsBase with PlanToString
               context = j.origin.getQueryContext,
               summary = j.origin.context.summary)
 
-          case j @ AsOfJoin(_, _, _, _, _, _, Some(toleranceAssertion)) =>
+          case j @ AsOfJoin(_, _, _, _, _, _, Some(toleranceAssertion), _, _, 
_, _) =>
             if (!toleranceAssertion.foldable) {
               j.failAnalysis(
                 errorClass = "AS_OF_JOIN.TOLERANCE_IS_UNFOLDABLE",
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/DeduplicateRelations.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/DeduplicateRelations.scala
index 1fb703814fb9..18045214e565 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/DeduplicateRelations.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/DeduplicateRelations.scala
@@ -48,7 +48,7 @@ object DeduplicateRelations extends Rule[LogicalPlan] {
           if right.resolved && !j.duplicateResolved && 
noMissingInput(right.plan) =>
         j.copy(right = right.withNewPlan(dedupRight(left, right.plan)))
       // Resolve duplicate output for AsOfJoin.
-      case j @ AsOfJoin(left, right, _, _, _, _, _)
+      case j @ AsOfJoin(left, right, _, _, _, _, _, _, _, _, _)
           if !j.duplicateResolved && noMissingInput(right) =>
         j.copy(right = dedupRight(left, right))
       // Resolve duplicate output for NearestByJoin.
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/RewriteAsOfJoin.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/RewriteAsOfJoin.scala
index 6f10cad108f7..31625b25b378 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/RewriteAsOfJoin.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/RewriteAsOfJoin.scala
@@ -58,7 +58,8 @@ object RewriteAsOfJoin extends Rule[LogicalPlan] {
     if (conf.sortMergeAsOfJoinEnabled) return plan
 
     plan.transformUpWithNewOutput {
-      case j @ AsOfJoin(left, right, asOfCondition, condition, joinType, 
orderExpression, _) =>
+      case j @ AsOfJoin(
+          left, right, asOfCondition, condition, joinType, orderExpression, _, 
_, _, _, _) =>
         val conditionWithOuterReference =
           condition.map(And(_, 
asOfCondition)).getOrElse(asOfCondition).transformUp {
             case a: AttributeReference if left.outputSet.contains(a) =>
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
index f5d8e600cb70..e6f4b574b73a 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
@@ -2505,11 +2505,17 @@ class AstBuilder extends DataTypeAstBuilder
         }
       }
 
-      if (ctx.nearestByClause != null) {
-        withNearestByJoin(ctx, base, baseJoinType)
+      val joinPostfix = Option(ctx.joinPostfix)
+      val joinCriteriaCtx = joinPostfix.flatMap(p => Option(p.joinCriteria))
+      val nearestByClauseCtx = joinPostfix.flatMap(p => 
Option(p.nearestByClause))
+
+      if (ctx.asofJoinCriteria != null) {
+        withAsOfJoin(ctx, base, ctx.asofJoinCriteria)
+      } else if (nearestByClauseCtx.isDefined) {
+        withNearestByJoin(ctx, base, baseJoinType, nearestByClauseCtx.get)
       } else {
         // Resolve the join type and join condition
-        val (joinType, condition) = Option(ctx.joinCriteria) match {
+        val (joinType, condition) = joinCriteriaCtx match {
           case Some(c) if c.USING != null =>
             if (ctx.LATERAL != null) {
               throw 
QueryParsingErrors.lateralJoinWithUsingJoinUnsupportedError(ctx)
@@ -2546,6 +2552,56 @@ class AstBuilder extends DataTypeAstBuilder
     }
   }
 
+  private def asOfMatchConditionFromExpression(
+      expr: Expression,
+      ctx: ParserRuleContext): (Expression, MatchComparisonOperator, 
Expression) = {
+    expr match {
+      case GreaterThanOrEqual(left, right) => (left, GreaterThanOrEqualOp, 
right)
+      case GreaterThan(left, right) => (left, GreaterThanOp, right)
+      case LessThanOrEqual(left, right) => (left, LessThanOrEqualOp, right)
+      case LessThan(left, right) => (left, LessThanOp, right)
+      case _ =>
+        throw QueryParsingErrors.sqlAsOfJoinMatchConditionInvalidOperator(
+          asOfMatchConditionInvalidOperatorText(expr), ctx)
+    }
+  }
+
+  private def asOfMatchConditionInvalidOperatorText(expr: Expression): String 
= expr match {
+    case EqualTo(_, _) => "="
+    case And(_, _) => "AND"
+    case Or(_, _) => "OR"
+    case _ => expr.prettyName
+  }
+
+  /**
+   * Build an [[AsOfJoin]] from the parsed `ASOF JOIN ... MATCH_CONDITION` 
clause.
+   */
+  private def withAsOfJoin(
+      ctx: JoinRelationContext,
+      base: LogicalPlan,
+      criteria: AsofJoinCriteriaContext): AsOfJoin = {
+    if (!conf.sqlAsOfJoinEnabled) {
+      throw 
QueryParsingErrors.sqlAsofJoinDisabled(SQLConf.SQL_ASOF_JOIN_ENABLED.key, ctx)
+    }
+    val joinType = Option(ctx.asofJoinType) match {
+      case None => Inner
+      case Some(jt) if jt.LEFT != null => LeftOuter
+      case _ => Inner
+    }
+    val (leftExpr, operator, rightExpr) =
+      asOfMatchConditionFromExpression(expression(criteria.matchExpr), 
criteria.matchExpr)
+    val (condition, usingColumns) =
+      (Option(criteria.onExpr), Option(criteria.identifierList)) match {
+      case (Some(expr), None) => (Some(expression(expr)), None)
+      case (None, Some(ids)) => (None, Some(visitIdentifierList(ids)))
+      case (None, None) => (None, None)
+      case _ =>
+        throw SparkException.internalError(s"Unimplemented asofJoinCriteria: 
$criteria")
+    }
+    AsOfJoin.fromMatchCondition(
+      base, plan(ctx.right), leftExpr, operator, rightExpr, condition, 
joinType, usingColumns)
+  }
+
   /**
    * Build a [[NearestByJoin]] from the parsed `NEAREST BY` clause attached to 
a join relation.
    * Validates that the clause is not combined with `LATERAL` and that the 
base join type is one
@@ -2555,7 +2611,8 @@ class AstBuilder extends DataTypeAstBuilder
   private def withNearestByJoin(
       ctx: JoinRelationContext,
       base: LogicalPlan,
-      baseJoinType: JoinType): NearestByJoin = {
+      baseJoinType: JoinType,
+      nearestByClause: NearestByClauseContext): NearestByJoin = {
     if (ctx.LATERAL != null) {
       throw QueryParsingErrors.nearestByJoinWithLateralUnsupportedError(ctx)
     }
@@ -2563,7 +2620,7 @@ class AstBuilder extends DataTypeAstBuilder
       throw QueryParsingErrors.unsupportedNearestByJoinTypeError(
         ctx, baseJoinType.sql, NearestByJoinType.supportedDisplay)
     }
-    val clause = ctx.nearestByClause
+    val clause = nearestByClause
     val approx = clause.APPROX != null
     val numResults = Option(clause.num).map { n =>
       // Guard against literals that overflow Long.
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/joinTypes.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/joinTypes.scala
index 790307e44ec9..ae1228c3d7bd 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/joinTypes.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/joinTypes.scala
@@ -160,6 +160,31 @@ case object Forward extends AsOfJoinDirection
 case object Backward extends AsOfJoinDirection
 case object Nearest extends AsOfJoinDirection
 
+sealed abstract class MatchComparisonOperator {
+  def sql: String
+  def flip: MatchComparisonOperator
+}
+
+case object GreaterThanOrEqualOp extends MatchComparisonOperator {
+  override def sql: String = ">="
+  override def flip: MatchComparisonOperator = LessThanOrEqualOp
+}
+
+case object GreaterThanOp extends MatchComparisonOperator {
+  override def sql: String = ">"
+  override def flip: MatchComparisonOperator = LessThanOp
+}
+
+case object LessThanOrEqualOp extends MatchComparisonOperator {
+  override def sql: String = "<="
+  override def flip: MatchComparisonOperator = GreaterThanOrEqualOp
+}
+
+case object LessThanOp extends MatchComparisonOperator {
+  override def sql: String = "<"
+  override def flip: MatchComparisonOperator = GreaterThanOp
+}
+
 object LateralJoinType {
 
   val supported = Seq(
@@ -226,6 +251,11 @@ object NearestByJoinType {
   }
 }
 
+object AsOfJoinType {
+
+  val supportedDisplay: String = "'INNER', 'LEFT OUTER'"
+}
+
 object NearestByJoinMode {
 
   /** @see [[NearestByJoinValidation.SupportedModes]] */
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala
index b7c441063874..244614a2c962 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/basicLogicalOperators.scala
@@ -2533,7 +2533,12 @@ case class AsOfJoin(
     condition: Option[Expression],
     joinType: JoinType,
     orderExpression: Expression,
-    toleranceAssertion: Option[Expression]) extends BinaryNode {
+    toleranceAssertion: Option[Expression],
+    usingColumns: Option[Seq[String]] = None,
+    matchLeftOperand: Option[Expression] = None,
+    matchOperator: Option[MatchComparisonOperator] = None,
+    matchRightOperand: Option[Expression] = None)
+    extends BinaryNode {
 
   require(Seq(Inner, LeftOuter).contains(joinType),
     s"Unsupported as-of join type $joinType")
@@ -2553,6 +2558,10 @@ case class AsOfJoin(
 
   override lazy val resolved: Boolean = {
     childrenResolved &&
+      usingColumns.isEmpty &&
+      matchLeftOperand.isEmpty &&
+      matchOperator.isEmpty &&
+      matchRightOperand.isEmpty &&
       expressions.forall(_.resolved) &&
       duplicateResolved &&
       asOfCondition.dataType == BooleanType &&
@@ -2588,6 +2597,33 @@ object AsOfJoin {
       orderingExpr, tolerance.map(t => GreaterThanOrEqual(t, 
Literal.default(t.dataType))))
   }
 
+  /**
+   * Build an [[AsOfJoin]] from a SQL `MATCH_CONDITION (left_expr op 
right_expr)` clause.
+   * Operand normalization is deferred until analysis when join inputs are 
resolved.
+   */
+  def fromMatchCondition(
+      left: LogicalPlan,
+      right: LogicalPlan,
+      leftExpr: Expression,
+      operator: MatchComparisonOperator,
+      rightExpr: Expression,
+      condition: Option[Expression],
+      joinType: JoinType,
+      usingColumns: Option[Seq[String]] = None): AsOfJoin = {
+    AsOfJoin(
+      left,
+      right,
+      asOfCondition = Literal.TrueLiteral,
+      condition = condition,
+      joinType = joinType,
+      orderExpression = Literal(0),
+      toleranceAssertion = None,
+      usingColumns = usingColumns,
+      matchLeftOperand = Some(leftExpr),
+      matchOperator = Some(operator),
+      matchRightOperand = Some(rightExpr))
+  }
+
   private def makeAsOfCond(
       leftAsOf: Expression,
       rightAsOf: Expression,
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 efa5b0352113..8b52f308403c 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
@@ -945,6 +945,15 @@ object SQLConf {
       .booleanConf
       .createWithDefault(false)
 
+  val SQL_ASOF_JOIN_ENABLED =
+    buildConf("spark.sql.join.asofJoin.enabled")
+      .doc("When true, enable SQL ASOF JOIN syntax with MATCH_CONDITION. When 
false, " +
+        "ASOF JOIN fails at parse time.")
+      .version("4.3.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(false)
+
   val REQUIRE_ALL_CLUSTER_KEYS_FOR_CO_PARTITION =
     buildConf("spark.sql.requireAllClusterKeysForCoPartition")
       .internal()
@@ -8562,6 +8571,8 @@ class SQLConf extends Serializable with Logging with 
SqlApiConf {
 
   def sortMergeAsOfJoinEnabled: Boolean = 
getConf(SORT_MERGE_AS_OF_JOIN_ENABLED)
 
+  def sqlAsOfJoinEnabled: Boolean = getConf(SQL_ASOF_JOIN_ENABLED)
+
   def enableRadixSort: Boolean = getConf(RADIX_SORT_ENABLED)
 
   def isParquetSchemaMergingEnabled: Boolean = 
getConf(PARQUET_SCHEMA_MERGING_ENABLED)
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala
index 6e5239b3d206..ae931604f4ad 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala
@@ -965,17 +965,112 @@ class PlanParserSuite extends AnalysisTest {
         stop = 73))
   }
 
+  test("asof join") {
+    withSQLConf(SQLConf.SQL_ASOF_JOIN_ENABLED.key -> "true") {
+      assertEqual(
+        "select * from t asof join u match_condition (t.a >= u.a)",
+        AsOfJoin.fromMatchCondition(
+          table("t"),
+          table("u"),
+          $"t.a",
+          GreaterThanOrEqualOp,
+          $"u.a",
+          None,
+          Inner).select(star()))
+
+      assertEqual(
+        "select * from t left asof join u match_condition (t.a >= u.a) on t.b 
= u.b",
+        AsOfJoin.fromMatchCondition(
+          table("t"),
+          table("u"),
+          $"t.a",
+          GreaterThanOrEqualOp,
+          $"u.a",
+          Some($"t.b" === $"u.b"),
+          LeftOuter).select(star()))
+
+      assertEqual(
+        "select * from t asof join u match_condition (t.a >= u.a) using (b)",
+        AsOfJoin.fromMatchCondition(
+          table("t"),
+          table("u"),
+          $"t.a",
+          GreaterThanOrEqualOp,
+          $"u.a",
+          None,
+          Inner,
+          usingColumns = Some(Seq("b"))).select(star()))
+
+      assertEqual(
+        "select * from t asof join u match_condition (u.a <= t.a)",
+        AsOfJoin.fromMatchCondition(
+          table("t"),
+          table("u"),
+          $"u.a",
+          LessThanOrEqualOp,
+          $"t.a",
+          None,
+          Inner).select(star()))
+    }
+  }
+
+  test("asof join - invalid match operator") {
+    withSQLConf(SQLConf.SQL_ASOF_JOIN_ENABLED.key -> "true") {
+      val sql =
+        "select * from t asof join u match_condition (t.a = u.a)"
+      checkError(
+        exception = parseException(sql),
+        condition = "ASOF_JOIN_MATCH_CONDITION_INVALID_OPERATOR",
+        sqlState = Some("42K0E"),
+        parameters = Map("operator" -> "="),
+        queryContext = Array(
+          ExpectedContext(
+            fragment = "asof join u match_condition (t.a = u.a)",
+            start = 16,
+            stop = 54)))
+    }
+  }
+
+  test("asof join - compound match condition rejected") {
+    withSQLConf(SQLConf.SQL_ASOF_JOIN_ENABLED.key -> "true") {
+      checkError(
+        exception = parseException(
+          "select * from t asof join u match_condition (t.a >= u.a and t.b >= 
u.b)"),
+        condition = "ASOF_JOIN_MATCH_CONDITION_INVALID_OPERATOR",
+        sqlState = Some("42K0E"),
+        parameters = Map("operator" -> "AND"),
+        queryContext = Array(
+          ExpectedContext(
+            fragment = "asof join u match_condition (t.a >= u.a and t.b >= 
u.b)",
+            start = 16,
+            stop = 70)))
+    }
+  }
+
+  test("asof join disabled by default") {
+    withSQLConf(SQLConf.SQL_ASOF_JOIN_ENABLED.key -> "false") {
+      checkError(
+        exception = parseException("select * from t asof join u 
match_condition (t.a >= u.a)"),
+        condition = "UNSUPPORTED_FEATURE.ASOF_JOIN",
+        sqlState = "0A000",
+        parameters = Map("config" -> "\"spark.sql.join.asofJoin.enabled\""),
+        context = ExpectedContext(
+          fragment = "asof join u match_condition (t.a >= u.a)",
+          start = 16,
+          stop = 55))
+    }
+  }
+
   test("nearest-by keywords are non-reserved (usable as identifiers)") {
-    // The five new keywords (APPROX, DISTANCE, EXACT, NEAREST, SIMILARITY) 
must remain
-    // non-reserved so they can continue to be used as column or table 
identifiers.
-    Seq("approx", "distance", "exact", "nearest", "similarity").foreach { kw =>
+    // Spark-specific join keywords must remain non-reserved so they can be 
used as identifiers.
+    Seq("approx", "asof", "distance", "exact", "nearest", 
"similarity").foreach { kw =>
       // As a column identifier in the SELECT list.
       parsePlan(s"select $kw from t")
       // As a table identifier in the FROM clause.
       parsePlan(s"select * from $kw")
     }
-    // All five together in a single SELECT list.
-    parsePlan("select approx, distance, exact, nearest, similarity from t")
+    // All six together in a single SELECT list.
+    parsePlan("select approx, asof, distance, exact, nearest, similarity from 
t")
   }
 
   test("sampled relations") {
diff --git 
a/sql/connect/client/jdbc/src/test/scala/org/apache/spark/sql/connect/client/jdbc/SparkConnectDatabaseMetaDataSuite.scala
 
b/sql/connect/client/jdbc/src/test/scala/org/apache/spark/sql/connect/client/jdbc/SparkConnectDatabaseMetaDataSuite.scala
index 6c35f73f5742..babac5dd9486 100644
--- 
a/sql/connect/client/jdbc/src/test/scala/org/apache/spark/sql/connect/client/jdbc/SparkConnectDatabaseMetaDataSuite.scala
+++ 
b/sql/connect/client/jdbc/src/test/scala/org/apache/spark/sql/connect/client/jdbc/SparkConnectDatabaseMetaDataSuite.scala
@@ -210,7 +210,7 @@ class SparkConnectDatabaseMetaDataSuite extends 
ConnectFunSuite with RemoteSpark
       val metadata = conn.getMetaData
       // scalastyle:off line.size.limit
       // CURRENT_PATH and SYSTEM are excluded: getSQLKeywords drops SQL:2003 
reserved words (see companion).
-      assert(metadata.getSQLKeywords === 
"ADD,AFTER,AGGREGATE,ALIGN,ALWAYS,ANALYZE,ANTI,ANY_VALUE,APPLY,APPROX,ARCHIVE,ASC,AUTO,BERNOULLI,BIN,BINDING,BIN_DISTRIBUTE_RATIO,BIN_END,BIN_START,BUCKET,BUCKETS,BYTE,CACHE,CASCADE,CATALOG,CATALOGS,CDC,CHANGE,CHANGES,CLEAR,CLUSTER,CLUSTERED,CODEGEN,COLLATION,COLLATIONS,COLLECTION,COLUMNS,COMMENT,COMPACT,COMPACTIONS,COMPENSATION,COMPUTE,CONCATENATE,CONTAINS,CONTINUE,COST,CURRENT_DATABASE,CURRENT_SCHEMA,DATA,DATABASE,DATABASES,DATEADD,DATEDIFF,DATE
 [...]
+      assert(metadata.getSQLKeywords === 
"ADD,AFTER,AGGREGATE,ALIGN,ALWAYS,ANALYZE,ANTI,ANY_VALUE,APPLY,APPROX,ARCHIVE,ASC,ASOF,AUTO,BERNOULLI,BIN,BINDING,BIN_DISTRIBUTE_RATIO,BIN_END,BIN_START,BUCKET,BUCKETS,BYTE,CACHE,CASCADE,CATALOG,CATALOGS,CDC,CHANGE,CHANGES,CLEAR,CLUSTER,CLUSTERED,CODEGEN,COLLATION,COLLATIONS,COLLECTION,COLUMNS,COMMENT,COMPACT,COMPACTIONS,COMPENSATION,COMPUTE,CONCATENATE,CONTAINS,CONTINUE,COST,CURRENT_DATABASE,CURRENT_SCHEMA,DATA,DATABASE,DATABASES,DATEADD,DATEDIFF
 [...]
       // scalastyle:on line.size.limit
     }
   }
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkStrategies.scala 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkStrategies.scala
index f8f1e9eeeb35..1329e0d5d6b3 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkStrategies.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkStrategies.scala
@@ -431,7 +431,7 @@ abstract class SparkStrategies extends 
QueryPlanner[SparkPlan] {
   object AsOfJoinSelection extends Strategy with PredicateHelper {
     def apply(plan: LogicalPlan): Seq[SparkPlan] = plan match {
       case j @ AsOfJoin(left, right, asOfCondition, condition, joinType,
-          orderExpression, _) if conf.sortMergeAsOfJoinEnabled =>
+          orderExpression, _, _, _, _, _) if conf.sortMergeAsOfJoinEnabled =>
         val (leftKeys, rightKeys, residual) = condition match {
           case Some(cond) => extractEquiJoinKeys(cond, left, right)
           case None => (Seq.empty[Expression], Seq.empty[Expression], None)
diff --git 
a/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out 
b/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
index 0c57178048eb..989bfb725111 100644
--- a/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
@@ -23,6 +23,7 @@ ARRAY false
 AS     true
 ASC    false
 ASENSITIVE     false
+ASOF   false
 AT     false
 ATOMIC false
 AUTHORIZATION  true
@@ -234,6 +235,7 @@ LOOP        false
 MACRO  false
 MAP    false
 MATCHED        false
+MATCH_CONDITION        false
 MATERIALIZED   false
 MAX    false
 MEASURE        false
diff --git a/sql/core/src/test/resources/sql-tests/results/keywords.sql.out 
b/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
index e0528a75c6ef..ee127e584f17 100644
--- a/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
@@ -23,6 +23,7 @@ ARRAY false
 AS     false
 ASC    false
 ASENSITIVE     false
+ASOF   false
 AT     false
 ATOMIC false
 AUTHORIZATION  false
@@ -234,6 +235,7 @@ LOOP        false
 MACRO  false
 MAP    false
 MATCHED        false
+MATCH_CONDITION        false
 MATERIALIZED   false
 MAX    false
 MEASURE        false
diff --git 
a/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out 
b/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
index e0528a75c6ef..ee127e584f17 100644
--- a/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
@@ -23,6 +23,7 @@ ARRAY false
 AS     false
 ASC    false
 ASENSITIVE     false
+ASOF   false
 AT     false
 ATOMIC false
 AUTHORIZATION  false
@@ -234,6 +235,7 @@ LOOP        false
 MACRO  false
 MAP    false
 MATCHED        false
+MATCH_CONDITION        false
 MATERIALIZED   false
 MAX    false
 MEASURE        false
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/AsOfJoinSQLSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/AsOfJoinSQLSuite.scala
new file mode 100644
index 000000000000..d8fead52af94
--- /dev/null
+++ b/sql/core/src/test/scala/org/apache/spark/sql/AsOfJoinSQLSuite.scala
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql
+
+import org.apache.spark.sql.catalyst.parser.ParseException
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.test.SharedSparkSession
+
+/**
+ * SQL ASOF JOIN surface tests (parser and feature gating).
+ * Execution semantics and complex MATCH_CONDITION types are covered by
+ * `AsOfJoinSortMergeSQLSuite`, which requires sort-merge ASOF join.
+ */
+class AsOfJoinSQLSuite extends QueryTest with SharedSparkSession {
+
+  override def beforeAll(): Unit = {
+    super.beforeAll()
+    spark.conf.set(SQLConf.SQL_ASOF_JOIN_ENABLED.key, "true")
+  }
+
+  override def afterAll(): Unit = {
+    spark.conf.unset(SQLConf.SQL_ASOF_JOIN_ENABLED.key)
+    super.afterAll()
+  }
+
+  test("equality operator is rejected in MATCH_CONDITION") {
+    sql(
+      """
+        |CREATE OR REPLACE TEMP VIEW trades(trade_time, symbol) AS
+        |VALUES (TIMESTAMP '2026-06-29 10:00:00', 'AAPL')
+        |""".stripMargin)
+    sql(
+      """
+        |CREATE OR REPLACE TEMP VIEW quotes(quote_time, symbol) AS
+        |VALUES (TIMESTAMP '2026-06-29 09:00:00', 'AAPL')
+        |""".stripMargin)
+    val sqlText =
+      """
+        |SELECT * FROM trades t
+        |ASOF JOIN quotes q
+        |  MATCH_CONDITION (t.trade_time = q.quote_time)
+        |  ON t.symbol = q.symbol
+        |""".stripMargin
+    checkError(
+      exception = intercept[ParseException](sql(sqlText)),
+      condition = "ASOF_JOIN_MATCH_CONDITION_INVALID_OPERATOR",
+      sqlState = Some("42K0E"),
+      parameters = Map("operator" -> "="),
+      queryContext = Array(
+        ExpectedContext(
+          fragment = """ASOF JOIN quotes q
+                       |  MATCH_CONDITION (t.trade_time = q.quote_time)
+                       |  ON t.symbol = q.symbol""".stripMargin,
+          start = 24,
+          stop = 114)))
+  }
+}
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/errors/QueryParsingErrorsSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/errors/QueryParsingErrorsSuite.scala
index f834db203b90..aab49da38ddc 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/errors/QueryParsingErrorsSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/errors/QueryParsingErrorsSuite.scala
@@ -472,7 +472,7 @@ class QueryParsingErrorsSuite extends SharedSparkSession {
       exception = parseException("select * from a left join_ b on a.id = 
b.id"),
       condition = "PARSE_SYNTAX_ERROR",
       sqlState = "42601",
-      parameters = Map("error" -> "'join_'", "hint" -> ": missing 'JOIN'"))
+      parameters = Map("error" -> "'join_'", "hint" -> ""))
 
     checkError(
       exception = parseException("select * from test where test.t is like 
'test'"),
diff --git 
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
 
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
index 4e8f117dc8a5..ff9e203f5915 100644
--- 
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
+++ 
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
@@ -214,7 +214,7 @@ trait ThriftServerWithSparkContextSuite extends 
SharedThriftServer {
       val sessionHandle = client.openSession(user, "")
       val infoValue = client.getInfo(sessionHandle, 
GetInfoType.CLI_ODBC_KEYWORDS)
       // scalastyle:off line.size.limit
-      assert(infoValue.getStringValue == 
"ADD,AFTER,AGGREGATE,ALIGN,ALL,ALTER,ALWAYS,ANALYZE,AND,ANTI,ANY,ANY_VALUE,APPLY,APPROX,ARCHIVE,ARRAY,AS,ASC,ASENSITIVE,AT,ATOMIC,AUTHORIZATION,AUTO,BEGIN,BERNOULLI,BETWEEN,BIGINT,BIN,BINARY,BINDING,BIN_DISTRIBUTE_RATIO,BIN_END,BIN_START,BOOLEAN,BOTH,BUCKET,BUCKETS,BY,BYTE,CACHE,CALL,CALLED,CASCADE,CASE,CAST,CATALOG,CATALOGS,CDC,CHANGE,CHANGES,CHAR,CHARACTER,CHECK,CLEAR,CLOSE,CLUSTER,CLUSTERED,CODEGEN,COLLATE,COLLATION,COLLATIONS,COLLECTION,COLUMN
 [...]
+      assert(infoValue.getStringValue == 
"ADD,AFTER,AGGREGATE,ALIGN,ALL,ALTER,ALWAYS,ANALYZE,AND,ANTI,ANY,ANY_VALUE,APPLY,APPROX,ARCHIVE,ARRAY,AS,ASC,ASENSITIVE,ASOF,AT,ATOMIC,AUTHORIZATION,AUTO,BEGIN,BERNOULLI,BETWEEN,BIGINT,BIN,BINARY,BINDING,BIN_DISTRIBUTE_RATIO,BIN_END,BIN_START,BOOLEAN,BOTH,BUCKET,BUCKETS,BY,BYTE,CACHE,CALL,CALLED,CASCADE,CASE,CAST,CATALOG,CATALOGS,CDC,CHANGE,CHANGES,CHAR,CHARACTER,CHECK,CLEAR,CLOSE,CLUSTER,CLUSTERED,CODEGEN,COLLATE,COLLATION,COLLATIONS,COLLECTION,C
 [...]
       // scalastyle:on line.size.limit
     }
   }


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


Reply via email to