PostgreSQL 原始碼解讀(99)- 分割槽表#5(資料查詢路由#2-RelOptInfo數...
上一章節已介紹瞭如何識別分割槽表並找到所有的分割槽子表以及函式expand_inherited_tables的主要實現邏輯,該函式把子表資訊放在root(PlannerInfo)->append_rel_list連結串列中。為了更好的理解相關的邏輯,本節重點介紹分割槽表查詢相關的重要資料結構,包括RelOptInfo/PlannerInfo/AppendRelInfo.
一、資料結構
RelOptInfo
RelOptInfo是規劃器/最佳化器使用的關係資訊結構體
在規劃過程中已存在的基表或者是在關係運算過程中產生的中間關係或者是最終產生的關係,都使用RelOptInfo結構體進行封裝表示.
查詢分割槽表時該結構體中的part_scheme儲存分割槽的schema,nparts儲存分割槽數,boundinfo是分割槽邊界資訊,partition_qual是分割槽約束條件,part_rels是分割槽表中每個分割槽的每個分割槽的RelOptInfo,partexprs是分割槽鍵表示式,partitioned_child_rels是關係中未修剪(unpruned)分割槽的RT索引.unpruned是指查詢分割槽表時,涉及的相關分割槽,比如 where c1 = 1 OR c1 = 2涉及的分割槽只有t_hash_partition_1和t_hash_partition_3,則在partitioned_child_rels中只有這兩個分割槽的資訊.
如何pruned,下節介紹
/*----------
* RelOptInfo
* Per-relation information for planning/optimization
* 規劃器/最佳化器使用的關係資訊結構體
*
* For planning purposes, a "base rel" is either a plain relation (a table)
* or the output of a sub-SELECT or function that appears in the range table.
* In either case it is uniquely identified by an RT index. A "joinrel"
* is the joining of two or more base rels. A joinrel is identified by
* the set of RT indexes for its component baserels. We create RelOptInfo
* nodes for each baserel and joinrel, and store them in the PlannerInfo's
* simple_rel_array and join_rel_list respectively.
* 出於計劃目的,“base rel”要麼是一個普通關係(表),
* 要麼是出現在範圍表中的子查詢或函式的輸出。
* 在這兩種情況下,它都是由RT索引惟一標識的。
* "joinrel"是兩個或兩個以上的base rels連線。
* 一個joinrel是由它的baserels的RT索引集標識。
* 我們為每個baserel和joinrel分別建立RelOptInfo節點,
* 並將它們分別儲存在PlannerInfo的simple_rel_array和join_rel_list中。
*
* Note that there is only one joinrel for any given set of component
* baserels, no matter what order we assemble them in; so an unordered
* set is the right datatype to identify it with.
* 請注意,對於任何給定的base rels,無論我們以何種順序組合它們,
* 都只有一個連線件;因此,一個無序的集合正好是標識它的資料型別。
*
* We also have "other rels", which are like base rels in that they refer to
* single RT indexes; but they are not part of the join tree, and are given
* a different RelOptKind to identify them.
* Currently the only kind of otherrels are those made for member relations
* of an "append relation", that is an inheritance set or UNION ALL subquery.
* An append relation has a parent RTE that is a base rel, which represents
* the entire append relation. The member RTEs are otherrels. The parent
* is present in the query join tree but the members are not. The member
* RTEs and otherrels are used to plan the scans of the individual tables or
* subqueries of the append set; then the parent baserel is given Append
* and/or MergeAppend paths comprising the best paths for the individual
* member rels. (See comments for AppendRelInfo for more information.)
* 同時存在“other rels”,類似於base rels,它們同樣都指向單個RT索引;
* 但是它們不是join樹的一部分,並且被賦予不同的RelOptKind來標識它們。
* 目前唯一的其他型別是那些為“append relation”的成員關係而建立的,
* 即繼承集或UNION ALL子查詢。
* 一個append relation有一個父RTE,它是一個基礎rel,表示整個append relation。
* 其他成員RTEs是otherrel.。
* 父節點存在於查詢的連線樹中,但成員無需儲存在連線樹中。
* 成員RTEs和otherrels用於計劃對append set的單表或子查詢的掃描;
* 然後給出parent base rel的APPEND路徑和/或MergeAppend路徑,這些路徑包含單個成員rels的最佳路徑。
* (更多資訊請參見AppendRelInfo的註釋。)
*
* At one time we also made otherrels to represent join RTEs, for use in
* handling join alias Vars. Currently this is not needed because all join
* alias Vars are expanded to non-aliased form during preprocess_expression.
* 曾經,還製作了其他的樹來表示連線rte,用於處理連線別名Vars。
* 目前不需要這樣做,因為在preprocess_expression期間,所有連線別名Vars都被擴充套件為非別名形式。
*
* We also have relations representing joins between child relations of
* different partitioned tables. These relations are not added to
* join_rel_level lists as they are not joined directly by the dynamic
* programming algorithm.
* 還有表示不同分割槽表的子關係之間的連線的關係。
* 這些關係不會新增到join_rel_level連結串列中,因為動態規劃演算法不會直接連線它們。
*
* There is also a RelOptKind for "upper" relations, which are RelOptInfos
* that describe post-scan/join processing steps, such as aggregation.
* Many of the fields in these RelOptInfos are meaningless, but their Path
* fields always hold Paths showing ways to do that processing step.
* 還有一種RelOptKind表示“upper”關係,
* 即描述掃描/連線後處理步驟(如聚合)的RelOptInfos。
* 這些RelOptInfos中的許多欄位都是沒有意義的,
* 但是它們的Path欄位總是包含顯示執行該處理步驟的路徑。
*
* Lastly, there is a RelOptKind for "dead" relations, which are base rels
* that we have proven we don't need to join after all.
* 最後,還有一種關係是“DEAD”的關係,在規劃期間已經證明不需要把此關係加入連線。
*
* Parts of this data structure are specific to various scan and join
* mechanisms. It didn't seem worth creating new node types for them.
* 該資料結構的某些部分特定於各種掃描和連線機制。
* 但似乎不值得為它們建立新的節點型別。
*
* relids - Set of base-relation identifiers; it is a base relation
* if there is just one, a join relation if more than one
* relids - 基礎關係識別符號的集合;如只有一個則為基礎關係,
* 如有多個則為連線關係
* rows - estimated number of tuples in the relation after restriction
* clauses have been applied (ie, output rows of a plan for it)
* rows - 應用約束條件子句後關係中元組的估算數目(即計劃的輸出行數)
* consider_startup - true if there is any value in keeping plain paths for
* this rel on the basis of having cheap startup cost
* consider_startup - 如果在具有低啟動成本的基礎上為這個rel保留訪問路徑有價值,則為真
* consider_param_startup - the same for parameterized paths
* consider_param_startup - 與parameterized訪問路徑一致
* reltarget - Default Path output tlist for this rel; normally contains
* Var and PlaceHolderVar nodes for the values we need to
* output from this relation.
* List is in no particular order, but all rels of an
* appendrel set must use corresponding orders.
* NOTE: in an appendrel child relation, may contain
* arbitrary expressions pulled up from a subquery!
* reltarget - 該rel的預設路徑輸出投影列;通常會包含Var和PlaceHolderVar
* pathlist - List of Path nodes, one for each potentially useful
* method of generating the relation
* 訪問路徑節點連結串列, 儲存每一種可能有用的生成關係的方法
* ppilist - ParamPathInfo nodes for parameterized Paths, if any
* 引數化路徑的ParamPathInfo節點(如果有的話)
* cheapest_startup_path - the pathlist member with lowest startup cost
* (regardless of ordering) among the unparameterized paths;
* or NULL if there is no unparameterized path
* 在非引數化路徑中啟動成本最低(無論順序如何)的路徑連結串列成員;
* 如果沒有非引數化路徑,則為NULL
* cheapest_total_path - the pathlist member with lowest total cost
* (regardless of ordering) among the unparameterized paths;
* or if there is no unparameterized path, the path with lowest
* total cost among the paths with minimum parameterization
* 在非引數化路徑中總成本最低(無論順序如何)的路徑列表成員;
* 如果沒有非引數化路徑,則在引數化最少的路徑中總成本最低的路徑
* cheapest_unique_path - for caching cheapest path to produce unique
* (no duplicates) output from relation; NULL if not yet requested
* 用於快取最便宜的路徑,以便從關係中產生唯一(無重複)輸出;
* 如果無此要求,則為NULL
* cheapest_parameterized_paths - best paths for their parameterizations;
* always includes cheapest_total_path, even if that's unparameterized
* 引數化的最佳路徑;總是包含cheapest_total_path,即使它是非引數化的
* direct_lateral_relids - rels this rel has direct LATERAL references to
* 該rel直接LATERAL依賴的rels
* lateral_relids - required outer rels for LATERAL, as a Relids set
* (includes both direct and indirect lateral references)
* LATERAL所需的外部rels,作為Relids集合(包括直接和間接的側向參考)
*
* If the relation is a base relation it will have these fields set:
* 如果關係是一個基本關係,它將設定這些欄位:
* relid - RTE index (this is redundant with the relids field, but
* is provided for convenience of access)
* RTE索引(這對於relids欄位來說是冗餘的,但是為了方便訪問而提供)
* rtekind - copy of RTE's rtekind field
* RTE's rtekind欄位的複製
* min_attr, max_attr - range of valid AttrNumbers for rel
* 關係有效AttrNumbers的範圍(最大/最小編號)
* attr_needed - array of bitmapsets indicating the highest joinrel
* in which each attribute is needed; if bit 0 is set then
* the attribute is needed as part of final targetlist
* 點陣圖集陣列,表示每個屬性所需的最高層joinrel;
* 如果設定為0,則需要將該屬性作為最終targetlist的一部分
* attr_widths - cache space for per-attribute width estimates;
* zero means not computed yet
* 用於每個屬性寬度估計的快取空間;0表示還沒有計算
* lateral_vars - lateral cross-references of rel, if any (list of
* Vars and PlaceHolderVars)
* rel的lateral交叉參照,如果有的話(Vars和PlaceHolderVars連結串列)
* lateral_referencers - relids of rels that reference this one laterally
* (includes both direct and indirect lateral references)
* lateral依賴此關係的relids(包括直接&間接lateral依賴)
* indexlist - list of IndexOptInfo nodes for relation's indexes
* (always NIL if it's not a table)
* 關係索引的IndexOptInfo節點連結串列(如果不是表,總是NIL)
* pages - number of disk pages in relation (zero if not a table)
* 關係的磁碟頁數(如不是表,則為0)
* tuples - number of tuples in relation (not considering restrictions)
* 關係的元組數統計(還沒有考慮約束條件)
* allvisfrac - fraction of disk pages that are marked all-visible
* 標記為all-visible的磁碟頁數比例
* subroot - PlannerInfo for subquery (NULL if it's not a subquery)
* 用於子查詢的PlannerInfo(如果不是子查詢,則為NULL)
* subplan_params - list of PlannerParamItems to be passed to subquery
* 要傳遞給子查詢的PlannerParamItems的連結串列
* Note: for a subquery, tuples and subroot are not set immediately
* upon creation of the RelOptInfo object; they are filled in when
* set_subquery_pathlist processes the object.
* 對於子查詢,元組和subroot不會在建立RelOptInfo物件時立即設定;
* 它們是在set_subquery_pathlist處理物件時填充的。
*
* For otherrels that are appendrel members, these fields are filled
* in just as for a baserel, except we don't bother with lateral_vars.
* 對於其他appendrel成員,這些欄位就像base rels一樣被填充,
* 除了我們不關心的lateral_vars。
*
* If the relation is either a foreign table or a join of foreign tables that
* all belong to the same foreign server and are assigned to the same user to
* check access permissions as (cf checkAsUser), these fields will be set:
* 如果關係是一個外表或一個外表的連線,這些表都屬於相同的外伺服器
* 並被分配給相同的使用者來檢查訪問許可權(cf checkAsUser),這些欄位將被設定:
*
* serverid - OID of foreign server, if foreign table (else InvalidOid)
* 外部伺服器的OID,如不為外部表則為InvalidOid
* userid - OID of user to check access as (InvalidOid means current user)
* 檢查訪問許可權的使用者OID(InvalidOid表示當前使用者)
* useridiscurrent - we've assumed that userid equals current user
* 我們假設userid為當前使用者
* fdwroutine - function hooks for FDW, if foreign table (else NULL)
* FDW的函式鉤子,如不是外部表則為NULL
* fdw_private - private state for FDW, if foreign table (else NULL)
* FDW的私有狀態,不是外部表則為NULL
*
* Two fields are used to cache knowledge acquired during the join search
* about whether this rel is provably unique when being joined to given other
* relation(s), ie, it can have at most one row matching any given row from
* that join relation. Currently we only attempt such proofs, and thus only
* populate these fields, for base rels; but someday they might be used for
* join rels too:
* 下面的兩個欄位用於快取在連線搜尋過程中獲取的資訊,
* 這些資訊是關於當這個rel被連線到給定的其他關係時是否被證明是唯一的,
* 也就是說,它最多隻能有一行匹配來自該連線關係的任何給定行。
* 目前我們只嘗試這樣的證明,因此只填充這些欄位,用於base rels;
* 但總有一天它們也可以被用來加入join rels:
*
* unique_for_rels - list of Relid sets, each one being a set of other
* rels for which this one has been proven unique
* Relid集合的連結串列,每一個都是一組other rels,這些rels已經被證明是唯一的
* non_unique_for_rels - list of Relid sets, each one being a set of
* other rels for which we have tried and failed to prove
* this one unique
* Relid集合的連結串列,每個集合都是other rels,這些rels試圖證明唯一的,但失敗了
*
* The presence of the following fields depends on the restrictions
* and joins that the relation participates in:
* 以下欄位的存在取決於約束條件和關係所參與的連線:
*
* baserestrictinfo - List of RestrictInfo nodes, containing info about
* each non-join qualification clause in which this relation
* participates (only used for base rels)
* RestrictInfo節點連結串列,其中包含關於此關係參與的每個非連線限定子句的資訊(僅用於基礎rels)
* baserestrictcost - Estimated cost of evaluating the baserestrictinfo
* clauses at a single tuple (only used for base rels)
* 在單個元組中解析baserestrictinfo子句的估算成本(僅用於基礎rels)
* baserestrict_min_security - Smallest security_level found among
* clauses in baserestrictinfo
* 在baserestrictinfo子句中找到的最小security_level
* joininfo - List of RestrictInfo nodes, containing info about each
* join clause in which this relation participates (but
* note this excludes clauses that might be derivable from
* EquivalenceClasses)
* RestrictInfo節點連結串列,其中包含關於此關係參與的每個連線條件子句的資訊
* (但請注意,這排除了可能從等價類派生的子句)
* has_eclass_joins - flag that EquivalenceClass joins are possible
* 用於標記等價類連線是可能的
*
* Note: Keeping a restrictinfo list in the RelOptInfo is useful only for
* base rels, because for a join rel the set of clauses that are treated as
* restrict clauses varies depending on which sub-relations we choose to join.
* (For example, in a 3-base-rel join, a clause relating rels 1 and 2 must be
* treated as a restrictclause if we join {1} and {2 3} to make {1 2 3}; but
* if we join {1 2} and {3} then that clause will be a restrictclause in {1 2}
* and should not be processed again at the level of {1 2 3}.) Therefore,
* the restrictinfo list in the join case appears in individual JoinPaths
* (field joinrestrictinfo), not in the parent relation. But it's OK for
* the RelOptInfo to store the joininfo list, because that is the same
* for a given rel no matter how we form it.
* 注意:在RelOptInfo中儲存一個restrictinfo連結串列只對基礎rels有用,
* 因為對於一個join rel,被視為限制子句的子句集會根據我們選擇加入的子關係而變化。(例如,在一個3-base-rel連線中,如果我們加入{1}和{2 3}以生成{1 2 3},則與efs 1和2相關的子句必須被視為限制性子句;但是如果我們加入{1 2}和{3},那麼該子句將是{1 2}中的一個限制性子句,不應該在{1 2 3}的級別上再次處理)。因此,在join案例中,節流資訊列表出現在單獨的連線路徑(欄位join節流資訊)中,而不是在父關係中。但是RelOptInfo可以儲存joininfo列表,因為對於給定的rel,無論我們如何形成它都是一樣的。
*
* We store baserestrictcost in the RelOptInfo (for base relations) because
* we know we will need it at least once (to price the sequential scan)
* and may need it multiple times to price index scans.
* 我們將baserestrictcost儲存在RelOptInfo(用於基本關係)中,
* 因為我們知道至少需要它一次(為順序掃描計算成本),並且可能需要它多次來為索引掃描計算成本。
*
* If the relation is partitioned, these fields will be set:
* 如果關係是分割槽表,會設定這些欄位:
*
* part_scheme - Partitioning scheme of the relation
* 關係的分割槽schema
* nparts - Number of partitions
* 關係的分割槽數
* boundinfo - Partition bounds
* 分割槽邊界資訊
* partition_qual - Partition constraint if not the root
* 如非root,則該欄位儲存分割槽約束條件
* part_rels - RelOptInfos for each partition
* 每個分割槽的RelOptInfos
* partexprs, nullable_partexprs - Partition key expressions
* 分割槽鍵表示式
* partitioned_child_rels - RT indexes of unpruned partitions of
* this relation that are partitioned tables
* themselves, in hierarchical order
* 關係中未修剪(unpruned)分割槽的RT索引,
* 這些分割槽本身就是分割槽表,按層次順序排列
*
* Note: A base relation always has only one set of partition keys, but a join
* relation may have as many sets of partition keys as the number of relations
* being joined. partexprs and nullable_partexprs are arrays containing
* part_scheme->partnatts elements each. Each of these elements is a list of
* partition key expressions. For a base relation each list in partexprs
* contains only one expression and nullable_partexprs is not populated. For a
* join relation, partexprs and nullable_partexprs contain partition key
* expressions from non-nullable and nullable relations resp. Lists at any
* given position in those arrays together contain as many elements as the
* number of joining relations.
* 注意:一個基本關係總是隻有一組分割槽鍵,
* 但是連線關係的分割槽鍵可能與被連線的關係的數量一樣多。
* partexprs和nullable_partexp是分別包含part_scheme->partnatts元素的陣列。
* 每個元素都是分割槽鍵表示式的連結串列。
* 對於基本關係,partexprs中的每個連結串列只包含一個表示式,
* 並且不填充nullable_partexprs。
* 對於連線關係,partexprs和nullable_partexprs包含來自非空和可空關係resp的分割槽鍵表示式。
* 這些陣列中任意給定位置的連結串列包含的元素與連線關係的數量一樣多。
*----------
*/
typedef enum RelOptKind
{
RELOPT_BASEREL,//基本關係(如基表/子查詢等)
RELOPT_JOINREL,//連線產生的關係,要注意的是透過連線等方式產生的結果亦可以視為關係
RELOPT_OTHER_MEMBER_REL,
RELOPT_OTHER_JOINREL,
RELOPT_UPPER_REL,//上層的關係
RELOPT_OTHER_UPPER_REL,
RELOPT_DEADREL
} RelOptKind;
/*
* Is the given relation a simple relation i.e a base or "other" member
* relation?
*/
#define IS_SIMPLE_REL(rel) \
((rel)->reloptkind == RELOPT_BASEREL || \
(rel)->reloptkind == RELOPT_OTHER_MEMBER_REL)
/* Is the given relation a join relation? */
#define IS_JOIN_REL(rel) \
((rel)->reloptkind == RELOPT_JOINREL || \
(rel)->reloptkind == RELOPT_OTHER_JOINREL)
/* Is the given relation an upper relation? */
#define IS_UPPER_REL(rel) \
((rel)->reloptkind == RELOPT_UPPER_REL || \
(rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
/* Is the given relation an "other" relation? */
#define IS_OTHER_REL(rel) \
((rel)->reloptkind == RELOPT_OTHER_MEMBER_REL || \
(rel)->reloptkind == RELOPT_OTHER_JOINREL || \
(rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
typedef struct RelOptInfo
{
//節點標識
NodeTag type;
//RelOpt型別
RelOptKind reloptkind;
/* all relations included in this RelOptInfo */
//所有關係都有的屬性
//Relids(rtindex)集合
Relids relids; /* set of base relids (rangetable indexes) */
/* size estimates generated by planner */
//規劃器生成的大小估算
//結果元組的估算數量
double rows; /* estimated number of result tuples */
/* per-relation planner control flags */
//規劃器使用的每個關係的控制標記
//是否考慮啟動成本?是,需要保留啟動成本低的路徑
bool consider_startup; /* keep cheap-startup-cost paths? */
//是否考慮引數化?的路徑
bool consider_param_startup; /* ditto for parameterized paths? */
//是否考慮並行處理路徑
bool consider_parallel; /* consider parallel paths? */
/* default result targetlist for Paths scanning this relation */
//掃描該Relation時預設的結果投影列
//Vars/Exprs,成本,行平均大小連結串列
struct PathTarget *reltarget; /* list of Vars/Exprs, cost, width */
/* materialization information */
//物化資訊
//訪問路徑連結串列
List *pathlist; /* Path structures */
//路徑連結串列中的ParamPathInfos連結串列
List *ppilist; /* ParamPathInfos used in pathlist */
//並行部分路徑
List *partial_pathlist; /* partial Paths */
//啟動代價最低的路徑
struct Path *cheapest_startup_path;
//整體代價最低的路徑
struct Path *cheapest_total_path;
//獲取唯一值代價最低的路徑
struct Path *cheapest_unique_path;
//引數化代價最低的路徑
List *cheapest_parameterized_paths;
/* parameterization information needed for both base rels and join rels */
/* (see also lateral_vars and lateral_referencers) */
//基本通道和連線通道都需要引數化資訊
//(參考lateral_vars和lateral_referencers)
//使用lateral語法,需依賴的Relids
Relids direct_lateral_relids; /* rels directly laterally referenced */
//rel的最小化引數資訊
Relids lateral_relids; /* minimum parameterization of rel */
/* information about a base rel (not set for join rels!) */
//reloptkind=RELOPT_BASEREL時使用的資料結構
//relid
Index relid; /* Relation ID */
//表空間
Oid reltablespace; /* containing tablespace */
//型別:基表?子查詢?還是函式等等?
RTEKind rtekind; /* RELATION, SUBQUERY, FUNCTION, etc */
//最小的屬性編號
AttrNumber min_attr; /* smallest attrno of rel (often <0) */
//最大的屬性編號
AttrNumber max_attr; /* largest attrno of rel */
//屬性陣列
Relids *attr_needed; /* array indexed [min_attr .. max_attr] */
//屬性寬度
int32 *attr_widths; /* array indexed [min_attr .. max_attr] */
//關係依賴的LATERAL Vars/PHVs
List *lateral_vars; /* LATERAL Vars and PHVs referenced by rel */
//依賴該關係的Relids
Relids lateral_referencers; /* rels that reference me laterally */
//該關係的IndexOptInfo連結串列
List *indexlist; /* list of IndexOptInfo */
//統計資訊連結串列
List *statlist; /* list of StatisticExtInfo */
//塊數
BlockNumber pages; /* size estimates derived from pg_class */
//元組數
double tuples; /* */
//
double allvisfrac; /* ? */
//如為子查詢,儲存子查詢的root
PlannerInfo *subroot; /* if subquery */
//如為子查詢,儲存子查詢的引數
List *subplan_params; /* if subquery */
//並行執行,需要多少個workers?
int rel_parallel_workers; /* wanted number of parallel workers */
/* Information about foreign tables and foreign joins */
//FWD相關資訊
//表或連線的伺服器識別符號
Oid serverid; /* identifies server for the table or join */
//使用者id標識
Oid userid; /* identifies user to check access as */
//對於當前使用者來說,連線才是有效的
bool useridiscurrent; /* join is only valid for current user */
/* use "struct FdwRoutine" to avoid including fdwapi.h here */
//使用結構體FdwRoutine,避免包含標頭檔案fdwapi.h
struct FdwRoutine *fdwroutine;
void *fdw_private;
/* cache space for remembering if we have proven this relation unique */
//如果已證明該關係是唯一的,那麼這些是用於快取這些資訊的欄位
//已知的,可保證唯一的Relids連結串列
List *unique_for_rels; /* known unique for these other relid
* set(s) */
//已知的,不唯一的Relids連結串列
List *non_unique_for_rels; /* known not unique for these set(s) */
/* used by various scans and joins: */
//用於各種掃描和連線
//如為基本關係,儲存約束條件連結串列
List *baserestrictinfo; /* RestrictInfo structures (if base rel) */
//解析約束表示式的成本?
QualCost baserestrictcost; /* cost of evaluating the above */
//最低安全等級
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
//連線語句的約束條件資訊
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
//是否存在等價類連線?
bool has_eclass_joins; /* T means joininfo is incomplete */
/* used by partitionwise joins: */
//partitionwise連線使用的欄位
//是否考慮使用partitionwise join?
bool consider_partitionwise_join; /* consider partitionwise
* join paths? (if
* partitioned rel) */
//最高層的父關係Relids
Relids top_parent_relids; /* Relids of topmost parents (if "other"
* rel) */
/* used for partitioned relations */
//分割槽表使用
//分割槽的schema
PartitionScheme part_scheme; /* Partitioning scheme. */
//分割槽數
int nparts; /* number of partitions */
//分割槽邊界資訊
struct PartitionBoundInfoData *boundinfo; /* Partition bounds */
//分割槽約束
List *partition_qual; /* partition constraint */
//分割槽的RelOptInfo陣列
struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions,
* stored in the same order of bounds */
//非空分割槽鍵表示式連結串列
List **partexprs; /* Non-nullable partition key expressions. */
//可為空的分割槽鍵表示式
List **nullable_partexprs; /* Nullable partition key expressions. */
// 分割槽子表RT Indexes連結串列
List *partitioned_child_rels; /*List of RT indexes. */
} RelOptInfo;
AppendRelInfo
Append-relation資訊.
當我們將可繼承表(分割槽表)或UNION-ALL子查詢展開為“追加關係”(本質上是子RTE的連結串列)時,為每個子RTE構建一個AppendRelInfo。
AppendRelInfos連結串列指示在展開父節點時必須包含哪些子rte,每個節點具有將引用父節點的Vars轉換為引用該子節點的Vars所需的所有資訊。
/*
* Append-relation info.
* Append-relation資訊.
*
* When we expand an inheritable table or a UNION-ALL subselect into an
* "append relation" (essentially, a list of child RTEs), we build an
* AppendRelInfo for each child RTE. The list of AppendRelInfos indicates
* which child RTEs must be included when expanding the parent, and each node
* carries information needed to translate Vars referencing the parent into
* Vars referencing that child.
* 當我們將可繼承表(分割槽表)或UNION-ALL子查詢展開為“追加關係”(本質上是子RTE的連結串列)時,
* 為每個子RTE構建一個AppendRelInfo。
* AppendRelInfos連結串列指示在展開父節點時必須包含哪些子rte,
* 每個節點具有將引用父節點的Vars轉換為引用該子節點的Vars所需的所有資訊。
*
* These structs are kept in the PlannerInfo node's append_rel_list.
* Note that we just throw all the structs into one list, and scan the
* whole list when desiring to expand any one parent. We could have used
* a more complex data structure (eg, one list per parent), but this would
* be harder to update during operations such as pulling up subqueries,
* and not really any easier to scan. Considering that typical queries
* will not have many different append parents, it doesn't seem worthwhile
* to complicate things.
* 這些結構體儲存在PlannerInfo節點的append_rel_list中。
* 注意,只是將所有的結構體放入一個連結串列中,並在希望展開任何父類時掃描整個連結串列。
* 本可以使用更復雜的資料結構(例如,每個父節點一個列表),
* 但是在提取子查詢之類的操作中更新它會更困難,
* 而且實際上也不會更容易掃描。
* 考慮到典型的查詢不會有很多不同的附加項,因此似乎不值得將事情複雜化。
*
* Note: after completion of the planner prep phase, any given RTE is an
* append parent having entries in append_rel_list if and only if its
* "inh" flag is set. We clear "inh" for plain tables that turn out not
* to have inheritance children, and (in an abuse of the original meaning
* of the flag) we set "inh" for subquery RTEs that turn out to be
* flattenable UNION ALL queries. This lets us avoid useless searches
* of append_rel_list.
* 注意:計劃準備階段完成後,
* 當且僅當它的“inh”標誌已設定時,給定的RTE是一個append parent在append_rel_list中的一個條目。
* 我們為沒有child的平面表清除“inh”標記,
* 同時(有濫用標記的嫌疑)為UNION ALL查詢中的子查詢RTEs設定“inh”標記。
* 這樣可以避免對append_rel_list進行無用的搜尋。
*
* Note: the data structure assumes that append-rel members are single
* baserels. This is OK for inheritance, but it prevents us from pulling
* up a UNION ALL member subquery if it contains a join. While that could
* be fixed with a more complex data structure, at present there's not much
* point because no improvement in the plan could result.
* 注意:資料結構假定附加的rel成員是獨立的baserels。
* 這對於繼承來說是可以的,但是如果UNION ALL member子查詢包含一個join,
* 那麼它將阻止我們提取UNION ALL member子查詢。
* 雖然可以用更復雜的資料結構解決這個問題,但目前沒有太大意義,因為該計劃可能不會有任何改進。
*/
typedef struct AppendRelInfo
{
NodeTag type;
/*
* These fields uniquely identify this append relationship. There can be
* (in fact, always should be) multiple AppendRelInfos for the same
* parent_relid, but never more than one per child_relid, since a given
* RTE cannot be a child of more than one append parent.
* 這些欄位惟一地標識這個append relationship。
* 對於同一個parent_relid可以有(實際上應該總是)多個AppendRelInfos,
* 但是每個child_relid不能有多個AppendRelInfos,
* 因為給定的RTE不能是多個append parent的子節點。
*/
Index parent_relid; /* parent rel的RT索引;RT index of append parent rel */
Index child_relid; /* child rel的RT索引;RT index of append child rel */
/*
* For an inheritance appendrel, the parent and child are both regular
* relations, and we store their rowtype OIDs here for use in translating
* whole-row Vars. For a UNION-ALL appendrel, the parent and child are
* both subqueries with no named rowtype, and we store InvalidOid here.
* 對於繼承appendrel,父類和子類都是普通關係,
* 我們將它們的rowtype OIDs儲存在這裡,用於轉換whole-row Vars。
* 對於UNION-ALL appendrel,父查詢和子查詢都是沒有指定行型別的子查詢,
* 我們在這裡儲存InvalidOid。
*/
Oid parent_reltype; /* OID of parent's composite type */
Oid child_reltype; /* OID of child's composite type */
/*
* The N'th element of this list is a Var or expression representing the
* child column corresponding to the N'th column of the parent. This is
* used to translate Vars referencing the parent rel into references to
* the child. A list element is NULL if it corresponds to a dropped
* column of the parent (this is only possible for inheritance cases, not
* UNION ALL). The list elements are always simple Vars for inheritance
* cases, but can be arbitrary expressions in UNION ALL cases.
* 這個列表的第N個元素是一個Var或表示式,表示與父元素的第N列對應的子列。
* 這用於將引用parent rel的Vars轉換為對子rel的引用。
* 如果連結串列元素與父元素的已刪除列相對應,則該元素為NULL
* (這隻適用於繼承情況,而不是UNION ALL)。
* 對於繼承情況,連結串列元素總是簡單的變數,但是可以是UNION ALL情況下的任意表示式。
*
* Notice we only store entries for user columns (attno > 0). Whole-row
* Vars are special-cased, and system columns (attno < 0) need no special
* translation since their attnos are the same for all tables.
* 注意,我們只儲存使用者列的條目(attno > 0)。
* Whole-row Vars是大小寫敏感的,系統列(attno < 0)不需要特別的轉換,
* 因為它們的attno對所有表都是相同的。
*
* Caution: the Vars have varlevelsup = 0. Be careful to adjust as needed
* when copying into a subquery.
* 注意:Vars的varlevelsup = 0。
* 在將資料複製到子查詢時,要注意根據需要進行調整。
*/
//child's Vars中的表示式
List *translated_vars; /* Expressions in the child's Vars */
/*
* We store the parent table's OID here for inheritance, or InvalidOid for
* UNION ALL. This is only needed to help in generating error messages if
* an attempt is made to reference a dropped parent column.
* 我們將父表的OID儲存在這裡用於繼承,
* 如為UNION ALL,則這裡儲存的是InvalidOid。
* 只有在試圖引用已刪除的父列時,才需要這樣做來幫助生成錯誤訊息。
*/
Oid parent_reloid; /* OID of parent relation */
} AppendRelInfo;
PlannerInfo
該資料結構用於儲存查詢語句在規劃/最佳化過程中的相關資訊
/*----------
* PlannerInfo
* Per-query information for planning/optimization
* 用於規劃/最佳化的每個查詢資訊
*
* This struct is conventionally called "root" in all the planner routines.
* It holds links to all of the planner's working state, in addition to the
* original Query. Note that at present the planner extensively modifies
* the passed-in Query data structure; someday that should stop.
* 在所有計劃程式例程中,這個結構通常稱為“root”。
* 除了原始查詢之外,它還儲存到所有計劃器工作狀態的連結。
* 注意,目前計劃器會毫無節制的修改傳入的查詢資料結構,相信總有一天這種情況會停止的。
*----------
*/
struct AppendRelInfo;
typedef struct PlannerInfo
{
NodeTag type;//Node標識
//查詢樹
Query *parse; /* the Query being planned */
//當前的planner全域性資訊
PlannerGlobal *glob; /* global info for current planner run */
//查詢層次,1標識最高層
Index query_level; /* 1 at the outermost Query */
// 如為子計劃,則這裡儲存父計劃器指標,NULL標識最高層
struct PlannerInfo *parent_root; /* NULL at outermost Query */
/*
* plan_params contains the expressions that this query level needs to
* make available to a lower query level that is currently being planned.
* outer_params contains the paramIds of PARAM_EXEC Params that outer
* query levels will make available to this query level.
* plan_params包含該查詢級別需要提供給當前計劃的較低查詢級別的表示式。
* outer_params包含PARAM_EXEC Params的引數,外部查詢級別將使該查詢級別可用這些引數。
*/
List *plan_params; /* list of PlannerParamItems, see below */
Bitmapset *outer_params;
/*
* simple_rel_array holds pointers to "base rels" and "other rels" (see
* comments for RelOptInfo for more info). It is indexed by rangetable
* index (so entry 0 is always wasted). Entries can be NULL when an RTE
* does not correspond to a base relation, such as a join RTE or an
* unreferenced view RTE; or if the RelOptInfo hasn't been made yet.
* simple_rel_array儲存指向“base rels”和“other rels”的指標
* (有關RelOptInfo的更多資訊,請參見注釋)。
* 它由可範圍表索引建立索引(因此條目0總是被浪費)。
* 當RTE與基本關係(如JOIN RTE或未被引用的檢視RTE時)不相對應
* 或者如果RelOptInfo還沒有生成,條目可以為NULL。
*/
//RelOptInfo陣列,儲存"base rels",比如基表/子查詢等.
//該陣列與RTE的順序一一對應,而且是從1開始,因此[0]無用 */
struct RelOptInfo **simple_rel_array; /* All 1-rel RelOptInfos */
int simple_rel_array_size; /* 陣列大小,allocated size of array */
/*
* simple_rte_array is the same length as simple_rel_array and holds
* pointers to the associated rangetable entries. This lets us avoid
* rt_fetch(), which can be a bit slow once large inheritance sets have
* been expanded.
* simple_rte_array的長度與simple_rel_array相同,
* 並儲存指向相應範圍表條目的指標。
* 這使我們可以避免執行rt_fetch(),因為一旦擴充套件了大型繼承集,rt_fetch()可能會有點慢。
*/
//RTE陣列
RangeTblEntry **simple_rte_array; /* rangetable as an array */
/*
* append_rel_array is the same length as the above arrays, and holds
* pointers to the corresponding AppendRelInfo entry indexed by
* child_relid, or NULL if none. The array itself is not allocated if
* append_rel_list is empty.
* append_rel_array與上述陣列的長度相同,
* 並儲存指向對應的AppendRelInfo條目的指標,該條目由child_relid索引,
* 如果沒有索引則為NULL。
* 如果append_rel_list為空,則不分配陣列本身。
*/
//處理集合操作如UNION ALL時使用和分割槽表時使用
struct AppendRelInfo **append_rel_array;
/*
* all_baserels is a Relids set of all base relids (but not "other"
* relids) in the query; that is, the Relids identifier of the final join
* we need to form. This is computed in make_one_rel, just before we
* start making Paths.
* all_baserels是查詢中所有base relids(但不是“other” relids)的一個Relids集合;
* 也就是說,這是需要形成的最終連線的Relids識別符號。
* 這是在開始建立路徑之前在make_one_rel中計算的。
*/
Relids all_baserels;//"base rels"
/*
* nullable_baserels is a Relids set of base relids that are nullable by
* some outer join in the jointree; these are rels that are potentially
* nullable below the WHERE clause, SELECT targetlist, etc. This is
* computed in deconstruct_jointree.
* nullable_baserels是由jointree中的某些外連線中值可為空的base Relids集合;
* 這些是在WHERE子句、SELECT targetlist等下面可能為空的樹。
* 這是在deconstruct_jointree中處理獲得的。
*/
//Nullable-side端的"base rels"
Relids nullable_baserels;
/*
* join_rel_list is a list of all join-relation RelOptInfos we have
* considered in this planning run. For small problems we just scan the
* list to do lookups, but when there are many join relations we build a
* hash table for faster lookups. The hash table is present and valid
* when join_rel_hash is not NULL. Note that we still maintain the list
* even when using the hash table for lookups; this simplifies life for
* GEQO.
* join_rel_list是在計劃執行中考慮的所有連線關係RelOptInfos的連結串列。
* 對於小問題,只需要掃描連結串列執行查詢,但是當存在許多連線關係時,
* 需要構建一個雜湊表來進行更快的查詢。
* 當join_rel_hash不為空時,雜湊表是有效可用於查詢的。
* 注意,即使在使用雜湊表進行查詢時,仍然維護該連結串列;這簡化了GEQO(遺傳演算法)的生命週期。
*/
//參與連線的Relation的RelOptInfo連結串列
List *join_rel_list; /* list of join-relation RelOptInfos */
//可加快連結串列訪問的hash表
struct HTAB *join_rel_hash; /* optional hashtable for join relations */
/*
* When doing a dynamic-programming-style join search, join_rel_level[k]
* is a list of all join-relation RelOptInfos of level k, and
* join_cur_level is the current level. New join-relation RelOptInfos are
* automatically added to the join_rel_level[join_cur_level] list.
* join_rel_level is NULL if not in use.
* 在執行動態規劃演算法的連線搜尋時,join_rel_level[k]是k級的所有連線關係RelOptInfos的列表,
* join_cur_level是當前級別。
* 新的連線關係RelOptInfos會自動新增到join_rel_level[join_cur_level]連結串列中。
* 如果不使用join_rel_level,則為NULL。
*/
//RelOptInfo指標連結串列陣列,k層的join儲存在[k]中
List **join_rel_level; /* lists of join-relation RelOptInfos */
//當前的join層次
int join_cur_level; /* index of list being extended */
//查詢的初始化計劃連結串列
List *init_plans; /* init SubPlans for query */
//CTE子計劃ID連結串列
List *cte_plan_ids; /* per-CTE-item list of subplan IDs */
//MULTIEXPR子查詢輸出的引數連結串列的連結串列
List *multiexpr_params; /* List of Lists of Params for MULTIEXPR
* subquery outputs */
//活動的等價類連結串列
List *eq_classes; /* list of active EquivalenceClasses */
//規範化的PathKey連結串列
List *canon_pathkeys; /* list of "canonical" PathKeys */
//外連線約束條件連結串列(左)
List *left_join_clauses; /* list of RestrictInfos for mergejoinable
* outer join clauses w/nonnullable var on
* left */
//外連線約束條件連結串列(右)
List *right_join_clauses; /* list of RestrictInfos for mergejoinable
* outer join clauses w/nonnullable var on
* right */
//全連線約束條件連結串列
List *full_join_clauses; /* list of RestrictInfos for mergejoinable
* full join clauses */
//特殊連線資訊連結串列
List *join_info_list; /* list of SpecialJoinInfos */
//AppendRelInfo連結串列
List *append_rel_list; /* list of AppendRelInfos */
//PlanRowMarks連結串列
List *rowMarks; /* list of PlanRowMarks */
//PHI連結串列
List *placeholder_list; /* list of PlaceHolderInfos */
// 外來鍵資訊連結串列
List *fkey_list; /* list of ForeignKeyOptInfos */
//query_planner()要求的PathKeys連結串列
List *query_pathkeys; /* desired pathkeys for query_planner() */
//分組子句路徑鍵
List *group_pathkeys; /* groupClause pathkeys, if any */
//視窗函式路徑鍵
List *window_pathkeys; /* pathkeys of bottom window, if any */
//distinctClause路徑鍵
List *distinct_pathkeys; /* distinctClause pathkeys, if any */
//排序路徑鍵
List *sort_pathkeys; /* sortClause pathkeys, if any */
//已規範化的分割槽Schema
List *part_schemes; /* Canonicalised partition schemes used in the
* query. */
//嘗試連線的RelOptInfo連結串列
List *initial_rels; /* RelOptInfos we are now trying to join */
/* Use fetch_upper_rel() to get any particular upper rel */
//上層的RelOptInfo連結串列
List *upper_rels[UPPERREL_FINAL + 1]; /* upper-rel RelOptInfos */
/* Result tlists chosen by grouping_planner for upper-stage processing */
//grouping_planner為上層處理選擇的結果tlists
struct PathTarget *upper_targets[UPPERREL_FINAL + 1];//
/*
* grouping_planner passes back its final processed targetlist here, for
* use in relabeling the topmost tlist of the finished Plan.
* grouping_planner在這裡傳回它最終處理過的targetlist,用於重新標記已完成計劃的最頂層tlist。
*/
////最後需處理的投影列
List *processed_tlist;
/* Fields filled during create_plan() for use in setrefs.c */
//setrefs.c中在create_plan()函式呼叫期間填充的欄位
//分組函式屬性對映
AttrNumber *grouping_map; /* for GroupingFunc fixup */
//MinMaxAggInfos連結串列
List *minmax_aggs; /* List of MinMaxAggInfos */
//記憶體上下文
MemoryContext planner_cxt; /* context holding PlannerInfo */
//關係的page計數
double total_table_pages; /* # of pages in all tables of query */
//query_planner輸入引數:元組處理比例
double tuple_fraction; /* tuple_fraction passed to query_planner */
//query_planner輸入引數:limit_tuple
double limit_tuples; /* limit_tuples passed to query_planner */
//表示式的最小安全等級
Index qual_security_level; /* minimum security_level for quals */
/* Note: qual_security_level is zero if there are no securityQuals */
//注意:如果沒有securityQuals, 則qual_security_level是NULL(0)
//如目標relation是分割槽表的child/partition/分割槽表,則透過此欄位標記
InheritanceKind inhTargetKind; /* indicates if the target relation is an
* inheritance child or partition or a
* partitioned table */
//是否存在RTE_JOIN的RTE
bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */
//是否存在標記為LATERAL的RTE
bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */
//是否存在已在jointree刪除的RTE
bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */
//是否存在Having子句
bool hasHavingQual; /* true if havingQual was non-null */
//如約束條件中存在pseudoconstant = true,則此欄位為T
bool hasPseudoConstantQuals; /* true if any RestrictInfo has
* pseudoconstant = true */
//是否存在遞迴語句
bool hasRecursion; /* true if planning a recursive WITH item */
/* These fields are used only when hasRecursion is true: */
//這些欄位僅在hasRecursion為T時使用:
//工作表的PARAM_EXEC ID
int wt_param_id; /* PARAM_EXEC ID for the work table */
//非遞迴模式的訪問路徑
struct Path *non_recursive_path; /* a path for non-recursive term */
/* These fields are workspace for createplan.c */
//這些欄位用於createplan.c
//當前節點之上的外部rels
Relids curOuterRels; /* outer rels above current node */
//未賦值的NestLoopParams引數
List *curOuterParams; /* not-yet-assigned NestLoopParams */
/* optional private data for join_search_hook, e.g., GEQO */
//可選的join_search_hook私有資料,例如GEQO
void *join_search_private;
/* Does this query modify any partition key columns? */
//該查詢是否更新分割槽鍵列?
bool partColsUpdated;
} PlannerInfo;
二、參考資料
Parallel Append implementation
Partition Elimination in PostgreSQL 11
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2374791/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PostgreSQL 原始碼解讀(98)- 分割槽表#4(資料查詢路由#1-“擴充套件”分割槽表)SQL原始碼路由套件
- PostgreSQL 原始碼解讀(100)- 分割槽表#6(資料查詢路由#3-prune part...SQL原始碼路由
- PostgreSQL 原始碼解讀(101)- 分割槽表#7(資料查詢路由#4-prune part...SQL原始碼路由
- PostgreSQL 原始碼解讀(102)- 分割槽表#8(資料查詢路由#5-構建APPEND訪問路徑)SQL原始碼路由APP
- PostgreSQL 原始碼解讀(92)- 分割槽表#1(資料插入路由#1)SQL原始碼路由
- PostgreSQL 原始碼解讀(94)- 分割槽表#2(資料插入路由#2)SQL原始碼路由
- PostgreSQL 原始碼解讀(103)- 分割槽表#9(資料查詢路由#6-APPEND初始化和實現)SQL原始碼路由APP
- PostgreSQL 原始碼解讀(96)- 分割槽表#3(資料插入路由#3-獲取分割槽鍵值)SQL原始碼路由
- PostgreSQL 原始碼解讀(183)- 查詢#99(聚合函式#4-ExecAgg)SQL原始碼函式
- PostgreSQL 原始碼解讀(234)- 查詢#127(NOT IN實現#5)SQL原始碼
- PostgreSQL 原始碼解讀(197)- 查詢#112(排序#5 - mergeruns)SQL原始碼排序
- PostgreSQL 原始碼解讀(20)- 查詢語句#5(查詢樹Query詳解)SQL原始碼
- PostgreSQL 原始碼解讀(205)- 查詢#118(資料結構RangeTblEntry)SQL原始碼資料結構
- Oracle查詢Interval partition分割槽表內資料Oracle
- PostgreSQL 原始碼解讀(206)- 查詢#119(資料結構RangSubselect等)SQL原始碼資料結構
- PostgreSQL 原始碼解讀(204)- 查詢#117(資料結構SelectStmt&Value)SQL原始碼資料結構
- PostgreSQL 原始碼解讀(207)- 查詢#120(資料結構FromExpr&JoinExpr)SQL原始碼資料結構
- PostgreSQL 原始碼解讀(184)- 查詢#100(聚合函式#5-simplehash)SQL原始碼函式
- PostgreSQL 原始碼解讀(230)- 查詢#123(NOT IN實現)SQL原始碼
- PostgreSQL 原始碼解讀(5)- 插入資料#4(ExecInsert)SQL原始碼
- PostgreSQL 原始碼解讀(6)- 插入資料#5(ExecModifyTable)SQL原始碼
- PostgreSQL 原始碼解讀(24)- 查詢語句#9(查詢重寫)SQL原始碼
- PostgreSQL/LightDB 分割槽表之分割槽裁剪SQL
- PostgreSQL 原始碼解讀(164)- 查詢#84(表示式求值)SQL原始碼
- PostgreSQL 原始碼解讀(215)- 查詢#122(varstrfastcmp_locale)SQL原始碼AST
- PostgreSQL 原始碼解讀(231)- 查詢#124(NOT IN實現#2)SQL原始碼
- PostgreSQL 原始碼解讀(233)- 查詢#126(NOT IN實現#4)SQL原始碼
- PostgreSQL 原始碼解讀(232)- 查詢#125(NOT IN實現#3)SQL原始碼
- PostgreSQL 原始碼解讀(193)- 查詢#109(排序#2 - ExecSort)SQL原始碼排序
- PostgreSQL 原始碼解讀(192)- 查詢#108(排序#1 - ExecInitSort)SQL原始碼排序
- PostgreSQL 原始碼解讀(198)- 查詢#113(排序#6 - Tuplesortstate)SQL原始碼排序
- PostgreSQL 原始碼解讀(97)- 查詢語句#79(ExecHashJoin函式#5-H...SQL原始碼函式
- PostgreSQL 原始碼解讀(43)- 查詢語句#28(query_planner函式#5)SQL原始碼函式
- PostgreSQL 原始碼解讀(19)- 查詢語句#4(ParseTree詳解)SQL原始碼
- PostgreSQL 原始碼解讀(17)- 查詢語句#2(查詢優化基礎)SQL原始碼優化
- PostgreSQL 原始碼解讀(25)- 查詢語句#10(查詢優化概覽)SQL原始碼優化
- PostgreSQL 原始碼解讀(29)- 查詢語句#14(查詢優化-上拉子查詢)SQL原始碼優化
- PostgreSQL 原始碼解讀(18)- 查詢語句#3(SQL Parse)SQL原始碼