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_OP。where 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ÿ…...
如何定制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
清华Ð&字节&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…...
可视化表单拖拽生成器优势多 助力流程化办公!
当前,很多企业需要实现流程化办公,进入数字化转型时期。要想实现这一目标,就需要借助更优质的平台产品。低代码技术平台是得到企业喜爱的发展平台,拥有可视化操作、灵活、高效、更可靠等优势特点,在推动企业实现流程化…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
