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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 93d449e650 [spark] Refactor SQL extensions parser into base + 
per-version wrappers (#8032)
93d449e650 is described below

commit 93d449e65046091a636dfa0002daeb95627d9f0b
Author: Kerwin Zhang <[email protected]>
AuthorDate: Fri May 29 21:50:25 2026 +0800

    [spark] Refactor SQL extensions parser into base + per-version wrappers 
(#8032)
---
 .../PaimonSpark4SqlExtensionsParser.scala          |   3 +-
 .../AbstractPaimonSparkSqlExtensionsParser.scala   | 468 ---------------------
 .../AbstractPaimonSparkSqlExtensionsParser.scala   |  26 +-
 .../PaimonSpark3SqlExtensionsParser.scala          |   3 +-
 .../PaimonSpark4SqlExtensionsParser.scala          |   3 +-
 5 files changed, 14 insertions(+), 489 deletions(-)

diff --git 
a/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
 
b/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
similarity index 93%
copy from 
paimon-spark/paimon-spark4-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
copy to 
paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
index 9bd395f333..fe4833a03c 100644
--- 
a/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
+++ 
b/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
@@ -23,7 +23,8 @@ import 
org.apache.spark.sql.catalyst.parser.extensions.AbstractPaimonSparkSqlExt
 import org.apache.spark.sql.types.StructType
 
 class PaimonSpark4SqlExtensionsParser(override val delegate: ParserInterface)
-  extends AbstractPaimonSparkSqlExtensionsParser(delegate) {
+  extends AbstractPaimonSparkSqlExtensionsParser(delegate)
+  with ParserInterface {
 
   override def parseRoutineParam(sqlText: String): StructType = 
delegate.parseRoutineParam(sqlText)
 }
diff --git 
a/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/AbstractPaimonSparkSqlExtensionsParser.scala
 
b/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/AbstractPaimonSparkSqlExtensionsParser.scala
deleted file mode 100644
index 3529944f37..0000000000
--- 
a/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/AbstractPaimonSparkSqlExtensionsParser.scala
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
- * 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.catalyst.parser.extensions
-
-import org.apache.paimon.spark.SparkProcedures
-
-import org.antlr.v4.runtime._
-import org.antlr.v4.runtime.atn.PredictionMode
-import org.antlr.v4.runtime.misc.{Interval, ParseCancellationException}
-import org.antlr.v4.runtime.tree.TerminalNodeImpl
-import org.apache.spark.internal.Logging
-import org.apache.spark.sql.{AnalysisException, PaimonSparkSession, 
SparkSession}
-import org.apache.spark.sql.catalyst.{FunctionIdentifier, TableIdentifier}
-import org.apache.spark.sql.catalyst.expressions.Expression
-import org.apache.spark.sql.catalyst.parser.{ParseException, ParserInterface}
-import 
org.apache.spark.sql.catalyst.parser.extensions.PaimonSqlExtensionsParser.{NonReservedContext,
 QuotedIdentifierContext}
-import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
-import org.apache.spark.sql.catalyst.rules.Rule
-import org.apache.spark.sql.internal.VariableSubstitution
-import org.apache.spark.sql.types.{DataType, StructType}
-
-import java.util.Locale
-
-import scala.collection.JavaConverters._
-
-/* This file is based on source code from the Iceberg Project 
(http://iceberg.apache.org/), licensed by the Apache
- * Software Foundation (ASF) under the Apache License, Version 2.0. See the 
NOTICE file distributed with this work for
- * additional information regarding copyright ownership. */
-
-/**
- * The implementation of [[ParserInterface]] that parsers the sql extension.
- *
- * <p>Most of the content of this class is referenced from Iceberg's
- * IcebergSparkSqlExtensionsParser.
- *
- * @param delegate
- *   The extension parser.
- */
-// Keep this class in the Spark 4.0 module so it is compiled against Spark 
4.0's ParserInterface.
-abstract class AbstractPaimonSparkSqlExtensionsParser(val delegate: 
ParserInterface)
-  extends org.apache.spark.sql.catalyst.parser.ParserInterface
-  with Logging {
-
-  private lazy val substitutor = new VariableSubstitution()
-  private lazy val astBuilder = new PaimonSqlExtensionsAstBuilder(delegate)
-  private val nonReservedIdentifierTokenTypes = Set(
-    PaimonSqlExtensionsParser.ALTER,
-    PaimonSqlExtensionsParser.AS,
-    PaimonSqlExtensionsParser.CALL,
-    PaimonSqlExtensionsParser.CREATE,
-    PaimonSqlExtensionsParser.DAYS,
-    PaimonSqlExtensionsParser.DELETE,
-    PaimonSqlExtensionsParser.EXISTS,
-    PaimonSqlExtensionsParser.HOURS,
-    PaimonSqlExtensionsParser.IF,
-    PaimonSqlExtensionsParser.LIKE,
-    PaimonSqlExtensionsParser.NOT,
-    PaimonSqlExtensionsParser.OF,
-    PaimonSqlExtensionsParser.OR,
-    PaimonSqlExtensionsParser.TABLE,
-    PaimonSqlExtensionsParser.REPLACE,
-    PaimonSqlExtensionsParser.RETAIN,
-    PaimonSqlExtensionsParser.VERSION,
-    PaimonSqlExtensionsParser.TAG,
-    PaimonSqlExtensionsParser.TRUE,
-    PaimonSqlExtensionsParser.FALSE,
-    PaimonSqlExtensionsParser.MAP,
-    PaimonSqlExtensionsParser.COPY,
-    PaimonSqlExtensionsParser.INTO,
-    PaimonSqlExtensionsParser.FROM,
-    PaimonSqlExtensionsParser.FILE_FORMAT,
-    PaimonSqlExtensionsParser.PATTERN,
-    PaimonSqlExtensionsParser.FORCE,
-    PaimonSqlExtensionsParser.ON_ERROR,
-    PaimonSqlExtensionsParser.ABORT_STATEMENT,
-    PaimonSqlExtensionsParser.OVERWRITE,
-    PaimonSqlExtensionsParser.CSV
-  )
-
-  /** Parses a string to a LogicalPlan. */
-  override def parsePlan(sqlText: String): LogicalPlan = {
-    val sqlTextAfterSubstitution = substitutor.substitute(sqlText)
-    if (isPaimonCommand(sqlTextAfterSubstitution)) {
-      parse(sqlTextAfterSubstitution)(parser => 
astBuilder.visit(parser.singleStatement()))
-        .asInstanceOf[LogicalPlan]
-    } else {
-      var plan =
-        try {
-          delegate.parsePlan(sqlText)
-        } catch {
-          case _: ParseException if 
maybeCatalogCreateTableLike(sqlTextAfterSubstitution) =>
-            parse(sqlTextAfterSubstitution)(parser => 
astBuilder.visit(parser.singleStatement()))
-              .asInstanceOf[LogicalPlan]
-        }
-      val sparkSession = PaimonSparkSession.active
-      parserRules(sparkSession).foreach(
-        rule => {
-          plan = rule.apply(plan)
-        })
-      plan
-    }
-  }
-
-  private def parserRules(sparkSession: SparkSession): Seq[Rule[LogicalPlan]] 
= {
-    Seq(
-      RewritePaimonViewCommands(sparkSession),
-      RewritePaimonFunctionCommands(sparkSession),
-      RewriteCreateTableLikeCommand(sparkSession),
-      RewriteSparkDDLCommands(sparkSession)
-    )
-  }
-
-  /** Parses a string to an Expression. */
-  override def parseExpression(sqlText: String): Expression =
-    delegate.parseExpression(sqlText)
-
-  /** Parses a string to a TableIdentifier. */
-  override def parseTableIdentifier(sqlText: String): TableIdentifier =
-    delegate.parseTableIdentifier(sqlText)
-
-  /** Parses a string to a FunctionIdentifier. */
-  override def parseFunctionIdentifier(sqlText: String): FunctionIdentifier =
-    delegate.parseFunctionIdentifier(sqlText)
-
-  /**
-   * Creates StructType for a given SQL string, which is a comma separated 
list of field definitions
-   * which will preserve the correct Hive metadata.
-   */
-  override def parseTableSchema(sqlText: String): StructType =
-    delegate.parseTableSchema(sqlText)
-
-  /** Parses a string to a DataType. */
-  override def parseDataType(sqlText: String): DataType =
-    delegate.parseDataType(sqlText)
-
-  /** Parses a string to a multi-part identifier. */
-  override def parseMultipartIdentifier(sqlText: String): Seq[String] =
-    delegate.parseMultipartIdentifier(sqlText)
-
-  /** Returns whether SQL text is command. */
-  private def isPaimonCommand(sqlText: String): Boolean = {
-    val normalized = sqlText
-      .toLowerCase(Locale.ROOT)
-      .trim()
-      .replaceAll("--.*?\\n", " ")
-      .replaceAll("\\s+", " ")
-      .replaceAll("/\\*.*?\\*/", " ")
-      .replaceAll("`", "")
-      .trim()
-    isPaimonProcedure(normalized) || isTagRefDdl(normalized) || 
isCopyInto(normalized)
-  }
-
-  // All builtin paimon procedures are under the 'sys' namespace
-  private def isPaimonProcedure(normalized: String): Boolean = {
-    normalized.startsWith("call") &&
-    SparkProcedures.names().asScala.map("sys." + _).exists(normalized.contains)
-  }
-
-  private def isTagRefDdl(normalized: String): Boolean = {
-    normalized.startsWith("show tags") ||
-    (normalized.startsWith("alter table") &&
-      (normalized.contains("create tag") ||
-        normalized.contains("replace tag") ||
-        normalized.contains("rename tag") ||
-        normalized.contains("delete tag")))
-  }
-
-  private def isCopyInto(normalized: String): Boolean = {
-    normalized.startsWith("copy into")
-  }
-
-  /**
-   * Cheap token-level check for `CREATE TABLE [IF NOT EXISTS] x.y[.z] LIKE 
...` shape. Used as a
-   * gate for the Paimon parser fallback when the delegate parser rejects a 
catalog-qualified CREATE
-   * TABLE LIKE statement.
-   */
-  private def maybeCatalogCreateTableLike(sqlText: String): Boolean = {
-    if (org.apache.spark.SPARK_VERSION < "3.4") {
-      return false
-    }
-    if (!startsWithCreateTable(sqlText)) {
-      return false
-    }
-
-    tokenStream(sqlText) match {
-      case Some(tokens) => maybeCreateTableLike(tokens)
-      case None => false
-    }
-  }
-
-  private def tokenStream(sqlText: String): Option[CommonTokenStream] = {
-    try {
-      val lexer = new PaimonSqlExtensionsLexer(
-        new UpperCaseCharStream(CharStreams.fromString(sqlText)))
-      lexer.removeErrorListeners()
-      lexer.addErrorListener(PaimonParseErrorListener)
-
-      val tokens = new CommonTokenStream(lexer)
-      tokens.fill()
-      Some(tokens)
-    } catch {
-      case _: PaimonParseException => None
-    }
-  }
-
-  private def maybeCreateTableLike(tokenStream: CommonTokenStream): Boolean = {
-    val tokens = tokenStream.getTokens.asScala
-      .filter(token => token.getChannel == Token.DEFAULT_CHANNEL)
-      .filterNot(token => token.getType == Token.EOF)
-
-    if (tokens.length < 5) return false
-    if (tokens(0).getType != PaimonSqlExtensionsParser.CREATE) return false
-    if (tokens(1).getType != PaimonSqlExtensionsParser.TABLE) return false
-
-    var idx = 2
-    if (
-      idx + 2 < tokens.length &&
-      tokens(idx).getType == PaimonSqlExtensionsParser.IF &&
-      tokens(idx + 1).getType == PaimonSqlExtensionsParser.NOT &&
-      tokens(idx + 2).getType == PaimonSqlExtensionsParser.EXISTS
-    ) {
-      idx += 3
-    }
-
-    if (idx >= tokens.length || !isIdentifierToken(tokens(idx))) return false
-    idx += 1
-
-    while (
-      idx + 1 < tokens.length &&
-      tokens(idx).getText == "." &&
-      isIdentifierToken(tokens(idx + 1))
-    ) {
-      idx += 2
-    }
-
-    idx < tokens.length && tokens(idx).getType == 
PaimonSqlExtensionsParser.LIKE
-  }
-
-  private def isIdentifierToken(token: Token): Boolean = {
-    token.getType == PaimonSqlExtensionsParser.IDENTIFIER ||
-    token.getType == PaimonSqlExtensionsParser.BACKQUOTED_IDENTIFIER ||
-    nonReservedIdentifierTokenTypes.contains(token.getType)
-  }
-
-  private def startsWithCreateTable(sqlText: String): Boolean = {
-    val createIndex = skipWhitespaceAndComments(sqlText, 0)
-    if (!matchesWord(sqlText, createIndex, "create")) {
-      return false
-    }
-
-    val tableIndex = skipWhitespaceAndComments(sqlText, createIndex + 
"create".length)
-    matchesWord(sqlText, tableIndex, "table")
-  }
-
-  private def skipWhitespaceAndComments(sqlText: String, start: Int): Int = {
-    var index = start
-    var continue = true
-
-    while (continue) {
-      while (index < sqlText.length && sqlText.charAt(index).isWhitespace) {
-        index += 1
-      }
-
-      if (
-        index + 1 < sqlText.length &&
-        sqlText.charAt(index) == '-' &&
-        sqlText.charAt(index + 1) == '-'
-      ) {
-        index += 2
-        while (
-          index < sqlText.length &&
-          sqlText.charAt(index) != '\n' &&
-          sqlText.charAt(index) != '\r'
-        ) {
-          index += 1
-        }
-      } else if (
-        index + 1 < sqlText.length &&
-        sqlText.charAt(index) == '/' &&
-        sqlText.charAt(index + 1) == '*'
-      ) {
-        val close = sqlText.indexOf("*/", index + 2)
-        index = if (close >= 0) close + 2 else sqlText.length
-      } else {
-        continue = false
-      }
-    }
-
-    index
-  }
-
-  private def matchesWord(sqlText: String, index: Int, word: String): Boolean 
= {
-    index + word.length <= sqlText.length &&
-    sqlText.regionMatches(true, index, word, 0, word.length) &&
-    (index + word.length == sqlText.length ||
-      !isIdentifierPart(sqlText.charAt(index + word.length)))
-  }
-
-  private def isIdentifierPart(char: Char): Boolean = {
-    char.isLetterOrDigit || char == '_'
-  }
-
-  protected def parse[T](command: String)(toResult: PaimonSqlExtensionsParser 
=> T): T = {
-    val lexer = new PaimonSqlExtensionsLexer(
-      new UpperCaseCharStream(CharStreams.fromString(command)))
-    lexer.removeErrorListeners()
-    lexer.addErrorListener(PaimonParseErrorListener)
-
-    val tokenStream = new CommonTokenStream(lexer)
-    val parser = new PaimonSqlExtensionsParser(tokenStream)
-    parser.addParseListener(PaimonSqlExtensionsPostProcessor)
-    parser.removeErrorListeners()
-    parser.addErrorListener(PaimonParseErrorListener)
-
-    try {
-      try {
-        parser.getInterpreter.setPredictionMode(PredictionMode.SLL)
-        toResult(parser)
-      } catch {
-        case _: ParseCancellationException =>
-          tokenStream.seek(0)
-          parser.reset()
-          parser.getInterpreter.setPredictionMode(PredictionMode.LL)
-          toResult(parser)
-      }
-    } catch {
-      case e: PaimonParseException if e.command.isDefined =>
-        throw e
-      case e: PaimonParseException =>
-        throw e.withCommand(command)
-      case e: AnalysisException =>
-        val position = Origin(e.line, e.startPosition)
-        throw new PaimonParseException(Option(command), e.message, position, 
position)
-    }
-  }
-
-  def parseQuery(sqlText: String): LogicalPlan =
-    parsePlan(sqlText)
-}
-
-/* Copied from Apache Spark's to avoid dependency on Spark Internals */
-class UpperCaseCharStream(wrapped: CodePointCharStream) extends CharStream {
-  override def consume(): Unit = wrapped.consume()
-  override def getSourceName: String = wrapped.getSourceName
-  override def index(): Int = wrapped.index
-  override def mark(): Int = wrapped.mark
-  override def release(marker: Int): Unit = wrapped.release(marker)
-  override def seek(where: Int): Unit = wrapped.seek(where)
-  override def size(): Int = wrapped.size
-
-  override def getText(interval: Interval): String = wrapped.getText(interval)
-
-  // scalastyle:off
-  override def LA(i: Int): Int = {
-    val la = wrapped.LA(i)
-    if (la == 0 || la == IntStream.EOF) la
-    else Character.toUpperCase(la)
-  }
-  // scalastyle:on
-}
-
-/** The post-processor validates & cleans-up the parse tree during the parse 
process. */
-case object PaimonSqlExtensionsPostProcessor extends 
PaimonSqlExtensionsBaseListener {
-
-  /** Removes the back ticks from an Identifier. */
-  override def exitQuotedIdentifier(ctx: QuotedIdentifierContext): Unit = {
-    replaceTokenByIdentifier(ctx, 1) {
-      token =>
-        // Remove the double back ticks in the string.
-        token.setText(token.getText.replace("``", "`"))
-        token
-    }
-  }
-
-  /** Treats non-reserved keywords as Identifiers. */
-  override def exitNonReserved(ctx: NonReservedContext): Unit = {
-    replaceTokenByIdentifier(ctx, 0)(identity)
-  }
-
-  private def replaceTokenByIdentifier(ctx: ParserRuleContext, stripMargins: 
Int)(
-      f: CommonToken => CommonToken = identity): Unit = {
-    val parent = ctx.getParent
-    parent.removeLastChild()
-    val token = ctx.getChild(0).getPayload.asInstanceOf[Token]
-    val newToken = new CommonToken(
-      new org.antlr.v4.runtime.misc.Pair(token.getTokenSource, 
token.getInputStream),
-      PaimonSqlExtensionsParser.IDENTIFIER,
-      token.getChannel,
-      token.getStartIndex + stripMargins,
-      token.getStopIndex - stripMargins
-    )
-    parent.addChild(new TerminalNodeImpl(f(newToken)))
-  }
-}
-
-/* Partially copied from Apache Spark's Parser to avoid dependency on Spark 
Internals */
-case object PaimonParseErrorListener extends BaseErrorListener {
-  override def syntaxError(
-      recognizer: Recognizer[_, _],
-      offendingSymbol: scala.Any,
-      line: Int,
-      charPositionInLine: Int,
-      msg: String,
-      e: RecognitionException): Unit = {
-    val (start, stop) = offendingSymbol match {
-      case token: CommonToken =>
-        val start = Origin(Some(line), Some(token.getCharPositionInLine))
-        val length = token.getStopIndex - token.getStartIndex + 1
-        val stop = Origin(Some(line), Some(token.getCharPositionInLine + 
length))
-        (start, stop)
-      case _ =>
-        val start = Origin(Some(line), Some(charPositionInLine))
-        (start, start)
-    }
-    throw new PaimonParseException(None, msg, start, stop)
-  }
-}
-
-/**
- * Copied from Apache Spark [[ParseException]], it contains fields and an 
extended error message
- * that make reporting and diagnosing errors easier.
- */
-class PaimonParseException(
-    val command: Option[String],
-    message: String,
-    start: Origin,
-    stop: Origin)
-  extends Exception {
-
-  override def getMessage: String = {
-    val builder = new StringBuilder
-    builder ++= "\n" ++= message
-    start match {
-      case Origin(Some(l), Some(p), Some(_), Some(_), Some(_), Some(_), 
Some(_)) =>
-        builder ++= s"(line $l, pos $p)\n"
-        command.foreach {
-          cmd =>
-            val (above, below) = cmd.split("\n").splitAt(l)
-            builder ++= "\n== SQL ==\n"
-            above.foreach(builder ++= _ += '\n')
-            builder ++= (0 until p).map(_ => "-").mkString("") ++= "^^^\n"
-            below.foreach(builder ++= _ += '\n')
-        }
-      case _ =>
-        command.foreach(cmd => builder ++= "\n== SQL ==\n" ++= cmd)
-    }
-    builder.toString
-  }
-
-  def withCommand(cmd: String): PaimonParseException =
-    new PaimonParseException(Option(cmd), message, start, stop)
-}
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/AbstractPaimonSparkSqlExtensionsParser.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/AbstractPaimonSparkSqlExtensionsParser.scala
index 1e0c13a573..3171190915 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/AbstractPaimonSparkSqlExtensionsParser.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/AbstractPaimonSparkSqlExtensionsParser.scala
@@ -43,18 +43,8 @@ import scala.collection.JavaConverters._
  * Software Foundation (ASF) under the Apache License, Version 2.0. See the 
NOTICE file distributed with this work for
  * additional information regarding copyright ownership. */
 
-/**
- * The implementation of [[ParserInterface]] that parsers the sql extension.
- *
- * <p>Most of the content of this class is referenced from Iceberg's
- * IcebergSparkSqlExtensionsParser.
- *
- * @param delegate
- *   The extension parser.
- */
 abstract class AbstractPaimonSparkSqlExtensionsParser(val delegate: 
ParserInterface)
-  extends org.apache.spark.sql.catalyst.parser.ParserInterface
-  with Logging {
+  extends Logging {
 
   private lazy val substitutor = new VariableSubstitution()
   private lazy val astBuilder = new PaimonSqlExtensionsAstBuilder(delegate)
@@ -93,7 +83,7 @@ abstract class AbstractPaimonSparkSqlExtensionsParser(val 
delegate: ParserInterf
   )
 
   /** Parses a string to a LogicalPlan. */
-  override def parsePlan(sqlText: String): LogicalPlan = {
+  def parsePlan(sqlText: String): LogicalPlan = {
     val sqlTextAfterSubstitution = substitutor.substitute(sqlText)
     if (isPaimonCommand(sqlTextAfterSubstitution)) {
       parse(sqlTextAfterSubstitution)(parser => 
astBuilder.visit(parser.singleStatement()))
@@ -126,30 +116,30 @@ abstract class AbstractPaimonSparkSqlExtensionsParser(val 
delegate: ParserInterf
   }
 
   /** Parses a string to an Expression. */
-  override def parseExpression(sqlText: String): Expression =
+  def parseExpression(sqlText: String): Expression =
     delegate.parseExpression(sqlText)
 
   /** Parses a string to a TableIdentifier. */
-  override def parseTableIdentifier(sqlText: String): TableIdentifier =
+  def parseTableIdentifier(sqlText: String): TableIdentifier =
     delegate.parseTableIdentifier(sqlText)
 
   /** Parses a string to a FunctionIdentifier. */
-  override def parseFunctionIdentifier(sqlText: String): FunctionIdentifier =
+  def parseFunctionIdentifier(sqlText: String): FunctionIdentifier =
     delegate.parseFunctionIdentifier(sqlText)
 
   /**
    * Creates StructType for a given SQL string, which is a comma separated 
list of field definitions
    * which will preserve the correct Hive metadata.
    */
-  override def parseTableSchema(sqlText: String): StructType =
+  def parseTableSchema(sqlText: String): StructType =
     delegate.parseTableSchema(sqlText)
 
   /** Parses a string to a DataType. */
-  override def parseDataType(sqlText: String): DataType =
+  def parseDataType(sqlText: String): DataType =
     delegate.parseDataType(sqlText)
 
   /** Parses a string to a multi-part identifier. */
-  override def parseMultipartIdentifier(sqlText: String): Seq[String] =
+  def parseMultipartIdentifier(sqlText: String): Seq[String] =
     delegate.parseMultipartIdentifier(sqlText)
 
   /** Returns whether SQL text is command. */
diff --git 
a/paimon-spark/paimon-spark3-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark3SqlExtensionsParser.scala
 
b/paimon-spark/paimon-spark3-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark3SqlExtensionsParser.scala
index 07481b6f63..04f8241454 100644
--- 
a/paimon-spark/paimon-spark3-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark3SqlExtensionsParser.scala
+++ 
b/paimon-spark/paimon-spark3-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark3SqlExtensionsParser.scala
@@ -22,4 +22,5 @@ import org.apache.spark.sql.catalyst.parser.ParserInterface
 import 
org.apache.spark.sql.catalyst.parser.extensions.AbstractPaimonSparkSqlExtensionsParser
 
 class PaimonSpark3SqlExtensionsParser(override val delegate: ParserInterface)
-  extends AbstractPaimonSparkSqlExtensionsParser(delegate) {}
+  extends AbstractPaimonSparkSqlExtensionsParser(delegate)
+  with ParserInterface
diff --git 
a/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
 
b/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
index 9bd395f333..fe4833a03c 100644
--- 
a/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
+++ 
b/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/paimon/spark/catalyst/parser/extensions/PaimonSpark4SqlExtensionsParser.scala
@@ -23,7 +23,8 @@ import 
org.apache.spark.sql.catalyst.parser.extensions.AbstractPaimonSparkSqlExt
 import org.apache.spark.sql.types.StructType
 
 class PaimonSpark4SqlExtensionsParser(override val delegate: ParserInterface)
-  extends AbstractPaimonSparkSqlExtensionsParser(delegate) {
+  extends AbstractPaimonSparkSqlExtensionsParser(delegate)
+  with ParserInterface {
 
   override def parseRoutineParam(sqlText: String): StructType = 
delegate.parseRoutineParam(sqlText)
 }

Reply via email to