查询解析
查询解析
在很多情况下,如果您能访问 Postgres 内部用来处理查询的查询树,将会有很大帮助。
例如,当您需要对查询执行非平凡的操作时,例如
- 过滤包含特定表的查询
- 重新格式化和美化查询字符串
- 修改或删除查询字符串的某些部分,例如重命名列
在 Postgres 文档 中,很好地概述了如何处理查询执行以及查询字符串的中间树表示。
方法
使用 Postgres 内部机制
Postgres 已经提供了从给定的查询字符串创建查询树的工具。
raw_parser
将查询语句作为输入,并将返回一个解析树。然后可以使用 nodeToString
将此解析树进行美化打印。
一个示例可以 在这里 查看,这很容易在 Postgres 扩展 中实现,如 这里 所示。
SELECT * FROM foo where bar = 42 ORDER BY id DESC LIMIT 23; ( {SELECT :distinctClause <> :intoClause <> :targetList ( {RESTARGET :name <> :indirection <> :val {COLUMNREF :fields ( {A_STAR } ) :location 7 } :location 7 } ) :fromClause ( {RANGEVAR :schemaname <> :relname foo :inhOpt 2 :relpersistence p :alias <> :location 14 } ) :whereClause {AEXPR :name ("=") :lexpr {COLUMNREF :fields ("bar") :location 24 } :rexpr {A_CONST :val 42 :location 30 } :location 28 } :groupClause <> :havingClause <> :windowClause <> :valuesLists <> :sortClause ( {SORTBY :node {COLUMNREF :fields ("id") :location 42 } :sortby_dir 2 :sortby_nulls 0 :useOp <> :location -1 } ) :limitOffset <> :limitCount {A_CONST :val 23 :location 56 } :lockingClause <> :withClause <> :op 0 :all false :larg <> :rarg <> } )
但是,这种方法并非没有缺点,因为 nodeToString 代码仅用于打印计划的查询树。这些树被重写为仅包含 执行器 最终需要的节点。这意味着所有在查询执行的后期阶段不需要的节点都无法打印,例如 INSERT/UPDATE/DELETE 节点 - 因为在后期,这些节点都聚合在一个 ModifyStmt
节点中。
pg_query
pg_query 是一个 Ruby gem,它可以生成以 JSON 格式表示的查询树。它通过使用一个修补过的 Postgres 版本来实现这一点,一方面解决了上面提到的缺点,另一方面它提供了一个新函数 nodeToJSONString
,它将查询树转换为 JSON 表示而不是原生 Postgres 格式。
Ruby 绑定只是 Postgres 代码周围的粘合代码。更多信息可以在 这篇博文 中找到
#<PgQuery:0x000000009673a0 @query="SELECT * FROM foo where bar = 42 ORDER BY id DESC LIMIT 23;", @parsetree= [{"SELECT"=> {"distinctClause"=>nil, "intoClause"=>nil, "targetList"=> [{"RESTARGET"=> {"name"=>nil, "indirection"=>nil, "val"=>{"COLUMNREF"=>{"fields"=>[{"A_STAR"=>{}}], "location"=>7}}, "location"=>7}}], "fromClause"=> [{"RANGEVAR"=> {"schemaname"=>nil, "relname"=>"foo", "inhOpt"=>2, "relpersistence"=>"p", "alias"=>nil, "location"=>14}}], "whereClause"=> {"AEXPR"=> {"name"=>["="], "lexpr"=>{"COLUMNREF"=>{"fields"=>["bar"], "location"=>24}}, "rexpr"=>{"A_CONST"=>{"val"=>42, "location"=>30}}, "location"=>28}}, "groupClause"=>nil, "havingClause"=>nil, "windowClause"=>nil, "valuesLists"=>nil, "sortClause"=> [{"SORTBY"=> {"node"=>{"COLUMNREF"=>{"fields"=>["id"], "location"=>42}}, "sortby_dir"=>2, "sortby_nulls"=>0, "useOp"=>nil, "location"=>-1}}], "limitOffset"=>nil, "limitCount"=>{"A_CONST"=>{"val"=>23, "location"=>56}}, "lockingClause"=>nil, "withClause"=>nil, "op"=>0, "all"=>false, "larg"=>nil, "rarg"=>nil}}], @warnings=[]>
pgpool-II
pgpool-II 是 Postgresql 的一个中间件,在最简单的用例中,它充当客户端和数据库服务器之间的连接代理。
为了能够进行高级查询路由,它需要了解从查询发送的查询。为了实现这一点,pgpool 项目 fork 了解析器并实现了他们自己的节点 outFuncs
,以便能够从给定的查询树中重新创建查询字符串。
如果您想创建自己的尽可能正确的 Postgres 查询美化打印程序,这可能是一个不错的起点。
sqlparse
sqlparse 是一个 Python 模块,它实现了一个非验证的 SQL 解析器,适用于解析、拆分和格式化 SQL 语句。它是 http://sqlformat.org/ 的基础 - 一个 SQL 美化打印程序。
对查询的内省非常有限,而且由于它不是专门针对 Postgres 的,所以它的功能在深入分析查询树时非常有限。
pglast
作为 sqlparse 的 Python 使用替代方案,还有 pglast,它与上面提到的 pg_query 一样,也是基于 libpg_query 的底层机制来实现的,使得 PostgreSQL 的内部查询解析代码和解析树的元素不仅可用于验证和按语法分解查询,还可用于从各个部分构建查询、修补查询等,并附带美化打印以及一些 PostgreSQL 特定的功能(将内置函数调用中的逗号分隔的参数列表重写为可用的标准等效短语,例如)以渲染到各种输出。例如:
>>> import pglast >>> import pglast.stream >>> s = pglast.parse_sql("select pg_catalog.overlay('terse', 'verbo', 1, 3)") >>> s (<RawStmt stmt=<SelectStmt targetList=(<ResTarget val=<FuncCall funcname=(<String sval='pg_catalog'>, <String sval='overlay'>) args=(<A_Const isnull=False val=<String sval='terse'>>, <A_Const isnull=False val=<String sval='verbo'>>, <A_Const isnull=False val=<Integer ival=1>>, <A_Const isnull=False val=<Integer ival=3>>) agg_within_group=False agg_star=False agg_distinct=False func_variadic=False funcformat=<CoercionForm.COERCE_EXPLICIT_CALL: 0>>>,) groupDistinct=False limitOption=<LimitOption.LIMIT_OPTION_DEFAULT: 0> op=<SetOperation.SETOP_NONE: 0> all=False> stmt_location=0 stmt_len=0>,) >>> pglast.stream.IndentedStream()(s[0].stmt) "SELECT pg_catalog.overlay('terse', 'verbo', 1, 3)" >>> pglast.stream.IndentedStream(special_functions=True)(s[0].stmt) "SELECT overlay('terse' PLACING 'verbo' FROM 1 FOR 3)" >>> s[0].stmt.targetList[0].val.args[0].val.sval = "hasten" >>> pglast.stream.IndentedStream(special_functions=True)(s[0].stmt) "SELECT overlay('hasten' PLACING 'verbo' FROM 1 FOR 3)" >>> import pprint >>> pprint.pprint(s[0].stmt()) # yes, a node (in this case, the SelectStmt at s[0].stmt) is called as a no-argument function to get its serialization {'@': 'SelectStmt', [... etc. ... ] 'targetList': ({'@': 'ResTarget', 'indirection': None, 'location': 7, 'name': None, 'val': {'@': 'FuncCall', 'agg_distinct': False, 'agg_filter': None, 'agg_order': None, 'agg_star': False, 'agg_within_group': False, 'args': ({'@': 'A_Const', 'isnull': False, 'val': {'@': 'String', 'sval': 'hasten'}}, {'@': 'A_Const', 'isnull': False, 'val': {'@': 'String', 'sval': 'verbo'}}, {'@': 'A_Const', 'isnull': False, 'val': {'@': 'Integer', 'ival': 1}}, {'@': 'A_Const', 'isnull': False, 'val': {'@': 'Integer', 'ival': 3}}), 'func_variadic': False, 'funcformat': {'#': 'CoercionForm', 'name': 'COERCE_EXPLICIT_CALL', 'value': 0}, 'funcname': ({'@': 'String', 'sval': 'pg_catalog'}, {'@': 'String', 'sval': 'overlay'}), 'location': 7, 'over': None}},), 'valuesLists': None, 'whereClause': None, 'windowClause': None, 'withClause': None}
它还包含一个 pgpp 脚本,该脚本在命令行中行使了大部分此功能。
其他 libpq_query 绑定
README for libpg_query 还列出了其他几种语言中的绑定。
待办事项
为了让使用查询树的人们的生活更轻松,以下事项需要在 Postgres 核心代码中解决
- 为所有节点类型实现 outFuncs,而不仅仅是那些在规划后阶段看到的节点类型。
- 提供可用于生成例如 JSON 树而不是原生 Postgres 树格式的备用 outFuncs。