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


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

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的声明,点击前往

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ系列学习笔记(八)--发布订阅模式

文章目录 一、发布订阅模式原理二、发布订阅模式实战1、消费者代码2、生产者代码3、查看运行结果 本文参考&#xff1a; 尚硅谷RabbitMQ教程丨快速掌握MQ消息中间件rabbitmq RabbitMQ 详解 Centos7环境安装Erlang、RabbitMQ详细过程(配图) 一、发布订阅模式原理 在开发过程中&…

大数据治理的核心思想

目录 ​编辑1.1 大数据治理的定义与重要性 1.2 大数据治理的关键要素 1.2.1 数据质量管理 1.2.2 数据安全管理 1.2.3 合规性管理 1.2.4 数据共享与协作 1.2.5 数据驱动的决策 二、对未来趋势的看法 2.1 技术发展趋势 2.1.1 人工智能与机器学习 2.1.2 云计算与边缘计…

Python数据处理工具笔记 - matplotlib, Numpy, Pandas

matplotlib, Numpy, Pandas 由于有很多例子是需要运算后的图表看着更明白一些&#xff0c;很明显csdn不支持 所以用谷歌的Colab(可以理解为白嫖谷歌的云端来运行的jupyter notebook)来展示&#xff1a; Colab链接(需要梯子)&#xff1a;Python数据挖掘 当然如果实在没有梯子&…

算法打卡 Day43(动态规划)-背包问题 + 分割等和子集

文章目录 0-1 背包问题理论基础0-1 背包问题滚动数组Leetcode 416-分割等和子集题目描述解题思路 0-1 背包问题理论基础 0-1 背包一般的题目要求是给定不同重量不同价值的物品&#xff0c;每个物品只有一个&#xff0c;已知背包中最大的负重&#xff0c;求在此限制条件下背包中…

达那福发布新品音致系列:以顶尖降噪技术,开启清晰聆听新篇章

近日&#xff0c;国际知名助听器品牌达那福推出其最新研发的音致系列助听器。该系列产品旨在通过顶尖的声音处理技术&#xff0c;直面助听器市场中普遍存在的挑战——如何在噪声环境中提供清晰的语音辨识。 根据助听器行业协会2022年的调查数据&#xff0c;高达86%的佩戴者认为…

数据结构——二叉树的基本操作及进阶操作

前言 介绍 &#x1f343;数据结构专区&#xff1a;数据结构 参考 该部分知识参考于《数据结构&#xff08;C语言版 第2版&#xff09;》116 ~ 122页 及 《数据结构教程》201 ~ 213页 重点 树的基本实现并不难&#xff0c;重点在于对递归的理解才是树这部分知识带来的最大收…

jmeter学习(8)界面的使用

1、新建test plan 3、 打开文件 4、保存 5、剪切 6、复制 7、粘贴 8、所有线程组展开 9、所有线程组收缩 10、置灰&#xff0c;操作后无法使用 11、执行 13、清空当前线程组结果 14、清空所有线程组结果 15、函数助手 搜索&#xff0c;可以用于搜索某个请求&#x…

Java基于微信小程序的健身小助手打卡预约教学系统(源码+lw+部署文档+讲解等)

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

使用OpenCV进行视频边缘检测:案例Python版江南style

1. 引言 本文将演示如何使用OpenCV库对视频中的每一帧进行边缘检测&#xff0c;并将结果保存为新的视频文件。边缘检测是一种图像处理技术&#xff0c;它可以帮助我们识别出图像中不同区域之间的边界。在计算机视觉领域&#xff0c;这项技术有着广泛的应用&#xff0c;比如物体…

登录时用户名密码加密传输(包含前后端代码)

页面输入用户名密码登录过程中&#xff0c;如果没有对用户名密码进行加密处理&#xff0c;可能会导致传输过程中数据被窃取&#xff0c;就算使用https协议&#xff0c;在浏览器控制台的Request Payload中也是能直接看到传输的明文&#xff0c;安全感是否还是不足。 大致流程&a…

redis—cluster集群

一&#xff1a;Redis Cluster特点 多主多从&#xff0c;去中心化&#xff1a;从节点作为备用&#xff0c;复制主节点&#xff0c;不做读写操作&#xff0c;不提供服务不支持处理多个key&#xff1a;因为数据分散在多个节点&#xff0c;在数据量大高并发的情况下会影响性能&…

Columns Page “列”页面

“列”页提供了列管理工具&#xff0c;其中包括用于添加和删除列的按钮、显示绑定数据源中字段名称的列表框以及网格列、提供对所选列属性的访问的属性网格。 Columns 页面提供 Column properties &#xff08;列属性&#xff09;、Column options &#xff08;列选项&#xff…

Electron-(三)网页报错处理与请求监听

在前端开发中&#xff0c;Electron 是一个强大的框架&#xff0c;它允许我们使用 Web 技术构建跨平台的桌面应用程序。在开发过程中&#xff0c;及时处理网页报错和监听请求是非常重要的环节。本文将详细介绍 Electron 中网页报错的日志记录、webContents 的监听事件以及如何监…

如何使用JMeter进行性能测试的保姆级教程

性能测试是确保网站在用户访问高峰时保持稳定和快速响应的关键环节。作为初学者&#xff0c;选择合适的工具尤为重要。JMeter 是一个强大的开源性能测试工具&#xff0c;可以帮助我们轻松模拟多用户场景&#xff0c;测试网站的稳定性与性能。本教程将引导你通过一个简单的登录场…

微信小程序canvas 生成二维码图片,画图片,生成图片,将两个canvas结合并保存图片

需求实现步骤如下 先定义两个canvas一个canvas myQrcode画二维码的图片另一个canvas mycanvas画一个背景图&#xff0c;并把二维码画到这个canvas上&#xff0c;mycanvas这个canvas生成一张图片&#xff0c;返回图片的临时路径最后保存图片到手机 首先wxml,新版微信小程序can…

Java之继承抽象类用法实例(三十一)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

使用Matplotlib绘制箱线图:详细指南与示例

在数据分析和可视化领域&#xff0c;箱线图&#xff08;Box Plot&#xff09;是一种强大的工具&#xff0c;用于展示数据的分布特征&#xff0c;包括中位数、四分位数、异常值等。本文将详细介绍如何使用Matplotlib库在Python中绘制箱线图&#xff0c;并通过一个实际的血压数据…

从0开始linux(13)——进程(4)进程空间地址(1)

欢迎来到博主的专栏&#xff1a;从0开始linux 博主ID&#xff1a;代码小豪 文章目录 进程空间地址 还记得博主在之前介绍子进程时说过的话吗&#xff1f;子进程与父进程共享代码&#xff0c;而数据却不共享&#xff1b;这很好理解&#xff0c;因为子进程和父进程是不同的进程&a…

Java线程安全集合之COW

概述 java.util.concurrent.CopyOnWriteArrayList写时复制顺序表&#xff0c;一种采用写时复制技术&#xff08;COW&#xff09;实现的线程安全的顺序表&#xff0c;可代替java.util.ArrayList用于并发环境中。写时复制&#xff0c;在写入时&#xff0c;会复制顺序表的新副本&…

K8S调度不平衡问题分析过程和解决方案

不平衡问题排查 问题描述&#xff1a; 1、业务部署大量pod(据反馈&#xff0c;基本为任务型进程)过程中&#xff0c;k8s node内存使用率表现不均衡&#xff0c;范围从80%到百分之几&#xff1b; 2、单个node内存使用率超过95%&#xff0c;仍未发生pod驱逐&#xff0c;存在node…