当前位置: 首页 > news >正文

PostgreSQL源码分析——常量表达式化简

常量表达式化简

常量表达式可以进行化简,可降低执行器计算表达式的代价。在逻辑优化阶段,会判断是否可以进行常量表达式化简,如果可以,则在执行器执行之前就预先对常量表达式树进行计算,计算出常量后,以新计算出的常量表达式代替原有的表达式。当进入执行器时,此时表达式已被替换为常量,避免了在执行器中频繁的计算表达式。

化简示例

我们以下面的SQL为例,条件表达式为a > 1 + 1,是常量表达式,可以进行化简。

-- 常量表达式可以进行化简
postgres=# explain select * from t1 where a >  1 + 1;   QUERY PLAN                      
-----------------------------------------------------Seq Scan on t1  (cost=0.00..17.50 rows=998 width=8)Filter: (a > 2)   -- 进入执行器中, 表达式已被提前替换为常量,每扫描一个元组,不用再进行1+1的表达式计算了
(2 rows)
-- 不满足常量表达式化简的条件
postgres=# explain select * from t1 where a >  1 + random();  QUERY PLAN                               
------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..25.00 rows=333 width=8)Filter: ((a)::double precision > ('1'::double precision + random()))     --  执行器中每扫描一个元组,都要进行一次表达式计算
(2 rows)

除了常量表达式,常量函数也可以在逻辑优化阶段提前执行,计算得到常量,用常量替换原有的函数表达式。

源码分析

我们以select * from t1 where a = 1 + 1;这条语句为例,分析一下在PostgreSQL中是如何进行化简的。主流程如下

exec_simple_query
--> pg_parse_query--> raw_parser--> base_yyparse
--> pg_analyze_and_rewrite--> transformStmt--> transformSelectStmt--> transformWhereClause--> transformExpr
--> pg_plan_queries--> pg_plan_query--> planner--> standard_planner--> subquery_planner--> preprocess_qual_conditions--> preprocess_expression--> eval_const_expressions  // 在这里完成常量表达式化简,完成1+1表达式替换为常量2--> eval_const_expressions_mutator--> simplify_function--> evaluate_function--> evaluate_expr  // 1+1的表达式被计算为常量2--> create_plan
--> PortalStart
--> PortalRun  // 执行器执行
--> PortalDrop

可以看到,在逻辑优化阶段,常量表达式已被化简为常量,进入执行器时,表达式树已被替换为常量。下面为详细实现。

/** preprocess_qual_conditions*		Recursively scan the query's jointree and do subquery_planner's*		preprocessing work on each qual condition found therein.*/
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode)
{if (jtnode == NULL)return;if (IsA(jtnode, RangeTblRef)){/* nothing to do here */}else if (IsA(jtnode, FromExpr)){FromExpr   *f = (FromExpr *) jtnode;ListCell   *l;foreach(l, f->fromlist)preprocess_qual_conditions(root, lfirst(l));f->quals = preprocess_expression(root, f->quals, EXPRKIND_QUAL);}else if (IsA(jtnode, JoinExpr)){// ...}elseelog(ERROR, "unrecognized node type: %d",(int) nodeTag(jtnode));
}/** preprocess_expression*		Do subquery_planner's preprocessing work for an expression,*		which can be a targetlist, a WHERE clause (including JOIN/ON*		conditions), a HAVING clause, or a few other things.*/
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind)
{// .../** Simplify constant expressions.  For function RTEs, this was already* done by preprocess_function_rtes.  */if (kind != EXPRKIND_RTFUNC)expr = eval_const_expressions(root, expr);/* If it's a qual or havingQual, canonicalize it. */if (kind == EXPRKIND_QUAL)expr = (Node *) canonicalize_qual((Expr *) expr, false);// .../** If it's a qual or havingQual, convert it to implicit-AND format. (We* don't want to do this before eval_const_expressions, since the latter* would be unable to simplify a top-level AND correctly. Also,* SS_process_sublinks expects explicit-AND format.)*/if (kind == EXPRKIND_QUAL)expr = (Node *) make_ands_implicit((Expr *) expr);return expr;
}/*--------------------* eval_const_expressions** Reduce any recognizably constant subexpressions of the given* expression tree, for example "2 + 2" => "4".  More interestingly,* we can reduce certain boolean expressions even when they contain* non-constant subexpressions: "x OR true" => "true" no matter what* the subexpression x is.  */
Node *eval_const_expressions(PlannerInfo *root, Node *node)
{eval_const_expressions_context context;if (root)context.boundParams = root->glob->boundParams;	/* bound Params */elsecontext.boundParams = NULL;context.root = root;		/* for inlined-function dependencies */context.active_fns = NIL;	/* nothing being recursively simplified */context.case_val = NULL;	/* no CASE being examined */context.estimate = false;	/* safe transformations only */return eval_const_expressions_mutator(node, &context);
}/* Recursive guts of eval_const_expressions/estimate_expression_value */
static Node *eval_const_expressions_mutator(Node *node,eval_const_expressions_context *context)
{if (node == NULL)return NULL;switch (nodeTag(node)){case T_FuncExpr:{// ...}case T_OpExpr:{OpExpr	   *expr = (OpExpr *) node;List	   *args = expr->args;Expr	   *simple;OpExpr	   *newexpr;/* Need to get OID of underlying function.  Okay to scribble on input to this extent. */set_opfuncid(expr);/** Code for op/func reduction is pretty bulky, so split it out* as a separate function. */simple = simplify_function(expr->opfuncid,expr->opresulttype, -1,expr->opcollid,expr->inputcollid,&args,false,true,true,context);if (simple)		/* successfully simplified it */return (Node *) simple;/** If the operator is boolean equality or inequality, we know* how to simplify cases involving one constant and one* non-constant argument.*/if (expr->opno == BooleanEqualOperator ||expr->opno == BooleanNotEqualOperator){simple = (Expr *) simplify_boolean_equality(expr->opno,args);if (simple) /* successfully simplified it */return (Node *) simple;}/** The expression cannot be simplified any further, so build* and return a replacement OpExpr node using the* possibly-simplified arguments.*/newexpr = makeNode(OpExpr);newexpr->opno = expr->opno;newexpr->opfuncid = expr->opfuncid;newexpr->opresulttype = expr->opresulttype;newexpr->opretset = expr->opretset;newexpr->opcollid = expr->opcollid;newexpr->inputcollid = expr->inputcollid;newexpr->args = args;newexpr->location = expr->location;return (Node *) newexpr;}// ...default:break;}/** For any node type not handled above, copy the node unchanged but* const-simplify its subexpressions.  This is the correct thing for node* types whose behavior might change between planning and execution, such* as CurrentOfExpr.  It's also a safe default for new node types not* known to this routine.*/return ece_generic_processing(node);
}/** Subroutine for eval_const_expressions: try to simplify a function call* (which might originally have been an operator; we don't care)*/
static Expr *
simplify_function(Oid funcid, Oid result_type, int32 result_typmod,Oid result_collid, Oid input_collid, List **args_p,bool funcvariadic, bool process_args, bool allow_non_const,eval_const_expressions_context *context)
{// .../* Now attempt simplification of the function call proper. */newexpr = evaluate_function(funcid, result_type, result_typmod,result_collid, input_collid,args, funcvariadic,func_tuple, context);// ...return newexpr;
}/** evaluate_function: try to pre-evaluate a function call*/
static Expr *
evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,Oid result_collid, Oid input_collid, List *args,bool funcvariadic,HeapTuple func_tuple,eval_const_expressions_context *context)
{// ...return evaluate_expr((Expr *) newexpr, result_type, result_typmod,result_collid);
}/** evaluate_expr: pre-evaluate a constant expression** We use the executor's routine ExecEvalExpr() to avoid duplication of* code and ensure we get the same result as the executor would get.*/
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,Oid result_collation)
{EState	   *estate;ExprState  *exprstate;MemoryContext oldcontext;Datum		const_val;bool		const_is_null;int16		resultTypLen;bool		resultTypByVal;/** To use the executor, we need an EState.*/estate = CreateExecutorState();/* We can use the estate's working context to avoid memory leaks. */oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);/* Make sure any opfuncids are filled in. */fix_opfuncids((Node *) expr);/** Prepare expr for execution.  (Note: we can't use ExecPrepareExpr* because it'd result in recursively invoking eval_const_expressions.)*/exprstate = ExecInitExpr(expr, NULL);/** And evaluate it.** It is OK to use a default econtext because none of the ExecEvalExpr()* code used in this situation will use econtext.  That might seem* fortuitous, but it's not so unreasonable --- a constant expression does* not depend on context, by definition, n'est ce pas?*/const_val = ExecEvalExprSwitchContext(exprstate,GetPerTupleExprContext(estate),&const_is_null);  // 计算表达式 1 + 1/* Get info needed about result datatype */get_typlenbyval(result_type, &resultTypLen, &resultTypByVal);/* Get back to outer memory context */MemoryContextSwitchTo(oldcontext);/** Must copy result out of sub-context used by expression eval.** Also, if it's varlena, forcibly detoast it.  This protects us against* storing TOAST pointers into plans that might outlive the referenced* data.  (makeConst would handle detoasting anyway, but it's worth a few* extra lines here so that we can do the copy and detoast in one step.)*/if (!const_is_null){if (resultTypLen == -1)const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val));elseconst_val = datumCopy(const_val, resultTypByVal, resultTypLen);}/* Release all the junk we just created */FreeExecutorState(estate);/** Make the constant result node.*/return (Expr *) makeConst(result_type, result_typmod, result_collation,resultTypLen,const_val, const_is_null,resultTypByVal);
}

如果对源码不熟悉,可以补充看一下这里关于解析层的代码,有助于理解PostgreSQL源码中表达式处理相关的逻辑。

补充解析层相关代码

我们看一下在解析层表达式是如何表示的,1+1表示为A_Expr,类型为AEXPR_OPwhere a = 1 + 1则是A_Expr,左子树为变量a,右子树为1+1的表达式A_Expr

where_clause:WHERE a_expr		{ $$ = $2; }| /*EMPTY*/			{ $$ = NULL; };
a_expr:		c_expr			{ $$ = $1; }| a_expr '+' a_expr     // 表示 1 + 1{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "+", $1, $3, @2); }| a_expr '=' a_expr{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", $1, $3, @2); }
c_expr:		columnref		{ $$ = $1; }| AexprConst	{ $$ = $1; }
AexprConst: Iconst{$$ = makeIntConst($1, @1);}
Iconst:		ICONST	{ $$ = $1; };       // 1 A_Expr *
makeSimpleA_Expr(A_Expr_Kind kind, char *name,Node *lexpr, Node *rexpr, int location)
{A_Expr	   *a = makeNode(A_Expr);a->kind = kind;a->name = list_make1(makeString((char *) name));a->lexpr = lexpr;a->rexpr = rexpr;a->location = location;return a;
}typedef struct A_Expr
{NodeTag		type;A_Expr_Kind kind;			/* see above */List	   *name;			/* possibly-qualified name of operator */Node	   *lexpr;			/* left argument, or NULL if none */Node	   *rexpr;			/* right argument, or NULL if none */int			location;		/* token location, or -1 if unknown */
} A_Expr;/* A_Const - a literal constant */
typedef struct A_Const
{NodeTag		type;Value		val;			/* value (includes type info, see value.h) */int			location;		/* token location, or -1 if unknown */
} A_Const;

相关文章:

PostgreSQL源码分析——常量表达式化简

常量表达式化简 常量表达式可以进行化简,可降低执行器计算表达式的代价。在逻辑优化阶段,会判断是否可以进行常量表达式化简,如果可以,则在执行器执行之前就预先对常量表达式树进行计算,计算出常量后,以新…...

速卖通自养号测评:安全高效的推广手段

在速卖通平台上,卖家们常常寻求各种方法来提升商品的曝光、转化率和店铺权重。其中,自养号测评作为一种低成本、高回报的推广方式,备受关注。然而,若操作不当,也可能带来风险。以下是如何安全有效地进行自养号测评的指…...

项目监督与控制

1.什么是项目过程度量?其方法有哪些? 项目过程度量是一种对项目执行过程中的活动和性能进行量化测量的方法。它涉及到收集、分析和解释项目数据,以便更好地理解项目的进度、质量和效率。过程度量的目的是提供关于项目健康状况的客观信息&…...

【LeetCode刷题】面试题 17.19. 消失的两个数字

1. 题目链接2. 题目描述3. 解题方法4. 代码 1. 题目链接 面试题 17.19. 消失的两个数字 2. 题目描述 3. 解题方法 例子假设: 数组A元素为 :1 ,4,5 缺少的元素为:2, 3 那么所有整数就为1 ~ 5&#xff…...

如何定制Spring的错误json信息

一,前言 相信很多同学都有遇到过这样的spring错误信息。 在我们没有做catch处理时或者做全局的exceptionHandle时,Spring遇到抛出向外的异常时,就会给我们封装返回这么个格式的异常信息。 那么问题来了,我们能否对这个返回增加错…...

【第20章】Vue实战篇之Vue Router(路由)

文章目录 前言一、使用Vue-Router1.安装2. 创建路由器实例3. 注册路由器插件4. 根组件 二、访问路由器1.理论2.使用3. 展示 三、嵌套路由(子路由)1. 准备文件2. 配置路由3. 菜单配置4. 展示 总结 前言 Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,…...

阿里云运维第一步(监控):开箱即用的监控

作者:仲阳 这是云的时代,现在云计算已经在各行各业广泛的应用。但是上云对于大多数客户来说,依然有很大的学习成本,如下图仅是阿里云都有几百款产品,怎么选择?怎么用?对于客户来说都是问题。“…...

Python量化交易学习——Part7:定制增强型中证红利策略

中证红利指数是一个反映A股市场高红利股票整体状况和走势的指数。它通过选取上海、深圳交易所中现金股息率高、分红比较稳定、具有一定规模及流动性的100只股票作为样本。这个指数的目的是提供一个全面且具有代表性的视角,以观察A股市场中高红利股票的表现。中证红利指数的样本…...

拥抱未来:探索改变游戏规则的新存储技术

目录 一.存储级内存(Storage-Class Memory) 3D XPoint 技术 特点 应用场景 优点 缺点 适用场景 示例 二.QLC NAND闪存 概述 优点 缺点 适用场景 前景展望 三.DNA存储 概述 优点 原理 实际应用 关键问题 研究进展 适用场景 分布式…...

shell中的流程控制

条件判断在流程控制中的重要性 有了条件判断才能进行if判断即分支流程,才能进行case的多分支流程,才能进行for循环和while循环。 单分支流程判断 如上图所示,在shell编程中常使用英文状态下的分号来在Linux控制台一次性执行多条命令&#x…...

DiffIR: Efficient Diffusion Model for Image Restoration

清华&ETH&字节&UTDhttps://github.com/Zj-BinXia/DiffIR 问题引入 IR任务和image synthesis任务不同点是IR任务本身有一个很强的低质量图片作为先验,所以可以不完全遵循图片生成的范式,本文主要在compact的IPR空间进行DM;本文提…...

xss一些笔记

(乱写的一些笔记) innerHTML只防script像是img就不会防 innerText都防 上面代码执行避免用户交互 js也可以用’‘执行 例子 alert’1‘ document.location.hash // #号后的部分,包括#号 document.location.host // 域名…...

以太坊网络中为什么要设置Gas上限

以太坊网络中的Gas上限(Gas Limit)是一个重要的机制,它主要出于以下几个目的: 防止无限循环和拒绝服务攻击(DoS): Gas上限防止了智能合约中的无限循环,这可以保护网络免受恶意合约的…...

vue-cli是什么?和 webpack是什么关系?

前言 Vue CLI是Vue.js项目的官方脚手架,基于Node.js与Webpack构建。安装Vue CLI前需确保Node.js已安装,随后通过npm全局安装。Vue CLI能迅速创建和管理Vue.js项目,提升开发效率。而Webpack则负责资源打包,通过配置文件管理依赖、插…...

leetcode刷题(46-50)

算法是码农的基本功,也是各个大厂必考察的重点,让我们一起坚持写题吧。 遇事不决,可问春风,春风不语,即是本心。 我们在我们能力范围内,做好我们该做的事,然后相信一切都事最好的安排就可以啦…...

[渗透测试学习] Runner-HackTheBox

Runner-HackTheBox 信息搜集 nmap扫描端口 nmap -sV -v 10.10.11.13扫描结果如下 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0) 80/tcp open http nginx 1.18.0 (Ubuntu) 8000…...

keil5显示内存和存储占用百分比进度条工具

简介 [Keil5_disp_size_bar] 以进度条百分比来显示keil编译后生成的固件对芯片的内存ram和存储flash的占用情况, 并生成各个源码文件对ram和flash的占比整合排序后的map信息的表格和饼图。 原理是使用C语言遍历当前目录找到keil工程和编译后生成的map文件 然后读取工程文件和m…...

示例:推荐一个应用Adorner做的消息对话框

一、目的:开发过程中,经常用到对话框,下面演示一个应用Adorner做的带遮盖层蒙版的控件,使用MainWindow的Adorner实现不需要额外定义遮盖层,使用Object作为参数,可自定义DataTemplate定制消息显示样式 二、效…...

Building wheels for collected packages: mmcv, mmcv-full 卡住

安装 anime-face-detector 的时候遇到一个问题:Installation takes forever #1386:在构建mmcv-full时卡住,这里分享下解决方法(安装 mmcv 同理,将下面命令中的 mmcv-full 替换成 mmcv) 具体表现如下&#x…...

可视化表单拖拽生成器优势多 助力流程化办公!

当前,很多企业需要实现流程化办公,进入数字化转型时期。要想实现这一目标,就需要借助更优质的平台产品。低代码技术平台是得到企业喜爱的发展平台,拥有可视化操作、灵活、高效、更可靠等优势特点,在推动企业实现流程化…...

数据集制作——语义分割前png、jpg格式标签图转yolo格式.txt文件(附代码)

💪 专业从事且热爱图像处理,图像处理专栏更新如下👇: 📝《图像去噪》 📝《超分辨率重建》 📝《语义分割》 📝《风格迁移》 📝《目标检测》 📝《暗光增强》 &a…...

机器学习课程复习——ANN

Q:ANN? 基本架构 由输入层、隐藏层、输出层等构建前馈/反馈传播 工作原理 先加权求和:每个神经元的输出是输入加权和的激活再送入激活函数:激活函数的存在使得其能够拟合各类非线性任务 联想:像adaboosting的加权求…...

C++回溯算法(2)

棋盘问题 #include<bits/stdc.h> using namespace std; void func(int,int); bool tf(int,int); void c(); int n,k; char a[110][110]; int cnt20; int main() {cin>>n>>k;for(int i0;i<n;i){for(int j0;j<n;j){cin>>a[i][j];}}func(0,0);cout…...

流量有限、日活低的APP适合对接广告变现吗?

APP广告变现&#xff0c;总用户数和日活用户&#xff08;DUA&#xff09;是衡量APP价值和影响力的重要指标之一。 APP DUA过万&#xff0c;尤其是大几万时&#xff0c;通常具备了商业化价值&#xff0c;适合接入广告变现。日活1W意味着每天有1万名用户在使用这款应用&#xff…...

Shell 学习笔记 - 变量的类型 + 变量的赋值

1.6 Shell 变量的类型 Shell 变量分为四类&#xff0c;分别是 自定义变量环境变量位置变量预定义变量 根据工作要求临时定义的变量称为自定义变量&#xff1b; 环境变量一般是指用 export 内置命令导出的变量&#xff0c;用于定义 Shell 的运行环境&#xff0c;保证 Shell …...

vue播放flv格式的直播流

在ios无法播放&#xff0c;安卓可以 安装 npm install flv.js --save页面 <template><div><videoref"videoElement"style"width: 100%; height: 100%"autoplayplaysinlinemuted></video></div> </template><scr…...

Qt入门小项目 | 实现一个图片查看器

文章目录 一、实现一个图片查看软件 一、实现一个图片查看软件 需要实现的功能&#xff1a; 打开目录选择图片显示图片的名字显示图片 在以上功能的基础上进行优化&#xff0c;需要解决如下问题&#xff1a; 如何记住上次打开的路径&#xff1f; 将路径保存到配置文件中&#x…...

qt仿制qq登录界面

#include "mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {// 设置窗口大小this->resize(window_width, window_heigth);// 固定窗口大小this->setFixedSize(window_width, window_heigth);// 设置窗口图标this->se…...

HashMap详解(含动画演示)

目录 HashMap1、HashMap的继承体系2、HashMap底层数据结构3、HashMap的构造函数①、无参构造②、有参构造1 和 有参构造2 (可以自定义初始容量和负载因子)③、有参构造3(接受一个Map参数)JDK 8之前版本的哈希方法&#xff1a;JDK 8版本的哈希方法 4、拉链法解决哈希冲突什么是拉…...

TVS的原理及选型

目录 案例描述 TVS管的功能与作用&#xff1a; TVS选型注意事项&#xff1a; 高速TVS管选型 最近项目中遇到TVS管选型错误的问题。在此对TVS的功能及选型做一个分享。 案例描述 项目中保护指标应为4-14V&#xff0c;而选型的TVS管位SMJ40CA&#xff0c;其保护电压为40V未…...

设置网站/优化关键词可以选择哪个工具

小学数学常用公式大全(几何体计算公式)1. 长方形的周长(长宽)2 公式&#xff1a;C(ab)22. 正方形的周长边长4 公式&#xff1a;C4a3. 长方形的面积长宽 公式&#xff1a;Sab4. 正方形的面积边长边长 公式&#xff1a;Sa*a5. 三角形的面积底高2 公式&#xff1a;Sah26. 平行四边…...

做营销网站制作/b站视频推广

点击上方蓝字&#xff0c;关注微联智控工作室可点击右上角的 …&#xff0c;分享这篇文章上一篇文章讲述了如何在nRF52840芯片上&#xff0c;把GPIO配置为输出模式&#xff0c;从而驱动LED&#xff0c;上一篇文章的内容请参看以下链接。(1)nRF52840配置GPIO输出驱动LEDNordic n…...

金华做公司网站/在线外链发布工具

现在&#xff0c;苹果作为全球最著名的科技公司&#xff0c;有之一&#xff0c;已经成为一种社会现象&#xff0c;人类像研究艺术一样来研究苹果产品&#xff0c;每天都会有人泄密最新款iPhone7的外观&#xff0c;也会有人分析苹果的利润走势&#xff0c;还有人吃饱了之后预测&…...

济宁网站建设 智雅/找百度

希腊神话记载&#xff0c;普罗米修斯偷来了火&#xff0c;被宙斯囚禁在高加索山上&#xff0c;每天会有一只鹰来啄他的肝脏 普罗米修斯&#xff0c;意思是“先见之明”(forethought)。他帮人类从奥林匹斯偷取了火&#xff0c;因此触怒宙斯&#xff0c;将他锁在高加索山的悬崖上…...

北京网站seowyhseo/沈阳网站制作优化推广

奇妙的数字 小明发现了一个奇妙的数字。它的平方和立方正好把0~9的10个数字每个用且只用了一次。你能猜出这个数字是多少吗&#xff1f; 请填写该数字&#xff0c;不要填写任何多余的内容。转载于:https://www.cnblogs.com/jinyufanfan/p/10138153.html...

网站艺术设计/微信推广引流加精准客户

jQuery has been removed, 新的项目不要用jQuery了 这些问题都已经有了解决方案 * $()选择器, * $.ajax, * $dom.on("click", function() {}), * HTML5 API兼容 * 自定义html标签 对应如下&#xff1a; * querySelectorAll - http://youmightnotneedjquery.com/ * …...