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
相关产品推荐
相关产品推荐

