WindowAgg
- WindowAggState 結構體
- 窗口聚合行為
- ExecInitWindowAgg 函數
- ExecWindowAgg 函數
- 代碼邏輯解釋:計算窗口偏移量
- 代碼邏輯詳細解釋:
- 代碼邏輯解釋:窗口聚合分區初始化與行推進邏輯
- 代碼邏輯詳細解釋:
- 代碼邏輯解釋:從Tuplestore讀取當前行并處理分組和排除邏輯
- 代碼邏輯詳細解釋:
- 代碼邏輯解釋:窗口聚合狀態管理與元組過濾
- 代碼邏輯詳細解釋:
- ExecEndWindowAgg 函數
聲明:本文的部分內容參考了他人的文章。在編寫過程中,我們尊重他人的知識產權和學術成果,力求遵循合理使用原則,并在適用的情況下注明引用來源。
本文主要參考了 postgresql-15.0 的開源代碼和《PostgresSQL數據庫內核分析》一書
??在【PostgreSQL內核學習 —— (WindowAgg(一))】中,我們介紹了窗口函數以及窗口聚合的核心計算過程。本文我們繼續學習WindowAgg算子的具體實現邏輯。
WindowAggState 結構體
??WindowAggState
結構體用于表示窗口聚合操作的執行狀態。它存儲了窗口聚合的相關數據和信息,包括窗口函數、聚合函數、分區信息、排序信息等。該結構體主要用于窗口聚合節點在查詢執行過程中維護當前的執行狀態和環境,確保窗口聚合操作按照指定的框架(例如,RANGE
、GROUPS
等)正確地執行。
typedef struct WindowAggState
{ScanState ss; /* 結構體的第一個字段是NodeTag,表示此結構體屬于ScanState類型 *//* 以下字段由ExecInitExpr填充,用于窗口聚合操作的初始化 */List *funcs; /* 存儲所有窗口函數節點的列表,這些函數將應用于目標列表 */int numfuncs; /* 窗口函數的總數量 */int numaggs; /* 純聚合函數的數量 */WindowStatePerFunc perfunc; /* 存儲每個窗口函數的狀態信息 */WindowStatePerAgg peragg; /* 存儲每個純聚合函數的狀態信息 */ExprState *partEqfunction; /* 分區列的相等性函數,用于分區的比較 */ExprState *ordEqfunction; /* 排序列的相等性函數,用于排序列的比較 */Tuplestorestate *buffer; /* 存儲當前分區的元組行數據 */int current_ptr; /* 當前分區中正在讀取的行的指針位置 */int framehead_ptr; /* 當前窗口框架頭的指針位置(若使用了框架) */int frametail_ptr; /* 當前窗口框架尾的指針位置(若使用了框架) */int grouptail_ptr; /* 當前組尾的指針位置(若使用了分組模式) */int64 spooled_rows; /* 當前分區中存儲的行數 */int64 currentpos; /* 當前元組在分區中的位置 */int64 frameheadpos; /* 當前框架頭的位置 */int64 frametailpos; /* 當前框架尾的位置(框架的結束位置+1) *//* 用于聚合獲取窗口對象(聚合操作的處理) */struct WindowObjectData *agg_winobj; /* 聚合窗口對象,供聚合操作使用 */int64 aggregatedbase; /* 當前聚合的起始行 */int64 aggregatedupto; /* 聚合處理到的行的上限 */WindowAggStatus status; /* 窗口聚合狀態,表示當前窗口的執行狀態 */int frameOptions; /* 窗口框架的選項,控制窗口的計算方式 */ExprState *startOffset; /* 表示窗口起始偏移量的表達式 */ExprState *endOffset; /* 表示窗口結束偏移量的表達式 */Datum startOffsetValue; /* startOffset計算后的結果值 */Datum endOffsetValue; /* endOffset計算后的結果值 *//* 以下字段用于RANGE偏移的PRECEDING/FOLLOWING模式 */FmgrInfo startInRangeFunc; /* 用于startOffset的in_range函數 */FmgrInfo endInRangeFunc; /* 用于endOffset的in_range函數 */Oid inRangeColl; /* 用于in_range測試的排序規則 */bool inRangeAsc; /* 在in_range測試中,是否使用升序排序 */bool inRangeNullsFirst; /* 在in_range測試中,null值是否排在前面 *//* 以下字段用于GROUPS模式 */int64 currentgroup; /* 當前元組所屬的組號 */int64 frameheadgroup; /* 當前窗口框架頭所在的組號 */int64 frametailgroup; /* 當前窗口框架尾所在的組號 */int64 groupheadpos; /* 當前元組所在組的頭部位置 */int64 grouptailpos; /* 當前元組所在組的尾部位置(組的結束位置+1) *//* 內存上下文,用于分區生命周期內的數據存儲 */MemoryContext partcontext; /* 存儲分區數據的內存上下文 */MemoryContext aggcontext; /* 聚合數據的共享內存上下文 */MemoryContext curaggcontext; /* 當前聚合操作的內存上下文 */ExprContext *tmpcontext; /* 短期的表達式計算上下文 */ExprState *runcondition; /* 窗口聚合執行的條件,若不滿足該條件,則停止執行 */bool use_pass_through; /* 如果為false,表示當runcondition不滿足時,停止執行窗口聚合;否則繼續評估窗口函數 */bool top_window; /* 如果為true,表示這是最頂層的窗口聚合,或是唯一的窗口聚合 */bool all_first; /* 如果為true,表示這是第一次掃描分區,尚未處理任何行 */bool partition_spooled; /* 如果為true,表示當前分區的所有元組已經被存儲到tuplestore中 */bool more_partitions; /* 如果為true,表示還有更多的分區需要處理 */bool framehead_valid; /* 如果為true,表示當前行的框架頭位置已經被更新 */bool frametail_valid; /* 如果為true,表示當前行的框架尾位置已經被更新 */bool grouptail_valid; /* 如果為true,表示當前行的組尾位置已經被更新 *//* 當前分區的第一行元組,用于開始或處理下一個分區 */TupleTableSlot *first_part_slot; /* 當前或下一個分區的第一個元組 */TupleTableSlot *framehead_slot; /* 當前框架的頭部元組 */TupleTableSlot *frametail_slot; /* 當前框架的尾部元組 *//* 臨時槽,用于從tuplestore中恢復元組 */TupleTableSlot *agg_row_slot;TupleTableSlot *temp_slot_1;TupleTableSlot *temp_slot_2;
} WindowAggState;
窗口聚合行為
??以下代碼段定義了許多與窗口函數相關的框架選項(frameOptions
),這些選項通過位運算來設置窗口聚合行為。具體來說,它們用于描述窗口框架的開始、結束、排除類型、以及是否使用默認行為等。
/** frameOptions 是這些位的按位 OR。NONDEFAULT 和 BETWEEN 位用于讓 ruleutils.c * 可以識別哪些屬性是用戶指定的,哪些是默認值;無論如何,必須設置正確的行為位。* START_foo 和 END_foo 選項必須成對出現,以便 gram.y 方便處理,* 盡管其中一些選項是無效或無用的。*/
#define FRAMEOPTION_NONDEFAULT 0x00001 /* 是否有任何選項被指定? */
#define FRAMEOPTION_RANGE 0x00002 /* 使用 RANGE 行為:表示窗口是基于值范圍來定義的 */
#define FRAMEOPTION_ROWS 0x00004 /* 使用 ROWS 行為:表示窗口是基于行數來定義的 */
#define FRAMEOPTION_GROUPS 0x00008 /* 使用 GROUPS 行為:表示窗口是基于分組來定義的 */
#define FRAMEOPTION_BETWEEN 0x00010 /* 是否給定了 BETWEEN 關鍵字? */
#define FRAMEOPTION_START_UNBOUNDED_PRECEDING 0x00020 /* 起始位置為 "UNBOUNDED PRECEDING" */
#define FRAMEOPTION_END_UNBOUNDED_PRECEDING 0x00040 /* 結束位置為 "UNBOUNDED PRECEDING"(不允許) */
#define FRAMEOPTION_START_UNBOUNDED_FOLLOWING 0x00080 /* 起始位置為 "UNBOUNDED FOLLOWING"(不允許) */
#define FRAMEOPTION_END_UNBOUNDED_FOLLOWING 0x00100 /* 結束位置為 "UNBOUNDED FOLLOWING" */
#define FRAMEOPTION_START_CURRENT_ROW 0x00200 /* 起始位置為 "CURRENT ROW" */
#define FRAMEOPTION_END_CURRENT_ROW 0x00400 /* 結束位置為 "CURRENT ROW" */
#define FRAMEOPTION_START_OFFSET_PRECEDING 0x00800 /* 起始位置為偏移量 "PRECEDING" */
#define FRAMEOPTION_END_OFFSET_PRECEDING 0x01000 /* 結束位置為偏移量 "PRECEDING" */
#define FRAMEOPTION_START_OFFSET_FOLLOWING 0x02000 /* 起始位置為偏移量 "FOLLOWING" */
#define FRAMEOPTION_END_OFFSET_FOLLOWING 0x04000 /* 結束位置為偏移量 "FOLLOWING" */
#define FRAMEOPTION_EXCLUDE_CURRENT_ROW 0x08000 /* 排除當前行(CURRENT ROW) */
#define FRAMEOPTION_EXCLUDE_GROUP 0x10000 /* 排除當前行及其同組行(peer group) */
#define FRAMEOPTION_EXCLUDE_TIES 0x20000 /* 排除當前行及其相同的同行(ties) *//* 對于偏移量選項,起始和結束偏移量必須一起使用 */
#define FRAMEOPTION_START_OFFSET \(FRAMEOPTION_START_OFFSET_PRECEDING | FRAMEOPTION_START_OFFSET_FOLLOWING)
#define FRAMEOPTION_END_OFFSET \(FRAMEOPTION_END_OFFSET_PRECEDING | FRAMEOPTION_END_OFFSET_FOLLOWING)/* 排除選項,表示排除當前行、同組行或相同值的同行 */
#define FRAMEOPTION_EXCLUSION \(FRAMEOPTION_EXCLUDE_CURRENT_ROW | FRAMEOPTION_EXCLUDE_GROUP | \FRAMEOPTION_EXCLUDE_TIES)/* 默認行為選項:指定默認的窗口框架行為 */
#define FRAMEOPTION_DEFAULTS \(FRAMEOPTION_RANGE | FRAMEOPTION_START_UNBOUNDED_PRECEDING | \FRAMEOPTION_END_CURRENT_ROW)
ExecInitWindowAgg 函數
??ExecInitWindowAgg 函數用于初始化窗口聚合節點的運行時信息。它負責創建窗口聚合狀態結構體,設置與窗口相關的各類內存上下文、表達式上下文,以及管理窗口函數和聚合函數的狀態。該函數還初始化了處理查詢計劃所需的各種資源,包括元組描述符、掃描槽、結果槽等。通過為每個窗口函數分配狀態,設置權限檢查,以及為聚合函數創建 WindowObject,它為窗口聚合操作做好了充分的準備。最終,窗口聚合節點的狀態被設置為運行狀態,準備執行窗口聚合計算。源碼如下所示(路徑:src\backend\executor\nodeWindowAgg.c
)
WindowAggState *
ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
{WindowAggState *winstate; // 聲明一個 WindowAggState 指針,表示窗口聚合操作的狀態Plan *outerPlan; // 聲明一個指向外部計劃節點的指針ExprContext *econtext; // 表達式上下文,用于存儲執行中的中間結果ExprContext *tmpcontext; // 臨時的表達式上下文WindowStatePerFunc perfunc; // 每個窗口函數的狀態信息WindowStatePerAgg peragg; // 每個聚合操作的狀態信息int frameOptions = node->frameOptions; // 獲取窗口的幀選項int numfuncs, // 窗口函數的數量wfuncno, // 當前窗口函數的編號numaggs, // 聚合操作的數量aggno; // 當前聚合操作的編號TupleDesc scanDesc; // 掃描描述符,表示掃描操作的元數據ListCell *l; // 列表單元,用于遍歷窗口函數/* 檢查不支持的標志 */Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); // 斷言不支持回溯和標記的標志/** 創建狀態結構*/winstate = makeNode(WindowAggState); // 創建并分配一個新的 WindowAggState 結構體winstate->ss.ps.plan = (Plan *) node; // 將當前計劃節點指針賦值給狀態結構體中的計劃字段winstate->ss.ps.state = estate; // 將執行狀態賦值給狀態結構體中的狀態字段winstate->ss.ps.ExecProcNode = ExecWindowAgg; // 設置執行節點處理函數為 ExecWindowAgg/* 將幀選項復制到狀態節點以便于訪問 */winstate->frameOptions = frameOptions; // 將窗口幀選項復制到窗口聚合狀態/** 創建表達式上下文。我們需要兩個,每個輸入元組一個* 處理和一個用于每個輸出元組處理。我們有點作弊* 通過使用ExecAssignExprContext()來構建兩者。*/ExecAssignExprContext(estate, &winstate->ss.ps); // 為狀態分配表達式上下文,用于輸入元組處理tmpcontext = winstate->ss.ps.ps_ExprContext; // 臨時上下文用于存儲中間結果winstate->tmpcontext = tmpcontext; // 將臨時上下文賦值給窗口狀態ExecAssignExprContext(estate, &winstate->ss.ps); // 為輸出元組分配新的表達式上下文/* 創建用于存儲分區本地內存等的長期上下文 */winstate->partcontext =AllocSetContextCreate(CurrentMemoryContext,"WindowAgg Partition",ALLOCSET_DEFAULT_SIZES); // 創建一個長期存儲上下文,用于保存分區相關的內存/** 為聚合跨值等創建中期上下文。** 請注意,移動聚合每個都使用自己的私有上下文,而不是這個上下文。*/winstate->aggcontext =AllocSetContextCreate(CurrentMemoryContext,"WindowAgg Aggregates",ALLOCSET_DEFAULT_SIZES); // 創建一個中期存儲上下文,用于保存聚合操作的臨時值/* 只有頂級WindowAgg可以具有qual */Assert(node->plan.qual == NIL || node->topWindow); // 確保只有最頂層的窗口聚合節點才可能有 QUAL 子句/* 初始化質量 */winstate->ss.ps.qual = ExecInitQual(node->plan.qual,(PlanState *) winstate); // 初始化查詢條件(QUAL)/** 如果我們從查詢計劃器收到運行條件,請設置運行條件。* 設置后,這可能允許我們進入直通模式,以便我們* 不必在中對WindowFuncs執行任何進一步的評估* 當前分區或可能完全停止返回元組* 元組位于同一分區中。*/winstate->runcondition = ExecInitQual(node->runCondition,(PlanState *) winstate); // 設置窗口聚合的執行條件,決定是否進入通行模式/** 當我們不是頂級WindowAgg節點,或者我們是但有一個* PARTITION BY子句我們必須進入WINDOWAGG_PASSTHROUNG之一* runCondition變為false時的模式。*/winstate->use_pass_through = !node->topWindow || node->partNumCols > 0; // 設置是否使用通行模式/* 記住我們是頂窗還是頂窗下方 */winstate->top_window = node->topWindow; // 記住當前是否為頂層窗口聚合/** 初始化子節點*/outerPlan = outerPlan(node); // 獲取窗口聚合節點的外部計劃節點outerPlanState(winstate) = ExecInitNode(outerPlan, estate, eflags); // 初始化外部計劃節點的狀態/** 初始化源元組類型(這也是我們將要初始化的元組類型* 存儲在元組存儲中,并在我們所有的工作槽中使用)。*/ExecCreateScanSlotFromOuterPlan(estate, &winstate->ss, &TTSOpsMinimalTuple); // 創建掃描槽并初始化scanDesc = winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; // 獲取掃描槽的元數據描述符/* 外部元組不是子元組,但始終是最小元組 */winstate->ss.ps.outeropsset = true; // 設置外部操作槽已設置winstate->ss.ps.outerops = &TTSOpsMinimalTuple; // 設置外部操作winstate->ss.ps.outeropsfixed = true; // 設置外部操作已固定/** 元組表初始化*/winstate->first_part_slot = ExecInitExtraTupleSlot(estate, scanDesc,&TTSOpsMinimalTuple); // 初始化額外的元組槽,用于第一部分winstate->agg_row_slot = ExecInitExtraTupleSlot(estate, scanDesc,&TTSOpsMinimalTuple); // 初始化額外的元組槽,用于聚合行winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate, scanDesc,&TTSOpsMinimalTuple); // 初始化臨時槽 1winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,&TTSOpsMinimalTuple); // 初始化臨時槽 2/** 僅在需要時創建幀頭和幀尾插槽(必須在中創建插槽* 與update_frameheadpos和update_frameailpos完全相同的情況需要他們)*/winstate->framehead_slot = winstate->frametail_slot = NULL; // 初始化幀頭和幀尾槽為 NULLif (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS)) // 如果幀選項為 RANGE 或 GROUPS{if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&node->ordNumCols != 0) ||(frameOptions & FRAMEOPTION_START_OFFSET)) // 判斷是否需要創建幀頭槽winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc,&TTSOpsMinimalTuple); // 初始化幀頭槽if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&node->ordNumCols != 0) ||(frameOptions & FRAMEOPTION_END_OFFSET)) // 判斷是否需要創建幀尾槽winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc,&TTSOpsMinimalTuple); // 初始化幀尾槽}/** 初始化結果槽、類型和投影。*/ExecInitResultTupleSlotTL(&winstate->ss.ps, &TTSOpsVirtual); // 初始化結果槽ExecAssignProjectionInfo(&winstate->ss.ps, NULL); // 設置投影信息/* 設置用于比較元組的數據 */if (node->partNumCols > 0) // 如果有分區列winstate->partEqfunction =execTuplesMatchPrepare(scanDesc,node->partNumCols,node->partColIdx,node->partOperators,node->partCollations,&winstate->ss.ps); // 初始化分區列的匹配函數if (node->ordNumCols > 0) // 如果有排序列winstate->ordEqfunction =execTuplesMatchPrepare(scanDesc,node->ordNumCols,node->ordColIdx,node->ordOperators,node->ordCollations,&winstate->ss.ps); // 初始化排序列的匹配函數/** 初始化將在執行過程中使用的函數。我們將為每個窗口函數創建獨立的函數,* 并使用 WindowAggState 作為每個函數的上下文。*/numfuncs = list_length(node->windowFuncs); // 獲取窗口函數的數量winstate->numWindowFuncs = numfuncs; // 將數量賦值給狀態winstate->windowFuncs = palloc0(sizeof(WindowStatePerFunc) * numfuncs); // 為窗口函數分配內存wfuncno = 0;foreach(l, node->windowFuncs) // 遍歷窗口函數列表{WindowFunc *wf = (WindowFunc *) lfirst(l); // 獲取當前窗口函數/* 對于每個窗口函數,初始化它 */perfunc = &winstate->windowFuncs[wfuncno]; // 獲取當前窗口函數的狀態perfunc->winfunc = wf; // 將窗口函數賦值給狀態結構perfunc->wfuncno = wfuncno; // 記錄當前窗口函數的編號perfunc->framehead_slot = winstate->framehead_slot; // 為該函數設置幀頭槽perfunc->frametail_slot = winstate->frametail_slot; // 為該函數設置幀尾槽/* 初始化窗口函數的每個函數狀態 */winstate->windowFuncs[wfuncno].state =ExecInitWindowFunc(wf, estate, frameOptions); // 初始化每個窗口函數的狀態++wfuncno; // 遞增窗口函數編號}/** 初始化聚合*/numaggs = list_length(node->aggFuncs); // 獲取聚合操作的數量winstate->numAggFuncs = numaggs; // 將聚合操作數量賦值給窗口狀態winstate->aggFuncs = palloc0(sizeof(WindowStatePerAgg) * numaggs); // 為聚合操作分配內存aggno = 0;foreach(l, node->aggFuncs) // 遍歷聚合函數列表{Aggregate *agg = (Aggregate *) lfirst(l); // 獲取當前聚合函數/* 對于每個聚合,對其進行初始化 */peragg = &winstate->aggFuncs[aggno]; // 獲取當前聚合函數的狀態peragg->aggfunc = agg; // 將聚合函數賦值給狀態結構/* 初始化每個聚合的狀態 */winstate->aggFuncs[aggno].state = ExecInitAggregate(agg, estate); // 初始化每個聚合操作的狀態++aggno; // 遞增聚合操作編號}/* Done */return winstate; // 返回初始化完成的窗口聚合狀態
}
??讓我們通過一個具體的例子來分析 ExecInitWindowAgg 函數的每一步操作,構造一個簡單的查詢場景來演示其功能。假設我們有一個包含員工工資數據的表 employee
,查詢計算每個部門的員工工資的滑動平均數(即窗口函數)。這個查詢涉及窗口函數的初始化和運行。
假設的查詢:
SELECT department_id, employee_id, salary,AVG(salary) OVER (PARTITION BY department_id ORDER BY employee_id) AS avg_salary
FROM employee;
查詢解析步驟:
- Plan Tree Generation:當執行查詢時,查詢計劃生成器生成了一個執行計劃,其中包含
WindowAgg
節點。WindowAgg
節點表示需要計算窗口聚合函數(如AVG
)的部分。 - 調用 ExecInitWindowAgg 函數:在執行查詢時,
ExecInitWindowAgg
被調用來初始化WindowAgg
節點的執行環境。此函數的主要任務是為窗口函數設置運行時信息,包括內存上下文的分配、表達式上下文的初始化,以及窗口函數的相關狀態。
具體的執行過程:
- 創建 WindowAggState 結構:
- 在查詢執行期間,
ExecInitWindowAgg
創建一個WindowAggState
結構體,用于存儲窗口聚合的運行時狀態。- 該結構體包含對窗口函數所需數據的引用和內存上下文。
- 創建并分配內存上下文:
ExecInitWindowAgg
函數為每個窗口分配內存上下文。在該場景中,窗口函數會基于department_id
劃分數據,因此需要為每個部門分配一個本地內存上下文。- 此外,創建了一個
aggcontext
,用于存儲聚合操作的中間值。
- 初始化表達式上下文:
- 窗口聚合涉及的所有表達式(例如
AVG(salary)
)會有各自的上下文,ExecInitWindowAgg
為每個窗口函數分配一個表達式上下文。- 表達式上下文會存儲評估窗口函數時所需的所有臨時數據,如窗口的分區和排序列等。
- 初始化 WindowFunc:
- 這個查詢包含了一個窗口函數
AVG(salary) OVER (PARTITION BY department_id ORDER BY employee_id)
,它將在窗口內計算salary
的平均值。ExecInitWindowAgg
為該窗口函數分配了一個WindowStatePerFunc
結構體,該結構體存儲了該函數的執行狀態、輸入數據的類型、排序信息等。
- 初始化分區和排序列:
ExecInitWindowAgg
為department_id
和employee_id
列設置分區和排序操作。- 如果存在
PARTITION BY
或ORDER BY
子句,ExecInitWindowAgg
會設置必要的比較函數,以便在執行時能夠正確地分區和排序數據。
- 初始化查詢的其他部分:
- 在初始化窗口聚合時,
ExecInitWindowAgg
還會初始化額外的執行計劃,如質檢條件(qual
),分區邊界的表達式等。- 它會為每個部門創建一個新的
WindowObject
,用于處理該部門的窗口聚合數據。
執行流程總結:
ExecInitWindowAgg
會通過初始化WindowAggState
,為每個窗口函數(如AVG(salary)
)分配執行狀態和內存上下文。- 它會設置數據的分區和排序方式,以確保每個窗口函數能在正確的分區內工作,并按正確的順序進行計算。
- 此外,它會為窗口聚合的每個階段(如計算中間結果和最終結果)創建適當的上下文和資源。
- 最終,通過該函數初始化的狀態結構,查詢的執行計劃能夠在實際執行過程中評估每個窗口函數,并進行相應的聚合計算。
舉例說明:
??假設有以下表數據:
department_id | employee_id | salary |
---|---|---|
1 | 1 | 5000 |
1 | 2 | 6000 |
1 | 3 | 7000 |
2 | 1 | 8000 |
2 | 2 | 8500 |
??窗口聚合查詢通過 AVG(salary)
計算每個部門的滑動平均薪資。
對于 department_id = 1,AVG(salary) 會計算 5000, 6000, 7000 的平均值。
對于 department_id = 2,AVG(salary) 會計算 8000, 8500 的平均值。
??在 ExecInitWindowAgg
的執行過程中,為每個部門創建了一個獨立的內存上下文并設置了分區(department_id
)。然后在每個窗口函數中,根據分區的邊界(如 department_id
),分別計算各自的平均薪資。
ExecWindowAgg 函數
??ExecWindowAgg 函數,主要用于執行窗口聚合操作。它從子查詢獲取元組(行),并將它們存儲到 tuplestore
(一個臨時存儲區),然后處理窗口函數。窗口聚合的計算方式允許在查詢的每一行上對一組行進行操作,而不是只處理單獨的行。以下是對代碼每一行的詳細中文注釋以及這段代碼的功能描述。源碼如下所示(路徑:src\backend\executor\nodeWindowAgg.c
)
/* -----------------* ExecWindowAgg** ExecWindowAgg函數接收來自外部子查詢的元組,* 并將它們存儲到tuplestore中,然后處理窗口函數。* 該節點不會減少或篩選任何行,因此返回的行數與其外部子查詢的結果完全相同。* -----------------*/
static TupleTableSlot *
ExecWindowAgg(PlanState *pstate)
{// 將pstate轉換為WindowAggState類型WindowAggState *winstate = castNode(WindowAggState, pstate);TupleTableSlot *slot;ExprContext *econtext;int i;int numfuncs;// 檢查中斷信號CHECK_FOR_INTERRUPTS();// 如果狀態為WINDOWAGG_DONE,說明窗口聚合已經完成,返回NULLif (winstate->status == WINDOWAGG_DONE)return NULL;/** 如果是第一次調用或執行了重新掃描,計算窗口框架的偏移值。* 這些值在掃描過程中會保持不變。* 如果偏移值為volatile表達式,我們只使用它的初始值。*/if (winstate->all_first){int frameOptions = winstate->frameOptions;ExprContext *econtext = winstate->ss.ps.ps_ExprContext;Datum value;bool isnull;int16 len;bool byval;// 計算開始偏移值if (frameOptions & FRAMEOPTION_START_OFFSET){Assert(winstate->startOffset != NULL);value = ExecEvalExprSwitchContext(winstate->startOffset,econtext,&isnull);if (isnull)ereport(ERROR,(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),errmsg("frame starting offset must not be null")));// 將值復制到查詢生命周期上下文中get_typlenbyval(exprType((Node *) winstate->startOffset->expr),&len, &byval);winstate->startOffsetValue = datumCopy(value, byval, len);if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS)){// 偏移值應該是int8類型int64 offset = DatumGetInt64(value);if (offset < 0)ereport(ERROR,(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),errmsg("frame starting offset must not be negative")));}}// 計算結束偏移值if (frameOptions & FRAMEOPTION_END_OFFSET){Assert(winstate->endOffset != NULL);value = ExecEvalExprSwitchContext(winstate->endOffset,econtext,&isnull);if (isnull)ereport(ERROR,(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),errmsg("frame ending offset must not be null")));// 將值復制到查詢生命周期上下文中get_typlenbyval(exprType((Node *) winstate->endOffset->expr),&len, &byval);winstate->endOffsetValue = datumCopy(value, byval, len);if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS)){// 偏移值應該是int8類型int64 offset = DatumGetInt64(value);if (offset < 0)ereport(ERROR,(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),errmsg("frame ending offset must not be negative")));}}// 標記為已計算偏移值winstate->all_first = false;}// 處理每一行的數據for (;;){// 如果沒有緩沖區,初始化新的分區,并將當前行位置設為0if (winstate->buffer == NULL){begin_partition(winstate);}else{// 否則,繼續推進當前分區的行位置winstate->currentpos++;// 如果當前幀已被標記無效,需要重新計算winstate->framehead_valid = false;winstate->frametail_valid = false;}// 如果還沒有將所有的元組讀取到緩沖區中,繼續讀取spool_tuples(winstate, winstate->currentpos);// 如果當前分區的所有行已經讀取完成,釋放當前分區并繼續處理下一個分區if (winstate->partition_spooled &&winstate->currentpos >= winstate->spooled_rows){release_partition(winstate);if (winstate->more_partitions){begin_partition(winstate);Assert(winstate->spooled_rows > 0);// 切換到下一個分區時退出直通模式winstate->status = WINDOWAGG_RUN;}else{// 如果沒有更多的分區,說明處理完成winstate->status = WINDOWAGG_DONE;return NULL;}}// 設置當前行的執行上下文econtext = winstate->ss.ps.ps_ExprContext;// 清除當前行的表達式上下文ResetExprContext(econtext);// 從tuplestore中讀取當前行并保存到ScanTupleSlot中tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr);if ((winstate->frameOptions & (FRAMEOPTION_GROUPS |FRAMEOPTION_EXCLUDE_GROUP |FRAMEOPTION_EXCLUDE_TIES)) &&winstate->currentpos > 0){// 如果是GROUPS模式或需要處理排除組的情況ExecCopySlot(winstate->temp_slot_2, winstate->ss.ss_ScanTupleSlot);if (!tuplestore_gettupleslot(winstate->buffer, true, true,winstate->ss.ss_ScanTupleSlot))elog(ERROR, "unexpected end of tuplestore");if (!are_peers(winstate, winstate->temp_slot_2,winstate->ss.ss_ScanTupleSlot)){winstate->currentgroup++;winstate->groupheadpos = winstate->currentpos;winstate->grouptail_valid = false;}ExecClearTuple(winstate->temp_slot_2);}else{if (!tuplestore_gettupleslot(winstate->buffer, true, true,winstate->ss.ss_ScanTupleSlot))elog(ERROR, "unexpected end of tuplestore");}// 在窗口函數執行時跳過直通模式if (winstate->status == WINDOWAGG_RUN){// 評估窗口函數numfuncs = winstate->numfuncs;for (i = 0; i < numfuncs; i++){WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);// 跳過純聚合函數if (perfuncstate->plain_agg)continue;eval_windowfunction(winstate, perfuncstate,&(econtext->ecxt_aggvalues[perfuncstate->wfuncstate->wfuncno]),&(econtext->ecxt_aggnulls[perfuncstate->wfuncstate->wfuncno]));}// 評估聚合函數if (winstate->numaggs > 0)eval_windowaggregates(winstate);}// 如果創建了輔助讀指針用于框架或組邊界,確保它們保持最新if (winstate->framehead_ptr >= 0)update_frameheadpos(winstate);if (winstate->frametail_ptr >= 0)update_frametailpos(winstate);if (winstate->grouptail_ptr >= 0)update_grouptailpos(winstate);// 刪除不再需要的行tuplestore_trim(winstate->buffer);// 生成最終輸出元組econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;slot = ExecProject(winstate->ss.ps.ps_ProjInfo);// 如果窗口聚合仍在運行,檢查是否需要進入直通模式if (winstate->status == WINDOWAGG_RUN){econtext->ecxt_scantuple = slot; // 設置掃描的元組為當前的slot/** 現在評估運行條件,看看是否需要進入傳遞模式(pass-through),或者可能完全停止。*/if (!ExecQual(winstate->runcondition, econtext)) // 如果運行條件不滿足{/** 判斷進入哪種模式。如果沒有 PARTITION BY 子句且我們是頂層 WindowAgg,那么我們已經完成了。* 這個元組以及未來的元組將不可能匹配運行條件。如果有 PARTITION BY 子句或者我們不是頂層窗口,* 那么不能立即停止,因為我們需要處理其他分區,或者確保上層的 WindowAgg 節點接收到* 它們所需的所有元組,以處理它們的窗口函數。*/if (winstate->use_pass_through) // 如果使用傳遞模式{/** 在有 PARTITION BY 子句時,頂層窗口需要嚴格的傳遞模式(STRICT pass-through)。* 否則,我們必須確保存儲那些不匹配運行條件的元組,以便它們可供上層的 WindowAgg 使用。*/if (winstate->top_window) // 如果是頂層窗口{winstate->status = WINDOWAGG_PASSTHROUGH_STRICT; // 設置為嚴格的傳遞模式continue; // 繼續循環,跳過后續處理}else{winstate->status = WINDOWAGG_PASSTHROUGH; // 設置為普通傳遞模式/** 如果我們不是頂層窗口,我們最好將聚合結果設為 NULL。* 在傳遞模式下我們不再更新這些結果,這樣可以避免舊的過時結果殘留。* 其中一些結果可能是引用類型(byref),所以不能指向已經釋放的內存。* 規劃器保證運行條件中的條件是嚴格的,因此頂層 WindowAgg 會在過濾子句中篩除這些 NULL。*/numfuncs = winstate->numfuncs;for (i = 0; i < numfuncs; i++){econtext->ecxt_aggvalues[i] = (Datum) 0; // 將聚合值設為 NULLecontext->ecxt_aggnulls[i] = true; // 將聚合 NULL 標志設為 true}}}else{/** 不需要傳遞模式,我們可以直接返回 NULL。* 因為沒有其他元組會匹配運行條件。*/winstate->status = WINDOWAGG_DONE; // 設置狀態為完成return NULL; // 返回 NULL,表示窗口聚合結束}}/** 過濾掉不需要的元組,只保留頂層 WindowAgg 需要的元組。*/if (!ExecQual(winstate->ss.ps.qual, econtext)) // 如果當前元組不符合過濾條件{InstrCountFiltered1(winstate, 1); // 統計被過濾的元組數continue; // 繼續下一輪循環,跳過當前元組}break; // 通過過濾,當前元組滿足條件,跳出循環,開始處理該元組}/** 如果不處于 WINDOWAGG_RUN 模式,只要不是頂層窗口,我們必須返回當前元組。*/else if (!winstate->top_window) // 如果不是頂層窗口break; // 跳出循環,返回當前元組// 如果結果集合為空,返回NULLif (TupIsNull(slot))return NULL;// 返回窗口結果return slot;}
}
代碼邏輯解釋:計算窗口偏移量
/** 如果是第一次調用或執行了重新掃描,計算窗口框架的偏移值。* 這些值在掃描過程中會保持不變。* 如果偏移值為volatile表達式,我們只使用它的初始值。*/if (winstate->all_first){int frameOptions = winstate->frameOptions;ExprContext *econtext = winstate->ss.ps.ps_ExprContext;Datum value;bool isnull;int16 len;bool byval;// 計算開始偏移值if (frameOptions & FRAMEOPTION_START_OFFSET){Assert(winstate->startOffset != NULL);value = ExecEvalExprSwitchContext(winstate->startOffset,econtext,&isnull);if (isnull)ereport(ERROR,(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),errmsg("frame starting offset must not be null")));// 將值復制到查詢生命周期上下文中get_typlenbyval(exprType((Node *) winstate->startOffset->expr),&len, &byval);winstate->startOffsetValue = datumCopy(value, byval, len);if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS)){// 偏移值應該是int8類型int64 offset = DatumGetInt64(value);if (offset < 0)ereport(ERROR,(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),errmsg("frame starting offset must not be negative")));}}// 計算結束偏移值if (frameOptions & FRAMEOPTION_END_OFFSET){Assert(winstate->endOffset != NULL);value = ExecEvalExprSwitchContext(winstate->endOffset,econtext,&isnull);if (isnull)ereport(ERROR,(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),errmsg("frame ending offset must not be null")));// 將值復制到查詢生命周期上下文中get_typlenbyval(exprType((Node *) winstate->endOffset->expr),&len, &byval);winstate->endOffsetValue = datumCopy(value, byval, len);if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS)){// 偏移值應該是int8類型int64 offset = DatumGetInt64(value);if (offset < 0)ereport(ERROR,(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),errmsg("frame ending offset must not be negative")));}}// 標記為已計算偏移值winstate->all_first = false;}
??這段代碼的目的是在 第一次調用 或者 重新掃描 時,計算并確定窗口框架的偏移量(startOffset
和 endOffset
)。這兩個偏移量在整個掃描過程中保持不變。如果表達式是可變的(例如,用戶提供了動態的表達式),代碼只會使用 初始值,確保偏移量不會在掃描過程中變化。
代碼邏輯詳細解釋:
-
初始化:
winstate->all_first
是一個標記,用來指示這是 第一次執行 窗口聚合操作(或者在重新掃描時)。frameOptions
是一個標志,表示啟用了哪些窗口框架選項,比如開始和結束偏移量。ExprContext *econtext
獲取當前的執行上下文,便于在評估表達式時使用。Datum value
存儲計算得到的偏移量。isnull
是一個標志,表示評估的結果是否為 NULL。len
,byval
是用于復制數據的類型信息。
-
計算
startOffset
(開始偏移量):- 如果啟用了
FRAMEOPTION_START_OFFSET
,即窗口框架指定了一個開始偏移量,則通過ExecEvalExprSwitchContext
計算該偏移量的值。 - 如果計算結果為
NULL
,則拋出錯誤(偏移量不能為NULL
)。 - 偏移量值被復制到查詢生命周期內的內存上下文中,確保其在整個查詢過程中有效。
- 如果框架行為是
ROWS
或GROUPS
,則假設偏移量是一個整數(int64
),并檢查其是否為負數。如果是負數,拋出錯誤(偏移量不能為負數)。
- 如果啟用了
-
計算
endOffset
(結束偏移量):- 如果啟用了
FRAMEOPTION_END_OFFSET
,即窗口框架指定了一個結束偏移量,則與startOffset
的處理類似,計算并存儲結束偏移量的值。 - 如果結果為
NULL
,同樣拋出錯誤。 - 如果偏移量為負數,同樣拋出錯誤。
- 如果啟用了
-
更新
winstate->all_first
:- 最后,將
winstate->all_first
設置為false
,表示不再是第一次掃描。
- 最后,將
代碼邏輯解釋:窗口聚合分區初始化與行推進邏輯
// 如果沒有緩沖區,初始化新的分區,并將當前行位置設為0if (winstate->buffer == NULL){begin_partition(winstate);}else{// 否則,繼續推進當前分區的行位置winstate->currentpos++;// 如果當前幀已被標記無效,需要重新計算winstate->framehead_valid = false;winstate->frametail_valid = false;}
??這段代碼主要處理了窗口聚合操作中的數據分區和當前行的移動。它根據窗口的狀態(是否已經初始化過緩沖區)來決定是初始化分區還是在已有分區中推進當前行。接下來,我將逐步解釋這段代碼的邏輯:
代碼邏輯詳細解釋:
-
初始化分區:
- 這里的
winstate->buffer == NULL
表示當前窗口聚合狀態還沒有緩沖區,說明還沒有處理任何分區。 begin_partition(winstate);
會調用一個函數來初始化當前的分區。通常,這個函數會為窗口聚合操作準備數據緩沖區,并將當前行的指針 (currentpos
) 設置為0
,即當前行指向分區中的第一個行。- 如果當前分區沒有輸入行,程序會在后續邏輯中檢測到這一情況并退出。
- 這里的
-
推進當前行:
- 如果
winstate->buffer
已經存在,表示窗口聚合操作已經在處理某個分區。此時,程序推進當前行:winstate->currentpos++;
:將當前行指針加一,表示處理到了分區中的下一行數據。
- 推進當前行后,可能會導致窗口幀的位置發生變化,因此需要將
framehead_valid
和frametail_valid
標記為false
,表示這些位置的值不再有效,需要在后續重新計算。winstate->framehead_valid = false;
和winstate->frametail_valid = false;
將窗口幀頭和幀尾的有效性標記為無效。
- 代碼中有一個注釋說明
grouptail
的有效性無需在這里無效化,具體邏輯將在后續的代碼中處理。
- 如果
總結:
- 這段代碼根據窗口聚合的狀態決定是否初始化新的分區,或者推進當前行。
- 如果是新的分區,初始化分區并將當前行設置為
0
。- 如果是已經處理過的分區,程序會推進當前行,同時標記窗口幀的頭部和尾部為無效,表示需要在后續重新計算幀的位置。
代碼邏輯解釋:從Tuplestore讀取當前行并處理分組和排除邏輯
// 從tuplestore中讀取當前行并保存到ScanTupleSlot中tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr);if ((winstate->frameOptions & (FRAMEOPTION_GROUPS |FRAMEOPTION_EXCLUDE_GROUP |FRAMEOPTION_EXCLUDE_TIES)) &&winstate->currentpos > 0){// 如果是GROUPS模式或需要處理排除組的情況ExecCopySlot(winstate->temp_slot_2, winstate->ss.ss_ScanTupleSlot);if (!tuplestore_gettupleslot(winstate->buffer, true, true,winstate->ss.ss_ScanTupleSlot))elog(ERROR, "unexpected end of tuplestore");if (!are_peers(winstate, winstate->temp_slot_2,winstate->ss.ss_ScanTupleSlot)){winstate->currentgroup++;winstate->groupheadpos = winstate->currentpos;winstate->grouptail_valid = false;}ExecClearTuple(winstate->temp_slot_2);}else{if (!tuplestore_gettupleslot(winstate->buffer, true, true,winstate->ss.ss_ScanTupleSlot))elog(ERROR, "unexpected end of tuplestore");}
??這段代碼的核心功能是從tuplestore
中讀取當前行,并根據窗口聚合的不同選項(如GROUPS
模式、排除組和排除相等值)進行相應的處理,確保處理過程中正確更新當前行的分組信息。
代碼邏輯詳細解釋:
-
從tuplestore讀取當前行:
- 使用
tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr);
設置讀取指針,指向當前的讀取位置。 - 然后調用
tuplestore_gettupleslot()
來從tuplestore
中獲取當前元組,并將其保存到winstate->ss.ss_ScanTupleSlot
中。
- 使用
-
處理GROUPS模式及排除組/排除相等的情況:
- 如果
winstate->frameOptions
啟用了FRAMEOPTION_GROUPS、FRAMEOPTION_EXCLUDE_GROUP
或FRAMEOPTION_EXCLUDE_TIES
,并且winstate->currentpos
大于0
(意味著當前已經不是分區中的第一個元組),則執行以下步驟:- 使用
ExecCopySlot()
將當前元組拷貝到臨時槽winstate->temp_slot_2
。 - 再次從
tuplestore
中獲取下一個元組,確保元組被成功讀取。 - 調用
are_peers()
函數檢查當前元組與臨時槽中存儲的上一個元組是否屬于同一個分組。如果不是同一個分組(即are_peers()
返回false
),則:- 增加
winstate->currentgroup
計數器,表示當前分組已結束并開始新的分組。 - 更新
winstate->groupheadpos
為當前元組的位置,標記當前分組的起始位置。 - 設置
winstate->grouptail_valid = false
,表明當前分組尾部信息無效,需要重新計算。
- 增加
- 使用
ExecClearTuple()
清除臨時槽中的數據,釋放空間。
- 使用
- 如果
-
默認處理:
- 如果不需要處理
GROUPS
模式或排除邏輯(即上述條件不滿足),直接從tuplestore
中讀取當前元組并存儲到winstate->ss.ss_ScanTupleSlot
中。 - 如果無法成功獲取元組,報錯
unexpected end of tuplestore
。
- 如果不需要處理
代碼邏輯解釋:窗口聚合狀態管理與元組過濾
if (winstate->status == WINDOWAGG_RUN)
{econtext->ecxt_scantuple = slot; // 設置掃描的元組為當前的slot/** 現在評估運行條件,看看是否需要進入傳遞模式(pass-through),或者可能完全停止。*/if (!ExecQual(winstate->runcondition, econtext)) // 如果運行條件不滿足{/** 判斷進入哪種模式。如果沒有 PARTITION BY 子句且我們是頂層 WindowAgg,那么我們已經完成了。* 這個元組以及未來的元組將不可能匹配運行條件。如果有 PARTITION BY 子句或者我們不是頂層窗口,* 那么不能立即停止,因為我們需要處理其他分區,或者確保上層的 WindowAgg 節點接收到* 它們所需的所有元組,以處理它們的窗口函數。*/if (winstate->use_pass_through) // 如果使用傳遞模式{/** 在有 PARTITION BY 子句時,頂層窗口需要嚴格的傳遞模式(STRICT pass-through)。* 否則,我們必須確保存儲那些不匹配運行條件的元組,以便它們可供上層的 WindowAgg 使用。*/if (winstate->top_window) // 如果是頂層窗口{winstate->status = WINDOWAGG_PASSTHROUGH_STRICT; // 設置為嚴格的傳遞模式continue; // 繼續循環,跳過后續處理}else{winstate->status = WINDOWAGG_PASSTHROUGH; // 設置為普通傳遞模式/** 如果我們不是頂層窗口,我們最好將聚合結果設為 NULL。* 在傳遞模式下我們不再更新這些結果,這樣可以避免舊的過時結果殘留。* 其中一些結果可能是引用類型(byref),所以不能指向已經釋放的內存。* 規劃器保證運行條件中的條件是嚴格的,因此頂層 WindowAgg 會在過濾子句中篩除這些 NULL。*/numfuncs = winstate->numfuncs;for (i = 0; i < numfuncs; i++){econtext->ecxt_aggvalues[i] = (Datum) 0; // 將聚合值設為 NULLecontext->ecxt_aggnulls[i] = true; // 將聚合 NULL 標志設為 true}}}else{/** 不需要傳遞模式,我們可以直接返回 NULL。* 因為沒有其他元組會匹配運行條件。*/winstate->status = WINDOWAGG_DONE; // 設置狀態為完成return NULL; // 返回 NULL,表示窗口聚合結束}}/** 過濾掉不需要的元組,只保留頂層 WindowAgg 需要的元組。*/if (!ExecQual(winstate->ss.ps.qual, econtext)) // 如果當前元組不符合過濾條件{InstrCountFiltered1(winstate, 1); // 統計被過濾的元組數continue; // 繼續下一輪循環,跳過當前元組}break; // 通過過濾,當前元組滿足條件,跳出循環,開始處理該元組
}/** 如果不處于 WINDOWAGG_RUN 模式,只要不是頂層窗口,我們必須返回當前元組。*/
else if (!winstate->top_window) // 如果不是頂層窗口break; // 跳出循環,返回當前元組
??這段代碼邏輯主要是用來處理窗口聚合操作中的運行條件(runcondition
)和控制窗口的傳遞模式。在窗口聚合過程中,可能會切換到pass-through
模式(即跳過窗口函數計算的狀態),并進行一系列條件判斷。
代碼邏輯詳細解釋:
-
檢查當前狀態:
- 代碼首先檢查
winstate->status
是否等于WINDOWAGG_RUN
,只有在窗口聚合處于運行模式時才會執行接下來的操作。
- 代碼首先檢查
-
運行條件判斷:
ExecQual(winstate->runcondition, econtext)
:通過執行runcondition
來判斷當前行是否滿足窗口函數的運行條件。如果不滿足條件,則需要切換到pass-through
模式或者結束處理。winstate->use_pass_through
控制是否使用pass-through
模式:- 如果是
top_window
且有PARTITION BY
子句,則進入嚴格的pass-through
模式:winstate->status = WINDOWAGG_PASSTHROUGH_STRICT
。 - 如果是非頂層窗口且不滿足
runcondition
,則進入普通的pass-through
模式,窗口聚合的結果會被清空。 - 如果不是頂層窗口,且不進入
pass-through
模式,則會將窗口聚合的結果設置為NULL
,避免殘留舊結果。
- 如果是
-
結束處理:
- 如果沒有
pass-through
模式且沒有滿足條件的行,直接將winstate->status
設置為WINDOWAGG_DONE
,結束窗口聚合操作,返回NULL
。
- 如果沒有
-
過濾不需要的元組:
- 通過
ExecQual(winstate->ss.ps.qual, econtext)
來判斷當前行是否需要。在頂層窗口中,如果當前行不滿足條件,則跳過該行。 InstrCountFiltered1(winstate, 1)
統計跳過的元組數。
- 通過
-
跳過條件不滿足的元組:
- 如果運行條件不滿足,則直接跳過當前元組,進入下一輪循環。
-
返回符合條件的元組:
- 在當前元組符合所有條件的情況下,跳出循環并繼續處理。
ExecEndWindowAgg 函數
??ExecEndWindowAgg 函數用于結束窗口聚合(WindowAgg
)節點的執行,釋放與該節點相關的資源。該函數的作用是清理和釋放內存,以確保在節點處理完成后不再占用任何多余的資源。(路徑:src\backend\executor\nodeWindowAgg.c
)
/* -----------------* ExecEndWindowAgg* -----------------*/
void
ExecEndWindowAgg(WindowAggState *node)
{PlanState *outerPlan; // 用于保存外部計劃節點的狀態int i;// 釋放分區相關資源release_partition(node);// 清理掃描元組槽中的數據ExecClearTuple(node->ss.ss_ScanTupleSlot);// 清理第一個分區槽中的數據ExecClearTuple(node->first_part_slot);// 清理聚合行槽中的數據ExecClearTuple(node->agg_row_slot);// 清理臨時槽1中的數據ExecClearTuple(node->temp_slot_1);// 清理臨時槽2中的數據ExecClearTuple(node->temp_slot_2);// 如果存在 framehead_slot,清理該槽中的數據if (node->framehead_slot)ExecClearTuple(node->framehead_slot);// 如果存在 frametail_slot,清理該槽中的數據if (node->frametail_slot)ExecClearTuple(node->frametail_slot);/** 釋放兩個表達式上下文(ExprContext)。*/// 釋放當前節點的表達式上下文ExecFreeExprContext(&node->ss.ps);// 將臨時上下文賦值給當前表達式上下文node->ss.ps.ps_ExprContext = node->tmpcontext;// 再次釋放表達式上下文ExecFreeExprContext(&node->ss.ps);// 遍歷每個聚合函數,釋放其對應的上下文for (i = 0; i < node->numaggs; i++){// 如果當前聚合函數的上下文不是默認的聚合上下文,則釋放該上下文if (node->peragg[i].aggcontext != node->aggcontext)MemoryContextDelete(node->peragg[i].aggcontext);}// 釋放分區上下文MemoryContextDelete(node->partcontext);// 釋放聚合上下文MemoryContextDelete(node->aggcontext);// 釋放聚合函數和聚合狀態數組的內存pfree(node->perfunc);pfree(node->peragg);// 獲取外部計劃節點的狀態outerPlan = outerPlanState(node);// 結束外部計劃節點的執行ExecEndNode(outerPlan);
}