PostgreSQL的学习心得和知识总结(一百五十五)|[performance]优化期间将 WHERE 子句中的 IN VALUES 替换为 ANY

news2024/10/22 3:39:22

注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往
7、参考书籍:《事务处理 概念与技术》


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于PostgreSQL master源码开发而成


优化期间将 WHERE 子句中的 IN VALUES 替换为 ANY

  • 文章快速说明索引
  • 功能实现背景说明
    • 简介
    • 引用
  • 功能实现源码解析
    • 现有语法分析
    • 新增补丁解析



文章快速说明索引

学习目标:

做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。


学习内容:(详见目录)

1、优化期间将 WHERE 子句中的 IN VALUES 替换为 ANY


学习时间:

2024年10月21日 21:53:26


学习产出:

1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习


注:下面我们所有的学习环境是Centos8+PostgreSQL master +Oracle19C+MySQL8.0

postgres=# select version();
                                                  version                                                   
------------------------------------------------------------------------------------------------------------
 PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21), 64-bit
(1 row)

postgres=#

#-----------------------------------------------------------------------------#

SQL> select * from v$version;          

BANNER        Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
BANNER_FULL	  Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0	
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
CON_ID 0


#-----------------------------------------------------------------------------#

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.06 sec)

mysql>

功能实现背景说明

原文链接:https://danolivo.substack.com/p/postgresql-values-any-transformation

discussion:Replace IN VALUES with ANY in WHERE clauses during optimization

简介

像往常一样,这个项目是由多个用户报告促成的,这些报告包含一些典型的抱怨,例如“SQL server执行查询的时间更快” 或 “Postgres 无法pick up我的索引”。这些报告共同的根本问题是经常使用的 VALUES 序列,通常在查询树中转换为 SEMI JOIN。

我还想讨论一个普遍的问题:开源 DBMS 是否应该纠正用户错误?我的意思是在开始搜索最佳计划之前优化查询,消除自连接、子查询和简化表达式 - 所有这些都可以通过适当的查询调整来实现。问题并不那么简单,因为 DBA 指出 Oracle 中查询规划的成本随着查询文本的复杂性而增长,这很可能是由于优化规则范围广泛等原因造成的。

现在,让我们将注意力转向 VALUES 构造。有趣的是,它不仅用于 INSERT 命令,而且还经常以集合包含测试的形式出现在 SELECT 查询中:

SELECT * FROM something WHERE x IN (VALUES (1), (2), ...);

在查询计划中,此语法结构转换为 SEMI JOIN。为了演示问题的本质,让我们生成一个测试表,其中某一列的数据分布不均匀:

postgres=# select version();
                                     version                                     
---------------------------------------------------------------------------------
 PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.1.0, 64-bit
(1 row)

postgres=# CREATE EXTENSION tablefunc;
CREATE EXTENSION
postgres=# CREATE TABLE norm_test AS
postgres-#   SELECT abs(r::integer) AS x, 'abc'||r AS payload
postgres-#   FROM normal_rand(1000, 1., 10.) AS r;
SELECT 1000
postgres=# CREATE INDEX ON norm_test (x);
CREATE INDEX
postgres=# ANALYZE norm_test;
ANALYZE
postgres=# 

这里,norm_test 表的值 x 服从正态分布,平均值为 1,标准差为 10 [1]。不同的值并不多,这些值都将包含在 MCV 统计信息中。因此,尽管分布不均匀,但仍可以准确计算每个值的重复数。此外,我们自然地在此列上引入了索引,从而简化了表的扫描。现在,让我们执行查询。查询很简单,对吧?使用两次索引扫描迭代来执行它是合理的。然而,在 Postgres 中,我们有:

postgres=# explain (verbose, costs off, analyze) SELECT * FROM norm_test WHERE x IN (VALUES (1), (29));
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Hash Semi Join (actual time=0.024..0.288 rows=97 loops=1)
   Output: norm_test.x, norm_test.payload
   Hash Cond: (norm_test.x = "*VALUES*".column1)
   ->  Seq Scan on public.norm_test (actual time=0.012..0.127 rows=1000 loops=1)
         Output: norm_test.x, norm_test.payload
   ->  Hash (actual time=0.005..0.006 rows=2 loops=1)
         Output: "*VALUES*".column1
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Values Scan on "*VALUES*" (actual time=0.001..0.002 rows=2 loops=1)
               Output: "*VALUES*".column1
 Planning Time: 0.522 ms
 Execution Time: 0.354 ms
(12 rows)

postgres=#

从这里开始,我稍微简化了解释,以便于理解。

嗯,当两个索引扫描就足够了时,是否要对所有表的元组进行顺序扫描?让我们禁用 HashJoin,看看会发生什么:

postgres=# SET enable_hashjoin = 'off';
SET
postgres=# explain (verbose, costs off, analyze) SELECT * FROM norm_test WHERE x IN (VALUES (1), (29));
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Nested Loop (actual time=0.184..0.309 rows=97 loops=1)
   Output: norm_test.x, norm_test.payload
   ->  Unique (actual time=0.010..0.014 rows=2 loops=1)
         Output: "*VALUES*".column1
         ->  Sort (actual time=0.009..0.010 rows=2 loops=1)
               Output: "*VALUES*".column1
               Sort Key: "*VALUES*".column1
               Sort Method: quicksort  Memory: 25kB
               ->  Values Scan on "*VALUES*" (actual time=0.002..0.003 rows=2 loops=1)
                     Output: "*VALUES*".column1
   ->  Bitmap Heap Scan on public.norm_test (actual time=0.089..0.135 rows=48 loops=2)
         Output: norm_test.x, norm_test.payload
         Recheck Cond: (norm_test.x = "*VALUES*".column1)
         Heap Blocks: exact=10
         ->  Bitmap Index Scan on norm_test_x_idx (actual time=0.061..0.061 rows=48 loops=2)
               Index Cond: (norm_test.x = "*VALUES*".column1)
 Planning Time: 0.442 ms
 Execution Time: 0.373 ms
(18 rows)

postgres=#

现在您可以看到 Postgres 已经挤出了最大值:在一次遍历每个外部值的 VALUES 集时,它会对表执行索引扫描。这比前一个选项有趣得多。但是,它并不像常规索引扫描那么简单。此外,如果您更仔细地查看查询说明,您会发现优化器在预测连接和索引扫描的基数时犯了一个错误。如果您重写没有 VALUES 的查询会发生什么:

postgres=# explain (verbose, costs off, analyze) SELECT * FROM norm_test WHERE x IN (1, 29);
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.norm_test (actual time=0.069..0.166 rows=97 loops=1)
   Output: x, payload
   Recheck Cond: (norm_test.x = ANY ('{1,29}'::integer[]))
   Heap Blocks: exact=8
   ->  Bitmap Index Scan on norm_test_x_idx (actual time=0.055..0.055 rows=97 loops=1)
         Index Cond: (norm_test.x = ANY ('{1,29}'::integer[]))
 Planning Time: 0.110 ms
 Execution Time: 0.192 ms
(8 rows)

postgres=# show enable_hashjoin ;
 enable_hashjoin 
-----------------
 off
(1 row)

postgres=# reset enable_hashjoin ;
RESET
postgres=# explain (verbose, costs off, analyze) SELECT * FROM norm_test WHERE x IN (1, 29);
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.norm_test (actual time=0.049..0.127 rows=97 loops=1)
   Output: x, payload
   Recheck Cond: (norm_test.x = ANY ('{1,29}'::integer[]))
   Heap Blocks: exact=8
   ->  Bitmap Index Scan on norm_test_x_idx (actual time=0.033..0.034 rows=97 loops=1)
         Index Cond: (norm_test.x = ANY ('{1,29}'::integer[]))
 Planning Time: 0.117 ms
 Execution Time: 0.157 ms
(8 rows)

postgres=#

如您所见,我们得到了一个仅包含索引扫描的查询计划,其成本几乎降低了一半。同时,通过从集合中估计每个值并将这两个值都包含在 MCV 统计信息中,Postgres 可以准确地预测此扫描的基数。

因此,使用 VALUES 序列本身并不是一个大问题(您始终可以使用 HashJoin 并对内部的 VALUES 进行哈希处理),但它却是一个危险的来源:

  • 优化器可以选择 NestLoop,但使用庞大的 VALUES 列表会降低性能。
  • 突然之间,可以选择 SeqScan 而不是 IndexScan。
  • 优化器在预测 JOIN 操作及其底层操作的基数时会出现重大估计错误。

顺便说一句,为什么有人需要使用这样的表达式?

我猜这是自动化系统(ORM 或 Rest API)测试将对象纳入特定对象集时的特殊情况。由于 VALUES 描述了一个关系表,并且这种列表的值是表行,因此我们最有可能处理的是每行代表应用程序中对象实例的情况。当对象仅由一个属性表征时,我们的案例是一个极端情况。如果我的猜测是错误的,请在评论中纠正我 - 也许有人知道其他原因?

因此,将 x IN VALUES 构造传递给优化器是有风险的。为什么不通过将此 VALUES 构造转换为数组来解决这种情况呢?然后,我们将有一个像 x = ANY [...] 这样的构造,这是 Postgres 代码中 ScalarArrayOpExpr 操作的一个特例。它将简化查询树,消除不必要的连接的出现。此外,Postgres 基数评估机制可以与数组包含检查操作一起使用。如果数组足够小(<100 个元素),它将逐个元素执行统计评估。此外,Postgres 可以通过对值进行哈希处理来优化数组搜索(如果所需的内存适合 work_mem 值)——每个人都会很高兴,对吧?

好吧,我们决定在优化实验室中尝试这样做 - 令人惊讶的是,它结果相对简单。我们遇到的第一个怪癖是转换仅适用于标量值的操作:也就是说,到目前为止,通常不可能转换形式为(x,y) IN (VALUES (1,1), (2,2), ...)的表达式,以便结果与转换前的状态完全匹配。为什么?这很难解释 - 原因在于记录类型的比较运算符的设计 - 要教会 Postgres 完全类似于标量类型地使用这样的运算符,类型缓存需要进行大量重新设计。其次,您必须记住检查此子查询(是的,VALUES 在查询树中表示为子查询)是否存在易失性函数 - 就是这样 - 查询树变量器的一次传递进行转换,非常类似于 [2] 用数组替换 VALUES,如果可能的话将其构造化。奇怪的是,即使 VALUES 包含参数、函数调用和复杂表达式,也可以进行转换,如下所示:

-- 这个是现在pg的执行计划

[postgres@localhost:~/test/bin]$ ./psql 
psql (18devel)
Type "help" for help.

postgres=# CREATE TEMP TABLE onek (ten int, two real, four real);
CREATE TABLE
postgres=# PREPARE test (int,numeric, text) AS
postgres-#   SELECT ten FROM onek
postgres-#   WHERE sin(two)*four/($3::real) IN (VALUES (sin($2)), (2), ($1));
PREPARE
postgres=# explain (verbose, costs off, analyze) EXECUTE test(1, 2, '3');
                                            QUERY PLAN                                             
---------------------------------------------------------------------------------------------------
 Hash Semi Join (actual time=0.010..0.011 rows=0 loops=1)
   Output: onek.ten
   Hash Cond: (((sin((onek.two)::double precision) * onek.four) / '3'::real) = "*VALUES*".column1)
   ->  Seq Scan on pg_temp.onek (actual time=0.009..0.009 rows=0 loops=1)
         Output: onek.ten, onek.two, onek.four
   ->  Hash (never executed)
         Output: "*VALUES*".column1
         ->  Values Scan on "*VALUES*" (never executed)
               Output: "*VALUES*".column1
 Planning Time: 1.317 ms
 Execution Time: 0.062 ms
(11 rows)

postgres=#

下面是他们patch的计划:

[postgres@localhost:~/test/bin]$ ./psql 
psql (18devel)
Type "help" for help.

postgres=# CREATE TEMP TABLE onek (ten int, two real, four real);
CREATE TABLE
postgres=# PREPARE test (int,numeric, text) AS
  SELECT ten FROM onek
  WHERE sin(two)*four/($3::real) IN (VALUES (sin($2)), (2), ($1));
PREPARE
postgres=# explain (verbose, costs off, analyze) EXECUTE test(1, 2, '3');
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on pg_temp.onek (actual time=0.009..0.010 rows=0 loops=1)
   Output: ten
   Filter: (((sin((onek.two)::double precision) * onek.four) / '3'::real) = ANY ('{0.9092974268256817,2,1}'::double precision[]))
 Planning Time: 1.336 ms
 Execution Time: 0.036 ms
(5 rows)

postgres=#

该功能目前正在测试中。查询树结构非常稳定,考虑到对内核版本的依赖性很小,因此没有理由修改代码;它可以在 Postgres 中使用,直到版本 10 甚至更早。像往常一样,您可以使用在典型的 Ubuntu 22 环境中编译的库的二进制文件 - 它没有任何 UI,可以静态或动态加载。

现在,我上面提到的真正的圣战。由于我们将其作为外部库执行,因此我们必须拦截规划器钩子(以在优化之前简化查询树),这需要我们额外通过查询树。显然,系统中的大多数查询都不需要这种转换,并且此操作只会增加开销。但是,当它起作用时,它可以提供明显的效果(从我的观察来看,它确实如此)。

直到最近,PostgreSQL 社区才达成了共识 [3, 4]:如果可以通过更改查询本身来解决问题,那么使内核代码复杂化就没有意义了,因为这将不可避免地导致维护成本增加,并且(回想一下 Oracle 的经验)会影响优化器本身的性能。

然而,通过查看核心提交,我注意到社区的意见似乎正在发生变化。例如,今年,他们通过添加相关子查询 [5] 使子查询到 SEMI JOIN 转换的技术变得复杂。不久之后,他们允许父查询接收有关子查询结果排序顺序的信息 [6],尽管以前为了简化规划,查询及其子查询是独立规划的。这看起来像是一种重新规划子查询的方法,不是吗?

您怎么看?开源项目是否能够支持多种转换规则,从而消除用户引入的冗余和复杂性,从而使查询更具可读性和可理解性?最重要的是 - 它值得吗?

引用

  1. F.41. tablefunc — functions that return tables
  2. OR-clause support for indexes
  3. Discussion on missing optimizations, 2017
  4. BUG #18643: EXPLAIN estimated rows mismatch, 2024
  5. Commit 9f13376. pull-up correlated subqueries
  6. Commit a65724d. Propagate pathkeys from CTEs up to the outer query

功能实现源码解析

现有语法分析

原作者patch备注,如下:

  • 将意外出现的 'x IN (VALUES, …) 表达式转换为 x IN ‘ANY …’。第二种变体更好,因为它可以让规划器避免使用一个不必要的 SEMI JOIN 运算符。
  • 这种表达式形式通常出现在自动生成的查询中,作为在一组其他查询中搜索对象的极端情况,当对象仅由一个属性描述时。
  • 让这种不寻常的优化成为核心,因为如果没有这种构造,规划器只会多花几个周期。

现在的x = 'ANY ...'x 'IN ...',语法如下:

a_expr:		c_expr									{ $$ = $1; }
			...
			| a_expr subquery_Op sub_type '(' a_expr ')'		%prec Op
				{
					if ($3 == ANY_SUBLINK)
						$$ = (Node *) makeA_Expr(AEXPR_OP_ANY, $2, $1, $5, @2);
					else
						$$ = (Node *) makeA_Expr(AEXPR_OP_ALL, $2, $1, $5, @2);
				}
			...
			;

sub_type:	ANY										{ $$ = ANY_SUBLINK; }
			| SOME									{ $$ = ANY_SUBLINK; }
			| ALL									{ $$ = ALL_SUBLINK; }
		;
a_expr:		c_expr									{ $$ = $1; }
			...
			| a_expr IN_P in_expr
				{
					/* in_expr returns a SubLink or a list of a_exprs */
					if (IsA($3, SubLink))
					{
						/* generate foo = ANY (subquery) */
						SubLink	   *n = (SubLink *) $3;

						n->subLinkType = ANY_SUBLINK;
						n->subLinkId = 0;
						n->testexpr = $1;
						n->operName = NIL;		/* show it's IN not = ANY */
						n->location = @2;
						$$ = (Node *) n;
					}
					else
					{
						/* 生成标量 IN 表达式 */
						$$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2);
					}
				}
			...
			;

看一下相关的执行计划,我这里在原来patch基础上增加了一个GUC参数进行控制:

在这里插入图片描述

在这里插入图片描述

如上,IN VALUES在这种情况下的执行计划不佳,这也是此次patch的目的。


继续对上面语法in_expr进行拆解,如下:

in_expr:	select_with_parens
				{
					SubLink	   *n = makeNode(SubLink);

					n->subselect = $1;
					/* other fields will be filled later */
					$$ = (Node *) n;
				}
			| '(' expr_list ')'						{ $$ = (Node *) $2; }
		;
select_with_parens:
			'(' select_no_parens ')'				{ $$ = $2; }
			| '(' select_with_parens ')'			{ $$ = $2; }
		;

select_no_parens:
			simple_select						{ $$ = $1; }
			...
			
simple_select:
				...
				| values_clause							{ $$ = $1; }
				...

values_clause:
			VALUES '(' expr_list ')'
				{
					SelectStmt *n = makeNode(SelectStmt);

					n->valuesLists = list_make1($3);
					$$ = (Node *) n;
				}
			| values_clause ',' '(' expr_list ')'
				{
					SelectStmt *n = (SelectStmt *) $1;

					n->valuesLists = lappend(n->valuesLists, $4);
					$$ = (Node *) n;
				}
		;

看到这里,做一个小结:

  • IN VALUES 在语法解析过程中,构造成了一个SubLink->subselect
  • IN 无VALUES 在语法解析过程中,构造成了一个$$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2)
  • ANY ...在语法解析过程中,构造成了一个$$ = (Node *) makeA_Expr(AEXPR_OP_ANY, $2, $1, $5, @2)

接下来,调试如下SQL:

在这里插入图片描述

如上,这两个SQL的执行计划是一样的,接下来看一下(下面这个SQL)内部的转换过程 如下:

在这里插入图片描述

此时的函数堆栈,如下:

transformAExprIn(ParseState * pstate, A_Expr * a)
transformExprRecurse(ParseState * pstate, Node * expr)
transformExpr(ParseState * pstate, Node * expr, ParseExprKind exprKind)
transformWhereClause(ParseState * pstate, Node * clause, ParseExprKind exprKind, const char * constructName)
transformSelectStmt(ParseState * pstate, SelectStmt * stmt)
transformStmt(ParseState * pstate, Node * parseTree)
transformOptionalSelectInto(ParseState * pstate, Node * parseTree)
transformExplainStmt(ParseState * pstate, ExplainStmt * stmt)
transformStmt(ParseState * pstate, Node * parseTree)
transformOptionalSelectInto(ParseState * pstate, Node * parseTree)
transformTopLevelStmt(ParseState * pstate, RawStmt * parseTree)
parse_analyze_fixedparams(RawStmt * parseTree, const char * sourceText, const Oid * paramTypes, int numParams, QueryEnvironment * queryEnv)
pg_analyze_and_rewrite_fixedparams(RawStmt * parsetree, const char * query_string, const Oid * paramTypes, int numParams, QueryEnvironment * queryEnv) 
exec_simple_query(const char * query_string)
...

新增补丁解析

调用入口,如下:

在这里插入图片描述

此时的函数堆栈,如下:

pull_up_sublinks_qual_recurse(PlannerInfo * root, Node * node, Node ** jtlink1, Relids available_rels1, Node ** jtlink2, Relids available_rels2)
pull_up_sublinks_jointree_recurse(PlannerInfo * root, Node * jtnode, Relids * relids)
pull_up_sublinks(PlannerInfo * root)
subquery_planner(PlannerGlobal * glob, Query * parse, PlannerInfo * parent_root, _Bool hasRecursion, double tuple_fraction, SetOperationStmt * setops)
standard_planner(Query * parse, const char * query_string, int cursorOptions, ParamListInfo boundParams)
planner(Query * parse, const char * query_string, int cursorOptions, ParamListInfo boundParams)
pg_plan_query(Query * querytree, const char * query_string, int cursorOptions, ParamListInfo boundParams)
standard_ExplainOneQuery(Query * query, int cursorOptions, IntoClause * into, ExplainState * es, const char * queryString, ParamListInfo params, QueryEnvironment * queryEnv)
ExplainOneQuery(Query * query, int cursorOptions, IntoClause * into, ExplainState * es, const char * queryString, ParamListInfo params, QueryEnvironment * queryEnv)
ExplainQuery(ParseState * pstate, ExplainStmt * stmt, ParamListInfo params, DestReceiver * dest)
standard_ProcessUtility(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc)
ProcessUtility(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc)
PortalRunUtility(Portal portal, PlannedStmt * pstmt, _Bool isTopLevel, _Bool setHoldSnapshot, DestReceiver * dest, QueryCompletion * qc)
FillPortalStore(Portal portal, _Bool isTopLevel)
PortalRun(Portal portal, long count, _Bool isTopLevel, _Bool run_once, DestReceiver * dest, DestReceiver * altdest, QueryCompletion * qc)
exec_simple_query(const char * query_string)
...

上面的GUC参数enable_convert_values_to_any是我新增的,可以忽略!

接下来就是此次patch的核心 函数convert_VALUES_to_ANY,如下:

// src/backend/optimizer/plan/subselect.c

/*
 * Transform appropriate testexpr and const VALUES expression to SaOpExpr.
 * 将适当的 testexpr 和 const VALUES 表达式转换为 SaOpExpr
 *
 * Return NULL, if transformation isn't allowed.
 */
ScalarArrayOpExpr *
convert_VALUES_to_ANY(Query *query, Node *testexpr)
{
	RangeTblEntry	   *rte;
	Node			   *leftop;
	Oid					consttype;
	int16				typlen;
	bool				typbyval;
	char				typalign;
	ArrayType		   *arrayConst;
	Oid					arraytype;
	Node			   *arrayNode;
	Oid					matchOpno;
	Form_pg_operator	operform;
	ScalarArrayOpExpr  *saopexpr;
	ListCell		   *lc;
	Oid					inputcollid;
	HeapTuple			opertup;
	bool				have_param = false;
	List			   *consts = NIL;

	/* Extract left side of SAOP from test epression */

	if (!IsA(testexpr, OpExpr) ||
		list_length(((OpExpr *) testexpr)->args) != 2 ||
		!is_simple_values_sequence(query))
		return NULL;

	rte = linitial_node(RangeTblEntry,query->rtable);
	leftop = linitial(((OpExpr *) testexpr)->args);
	matchOpno = ((OpExpr *) testexpr)->opno;
	inputcollid = linitial_oid(rte->colcollations);

	foreach (lc, rte->values_lists)
	{
		List *elem = lfirst(lc);
		Node *value = linitial(elem);

		value = eval_const_expressions(NULL, value);

		if (!IsA(value, Const))
			have_param = true;
		else if (((Const *) value)->constisnull)
			/*
			 * Constant expression isn't converted because it is a NULL.
			 * NULLS just not supported by the construct_array routine.
			 */
			return NULL;

		consts = lappend(consts, value);

	}
	Assert(list_length(consts) == list_length(rte->values_lists));

	consttype = linitial_oid(rte->coltypes);
	Assert(list_length(rte->coltypes) == 1 && OidIsValid(consttype));
	arraytype = get_array_type(linitial_oid(rte->coltypes));
	if (!OidIsValid(arraytype))
		return NULL;

	/* TODO: remember parameters */
	if (have_param)
	{
		/*
		 * We need to construct an ArrayExpr given we have Param's not just
		 * Const's.
		 */
		ArrayExpr  *arrayExpr = makeNode(ArrayExpr);

		/* array_collid will be set by parse_collate.c */
		arrayExpr->element_typeid = consttype;
		arrayExpr->array_typeid = arraytype;
		arrayExpr->multidims = false;
		arrayExpr->elements = consts;
		arrayExpr->location = -1;

		arrayNode = (Node *) arrayExpr;
	}
	else
	{
		int			i = 0;
		ListCell   *lc1;
		Datum	   *elems;

		/* Direct creation of Const array */

		elems = (Datum *) palloc(sizeof(Datum) * list_length(consts));
		foreach (lc1, consts)
			elems[i++] = lfirst_node(Const, lc1)->constvalue;

		get_typlenbyvalalign(consttype, &typlen, &typbyval, &typalign);

		arrayConst = construct_array(elems, i, consttype,
									 typlen, typbyval, typalign);
		arrayNode = (Node *) makeConst(arraytype, -1, inputcollid,
									   -1, PointerGetDatum(arrayConst),
									   false, false);
		pfree(elems);
	}

	/* Lookup for operator to fetch necessary information for the SAOP node */
	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(matchOpno));
	if (!HeapTupleIsValid(opertup))
		elog(ERROR, "cache lookup failed for operator %u", matchOpno);

	operform = (Form_pg_operator) GETSTRUCT(opertup);

	/* Build the SAOP expression node */
	saopexpr = makeNode(ScalarArrayOpExpr);
	saopexpr->opno = matchOpno;
	saopexpr->opfuncid = operform->oprcode;
	saopexpr->hashfuncid = InvalidOid;
	saopexpr->negfuncid = InvalidOid;
	saopexpr->useOr = true;
	saopexpr->inputcollid = inputcollid;
	saopexpr->args = list_make2(leftop, arrayNode);
	saopexpr->location = -1;

	ReleaseSysCache(opertup);

	return saopexpr;
}

对于都是ConstValue,直接创建 Const 数组,如下:

	else
	{
		int			i = 0;
		ListCell   *lc1;
		Datum	   *elems;

		/* Direct creation of Const array */

		elems = (Datum *) palloc(sizeof(Datum) * list_length(consts));
		foreach (lc1, consts)
			elems[i++] = lfirst_node(Const, lc1)->constvalue;

		get_typlenbyvalalign(consttype, &typlen, &typbyval, &typalign);

		arrayConst = construct_array(elems, i, consttype,
									 typlen, typbyval, typalign);
		arrayNode = (Node *) makeConst(arraytype, -1, inputcollid,
									   -1, PointerGetDatum(arrayConst),
									   false, false);
		pfree(elems);
	}

在这里插入图片描述

接下来从缓存中查找的操作符,如下:

operform = (Form_pg_operator) GETSTRUCT(opertup);
{ oid => '96', oid_symbol => 'Int4EqualOperator', descr => 'equal',
  oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'int4',
  oprright => 'int4', oprresult => 'bool', oprcom => '=(int4,int4)',
  oprnegate => '<>(int4,int4)', oprcode => 'int4eq', oprrest => 'eqsel',
  oprjoin => 'eqjoinsel' },

最后就是构造这个ANY,如下:

	/* Build the SAOP expression node */
	saopexpr = makeNode(ScalarArrayOpExpr);
	saopexpr->opno = matchOpno;
	saopexpr->opfuncid = operform->oprcode;
	saopexpr->hashfuncid = InvalidOid;
	saopexpr->negfuncid = InvalidOid;
	saopexpr->useOr = true;
	saopexpr->inputcollid = inputcollid;
	saopexpr->args = list_make2(leftop, arrayNode);
	saopexpr->location = -1;

如上这块的实现与上面make_scalar_array_op一致,有兴趣的小伙伴可以深入了解!


而对于有 Param( VALUES 包含参数、函数调用和复杂表达式等),而不仅仅是 Const的情况,则需要构造一个 ArrayExpr。如下:

	if (have_param)
	{
		/*
		 * We need to construct an ArrayExpr given we have Param's not just
		 * Const's.
		 */
		ArrayExpr  *arrayExpr = makeNode(ArrayExpr);

		/* array_collid will be set by parse_collate.c */
		arrayExpr->element_typeid = consttype;
		arrayExpr->array_typeid = arraytype;
		arrayExpr->multidims = false;
		arrayExpr->elements = consts;
		arrayExpr->location = -1;

		arrayNode = (Node *) arrayExpr;
	}

在这里插入图片描述

元信息,如下:

{ oid => '701', array_type_oid => '1022',
  descr => 'double-precision floating point number, 8-byte storage',
  typname => 'float8', typlen => '8', typbyval => 'FLOAT8PASSBYVAL',
  typcategory => 'N', typispreferred => 't', typinput => 'float8in',
  typoutput => 'float8out', typreceive => 'float8recv', typsend => 'float8send',
  typalign => 'd' },
{ oid => '670', descr => 'equal',
  oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'float8',
  oprright => 'float8', oprresult => 'bool', oprcom => '=(float8,float8)',
  oprnegate => '<>(float8,float8)', oprcode => 'float8eq', oprrest => 'eqsel',
  oprjoin => 'eqjoinsel' },

这两种情况下的arrayNode分别如下所示:

在这里插入图片描述

{ oid => '1604', descr => 'sine',
  proname => 'sin', prorettype => 'float8', proargtypes => 'float8',
  prosrc => 'dsin' },

{ oid => '1746', descr => 'convert numeric to float8',
  proname => 'float8', prorettype => 'float8', proargtypes => 'numeric',
  prosrc => 'numeric_float8' },
 
{ oid => '316', descr => 'convert int4 to float8',
  proname => 'float8', proleakproof => 't', prorettype => 'float8',
  proargtypes => 'int4', prosrc => 'i4tod' },

在这里插入图片描述


关于上面node的打印,我这里使用的是vscode,如下:

-exec call elog_node_display(15, "have_param_true", arrayNode, 1)

-exec call elog_node_display(15, "have_param_false", arrayNode, 1)

对此感兴趣的小伙伴可以看一下本人之前的博客,如下:

  • PostgreSQL的学习心得和知识总结(七十二)|深入理解PostgreSQL数据库开源节点树打印工具pgNodeGraph的作用原理及继续维护pgNodeGraph的声明,点击前往

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220502.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二叉树遍历(前序、中序、后续)

目录 什么是二叉树二叉树遍历以递归创建树的角度看前、中、后序遍历前序遍历中序遍历后序遍历 栈来实现前、中、后序遍历栈的实现栈操作进行前序、中序遍历代码实现中序遍历和先序遍历栈操作进行后序遍历 什么是二叉树 树&#xff1a;树的根节点没有前驱&#xff0c;除根节点以…

Spring声明式事务管理:深入探索XML配置方式

前言 Spring的事务管理&#xff0c;无论是基于xml还是注解实现&#xff0c;本质上还是实现数据库的事务管理机制&#xff0c;因此要注意发送SQL的连接是否为同一个&#xff0c;这是实现声明式事务的关键。 以下案例和实现基于SSM整合框架完成&#xff0c;不知道如何整合SSM&…

CTFHUB技能树之文件上传——无验证

开启靶场&#xff0c;打开链接&#xff1a; 直接上传一句话木马&#xff1a; <?php eval($_POST[pass]);?> 成功提交并显示了上传的文件的路径 访问一下该文件触发一句话木马&#xff1a; 看到一片空白是正常的&#xff0c;因为没有写什么函数&#xff0c;比如&#x…

FineReport 计算同比增长

1、数据库查询 SELECTt1.年,t1.月,t1.总金额 AS 同期金额,t1.仓库名称,t2.总金额 AS 上期金额 FROMtest t1LEFT JOIN test t2 ON ( t1.年 t2.年 1 ) AND t1.月 t2.月 AND t1.仓库名称 t2.仓库名称2、配置字段 月份字段加后缀 月 数据列加后缀 计算同比增长率 if(LEN(B3)0 …

移动零---双指针法

目录 一&#xff1a;题目 二:算法原理讲解 三&#xff1a;代码编写 一&#xff1a;题目 题目链接&#xff1a;https://leetcode.cn/problems/move-zeroes/description/ 二:算法原理讲解 三&#xff1a;代码编写 void moveZeroes2(vector<int>& nums) {for (int d…

数据轻松上云——Mbox边缘计算网关

随着工业4.0时代的到来&#xff0c;工厂数字化转型已成为提升生产效率、优化资源配置、增强企业竞争力的关键。我们凭借其先进的边缘计算网关与云平台技术&#xff0c;为工厂提供了高效、稳定的数据采集与上云解决方案。本文将为您介绍Mbox边缘计算网关如何配合明达云平台&…

基于Java语言的培训平台+学习平台+在线学习培训系统+教育平台+教育学习系统+课程学习平台

简述 企业培训平台企业考试系统培训平台考试系统企业大学企业视频网站视频学习平台 介绍 企业培训平台支持企业培训考试全流程&#xff0c;在线学习、在线考试&#xff0c;成熟的企业培训考试解决方案&#xff0c;充分满足企业培训需求。 独立部署&#xff0c;仅内部员工登录…

JAVA高级--常用类(九)

JAVA高级–常用类 观看b站尚硅谷视频做的笔记 1、字符串相关的类 1.1 String 的使用 String 的特性&#xff1a; String 类&#xff1a;代表字符串。Java 程序中的所有字符串字面值&#xff08;如 “abc”&#xff09;都作为此类的实例实现。 String 是一个 final 类&#…

ESP8266 模块介绍—AT指令学习 笔记

零、简介 感谢百文网韦东山 老师对ESP8266模块的讲解 笔记在CSDN也有文章备份 大家可以在我的gitee仓库 中下载笔记源文件、ESP8266资料等 笔记源文件可以在Notion中导入 一、ESP8266-01S模块详细介绍 1. 名字的由来 ESP8266 是方形的主控芯片旁边的长方形是一个Flash-0…

IO编程——消息队列

题目&#xff1a; 代码实现&#xff1a; #include <myhead.h> //正文大小 #define MSGSZ (sizeof(struct msgbuf)-sizeof(long)) //定义要发送的消息类型 struct msgbuf{long msgtype; //消息类型char mtext[1024]; //消息正文 };int main(int argc, const char *ar…

全面升级:亚马逊测评环境方案的最新趋势与实践

在亚马逊测评领域深耕多年&#xff0c;见证了无数环境方案的更迭与演变&#xff0c;每一次变化都体现了国人不畏艰难、勇于创新的精神。面对平台的政策调整&#xff0c;总能找到相应的对策。那么&#xff0c;当前是否存在一套相对稳定且高效的技术方案呢&#xff1f;答案是肯定…

【计网】从零开始理解TCP协议 --- 拥塞控制机制,延迟应答机制,捎带应答,面向字节流

时间就是性命。 无端的空耗别人的时间&#xff0c; 其实是无异于谋财害命的。 --- 鲁迅 --- 从零开始理解TCP协议 1 拥塞控制2 延迟应答3 捎带应答4 面向字节流5 TCP异常情况TCP小结 1 拥塞控制 尽管TCP拥有滑动窗口这一高效的数据传输机制&#xff0c;能够确保在对方接收…

基于卷积神经网络的蔬菜识别系统,resnet50,mobilenet模型【pytorch框架+python源码】

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; 基于卷积神经网络的蔬菜识别系统&#xff0c;resnet50&#xff0c;mobilenet【pytorch框架&#xff0c;python&#xff0c;tkinter】_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于卷积神…

关于vue3中如何实现多个v-model的自定义组件

实现自定义组件<User v-model"userInfo" v-model:gender"gender"></User> User组件中更改数据可以同步更改父组件中的数据&#xff1a; 1 父组件&#xff1a; <User v-model"userInfo" v-model:gender"gender">&…

ROUGE:摘要自动评估软件包

算法解析 ROUGE&#xff08;Recall-Oriented Understudy for Gisting Evaluation&#xff09;是一组用于自动评估文本摘要质量的指标&#xff0c;主要通过比较机器生成的摘要与一个或多个参考摘要之间的重合程度来衡量。ROUGE 包括多个变体&#xff0c;其中最常用的有 ROUGE-N…

基于Java微信小程序的的儿童阅读系统的详细设计和实现(源码+lw+部署文档+讲解等)

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

WebGoat SQL Injection (intro) 源码分析

首先了解 java 中 mysql 的连接&#xff1a;java连接Mysql WebGoat SQL Injection (intro) 10 根据提示&#xff1a;下面两个输入框只有一个受到 sql 注入攻击。题目要求是检索到所有数据 发现请求路径为 SqlInjection/assignment5b 定位到所在文件如下&#xff0c;根据代码…

基于递推式最小二乘法的PMSM参数辨识MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 模型简介 最小二乘法是一种回归估计法&#xff0c;适用于被辨识的参数与系统输出为线性关 系的情况。它是在一定数据量下&#xff0c;基于系统输出误差的平方和最小的准则对参 数进行辨识的方法。此模型通过…

使用Flask实现本机的模型部署

前言 模型部署是指将大模型运行在专属的计算资源上&#xff0c;使模型在独立的运行环境中高效、可靠地运行&#xff0c;并为业务应用提供推理服务。其目标是将机器学习模型应用于实际业务中&#xff0c;使最终用户或系统能够利用模型的输出&#xff0c;从而发挥其作用。 一、设…

unity 屏幕波动反馈打击效果(附资源下载)

unity 屏幕波动反馈打击效果 一枪打出去整个屏幕都回波动的效果反馈。 知识点&#xff1a; 1、动画事件 2、屏幕后处理 效果如图&#xff1a;&#xff08;波动速度浮动都可调整&#xff09; 附件下载