You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala Parser Combinator忽略可选元素错误的技术问询

构建支持可选子句的类SQL DSL解析器

我来帮你搞定这个类SQL DSL解析器的构建问题——既要把where、order by这类子句设为可选,又要解决解析器随便忽略输入错误的毛病!

第一步:定义查询的数据结构

先给咱们的查询结构搭个架子,用Scala的case class来建模最方便不过了。这里把where、order by、group by都改成可选类型(Option),完美适配“可有可无”的需求:

case class Query(
  select: Select,
  where: Option[Where] = None,
  orderBy: Option[OrderBy] = None,
  groupBy: Option[GroupBy] = None
)
case class Select(cols: Seq[String])
case class Where(conditions: Seq[String])
case class OrderBy(cols: Seq[String])
case class GroupBy(cols: Seq[String])

第二步:基于StandardTokenParsers实现解析器

接下来是核心的解析逻辑,咱们要调整规则支持可选子句,同时给错误处理加个“紧箍咒”,不能再让它随便忽略输入错误了:

object QueryLanguage extends StandardTokenParsers {
  // 先把SQL里的关键字和分隔符定义好,避免解析歧义
  lexical.delimiters ++= List(",", "=", "<", ">", "AND", "OR")
  lexical.reserved ++= List("SELECT", "FROM", "WHERE", "ORDER", "BY", "GROUP")

  // 解析SELECT子句:匹配"SELECT"后跟逗号分隔的字段列表
  lazy val selectBlock: Parser[Select] = 
    "SELECT" ~> repsep(ident, ",") ^^ { cols => Select(cols) }

  // 解析WHERE子句(可选):用opt()包装,不存在就返回None
  lazy val whereBlock: Parser[Option[Where]] = 
    opt("WHERE" ~> repsep(ident ~ ("=" | "<" | ">") ~ ident, "AND") ^^ { conditions =>
      // 把解析出来的条件转换成字符串格式
      Where(conditions.map(c => s"${c._1._1}${c._1._2}${c._2}"))
    })

  // 解析ORDER BY子句(可选)
  lazy val orderByBlock: Parser[Option[OrderBy]] = 
    opt("ORDER" ~ "BY" ~> repsep(ident, ",") ^^ { cols => OrderBy(cols) })

  // 解析GROUP BY子句(可选)
  lazy val groupByBlock: Parser[Option[GroupBy]] = 
    opt("GROUP" ~ "BY" ~> repsep(ident, ",") ^^ { cols => GroupBy(cols) })

  // 完整的查询解析规则:强制要求FROM子句(符合SQL基本规范)
  lazy val sql: Parser[Query] = 
    selectBlock ~ "FROM" ~ ident ~ whereBlock ~ orderByBlock ~ groupByBlock ^^ {
      case select ~ _ ~ _ ~ where ~ orderBy ~ groupBy => 
        Query(select, where, orderBy, groupBy)
    }

  // 自定义解析入口,明确返回结果或错误信息,拒绝静默忽略错误
  def parse(input: String): Either[String, Query] = {
    val tokens = new lexical.Scanner(input)
    phrase(sql)(tokens) match {
      case Success(query, _) => Right(query)
      case Failure(msg, _) => Left(s"解析失败:$msg")
      case Error(msg, _) => Left(s"解析错误:$msg")
    }
  }
}

关键细节说明

  • 可选子句处理:用opt()包装where、order by、group by的解析规则,当这些子句不存在时,解析器会返回None,存在则返回Some(对应实例),完美适配需求。
  • 错误处理优化:默认的StandardTokenParsers可能会在解析失败时静默跳过错误,咱们用phrase()确保整个输入都被完整解析,然后通过Either返回明确的错误信息,再也不会“假装没看见”错误了。
  • 词法规则完善:把关键字和分隔符分开定义,避免解析时出现歧义(比如把"ORDER BY"拆成两个关键字处理,更符合词法解析的规范)。

实际使用示例

来试试咱们的解析器好不好用:

// 带where和order by的完整查询
val validFullQuery = "SELECT name, age FROM users WHERE age > 18 ORDER BY name"
QueryLanguage.parse(validFullQuery)
// 输出:Right(Query(Select(List(name, age)), Some(Where(List(age>18))), Some(OrderBy(List(name))), None))

// 只有select的极简查询
val minimalQuery = "SELECT id FROM products"
QueryLanguage.parse(minimalQuery)
// 输出:Right(Query(Select(List(id)), None, None, None))

// 有语法错误的查询(缺少条件值)
val invalidQuery = "SELECT name FROM users WHERE age > "
QueryLanguage.parse(invalidQuery)
// 输出:Left(解析失败:identifier expected but end of input found)

内容的提问来源于stack exchange,提问作者Krishnan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:05:33