【PostgreSQL內核學習 —— (WindowAgg(二))】

WindowAgg

  • WindowAggState 結構體
  • 窗口聚合行為
  • ExecInitWindowAgg 函數
  • ExecWindowAgg 函數
    • 代碼邏輯解釋:計算窗口偏移量
      • 代碼邏輯詳細解釋:
    • 代碼邏輯解釋:窗口聚合分區初始化與行推進邏輯
      • 代碼邏輯詳細解釋:
    • 代碼邏輯解釋:從Tuplestore讀取當前行并處理分組和排除邏輯
      • 代碼邏輯詳細解釋:
    • 代碼邏輯解釋:窗口聚合狀態管理與元組過濾
      • 代碼邏輯詳細解釋:
  • ExecEndWindowAgg 函數

聲明:本文的部分內容參考了他人的文章。在編寫過程中,我們尊重他人的知識產權和學術成果,力求遵循合理使用原則,并在適用的情況下注明引用來源。
本文主要參考了 postgresql-15.0 的開源代碼和《PostgresSQL數據庫內核分析》一書

??在【PostgreSQL內核學習 —— (WindowAgg(一))】中,我們介紹了窗口函數以及窗口聚合的核心計算過程。本文我們繼續學習WindowAgg算子的具體實現邏輯。

WindowAggState 結構體

??WindowAggState 結構體用于表示窗口聚合操作的執行狀態。它存儲了窗口聚合的相關數據和信息,包括窗口函數聚合函數分區信息排序信息等。該結構體主要用于窗口聚合節點在查詢執行過程中維護當前的執行狀態和環境,確保窗口聚合操作按照指定的框架(例如,RANGEGROUPS等)正確地執行。

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;

查詢解析步驟:

  1. Plan Tree Generation:當執行查詢時,查詢計劃生成器生成了一個執行計劃,其中包含 WindowAgg 節點。WindowAgg 節點表示需要計算窗口聚合函數(如 AVG)的部分。
  2. 調用 ExecInitWindowAgg 函數:在執行查詢時,ExecInitWindowAgg 被調用來初始化 WindowAgg 節點的執行環境。此函數的主要任務是為窗口函數設置運行時信息,包括內存上下文的分配、表達式上下文的初始化,以及窗口函數的相關狀態。

具體的執行過程:

  1. 創建 WindowAggState 結構:
  • 在查詢執行期間,ExecInitWindowAgg 創建一個 WindowAggState 結構體,用于存儲窗口聚合的運行時狀態。
  • 該結構體包含對窗口函數所需數據的引用和內存上下文。
  1. 創建并分配內存上下文:
  • ExecInitWindowAgg 函數為每個窗口分配內存上下文。在該場景中,窗口函數會基于 department_id 劃分數據,因此需要為每個部門分配一個本地內存上下文。
  • 此外,創建了一個 aggcontext,用于存儲聚合操作的中間值。
  1. 初始化表達式上下文:
  • 窗口聚合涉及的所有表達式(例如 AVG(salary))會有各自的上下文,ExecInitWindowAgg 為每個窗口函數分配一個表達式上下文。
  • 表達式上下文會存儲評估窗口函數時所需的所有臨時數據,如窗口的分區和排序列等。
  1. 初始化 WindowFunc:
  • 這個查詢包含了一個窗口函數 AVG(salary) OVER (PARTITION BY department_id ORDER BY employee_id),它將在窗口內計算 salary 的平均值。
  • ExecInitWindowAgg 為該窗口函數分配了一個 WindowStatePerFunc 結構體,該結構體存儲了該函數的執行狀態輸入數據的類型排序信息等。
  1. 初始化分區和排序列:
  • ExecInitWindowAggdepartment_idemployee_id 列設置分區和排序操作
  • 如果存在 PARTITION BYORDER BY 子句,ExecInitWindowAgg 會設置必要的比較函數,以便在執行時能夠正確地分區和排序數據。
  1. 初始化查詢的其他部分:
  • 在初始化窗口聚合時,ExecInitWindowAgg 還會初始化額外的執行計劃,如質檢條件qual),分區邊界的表達式等。
  • 它會為每個部門創建一個新的 WindowObject,用于處理該部門的窗口聚合數據。

執行流程總結:

  • ExecInitWindowAgg 會通過初始化 WindowAggState,為每個窗口函數(如 AVG(salary))分配執行狀態和內存上下文。
  • 它會設置數據的分區和排序方式,以確保每個窗口函數能在正確的分區內工作,并按正確的順序進行計算。
  • 此外,它會為窗口聚合的每個階段(如計算中間結果和最終結果)創建適當的上下文和資源
  • 最終,通過該函數初始化的狀態結構,查詢的執行計劃能夠在實際執行過程中評估每個窗口函數,并進行相應的聚合計算。

舉例說明:
??假設有以下表數據:

department_idemployee_idsalary
115000
126000
137000
218000
228500

??窗口聚合查詢通過 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;}

??這段代碼的目的是在 第一次調用 或者 重新掃描 時,計算并確定窗口框架的偏移量(startOffsetendOffset)。這兩個偏移量在整個掃描過程中保持不變。如果表達式是可變的(例如,用戶提供了動態的表達式),代碼只會使用 初始值,確保偏移量不會在掃描過程中變化。

代碼邏輯詳細解釋:

  1. 初始化:

    • winstate->all_first 是一個標記,用來指示這是 第一次執行 窗口聚合操作(或者在重新掃描時)。
    • frameOptions 是一個標志,表示啟用了哪些窗口框架選項,比如開始和結束偏移量。
    • ExprContext *econtext 獲取當前的執行上下文,便于在評估表達式時使用。
    • Datum value 存儲計算得到的偏移量。
    • isnull 是一個標志,表示評估的結果是否為 NULL。
    • len, byval 是用于復制數據的類型信息。
  2. 計算 startOffset(開始偏移量):

    • 如果啟用了 FRAMEOPTION_START_OFFSET,即窗口框架指定了一個開始偏移量,則通過 ExecEvalExprSwitchContext 計算該偏移量的值。
    • 如果計算結果為 NULL,則拋出錯誤(偏移量不能為 NULL)。
    • 偏移量值被復制到查詢生命周期內的內存上下文中,確保其在整個查詢過程中有效。
    • 如果框架行為是 ROWSGROUPS,則假設偏移量是一個整數(int64),并檢查其是否為負數。如果是負數,拋出錯誤(偏移量不能為負數)。
  3. 計算 endOffset(結束偏移量):

    • 如果啟用了 FRAMEOPTION_END_OFFSET,即窗口框架指定了一個結束偏移量,則與 startOffset 的處理類似,計算并存儲結束偏移量的值。
    • 如果結果為 NULL,同樣拋出錯誤。
    • 如果偏移量為負數,同樣拋出錯誤。
  4. 更新 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;}

??這段代碼主要處理了窗口聚合操作中的數據分區和當前行的移動。它根據窗口的狀態(是否已經初始化過緩沖區)來決定是初始化分區還是在已有分區中推進當前行。接下來,我將逐步解釋這段代碼的邏輯:

代碼邏輯詳細解釋:

  1. 初始化分區:

    • 這里的 winstate->buffer == NULL 表示當前窗口聚合狀態還沒有緩沖區,說明還沒有處理任何分區
    • begin_partition(winstate); 會調用一個函數來初始化當前的分區。通常,這個函數會為窗口聚合操作準備數據緩沖區,并將當前行的指針 (currentpos) 設置為 0,即當前行指向分區中的第一個行。
    • 如果當前分區沒有輸入行,程序會在后續邏輯中檢測到這一情況并退出。
  2. 推進當前行:

    • 如果 winstate->buffer 已經存在,表示窗口聚合操作已經在處理某個分區。此時,程序推進當前行:
      • winstate->currentpos++;:將當前行指針加一,表示處理到了分區中的下一行數據。
    • 推進當前行后,可能會導致窗口幀的位置發生變化,因此需要將 framehead_validframetail_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模式、排除組和排除相等值)進行相應的處理,確保處理過程中正確更新當前行的分組信息。

代碼邏輯詳細解釋:

  1. 從tuplestore讀取當前行:

    • 使用 tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr); 設置讀取指針,指向當前的讀取位置。
    • 然后調用 tuplestore_gettupleslot() 來從tuplestore中獲取當前元組,并將其保存到 winstate->ss.ss_ScanTupleSlot 中。
  2. 處理GROUPS模式及排除組/排除相等的情況:

    • 如果 winstate->frameOptions 啟用了 FRAMEOPTION_GROUPS、FRAMEOPTION_EXCLUDE_GROUPFRAMEOPTION_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() 清除臨時槽中的數據,釋放空間。
  3. 默認處理:

    • 如果不需要處理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模式(即跳過窗口函數計算的狀態),并進行一系列條件判斷。

代碼邏輯詳細解釋:

  1. 檢查當前狀態:

    • 代碼首先檢查 winstate->status 是否等于 WINDOWAGG_RUN,只有在窗口聚合處于運行模式時才會執行接下來的操作。
  2. 運行條件判斷:

    • 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,避免殘留舊結果。
  3. 結束處理:

    • 如果沒有 pass-through 模式且沒有滿足條件的行,直接將 winstate->status 設置為 WINDOWAGG_DONE結束窗口聚合操作,返回 NULL
  4. 過濾不需要的元組:

    • 通過 ExecQual(winstate->ss.ps.qual, econtext) 來判斷當前行是否需要。在頂層窗口中,如果當前行不滿足條件,則跳過該行。
    • InstrCountFiltered1(winstate, 1) 統計跳過的元組數。
  5. 跳過條件不滿足的元組:

    • 如果運行條件不滿足,則直接跳過當前元組,進入下一輪循環。
  6. 返回符合條件的元組:

    • 在當前元組符合所有條件的情況下,跳出循環并繼續處理。

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);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/68137.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/68137.shtml
英文地址,請注明出處:http://en.pswp.cn/web/68137.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

區塊鏈項目孵化與包裝設計:從概念到市場的全流程指南

區塊鏈技術的快速發展催生了大量創新項目&#xff0c;但如何將一個區塊鏈項目從概念孵化成市場認可的產品&#xff0c;是許多團隊面臨的挑戰。本文將從孵化策略、包裝設計和市場落地三個維度&#xff0c;為你解析區塊鏈項目成功的關鍵步驟。 一、區塊鏈項目孵化的核心要素 明確…

【React】受控組件和非受控組件

目錄 受控組件非受控組件基于ref獲取DOM元素1、在標簽中使用2、在組件中使用 受控組件 表單元素的狀態&#xff08;值&#xff09;由 React 組件的 state 完全控制。組件的 state 保存了表單元素的值&#xff0c;并且每次用戶輸入時&#xff0c;React 通過事件處理程序來更新 …

C#開發的進銷存管理系統軟件

#### 介紹 進銷存管理系統實現的功能及用途 含模塊銷售管理、采購管理、存貨管理、庫存管理、賬款管理、用戶管理、職員管理七個部分 進銷存管理系統實現以下功能&#xff1a; 1. 庫存管理 2. 應付賬款和應收賬款的統計 3. 對訂單未結數量和采購單未結數量的統計 4. 權限的管理…

高性能 AI 處理器親和性調度算法實現

目錄 題目描述解題思路分析C 語言實現 生成組合的函數主程序實現C 語言代碼使用示例Python 實現 生成組合的函數主程序實現Python 代碼使用示例總結與展望題目描述 某公司研發的高性能 AI 處理器,每臺物理設備 a 包含 8 顆 AI 處理器,編號為 0 - 7。其中,編號 0 - 3 的處理…

快手ip屬地是定位嗎?怎么改

在當今數字化時代&#xff0c;隨著網絡平臺的不斷發展&#xff0c;用戶隱私和數據安全成為了公眾關注的焦點。各大社交媒體平臺紛紛推出的“IP屬地”功能&#xff0c;無疑為網絡環境增添了一抹新的色彩。其中&#xff0c;快手的IP屬地顯示功能尤為引人注目。那么&#xff0c;快…

Git 常用基礎命令詳解:init、add、commit

一、引言 在軟件開發的世界里&#xff0c;版本控制是一項至關重要的技術&#xff0c;它就像是一個時光機器&#xff0c;讓開發者能夠追蹤代碼的每一次變化&#xff0c;輕松回溯到任意歷史版本&#xff0c;同時也為多人協作開發提供了強大的支持。而 Git&#xff0c;作為目前最…

1-kafka服務端之延時操作前傳--時間輪

文章目錄 背景時間輪層級時間輪時間輪降級kafka中的時間輪kafka如何進行時間輪運行 背景 Kafka中存在大量的延時操作&#xff0c;比如延時生產、延時拉取和延時刪除等。Kafka并沒有使用JDK自帶的Timer或DelayQueue來實現延時的功能&#xff0c;而是基于時間輪的概念自定義實現…

從零開始:OpenCV 圖像處理快速入門教程

文章大綱 第1章 OpenCV 概述 1.1 OpenCV的模塊與功能  1.2 OpenCV的發展 1.3 OpenCV的應用 第2章 基本數據類型 2.1 cv::Vec類 2.2 cv&#xff1a;&#xff1a;Point類 2.3 cv&#xff1a;&#xff1a;Rng類 2.4 cv&#xff1a;&#xff1a;Size類 2.5 cv&#xff1a;&…

網絡工程師 (22)網絡協議

前言 網絡協議是計算機網絡中進行數據交換而建立的規則、標準或約定的集合&#xff0c;它規定了通信時信息必須采用的格式和這些格式的意義。 一、基本要素 語法&#xff1a;規定信息格式&#xff0c;包括數據及控制信息的格式、編碼及信號電平等。這是協議的基礎&#xff0c;確…

vue如何解決跨域

文章目錄 vue如何解決跨域1. 什么是跨域2. 如何解決2.1 CROS&#xff08;Cross-Origin Resource Sharing&#xff0c;跨域資源共享&#xff09;2.2 Proxy2.2.1 使用webpack proxy2.2.2 服務端代理轉發2.2.3 通過nginx實現代理 vue如何解決跨域 1. 什么是跨域 跨域本質是瀏覽器…

算法與數據結構(括號匹配問題)

思路 從題干可以看出&#xff0c;只要給出的括號對應關系正確&#xff0c;那么就可以返回true,否則返回false。這個題可以使用棧來解決 解題過程 首先從第一個字符開始遍歷&#xff0c;如果是括號的左邊&#xff08;‘&#xff08;‘&#xff0c;’[‘&#xff0c;’}‘&…

在linux 中搭建deepseek 做微調,硬件配置要求說明

搭建 可參考 使用deepseek-CSDN博客 官方網站&#xff1a;DeepSeek DeepSeek 是一個基于深度學習的開源項目&#xff0c;旨在通過深度學習技術來提升搜索引擎的準確性和效率。如果你想在 Linux 系統上搭建 DeepSeek&#xff0c;你可以遵循以下步驟。這里我將提供一個基本的指…

mounted鉤子函數里如何操作子組件的DOM?

在 Vue 的 mounted 鉤子函數中,操作子組件的 DOM 可以通過幾種方式實現,具體取決于對子組件的訪問方式。以下是一些常用的方法: 一、使用 ref 引用 定義 ref在父組件中,給子組件添加一個 ref 屬性,這樣就可以在父組件中通過 this.$refs 訪問到子組件的實例。 父組件示例…

vue2-為啥data屬性是一個函數而不是對象

vue2-為啥data屬性是一個函數而不是對象 1. data在vue實例和組件中的表現差異 vue實例的時候&#xff0c;data既可以是一個對象也可以是一個函數 new Vue({data:{//對象name:tom},data(){//函數return{name:tom}} })而在組件中定義data&#xff0c;只能是函數&#xff0c;如…

利用deepseek參與軟件測試 基本架構如何 又該在什么環節接入deepseek

利用DeepSeek參與軟件測試&#xff0c;可以考慮以下基本架構和接入環節&#xff1a; ### 基本架構 - **數據層** - **測試數據存儲**&#xff1a;用于存放各種測試數據&#xff0c;包括正常輸入數據、邊界值數據、異常數據等&#xff0c;這些數據可以作為DeepSeek的輸入&…

Word List 2

詞匯顏色標識解釋 詞匯表中的生詞 詞匯表中的詞組成的搭配、派生詞 例句中的生詞 我自己寫的生詞&#xff08;用于區分易混淆的詞&#xff0c;無顏色標識&#xff09; 不認識的單詞或句式 單詞的主要漢語意思 不太理解的句子語法和結構 Word List 2 英文音標中文regi…

樹欲靜而鳳不止

我不知道為什么要求一定要在抖音上舉辦婚禮&#xff1f;覺得唯一的一個作用&#xff0c;財力的體現。 做到了&#xff0c;就見了。讓我覺得就像買見面一樣。 見了不合適&#xff0c;該當如何&#xff1f; 這個對于認真找對象&#xff0c;真的很重要嗎&#xff1f; 分錢給平臺&…

kaggle比賽入門 - Spaceship Titanic (第一部分)

1. 導入packages import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns sns.set(styledarkgrid, font_scale1.4) from imblearn.over_sampling import SMOTE import itertools import warnings warnings.filter…

java基礎2(黑馬)

一、變量里的數據在計算機中的存儲原理 1.二進制 .二進制&#xff1a;只有0、1&#xff0c; 按照逢二進一的方式表示數據。 十進制數字11轉換為&#xff1a;1011 方法&#xff1a;除二取余法 計算機中表示數據的最小單元&#xff0c;一個字節&#xff08;Byte&#xff0c;簡…

【戒抖音系列】短視頻戒除-1-對推薦算法進行干擾

如今推薦算法已經滲透到人們生活的方方面面&#xff0c;尤其是抖音等短視頻核心就是推薦算法。 【短視頻的危害】 1> 會讓人變笨&#xff0c;慢慢讓人喪失注意力與專注力 2> 讓人喪失閱讀長文的能力 3> 讓人沉浸在一個又一個快感與嗨點當中。當我們刷短視頻時&#x…