forked from Cachuela/openGauss-server
Compare commits
126 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
47c60e734b | |
|
|
a29e320ec9 | |
|
|
dfb70012c8 | |
|
|
68c47dab3f | |
|
|
0b22bdc6a1 | |
|
|
8725ebefda | |
|
|
594d1eac84 | |
|
|
038ff1185a | |
|
|
1586fe56b4 | |
|
|
dc653b252b | |
|
|
3d69ac3ea8 | |
|
|
b9d2bca37c | |
|
|
ae60a2b832 | |
|
|
15fcbf46f3 | |
|
|
0434074e45 | |
|
|
3c36671432 | |
|
|
baffef91da | |
|
|
bff76fdac2 | |
|
|
bc31ad550b | |
|
|
3510614f6f | |
|
|
4ad2d6e31c | |
|
|
8d51f91c07 | |
|
|
a05a2209e9 | |
|
|
2f7b85b382 | |
|
|
647ac11bc6 | |
|
|
87a70b286a | |
|
|
5dbc328cc9 | |
|
|
06241dcceb | |
|
|
8e75665744 | |
|
|
181eff6471 | |
|
|
70496f2dcc | |
|
|
b01ae798e4 | |
|
|
24604252dd | |
|
|
8d2917e680 | |
|
|
ea99422c9c | |
|
|
5b055b3cd2 | |
|
|
fc5cdc7aa6 | |
|
|
511eda7371 | |
|
|
d03d910cf9 | |
|
|
5445b2c69b | |
|
|
850da86695 | |
|
|
541157ecf2 | |
|
|
2133f5fc9c | |
|
|
1f483651e0 | |
|
|
5722c3940a | |
|
|
922fc62878 | |
|
|
864c14e309 | |
|
|
4a70a794cd | |
|
|
ab615844e1 | |
|
|
7e869b54b9 | |
|
|
dcaed27c9a | |
|
|
c436a7dbc3 | |
|
|
0f180510a5 | |
|
|
c16abbdbd0 | |
|
|
8478405041 | |
|
|
21fb964bad | |
|
|
e125e11481 | |
|
|
44b112e164 | |
|
|
650de14ed7 | |
|
|
5193cc7bf8 | |
|
|
e7f3226cf7 | |
|
|
e71710b1f8 | |
|
|
f16b475637 | |
|
|
a9a55e9031 | |
|
|
1cfc752cd2 | |
|
|
d126d16538 | |
|
|
f73fad8bd5 | |
|
|
d4ae513702 | |
|
|
15d0f1c8af | |
|
|
afd463de16 | |
|
|
8a2b4a2362 | |
|
|
6e2cd8de5a | |
|
|
38e3d3c9e9 | |
|
|
e5253dde55 | |
|
|
6a93e1b4d7 | |
|
|
89c85f7c0f | |
|
|
58e1716952 | |
|
|
8019c659ed | |
|
|
f272b559b1 | |
|
|
accfd156a5 | |
|
|
4ccda82f0c | |
|
|
8231771602 | |
|
|
e85199285a | |
|
|
417fa91ed8 | |
|
|
a7cabdfdab | |
|
|
ae66ff5593 | |
|
|
1146cf5da7 | |
|
|
b08c392980 | |
|
|
7953840a56 | |
|
|
62f8c2e899 | |
|
|
4519fe165e | |
|
|
2fdb911b9a | |
|
|
352d38eaf6 | |
|
|
a653b1fbe0 | |
|
|
83c7549366 | |
|
|
6740d25f0a | |
|
|
2edacdcbb3 | |
|
|
1eea40e786 | |
|
|
3376c8bb21 | |
|
|
332fbf113f | |
|
|
c4a23cca76 | |
|
|
8126eba498 | |
|
|
98eb7c4433 | |
|
|
d199135909 | |
|
|
3313f38d43 | |
|
|
f39e50c72c | |
|
|
f4b4ba72f2 | |
|
|
10e14f95db | |
|
|
ae9383e4a8 | |
|
|
ca0ca5a7b0 | |
|
|
72bc09be29 | |
|
|
77b21465dd | |
|
|
c19527724c | |
|
|
d6f3f620ee | |
|
|
3ec0d8c554 | |
|
|
a8fd03b2af | |
|
|
558e4d082c | |
|
|
802a928723 | |
|
|
6cf5ecbfeb | |
|
|
e34d970937 | |
|
|
c2415f2fe6 | |
|
|
4733646063 | |
|
|
35974ee066 | |
|
|
7309896381 | |
|
|
e3cff195d8 | |
|
|
cffc278146 |
|
|
@ -49,34 +49,35 @@
|
|||
* is specified by a BASETYPE element in the parameters. Otherwise,
|
||||
* "args" defines the input type(s).
|
||||
*/
|
||||
// 定义聚合函数
|
||||
void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters)
|
||||
{
|
||||
char* aggName = NULL;
|
||||
Oid aggNamespace;
|
||||
char* aggName = NULL;// 聚合函数的名称
|
||||
Oid aggNamespace;// 聚合函数所在的命名空间
|
||||
AclResult aclresult;
|
||||
List* transfuncName = NIL;
|
||||
List* finalfuncName = NIL;
|
||||
List* sortoperatorName = NIL;
|
||||
TypeName* baseType = NULL;
|
||||
TypeName* transType = NULL;
|
||||
char* initval = NULL;
|
||||
List* transfuncName = NIL;// 过渡函数的名称
|
||||
List* finalfuncName = NIL;// 最终函数的名称
|
||||
List* sortoperatorName = NIL;// 排序操作符的名称
|
||||
TypeName* baseType = NULL;// 输入类型的名称
|
||||
TypeName* transType = NULL;// 过渡类型的名称
|
||||
char* initval = NULL; // 初始化值
|
||||
#ifdef PGXC
|
||||
List* collectfuncName = NIL;
|
||||
char* initcollect = NULL;
|
||||
List* collectfuncName = NIL;// 收集函数的名称
|
||||
char* initcollect = NULL;// 初始化收集值
|
||||
#endif
|
||||
Oid* aggArgTypes = NULL;
|
||||
Oid* aggArgTypes = NULL;// 聚合函数的参数类型列表
|
||||
int numArgs;
|
||||
Oid transTypeId;
|
||||
Oid transTypeId; // 过渡类型的OID
|
||||
ListCell* pl = NULL;
|
||||
|
||||
/* attribute for ordered set aggregate */
|
||||
char aggKind = AGGKIND_NORMAL;
|
||||
char aggKind = AGGKIND_NORMAL; // 聚合函数的类型,默认为普通聚合函数
|
||||
|
||||
/* Convert list of names to a name and namespace */
|
||||
aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);
|
||||
aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName);// 从名称获取聚合函数所在的命名空间和名称
|
||||
|
||||
/* Check we have creation rights in target namespace */
|
||||
aclresult = pg_namespace_aclcheck(aggNamespace, GetUserId(), ACL_CREATE);
|
||||
aclresult = pg_namespace_aclcheck(aggNamespace, GetUserId(), ACL_CREATE);// 检查当前用户是否有在命名空间下创建聚合函数的权限
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(aggNamespace));
|
||||
if (u_sess->attr.attr_sql.enforce_a_behavior) {
|
||||
|
|
@ -99,7 +100,7 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters)
|
|||
aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(aggNamespace));
|
||||
}
|
||||
}
|
||||
foreach (pl, parameters) {
|
||||
foreach (pl, parameters) {// 遍历参数列表,解析聚合函数的各个属性
|
||||
DefElem* defel = (DefElem*)lfirst(pl);
|
||||
|
||||
/*
|
||||
|
|
@ -138,15 +139,15 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters)
|
|||
/*
|
||||
* make sure we have our required definitions
|
||||
*/
|
||||
if (transType == NULL)
|
||||
if (transType == NULL)// 检查过渡类型是否已经指定
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate stype must be specified")));
|
||||
if (transfuncName == NIL)
|
||||
if (transfuncName == NIL)// 检查过渡函数是否已经指定
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate sfunc must be specified")));
|
||||
|
||||
/*
|
||||
* look up the aggregate's input datatype(s).
|
||||
*/
|
||||
if (oldstyle) {
|
||||
if (oldstyle) {// 如果使用旧式语法定义聚合函数
|
||||
/*
|
||||
* Old style: use basetype parameter. This supports aggregates of
|
||||
* zero or one input, with input type ANY meaning zero inputs.
|
||||
|
|
@ -205,7 +206,9 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters)
|
|||
* worse) by connecting up incompatible internal-using functions in an
|
||||
* aggregate.
|
||||
*/
|
||||
// 解析过渡类型的OID
|
||||
transTypeId = typenameTypeId(NULL, transType);
|
||||
// 检查过渡类型是否为伪类型,且不是多态类型
|
||||
if (get_typtype(transTypeId) == TYPTYPE_PSEUDO && !IsPolymorphicType(transTypeId)
|
||||
&& (transTypeId != INTERNALOID || !superuser())) {
|
||||
ereport(ERROR,
|
||||
|
|
@ -235,9 +238,9 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters)
|
|||
#endif
|
||||
}
|
||||
|
||||
void RenameAggregate(List* name, List* args, const char* newname)
|
||||
void RenameAggregate(List* name, List* args, const char* newname)// 重命名聚合函数
|
||||
{
|
||||
Oid procOid;
|
||||
Oid procOid;// 聚合函数的OID
|
||||
Oid namespaceOid;
|
||||
HeapTuple tup;
|
||||
Form_pg_proc procForm;
|
||||
|
|
@ -247,9 +250,9 @@ void RenameAggregate(List* name, List* args, const char* newname)
|
|||
rel = heap_open(ProcedureRelationId, RowExclusiveLock);
|
||||
|
||||
/* Look up function and make sure it's an aggregate */
|
||||
procOid = LookupAggNameTypeNames(name, args, false);
|
||||
procOid = LookupAggNameTypeNames(name, args, false);// 获取聚合函数的OID
|
||||
|
||||
tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(procOid));
|
||||
tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(procOid));// 在系统表中查找聚合函数的元组
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", procOid)));
|
||||
procForm = (Form_pg_proc)GETSTRUCT(tup);
|
||||
|
|
@ -261,7 +264,7 @@ void RenameAggregate(List* name, List* args, const char* newname)
|
|||
if (isNull) {
|
||||
packageoid = DatumGetObjectId(InvalidOid);
|
||||
}
|
||||
|
||||
// 检查新名称是否与已存在的函数名称冲突
|
||||
#ifndef ENABLE_MULTIPLE_NODES
|
||||
Datum allargtypes = ProcedureGetAllArgTypes(tup, &isNull);
|
||||
Datum argmodes = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proargmodes, &isNull);
|
||||
|
|
@ -289,16 +292,16 @@ void RenameAggregate(List* name, List* args, const char* newname)
|
|||
get_namespace_name(namespaceOid))));
|
||||
#endif
|
||||
/* must be owner */
|
||||
if (!pg_proc_ownercheck(procOid, GetUserId()))
|
||||
if (!pg_proc_ownercheck(procOid, GetUserId()))// 检查当前用户是否是函数的所有者
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(name));
|
||||
|
||||
/* must have CREATE privilege on namespace */
|
||||
aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
|
||||
aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);// 检查命名空间的权限
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid));
|
||||
|
||||
/* rename */
|
||||
(void)namestrcpy(&(((Form_pg_proc)GETSTRUCT(tup))->proname), newname);
|
||||
(void)namestrcpy(&(((Form_pg_proc)GETSTRUCT(tup))->proname), newname);// 更新函数名称并更新系统缓存
|
||||
simple_heap_update(rel, &tup->t_self, tup);
|
||||
CatalogUpdateIndexes(rel, tup);
|
||||
|
||||
|
|
@ -309,7 +312,7 @@ void RenameAggregate(List* name, List* args, const char* newname)
|
|||
/*
|
||||
* Change aggregate owner
|
||||
*/
|
||||
void AlterAggregateOwner(List* name, List* args, Oid newOwnerId)
|
||||
void AlterAggregateOwner(List* name, List* args, Oid newOwnerId)// 修改聚合函数的所有者
|
||||
{
|
||||
Oid procOid;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ void geqo_copy(PlannerInfo* root, Chromosome* chromo1, Chromosome* chromo2, int
|
|||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < string_length; i++)
|
||||
for (i = 0; i < string_length; i++)// 使用循环遍历染色体的字符串,将chromo2的内容复制到chromo1中
|
||||
chromo1->string[i] = chromo2->string[i];
|
||||
|
||||
chromo1->worth = chromo2->worth;
|
||||
chromo1->worth = chromo2->worth;// 复制chromo2的worth值到chromo1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,17 +41,19 @@
|
|||
*
|
||||
* cycle crossover
|
||||
*/
|
||||
//这段代码实现了遗传算法中的一种交叉操作(Crossover),用于生成新的候选解。
|
||||
//函数的目标是从两个不同的父代查询计划,生成一个新的子代查询计划
|
||||
int cx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_gene, City* city_table)
|
||||
{
|
||||
int i, start_pos, curr_pos;
|
||||
int count = 0;
|
||||
int num_diffs = 0;
|
||||
int num_diffs = 0;
|
||||
|
||||
/* initialize city table */
|
||||
for (i = 1; i <= num_gene; i++) {
|
||||
city_table[i].used = 0;
|
||||
city_table[tour2[i - 1]].tour2_position = i - 1;
|
||||
city_table[tour1[i - 1]].tour1_position = i - 1;
|
||||
city_table[i].used = 0;// 将节点表标记为未使用
|
||||
city_table[tour2[i - 1]].tour2_position = i - 1;// 记录tour2中每个基因在数组中的位置
|
||||
city_table[tour1[i - 1]].tour1_position = i - 1;// 记录tour1中每个基因在数组中的位置
|
||||
}
|
||||
|
||||
/* choose random cycle starting position */
|
||||
|
|
@ -69,10 +71,11 @@ int cx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_gen
|
|||
/* cx main part */
|
||||
/* STEP 1 */
|
||||
while (tour2[curr_pos] != tour1[start_pos]) {
|
||||
city_table[(int)tour2[curr_pos]].used = 1;
|
||||
curr_pos = city_table[(int)tour2[curr_pos]].tour1_position;
|
||||
offspring[curr_pos] = tour1[curr_pos];
|
||||
count++;
|
||||
city_table[(int)tour2[curr_pos]].used = 1;// 标记tour2中的节点为已使用
|
||||
curr_pos = city_table[(int)tour2[curr_pos]].tour1_position;// 切换到tour1中相应节点的位置
|
||||
offspring[curr_pos] = tour1[curr_pos];// 将对应的tour1节点添加到offspring中
|
||||
|
||||
count++;// 增加已添加节点的计数
|
||||
}
|
||||
|
||||
/* STEP 2 */
|
||||
|
|
@ -81,7 +84,7 @@ int cx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_gen
|
|||
for (i = 1; i <= num_gene; i++) {
|
||||
if (!city_table[i].used) {
|
||||
offspring[city_table[i].tour2_position] = tour2[(int)city_table[i].tour2_position];
|
||||
count++;
|
||||
count++;// 增加已添加节点的计数
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,10 +95,10 @@ int cx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_gen
|
|||
/* count the number of differences between mom and offspring */
|
||||
for (i = 0; i < num_gene; i++) {
|
||||
if (tour1[i] != offspring[i]) {
|
||||
num_diffs++;
|
||||
num_diffs++;// 增加不同之处的计数
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return num_diffs;
|
||||
return num_diffs;// 返回不同之处的数量
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,17 @@
|
|||
#include "optimizer/geqo_recombination.h"
|
||||
#include "optimizer/geqo_random.h"
|
||||
|
||||
// 声明函数gimme_edge,它接受PlannerInfo结构体指针root,两个Gene节点gene1和gene2,
|
||||
// 以及Edge结构体指针edge_table作为参数,返回一个整数。
|
||||
static int gimme_edge(PlannerInfo* root, Gene gene1, Gene gene2, Edge* edge_table);
|
||||
// 声明函数remove_gene,它接受PlannerInfo结构体指针root,Gene gene,Edge edge,
|
||||
// 以及Edge结构体指针edge_table作为参数,不返回值。
|
||||
static void remove_gene(PlannerInfo* root, Gene gene, Edge edge, Edge* edge_table);
|
||||
// 声明函数gimme_gene,它接受PlannerInfo结构体指针root,Edge edge,
|
||||
// 以及Edge结构体指针edge_table作为参数,返回一个Gene。
|
||||
static Gene gimme_gene(PlannerInfo* root, Edge edge, Edge* edge_table);
|
||||
|
||||
// 声明函数edge_failure,它接受PlannerInfo结构体指针root,Gene指针gene,整数index,
|
||||
// Edge结构体指针edge_table,以及整数num_gene作为参数,返回一个Gene。
|
||||
static Gene edge_failure(PlannerInfo* root, Gene* gene, int index, Edge* edge_table, int num_gene);
|
||||
|
||||
/* alloc_edge_table
|
||||
|
|
@ -44,6 +51,8 @@ static Gene edge_failure(PlannerInfo* root, Gene* gene, int index, Edge* edge_ta
|
|||
* allocate memory for edge table
|
||||
*
|
||||
*/
|
||||
// 分配Edge结构体数组edge_table的内存,接受PlannerInfo结构体指针root和整数num_gene作为参数,
|
||||
// 返回一个Edge结构体指针。
|
||||
Edge* alloc_edge_table(PlannerInfo* root, int num_gene)
|
||||
{
|
||||
Edge* edge_table = NULL;
|
||||
|
|
@ -52,7 +61,7 @@ Edge* alloc_edge_table(PlannerInfo* root, int num_gene)
|
|||
* palloc one extra location so that nodes numbered 1..n can be indexed
|
||||
* directly; 0 will not be used
|
||||
*/
|
||||
edge_table = (Edge*)palloc((num_gene + 1) * sizeof(Edge));
|
||||
edge_table = (Edge*)palloc((num_gene + 1) * sizeof(Edge));// 使用palloc函数为edge_table分配内存。
|
||||
|
||||
return edge_table;
|
||||
}
|
||||
|
|
@ -62,6 +71,8 @@ Edge* alloc_edge_table(PlannerInfo* root, int num_gene)
|
|||
* deallocate memory of edge table
|
||||
*
|
||||
*/
|
||||
// 释放Edge结构体数组edge_table的内存,接受PlannerInfo结构体指针root和Edge结构体指针edge_table作为参数,
|
||||
// 不返回值。
|
||||
void free_edge_table(PlannerInfo* root, Edge* edge_table)
|
||||
{
|
||||
pfree_ext(edge_table);
|
||||
|
|
@ -80,20 +91,22 @@ void free_edge_table(PlannerInfo* root, Edge* edge_table)
|
|||
* where 2.0=homogeneous; 4.0=diverse
|
||||
*
|
||||
*/
|
||||
// 计算两个遗传算法个体(tour1和tour2)之间的边数,接受PlannerInfo结构体指针root,
|
||||
// 两个Gene指针tour1和tour2,整数num_gene,以及Edge结构体指针edge_table作为参数,返回一个浮点数。
|
||||
float gimme_edge_table(PlannerInfo* root, Gene* tour1, Gene* tour2, int num_gene, Edge* edge_table)
|
||||
{
|
||||
int i, index1, index2;
|
||||
int edge_total; /* total number of unique edges in two genes */
|
||||
|
||||
/* at first clear the edge table's old data */
|
||||
for (i = 1; i <= num_gene; i++) {
|
||||
for (i = 1; i <= num_gene; i++) {// 初始化edge_table中各个表节点的边数信息。
|
||||
edge_table[i].total_edges = 0;
|
||||
edge_table[i].unused_edges = 0;
|
||||
}
|
||||
|
||||
/* fill edge table with new data */
|
||||
edge_total = 0;
|
||||
|
||||
// 计算两个遗传算法个体之间的边数,调用gimme_edge函数。
|
||||
for (index1 = 0; index1 < num_gene; index1++) {
|
||||
/*
|
||||
* presume the tour is circular, i.e. 1->2, 2->3, 3->1 this operaton
|
||||
|
|
@ -130,6 +143,8 @@ float gimme_edge_table(PlannerInfo* root, Gene* tour1, Gene* tour2, int num_gene
|
|||
* returns 1 if edge was not already registered and was just added;
|
||||
* 0 if edge was already registered and edge_table is unchanged
|
||||
*/
|
||||
// 实现gimme_edge函数,接受PlannerInfo结构体指针root,两个Gene gene1和gene2,
|
||||
// 以及Edge结构体指针edge_table作为参数,返回一个整数。
|
||||
static int gimme_edge(PlannerInfo* root, Gene gene1, Gene gene2, Edge* edge_table)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -138,10 +153,10 @@ static int gimme_edge(PlannerInfo* root, Gene gene1, Gene gene2, Edge* edge_tabl
|
|||
int city2 = (int)gene2;
|
||||
|
||||
/* check whether edge city1->city2 already exists */
|
||||
edges = edge_table[city1].total_edges;
|
||||
edges = edge_table[city1].total_edges;// 获取表city1的边数。
|
||||
|
||||
for (i = 0; i < edges; i++) {
|
||||
if ((Gene)Abs(edge_table[city1].edge_list[i]) == city2) {
|
||||
for (i = 0; i < edges; i++) {// 遍历查询表city1的边列表,查找是否有查询表city2。
|
||||
if ((Gene)Abs(edge_table[city1].edge_list[i]) == city2) { // 如果找到,将对应边的标记置为负数表示已使用。
|
||||
/* mark shared edges as negative */
|
||||
edge_table[city1].edge_list[i] = 0 - city2;
|
||||
|
||||
|
|
@ -150,7 +165,7 @@ static int gimme_edge(PlannerInfo* root, Gene gene1, Gene gene2, Edge* edge_tabl
|
|||
}
|
||||
|
||||
/* add city1->city2; */
|
||||
edge_table[city1].edge_list[edges] = city2;
|
||||
edge_table[city1].edge_list[edges] = city2;// 如果没有找到表city2,将city2添加到city1的边列表中。
|
||||
|
||||
/* increment the number of edges from city1 */
|
||||
edge_table[city1].total_edges++;
|
||||
|
|
@ -167,10 +182,11 @@ static int gimme_edge(PlannerInfo* root, Gene gene1, Gene gene2, Edge* edge_tabl
|
|||
* in the edge table.)
|
||||
*
|
||||
*/
|
||||
|
||||
int gimme_tour(PlannerInfo* root, Edge* edge_table, Gene* new_gene, int num_gene)
|
||||
{
|
||||
int i;
|
||||
int edge_failures = 0;
|
||||
int edge_failures = 0;// 记录连接失败的数量。
|
||||
|
||||
/* choose int between 1 and num_gene */
|
||||
new_gene[0] = (Gene)geqo_randint(root, num_gene, 1);
|
||||
|
|
@ -180,23 +196,22 @@ int gimme_tour(PlannerInfo* root, Edge* edge_table, Gene* new_gene, int num_gene
|
|||
* as each point is entered into the tour, remove it from the edge
|
||||
* table
|
||||
*/
|
||||
remove_gene(root, new_gene[i - 1], edge_table[(int)new_gene[i - 1]], edge_table);
|
||||
remove_gene(root, new_gene[i - 1], edge_table[(int)new_gene[i - 1]], edge_table);// 从边表中移除前一个表和当前表之间的连接。
|
||||
|
||||
/* find destination for the newly entered point */
|
||||
if (edge_table[new_gene[i - 1]].unused_edges > 0)
|
||||
if (edge_table[new_gene[i - 1]].unused_edges > 0)// 如果前一个表还有未使用的连接,就随机选择下一个表。
|
||||
new_gene[i] = gimme_gene(root, edge_table[(int)new_gene[i - 1]], edge_table);
|
||||
|
||||
else { /* cope with fault */
|
||||
else { /* cope with fault */// 如果前一个表没有未使用的连接,则处理连接失败。
|
||||
edge_failures++;
|
||||
|
||||
new_gene[i] = edge_failure(root, new_gene, i - 1, edge_table, num_gene);
|
||||
}
|
||||
|
||||
/* mark this node as incorporated */
|
||||
edge_table[(int)new_gene[i - 1]].unused_edges = -1;
|
||||
}
|
||||
edge_table[(int)new_gene[i - 1]].unused_edges = -1;// 将前一个表的未使用连接数设为-1,表示已经使用过。
|
||||
|
||||
return edge_failures;
|
||||
return edge_failures;// 返回连接失败的数量。
|
||||
}
|
||||
|
||||
/* remove_gene
|
||||
|
|
@ -206,23 +221,25 @@ int gimme_tour(PlannerInfo* root, Edge* edge_table, Gene* new_gene, int num_gene
|
|||
* to identify deletion locations within edge table.
|
||||
*
|
||||
*/
|
||||
// 实现remove_gene函数,用于从边列表中移除gene,接受PlannerInfo结构体指针root,
|
||||
// Gene gene,Edge edge,以及Edge结构体指针edge_table作为参数,不返回值。
|
||||
static void remove_gene(PlannerInfo* root, Gene gene, Edge edge, Edge* edge_table)
|
||||
{
|
||||
int i, j;
|
||||
int possess_edge;
|
||||
int genes_remaining;
|
||||
int i, j;//循环计数器和内部循环计数器
|
||||
int possess_edge;//用于存储另一个查询表的标识
|
||||
int genes_remaining;//用于跟踪节点表的未使用边列表中剩余的未使用边的数量。
|
||||
|
||||
/*
|
||||
* do for every gene known to have an edge to input gene (i.e. in
|
||||
* edge_list for input edge)
|
||||
*/
|
||||
for (i = 0; i < edge.unused_edges; i++) {
|
||||
for (i = 0; i < edge.unused_edges; i++) { // 遍历城市gene的未使用边列表。
|
||||
possess_edge = (int)Abs(edge.edge_list[i]);
|
||||
genes_remaining = edge_table[possess_edge].unused_edges;
|
||||
|
||||
/* find the input gene in all edge_lists and delete it */
|
||||
for (j = 0; j < genes_remaining; j++) {
|
||||
if ((Gene)Abs(edge_table[possess_edge].edge_list[j]) == gene) {
|
||||
if ((Gene)Abs(edge_table[possess_edge].edge_list[j]) == gene) {// 找到城市gene,将其从边列表中移除。
|
||||
edge_table[possess_edge].unused_edges--;
|
||||
|
||||
edge_table[possess_edge].edge_list[j] = edge_table[possess_edge].edge_list[genes_remaining - 1];
|
||||
|
|
@ -239,23 +256,25 @@ static void remove_gene(PlannerInfo* root, Gene gene, Edge edge, Edge* edge_tabl
|
|||
* (i.e. edges which both genes possess)
|
||||
*
|
||||
*/
|
||||
// 实现gimme_gene函数,接受PlannerInfo结构体指针root,Edge edge,
|
||||
// 以及Edge结构体指针edge_table作为参数,返回一个Gene。
|
||||
static Gene gimme_gene(PlannerInfo* root, Edge edge, Edge* edge_table)
|
||||
{
|
||||
int i;
|
||||
Gene frnd;
|
||||
int minimum_edges;
|
||||
int minimum_count = -1;
|
||||
int rand_decision;
|
||||
const int EDGES_SIZE = 5;
|
||||
int i;// 循环计数器
|
||||
Gene frnd;// 用于存储目标节点的标识
|
||||
int minimum_edges;// 用于跟踪最小未使用边数
|
||||
int minimum_count = -1;// 用于跟踪最小未使用边数的节点表数量
|
||||
int rand_decision;// 随机决策的值
|
||||
const int EDGES_SIZE = 5;// 用于指定边的大小上限
|
||||
|
||||
/*
|
||||
* no point has edges to more than 4 other points thus, this contrived
|
||||
* minimum will be replaced
|
||||
*/
|
||||
minimum_edges = EDGES_SIZE;
|
||||
minimum_edges = EDGES_SIZE;// 初始化minimum_edges为EDGES_SIZE。
|
||||
|
||||
/* consider candidate destination points in edge list */
|
||||
for (i = 0; i < edge.unused_edges; i++) {
|
||||
for (i = 0; i < edge.unused_edges; i++) { /* 考虑边列表中的候选目标点 */
|
||||
frnd = (Gene)edge.edge_list[i];
|
||||
|
||||
/*
|
||||
|
|
@ -266,6 +285,7 @@ static Gene gimme_gene(PlannerInfo* root, Edge edge, Edge* edge_table)
|
|||
* converting to absolute values
|
||||
*/
|
||||
if (frnd < 0)
|
||||
// 如果目标节点标识小于0,返回其绝对值,表示已经访问过。
|
||||
return (Gene)Abs(frnd);
|
||||
|
||||
/*
|
||||
|
|
@ -281,10 +301,11 @@ static Gene gimme_gene(PlannerInfo* root, Edge edge, Edge* edge_table)
|
|||
* that the test will always succeed the first time around. If it can
|
||||
* fail then the code is in error
|
||||
*/
|
||||
if (edge_table[(int)frnd].unused_edges < minimum_edges) {
|
||||
if (edge_table[(int)frnd].unused_edges < minimum_edges) {// 如果目标节点的未使用边数少于minimum_edges,则更新minimum_edges和minimum_count。
|
||||
minimum_edges = edge_table[(int)frnd].unused_edges;
|
||||
minimum_count = 1;
|
||||
} else if (minimum_count == -1)
|
||||
// 如果minimum_count未设置,报告错误。
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT), errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE), errmsg("minimum_count not set")));
|
||||
else if (edge_table[(int)frnd].unused_edges == minimum_edges)
|
||||
|
|
@ -292,13 +313,13 @@ static Gene gimme_gene(PlannerInfo* root, Edge edge, Edge* edge_table)
|
|||
}
|
||||
|
||||
/* random decision of the possible candidates to use */
|
||||
rand_decision = geqo_randint(root, minimum_count - 1, 0);
|
||||
rand_decision = geqo_randint(root, minimum_count - 1, 0);// 随机选择一个目标节点。
|
||||
|
||||
for (i = 0; i < edge.unused_edges; i++) {
|
||||
frnd = (Gene)edge.edge_list[i];
|
||||
|
||||
/* return the chosen candidate point */
|
||||
if (edge_table[(int)frnd].unused_edges == minimum_edges) {
|
||||
if (edge_table[(int)frnd].unused_edges == minimum_edges) {// 如果目标节点的未使用边数等于minimum_edges,则减少minimum_count,并根据rand_decision返回目标城市。
|
||||
minimum_count--;
|
||||
|
||||
if (minimum_count == rand_decision)
|
||||
|
|
@ -307,7 +328,7 @@ static Gene gimme_gene(PlannerInfo* root, Edge edge, Edge* edge_table)
|
|||
}
|
||||
|
||||
/* ... should never be reached */
|
||||
ereport(ERROR,
|
||||
ereport(ERROR,// 如果没有找到满足条件的目标节点,报告错误
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
errmsg("neither shared nor minimum number nor random edge found")));
|
||||
|
|
@ -319,19 +340,22 @@ static Gene gimme_gene(PlannerInfo* root, Edge edge, Edge* edge_table)
|
|||
* routine for handling edge failure
|
||||
*
|
||||
*/
|
||||
// 实现edge_failure函数,接受PlannerInfo结构体指针root,Gene指针gene,整数index,
|
||||
// Edge结构体指针edge_table,以及整数num_gene作为参数,返回一个Gene。
|
||||
static Gene edge_failure(PlannerInfo* root, Gene* gene, int index, Edge* edge_table, int num_gene)
|
||||
{
|
||||
int i;
|
||||
Gene fail_gene = gene[index];
|
||||
int remaining_edges = 0;
|
||||
int four_count = 0;
|
||||
int rand_decision;
|
||||
const int TOTAL_EDGES_COUNT = 4;
|
||||
int i;// 循环计数器
|
||||
Gene fail_gene = gene[index];// 存储失败的基因
|
||||
int remaining_edges = 0;// 剩余的未使用边数
|
||||
int four_count = 0;// 拥有四条边的节点数量
|
||||
int rand_decision;// 随机决策的值
|
||||
const int TOTAL_EDGES_COUNT = 4;// 总边数的限制值
|
||||
|
||||
/*
|
||||
* how many edges remain? how many gene with four total (initial) edges
|
||||
* remain?
|
||||
*/
|
||||
// 计算剩余的未使用边数和total_edges等于TOTAL_EDGES_COUNT的节点数量。
|
||||
for (i = 1; i <= num_gene; i++) {
|
||||
if ((edge_table[i].unused_edges != -1) && (i != (int)fail_gene)) {
|
||||
remaining_edges++;
|
||||
|
|
@ -345,7 +369,7 @@ static Gene edge_failure(PlannerInfo* root, Gene* gene, int index, Edge* edge_ta
|
|||
* random decision of the gene with remaining edges and whose total_edges
|
||||
* == 4
|
||||
*/
|
||||
if (four_count != 0) {
|
||||
if (four_count != 0) {// 如果有total_edges等于TOTAL_EDGES_COUNT的节点,随机选择一个返回。
|
||||
rand_decision = geqo_randint(root, four_count - 1, 0);
|
||||
for (i = 1; i <= num_gene; i++) {
|
||||
if ((Gene)i != fail_gene && edge_table[i].unused_edges != -1 && edge_table[i].total_edges == TOTAL_EDGES_COUNT) {
|
||||
|
|
@ -355,7 +379,7 @@ static Gene edge_failure(PlannerInfo* root, Gene* gene, int index, Edge* edge_ta
|
|||
return (Gene)i;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到满足条件的节点,记录日志。
|
||||
elog(LOG, "no edge found via random decision and total_edges == 4");
|
||||
} else if (remaining_edges != 0) {
|
||||
/* random decision of the gene with remaining edges */
|
||||
|
|
@ -370,9 +394,9 @@ static Gene edge_failure(PlannerInfo* root, Gene* gene, int index, Edge* edge_ta
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到满足条件的节点,记录日志。
|
||||
elog(LOG, "no edge found via random decision with remaining edges");
|
||||
} else {
|
||||
} else { // 如果没有剩余的未使用边,返回最后一个未使用点
|
||||
/*
|
||||
* edge table seems to be empty; this happens sometimes on the last point
|
||||
* due to the fact that the first point is removed from the table even
|
||||
|
|
@ -389,6 +413,6 @@ static Gene edge_failure(PlannerInfo* root, Gene* gene, int index, Edge* edge_ta
|
|||
}
|
||||
|
||||
/* ... should never be reached */
|
||||
ereport(ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE), errmsg("no edge found")));
|
||||
ereport(ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE), errmsg("no edge found")));// 如果没有找到满足条件的城市,报告错误。
|
||||
return 0; /* to keep the compiler quiet */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,26 +34,33 @@
|
|||
|
||||
/* A "clump" of already-joined relations within gimme_tree */
|
||||
typedef struct {
|
||||
// 关联的关系信息
|
||||
RelOptInfo* joinrel; /* joinrel for the set of relations */
|
||||
// 关联的大小
|
||||
int size; /* number of input relations in clump */
|
||||
} Clump;
|
||||
|
||||
static List* merge_clump(PlannerInfo* root, List* clumps, Clump* new_clump, bool force);
|
||||
static bool desirable_join(PlannerInfo* root, RelOptInfo* outer_rel, RelOptInfo* inner_rel);
|
||||
static List* merge_clump(PlannerInfo* root, List* clumps, Clump* new_clump, bool force);// 静态函数——合并关联的 Clump 结构体列表
|
||||
static bool desirable_join(PlannerInfo* root, RelOptInfo* outer_rel, RelOptInfo* inner_rel);// 静态函数——确定连接两个关系是否是合适的
|
||||
|
||||
/*
|
||||
* geqo_eval
|
||||
*
|
||||
* Returns cost of a query tree as an individual of the population.
|
||||
*/
|
||||
|
||||
/*
|
||||
函数接收一个查询规划器(PlannerInfo)和一个遗传算法的代表性基因序列(Gene* tour),
|
||||
并尝试通过优化连接关系表的顺序来找到查询的最佳执行计划,以最小化总成本
|
||||
*/
|
||||
Cost geqo_eval(PlannerInfo* root, Gene* tour, int num_gene)
|
||||
{
|
||||
MemoryContext mycontext;
|
||||
MemoryContext oldcxt;
|
||||
RelOptInfo* joinrel = NULL;
|
||||
Cost fitness;
|
||||
int savelength;
|
||||
struct HTAB* savehash;
|
||||
MemoryContext mycontext;// 创建一个内存上下文,用于存储 GEQO 相关数据
|
||||
MemoryContext oldcxt;// 用于保存当前内存上下文
|
||||
RelOptInfo* joinrel = NULL;// 初始化关联的关系信息
|
||||
Cost fitness;// 存储查询执行计划的适应度(成本)
|
||||
int savelength;// 保存当前关联的关系表列表的长度
|
||||
struct HTAB* savehash;// 保存当前的关系表哈希表
|
||||
|
||||
/*
|
||||
* Create a private memory context that will hold all temp storage
|
||||
|
|
@ -65,8 +72,9 @@ Cost geqo_eval(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
* be freed even if we abort via ereport(ERROR).
|
||||
*/
|
||||
mycontext = AllocSetContextCreate(
|
||||
// 创建一个内存上下文,用于存储 GEQO 相关数据
|
||||
CurrentMemoryContext, "GEQO", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
||||
oldcxt = MemoryContextSwitchTo(mycontext);
|
||||
oldcxt = MemoryContextSwitchTo(mycontext); // 切换到新的内存上下文
|
||||
|
||||
/*
|
||||
* gimme_tree will add entries to root->join_rel_list, which may or may
|
||||
|
|
@ -83,14 +91,14 @@ Cost geqo_eval(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
*
|
||||
* join_rel_level[] shouldn't be in use, so just Assert it isn't.
|
||||
*/
|
||||
savelength = list_length(root->join_rel_list);
|
||||
savehash = root->join_rel_hash;
|
||||
AssertEreport(root->join_rel_level == NULL, MOD_OPT, "");
|
||||
savelength = list_length(root->join_rel_list);// 获取当前关联的关系表列表的长度
|
||||
savehash = root->join_rel_hash;// 保存当前的关系表哈希表
|
||||
AssertEreport(root->join_rel_level == NULL, MOD_OPT, "");// 使用断言确保当前的关联关系级别为空
|
||||
|
||||
root->join_rel_hash = NULL;
|
||||
root->join_rel_hash = NULL;// 清空当前关系表哈希表
|
||||
|
||||
/* construct the best path for the given combination of relations */
|
||||
joinrel = gimme_tree(root, tour, num_gene);
|
||||
joinrel = gimme_tree(root, tour, num_gene);// 调用 gimme_tree 函数获取关联的关系树
|
||||
|
||||
/*
|
||||
* compute fitness
|
||||
|
|
@ -98,20 +106,20 @@ Cost geqo_eval(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
* XXX geqo does not currently support optimization for partial result
|
||||
* retrieval --- how to fix?
|
||||
*/
|
||||
fitness = ((Path*)linitial(joinrel->cheapest_total_path))->total_cost;
|
||||
fitness = ((Path*)linitial(joinrel->cheapest_total_path))->total_cost;// 获取最便宜的路径的总代价
|
||||
|
||||
/*
|
||||
* Restore join_rel_list to its former state, and put back original
|
||||
* hashtable if any.
|
||||
*/
|
||||
root->join_rel_list = list_truncate(root->join_rel_list, savelength);
|
||||
root->join_rel_hash = savehash;
|
||||
root->join_rel_list = list_truncate(root->join_rel_list, savelength);// 恢复关系表列表的长度
|
||||
root->join_rel_hash = savehash;// 恢复关系表哈希表
|
||||
|
||||
/* release all the memory acquired within gimme_tree */
|
||||
(void)MemoryContextSwitchTo(oldcxt);
|
||||
MemoryContextDelete(mycontext);
|
||||
(void)MemoryContextSwitchTo(oldcxt);// 切换回原始的内存上下文
|
||||
MemoryContextDelete(mycontext);// 删除新创建的内存上下文
|
||||
|
||||
return fitness;
|
||||
return fitness;// 返回计算的适应度值
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -138,9 +146,12 @@ Cost geqo_eval(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
* generated plans.
|
||||
*/
|
||||
RelOptInfo* gimme_tree(PlannerInfo* root, Gene* tour, int num_gene)
|
||||
/*
|
||||
函数的功能是根据基因序列和初始关系表信息生成一个最优的关系表连接树,以便在查询优化中使用
|
||||
*/
|
||||
{
|
||||
GeqoPrivateData* priv = (GeqoPrivateData*)root->join_search_private;
|
||||
List* clumps = NIL;
|
||||
GeqoPrivateData* priv = (GeqoPrivateData*)root->join_search_private;// 获取 GEQO 的私有数据
|
||||
List* clumps = NIL;// 创建一个关联的关系表列表
|
||||
int rel_count;
|
||||
|
||||
/*
|
||||
|
|
@ -155,7 +166,7 @@ RelOptInfo* gimme_tree(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
* joins might still fail due to semantics, but we should always be able
|
||||
* to find some join order that works.
|
||||
*/
|
||||
clumps = NIL;
|
||||
clumps = NIL;// 初始化关系表列表
|
||||
|
||||
for (rel_count = 0; rel_count < num_gene; rel_count++) {
|
||||
int cur_rel_index;
|
||||
|
|
@ -163,16 +174,16 @@ RelOptInfo* gimme_tree(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
Clump* cur_clump = NULL;
|
||||
|
||||
/* Get the next input relation */
|
||||
cur_rel_index = (int)tour[rel_count];
|
||||
cur_rel = (RelOptInfo*)list_nth(priv->initial_rels, cur_rel_index - 1);
|
||||
cur_rel_index = (int)tour[rel_count];// 获取当前关联的关系索引
|
||||
cur_rel = (RelOptInfo*)list_nth(priv->initial_rels, cur_rel_index - 1);// 根据索引获取当前关系的信息
|
||||
|
||||
/* Make it into a single-rel clump */
|
||||
cur_clump = (Clump*)palloc(sizeof(Clump));
|
||||
cur_clump->joinrel = cur_rel;
|
||||
cur_clump->size = 1;
|
||||
cur_clump = (Clump*)palloc(sizeof(Clump));// 分配内存以存储当前的关联关系信息
|
||||
cur_clump->joinrel = cur_rel;// 设置当前关联的关系信息
|
||||
cur_clump->size = 1;// 设置关系大小为1
|
||||
|
||||
/* Merge it into the clumps list, using only desirable joins */
|
||||
clumps = merge_clump(root, clumps, cur_clump, false);
|
||||
clumps = merge_clump(root, clumps, cur_clump, false);// 调用 merge_clump 函数将当前关联关系合并到列表中
|
||||
}
|
||||
|
||||
if (list_length(clumps) > 1) {
|
||||
|
|
@ -184,7 +195,7 @@ RelOptInfo* gimme_tree(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
Clump* clump = (Clump*)lfirst(lc);
|
||||
|
||||
fclumps = merge_clump(root, fclumps, clump, true);
|
||||
}
|
||||
}// 强制合并关联关系列表
|
||||
clumps = fclumps;
|
||||
}
|
||||
|
||||
|
|
@ -193,9 +204,9 @@ RelOptInfo* gimme_tree(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
errmsg("failed to join all relations together")));
|
||||
errmsg("failed to join all relations together"))); // 如果无法将所有关联关系连接在一起,则报告错误
|
||||
|
||||
return ((Clump*)linitial(clumps))->joinrel;
|
||||
return ((Clump*)linitial(clumps))->joinrel;// 返回最终的关联的关系表
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -210,17 +221,25 @@ RelOptInfo* gimme_tree(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
* a cartesian join to be performed. When force is false, do only
|
||||
* "desirable" joins.
|
||||
*/
|
||||
|
||||
/*
|
||||
这段代码的功能是实现一个查询计划中的Clump合并操作。Clump是一种数据结构,用于表示查询计划中的关联关系,
|
||||
merge_clump函数接受一个PlannerInfo结构体(查询规划信息)、一个Clump列表clumps、一个新的Clump new_
|
||||
clump以及一个布尔标志force。它会尝试合并新的Clump与已有的Clump,以优化查询计划。函数首先迭代遍历clumps
|
||||
列表,尝试找到可合并的旧Clump,如果找到可合并的关联关系,就将它们合并,并递归调用自身以处理可能的进
|
||||
一步合并。如果无法合并,它会根据一定的规则将新Clump插入到clumps列表中,并返回最终的合并结果。
|
||||
*/
|
||||
static List* merge_clump(PlannerInfo* root, List* clumps, Clump* new_clump, bool force)
|
||||
{
|
||||
ListCell* prev = NULL;
|
||||
ListCell* lc = NULL;
|
||||
ListCell* prev = NULL;// 用于追踪前一个列表元素的指针
|
||||
ListCell* lc = NULL;// 用于迭代遍历clumps列表的指针
|
||||
|
||||
/* Look for a clump that new_clump can join to */
|
||||
foreach (lc, clumps) {
|
||||
Clump* old_clump = (Clump*)lfirst(lc);
|
||||
foreach (lc, clumps) {// 对clumps列表中的每个Clump元素进行迭代处理
|
||||
Clump* old_clump = (Clump*)lfirst(lc);// 获取当前迭代元素的指针
|
||||
|
||||
if (force || desirable_join(root, old_clump->joinrel, new_clump->joinrel)) {
|
||||
RelOptInfo* joinrel = NULL;
|
||||
if (force || desirable_join(root, old_clump->joinrel, new_clump->joinrel)) {// 如果force标志为true,或者新旧Clump之间存在可合并的关联关系
|
||||
RelOptInfo* joinrel = NULL;// 用于存储合并后的关系信息
|
||||
|
||||
/*
|
||||
* Construct a RelOptInfo representing the join of these two input
|
||||
|
|
@ -230,17 +249,17 @@ static List* merge_clump(PlannerInfo* root, List* clumps, Clump* new_clump, bool
|
|||
*/
|
||||
joinrel = make_join_rel(root, old_clump->joinrel, new_clump->joinrel);
|
||||
/* Keep searching if join order is not valid */
|
||||
if (joinrel != NULL) {
|
||||
if (joinrel != NULL) {// 如果成功创建了新的关联关系
|
||||
/* Find and save the cheapest paths for this joinrel */
|
||||
set_cheapest(joinrel);
|
||||
set_cheapest(joinrel);// 标记joinrel为最便宜的查询计划
|
||||
|
||||
/* Absorb new clump into old */
|
||||
old_clump->joinrel = joinrel;
|
||||
old_clump->size += new_clump->size;
|
||||
pfree_ext(new_clump);
|
||||
old_clump->joinrel = joinrel; // 更新旧Clump的关联关系信息
|
||||
old_clump->size += new_clump->size;// 更新旧Clump的大小
|
||||
pfree_ext(new_clump);// 释放新Clump的内存
|
||||
|
||||
/* Remove old_clump from list */
|
||||
clumps = list_delete_cell(clumps, lc, prev);
|
||||
clumps = list_delete_cell(clumps, lc, prev);// 从clumps列表中删除旧Clump
|
||||
|
||||
/*
|
||||
* Recursively try to merge the enlarged old_clump with
|
||||
|
|
@ -250,7 +269,7 @@ static List* merge_clump(PlannerInfo* root, List* clumps, Clump* new_clump, bool
|
|||
return merge_clump(root, clumps, old_clump, force);
|
||||
}
|
||||
}
|
||||
prev = lc;
|
||||
prev = lc;// 更新prev指针以继续迭代
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -258,13 +277,14 @@ static List* merge_clump(PlannerInfo* root, List* clumps, Clump* new_clump, bool
|
|||
* proper order according to size. We can be fast for the common case
|
||||
* where it has size 1 --- it should always go at the end.
|
||||
*/
|
||||
if (clumps == NIL || new_clump->size == 1)
|
||||
if (clumps == NIL || new_clump->size == 1) // 如果clumps为空或新Clump的大小为1,将新Clump添加到clumps列表末尾
|
||||
|
||||
return lappend(clumps, new_clump);
|
||||
|
||||
/* Check if it belongs at the front */
|
||||
lc = list_head(clumps);
|
||||
lc = list_head(clumps);// 获取clumps列表的头元素指针
|
||||
if (new_clump->size > ((Clump*)lfirst(lc))->size)
|
||||
return lcons(new_clump, clumps);
|
||||
return lcons(new_clump, clumps);// 如果新Clump的大小大于第一个Clump的大小,将新Clump插入列表头部
|
||||
|
||||
/* Else search for the place to insert it */
|
||||
for (;;) {
|
||||
|
|
@ -272,9 +292,10 @@ static List* merge_clump(PlannerInfo* root, List* clumps, Clump* new_clump, bool
|
|||
|
||||
if (nxt == NULL || new_clump->size > ((Clump*)lfirst(nxt))->size)
|
||||
break; /* it belongs after 'lc', before 'nxt' */
|
||||
// 如果新Clump的大小大于下一个Clump的大小,退出循环,确定插入位置
|
||||
lc = nxt;
|
||||
}
|
||||
(void)lappend_cell(clumps, lc, new_clump);
|
||||
(void)lappend_cell(clumps, lc, new_clump);// 在lc之后插入新Clump
|
||||
|
||||
return clumps;
|
||||
}
|
||||
|
|
@ -288,6 +309,7 @@ static bool desirable_join(PlannerInfo* root, RelOptInfo* outer_rel, RelOptInfo*
|
|||
* Join if there is an applicable join clause, or if there is a join order
|
||||
* restriction forcing these rels to be joined.
|
||||
*/
|
||||
// 判断是否有相关的连接条件或连接顺序限制
|
||||
if (have_relevant_joinclause(root, outer_rel, inner_rel) || have_join_order_restriction(root, outer_rel, inner_rel))
|
||||
return true;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,21 +32,28 @@
|
|||
#include "optimizer/geqo_random.h"
|
||||
#include "optimizer/geqo_selection.h"
|
||||
|
||||
static int gimme_pool_size(int nr_rel);
|
||||
static int gimme_number_generations(int pool_size);
|
||||
static int gimme_pool_size(int nr_rel);//根据关系数(nr_rel)计算池大小
|
||||
static int gimme_number_generations(int pool_size);//根据池大小计算生成的代数数量
|
||||
|
||||
/* define edge recombination crossover [ERX] per default */
|
||||
#if !defined(ERX) && !defined(PMX) && !defined(CX) && !defined(PX) && !defined(OX1) && !defined(OX2)
|
||||
#define ERX
|
||||
#endif
|
||||
|
||||
/*
|
||||
该部分代码在实现了遗传查询优化算法,其中查询计划的一组候选解(染色体)在多代之间演化,以找到给定关系集的最佳查询计划
|
||||
特定的交叉和变异方法可以在编译时基于宏选择,算法旨在通过评估和演化候选查询计划来找到最佳的查询计划
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* geqo
|
||||
* solution of the query optimization problem
|
||||
* similar to a constrained Traveling Salesman Problem (TSP)
|
||||
*/
|
||||
RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
||||
RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)// GEQO(遗传查询优化)算法主函数
|
||||
{
|
||||
// 定义GEQO的私有数据结构和变量
|
||||
GeqoPrivateData priv;
|
||||
int generation;
|
||||
Chromosome* momma = NULL;
|
||||
|
|
@ -55,7 +62,7 @@ RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
|||
Pool* pool = NULL;
|
||||
int pool_size, number_generations;
|
||||
|
||||
#ifdef GEQO_DEBUG
|
||||
#ifdef GEQO_DEBUG// 根据宏定义,为特定的交叉方法分配额外的数据结构
|
||||
int status_interval;
|
||||
#endif
|
||||
Gene* best_tour = NULL;
|
||||
|
|
@ -74,19 +81,19 @@ RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
|||
#endif
|
||||
|
||||
/* set up private information */
|
||||
root->join_search_private = (void*)&priv;
|
||||
root->join_search_private = (void*)&priv;// 设置PlannerInfo中的私有数据
|
||||
priv.initial_rels = initial_rels;
|
||||
|
||||
/* initialize private number generator */
|
||||
geqo_set_seed(root, u_sess->attr.attr_sql.Geqo_seed);
|
||||
geqo_set_seed(root, u_sess->attr.attr_sql.Geqo_seed);// 设置GEQO的随机种子
|
||||
|
||||
/* set GA parameters */
|
||||
pool_size = gimme_pool_size(number_of_rels);
|
||||
pool_size = gimme_pool_size(number_of_rels);// 根据输入计算池大小和生成代数数量
|
||||
number_generations = gimme_number_generations(pool_size);
|
||||
#ifdef GEQO_DEBUG
|
||||
status_interval = 10;
|
||||
#endif
|
||||
|
||||
// 为GEQO池分配内存并初始化
|
||||
/* allocate genetic pool memory */
|
||||
pool = alloc_pool(root, pool_size, number_of_rels);
|
||||
|
||||
|
|
@ -97,7 +104,7 @@ RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
|||
sort_pool(root, pool); /* we have to do it only one time, since all
|
||||
* kids replace the worst individuals in
|
||||
* future (-> geqo_pool.c:spread_chromo ) */
|
||||
#ifdef GEQO_DEBUG
|
||||
#ifdef GEQO_DEBUG// 在调试模式下记录池统计信息
|
||||
elog(DEBUG1,
|
||||
"GEQO selected %d pool entries, best %.2f, worst %.2f",
|
||||
pool_size,
|
||||
|
|
@ -106,9 +113,10 @@ RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
|||
#endif
|
||||
|
||||
/* allocate chromosome momma and daddy memory */
|
||||
momma = alloc_chromo(root, pool->string_length);
|
||||
momma = alloc_chromo(root, pool->string_length);// 为父代染色体(momma和daddy)分配内存
|
||||
daddy = alloc_chromo(root, pool->string_length);
|
||||
|
||||
// 根据所选的交叉方法,为额外的数据结构分配内存
|
||||
#if defined(ERX)
|
||||
#ifdef GEQO_DEBUG
|
||||
elog(DEBUG2, "using edge recombination crossover [ERX]");
|
||||
|
|
@ -153,9 +161,9 @@ RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
|||
|
||||
/* my pain main part: */
|
||||
/* iterative optimization */
|
||||
for (generation = 0; generation < number_generations; generation++) {
|
||||
for (generation = 0; generation < number_generations; generation++) {// GEQO算法的主循环,用于代数迭代
|
||||
/* SELECTION: using linear bias function */
|
||||
geqo_selection(root, momma, daddy, pool, u_sess->attr.attr_sql.Geqo_selection_bias);
|
||||
geqo_selection(root, momma, daddy, pool, u_sess->attr.attr_sql.Geqo_selection_bias);// 执行父代选择和基于所选方法的交叉操作
|
||||
|
||||
#if defined(ERX)
|
||||
/* EDGE RECOMBINATION CROSSOVER */
|
||||
|
|
@ -188,10 +196,10 @@ RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
|||
#endif
|
||||
|
||||
/* EVALUATE FITNESS */
|
||||
kid->worth = geqo_eval(root, kid->string, pool->string_length);
|
||||
kid->worth = geqo_eval(root, kid->string, pool->string_length);// 评估子代染色体的价值
|
||||
|
||||
/* push the kid into the wilderness of life according to its worth */
|
||||
spread_chromo(root, kid, pool);
|
||||
spread_chromo(root, kid, pool);// 将子代染色体传播到池中以用于下一代
|
||||
|
||||
#ifdef GEQO_DEBUG
|
||||
if (status_interval && !(generation % status_interval))
|
||||
|
|
@ -199,7 +207,7 @@ RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
|||
#endif
|
||||
}
|
||||
|
||||
#if defined(ERX) && defined(GEQO_DEBUG)
|
||||
#if defined(ERX) && defined(GEQO_DEBUG)// 在调试模式下打印池的状态
|
||||
if (edge_failures != 0)
|
||||
elog(LOG, "[GEQO] failures: %d, average: %d", edge_failures, number_generations / edge_failures);
|
||||
else
|
||||
|
|
@ -270,14 +278,14 @@ RelOptInfo* geqo(PlannerInfo* root, int number_of_rels, List* initial_rels)
|
|||
* The default is based on query size (no. of relations) = 2^(QS+1),
|
||||
* but constrained to a range based on the effort value.
|
||||
*/
|
||||
static int gimme_pool_size(int nr_rel)
|
||||
static int gimme_pool_size(int nr_rel)// 用于根据关系数计算池大小的函数
|
||||
{
|
||||
double size;
|
||||
int minsize;
|
||||
int maxsize;
|
||||
|
||||
/* Legal pool size *must* be at least 2, so ignore attempt to select 1 */
|
||||
if (u_sess->attr.attr_sql.Geqo_pool_size >= 2)
|
||||
if (u_sess->attr.attr_sql.Geqo_pool_size >= 2)// 检查配置中是否设置了特定的池大小
|
||||
return u_sess->attr.attr_sql.Geqo_pool_size;
|
||||
|
||||
size = pow(2.0, nr_rel + 1.0);
|
||||
|
|
@ -302,10 +310,11 @@ static int gimme_pool_size(int nr_rel)
|
|||
* sure that less-fit individuals get pushed out of the breeding
|
||||
* population before the run finishes.
|
||||
*/
|
||||
static int gimme_number_generations(int pool_size)
|
||||
static int gimme_number_generations(int pool_size)// 根据池大小计算生成代数数量的函数
|
||||
{
|
||||
// 检查配置中是否设置了特定的代数数量
|
||||
if (u_sess->attr.attr_sql.Geqo_generations > 0)
|
||||
return u_sess->attr.attr_sql.Geqo_generations;
|
||||
|
||||
return pool_size;
|
||||
return pool_size;// 使用池大小作为默认的代数数量
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,12 @@
|
|||
/*
|
||||
* avg_pool
|
||||
*/
|
||||
static double avg_pool(Pool* pool)
|
||||
|
||||
/*
|
||||
这段代码是关于查询优化器中的一些辅助函数,用于汇总和打印池(pool)和边缘表(edge table)的信息
|
||||
*/
|
||||
|
||||
static double avg_pool(Pool* pool)//这个函数计算池中数据元素的平均值,先检查池的大小是否为零,然后遍历每个元素,将其值累加到cumulative中,最后返回平均值
|
||||
{
|
||||
int i;
|
||||
double cumulative = 0.0;
|
||||
|
|
@ -44,37 +49,37 @@ static double avg_pool(Pool* pool)
|
|||
* little in speed and accuracy, but this routine is only used for debug
|
||||
* printouts, so we don't care that much.
|
||||
*/
|
||||
for (i = 0; i < pool->size; i++)
|
||||
for (i = 0; i < pool->size; i++)// 遍历池中的数据,累加每个元素的值
|
||||
cumulative += pool->data[i].worth / pool->size;
|
||||
|
||||
return cumulative;
|
||||
return cumulative;// 返回平均值
|
||||
}
|
||||
|
||||
/* print_pool
|
||||
*/
|
||||
void print_pool(FILE* fp, Pool* pool, int start, int stop)
|
||||
void print_pool(FILE* fp, Pool* pool, int start, int stop)//这个函数用于打印池中的数据元素到指定文件,可以指定起始和结束位置,如果它们超出了池的边界,会重新设置它们
|
||||
{
|
||||
int i, j;
|
||||
|
||||
/* be extra careful that start and stop are valid inputs */
|
||||
if (start < 0)
|
||||
if (start < 0)// 如果start小于0,将其设置为0
|
||||
start = 0;
|
||||
if (stop > pool->size)
|
||||
if (stop > pool->size)// 如果stop大于池的大小,将其设置为池的大小
|
||||
stop = pool->size;
|
||||
|
||||
if (start + stop > pool->size) {
|
||||
if (start + stop > pool->size) {// 如果start和stop的和大于池的大小,重新设置它们
|
||||
start = 0;
|
||||
stop = pool->size;
|
||||
}
|
||||
|
||||
for (i = start; i < stop; i++) {
|
||||
for (i = start; i < stop; i++) {// 打印池中的数据元素到指定文件
|
||||
fprintf(fp, "%d)\t", i);
|
||||
for (j = 0; j < pool->string_length; j++)
|
||||
fprintf(fp, "%d ", pool->data[i].string[j]);
|
||||
fprintf(fp, "%g\n", pool->data[i].worth);
|
||||
}
|
||||
|
||||
fflush(fp);
|
||||
fflush(fp);// 刷新文件缓冲区
|
||||
}
|
||||
|
||||
/* print_gen
|
||||
|
|
@ -89,7 +94,7 @@ void print_gen(FILE* fp, Pool* pool, int generation)
|
|||
/* Use 2nd to last since last is buffer. */
|
||||
lowest = pool->size > 1 ? pool->size - 2 : 0;
|
||||
|
||||
fprintf(fp,
|
||||
fprintf(fp,// 打印池的<E6B1A0><E79A84><EFBFBD>些统计信息,包括最佳、最差、平均和平均池值
|
||||
"%5d | Best: %g Worst: %g Mean: %g Avg: %g\n",
|
||||
generation,
|
||||
pool->data[0].worth,
|
||||
|
|
@ -104,7 +109,7 @@ void print_edge_table(FILE* fp, Edge* edge_table, int num_gene)
|
|||
{
|
||||
int i, j;
|
||||
|
||||
fprintf(fp, "\nEDGE TABLE\n");
|
||||
fprintf(fp, "\nEDGE TABLE\n");// 打印边缘表的内容
|
||||
|
||||
for (i = 1; i <= num_gene; i++) {
|
||||
fprintf(fp, "%d :", i);
|
||||
|
|
|
|||
|
|
@ -37,22 +37,29 @@
|
|||
|
||||
void geqo_mutation(PlannerInfo* root, Gene* tour, int num_gene)
|
||||
{
|
||||
// 定义两个整数变量来表示要交换的基因的索引
|
||||
int swap1;
|
||||
int swap2;
|
||||
// 计算要执行的交换次数,这里使用随机数,num_gene/3表示最大交换次数
|
||||
int num_swaps = geqo_randint(root, num_gene / 3, 0);
|
||||
Gene temp;
|
||||
Gene temp;// 临时变量,用于存储基因交换时的中间结果
|
||||
|
||||
while (num_swaps > 0) {
|
||||
swap1 = geqo_randint(root, num_gene - 1, 0);
|
||||
while (num_swaps > 0) {// 当还有交换次数时循环执行交换
|
||||
swap1 = geqo_randint(root, num_gene - 1, 0);// 随机选择两个不同的基因索引
|
||||
swap2 = geqo_randint(root, num_gene - 1, 0);
|
||||
|
||||
while (swap1 == swap2)
|
||||
while (swap1 == swap2)// 确保选到的两个索引不相同
|
||||
swap2 = geqo_randint(root, num_gene - 1, 0);
|
||||
|
||||
temp = tour[swap1];
|
||||
temp = tour[swap1];// 执行基因交换,将选中的两个基因进行互换
|
||||
tour[swap1] = tour[swap2];
|
||||
tour[swap2] = temp;
|
||||
|
||||
num_swaps -= 1;
|
||||
num_swaps -= 1;// 减少剩余的交换次数
|
||||
}
|
||||
}
|
||||
/*
|
||||
这段代码的功能是在给定基因序列(tour)中执行基因交换操作。
|
||||
它首先确定要执行的交换次数,然后随机选择两个不同的基因位置进行交换。
|
||||
通过这种方式,它可以引入随机性,有助于在搜索空间中找到更多可能的解决方案,以提高查询优化的效果
|
||||
*/
|
||||
|
|
@ -46,34 +46,44 @@ void ox1(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
int left, right, k, p, temp;
|
||||
|
||||
/* initialize city table */
|
||||
for (k = 1; k <= num_gene; k++)
|
||||
for (k = 1; k <= num_gene; k++)// 初始化节点表中的标志位,用于记录城市是否已经在后代中出现
|
||||
city_table[k].used = 0;
|
||||
|
||||
/* select portion to copy from tour1 */
|
||||
// 随机选择要从tour1中复制到后代中的基因片段
|
||||
left = geqo_randint(root, num_gene - 1, 0);
|
||||
right = geqo_randint(root, num_gene - 1, 0);
|
||||
if (left > right) {
|
||||
if (left > right) {// 确保left小于right
|
||||
temp = left;
|
||||
left = right;
|
||||
right = temp;
|
||||
}
|
||||
|
||||
/* copy portion from tour1 to offspring */
|
||||
for (k = left; k <= right; k++) {
|
||||
for (k = left; k <= right; k++) {// 复制tour1中选定的基因片段到后代
|
||||
offspring[k] = tour1[k];
|
||||
city_table[(int)tour1[k]].used = 1;
|
||||
}
|
||||
|
||||
// 初始化k和p,用于迭代地从tour2中选择未出现在后代中的基因
|
||||
k = (right + 1) % num_gene; /* index into offspring */
|
||||
p = k; /* index into tour2 */
|
||||
|
||||
/* copy stuff from tour2 to offspring */
|
||||
while (k != left) {
|
||||
while (k != left) {// 开始迭代
|
||||
// 如果tour2中的基因没有在后代中出现过,将其添加到后代中
|
||||
if (!city_table[(int)tour2[p]].used) {
|
||||
offspring[k] = tour2[p];
|
||||
k = (k + 1) % num_gene;
|
||||
city_table[(int)tour2[p]].used = 1;
|
||||
}
|
||||
p = (p + 1) % num_gene; /* increment tour2-index */
|
||||
p = (p + 1) % num_gene; /* increment tour2-index */// 增加tour2的索引
|
||||
}
|
||||
}
|
||||
/*
|
||||
这段代码实现了一种遗传算法的交叉操作,称为OX1 (Order Crossover 1)。
|
||||
它从两个父代基因序列 (tour1 和 tour2) 中选取一个随机的基因片段,
|
||||
然后将该片段复制到后代基因序列 (offspring) 中。
|
||||
接着,它从第二个父代基因序列 (tour2) 中选择未在后代中出现的基因,并按照原始顺序添加到后代中,以保持基因的顺序。
|
||||
这有助于保留父代的一些特性,并引入一些随机性,以生成新的个体
|
||||
*/
|
||||
|
|
@ -45,26 +45,27 @@ void ox2(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
int k, j, count, pos, select, num_positions;
|
||||
|
||||
/* initialize city table */
|
||||
for (k = 1; k <= num_gene; k++) {
|
||||
city_table[k].used = 0;
|
||||
city_table[k - 1].select_list = -1;
|
||||
for (k = 1; k <= num_gene; k++) {// 随机生成要选择的位置数目
|
||||
city_table[k].used = 0;// 标记节点尚未在后代中出现过
|
||||
city_table[k - 1].select_list = -1;// 初始化选择列表为-1
|
||||
}
|
||||
|
||||
/* determine the number of positions to be inherited from tour1 */
|
||||
num_positions = geqo_randint(root, 2 * num_gene / 3, num_gene / 3);
|
||||
// 随机生成要选择的位置数目
|
||||
|
||||
/* make a list of selected cities */
|
||||
for (k = 0; k < num_positions; k++) {
|
||||
pos = geqo_randint(root, num_gene - 1, 0);
|
||||
city_table[pos].select_list = (int)tour1[pos];
|
||||
city_table[(int)tour1[pos]].used = 1; /* mark used */
|
||||
for (k = 0; k < num_positions; k++) {// 随机选择位置并将对应的节点标记为已使用
|
||||
pos = geqo_randint(root, num_gene - 1, 0);// 随机选择一个位置
|
||||
city_table[pos].select_list = (int)tour1[pos];// 记录tour1中选中位置的基因值
|
||||
city_table[(int)tour1[pos]].used = 1; /* mark used */ // 标记节点已在后代中使用
|
||||
}
|
||||
|
||||
count = 0;
|
||||
k = 0;
|
||||
|
||||
/* consolidate the select list to adjacent positions */
|
||||
while (count < num_positions) {
|
||||
while (count < num_positions) {// 创建选择列表,用于在后代中选择未出现的节点
|
||||
if (city_table[k].select_list == -1) {
|
||||
j = k + 1;
|
||||
while ((city_table[j].select_list == -1) && (j < num_gene))
|
||||
|
|
@ -79,7 +80,7 @@ void ox2(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
}
|
||||
|
||||
select = 0;
|
||||
|
||||
// 根据选择列表构建后代基因序列
|
||||
for (k = 0; k < num_gene; k++) {
|
||||
if (city_table[(int)tour2[k]].used) {
|
||||
offspring[k] = (Gene)city_table[select].select_list;
|
||||
|
|
@ -89,3 +90,10 @@ void ox2(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
offspring[k] = tour2[k];
|
||||
}
|
||||
}
|
||||
/*
|
||||
这段代码实现了另一种遗传算法的交叉操作,称为OX2 (Order Crossover 2)。
|
||||
它的目标是生成后代基因序列 (offspring),将两个父代基因序列 (tour1 和 tour2) 结合起来。
|
||||
首先,它随机选择一些位置,并将相应的节点标记为已使用。
|
||||
接着,它构建一个选择列表,用于在后代中选择未出现的节点。
|
||||
最后,它根据选择列表构建后代基因序列,保持了一部分父代的顺序特性,同时引入了一些随机性。这有助于维持多样性并生成更多的解决方案
|
||||
*/
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
*/
|
||||
void pmx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_gene)
|
||||
{
|
||||
// 为辅助数组分配内存
|
||||
int* failed = (int*)palloc((num_gene + 1) * sizeof(int));
|
||||
int* from = (int*)palloc((num_gene + 1) * sizeof(int));
|
||||
int* indx = (int*)palloc((num_gene + 1) * sizeof(int));
|
||||
|
|
@ -53,33 +54,33 @@ void pmx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
|
||||
/* no mutation so start up the pmx replacement algorithm */
|
||||
/* initialize failed[], from[], check_list[] */
|
||||
for (k = 0; k < num_gene; k++) {
|
||||
failed[k] = -1;
|
||||
from[k] = -1;
|
||||
check_list[k + 1] = 0;
|
||||
for (k = 0; k < num_gene; k++) {// 初始化辅助数组和检查列表
|
||||
failed[k] = -1;// 用于记录失败的映射关系
|
||||
from[k] = -1; // 用于记录映射的来源 (DAD or MOM)
|
||||
check_list[k + 1] = 0; // 检查列表用于记录节点的出现次数
|
||||
}
|
||||
|
||||
// 随机选择要进行映射的基因片段
|
||||
/* locate crossover points */
|
||||
left = geqo_randint(root, num_gene - 1, 0);
|
||||
right = geqo_randint(root, num_gene - 1, 0);
|
||||
if (left > right) {
|
||||
if (left > right) {// 确保left小于right
|
||||
temp = left;
|
||||
left = right;
|
||||
right = temp;
|
||||
}
|
||||
|
||||
/* copy tour2 into offspring */
|
||||
for (k = 0; k < num_gene; k++) {
|
||||
for (k = 0; k < num_gene; k++) { // 复制tour2中的基因到后代,并初始化相关数据结构
|
||||
offspring[k] = tour2[k];
|
||||
from[k] = DAD;
|
||||
check_list[tour2[k]]++;
|
||||
from[k] = DAD;// 从父代DAD中继承的基因
|
||||
check_list[tour2[k]]++;// 记录节点的出现次数
|
||||
}
|
||||
|
||||
/* copy tour1 into offspring */
|
||||
for (k = left; k <= right; k++) {
|
||||
for (k = left; k <= right; k++) {// 执行部分映射操作,更新后代和相关数据结构
|
||||
check_list[offspring[k]]--;
|
||||
offspring[k] = tour1[k];
|
||||
from[k] = MOM;
|
||||
from[k] = MOM; // 从父代MOM中继承的基因
|
||||
check_list[tour1[k]]++;
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +88,7 @@ void pmx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
mx_fail = 0;
|
||||
|
||||
/* STEP 1 */
|
||||
// 处理没有成功映射的情况
|
||||
for (k = left; k <= right; k++) { /* for all elements in the tour1-2 */
|
||||
if (tour1[k] == tour2[k])
|
||||
found = 1; /* find match in tour2 */
|
||||
|
|
@ -116,7 +118,7 @@ void pmx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
|
||||
/* STEP 2 */
|
||||
/* see if any genes could not be replaced */
|
||||
if (mx_fail > 0) {
|
||||
if (mx_fail > 0) {// 处理多次映射的情况
|
||||
mx_hold = mx_fail;
|
||||
|
||||
for (k = 0; k < mx_hold; k++) {
|
||||
|
|
@ -140,6 +142,7 @@ void pmx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
} /* ... if */
|
||||
/* STEP 3 */
|
||||
for (k = 1; k <= num_gene; k++) {
|
||||
// 处理重复节点的情况,确保每个基因只出现一次
|
||||
|
||||
if (check_list[k] > 1) {
|
||||
i = 0;
|
||||
|
|
@ -164,9 +167,15 @@ void pmx(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_g
|
|||
} /* end while */
|
||||
}
|
||||
} /* ... for */
|
||||
|
||||
// 释放内存
|
||||
pfree_ext(failed);
|
||||
pfree_ext(from);
|
||||
pfree_ext(indx);
|
||||
pfree_ext(check_list);
|
||||
}
|
||||
/*
|
||||
这段代码实现了PMX (Partially Mapped Crossover) 运算,用于生成后代基因序列。
|
||||
它首先随机选择一个基因片段来映射,然后根据映射关系进行替换。
|
||||
在映射操作中,它处理了映射失败和多次映射的情况,并最终确保后代中每个基因只出现一次。
|
||||
这有助于保持基因的顺序性和多样性。
|
||||
*/
|
||||
|
|
@ -30,24 +30,24 @@
|
|||
#include "optimizer/geqo_copy.h"
|
||||
#include "optimizer/geqo_pool.h"
|
||||
#include "optimizer/geqo_recombination.h"
|
||||
|
||||
// 定义比较函数,用于排序
|
||||
static int compare(const void* arg1, const void* arg2);
|
||||
|
||||
/*
|
||||
* alloc_pool
|
||||
* allocates memory for GA pool
|
||||
*/
|
||||
Pool* alloc_pool(PlannerInfo* root, int pool_size, int string_length)
|
||||
Pool* alloc_pool(PlannerInfo* root, int pool_size, int string_length)// 分配内存并初始化一个新的池
|
||||
{
|
||||
Pool* new_pool = NULL;
|
||||
Chromosome* chromo = NULL;
|
||||
int i;
|
||||
|
||||
// 分配内存以存储新池
|
||||
/* pool */
|
||||
new_pool = (Pool*)palloc(sizeof(Pool));
|
||||
new_pool->size = (int)pool_size;
|
||||
new_pool->string_length = (int)string_length;
|
||||
|
||||
// 分配内存以存储染色体数据
|
||||
/* all chromosome */
|
||||
new_pool->data = (Chromosome*)palloc(pool_size * sizeof(Chromosome));
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ Pool* alloc_pool(PlannerInfo* root, int pool_size, int string_length)
|
|||
* free_pool
|
||||
* deallocates memory for GA pool
|
||||
*/
|
||||
void free_pool(PlannerInfo* root, Pool* pool)
|
||||
void free_pool(PlannerInfo* root, Pool* pool)// 释放池及其相关的内存
|
||||
{
|
||||
Chromosome* chromo = NULL;
|
||||
int i;
|
||||
|
|
@ -74,18 +74,19 @@ void free_pool(PlannerInfo* root, Pool* pool)
|
|||
pfree_ext(chromo[i].string);
|
||||
|
||||
/* all chromosome */
|
||||
pfree_ext(pool->data);
|
||||
pfree_ext(pool->data);// 释放染色体数据
|
||||
|
||||
/* pool */
|
||||
pfree_ext(pool);
|
||||
// 释放池本身
|
||||
}
|
||||
|
||||
void random_init_pool(PlannerInfo* root, Pool* pool)
|
||||
void random_init_pool(PlannerInfo* root, Pool* pool)// 随机初始化池中的染色体
|
||||
{
|
||||
Chromosome* chromo = (Chromosome*)pool->data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pool->size; i++) {
|
||||
for (i = 0; i < pool->size; i++) {// 初始化池中的染色体
|
||||
init_tour(root, chromo[i].string, pool->string_length);
|
||||
pool->data[i].worth = geqo_eval(root, chromo[i].string, pool->string_length);
|
||||
}
|
||||
|
|
@ -97,7 +98,7 @@ void random_init_pool(PlannerInfo* root, Pool* pool)
|
|||
*
|
||||
* maybe you have to change compare() for different ordering ...
|
||||
*/
|
||||
void sort_pool(PlannerInfo* root, Pool* pool)
|
||||
void sort_pool(PlannerInfo* root, Pool* pool)// 对池中的染色体按照适应度进行排序
|
||||
{
|
||||
qsort(pool->data, pool->size, sizeof(Chromosome), compare);
|
||||
}
|
||||
|
|
@ -110,7 +111,7 @@ static int compare(const void* arg1, const void* arg2)
|
|||
{
|
||||
const Chromosome* chromo1 = (const Chromosome*)arg1;
|
||||
const Chromosome* chromo2 = (const Chromosome*)arg2;
|
||||
|
||||
// 根据染色体的适应度比较它们的顺序
|
||||
if (chromo1->worth - chromo2->worth == 0)
|
||||
return 0;
|
||||
else if (chromo1->worth > chromo2->worth)
|
||||
|
|
@ -122,10 +123,10 @@ static int compare(const void* arg1, const void* arg2)
|
|||
/* alloc_chromo
|
||||
* allocates a chromosome and string space
|
||||
*/
|
||||
Chromosome* alloc_chromo(PlannerInfo* root, int string_length)
|
||||
Chromosome* alloc_chromo(PlannerInfo* root, int string_length)// 分配内存并初始化一个新的染色体
|
||||
{
|
||||
Chromosome* chromo = NULL;
|
||||
|
||||
// 分配内存以存储染色体
|
||||
chromo = (Chromosome*)palloc(sizeof(Chromosome));
|
||||
chromo->string = (Gene*)palloc((string_length + 1) * sizeof(Gene));
|
||||
|
||||
|
|
@ -136,23 +137,24 @@ Chromosome* alloc_chromo(PlannerInfo* root, int string_length)
|
|||
* deallocates a chromosome and string space
|
||||
*/
|
||||
void free_chromo(PlannerInfo* root, Chromosome* chromo)
|
||||
// 释放染色体及其相关的内存
|
||||
{
|
||||
pfree_ext(chromo->string);
|
||||
pfree_ext(chromo);
|
||||
pfree_ext(chromo->string);// 释放染色体数据
|
||||
pfree_ext(chromo);// 释放染色体本身
|
||||
}
|
||||
|
||||
/* spread_chromo
|
||||
* inserts a new chromosome into the pool, displacing worst gene in pool
|
||||
* assumes best->worst = smallest->largest
|
||||
*/
|
||||
void spread_chromo(PlannerInfo* root, Chromosome* chromo, Pool* pool)
|
||||
void spread_chromo(PlannerInfo* root, Chromosome* chromo, Pool* pool)// 将染色体插入到池中并维护池的排序顺序
|
||||
{
|
||||
int top, mid, bot;
|
||||
int i, index;
|
||||
Chromosome swap_chromo, tmp_chromo;
|
||||
|
||||
/* new chromo is so bad we can't use it */
|
||||
if (chromo->worth > pool->data[pool->size - 1].worth)
|
||||
if (chromo->worth > pool->data[pool->size - 1].worth)// 如果染色体的适应度高于池中的最差染色体,则不插入
|
||||
return;
|
||||
|
||||
/* do a binary search to find the index of the new chromo */
|
||||
|
|
@ -160,7 +162,7 @@ void spread_chromo(PlannerInfo* root, Chromosome* chromo, Pool* pool)
|
|||
mid = pool->size / 2;
|
||||
bot = pool->size - 1;
|
||||
index = -1;
|
||||
|
||||
// 在池中查找插入位置
|
||||
while (index == -1) {
|
||||
/* these 4 cases find a new location */
|
||||
if (chromo->worth <= pool->data[top].worth) {
|
||||
|
|
@ -191,7 +193,7 @@ void spread_chromo(PlannerInfo* root, Chromosome* chromo, Pool* pool)
|
|||
/*
|
||||
* copy new gene into pool storage; always replace worst gene in pool
|
||||
*/
|
||||
geqo_copy(root, &pool->data[pool->size - 1], chromo, pool->string_length);
|
||||
geqo_copy(root, &pool->data[pool->size - 1], chromo, pool->string_length);// 复制染色体并维护池的排序顺序
|
||||
|
||||
swap_chromo.string = pool->data[pool->size - 1].string;
|
||||
swap_chromo.worth = pool->data[pool->size - 1].worth;
|
||||
|
|
@ -207,3 +209,9 @@ void spread_chromo(PlannerInfo* root, Chromosome* chromo, Pool* pool)
|
|||
swap_chromo.worth = tmp_chromo.worth;
|
||||
}
|
||||
}
|
||||
/*
|
||||
这些代码涉及到遗传算法中的池管理和排序操作。
|
||||
它们用于分配和释放内存、随机初始化染色体、对池中的染色体按适应度进行排序以及插入新染色体并维护排序顺序。
|
||||
compare 函数用于比较两个染色体的适应度,以便在排序时使用。
|
||||
这些操作是遗传算法中的关键步骤,用于生成和维护染色体集合。
|
||||
*/
|
||||
|
|
@ -41,20 +41,30 @@
|
|||
*
|
||||
* position crossover
|
||||
*/
|
||||
// 实现部分顺序交叉算子
|
||||
/*
|
||||
//PlannerInfo* root 查询优化器的信息结构
|
||||
//Gene* tour1 第一个染色体
|
||||
//Gene* tour2 第二个染色体
|
||||
//Gene* offspring 生成的子代染色体
|
||||
//int num_gene 基因的数量
|
||||
//City* city_table 城市信息表,用于标记城市是否已被使用
|
||||
*/
|
||||
|
||||
void px(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_gene, City* city_table)
|
||||
{
|
||||
int num_positions;
|
||||
int i, pos, tour2_index, offspring_index;
|
||||
int num_positions; // 用于存储要交叉的位置数量
|
||||
int i, pos, tour2_index, offspring_index;// 循环变量和临时位置变量和用于追踪tour2和offspring中的位置
|
||||
|
||||
/* initialize city table */
|
||||
for (i = 1; i <= num_gene; i++)
|
||||
for (i = 1; i <= num_gene; i++)// 初始化城市使用标志,标记城市是否已被使用
|
||||
city_table[i].used = 0;
|
||||
|
||||
/* choose random positions that will be inherited directly from parent */
|
||||
num_positions = geqo_randint(root, 2 * num_gene / 3, num_gene / 3);
|
||||
num_positions = geqo_randint(root, 2 * num_gene / 3, num_gene / 3);// 随机确定要交叉的位置数量
|
||||
|
||||
/* choose random position */
|
||||
for (i = 0; i < num_positions; i++) {
|
||||
for (i = 0; i < num_positions; i++) {// 随机选择并复制部分基因片段从tour1到offspring,并标记城市为已使用
|
||||
pos = geqo_randint(root, num_gene - 1, 0);
|
||||
|
||||
offspring[pos] = tour1[pos]; /* transfer cities to child */
|
||||
|
|
@ -65,7 +75,7 @@ void px(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_ge
|
|||
offspring_index = 0;
|
||||
|
||||
/* px main part */
|
||||
while (offspring_index < num_gene) {
|
||||
while (offspring_index < num_gene) {// 从tour2中选择未被标记为已使用的城市,将其添加到offspring中
|
||||
/* next position in offspring filled */
|
||||
if (!city_table[(int)tour1[offspring_index]].used) {
|
||||
/* next city in tour1 not used */
|
||||
|
|
@ -82,3 +92,8 @@ void px(PlannerInfo* root, Gene* tour1, Gene* tour2, Gene* offspring, int num_ge
|
|||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
这段代码实现了部分顺序交叉(PX)算子,用于在遗传算法中交叉两个染色体(tour1和tour2)以生成一个新的染色体(offspring)。
|
||||
在PX算子中,首先随机选择一定数量的基因位置来交叉,然后将这些位置上的基因从tour1复制到offspring,并将相应的城市标记为已使用。
|
||||
接着,从tour2中选择未被标记为已使用的城市,并将它们添加到offspring中,以确保生成的染色体包含tour1和tour2的信息。
|
||||
*/
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#include "optimizer/geqo_random.h"
|
||||
|
||||
void geqo_set_seed(PlannerInfo* root, double seed)
|
||||
void geqo_set_seed(PlannerInfo* root, double seed)// 设置遗传查询优化的随机种子
|
||||
{
|
||||
GeqoPrivateData* priv = (GeqoPrivateData*)root->join_search_private;
|
||||
|
||||
|
|
@ -26,15 +26,19 @@ void geqo_set_seed(PlannerInfo* root, double seed)
|
|||
* critical to do so.
|
||||
*/
|
||||
errno_t rc = EOK;
|
||||
rc = memset_s(priv->random_state, sizeof(priv->random_state), 0, sizeof(priv->random_state));
|
||||
rc = memset_s(priv->random_state, sizeof(priv->random_state), 0, sizeof(priv->random_state));// 使用memset_s将随机状态数组初始化为0
|
||||
securec_check(rc, "\0", "\0");
|
||||
// 使用memcpy_s将给定的种子复制到随机状态数组中
|
||||
rc = memcpy_s(priv->random_state, sizeof(priv->random_state), &seed, Min(sizeof(priv->random_state), sizeof(seed)));
|
||||
securec_check(rc, "\0", "\0");
|
||||
}
|
||||
|
||||
// 生成随机数(0到1之间)用于遗传查询优化
|
||||
double geqo_rand(PlannerInfo* root)
|
||||
{
|
||||
GeqoPrivateData* priv = (GeqoPrivateData*)root->join_search_private;
|
||||
|
||||
// 使用erand48函数生成随机数,基于随机状态数组
|
||||
return erand48(priv->random_state);
|
||||
}
|
||||
/*
|
||||
第一个函数用于设置随机种子,第二个函数用于生成遗传查询优化中的随机数。
|
||||
*/
|
||||
|
|
@ -32,21 +32,21 @@
|
|||
* this array. When a city is chosen, the array is shortened
|
||||
* and the procedure repeated.
|
||||
*/
|
||||
void init_tour(PlannerInfo* root, Gene* tour, int num_gene)
|
||||
void init_tour(PlannerInfo* root, Gene* tour, int num_gene)// 初始化一个遍历染色体(tour)的函数
|
||||
{
|
||||
Gene* tmp = NULL;
|
||||
int remainder;
|
||||
int next, i;
|
||||
Gene* tmp = NULL;// 临时数组,用于初始化基因
|
||||
int remainder;// 剩余未处理的基因数量
|
||||
int next, i;// 下一个基因的位置和循环变量
|
||||
|
||||
/* Fill a temp array with the IDs of all not-yet-visited cities */
|
||||
tmp = (Gene*)palloc(num_gene * sizeof(Gene));
|
||||
tmp = (Gene*)palloc(num_gene * sizeof(Gene));// 分配临时数组以初始化基因
|
||||
|
||||
for (i = 0; i < num_gene; i++)
|
||||
tmp[i] = (Gene)(i + 1);
|
||||
|
||||
remainder = num_gene - 1;
|
||||
|
||||
for (i = 0; i < num_gene; i++) {
|
||||
for (i = 0; i < num_gene; i++) {// 随机生成遍历顺序,初始化染色体
|
||||
/* choose value between 0 and remainder inclusive */
|
||||
next = geqo_randint(root, remainder, 0);
|
||||
/* output that element of the tmp array */
|
||||
|
|
@ -56,6 +56,7 @@ void init_tour(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
remainder--;
|
||||
}
|
||||
|
||||
// 释放临时数组内存
|
||||
pfree_ext(tmp);
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +64,7 @@ void init_tour(PlannerInfo* root, Gene* tour, int num_gene)
|
|||
*
|
||||
* allocate memory for city table
|
||||
*/
|
||||
City* alloc_city_table(PlannerInfo* root, int num_gene)
|
||||
City* alloc_city_table(PlannerInfo* root, int num_gene)// 分配城市信息表的内存
|
||||
{
|
||||
City* city_table = NULL;
|
||||
|
||||
|
|
@ -80,7 +81,10 @@ City* alloc_city_table(PlannerInfo* root, int num_gene)
|
|||
*
|
||||
* deallocate memory of city table
|
||||
*/
|
||||
void free_city_table(PlannerInfo* root, City* city_table)
|
||||
void free_city_table(PlannerInfo* root, City* city_table)// 释放城市信息表的内存
|
||||
{
|
||||
pfree_ext(city_table);
|
||||
}
|
||||
/*
|
||||
这些代码包括了初始化遍历染色体的函数、分配城市信息表内存的函数以及释放城市信息表内存的函数
|
||||
*/
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
#include "optimizer/geqo_copy.h"
|
||||
#include "optimizer/geqo_random.h"
|
||||
#include "optimizer/geqo_selection.h"
|
||||
|
||||
// 线性随机数生成函数,用于选择遗传算法中的父代
|
||||
static int linear_rand(PlannerInfo* root, int max, double bias);
|
||||
|
||||
/*
|
||||
|
|
@ -49,11 +49,13 @@ static int linear_rand(PlannerInfo* root, int max, double bias);
|
|||
* according to bias described by input parameters,
|
||||
* first and second genes are selected from the pool
|
||||
*/
|
||||
void geqo_selection(PlannerInfo* root, Chromosome* momma, Chromosome* daddy, Pool* pool, double bias)
|
||||
void geqo_selection(PlannerInfo* root, Chromosome* momma, Chromosome* daddy, Pool* pool, double bias)// 遗传算法中的选择操作,选择两个染色体作为父代
|
||||
|
||||
{
|
||||
int first, second;
|
||||
|
||||
// 使用linear_rand函数随机选择第一个父代
|
||||
first = linear_rand(root, pool->size, bias);
|
||||
// 使用linear_rand函数随机选择第二个父代
|
||||
second = linear_rand(root, pool->size, bias);
|
||||
|
||||
/*
|
||||
|
|
@ -64,11 +66,11 @@ void geqo_selection(PlannerInfo* root, Chromosome* momma, Chromosome* daddy, Poo
|
|||
* platform's implementation of erand48() was broken. We now always use
|
||||
* our own version.
|
||||
*/
|
||||
if (pool->size > 1) {
|
||||
if (pool->size > 1) {// 确保选择的两个父代不相同(如果池中有多于一个染色体)
|
||||
while (first == second)
|
||||
second = linear_rand(root, pool->size, bias);
|
||||
}
|
||||
|
||||
// 复制选定的父代到momma和daddy中
|
||||
geqo_copy(root, momma, &pool->data[first], pool->string_length);
|
||||
geqo_copy(root, daddy, &pool->data[second], pool->string_length);
|
||||
}
|
||||
|
|
@ -83,10 +85,10 @@ void geqo_selection(PlannerInfo* root, Chromosome* momma, Chromosome* daddy, Poo
|
|||
* probability distribution function is: f(x) = bias - 2(bias - 1)x
|
||||
* bias = (prob of first rule) / (prob of middle rule)
|
||||
*/
|
||||
static int linear_rand(PlannerInfo* root, int pool_size, double bias)
|
||||
static int linear_rand(PlannerInfo* root, int pool_size, double bias)// 线性随机数生成函数,用于选择遗传算法中的父代
|
||||
{
|
||||
double index; /* index between 0 and pop_size */
|
||||
double max = (double)pool_size;
|
||||
double index; /* index between 0 and pop_size */// 生成的随机索引值
|
||||
double max = (double)pool_size;// 池的最大大小
|
||||
|
||||
/*
|
||||
* If geqo_rand() returns exactly 1.0 then we will get exactly max from
|
||||
|
|
@ -96,8 +98,8 @@ static int linear_rand(PlannerInfo* root, int pool_size, double bias)
|
|||
* sqrt(). If we get a bad value just try again.
|
||||
*/
|
||||
do {
|
||||
double sqrtval;
|
||||
|
||||
double sqrtval; // 平方根值
|
||||
// 根据bias和随机数生成index值
|
||||
sqrtval = (bias * bias) - 4.0 * (bias - 1.0) * geqo_rand(root);
|
||||
if (sqrtval > 0.0)
|
||||
sqrtval = sqrt(sqrtval);
|
||||
|
|
|
|||
|
|
@ -662,125 +662,145 @@ static List* switch_arg_items(Node* funExpr, Const* cnst, Oid* eqlOprOid, Oid* i
|
|||
return NIL;
|
||||
}
|
||||
|
||||
CoercionPathType pathtype = find_coercion_pathway(argType, constType, COERCION_IMPLICIT, &funcId);
|
||||
// 查找强制类型转换的路径,获取pathtype(强制转换路径的类型)、funcId(强制转换函数的ID)
|
||||
CoercionPathType pathtype = find_coercion_pathway(argType, constType, COERCION_IMPLICIT, &funcId);
|
||||
|
||||
if (pathtype != COERCION_PATH_NONE) {
|
||||
MemoryContext current_context = CurrentMemoryContext;
|
||||
bool outer_is_stream = false;
|
||||
bool outer_is_stream_support = false;
|
||||
ResourceOwner currentOwner = t_thrd.utils_cxt.CurrentResourceOwner;
|
||||
ResourceOwner tempOwner = ResourceOwnerCreate(t_thrd.utils_cxt.CurrentResourceOwner, "SwitchArgItems",
|
||||
THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER));
|
||||
t_thrd.utils_cxt.CurrentResourceOwner = tempOwner;
|
||||
// 如果存在强制转换路径
|
||||
if (pathtype != COERCION_PATH_NONE) {
|
||||
// 保存当前内存上下文
|
||||
MemoryContext current_context = CurrentMemoryContext;
|
||||
bool outer_is_stream = false;
|
||||
bool outer_is_stream_support = false;
|
||||
// 保存当前资源拥有者
|
||||
ResourceOwner currentOwner = t_thrd.utils_cxt.CurrentResourceOwner;
|
||||
// 创建临时资源拥有者
|
||||
ResourceOwner tempOwner = ResourceOwnerCreate(t_thrd.utils_cxt.CurrentResourceOwner, "SwitchArgItems",
|
||||
THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER));
|
||||
t_thrd.utils_cxt.CurrentResourceOwner = tempOwner;
|
||||
|
||||
if (IS_PGXC_COORDINATOR) {
|
||||
outer_is_stream = u_sess->opt_cxt.is_stream;
|
||||
outer_is_stream_support = u_sess->opt_cxt.is_stream_support;
|
||||
}
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
constValue = OidFunctionCall1(funcId, ((Const*)cnst)->constvalue);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
MemoryContextSwitchTo(current_context);
|
||||
FlushErrorState();
|
||||
|
||||
/* in case they are not set back */
|
||||
if (IS_PGXC_COORDINATOR) {
|
||||
u_sess->opt_cxt.is_stream = outer_is_stream;
|
||||
u_sess->opt_cxt.is_stream_support = outer_is_stream_support;
|
||||
}
|
||||
|
||||
/* release resource applied in OidFunctionCall1 of the PG_TRY. */
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_LOCKS, false, false);
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
|
||||
t_thrd.utils_cxt.CurrentResourceOwner = currentOwner;
|
||||
ResourceOwnerDelete(tempOwner);
|
||||
|
||||
return NIL;
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
/* release resource applied in standard_planner of the PG_TRY. */
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_LOCKS, false, false);
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
|
||||
t_thrd.utils_cxt.CurrentResourceOwner = currentOwner;
|
||||
ResourceOwnerDelete(tempOwner);
|
||||
|
||||
if (IS_PGXC_COORDINATOR) {
|
||||
u_sess->opt_cxt.is_stream = outer_is_stream;
|
||||
u_sess->opt_cxt.is_stream_support = outer_is_stream_support;
|
||||
}
|
||||
}
|
||||
|
||||
if (constValue) {
|
||||
typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argType));
|
||||
if (!HeapTupleIsValid(typeTuple)) {
|
||||
return NIL;
|
||||
}
|
||||
Form_pg_type type = (Form_pg_type)GETSTRUCT(typeTuple);
|
||||
cnp = makeConst(
|
||||
argType, exprTypmod(arg), type->typcollation, type->typlen, constValue, false, type->typbyval);
|
||||
ReleaseSysCache(typeTuple);
|
||||
}
|
||||
}
|
||||
if (cnp != NULL) {
|
||||
argList = lappend(argList, arg);
|
||||
argList = lappend(argList, cnp);
|
||||
|
||||
if (argType == VARCHAROID) {
|
||||
argType = TEXTOID;
|
||||
}
|
||||
|
||||
HeapTuple opertup;
|
||||
opertup = SearchSysCache4(OPERNAMENSP,
|
||||
CStringGetDatum(isequal ? "=" : "<>"),
|
||||
ObjectIdGetDatum(argType),
|
||||
ObjectIdGetDatum(argType),
|
||||
ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
|
||||
if (!HeapTupleIsValid(opertup)) {
|
||||
return NIL;
|
||||
}
|
||||
|
||||
*eqlOprOid = HeapTupleGetOid(opertup);
|
||||
|
||||
ReleaseSysCache(opertup);
|
||||
|
||||
*inputcollid = exprCollation(arg);
|
||||
// 如果当前运行在PGXC协调器上
|
||||
if (IS_PGXC_COORDINATOR) {
|
||||
// 保存原始的流式查询标志和流式查询支持标志
|
||||
outer_is_stream = u_sess->opt_cxt.is_stream;
|
||||
outer_is_stream_support = u_sess->opt_cxt.is_stream_support;
|
||||
}
|
||||
|
||||
return argList;
|
||||
// 在PG_TRY块中执行强制转换函数,捕获可能的异常
|
||||
PG_TRY();
|
||||
{
|
||||
// 调用强制转换函数,将常量值转换为目标类型
|
||||
constValue = OidFunctionCall1(funcId, ((Const*)cnst)->constvalue);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
// 切换回原来的内存上下文
|
||||
MemoryContextSwitchTo(current_context);
|
||||
FlushErrorState();
|
||||
|
||||
// 在PGXC协调器上,恢复流式查询标志和流式查询支持标志
|
||||
if (IS_PGXC_COORDINATOR) {
|
||||
u_sess->opt_cxt.is_stream = outer_is_stream;
|
||||
u_sess->opt_cxt.is_stream_support = outer_is_stream_support;
|
||||
}
|
||||
|
||||
// 释放在PG_TRY中申请的资源
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_LOCKS, false, false);
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
|
||||
t_thrd.utils_cxt.CurrentResourceOwner = currentOwner;
|
||||
ResourceOwnerDelete(tempOwner);
|
||||
|
||||
// 返回一个空列表(NIL)
|
||||
return NIL;
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
// 释放在PG_TRY块中申请的资源
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_LOCKS, false, false);
|
||||
ResourceOwnerRelease(tempOwner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
|
||||
t_thrd.utils_cxt.CurrentResourceOwner = currentOwner;
|
||||
ResourceOwnerDelete(tempOwner);
|
||||
|
||||
// 如果在PGXC协调器上,恢复流式查询标志和流式查询支持标志
|
||||
if (IS_PGXC_COORDINATOR) {
|
||||
u_sess->opt_cxt.is_stream = outer_is_stream;
|
||||
u_sess->opt_cxt.is_stream_support = outer_is_stream_support;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果成功执行强制类型转换,constValue将不为NULL
|
||||
if (constValue) {
|
||||
// 在系统缓存中查找目标类型的元组
|
||||
typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argType));
|
||||
// 如果找不到目标类型的元组,返回一个空列表(NIL)
|
||||
if (!HeapTupleIsValid(typeTuple)) {
|
||||
return NIL;
|
||||
}
|
||||
// 获取目标类型的元组数据
|
||||
Form_pg_type type = (Form_pg_type)GETSTRUCT(typeTuple);
|
||||
// 创建一个新的常量节点,表示转换后的常量
|
||||
cnp = makeConst(
|
||||
argType, exprTypmod(arg), type->typcollation, type->typlen, constValue, false, type->typbyval);
|
||||
// 释放系统缓存中的目标类型元组
|
||||
ReleaseSysCache(typeTuple);
|
||||
}
|
||||
|
||||
// 如果cnp不为空,表示成功创建了新的常量节点
|
||||
if (cnp != NULL) {
|
||||
// 将原始参数和新的常量节点添加到参数列表中
|
||||
argList = lappend(argList, arg);
|
||||
argList = lappend(argList, cnp);
|
||||
|
||||
// 如果目标类型是VARCHAROID,则将argType更改为TEXTOID
|
||||
if (argType == VARCHAROID) {
|
||||
argType = TEXTOID;
|
||||
}
|
||||
|
||||
HeapTuple opertup;
|
||||
// 在系统缓存中查找等于(=)或不等于(<>)操作符的元组
|
||||
opertup = SearchSysCache4(OPERNAMENSP,
|
||||
CStringGetDatum(isequal ? "=" : "<>"),
|
||||
ObjectIdGetDatum(argType),
|
||||
ObjectIdGetDatum(argType),
|
||||
ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
|
||||
// 如果找不到操作符的元组,返回一个空列表(NIL)
|
||||
if (!HeapTupleIsValid(opertup)) {
|
||||
return NIL;
|
||||
}
|
||||
|
||||
// 获取操作符的OID并存储在eqlOprOid指针中
|
||||
*eqlOprOid = HeapTupleGetOid(opertup);
|
||||
|
||||
// 释放系统缓存中的操作符元组
|
||||
ReleaseSysCache(opertup);
|
||||
|
||||
// 获取表达式的排序规则(collation)
|
||||
*inputcollid = exprCollation(arg);
|
||||
}
|
||||
|
||||
// 返回参数列表
|
||||
return argList;
|
||||
|
||||
static List* do_restrictinfo_conversion(List* args, Oid* eqlOprOid, Oid* inputcollid, bool isequal)
|
||||
{
|
||||
AssertEreport(list_length(args) == 2, MOD_OPT, "");
|
||||
|
||||
bool lIsConst = false;
|
||||
AssertEreport(list_length(args) == 2, MOD_OPT, "");// 使用 AssertEreport 断言确保传入的参数列表 args 包含且仅包含两个参数,否则引发错误
|
||||
bool lIsConst = false;//用于表示左参数和右参数是否为常量
|
||||
bool rIsConst = false;
|
||||
Node* lNode = (Node*)linitial(args);
|
||||
Node* lNode = (Node*)linitial(args);// 从参数列表 args 中提取左参数 lNode 和右参数 rNode。
|
||||
Node* rNode = (Node*)list_nth(args, 1);
|
||||
|
||||
List* argsList = NULL;
|
||||
|
||||
if (IsA(lNode, Const)) {
|
||||
lIsConst = true;
|
||||
}
|
||||
|
||||
if (IsA(rNode, Const)) {
|
||||
rIsConst = true;
|
||||
}
|
||||
|
||||
if (lIsConst == true && rIsConst == false) {
|
||||
if (lIsConst == true && rIsConst == false) {// 调用 switch_arg_items 函数,将右参数 rNode 视为常量,将左参数 lNode 视为函数参数,并将结果存储在 argsList 中
|
||||
argsList = switch_arg_items(rNode, (Const*)lNode, eqlOprOid, inputcollid, isequal);
|
||||
} else if (lIsConst == false && rIsConst == true) {
|
||||
argsList = switch_arg_items(lNode, (Const*)rNode, eqlOprOid, inputcollid, isequal);
|
||||
}
|
||||
|
||||
return argsList;
|
||||
}
|
||||
|
||||
|
|
@ -798,19 +818,22 @@ static List* do_restrictinfo_conversion(List* args, Oid* eqlOprOid, Oid* inputco
|
|||
*
|
||||
* Returns: void
|
||||
*/
|
||||
// 定义一个静态函数get_vardata_for_filter_or_semijoin,接受多个参数
|
||||
static void get_vardata_for_filter_or_semijoin(
|
||||
PlannerInfo* root, Node* clause, int varRelid, Selectivity selec, SpecialJoinInfo* sjinfo, RatioType type)
|
||||
{
|
||||
// 创建get_vardata_for_filter_or_semijoin_context结构体变量context,用于存储上下文信息
|
||||
get_vardata_for_filter_or_semijoin_context context;
|
||||
bool vardataIsValid = false;
|
||||
|
||||
/* construct context members. */
|
||||
context.root = root;
|
||||
context.varRelid = varRelid;
|
||||
context.ratiotype = type;
|
||||
context.sjinfo = sjinfo;
|
||||
/* 构建上下文成员 */
|
||||
context.root = root; // 存储PlannerInfo指针
|
||||
context.varRelid = varRelid; // 存储关联变量的Relid
|
||||
context.ratiotype = type; // 存储比率类型
|
||||
context.sjinfo = sjinfo; // 存储特殊连接信息
|
||||
errno_t rc = EOK;
|
||||
|
||||
// 初始化context中的VariableStatData结构体成员为0
|
||||
rc = memset_s(&context.filter_vardata, sizeof(VariableStatData), 0, sizeof(VariableStatData));
|
||||
securec_check(rc, "\0", "\0");
|
||||
rc = memset_s(&context.semijoin_vardata1, sizeof(VariableStatData), 0, sizeof(VariableStatData));
|
||||
|
|
@ -818,36 +841,43 @@ static void get_vardata_for_filter_or_semijoin(
|
|||
rc = memset_s(&context.semijoin_vardata2, sizeof(VariableStatData), 0, sizeof(VariableStatData));
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
/* get vardata walker for clause. */
|
||||
/* 获取clause中的变量数据 */
|
||||
// 调用get_vardata_for_filter_or_semijoin_walker函数,获取变量数据
|
||||
vardataIsValid = get_vardata_for_filter_or_semijoin_walker(clause, &context);
|
||||
|
||||
/* we don't need set var ratio if vardata is invalid. */
|
||||
/* 如果变量数据无效,不需要设置变量比率,直接返回 */
|
||||
if (!vardataIsValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* set var ratio for filter or semi/anti join. */
|
||||
/* 设置过滤器或半连接/反半连接的变量比率 */
|
||||
if (RatioType_Filter == type) {
|
||||
set_varratio_after_calc_selectivity(&context.filter_vardata, RatioType_Filter, selec, NULL);
|
||||
// 释放VariableStatData资源
|
||||
ReleaseVariableStats(context.filter_vardata);
|
||||
} else {
|
||||
set_varratio_after_calc_selectivity(&context.semijoin_vardata1, RatioType_Join, selec, sjinfo);
|
||||
set_varratio_after_calc_selectivity(&context.semijoin_vardata2, RatioType_Join, selec, sjinfo);
|
||||
|
||||
// 释放VariableStatData资源
|
||||
ReleaseVariableStats(context.semijoin_vardata1);
|
||||
ReleaseVariableStats(context.semijoin_vardata2);
|
||||
}
|
||||
}
|
||||
|
||||
// 定义一个函数getVardataFromScalarArray,用于从标量数组中获取变量数据
|
||||
void getVardataFromScalarArray(Node* node, get_vardata_for_filter_or_semijoin_context* context)
|
||||
{
|
||||
Node* left = NULL;
|
||||
// 如果比率类型是Join
|
||||
if (RatioType_Join == context->ratiotype) {
|
||||
bool join_is_reversed = false;
|
||||
// 调用get_join_variables函数,获取连接中的变量数据
|
||||
get_join_variables(context->root, ((ScalarArrayOpExpr*)node)->args, context->sjinfo,
|
||||
&context->semijoin_vardata1, &context->semijoin_vardata2, &join_is_reversed);
|
||||
} else {
|
||||
left = (Node*)linitial(((ScalarArrayOpExpr*)node)->args);
|
||||
// 调用examine_variable函数,检查变量数据
|
||||
examine_variable(context->root, left, context->varRelid, &context->filter_vardata);
|
||||
}
|
||||
}
|
||||
|
|
@ -861,39 +891,41 @@ void getVardataFromScalarArray(Node* node, get_vardata_for_filter_or_semijoin_co
|
|||
*
|
||||
* Returns: bool(true:vardata is valid)
|
||||
*/
|
||||
// 定义静态函数get_vardata_for_filter_or_semijoin_walker,用于获取变量数据
|
||||
static bool get_vardata_for_filter_or_semijoin_walker(Node* node, get_vardata_for_filter_or_semijoin_context* context)
|
||||
{
|
||||
List* args = NIL;
|
||||
Node* other = NULL;
|
||||
Node* left = NULL;
|
||||
Node* clause = NULL;
|
||||
bool varonleft = false;
|
||||
List* args = NIL; // 用于存储参数列表
|
||||
Node* other = NULL; // 用于存储另一个节点
|
||||
Node* left = NULL; // 用于存储左节点
|
||||
Node* clause = NULL; // 用于存储子节点
|
||||
bool varonleft = false; // 标志变量是否在左侧
|
||||
|
||||
if (node == NULL)
|
||||
return false;
|
||||
|
||||
/* get vardata info from different clause's args. */
|
||||
if (IsA(node, Var)) {
|
||||
/* 从不同子节点中获取变量数据 */
|
||||
if (IsA(node, Var)) { // 如果节点是一个变量
|
||||
Var* var = (Var*)node;
|
||||
|
||||
/*
|
||||
* We probably shouldn't ever see an uplevel Var here, but if we do,
|
||||
* return the default selectivity...
|
||||
* 我们可能不应该在这里看到uplevel Var,但如果出现,返回默认选择度...
|
||||
*/
|
||||
if (var->varlevelsup == 0 && (context->varRelid == 0 || context->varRelid == (int)var->varno)) {
|
||||
// 检查变量数据
|
||||
examine_variable(context->root, (Node*)var, context->varRelid, &context->filter_vardata);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (not_clause(node)) {
|
||||
} else if (not_clause(node)) { // 如果节点是NOT表达式
|
||||
clause = (Node*)get_notclausearg((Expr*)node);
|
||||
} else if (is_opclause(node)) {
|
||||
} else if (is_opclause(node)) { // 如果节点是操作符表达式
|
||||
OpExpr* opclause = (OpExpr*)node;
|
||||
Oid opno = opclause->opno;
|
||||
|
||||
if (RatioType_Join == context->ratiotype) {
|
||||
if (RatioType_Join == context->ratiotype) { // 如果是连接比率类型
|
||||
bool join_is_reversed = false;
|
||||
// 获取连接中的变量数据
|
||||
get_join_variables(context->root, opclause->args, context->sjinfo, &context->semijoin_vardata1,
|
||||
&context->semijoin_vardata2, &join_is_reversed);
|
||||
return true;
|
||||
|
|
@ -901,8 +933,9 @@ static bool get_vardata_for_filter_or_semijoin_walker(Node* node, get_vardata_fo
|
|||
Oid eqlOprOid = 0;
|
||||
List* argList = NULL;
|
||||
Oid inputcollid = 0;
|
||||
/* only handle = or <> operator */
|
||||
/* 只处理=或<>操作符 */
|
||||
if (get_oprrest(opno) == EQSELRETURNOID || get_oprrest(opno) == NEQSELRETURNOID) {
|
||||
// 进行操作符参数的转换
|
||||
argList = do_restrictinfo_conversion(
|
||||
opclause->args, &eqlOprOid, &inputcollid, get_oprrest(opno) == EQSELRETURNOID);
|
||||
}
|
||||
|
|
@ -912,30 +945,36 @@ static bool get_vardata_for_filter_or_semijoin_walker(Node* node, get_vardata_fo
|
|||
else
|
||||
args = opclause->args;
|
||||
|
||||
// 获取限制条件中的变量数据
|
||||
return get_restriction_variable(
|
||||
context->root, args, context->varRelid, &context->filter_vardata, &other, &varonleft);
|
||||
}
|
||||
} else if (IsA(node, ScalarArrayOpExpr)) {
|
||||
} else if (IsA(node, ScalarArrayOpExpr)) { // 如果节点是标量数组操作符表达式
|
||||
// 从标量数组中获取变量数据
|
||||
getVardataFromScalarArray(node, context);
|
||||
return true;
|
||||
} else if (IsA(node, RowCompareExpr)) {
|
||||
} else if (IsA(node, RowCompareExpr)) { // 如果节点是行比较表达式
|
||||
args = list_make2(linitial(((RowCompareExpr*)node)->largs), linitial(((RowCompareExpr*)node)->rargs));
|
||||
// 获取限制条件中的变量数据
|
||||
return get_restriction_variable(
|
||||
context->root, args, context->varRelid, &context->filter_vardata, &other, &varonleft);
|
||||
} else if (IsA(node, NullTest)) {
|
||||
} else if (IsA(node, NullTest)) { // 如果节点是Null测试表达式
|
||||
left = (Node*)((NullTest*)node)->arg;
|
||||
// 检查变量数据
|
||||
examine_variable(context->root, left, context->varRelid, &context->filter_vardata);
|
||||
return true;
|
||||
} else if (IsA(node, BooleanTest)) {
|
||||
} else if (IsA(node, BooleanTest)) { // 如果节点是布尔测试表达式
|
||||
left = (Node*)((BooleanTest*)node)->arg;
|
||||
// 检查变量数据
|
||||
examine_variable(context->root, left, context->varRelid, &context->filter_vardata);
|
||||
return true;
|
||||
} else if (IsA(node, RelabelType)) {
|
||||
} else if (IsA(node, RelabelType)) { // 如果节点是重新标记类型表达式
|
||||
clause = (Node*)((RelabelType*)node)->arg;
|
||||
} else if (IsA(node, CoerceToDomain)) {
|
||||
} else if (IsA(node, CoerceToDomain)) { // 如果节点是强制转换为域类型表达式
|
||||
clause = (Node*)((CoerceToDomain*)node)->arg;
|
||||
}
|
||||
|
||||
// 递归处理子节点
|
||||
return get_vardata_for_filter_or_semijoin_walker(clause, context);
|
||||
}
|
||||
|
||||
|
|
@ -950,8 +989,10 @@ static bool get_vardata_for_filter_or_semijoin_walker(Node* node, get_vardata_fo
|
|||
* Returns: bool(true:the clause is range query)
|
||||
*/
|
||||
static bool is_rangequery_clause(Node* clause, RestrictInfo* rinfo, bool* varonleft)
|
||||
// 定义静态函数is_rangequery_clause,用于检测是否是范围查询子句
|
||||
{
|
||||
bool isrqclause = false;
|
||||
// 初始化isrqclause为假
|
||||
|
||||
/*
|
||||
* See if it looks like a restriction clause with a pseudoconstant on
|
||||
|
|
@ -959,24 +1000,29 @@ static bool is_rangequery_clause(Node* clause, RestrictInfo* rinfo, bool* varonl
|
|||
* the simple way we are expecting.) Most of the tests here can be
|
||||
* done more efficiently with rinfo than without.
|
||||
*/
|
||||
// 如果是操作符表达式并且参数个数为2
|
||||
if (is_opclause(clause) && list_length(((OpExpr*)clause)->args) == 2) {
|
||||
OpExpr* expr = (OpExpr*)clause;
|
||||
OpExpr* expr = (OpExpr*)clause; // 强制转换为操作符表达式类型
|
||||
|
||||
// 如果有rinfo信息
|
||||
if (rinfo != NULL) {
|
||||
// 检查rinfo的clause_relids成员是否只包含一个集合成员
|
||||
// 同时检查表达式的左侧或右侧是否为伪常量子句
|
||||
isrqclause = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) &&
|
||||
(is_pseudo_constant_clause_relids((Node*)lsecond(expr->args), rinfo->right_relids) ||
|
||||
(*varonleft = false,
|
||||
is_pseudo_constant_clause_relids((Node*)linitial(expr->args), rinfo->left_relids)));
|
||||
} else {
|
||||
} else { // 如果没有rinfo信息
|
||||
// 检查clause的NumRelids是否为1
|
||||
// 同时检查表达式的左侧或右侧是否为伪常量子句
|
||||
isrqclause = (NumRelids(clause) == 1) &&
|
||||
(is_pseudo_constant_clause((Node*)lsecond(expr->args)) ||
|
||||
(*varonleft = false, is_pseudo_constant_clause((Node*)linitial(expr->args))));
|
||||
}
|
||||
}
|
||||
|
||||
return isrqclause;
|
||||
return isrqclause; // 返回是否是范围查询子句的结果
|
||||
}
|
||||
|
||||
/*
|
||||
* is_rangequery_contain_scalarop: if the range query contain scalar operator or not.
|
||||
*
|
||||
|
|
@ -986,20 +1032,24 @@ static bool is_rangequery_clause(Node* clause, RestrictInfo* rinfo, bool* varonl
|
|||
*
|
||||
* Returns: bool(true:the range query contain scalar operator)
|
||||
*/
|
||||
// 定义静态函数is_rangequery_contain_scalarop,用于检测范围查询子句是否包含标量操作符
|
||||
static bool is_rangequery_contain_scalarop(Node* clause, RestrictInfo* rinfo)
|
||||
{
|
||||
bool varonleft = false;
|
||||
bool varonleft = false; // 初始化变量varonleft为假
|
||||
|
||||
// 调用is_rangequery_clause函数,检查子句是否符合范围查询条件,并获取varonleft值
|
||||
if (is_rangequery_clause(clause, rinfo, &varonleft)) {
|
||||
OpExpr* expr = (OpExpr*)clause;
|
||||
OpExpr* expr = (OpExpr*)clause; // 强制转换为操作符表达式类型
|
||||
|
||||
// 检查操作符的返回值是否为F_SCALARLTSEL或F_SCALARGTSEL
|
||||
if ((F_SCALARLTSEL == get_oprrest(expr->opno)) || (F_SCALARGTSEL == get_oprrest(expr->opno)))
|
||||
return true;
|
||||
return true; // 如果满足条件,返回真
|
||||
}
|
||||
|
||||
return false;
|
||||
return false; // 如果不满足条件,返回假
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* set_varratio_for_rqclause: set var ratio for range query clause.
|
||||
*
|
||||
|
|
@ -1013,22 +1063,27 @@ static bool is_rangequery_contain_scalarop(Node* clause, RestrictInfo* rinfo)
|
|||
*
|
||||
* Returns: void
|
||||
*/
|
||||
static void set_varratio_for_rqclause(
|
||||
static void set_varratio_for_rqclause(// 定义函数set_varratio_for_rqclause,用于为范围查询子句设置变量比率
|
||||
PlannerInfo* root, List* varlist, int varRelid, double ratio, SpecialJoinInfo* sjinfo)
|
||||
{
|
||||
ListCell* lc = NULL;
|
||||
ListCell* lc = NULL; // 定义链表迭代器
|
||||
|
||||
// 遍历变量列表
|
||||
foreach (lc, varlist) {
|
||||
VariableStatData vardata;
|
||||
Node* node = (Node*)lfirst(lc);
|
||||
VariableStatData vardata; // 定义变量统计数据结构体
|
||||
Node* node = (Node*)lfirst(lc); // 获取链表元素并强制转换为Node类型
|
||||
|
||||
// 调用examine_variable函数,检查变量数据
|
||||
examine_variable(root, node, varRelid, &vardata);
|
||||
|
||||
// 根据sjinfo是否为空,调用不同的函数设置变量比率
|
||||
if (sjinfo == NULL)
|
||||
set_varratio_after_calc_selectivity(&vardata, RatioType_Filter, ratio, NULL);
|
||||
else
|
||||
set_varratio_after_calc_selectivity(&vardata, RatioType_Join, ratio, sjinfo);
|
||||
|
||||
ReleaseVariableStats(vardata);
|
||||
ReleaseVariableStats(vardata); // 释放变量统计数据资源
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//这两个函数分别用于检测范围查询子句是否包含标量操作符,并为范围查询子句中的变量设置比率。第一个函数返回一个布尔值,指示是否包含标量操作符,第二个函数遍历变量列表,并根据条件设置变量比率。
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -71,35 +71,40 @@ static PathKey* makePathKey(EquivalenceClass* eclass, Oid opfamily, int strategy
|
|||
static PathKey* make_canonical_pathkey(
|
||||
PlannerInfo* root, EquivalenceClass* eclass, Oid opfamily, int strategy, bool nulls_first)
|
||||
{
|
||||
PathKey* pk = NULL;
|
||||
ListCell* lc = NULL;
|
||||
MemoryContext oldcontext;
|
||||
PathKey* pk = NULL; // 声明一个指向PathKey的指针,并将其初始化为NULL。
|
||||
ListCell* lc = NULL; // 声明一个指向ListCell的指针,并将其初始化为NULL。
|
||||
MemoryContext oldcontext; // 声明一个MemoryContext变量,命名为oldcontext。
|
||||
|
||||
/* The passed eclass might be non-canonical, so chase up to the top */
|
||||
// 遍历等价类(EquivalenceClass)的层次结构,直到达到顶层的等价类。
|
||||
while (eclass->ec_merged)
|
||||
eclass = eclass->ec_merged;
|
||||
|
||||
// 遍历PlannerInfo中的规范路径键(canonical pathkeys)列表。
|
||||
foreach (lc, root->canon_pathkeys) {
|
||||
/* Here need ensure ec_group_set be also equal. */
|
||||
// 获取列表中的下一个PathKey。
|
||||
pk = (PathKey*)lfirst(lc);
|
||||
|
||||
// 检查当前PathKey是否与给定的等价类(eclass)、操作族(opfamily)、策略(strategy)和nulls_first相匹配。
|
||||
if (eclass == pk->pk_eclass && eclass->ec_group_set == pk->pk_eclass->ec_group_set &&
|
||||
OpFamilyEquals(opfamily, pk->pk_opfamily) && strategy == pk->pk_strategy &&
|
||||
nulls_first == pk->pk_nulls_first)
|
||||
return pk;
|
||||
return pk; // 如果找到匹配的PathKey,则返回该PathKey。
|
||||
}
|
||||
|
||||
/*
|
||||
* Be sure canonical pathkeys are allocated in the main planning context.
|
||||
* Not an issue in normal planning, but it is for GEQO.
|
||||
*/
|
||||
// 切换到另一个内存上下文以进行分配。
|
||||
oldcontext = MemoryContextSwitchTo(root->planner_cxt);
|
||||
|
||||
// 使用提供的参数创建新的PathKey,并将其添加到规范路径键列表中。
|
||||
pk = makePathKey(eclass, opfamily, strategy, nulls_first);
|
||||
root->canon_pathkeys = lappend(root->canon_pathkeys, pk);
|
||||
|
||||
// 切换回原始内存上下文。
|
||||
(void)MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
return pk;
|
||||
return pk; // 返回创建的或现有的PathKey。
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -134,50 +139,52 @@ static PathKey* make_canonical_pathkey(
|
|||
*/
|
||||
static bool pathkey_is_redundant(PathKey* new_pathkey, List* pathkeys, bool predpush)
|
||||
{
|
||||
EquivalenceClass* new_ec = new_pathkey->pk_eclass;
|
||||
ListCell* lc = NULL;
|
||||
EquivalenceClass* new_ec = new_pathkey->pk_eclass; // 获取新路径键的等价类。
|
||||
|
||||
/* Assert we've been given canonical pathkeys */
|
||||
// 使用断言确保新等价类未合并。
|
||||
Assert(!new_ec->ec_merged);
|
||||
|
||||
/* Check for EC containing a constant --- unconditionally redundant */
|
||||
if (predpush) {
|
||||
/* skip the Param */
|
||||
bool have_const = false;
|
||||
if (EC_MUST_BE_REDUNDANT(new_ec))
|
||||
{
|
||||
if (predpush) { // 如果是谓词推送(predicate pushdown):
|
||||
|
||||
bool have_const = false; // 初始化一个标志,表示是否存在常量表达式。
|
||||
|
||||
if (EC_MUST_BE_REDUNDANT(new_ec)) { // 检查等价类是否必须是冗余的。
|
||||
|
||||
lc = NULL;
|
||||
foreach (lc, new_ec->ec_members) {
|
||||
foreach (lc, new_ec->ec_members) { // 遍历等价类的成员。
|
||||
EquivalenceMember *mem = (EquivalenceMember *)lfirst(lc);
|
||||
if (mem->em_is_const && !check_param_clause((Node *)mem->em_expr)) {
|
||||
have_const = true;
|
||||
have_const = true; // 如果存在常量表达式且不是参数子句,将标志设置为true。
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果存在常量且不在外连接下,且没有等价类组合,则认为是冗余的。
|
||||
if ((have_const && !new_ec->ec_below_outer_join) && !new_ec->ec_group_set)
|
||||
return true;
|
||||
|
||||
} else {
|
||||
} else { // 如果不是谓词推送:
|
||||
|
||||
// 如果等价类必须是冗余的且没有等价类组合,则认为是冗余的。
|
||||
if (EC_MUST_BE_REDUNDANT(new_ec) && !new_ec->ec_group_set)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If same EC already used in list, then redundant */
|
||||
foreach (lc, pathkeys) {
|
||||
PathKey* old_pathkey = (PathKey*)lfirst(lc);
|
||||
|
||||
/* Assert we've been given canonical pathkeys */
|
||||
Assert(!old_pathkey->pk_eclass->ec_merged);
|
||||
|
||||
if (new_ec == old_pathkey->pk_eclass)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
foreach (lc, pathkeys) { // 遍历路径键列表。
|
||||
PathKey* old_pathkey = (PathKey*)lfirst(lc); // 获取旧路径键。
|
||||
|
||||
// 使用断言确保旧路径键的等价类未合并。
|
||||
Assert(!old_pathkey->pk_eclass->ec_merged);
|
||||
|
||||
if (new_ec == old_pathkey->pk_eclass) // 如果新等价类与旧路径键的等价类相同,则认为是冗余的。
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // 如果没有冗余,返回false。
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* canonicalize_pathkeys
|
||||
* Convert a not-necessarily-canonical pathkeys list to canonical form.
|
||||
|
|
@ -228,33 +235,31 @@ List* canonicalize_pathkeys(PlannerInfo* root, List* pathkeys)
|
|||
*/
|
||||
List* remove_param_pathkeys(PlannerInfo* root, List* pathkeys)
|
||||
{
|
||||
List* new_pathkeys = NIL;
|
||||
ListCell* l = NULL;
|
||||
List* new_pathkeys = NIL; // 声明一个新的路径键列表,并初始化为空列表。
|
||||
ListCell* l = NULL; // 声明一个指向ListCell的指针,并初始化为空。
|
||||
|
||||
if (pathkeys == NULL)
|
||||
if (pathkeys == NULL) // 如果输入的路径键列表为空,直接返回空列表。
|
||||
return NULL;
|
||||
|
||||
foreach (l, pathkeys) {
|
||||
PathKey* pathkey = (PathKey*)lfirst(l);
|
||||
EquivalenceClass* eclass = NULL;
|
||||
foreach (l, pathkeys) { // 遍历输入的路径键列表。
|
||||
PathKey* pathkey = (PathKey*)lfirst(l); // 获取当前路径键。
|
||||
|
||||
/* Find the canonical (merged) EquivalenceClass */
|
||||
eclass = pathkey->pk_eclass;
|
||||
Assert(eclass->ec_merged == NULL);
|
||||
EquivalenceClass* eclass = NULL; // 声明一个指向等价类的指针,并初始化为空。
|
||||
|
||||
/*
|
||||
* If we can tell it's redundant just from the EC, skip.
|
||||
* pathkey_is_redundant would notice that, but we needn't even bother
|
||||
* constructing the node...
|
||||
*/
|
||||
eclass = pathkey->pk_eclass; // 获取路径键的等价类。
|
||||
Assert(eclass->ec_merged == NULL); // 使用断言确保等价类未合并。
|
||||
|
||||
// 如果等价类必须是冗余的且没有等价类组合,则跳过当前路径键。
|
||||
if (EC_MUST_BE_REDUNDANT(eclass) && !eclass->ec_group_set)
|
||||
continue;
|
||||
|
||||
new_pathkeys = lappend(new_pathkeys, pathkey);
|
||||
new_pathkeys = lappend(new_pathkeys, pathkey); // 否则,将路径键添加到新的路径键列表中。
|
||||
}
|
||||
return new_pathkeys;
|
||||
|
||||
return new_pathkeys; // 返回新的路径键列表。
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* make_pathkey_from_sortinfo
|
||||
* Given an expression and sort-order information, create a PathKey.
|
||||
|
|
@ -279,11 +284,12 @@ List* remove_param_pathkeys(PlannerInfo* root, List* pathkeys)
|
|||
static PathKey* make_pathkey_from_sortinfo(PlannerInfo* root, Expr* expr, Oid opfamily, Oid opcintype, Oid collation,
|
||||
bool reverse_sort, bool nulls_first, Index sortref, bool groupSet, Relids rel, bool create_it, bool canonicalize)
|
||||
{
|
||||
int16 strategy;
|
||||
Oid equality_op;
|
||||
List* opfamilies = NIL;
|
||||
EquivalenceClass* eclass = NULL;
|
||||
int16 strategy; // 声明一个int16类型的变量strategy,用于表示排序策略。
|
||||
Oid equality_op; // 声明一个Oid类型的变量equality_op,用于表示相等比较运算符。
|
||||
List* opfamilies = NIL; // 声明一个List类型的变量opfamilies,用于保存操作族列表。
|
||||
EquivalenceClass* eclass = NULL; // 声明一个EquivalenceClass类型的指针eclass,初始化为NULL。
|
||||
|
||||
// 根据是否逆序设置排序策略。
|
||||
strategy = reverse_sort ? BTGreaterStrategyNumber : BTLessStrategyNumber;
|
||||
|
||||
/*
|
||||
|
|
@ -292,28 +298,36 @@ static PathKey* make_pathkey_from_sortinfo(PlannerInfo* root, Expr* expr, Oid op
|
|||
* more than one opfamily. So we have to look up the opfamily's equality
|
||||
* operator and get its membership.
|
||||
*/
|
||||
// 获取操作族中的相等比较运算符。
|
||||
equality_op = get_opfamily_member(opfamily, opcintype, opcintype, BTEqualStrategyNumber);
|
||||
if (!OidIsValid(equality_op)) /* shouldn't happen */
|
||||
|
||||
// 如果没有找到相等比较运算符,报告错误。
|
||||
if (!OidIsValid(equality_op))
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
(errmsg(
|
||||
"could not find equality operator for opfamily %u when make pathkey from sortinfo", opfamily))));
|
||||
|
||||
// 获取相等比较运算符对应的操作族列表。
|
||||
opfamilies = get_mergejoin_opfamilies(equality_op);
|
||||
if (opfamilies == NIL) /* certainly should find some */
|
||||
|
||||
// 如果没有找到操作族列表,报告错误。
|
||||
if (opfamilies == NIL)
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
(errmsg("could not find opfamilies for equality operator %u when make pathkey from sortinfo",
|
||||
equality_op))));
|
||||
/* Now find or (optionally) create a matching EquivalenceClass */
|
||||
|
||||
// 为排序表达式获取等价类。
|
||||
eclass = get_eclass_for_sort_expr(root, expr, opfamilies, opcintype, collation, sortref, groupSet, rel, create_it);
|
||||
|
||||
/* Fail if no EC and !create_it */
|
||||
// 如果未找到等价类,返回NULL。
|
||||
if (eclass == NULL)
|
||||
return NULL;
|
||||
|
||||
/* And finally we can find or create a PathKey node */
|
||||
// 如果要规范化路径键,调用make_canonical_pathkey函数,否则调用makePathKey函数。
|
||||
if (canonicalize)
|
||||
return make_canonical_pathkey(root, eclass, opfamily, strategy, nulls_first);
|
||||
else
|
||||
|
|
@ -333,16 +347,17 @@ static PathKey* make_pathkey_from_sortop(PlannerInfo* root, Expr* expr, Oid orde
|
|||
Oid opfamily, opcintype, collation;
|
||||
int16 strategy;
|
||||
|
||||
/* Find the operator in pg_amop --- failure shouldn't happen */
|
||||
/* 通过在pg_amop中查找操作符,获取操作族、操作数类型和排序策略 */
|
||||
if (!get_ordering_op_properties(ordering_op, &opfamily, &opcintype, &strategy))
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
(errmsg("operator %u is not a valid ordering operator when make pathkey from sortinfo", ordering_op))));
|
||||
|
||||
/* Because SortGroupClause doesn't carry collation, consult the expr */
|
||||
/* 由于SortGroupClause不携带排序规则,需查看表达式获取排序规则 */
|
||||
collation = exprCollation((Node*)expr);
|
||||
|
||||
// 调用make_pathkey_from_sortinfo函数来创建路径键,并返回结果。
|
||||
return make_pathkey_from_sortinfo(root,
|
||||
expr,
|
||||
opfamily,
|
||||
|
|
@ -356,7 +371,6 @@ static PathKey* make_pathkey_from_sortop(PlannerInfo* root, Expr* expr, Oid orde
|
|||
create_it,
|
||||
canonicalize);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* PATHKEY COMPARISONS
|
||||
****************************************************************************/
|
||||
|
|
@ -392,25 +406,32 @@ PathKeysComparison compare_pathkeys(List* keys1, List* keys2)
|
|||
* but PlannerInfo not accessible here...
|
||||
*/
|
||||
#ifdef NOT_USED
|
||||
AssertEreport(list_member_ptr(root->canon_pathkeys, pathkey1), MOD_OPT, "pathky1 is not a member in pathkeys");
|
||||
// 使用断言确保pathkey1在root->canon_pathkeys中。
|
||||
AssertEreport(list_member_ptr(root->canon_pathkeys, pathkey1), MOD_OPT, "pathkey1 is not a member in pathkeys");
|
||||
|
||||
AssertEreport(list_member_ptr(root->canon_pathkeys, pathkey2), MOD_OPT, "pathky2 is not a member in pathkeys");
|
||||
// 使用断言确保pathkey2在root->canon_pathkeys中。
|
||||
AssertEreport(list_member_ptr(root->canon_pathkeys, pathkey2), MOD_OPT, "pathkey2 is not a member in pathkeys");
|
||||
#endif
|
||||
if (pathkey1 == pathkey2) {
|
||||
continue;
|
||||
}
|
||||
if (pathkey1 == NULL && pathkey2 != NULL) {
|
||||
return PATHKEYS_DIFFERENT; /* no need to keep looking */
|
||||
}
|
||||
if (pathkey1 != NULL && pathkey2 == NULL) {
|
||||
return PATHKEYS_DIFFERENT; /* no need to keep looking */
|
||||
}
|
||||
if (pathkey1->type != pathkey2->type || !OpFamilyEquals(pathkey1->pk_opfamily, pathkey2->pk_opfamily) ||
|
||||
pathkey1->pk_eclass != pathkey2->pk_eclass || pathkey1->pk_strategy != pathkey2->pk_strategy ||
|
||||
pathkey1->pk_nulls_first != pathkey2->pk_nulls_first) {
|
||||
return PATHKEYS_DIFFERENT; /* no need to keep looking */
|
||||
}
|
||||
|
||||
// 如果pathkey1等于pathkey2,继续循环。
|
||||
if (pathkey1 == pathkey2) {
|
||||
continue;
|
||||
}
|
||||
// 如果pathkey1为NULL而pathkey2不为NULL,则返回PATHKEYS_DIFFERENT,不再继续查找。
|
||||
if (pathkey1 == NULL && pathkey2 != NULL) {
|
||||
return PATHKEYS_DIFFERENT;
|
||||
}
|
||||
// 如果pathkey1不为NULL而pathkey2为NULL,则返回PATHKEYS_DIFFERENT,不再继续查找。
|
||||
if (pathkey1 != NULL && pathkey2 == NULL) {
|
||||
return PATHKEYS_DIFFERENT;
|
||||
}
|
||||
// 如果pathkey1和pathkey2的类型、操作族、等价类、策略和nulls_first都不相等,则返回PATHKEYS_DIFFERENT,不再继续查找。
|
||||
if (pathkey1->type != pathkey2->type || !OpFamilyEquals(pathkey1->pk_opfamily, pathkey2->pk_opfamily) ||
|
||||
pathkey1->pk_eclass != pathkey2->pk_eclass || pathkey1->pk_strategy != pathkey2->pk_strategy ||
|
||||
pathkey1->pk_nulls_first != pathkey2->pk_nulls_first) {
|
||||
return PATHKEYS_DIFFERENT;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we reached the end of only one list, the other is longer and
|
||||
|
|
@ -532,32 +553,34 @@ Path* get_cheapest_fractional_path_for_pathkeys(List* paths, List* pathkeys, Rel
|
|||
*/
|
||||
List* build_index_pathkeys(PlannerInfo* root, IndexOptInfo* index, ScanDirection scandir)
|
||||
{
|
||||
List* retval = NIL;
|
||||
ListCell* lc = NULL;
|
||||
List* retval = NIL; // 声明一个结果列表,并初始化为空。
|
||||
ListCell* lc = NULL; // 声明一个指向ListCell的指针,并初始化为空。
|
||||
int i;
|
||||
|
||||
// 如果索引没有排序操作符族(sortopfamily),返回一个空列表,表示索引不支持有序扫描。
|
||||
if (index->sortopfamily == NULL)
|
||||
return NIL; /* non-orderable index */
|
||||
return NIL;
|
||||
|
||||
i = 0;
|
||||
foreach (lc, index->indextlist) {
|
||||
TargetEntry* indextle = (TargetEntry*)lfirst(lc);
|
||||
Expr* indexkey = NULL;
|
||||
bool reverse_sort = false;
|
||||
bool nulls_first = false;
|
||||
PathKey* cpathkey = NULL;
|
||||
foreach (lc, index->indextlist) { // 遍历索引列列表。
|
||||
TargetEntry* indextle = (TargetEntry*)lfirst(lc); // 获取索引目标条目。
|
||||
Expr* indexkey = NULL; // 声明一个表达式指针,并初始化为空。
|
||||
bool reverse_sort = false; // 是否逆序排序的标志,默认为false。
|
||||
bool nulls_first = false; // NULL值排在前面的标志,默认为false。
|
||||
PathKey* cpathkey = NULL; // 声明一个PathKey指针,并初始化为空。
|
||||
|
||||
/*
|
||||
* INCLUDE columns are stored in index unordered, so they don't
|
||||
* support ordered index scan.
|
||||
* INCLUDE列存储在索引中无序,因此它们不支持有序索引扫描。
|
||||
* 如果i大于或等于索引的关键列数,跳出循环。
|
||||
*/
|
||||
if (i >= index->nkeycolumns) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* We assume we don't need to make a copy of the tlist item */
|
||||
indexkey = indextle->expr;
|
||||
/* 假设我们不需要复制tlist项目 */
|
||||
indexkey = indextle->expr; // 获取索引键的表达式。
|
||||
|
||||
// 根据扫描方向设置逆序排序和NULL值排在前面的标志。
|
||||
if (ScanDirectionIsBackward(scandir)) {
|
||||
reverse_sort = !index->reverse_sort[i];
|
||||
nulls_first = !index->nulls_first[i];
|
||||
|
|
@ -566,7 +589,7 @@ List* build_index_pathkeys(PlannerInfo* root, IndexOptInfo* index, ScanDirection
|
|||
nulls_first = index->nulls_first[i];
|
||||
}
|
||||
|
||||
/* OK, try to make a canonical pathkey for this sort key */
|
||||
/* 尝试为这个排序键创建一个规范路径键 */
|
||||
cpathkey = make_pathkey_from_sortinfo(root,
|
||||
indexkey,
|
||||
index->sortopfamily[i],
|
||||
|
|
@ -581,23 +604,23 @@ List* build_index_pathkeys(PlannerInfo* root, IndexOptInfo* index, ScanDirection
|
|||
true);
|
||||
|
||||
/*
|
||||
* If the sort key isn't already present in any EquivalenceClass, then
|
||||
* it's not an interesting sort order for this query. So we can stop
|
||||
* now --- lower-order sort keys aren't useful either.
|
||||
* 如果排序键尚未存在于任何等价类中,则它对于此查询不是有趣的排序顺序。
|
||||
* 因此,我们可以停止遍历 --- 较低阶的排序键也不有用。
|
||||
*/
|
||||
if (cpathkey == NULL)
|
||||
break;
|
||||
|
||||
/* Add to list unless redundant */
|
||||
/* 将路径键添加到列表,除非它是冗余的 */
|
||||
if (!pathkey_is_redundant(cpathkey, retval))
|
||||
retval = lappend(retval, cpathkey);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return retval;
|
||||
return retval; // 返回结果列表。
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* convert_subquery_pathkeys
|
||||
* Build a pathkeys list that describes the ordering of a subquery's
|
||||
|
|
@ -613,43 +636,55 @@ List* build_index_pathkeys(PlannerInfo* root, IndexOptInfo* index, ScanDirection
|
|||
*/
|
||||
List* convert_subquery_pathkeys(PlannerInfo* root, RelOptInfo* rel, List* subquery_pathkeys)
|
||||
{
|
||||
List* retval = NIL;
|
||||
int retvallen = 0;
|
||||
int outer_query_keys = list_length(root->query_pathkeys);
|
||||
List* sub_tlist = rel->subplan->targetlist;
|
||||
ListCell* i = NULL;
|
||||
List* retval = NIL; // 声明一个结果列表,并初始化为空。
|
||||
int retvallen = 0; // 初始化结果列表的长度为0。
|
||||
int outer_query_keys = list_length(root->query_pathkeys); // 获取外部查询的路径键数。
|
||||
List* sub_tlist = rel->subplan->targetlist; // 获取子查询的目标列表。
|
||||
ListCell* i = NULL; // 声明一个指向ListCell的指针,并初始化为空。
|
||||
|
||||
// 遍历子查询的路径键列表。
|
||||
foreach (i, subquery_pathkeys) {
|
||||
PathKey* sub_pathkey = (PathKey*)lfirst(i);
|
||||
EquivalenceClass* sub_eclass = sub_pathkey->pk_eclass;
|
||||
PathKey* best_pathkey = NULL;
|
||||
PathKey* sub_pathkey = (PathKey*)lfirst(i); // 获取子查询路径键。
|
||||
EquivalenceClass* sub_eclass = sub_pathkey->pk_eclass; // 获取子查询等价类。
|
||||
PathKey* best_pathkey = NULL; // 声明一个最佳路径键指针,并初始化为空。
|
||||
|
||||
if (sub_eclass->ec_has_volatile) {
|
||||
/*
|
||||
* If the sub_pathkey's EquivalenceClass is volatile, then it must
|
||||
* have come from an ORDER BY clause, and we have to match it to
|
||||
* that same targetlist entry.
|
||||
* 如果子查询路径键的等价类是易失性的,那么它必须来自于ORDER BY子句,
|
||||
* 我们必须将它与相同的目标列表条目匹配。
|
||||
*/
|
||||
TargetEntry* tle = NULL;
|
||||
|
||||
if (sub_eclass->ec_sortref == 0) /* can't happen */
|
||||
// 如果子查询路径键的等价类没有排序引用(sortref),则报告错误。
|
||||
if (sub_eclass->ec_sortref == 0)
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
(errmsg("volatile EquivalenceClass has no sortref when convert subquery pathkeys"))));
|
||||
|
||||
// 获取与子查询路径键等价类对应的目标列表条目。
|
||||
tle = get_sortgroupref_tle(sub_eclass->ec_sortref, sub_tlist);
|
||||
|
||||
// 使用断言确保目标列表条目不为空。
|
||||
AssertEreport(tle != NULL, MOD_OPT, "tle is NULL");
|
||||
/* resjunk items aren't visible to outer query */
|
||||
|
||||
/* resjunk 项对外部查询不可见 */
|
||||
if (!tle->resjunk) {
|
||||
/* We can represent this sub_pathkey */
|
||||
/* 我们可以表示这个子查询路径键 */
|
||||
EquivalenceMember* sub_member = NULL;
|
||||
Expr* outer_expr = NULL;
|
||||
EquivalenceClass* outer_ec = NULL;
|
||||
|
||||
// 使用断言确保等价类成员列表长度为1。
|
||||
AssertEreport(list_length(sub_eclass->ec_members) == 1, MOD_OPT, "ec member number is not 1");
|
||||
|
||||
// 获取等价类的成员。
|
||||
sub_member = (EquivalenceMember*)linitial(sub_eclass->ec_members);
|
||||
|
||||
// 创建外部查询的表达式,以引用子查询的目标列表条目。
|
||||
outer_expr = (Expr*)makeVarFromTargetEntry(rel->relid, tle);
|
||||
|
||||
|
||||
/*
|
||||
* Note: it might look funny to be setting sortref = 0 for a
|
||||
* reference to a volatile sub_eclass. However, the
|
||||
|
|
@ -696,44 +731,47 @@ List* convert_subquery_pathkeys(PlannerInfo* root, RelOptInfo* rel, List* subque
|
|||
* query_pathkeys). This is the most likely to be useful in the
|
||||
* outer query.
|
||||
*/
|
||||
int best_score = -1;
|
||||
ListCell* j = NULL;
|
||||
int best_score = -1; // 初始化最佳分数为-1。
|
||||
ListCell* j = NULL; // 声明一个指向ListCell的指针,并初始化为空。
|
||||
|
||||
foreach (j, sub_eclass->ec_members) {
|
||||
EquivalenceMember* sub_member = (EquivalenceMember*)lfirst(j);
|
||||
Expr* sub_expr = sub_member->em_expr;
|
||||
Oid sub_expr_type = sub_member->em_datatype;
|
||||
Oid sub_expr_coll = sub_eclass->ec_collation;
|
||||
ListCell* k = NULL;
|
||||
int seq = 0;
|
||||
foreach (j, sub_eclass->ec_members) {
|
||||
EquivalenceMember* sub_member = (EquivalenceMember*)lfirst(j); // 获取等价类成员。
|
||||
Expr* sub_expr = sub_member->em_expr; // 获取等价类成员的表达式。
|
||||
Oid sub_expr_type = sub_member->em_datatype; // 获取等价类成员的数据类型。
|
||||
Oid sub_expr_coll = sub_eclass->ec_collation; // 获取等价类成员的排序规则。
|
||||
ListCell* k = NULL;
|
||||
int seq = 0;
|
||||
|
||||
if (sub_member->em_is_child)
|
||||
continue; /* ignore children here */
|
||||
if (sub_member->em_is_child)
|
||||
continue; /* 忽略子查询的子查询等价类 */
|
||||
|
||||
foreach (k, sub_tlist) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(k);
|
||||
Expr* tle_expr = NULL;
|
||||
Expr* outer_expr = NULL;
|
||||
EquivalenceClass* outer_ec = NULL;
|
||||
PathKey* outer_pk = NULL;
|
||||
int score;
|
||||
ListCell* lc = NULL;
|
||||
foreach (k, sub_tlist) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(k); // 获取子查询目标列表的目标条目。
|
||||
Expr* tle_expr = NULL;
|
||||
Expr* outer_expr = NULL;
|
||||
EquivalenceClass* outer_ec = NULL;
|
||||
PathKey* outer_pk = NULL;
|
||||
int score;
|
||||
ListCell* lc = NULL;
|
||||
|
||||
seq++;
|
||||
seq++;
|
||||
|
||||
/* resjunk items aren't visible to outer query */
|
||||
if (tle->resjunk)
|
||||
continue;
|
||||
/* resjunk 项对外部查询不可见 */
|
||||
if (tle->resjunk)
|
||||
continue;
|
||||
|
||||
/* check if targetentry exists in final subquery targetlist */
|
||||
foreach (lc, rel->reltargetlist) {
|
||||
Node* n = (Node*)lfirst(lc);
|
||||
if (IsA(n, Var) && ((Var*)n)->varattno == seq)
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* 检查目标条目是否存在于最终的子查询目标列表中。
|
||||
* 如果不在子查询目标列表中,则继续下一条目。
|
||||
*/
|
||||
foreach (lc, rel->reltargetlist) {
|
||||
Node* n = (Node*)lfirst(lc);
|
||||
if (IsA(n, Var) && ((Var*)n)->varattno == seq)
|
||||
break;
|
||||
}
|
||||
|
||||
if (lc == NULL)
|
||||
continue;
|
||||
if (lc == NULL)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* The targetlist entry is considered to match if it
|
||||
|
|
@ -861,16 +899,22 @@ List* build_join_pathkeys(PlannerInfo* root, RelOptInfo* joinrel, JoinType joint
|
|||
*/
|
||||
List* make_pathkeys_for_sortclauses(PlannerInfo* root, List* sortclauses, List* tlist, bool canonicalize)
|
||||
{
|
||||
List* pathkeys = NIL;
|
||||
ListCell* l = NULL;
|
||||
List* pathkeys = NIL; // 声明一个路径键列表,并初始化为空。
|
||||
ListCell* l = NULL; // 声明一个指向ListCell的指针,并初始化为空。
|
||||
|
||||
// 遍历排序子句列表。
|
||||
foreach (l, sortclauses) {
|
||||
SortGroupClause* sortcl = (SortGroupClause*)lfirst(l);
|
||||
Expr* sortkey = NULL;
|
||||
PathKey* pathkey = NULL;
|
||||
SortGroupClause* sortcl = (SortGroupClause*)lfirst(l); // 获取排序子句。
|
||||
Expr* sortkey = NULL; // 声明一个排序键表达式指针,并初始化为空。
|
||||
PathKey* pathkey = NULL; // 声明一个路径键指针,并初始化为空。
|
||||
|
||||
// 获取排序子句的排序键表达式。
|
||||
sortkey = (Expr*)get_sortgroupclause_expr(sortcl, tlist);
|
||||
|
||||
// 使用断言确保排序子句的排序操作符是有效的。
|
||||
AssertEreport(OidIsValid(sortcl->sortop), MOD_OPT, "ordering operator is invalid");
|
||||
|
||||
// 调用make_pathkey_from_sortop函数创建路径键。
|
||||
pathkey = make_pathkey_from_sortop(root,
|
||||
sortkey,
|
||||
sortcl->sortop,
|
||||
|
|
@ -880,16 +924,19 @@ List* make_pathkeys_for_sortclauses(PlannerInfo* root, List* sortclauses, List*
|
|||
true,
|
||||
canonicalize);
|
||||
|
||||
/* Canonical form eliminates redundant ordering keys */
|
||||
/* 规范形式消除冗余的排序键 */
|
||||
if (canonicalize) {
|
||||
// 如果路径键不是冗余的,则将其添加到路径键列表中。
|
||||
if (!pathkey_is_redundant(pathkey, pathkeys, ENABLE_PRED_PUSH_ALL(root)))
|
||||
pathkeys = lappend(pathkeys, pathkey);
|
||||
} else
|
||||
pathkeys = lappend(pathkeys, pathkey);
|
||||
pathkeys = lappend(pathkeys, pathkey); // 将路径键添加到路径键列表中。
|
||||
}
|
||||
return pathkeys;
|
||||
|
||||
return pathkeys; // 返回路径键列表。
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* PATHKEYS AND MERGECLAUSES
|
||||
****************************************************************************/
|
||||
|
|
@ -914,17 +961,19 @@ List* make_pathkeys_for_sortclauses(PlannerInfo* root, List* sortclauses, List*
|
|||
*/
|
||||
void initialize_mergeclause_eclasses(PlannerInfo* root, RestrictInfo* restrictinfo)
|
||||
{
|
||||
Expr* clause = restrictinfo->clause;
|
||||
Expr* clause = restrictinfo->clause;// 获取限制信息中的表达式。
|
||||
Oid lefttype, righttype;
|
||||
|
||||
/* Should be a mergeclause ... */
|
||||
AssertEreport(restrictinfo->mergeopfamilies != NIL, MOD_OPT, "clause is not mergejoinable");
|
||||
//检查限制信息是否表示一个合并条件(mergeclause)
|
||||
/* ... with links not yet set */
|
||||
AssertEreport(restrictinfo->left_ec == NULL, MOD_OPT, "lefthand mergeclause processing is set");
|
||||
AssertEreport(restrictinfo->right_ec == NULL, MOD_OPT, "righthand mergeclause processing is set");
|
||||
//检查左侧和右侧等价类是否尚未设置链接
|
||||
|
||||
/* Need the declared input types of the operator */
|
||||
op_input_types(((OpExpr*)clause)->opno, &lefttype, &righttype);
|
||||
op_input_types(((OpExpr*)clause)->opno, &lefttype, &righttype);// 获取操作符的输入类型
|
||||
|
||||
/* Find or create a matching EquivalenceClass for each side */
|
||||
restrictinfo->left_ec = get_eclass_for_sort_expr(root,
|
||||
|
|
@ -992,21 +1041,22 @@ void update_mergeclause_eclasses(PlannerInfo* root, RestrictInfo* restrictinfo)
|
|||
*/
|
||||
List* find_mergeclauses_for_outer_pathkeys(PlannerInfo* root, List* pathkeys, List* restrictinfos)
|
||||
{
|
||||
List* mergeclauses = NIL;
|
||||
ListCell* i = NULL;
|
||||
List* mergeclauses = NIL; // 初始化合并条件列表为空。
|
||||
ListCell* i = NULL; // 声明一个指向ListCell的指针,并初始化为空。
|
||||
|
||||
/* make sure we have eclasses cached in the clauses */
|
||||
// 遍历限制信息列表。
|
||||
foreach (i, restrictinfos) {
|
||||
RestrictInfo* rinfo = (RestrictInfo*)lfirst(i);
|
||||
RestrictInfo* rinfo = (RestrictInfo*)lfirst(i); // 获取限制信息。
|
||||
|
||||
update_mergeclause_eclasses(root, rinfo);
|
||||
update_mergeclause_eclasses(root, rinfo); // 更新合并条件的等价类。
|
||||
}
|
||||
|
||||
foreach (i, pathkeys) {
|
||||
PathKey* pathkey = (PathKey*)lfirst(i);
|
||||
EquivalenceClass* pathkey_ec = pathkey->pk_eclass;
|
||||
List* matched_restrictinfos = NIL;
|
||||
ListCell* j = NULL;
|
||||
PathKey* pathkey = (PathKey*)lfirst(i); // 获取路径键。
|
||||
EquivalenceClass* pathkey_ec = pathkey->pk_eclass; // 获取路径键的等价类。
|
||||
List* matched_restrictinfos = NIL; // 初始化匹配的限制信息列表为空。
|
||||
ListCell* j = NULL; // 声明一个指向ListCell的指针,并初始化为空。
|
||||
|
||||
/* ----------
|
||||
* A mergejoin clause matches a pathkey if it has the same EC.
|
||||
|
|
@ -1248,43 +1298,44 @@ List* select_outer_pathkeys_for_merge(PlannerInfo* root, List* mergeclauses, Rel
|
|||
*/
|
||||
List* make_inner_pathkeys_for_merge(PlannerInfo* root, List* mergeclauses, List* outer_pathkeys)
|
||||
{
|
||||
List* pathkeys = NIL;
|
||||
EquivalenceClass* lastoeclass = NULL;
|
||||
PathKey* opathkey = NULL;
|
||||
ListCell* lc = NULL;
|
||||
ListCell* lop = NULL;
|
||||
List* pathkeys = NIL; // 初始化内部路径键列表为空。
|
||||
EquivalenceClass* lastoeclass = NULL; // 上一个外部等价类。
|
||||
PathKey* opathkey = NULL; // 外部路径键。
|
||||
ListCell* lc = NULL; // 声明一个指向ListCell的指针。
|
||||
ListCell* lop = NULL; // 声明一个指向ListCell的指针。
|
||||
|
||||
lastoeclass = NULL;
|
||||
opathkey = NULL;
|
||||
lop = list_head(outer_pathkeys);
|
||||
lastoeclass = NULL; // 初始化上一个外部等价类为空。
|
||||
opathkey = NULL; // 初始化外部路径键为空。
|
||||
lop = list_head(outer_pathkeys); // 初始化外部路径键列表的头指针。
|
||||
|
||||
// 遍历合并条件列表。
|
||||
foreach (lc, mergeclauses) {
|
||||
RestrictInfo* rinfo = (RestrictInfo*)lfirst(lc);
|
||||
EquivalenceClass* oeclass = NULL;
|
||||
EquivalenceClass* ieclass = NULL;
|
||||
PathKey* pathkey = NULL;
|
||||
RestrictInfo* rinfo = (RestrictInfo*)lfirst(lc); // 获取限制信息。
|
||||
EquivalenceClass* oeclass = NULL; // 外部等价类。
|
||||
EquivalenceClass* ieclass = NULL; // 内部等价类。
|
||||
PathKey* pathkey = NULL; // 路径键。
|
||||
|
||||
update_mergeclause_eclasses(root, rinfo);
|
||||
update_mergeclause_eclasses(root, rinfo); // 更新合并条件的等价类。
|
||||
|
||||
if (rinfo->outer_is_left) {
|
||||
oeclass = rinfo->left_ec;
|
||||
ieclass = rinfo->right_ec;
|
||||
oeclass = rinfo->left_ec; // 左侧是外部等价类。
|
||||
ieclass = rinfo->right_ec; // 右侧是内部等价类。
|
||||
} else {
|
||||
oeclass = rinfo->right_ec;
|
||||
ieclass = rinfo->left_ec;
|
||||
oeclass = rinfo->right_ec; // 右侧是外部等价类。
|
||||
ieclass = rinfo->left_ec; // 左侧是内部等价类。
|
||||
}
|
||||
|
||||
/* outer eclass should match current or next pathkeys */
|
||||
/* we check this carefully for debugging reasons */
|
||||
// 如果外部等价类不同于上一个外部等价类。
|
||||
if (oeclass != lastoeclass) {
|
||||
if (lop == NULL)
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
(errmsg("too few pathkeys for mergeclauses when make inner pathkeys for merge"))));
|
||||
opathkey = (PathKey*)lfirst(lop);
|
||||
lop = lnext(lop);
|
||||
lastoeclass = opathkey->pk_eclass;
|
||||
opathkey = (PathKey*)lfirst(lop); // 获取下一个外部路径键。
|
||||
lop = lnext(lop); // 移动到下一个外部路径键。
|
||||
lastoeclass = opathkey->pk_eclass; // 更新上一个外部等价类。
|
||||
if (oeclass != lastoeclass)
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
|
|
@ -1297,6 +1348,7 @@ List* make_inner_pathkeys_for_merge(PlannerInfo* root, List* mergeclauses, List*
|
|||
* pathkey is also canonical for the inner side, and we can skip a
|
||||
* useless search.
|
||||
*/
|
||||
// 如果内部等价类等于外部等价类,则使用外部路径键。
|
||||
if (ieclass == oeclass)
|
||||
pathkey = opathkey;
|
||||
else
|
||||
|
|
@ -1312,11 +1364,12 @@ List* make_inner_pathkeys_for_merge(PlannerInfo* root, List* mergeclauses, List*
|
|||
* reason, it certainly wouldn't match any available sort order for
|
||||
* the input relation.
|
||||
*/
|
||||
// 如果路径键不是多余的,则添加到内部路径键列表。
|
||||
if (!pathkey_is_redundant(pathkey, pathkeys))
|
||||
pathkeys = lappend(pathkeys, pathkey);
|
||||
}
|
||||
|
||||
return pathkeys;
|
||||
return pathkeys;// 返回内部路径键列表。
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1488,13 +1541,15 @@ static int pathkeys_useful_for_merging(PlannerInfo* root, RelOptInfo* rel, List*
|
|||
*/
|
||||
static bool right_merge_direction(PlannerInfo* root, PathKey* pathkey)
|
||||
{
|
||||
ListCell* l = NULL;
|
||||
ListCell* l = NULL; // 声明一个指向ListCell的指针。
|
||||
|
||||
// 遍历查询路径键列表。
|
||||
foreach (l, root->query_pathkeys) {
|
||||
PathKey* query_pathkey = (PathKey*)lfirst(l);
|
||||
PathKey* query_pathkey = (PathKey*)lfirst(l); // 获取查询路径键。
|
||||
|
||||
// 如果路径键的等价类和操作符族相同。
|
||||
if (pathkey->pk_eclass == query_pathkey->pk_eclass &&
|
||||
OpFamilyEquals(pathkey->pk_opfamily, query_pathkey->pk_opfamily)) {
|
||||
OpFamilyEquals(pathkey->pk_opfamily, query_pathkey->pk_opfamily)) {
|
||||
/*
|
||||
* Found a matching query sort column. Prefer this pathkey's
|
||||
* direction iff it matches. Note that we ignore pk_nulls_first,
|
||||
|
|
@ -1502,12 +1557,12 @@ static bool right_merge_direction(PlannerInfo* root, PathKey* pathkey)
|
|||
* want to prefer only one of the two possible directions, and we
|
||||
* might as well use this one.
|
||||
*/
|
||||
return (pathkey->pk_strategy == query_pathkey->pk_strategy);
|
||||
return (pathkey->pk_strategy == query_pathkey->pk_strategy); // 返回策略是否相同。
|
||||
}
|
||||
}
|
||||
|
||||
/* If no matching ORDER BY request, prefer the ASC direction */
|
||||
return (pathkey->pk_strategy == BTLessStrategyNumber);
|
||||
return (pathkey->pk_strategy == BTLessStrategyNumber);// 如果没有匹配的查询路径键,默认返回 false。
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1544,8 +1599,13 @@ List* truncate_useless_pathkeys(PlannerInfo* root, RelOptInfo* rel, List* pathke
|
|||
int nuseful;
|
||||
int nuseful2;
|
||||
|
||||
// 计算路径键对于合并连接操作的有用性。
|
||||
nuseful = pathkeys_useful_for_merging(root, rel, pathkeys);
|
||||
|
||||
// 计算路径键对于排序操作的有用性。
|
||||
nuseful2 = pathkeys_useful_for_ordering(root, pathkeys);
|
||||
|
||||
// 如果排序操作的有用性更高,则使用排序操作的有用性。
|
||||
if (nuseful2 > nuseful) {
|
||||
nuseful = nuseful2;
|
||||
}
|
||||
|
|
@ -1554,10 +1614,13 @@ List* truncate_useless_pathkeys(PlannerInfo* root, RelOptInfo* rel, List* pathke
|
|||
* Note: not safe to modify input list destructively, but we can avoid
|
||||
* copying the list if we're not actually going to change it
|
||||
*/
|
||||
// 如果没有有用的路径键,返回空列表。
|
||||
if (nuseful == 0)
|
||||
return NIL;
|
||||
// 如果所有路径键都有用,返回原始路径键列表。
|
||||
else if (nuseful == list_length(pathkeys))
|
||||
return pathkeys;
|
||||
// 否则,截断路径键列表,只保留有用的部分。
|
||||
else
|
||||
return list_truncate(list_copy(pathkeys), nuseful);
|
||||
}
|
||||
|
|
@ -1604,23 +1667,24 @@ construct_pathkeys(PlannerInfo *root, List *tlist, List *activeWindows,
|
|||
*/
|
||||
|
||||
/* To groupingSet, we need build it's groupPathKey according to it's lower levels sort clause.*/
|
||||
if (groupClause && grouping_is_sortable(groupClause)) {
|
||||
root->group_pathkeys = make_pathkeys_for_sortclauses(root, groupClause, tlist, canonical);
|
||||
} else {
|
||||
root->group_pathkeys = NIL;
|
||||
}
|
||||
|
||||
// 设置 group_pathkeys,如果 groupClause 存在并且可排序。
|
||||
if (groupClause && grouping_is_sortable(groupClause)) {
|
||||
root->group_pathkeys = make_pathkeys_for_sortclauses(root, groupClause, tlist, canonical);
|
||||
} else {
|
||||
root->group_pathkeys = NIL; // 否则,设置为空列表。
|
||||
}
|
||||
/* We consider only the first (bottom) window in pathkeys logic */
|
||||
if (activeWindows != NIL) {
|
||||
WindowClause* wc = NULL;
|
||||
// 获取活动窗口中的第一个窗口子句。
|
||||
|
||||
wc = (WindowClause*)linitial(activeWindows);
|
||||
wc = (WindowClause*)linitial(activeWindows);// 设置 window_pathkeys,如果窗口子句存在并且可排序。
|
||||
|
||||
root->window_pathkeys = make_pathkeys_for_window(root, wc, tlist, canonical);
|
||||
} else {
|
||||
root->window_pathkeys = NIL;
|
||||
root->window_pathkeys = NIL;// 否则,设置为空列表。
|
||||
}
|
||||
|
||||
// 设置 distinct_pathkeys,如果 distinctClause 存在并且可排序。
|
||||
if (parse->distinctClause && grouping_is_sortable(parse->distinctClause)) {
|
||||
|
||||
root->distinct_pathkeys = make_pathkeys_for_sortclauses(root,
|
||||
|
|
@ -1628,10 +1692,11 @@ construct_pathkeys(PlannerInfo *root, List *tlist, List *activeWindows,
|
|||
} else {
|
||||
root->distinct_pathkeys = NIL;
|
||||
}
|
||||
|
||||
// 设置 sort_pathkeys。
|
||||
root->sort_pathkeys = make_pathkeys_for_sortclauses(root, parse->sortClause, tlist, canonical);
|
||||
|
||||
/* Remove the PARAM EC */
|
||||
// 如果 canonical 为真,移除参数路径键(不包含参数的路径键)。
|
||||
if (canonical) {
|
||||
root->group_pathkeys = remove_param_pathkeys(root, root->group_pathkeys);
|
||||
root->window_pathkeys = remove_param_pathkeys(root, root->window_pathkeys);
|
||||
|
|
@ -1657,52 +1722,68 @@ construct_pathkeys(PlannerInfo *root, List *tlist, List *activeWindows,
|
|||
* versus ORDER BY is much easier, since we know that the parser
|
||||
* ensured that one is a superset of the other.
|
||||
*/
|
||||
if (root->group_pathkeys)
|
||||
root->query_pathkeys = root->group_pathkeys;
|
||||
else if (root->window_pathkeys)
|
||||
root->query_pathkeys = root->window_pathkeys;
|
||||
else if (list_length(root->distinct_pathkeys) > list_length(root->sort_pathkeys))
|
||||
root->query_pathkeys = root->distinct_pathkeys;
|
||||
else if (root->sort_pathkeys)
|
||||
root->query_pathkeys = root->sort_pathkeys;
|
||||
else
|
||||
root->query_pathkeys = NIL;
|
||||
// 如果存在 group_pathkeys,则将 query_pathkeys 设置为 group_pathkeys。
|
||||
if (root->group_pathkeys) {
|
||||
root->query_pathkeys = root->group_pathkeys;
|
||||
}
|
||||
// 如果存在 window_pathkeys 且没有 group_pathkeys,则将 query_pathkeys 设置为 window_pathkeys。
|
||||
else if (root->window_pathkeys) {
|
||||
root->query_pathkeys = root->window_pathkeys;
|
||||
}
|
||||
// 如果 distinct_pathkeys 的长度大于 sort_pathkeys 的长度,且没有 group_pathkeys 或 window_pathkeys,则将 query_pathkeys 设置为 distinct_pathkeys。
|
||||
else if (list_length(root->distinct_pathkeys) > list_length(root->sort_pathkeys)) {
|
||||
root->query_pathkeys = root->distinct_pathkeys;
|
||||
}
|
||||
// 如果存在 sort_pathkeys 且没有 group_pathkeys、window_pathkeys 或 distinct_pathkeys,则将 query_pathkeys 设置为 sort_pathkeys。
|
||||
else if (root->sort_pathkeys) {
|
||||
root->query_pathkeys = root->sort_pathkeys;
|
||||
}
|
||||
// 如果没有任何路径键,则将 query_pathkeys 设置为空列表。
|
||||
else {
|
||||
root->query_pathkeys = NIL;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init the standard_qp_extra
|
||||
*/
|
||||
void
|
||||
standard_qp_init(PlannerInfo *root, void *extra, List *tlist,
|
||||
// 初始化查询计划的路径键,可以选择启用或禁用规范化路径键的功能。
|
||||
void standard_qp_init(PlannerInfo *root, void *extra, List *tlist,
|
||||
List *activeWindows, List *groupClause)
|
||||
{
|
||||
// 如果启用了 SQL BETA 功能中的规范化路径键功能。
|
||||
if (ENABLE_SQL_BETA_FEATURE(CANONICAL_PATHKEY)) {
|
||||
Assert (extra != NULL);
|
||||
Assert(extra != NULL); // 断言 extra 不为空。
|
||||
standard_qp_extra *qp_extra = (standard_qp_extra *)extra;
|
||||
qp_extra->tlist = tlist;
|
||||
qp_extra->activeWindows = activeWindows;
|
||||
qp_extra->groupClause = groupClause;
|
||||
qp_extra->tlist = tlist; // 将 tlist 设置为额外数据的成员。
|
||||
qp_extra->activeWindows = activeWindows; // 将 activeWindows 设置为额外数据的成员。
|
||||
qp_extra->groupClause = groupClause; // 将 groupClause 设置为额外数据的成员。
|
||||
} else {
|
||||
// 如果未启用规范化路径键功能,则构建路径键。
|
||||
construct_pathkeys(root, tlist, activeWindows, groupClause, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Compute query_pathkeys and other pathkeys during plan generation
|
||||
*/
|
||||
void
|
||||
standard_qp_callback(PlannerInfo *root, void *extra)
|
||||
// 查询计划的回调函数,用于执行规范化路径键操作。
|
||||
void standard_qp_callback(PlannerInfo *root, void *extra)
|
||||
{
|
||||
// 如果启用了 SQL BETA 功能中的规范化路径键功能。
|
||||
if (ENABLE_SQL_BETA_FEATURE(CANONICAL_PATHKEY)) {
|
||||
Assert (extra != NULL);
|
||||
Assert(extra != NULL); // 断言 extra 不为空。
|
||||
standard_qp_extra *qp_extra = (standard_qp_extra *)extra;
|
||||
construct_pathkeys(root, qp_extra->tlist, qp_extra->activeWindows,
|
||||
qp_extra->groupClause, true);
|
||||
qp_extra->groupClause, true); // 执行构建路径键操作,规范化路径键。
|
||||
} else {
|
||||
// 如果未启用规范化路径键功能,则对各种路径键进行规范化处理。
|
||||
root->group_pathkeys = canonicalize_pathkeys(root, root->group_pathkeys);
|
||||
root->window_pathkeys = canonicalize_pathkeys(root, root->window_pathkeys);
|
||||
root->distinct_pathkeys = canonicalize_pathkeys(root, root->distinct_pathkeys);
|
||||
|
|
|
|||
|
|
@ -26,9 +26,11 @@
|
|||
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
|
||||
static RemoteQueryPath* pgxc_find_remotequery_path(RelOptInfo* rel);
|
||||
static RemoteQueryPath* pgxc_find_remotequery_path(RelOptInfo* rel);// 定义一个静态函数pgxc_find_remotequery_path,该函数返回RemoteQueryPath指针,并接受RelOptInfo类型的参数rel。
|
||||
static RemoteQueryPath* create_remotequery_path(PlannerInfo* root, RelOptInfo* rel, ExecNodes* exec_nodes,
|
||||
// 定义一个静态函数create_remotequery_path,该函数返回RemoteQueryPath指针,并接受多个参数,包括PlannerInfo、RelOptInfo、ExecNodes等。
|
||||
RemoteQueryPath* leftpath, RemoteQueryPath* rightpath, JoinType jointype, List* join_restrictlist);
|
||||
|
||||
/*
|
||||
* create_remotequery_path
|
||||
* Creates a path for given RelOptInfo (for base rel or a join rel) so that
|
||||
|
|
@ -43,51 +45,66 @@ static RemoteQueryPath* create_remotequery_path(PlannerInfo* root, RelOptInfo* r
|
|||
* If any of the relations involved in this path is a temporary relation,
|
||||
* record that fact.
|
||||
*/
|
||||
// 实现create_remotequery_path函数
|
||||
static RemoteQueryPath* create_remotequery_path(PlannerInfo* root, RelOptInfo* rel, ExecNodes* exec_nodes,
|
||||
RemoteQueryPath* leftpath, RemoteQueryPath* rightpath, JoinType jointype, List* join_restrictlist)
|
||||
{
|
||||
// 创建RemoteQueryPath结构体指针rqpath并初始化为新节点
|
||||
RemoteQueryPath* rqpath = makeNode(RemoteQueryPath);
|
||||
bool unshippable_quals = false;
|
||||
// 初始化一个布尔值unshippable_quals为false
|
||||
|
||||
|
||||
// 如果rel的reloptkind属性为RELOPT_JOINREL,并且leftpath或rightpath为空,则抛出错误。
|
||||
if (rel->reloptkind == RELOPT_JOINREL && (!leftpath || !rightpath))
|
||||
elog(ERROR, "a join rel requires both the left path and right path");
|
||||
|
||||
rqpath->path.pathtype = T_RemoteQuery;
|
||||
rqpath->path.parent = rel;
|
||||
// 设置rqpath的各个属性值
|
||||
rqpath->path.pathtype = T_RemoteQuery; // 设置pathtype属性为T_RemoteQuery
|
||||
rqpath->path.parent = rel; // 设置parent属性为传入的rel
|
||||
/* PGXC_TODO: do we want to care about it */
|
||||
rqpath->path.param_info = NULL;
|
||||
rqpath->path.pathkeys = NIL; /* result is always unordered */
|
||||
rqpath->rqpath_en = exec_nodes;
|
||||
rqpath->leftpath = leftpath;
|
||||
rqpath->rightpath = rightpath;
|
||||
rqpath->jointype = jointype;
|
||||
rqpath->join_restrictlist = join_restrictlist;
|
||||
rqpath->path.param_info = NULL; // 设置param_info属性为NULL
|
||||
rqpath->path.pathkeys = NIL; // 设置pathkeys属性为空列表(结果总是无序的)
|
||||
rqpath->rqpath_en = exec_nodes; // 设置rqpath_en属性为传入的exec_nodes
|
||||
rqpath->leftpath = leftpath; // 设置leftpath属性为传入的leftpath
|
||||
rqpath->rightpath = rightpath; // 设置rightpath属性为传入的rightpath
|
||||
rqpath->jointype = jointype; // 设置jointype属性为传入的jointype
|
||||
rqpath->join_restrictlist = join_restrictlist; // 设置join_restrictlist属性为传入的join_restrictlist
|
||||
|
||||
// 根据rel的reloptkind属性不同,执行不同的分支
|
||||
switch (rel->reloptkind) {
|
||||
case RELOPT_BASEREL:
|
||||
case RELOPT_OTHER_MEMBER_REL: {
|
||||
// 获取关联的RangeTblEntry
|
||||
RangeTblEntry* rte = rt_fetch(rel->relid, root->parse->rtable);
|
||||
// 如果rte的rtekind不是RTE_RELATION,则抛出错误
|
||||
if (rte->rtekind != RTE_RELATION)
|
||||
elog(ERROR, "can not create remote path for ranges of type %d", rte->rtekind);
|
||||
// 检查关联的表是否为临时表
|
||||
rqpath->rqhas_temp_rel = IsTempTable(rte->relid);
|
||||
// 检查不可运输的限制条件(unshippable_quals)
|
||||
unshippable_quals =
|
||||
!pgxc_is_expr_shippable((Expr*)extract_actual_clauses(rel->baserestrictinfo, false), NULL);
|
||||
} break;
|
||||
|
||||
case RELOPT_JOINREL: {
|
||||
// 检查是否有临时关系
|
||||
rqpath->rqhas_temp_rel = leftpath->rqhas_temp_rel || rightpath->rqhas_temp_rel;
|
||||
// 检查不可运输的连接限制条件(join_restrictlist)
|
||||
unshippable_quals = !pgxc_is_expr_shippable((Expr*)extract_actual_clauses(join_restrictlist, false), NULL);
|
||||
} break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "can not create remote path for relation of type %d", rel->reloptkind);
|
||||
}
|
||||
// 设置rqhas_unshippable_qual属性为unshippable_quals
|
||||
rqpath->rqhas_unshippable_qual = unshippable_quals;
|
||||
// 检查不可运输的目标列表
|
||||
rqpath->rqhas_unshippable_tlist = !pgxc_is_expr_shippable((Expr*)rel->reltargetlist, NULL);
|
||||
|
||||
/* set cost properly */
|
||||
// 计算远程查询的成本
|
||||
cost_remotequery(rqpath, root, rel);
|
||||
|
||||
// 返回rqpath
|
||||
return rqpath;
|
||||
}
|
||||
|
||||
|
|
@ -99,34 +116,36 @@ static RemoteQueryPath* create_remotequery_path(PlannerInfo* root, RelOptInfo* r
|
|||
* The caller can decide whether to add the scan paths depending upon the return
|
||||
* value.
|
||||
*/
|
||||
// 定义一个外部函数create_plainrel_rqpath,该函数返回布尔值,并接受多个参数,包括PlannerInfo、RelOptInfo和RangeTblEntry。
|
||||
extern bool create_plainrel_rqpath(PlannerInfo* root, RelOptInfo* rel, RangeTblEntry* rte)
|
||||
{
|
||||
#ifndef ENABLE_MULTIPLE_NODES
|
||||
// 如果不启用多节点支持,则返回false
|
||||
return false;
|
||||
#else
|
||||
List* quals = NIL;
|
||||
ExecNodes* exec_nodes = NULL;
|
||||
|
||||
/*
|
||||
* If we are on the Coordinator, we always want to use
|
||||
* the remote query path unless relation is local to coordinator or the
|
||||
* query is to entirely executed on coordinator.
|
||||
*/
|
||||
// 如果不是PGXC协调器、连接来自协调器或是本地查询,则返回false
|
||||
if (!IS_PGXC_COORDINATOR || IsConnFromCoord() || root->parse->is_local)
|
||||
return false;
|
||||
|
||||
// 提取实际限制条件(quals)
|
||||
quals = extract_actual_clauses(rel->baserestrictinfo, false);
|
||||
// 根据限制条件获取执行节点信息
|
||||
exec_nodes = GetRelationNodesByQuals(rte->relid, rel->relid, (Node*)quals, RELATION_ACCESS_READ);
|
||||
// 如果没有找到执行节点,则返回false
|
||||
if (!exec_nodes)
|
||||
return false;
|
||||
|
||||
// 如果执行节点是按值分布的,则设置en_dist_vars属性
|
||||
if (IsExecNodesDistributedByValue(exec_nodes)) {
|
||||
Var* dist_var = pgxc_get_dist_var(rel->relid, rte, rel->reltargetlist);
|
||||
exec_nodes->en_dist_vars = list_make1(dist_var);
|
||||
}
|
||||
|
||||
/* We don't have subpaths for a plain base relation */
|
||||
add_path(rel, (Path*)create_remotequery_path(root, rel, exec_nodes, NULL, NULL, 0, NULL));
|
||||
// 调用pgxc_find_remotequery_path函数查找RemoteQueryPath
|
||||
pgxc_find_remotequery_path(rel);
|
||||
// 返回true表示成功创建了RemoteQueryPath
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -137,15 +156,19 @@ extern bool create_plainrel_rqpath(PlannerInfo* root, RelOptInfo* rel, RangeTblE
|
|||
* if one found, NULL otherwise. There should be only one RemoteQuery path for
|
||||
* each rel, but we don't check for this.
|
||||
*/
|
||||
// 定义一个函数pgxc_find_remotequery_path,该函数返回RemoteQueryPath指针,并接受RelOptInfo类型的参数rel。
|
||||
static RemoteQueryPath* pgxc_find_remotequery_path(RelOptInfo* rel)
|
||||
{
|
||||
ListCell* cell = NULL;
|
||||
|
||||
// 遍历关系的路径列表pathlist
|
||||
foreach (cell, rel->pathlist) {
|
||||
Path* path = (Path*)lfirst(cell);
|
||||
Path* path = (Path*)lfirst(cell); // 获取当前路径
|
||||
// 如果当前路径是RemoteQueryPath类型,则返回该路径
|
||||
if (IsA(path, RemoteQueryPath))
|
||||
return (RemoteQueryPath*)path;
|
||||
}
|
||||
// 如果没有找到RemoteQueryPath,返回NULL
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -155,10 +178,12 @@ static RemoteQueryPath* pgxc_find_remotequery_path(RelOptInfo* rel)
|
|||
* is shippable to the datanodes, and if so, create a remotequery path for this
|
||||
* JOIN.
|
||||
*/
|
||||
// 定义一个外部函数create_joinrel_rqpath,该函数用于创建连接关系的远程查询路径。
|
||||
extern void create_joinrel_rqpath(PlannerInfo* root, RelOptInfo* joinrel, RelOptInfo* outerrel, RelOptInfo* innerrel,
|
||||
List* restrictlist, JoinType jointype, SpecialJoinInfo* sjinfo)
|
||||
{
|
||||
#ifndef ENABLE_MULTIPLE_NODES
|
||||
// 如果不启用多节点支持,则直接返回
|
||||
return;
|
||||
#else
|
||||
RemoteQueryPath* innerpath = NULL;
|
||||
|
|
@ -170,9 +195,11 @@ extern void create_joinrel_rqpath(PlannerInfo* root, RelOptInfo* joinrel, RelOpt
|
|||
List* other_quals = NIL;
|
||||
|
||||
/* If GUC does not allow remote join optimization, so be it */
|
||||
// 如果GUC不允许远程连接优化,则直接返回
|
||||
if (!enable_remotejoin)
|
||||
return;
|
||||
|
||||
// 通过pgxc_find_remotequery_path函数查找内部关系和外部关系的远程查询路径
|
||||
innerpath = pgxc_find_remotequery_path(innerrel);
|
||||
outerpath = pgxc_find_remotequery_path(outerrel);
|
||||
/*
|
||||
|
|
@ -184,14 +211,18 @@ extern void create_joinrel_rqpath(PlannerInfo* root, RelOptInfo* joinrel, RelOpt
|
|||
* attaching the unshippable qual to the join itself, and thus shipping join
|
||||
* but evaluating the qual on join result. But we don't attempt it for now
|
||||
*/
|
||||
// 如果没有找到内部路径、外部路径,或者这些路径具有不可运输的限制条件,则直接返回
|
||||
if (!innerpath || !outerpath || innerpath->rqhas_unshippable_qual || outerpath->rqhas_unshippable_qual)
|
||||
return;
|
||||
|
||||
// 获取内部路径和外部路径的执行节点信息
|
||||
inner_en = innerpath->rqpath_en;
|
||||
outer_en = outerpath->rqpath_en;
|
||||
|
||||
// 如果内部执行节点或外部执行节点为空,则抛出错误
|
||||
if (!inner_en || !outer_en)
|
||||
elog(ERROR, "No node list provided for remote query path");
|
||||
|
||||
/*
|
||||
* Collect quals from restrictions so as to check the shippability of a JOIN
|
||||
* between distributed relations.
|
||||
|
|
@ -216,15 +247,20 @@ extern void create_joinrel_rqpath(PlannerInfo* root, RelOptInfo* joinrel, RelOpt
|
|||
* If the nodelists on both the sides of JOIN can be merged, the JOIN is
|
||||
* shippable.
|
||||
*/
|
||||
join_en = pgxc_is_join_shippable(inner_en,
|
||||
outer_en,
|
||||
innerpath->rqhas_unshippable_tlist,
|
||||
outerpath->rqhas_unshippable_tlist,
|
||||
jointype,
|
||||
(Node*)join_quals);
|
||||
if (join_en)
|
||||
add_path(joinrel,
|
||||
(Path*)create_remotequery_path(root, joinrel, join_en, outerpath, innerpath, jointype, restrictlist));
|
||||
return;
|
||||
// 调用pgxc_is_join_shippable函数来检查连接是否可以进行远程运行,并获取适用于连接的执行节点信息
|
||||
join_en = pgxc_is_join_shippable(inner_en,
|
||||
outer_en,
|
||||
innerpath->rqhas_unshippable_tlist,
|
||||
outerpath->rqhas_unshippable_tlist,
|
||||
jointype,
|
||||
(Node*)join_quals);
|
||||
|
||||
// 如果join_en不为空(表示连接可以进行远程运行),则创建连接关系的远程查询路径并将其添加到joinrel的路径列表中
|
||||
if (join_en)
|
||||
add_path(joinrel,
|
||||
(Path*)create_remotequery_path(root, joinrel, join_en, outerpath, innerpath, jointype, restrictlist));
|
||||
|
||||
// 函数结束
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,22 +62,29 @@
|
|||
* @param[IN] src: the source stream info pair.
|
||||
* @return void
|
||||
*/
|
||||
// 复制 StreamInfoPair 结构体的内容,从源 (src) 到目标 (dst)。
|
||||
void copy_stream_info_pair(StreamInfoPair* dst, StreamInfoPair* src)
|
||||
{
|
||||
// 检查目标和源是否为 NULL,如果是则直接返回。
|
||||
if (dst == NULL || src == NULL)
|
||||
return;
|
||||
|
||||
// 使用 errno_t 变量 rc 来存储 memcpy_s 函数的返回值。
|
||||
errno_t rc = EOK;
|
||||
|
||||
// 使用 memcpy_s 函数将 src 的 inner_info 成员的内容复制到 dst 的 inner_info 成员。
|
||||
rc = memcpy_s(&dst->inner_info, sizeof(StreamInfo), &src->inner_info, sizeof(StreamInfo));
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
// 使用 memcpy_s 函数将 src 的 outer_info 成员的内容复制到 dst 的 outer_info 成员。
|
||||
rc = memcpy_s(&dst->outer_info, sizeof(StreamInfo), &src->outer_info, sizeof(StreamInfo));
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
// 将 dst 的 skew_optimize 成员设置为 SKEW_RES_NONE。
|
||||
dst->skew_optimize = SKEW_RES_NONE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @Description: construnctor for PathGen.
|
||||
*
|
||||
|
|
@ -123,9 +130,11 @@ void PathGen::addPath(Path* new_path)
|
|||
* @param[IN] required_outer: the set of required outer rels.
|
||||
*/
|
||||
JoinPathGenBase::JoinPathGenBase(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, JoinType save_jointype,
|
||||
// JoinPathGenBase 类的构造函数,用于初始化关联的属性和成员变量。
|
||||
SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors, List* joinclauses, List* restrictinfo,
|
||||
Path* outer_path, Path* inner_path, Relids required_outer)
|
||||
: PathGen(root, joinrel),
|
||||
: PathGen(root, joinrel),// 调用基类 PathGen 的构造函数初始化基类成员。
|
||||
// 初始化各种成员变量,这些成员变量在类定义中声明。
|
||||
m_jointype(jointype),
|
||||
m_saveJointype(save_jointype),
|
||||
m_workspace(NULL),
|
||||
|
|
@ -161,23 +170,33 @@ JoinPathGenBase::JoinPathGenBase(PlannerInfo* root, RelOptInfo* joinrel, JoinTyp
|
|||
m_redistributeOuter(false),
|
||||
m_canBroadcastInner(false),
|
||||
m_canBroadcastOuter(false)
|
||||
{
|
||||
{ // 调用 init() 函数来完成进一步的初始化。
|
||||
init();
|
||||
}
|
||||
|
||||
/*
|
||||
* @Description: decontructor function for join path generation.
|
||||
*/
|
||||
// 析构函数,用于释放资源和清理成员变量。
|
||||
JoinPathGenBase::~JoinPathGenBase()
|
||||
{
|
||||
// 检查 m_resourceOwner 是否为 NULL。
|
||||
if (m_resourceOwner != NULL) {
|
||||
// 释放资源:在锁之前的阶段释放。
|
||||
ResourceOwnerRelease(m_resourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
|
||||
// 释放资源:锁定阶段释放。
|
||||
ResourceOwnerRelease(m_resourceOwner, RESOURCE_RELEASE_LOCKS, false, false);
|
||||
// 释放资源:在锁之后的阶段释放。
|
||||
ResourceOwnerRelease(m_resourceOwner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
|
||||
|
||||
// 删除 m_resourceOwner 对象。
|
||||
ResourceOwnerDelete(m_resourceOwner);
|
||||
|
||||
// 将 m_resourceOwner 设置为 NULL,表示资源已被释放。
|
||||
m_resourceOwner = NULL;
|
||||
}
|
||||
|
||||
// 重置以下成员变量,将它们设置为初始状态或 NULL。
|
||||
m_distributeKeysInner = NIL;
|
||||
m_distributeKeysOuter = NIL;
|
||||
m_innerPath = NULL;
|
||||
|
|
@ -206,63 +225,92 @@ JoinPathGenBase::~JoinPathGenBase()
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
// 初始化方法,用于设置成员变量的初始状态。
|
||||
void JoinPathGenBase::init()
|
||||
{
|
||||
// 设置连接方法为哈希连接。
|
||||
m_joinmethod = T_HashJoin;
|
||||
|
||||
// 初始化工作区为 NULL。
|
||||
m_workspace = NULL;
|
||||
|
||||
// 初始化路径键列表为空。
|
||||
m_pathkeys = NIL;
|
||||
|
||||
// 初始化目标分发方式为空。
|
||||
m_targetDistribution = NULL;
|
||||
|
||||
// 初始化外部流路径和内部流路径为空。
|
||||
m_outerStreamPath = NULL;
|
||||
m_innerStreamPath = NULL;
|
||||
|
||||
// 获取内部和外部关系。
|
||||
m_innerRel = m_innerPath->parent;
|
||||
m_outerRel = m_outerPath->parent;
|
||||
|
||||
// 初始化 RRInfo(Range Table),将其置为空列表。
|
||||
m_rrinfoInner = NIL;
|
||||
m_rrinfoOuter = NIL;
|
||||
|
||||
// 获取内部和外部路径的分发键列表。
|
||||
m_distributeKeysInner = m_innerPath->distribute_keys;
|
||||
m_distributeKeysOuter = m_outerPath->distribute_keys;
|
||||
|
||||
/* Init replicate flag. */
|
||||
// 初始化复制标志。
|
||||
m_replicateInner = is_replicated_path(m_innerPath);
|
||||
m_replicateOuter = is_replicated_path(m_outerPath);
|
||||
|
||||
/* Init broadcast flag base on join type etc. */
|
||||
// 根据连接类型等信息初始化广播标志。
|
||||
m_canBroadcastInner =
|
||||
can_broadcast_inner(m_jointype, m_saveJointype, m_replicateOuter, m_distributeKeysOuter, m_outerPath);
|
||||
m_canBroadcastOuter =
|
||||
can_broadcast_outer(m_jointype, m_saveJointype, m_replicateInner, m_distributeKeysInner, m_innerPath);
|
||||
|
||||
// 初始化流信息列表为空列表。
|
||||
m_streamInfoList = NIL;
|
||||
|
||||
// 初始化流信息对为空。
|
||||
m_streamInfoPair = NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create a resource owner to keep track of resources
|
||||
* in order to release resources when catch the exception.
|
||||
*/
|
||||
m_resourceOwner = ResourceOwnerCreate(t_thrd.utils_cxt.CurrentResourceOwner, "join_path_gen",
|
||||
THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER));
|
||||
// 创建资源所有者,用于管理资源的释放。
|
||||
m_resourceOwner = ResourceOwnerCreate(t_thrd.utils_cxt.CurrentResourceOwner, "join_path_gen",
|
||||
THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER));
|
||||
|
||||
m_dop = 0;
|
||||
m_multipleInner = 1.0;
|
||||
m_multipleOuter = 1.0;
|
||||
// 初始化 DOP(Degree of Parallelism),设置为 0。
|
||||
m_dop = 0;
|
||||
|
||||
// 初始化内部和外部的多重度,都设置为 1.0。
|
||||
m_multipleInner = 1.0;
|
||||
m_multipleOuter = 1.0;
|
||||
|
||||
// 初始化内部和外部是否需要重新分发数据的标志,都设置为 false。
|
||||
m_redistributeInner = false;
|
||||
m_redistributeOuter = false;
|
||||
|
||||
m_redistributeInner = false;
|
||||
m_redistributeOuter = false;
|
||||
}
|
||||
|
||||
void JoinPathGenBase::initRangeListDistribution()
|
||||
{
|
||||
m_rangelistOuter = IsLocatorDistributedBySlice(m_outerPath->locator_type);
|
||||
m_rangelistInner = IsLocatorDistributedBySlice(m_innerPath->locator_type);
|
||||
m_sameBoundary = false;
|
||||
|
||||
if (m_rangelistOuter || m_rangelistInner) {
|
||||
m_sameBoundary = IsSliceInfoEqualByOid(m_outerPath->rangelistOid, m_innerPath->rangelistOid);
|
||||
if (m_sameBoundary) {
|
||||
if (m_redistributeOuter || m_redistributeInner) {
|
||||
// 检查外部路径是否分布在分片上(range/list分布方式)
|
||||
m_rangelistOuter = IsLocatorDistributedBySlice(m_outerPath->locator_type);
|
||||
|
||||
// 检查内部路径是否分布在分片上(range/list分布方式)
|
||||
m_rangelistInner = IsLocatorDistributedBySlice(m_innerPath->locator_type);
|
||||
|
||||
// 初始化标志以指示外部和内部路径是否具有相同的边界
|
||||
m_sameBoundary = false;
|
||||
|
||||
// 如果外部或内部路径分布在分片上,则检查它们是否具有相同的边界
|
||||
if (m_rangelistOuter || m_rangelistInner) {
|
||||
m_sameBoundary = IsSliceInfoEqualByOid(m_outerPath->rangelistOid, m_innerPath->rangelistOid);
|
||||
|
||||
// 如果具有相同的边界
|
||||
if (m_sameBoundary) {
|
||||
if (m_redistributeOuter || m_redistributeInner) {
|
||||
/* if one side needs to redistribtue, the other side should redistribute too */
|
||||
m_redistributeOuter = true;
|
||||
m_redistributeInner = true;
|
||||
|
|
@ -285,11 +333,14 @@ void JoinPathGenBase::initRangeListDistribution()
|
|||
m_redistributeInner = true;
|
||||
}
|
||||
}
|
||||
if (m_rangelistOuter && m_redistributeOuter) {
|
||||
m_distributeKeysOuter = NIL;
|
||||
}
|
||||
if (m_rangelistInner && m_redistributeInner) {
|
||||
m_distributeKeysInner = NIL;
|
||||
// 如果外部路径使用范围/列表分布并需要重新分发,则清除外部分布键
|
||||
if (m_rangelistOuter && m_redistributeOuter) {
|
||||
m_distributeKeysOuter = NIL;
|
||||
}
|
||||
|
||||
// 如果内部路径使用范围/列表分布并需要重新分发,则清除内部分布键
|
||||
if (m_rangelistInner && m_redistributeInner) {
|
||||
m_distributeKeysInner = NIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -351,6 +402,7 @@ const bool JoinPathGenBase::isParallelEnable()
|
|||
*/
|
||||
List* JoinPathGenBase::getOthersideKey(bool stream_outer)
|
||||
{
|
||||
// 根据是否是外部流,选择相应的约束信息和关联的关系信息
|
||||
List* rinfo = stream_outer ? m_rrinfoInner : m_rrinfoOuter;
|
||||
RelOptInfo* otherside_rel = stream_outer ? m_outerRel : m_innerRel;
|
||||
double* multiple = stream_outer ? &m_multipleOuter : &m_multipleInner;
|
||||
|
|
@ -361,11 +413,13 @@ List* JoinPathGenBase::getOthersideKey(bool stream_outer)
|
|||
Node* match_var = NULL;
|
||||
ListCell* cell = NULL;
|
||||
|
||||
// 遍历约束信息
|
||||
foreach (cell, rinfo) {
|
||||
EquivalenceClass* oeclass = NULL;
|
||||
RestrictInfo* restrictinfo = (RestrictInfo*)lfirst(cell);
|
||||
match_var = NULL;
|
||||
|
||||
// 根据约束信息的左右侧关系判断 EquivalenceClass
|
||||
if (bms_is_subset(restrictinfo->left_relids, otherside_rel->relids)) {
|
||||
oeclass = restrictinfo->left_ec;
|
||||
} else {
|
||||
|
|
@ -375,6 +429,7 @@ List* JoinPathGenBase::getOthersideKey(bool stream_outer)
|
|||
|
||||
Assert(restrictinfo->orclause == NULL);
|
||||
|
||||
// 遍历 EquivalenceClass 的成员
|
||||
foreach (lc1, oeclass->ec_members) {
|
||||
EquivalenceMember* em = (EquivalenceMember*)lfirst(lc1);
|
||||
Node* nem = (Node*)em->em_expr;
|
||||
|
|
@ -382,10 +437,14 @@ List* JoinPathGenBase::getOthersideKey(bool stream_outer)
|
|||
List* vars = NIL;
|
||||
Relids relIds;
|
||||
|
||||
// 如果数据类型无效或者不可分布,则跳过
|
||||
if (!OidIsValid(datatype) || !IsTypeDistributable(datatype))
|
||||
continue;
|
||||
|
||||
// 提取表达式中的关系 ID
|
||||
relIds = pull_varnos(nem);
|
||||
|
||||
// 如果关系 ID 为空或不是目标关系的子集,则跳过
|
||||
if (bms_is_empty(relIds) || !bms_is_subset(relIds, otherside_rel->relids)) {
|
||||
bms_free(relIds);
|
||||
continue;
|
||||
|
|
@ -428,7 +487,7 @@ List* JoinPathGenBase::getOthersideKey(bool stream_outer)
|
|||
|
||||
/* Calculate skew multiple of the distribute keys. */
|
||||
*multiple = get_multiple_by_distkey(m_root, key_list, otherside_rel->rows);
|
||||
|
||||
// 检查分布键是否有效
|
||||
if (!ng_is_distribute_key_valid(m_root, key_list, targetlist)) {
|
||||
list_free(key_list);
|
||||
key_list = NIL;
|
||||
|
|
@ -447,6 +506,7 @@ List* JoinPathGenBase::getOthersideKey(bool stream_outer)
|
|||
* @return void.
|
||||
*/
|
||||
void JoinPathGenBase::getDistributeKeys(
|
||||
// 调用 get_distribute_keys 函数以获取连接路径的分布键信息
|
||||
List** distribute_keys_outer, List** distribute_keys_inner, List* desired_keys, bool exact_match)
|
||||
{
|
||||
get_distribute_keys(m_root,
|
||||
|
|
@ -472,31 +532,43 @@ void JoinPathGenBase::getDistributeKeys(
|
|||
*/
|
||||
bool JoinPathGenBase::checkJoinMethodAlternative(bool* try_eq_related_indirectly)
|
||||
{
|
||||
// 初始化变量 hasalternative 为 false
|
||||
bool hasalternative = false;
|
||||
ListCell* l = NULL;
|
||||
|
||||
// 遍历连接限制信息列表 m_joinRestrictinfo
|
||||
foreach (l, m_joinRestrictinfo) {
|
||||
RestrictInfo* restrictinfo = (RestrictInfo*)lfirst(l);
|
||||
|
||||
/* Check if clause is a hashable or mergeable operator clause */
|
||||
// 检查限制条件是否可以使用哈希连接或合并连接,并且连接的两个表匹配
|
||||
if (restrictinfo->can_join && clause_sides_match_join(restrictinfo, m_outerRel, m_innerRel)) {
|
||||
// 如果启用了哈希连接并且限制条件包含哈希连接操作符,则设置 hasalternative 为 true
|
||||
if (u_sess->attr.attr_sql.enable_hashjoin && restrictinfo->hashjoinoperator != InvalidOid)
|
||||
hasalternative = true;
|
||||
|
||||
// 如果启用了合并连接并且限制条件包含合并连接的操作符族,则设置 hasalternative 为 true
|
||||
if (u_sess->attr.attr_sql.enable_mergejoin && restrictinfo->mergeopfamilies != NIL)
|
||||
hasalternative = true;
|
||||
|
||||
// 如果启用了哈希连接或合并连接,将 try_eq_related_indirectly 设置为 true
|
||||
if (u_sess->attr.attr_sql.enable_hashjoin || u_sess->attr.attr_sql.enable_mergejoin)
|
||||
*try_eq_related_indirectly = true;
|
||||
}
|
||||
|
||||
// 如果已经找到了可行的连接方法,则退出循环
|
||||
if (hasalternative)
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果启用了嵌套循环连接,并且连接类型不是全外连接,则设置 hasalternative 为 true
|
||||
if (u_sess->attr.attr_sql.enable_nestloop && m_jointype != JOIN_FULL)
|
||||
hasalternative = true;
|
||||
|
||||
// 返回是否找到可行的连接方法
|
||||
return hasalternative;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @Description: check to see if this path is a nestloop index params path
|
||||
*
|
||||
|
|
@ -571,23 +643,31 @@ bool JoinPathGenBase::isReplicateJoinCanRedistribute()
|
|||
* redistribute; 2.Outer is hash and inner is replicate: LHS join or probing side execute on CN, and build side need
|
||||
* redistribute or is param path;
|
||||
*/
|
||||
// 如果外部表为复制表而内部表不是
|
||||
if (m_replicateOuter && !m_replicateInner) {
|
||||
// 如果连接类型是右连接且内部表需要重新分布,则不能重新分布
|
||||
if (RHS_join(m_saveJointype) && m_redistributeInner)
|
||||
can_redistribute = false;
|
||||
} else if (!m_replicateOuter && m_replicateInner) {
|
||||
}
|
||||
// 如果内部表为复制表而外部表不是
|
||||
else if (!m_replicateOuter && m_replicateInner) {
|
||||
// 如果连接类型是左连接且外部表需要重新分布或者是参数路径,则不能重新分布
|
||||
if (LHS_join(m_saveJointype) && (m_redistributeOuter || is_param_path()))
|
||||
can_redistribute = false;
|
||||
} else {
|
||||
}
|
||||
// 如果外部表和内部表都不是复制表
|
||||
else {
|
||||
can_redistribute = false;
|
||||
}
|
||||
|
||||
/* Need hash filter for replicate table, so delete this path. */
|
||||
// 如果可以重新分布,则从 m_streamInfoList 中删除 m_streamInfoPair,并释放内存
|
||||
if (can_redistribute) {
|
||||
m_streamInfoList = list_delete(m_streamInfoList, m_streamInfoPair);
|
||||
pfree_ext(m_streamInfoPair);
|
||||
m_streamInfoPair = NULL;
|
||||
}
|
||||
|
||||
// 返回是否可以重新分布
|
||||
return can_redistribute;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,12 +34,19 @@
|
|||
#include "utils/lsyscache.h"
|
||||
|
||||
/* local functions */
|
||||
// 检查特殊连接是否可移除
|
||||
static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo);
|
||||
// 从查询中移除关系
|
||||
static void remove_rel_from_query(PlannerInfo* root, int relid, Relids joinrelids);
|
||||
// 从连接列表中移除关系
|
||||
static List* remove_rel_from_joinlist(List* joinlist, int relid, int* nremoved);
|
||||
// 检查关系是否支持去重
|
||||
static bool rel_supports_distinctness(PlannerInfo* root, RelOptInfo* rel);
|
||||
// 检查关系是否对指定子句去重
|
||||
static bool rel_is_distinct_for(PlannerInfo* root, RelOptInfo* rel, List* clause_list);
|
||||
// 检查列的唯一性
|
||||
static bool check_column_uniqueness(List* groupClause, List* targetList, List* colnos, List* opids);
|
||||
// 查找具有唯一性的列
|
||||
static Oid distinct_col_search(int colno, List* colnos, List* opids);
|
||||
|
||||
/*
|
||||
|
|
@ -50,23 +57,25 @@ static Oid distinct_col_search(int colno, List* colnos, List* opids);
|
|||
* We are passed the current joinlist and return the updated list. Other
|
||||
* data structures that have to be updated are accessible via "root".
|
||||
*/
|
||||
List* remove_useless_joins(PlannerInfo* root, List* joinlist)
|
||||
List* remove_useless_joins(PlannerInfo* root, List* joinlist)// 移除无用的连接
|
||||
{
|
||||
ListCell* lc = NULL;
|
||||
ListCell* pnext = NULL;
|
||||
ListCell* lc = NULL;// 用于遍历join_info_list的指针
|
||||
ListCell* pnext = NULL;// 用于保存下一个元素的指针
|
||||
|
||||
/*
|
||||
* We are only interested in relations that are left-joined to, so we can
|
||||
* scan the join_info_list to find them easily.
|
||||
*/
|
||||
restart:
|
||||
// 遍历特殊连接信息列表 join_info_list
|
||||
for (lc = list_head(root->join_info_list); lc != NULL; lc = pnext) {
|
||||
SpecialJoinInfo* sjinfo = (SpecialJoinInfo*)lfirst(lc);
|
||||
pnext = lnext(lc);
|
||||
int innerrelid;
|
||||
int nremoved;
|
||||
SpecialJoinInfo* sjinfo = (SpecialJoinInfo*)lfirst(lc);// 获取特殊连接信息
|
||||
pnext = lnext(lc); // 保存下一个元素的指针
|
||||
int innerrelid;// 内部关系的标识符
|
||||
int nremoved;// 移除的关系数量
|
||||
|
||||
/* Skip if not removable */
|
||||
// 检查特殊连接是否可移除,如果不可移除则继续下一个连接
|
||||
if (!join_is_removable(root, sjinfo))
|
||||
continue;
|
||||
|
||||
|
|
@ -75,14 +84,14 @@ restart:
|
|||
* righthand is a single baserel. Remove that rel from the query and
|
||||
* joinlist.
|
||||
*/
|
||||
innerrelid = bms_singleton_member(sjinfo->min_righthand);
|
||||
innerrelid = bms_singleton_member(sjinfo->min_righthand);// 获取内部关系的标识符
|
||||
|
||||
remove_rel_from_query(root, innerrelid, bms_union(sjinfo->min_lefthand, sjinfo->min_righthand));
|
||||
remove_rel_from_query(root, innerrelid, bms_union(sjinfo->min_lefthand, sjinfo->min_righthand));// 从查询中移除内部关系
|
||||
|
||||
/* We verify that exactly one reference gets removed from joinlist */
|
||||
nremoved = 0;
|
||||
joinlist = remove_rel_from_joinlist(joinlist, innerrelid, &nremoved);
|
||||
if (nremoved != 1)
|
||||
joinlist = remove_rel_from_joinlist(joinlist, innerrelid, &nremoved);// 从连接列表 joinlist 中移除内部关系,并记录移除的关系数量
|
||||
if (nremoved != 1)// 从连接列表 joinlist 中移除内部关系,并记录移除的关系数量
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
|
|
@ -92,7 +101,7 @@ restart:
|
|||
* We can delete this SpecialJoinInfo from the list too, since it's no
|
||||
* longer of interest.
|
||||
*/
|
||||
root->join_info_list = list_delete_ptr(root->join_info_list, sjinfo);
|
||||
root->join_info_list = list_delete_ptr(root->join_info_list, sjinfo);// 从连接列表 joinlist 中移除内部关系,并记录移除的关系数量
|
||||
|
||||
/*
|
||||
* Restart the scan. This is necessary to ensure we find all
|
||||
|
|
@ -102,10 +111,10 @@ restart:
|
|||
* current list cell, we'd have to have some kluge to continue the
|
||||
* list scan anyway.
|
||||
*/
|
||||
goto restart;
|
||||
goto restart;// 从连接列表 joinlist 中移除内部关系,并记录移除的关系数量
|
||||
}
|
||||
|
||||
return joinlist;
|
||||
return joinlist;// 从连接列表 joinlist 中移除内部关系,并记录移除的关系数量
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -118,18 +127,21 @@ restart:
|
|||
* rather than mixing outer and inner vars on either side. If it matches,
|
||||
* we set the transient flag outer_is_left to identify which side is which.
|
||||
*/
|
||||
static inline bool clause_sides_match_join(RestrictInfo* rinfo, Relids outerrelids, Relids innerrelids)
|
||||
static inline bool clause_sides_match_join(RestrictInfo* rinfo, Relids outerrelids, Relids innerrelids)// 内联函数,检查子句的两侧是否匹配连接
|
||||
|
||||
{
|
||||
// 检查约束信息的左侧关系是否是外部关系的子集,右侧关系是否是内部关系的子集
|
||||
if (bms_is_subset(rinfo->left_relids, outerrelids) && bms_is_subset(rinfo->right_relids, innerrelids)) {
|
||||
/* lefthand side is outer */
|
||||
rinfo->outer_is_left = true;
|
||||
return true;
|
||||
rinfo->outer_is_left = true;// 设置 outer_is_left 为 true,表示左侧关系在连接中是外部关系
|
||||
return true;// 返回 true,表示约束信息的关系符合连接
|
||||
} else if (bms_is_subset(rinfo->left_relids, innerrelids) && bms_is_subset(rinfo->right_relids, outerrelids)) {
|
||||
/* righthand side is outer */
|
||||
rinfo->outer_is_left = false;
|
||||
return true;
|
||||
rinfo->outer_is_left = false;// 设置 outer_is_left 为 false,表示左侧关系在连接中是内部关系
|
||||
return true;// 返回 true,表示约束信息的关系符合连接
|
||||
}
|
||||
return false; /* no good for these input relations */
|
||||
// 如果左右关系都不符合连接,返回 false
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -143,7 +155,7 @@ static inline bool clause_sides_match_join(RestrictInfo* rinfo, Relids outerreli
|
|||
* have to check that the inner side doesn't generate any variables needed
|
||||
* above the join.
|
||||
*/
|
||||
static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
||||
static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)// 检查连接是否可移除
|
||||
{
|
||||
int innerrelid;
|
||||
RelOptInfo* innerrel = NULL;
|
||||
|
|
@ -156,6 +168,7 @@ static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
|||
* Must be a non-delaying left join to a single baserel, else we aren't
|
||||
* going to be able to do anything with it.
|
||||
*/
|
||||
// 仅适用于左连接和左反连接,且右侧关系为单一关系
|
||||
if ((sjinfo->jointype != JOIN_LEFT && sjinfo->jointype != JOIN_LEFT_ANTI_FULL) || sjinfo->delay_upper_joins ||
|
||||
bms_membership(sjinfo->min_righthand) != BMS_SINGLETON)
|
||||
return false;
|
||||
|
|
@ -168,11 +181,11 @@ static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
|||
* are needed above the join, make a quick check to eliminate cases in
|
||||
* which we will surely be unable to prove uniqueness of the innerrel.
|
||||
*/
|
||||
if (!rel_supports_distinctness(root, innerrel))
|
||||
if (!rel_supports_distinctness(root, innerrel))// 检查右侧关系是否支持去重
|
||||
return false;
|
||||
|
||||
/* Compute the relid set for the join we are considering */
|
||||
joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
|
||||
joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);// 计算连接关系的集合
|
||||
|
||||
/*
|
||||
* We can't remove the join if any inner-rel attributes are used above the
|
||||
|
|
@ -187,7 +200,7 @@ static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
|||
* theory that the system attributes are somewhat less likely to be wanted
|
||||
* and should be tested last.
|
||||
*/
|
||||
for (attroff = innerrel->max_attr - innerrel->min_attr; attroff >= 0; attroff--) {
|
||||
for (attroff = innerrel->max_attr - innerrel->min_attr; attroff >= 0; attroff--) {// 检查左侧关系所需的属性是否完全包含在右侧关系中
|
||||
if (!bms_is_subset(innerrel->attr_needed[attroff], joinrelids))
|
||||
return false;
|
||||
}
|
||||
|
|
@ -199,7 +212,7 @@ static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
|||
* for that is relatively expensive, so we first check against ph_eval_at,
|
||||
* which must mention the inner rel if the PHV uses any inner-rel attrs.
|
||||
*/
|
||||
foreach (l, root->placeholder_list) {
|
||||
foreach (l, root->placeholder_list) {// 检查占位符的需要属性是否完全包含在右侧关系中
|
||||
PlaceHolderInfo* phinfo = (PlaceHolderInfo*)lfirst(l);
|
||||
|
||||
if (bms_is_subset(phinfo->ph_needed, joinrelids))
|
||||
|
|
@ -217,7 +230,7 @@ static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
|||
* it's what we want. The mergejoinability test also eliminates clauses
|
||||
* containing volatile functions, which we couldn't depend on.
|
||||
*/
|
||||
foreach (l, innerrel->joininfo) {
|
||||
foreach (l, innerrel->joininfo) {// 遍历右侧关系的连接信息,检查是否符合移除条件
|
||||
RestrictInfo* restrictinfo = (RestrictInfo*)lfirst(l);
|
||||
|
||||
/*
|
||||
|
|
@ -226,6 +239,7 @@ static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
|||
* above the outer join, even if it references no other rels (it might
|
||||
* be from WHERE, for example).
|
||||
*/
|
||||
// 如果约束已下推或所需关系不符合连接关系,则跳过
|
||||
if (restrictinfo->is_pushed_down || !bms_equal(restrictinfo->required_relids, joinrelids)) {
|
||||
/*
|
||||
* If such a clause actually references the inner rel then join
|
||||
|
|
@ -246,11 +260,11 @@ static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
|||
* Check if clause has the form "outer op inner" or "inner op outer",
|
||||
* and if so mark which side is inner.
|
||||
*/
|
||||
if (!clause_sides_match_join(restrictinfo, sjinfo->min_lefthand, innerrel->relids))
|
||||
if (!clause_sides_match_join(restrictinfo, sjinfo->min_lefthand, innerrel->relids))// 检查约束的两侧是否匹配连接
|
||||
continue; /* no good for these input relations */
|
||||
|
||||
/* OK, add to list */
|
||||
clause_list = lappend(clause_list, restrictinfo);
|
||||
clause_list = lappend(clause_list, restrictinfo); // 将符合条件的约束加入列表
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -277,38 +291,42 @@ static bool join_is_removable(PlannerInfo* root, SpecialJoinInfo* sjinfo)
|
|||
* Also, join quals involving the rel have to be removed from the joininfo
|
||||
* lists, but only if they belong to the outer join identified by joinrelids.
|
||||
*/
|
||||
// 静态函数,用于从查询计划中移除与给定 relid 关联的关系
|
||||
static void remove_rel_from_query(PlannerInfo* root, int relid, Relids joinrelids)
|
||||
{
|
||||
RelOptInfo* rel = find_base_rel(root, relid);
|
||||
List* joininfos = NIL;
|
||||
Index rti;
|
||||
ListCell* l = NULL;
|
||||
RelOptInfo* rel = find_base_rel(root, relid);// 查找与给定 relid 相关的 RelOptInfo 结构体
|
||||
List* joininfos = NIL;// 用于存储连接信息的列表
|
||||
Index rti;// 用于遍历 RelOptInfo 结构体数组的索引
|
||||
ListCell* l = NULL;// 用于循环迭代的列表元素指针
|
||||
ListCell* nextl = NULL;
|
||||
|
||||
/*
|
||||
* Mark the rel as "dead" to show it is no longer part of the join tree.
|
||||
* (Removing it from the baserel array altogether seems too risky.)
|
||||
*/
|
||||
rel->reloptkind = RELOPT_DEADREL;
|
||||
rel->reloptkind = RELOPT_DEADREL;// 将 rel 标记为已删除状态
|
||||
|
||||
/*
|
||||
* Remove references to the rel from other baserels' attr_needed arrays.
|
||||
*/
|
||||
for (rti = 1; rti < (unsigned int)root->simple_rel_array_size; rti++) {
|
||||
RelOptInfo* otherrel = root->simple_rel_array[rti];
|
||||
for (rti = 1; rti < (unsigned int)root->simple_rel_array_size; rti++) {// 遍历 simple_rel_array 数组中的 RelOptInfo 结构体
|
||||
RelOptInfo* otherrel = root->simple_rel_array[rti]; // 获取当前的 RelOptInfo 结构体
|
||||
int attroff;
|
||||
|
||||
/* there may be empty slots corresponding to non-baserel RTEs */
|
||||
|
||||
// 如果 RelOptInfo 结构体为空,则继续下一轮循环
|
||||
if (otherrel == NULL)
|
||||
continue;
|
||||
|
||||
// 断言确保 RelOptInfo 的索引正确
|
||||
AssertEreport(otherrel->relid == rti, MOD_OPT, "RelOptInfo Index Incorrect.");
|
||||
|
||||
/* no point in processing target rel itself */
|
||||
if (otherrel == rel)
|
||||
if (otherrel == rel)// 如果当前的 RelOptInfo 与目标 rel 相同,则继续下一轮循环
|
||||
continue;
|
||||
|
||||
for (attroff = otherrel->max_attr - otherrel->min_attr; attroff >= 0; attroff--) {
|
||||
for (attroff = otherrel->max_attr - otherrel->min_attr; attroff >= 0; attroff--) {// 遍历属性列表,从属性的 attr_needed 中删除 relid
|
||||
otherrel->attr_needed[attroff] = bms_del_member(otherrel->attr_needed[attroff], relid);
|
||||
}
|
||||
}
|
||||
|
|
@ -321,7 +339,7 @@ static void remove_rel_from_query(PlannerInfo* root, int relid, Relids joinrelid
|
|||
* RHS of the target outer join will be made empty here, but that's OK
|
||||
* since caller will delete that SpecialJoinInfo entirely.
|
||||
*/
|
||||
foreach (l, root->join_info_list) {
|
||||
foreach (l, root->join_info_list) {// 遍历 join_info_list 列表,更新连接信息中的左右关系
|
||||
SpecialJoinInfo* sjinfo = (SpecialJoinInfo*)lfirst(l);
|
||||
|
||||
sjinfo->min_lefthand = bms_del_member(sjinfo->min_lefthand, relid);
|
||||
|
|
@ -338,11 +356,12 @@ static void remove_rel_from_query(PlannerInfo* root, int relid, Relids joinrelid
|
|||
* included in any lateral_lhs set. (It probably can't be, since that
|
||||
* should have precluded deciding to remove it; but let's cope anyway.)
|
||||
*/
|
||||
for (l = list_head(root->lateral_info_list); l != NULL; l = nextl)
|
||||
for (l = list_head(root->lateral_info_list); l != NULL; l = nextl)// 遍历 lateral_info_list 列表,更新 lateral 关系信息
|
||||
{
|
||||
LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
|
||||
|
||||
nextl = lnext(l);
|
||||
// 如果 lateral_rhs 与给定的 relid 相同,则从列表中删除该项
|
||||
if (ljinfo->lateral_rhs == (Index)relid)
|
||||
root->lateral_info_list = list_delete_ptr(root->lateral_info_list,
|
||||
ljinfo);
|
||||
|
|
@ -358,14 +377,14 @@ static void remove_rel_from_query(PlannerInfo* root, int relid, Relids joinrelid
|
|||
* set. An empty eval_at set would confuse later processing since it
|
||||
* would match every possible eval placement.
|
||||
*/
|
||||
foreach (l, root->placeholder_list) {
|
||||
foreach (l, root->placeholder_list) {// 遍历 placeholder_list 列表,更新占位符信息
|
||||
PlaceHolderInfo* phinfo = (PlaceHolderInfo*)lfirst(l);
|
||||
|
||||
phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid);
|
||||
if (bms_is_empty(phinfo->ph_eval_at)) /* oops, belay that */
|
||||
phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid);// 从 ph_eval_at 中删除 relid
|
||||
if (bms_is_empty(phinfo->ph_eval_at)) /* oops, belay that */// 如果 ph_eval_at 变为空集,将 relid 添加回去
|
||||
phinfo->ph_eval_at = bms_add_member(phinfo->ph_eval_at, relid);
|
||||
|
||||
phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid);
|
||||
phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid);// 从 ph_needed 中删除 relid
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -382,22 +401,22 @@ static void remove_rel_from_query(PlannerInfo* root, int relid, Relids joinrelid
|
|||
* loop, because otherwise remove_join_clause_from_rels would destroy the
|
||||
* list while we're scanning it.
|
||||
*/
|
||||
joininfos = list_copy(rel->joininfo);
|
||||
joininfos = list_copy(rel->joininfo);// 复制 rel 的 joininfo 列表,并遍历处理
|
||||
foreach (l, joininfos) {
|
||||
RestrictInfo* rinfo = (RestrictInfo*)lfirst(l);
|
||||
|
||||
remove_join_clause_from_rels(root, rinfo, rinfo->required_relids);
|
||||
remove_join_clause_from_rels(root, rinfo, rinfo->required_relids);// 从连接信息中移除与 relid 相关的子句
|
||||
|
||||
if (rinfo->is_pushed_down || !bms_equal(rinfo->required_relids, joinrelids)) {
|
||||
if (rinfo->is_pushed_down || !bms_equal(rinfo->required_relids, joinrelids)) {// 如果子句已被推送下来或者不等于 joinrelids,则进行处理
|
||||
/* Recheck that qual doesn't actually reference the target rel */
|
||||
AssertEreport(!bms_is_member(relid, rinfo->clause_relids), MOD_OPT, "");
|
||||
/*
|
||||
* The required_relids probably aren't shared with anything else,
|
||||
* but let's copy them just to be sure.
|
||||
*/
|
||||
rinfo->required_relids = bms_copy(rinfo->required_relids);
|
||||
rinfo->required_relids = bms_copy(rinfo->required_relids);// 复制 required_relids,然后从中删除 relid
|
||||
rinfo->required_relids = bms_del_member(rinfo->required_relids, relid);
|
||||
distribute_restrictinfo_to_rels(root, rinfo);
|
||||
distribute_restrictinfo_to_rels(root, rinfo);// 将处理后的信息重新分发到相关的 RelOptInfo 结构体中
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -411,22 +430,23 @@ static void remove_rel_from_query(PlannerInfo* root, int relid, Relids joinrelid
|
|||
* *nremoved is incremented by the number of occurrences removed (there
|
||||
* should be exactly one, but the caller checks that).
|
||||
*/
|
||||
// 静态函数,从连接列表中移除与给定 relid 相关的关系
|
||||
static List* remove_rel_from_joinlist(List* joinlist, int relid, int* nremoved)
|
||||
{
|
||||
List* result = NIL;
|
||||
ListCell* jl = NULL;
|
||||
List* result = NIL;// 用于存储结果的列表
|
||||
ListCell* jl = NULL;// 用于遍历连接列表的列表元素指针
|
||||
|
||||
foreach (jl, joinlist) {
|
||||
Node* jlnode = (Node*)lfirst(jl);
|
||||
foreach (jl, joinlist) {// 遍历连接列表
|
||||
Node* jlnode = (Node*)lfirst(jl);// 获取当前列表元素
|
||||
|
||||
if (IsA(jlnode, RangeTblRef)) {
|
||||
if (IsA(jlnode, RangeTblRef)) {// 如果当前元素是 RangeTblRef
|
||||
int varno = ((RangeTblRef*)jlnode)->rtindex;
|
||||
|
||||
if (varno == relid)
|
||||
if (varno == relid)// 如果 varno 等于 relid,则增加 nremoved 计数
|
||||
(*nremoved)++;
|
||||
else
|
||||
else// 否则将当前元素添加到结果列表中
|
||||
result = lappend(result, jlnode);
|
||||
} else if (IsA(jlnode, List)) {
|
||||
} else if (IsA(jlnode, List)) {// 如果当前元素是 List,则递归调用 remove_rel_from_joinlist 处理子列表
|
||||
/* Recurse to handle subproblem */
|
||||
List* sublist = NIL;
|
||||
|
||||
|
|
@ -434,7 +454,7 @@ static List* remove_rel_from_joinlist(List* joinlist, int relid, int* nremoved)
|
|||
/* Avoid including empty sub-lists in the result */
|
||||
if (sublist != NIL)
|
||||
result = lappend(result, sublist);
|
||||
} else {
|
||||
} else {// 如果是其他类型的节点,则报错
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_UNEXPECTED_NODE_STATE),
|
||||
|
|
@ -443,7 +463,7 @@ static List* remove_rel_from_joinlist(List* joinlist, int relid, int* nremoved)
|
|||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;// 返回处理后的结果列表
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -457,7 +477,7 @@ static List* remove_rel_from_joinlist(List* joinlist, int relid, int* nremoved)
|
|||
* rel_is_distinct_for()'s argument lists if the call could not possibly
|
||||
* succeed.
|
||||
*/
|
||||
static bool rel_supports_distinctness(PlannerInfo* root, RelOptInfo* rel)
|
||||
static bool rel_supports_distinctness(PlannerInfo* root, RelOptInfo* rel)// 静态函数,判断 rel 是否支持 DISTINCT 操作
|
||||
{
|
||||
/*
|
||||
* We only handle two cases here:
|
||||
|
|
@ -465,9 +485,9 @@ static bool rel_supports_distinctness(PlannerInfo* root, RelOptInfo* rel)
|
|||
* 2. subquery with aggreations
|
||||
* We can improve later when we can propagate uniqueness
|
||||
*/
|
||||
if (rel->reloptkind != RELOPT_BASEREL)
|
||||
if (rel->reloptkind != RELOPT_BASEREL)// 如果 rel 不是基本关系,则不支持 DISTINCT
|
||||
return false;
|
||||
if (rel->rtekind == RTE_RELATION) {
|
||||
if (rel->rtekind == RTE_RELATION) {// 如果 rel 是关系表(RTE_RELATION)
|
||||
/*
|
||||
* For a plain relation, we only know how to prove uniqueness by
|
||||
* reference to unique indexes. Make sure there's at least one
|
||||
|
|
@ -475,17 +495,18 @@ static bool rel_supports_distinctness(PlannerInfo* root, RelOptInfo* rel)
|
|||
* it's a partial index, it must match the query. (Keep these
|
||||
* conditions in sync with relation_has_unique_index_for!)
|
||||
*/
|
||||
// 遍历索引列表,查找唯一且立即可用的索引
|
||||
ListCell* lc = NULL;
|
||||
|
||||
foreach (lc, rel->indexlist) {
|
||||
IndexOptInfo* ind = (IndexOptInfo*)lfirst(lc);
|
||||
|
||||
// 如果索引是唯一的、立即可用的,并且没有过滤条件,则支持 DISTINCT
|
||||
if (ind->unique && ind->immediate && (ind->indpred == NIL || ind->predOK))
|
||||
return true;
|
||||
}
|
||||
} else if (rel->rtekind == RTE_SUBQUERY) {
|
||||
Query* subquery = root->simple_rte_array[rel->relid]->subquery;
|
||||
|
||||
// 如果 rel 是子查询,则判断子查询是否支持 DISTINCT
|
||||
/* Check if the subquery has any qualities that support distinctness */
|
||||
if (query_supports_distinctness(subquery))
|
||||
return true;
|
||||
|
|
@ -512,24 +533,25 @@ static bool rel_supports_distinctness(PlannerInfo* root, RelOptInfo* rel)
|
|||
* is OK for current uses, because the clause_list is built by the caller for
|
||||
* the sole purpose of passing to this function.
|
||||
*/
|
||||
static bool rel_is_distinct_for(PlannerInfo* root, RelOptInfo* rel, List* clause_list)
|
||||
static bool rel_is_distinct_for(PlannerInfo* root, RelOptInfo* rel, List* clause_list)// 静态函数,判断 rel 是否对给定的列列表支持 DISTINCT 操作
|
||||
{
|
||||
/*
|
||||
* We could skip a couple of tests here if we assume all callers checked
|
||||
* rel_supports_distinctness first, but it doesn't seem worth taking any
|
||||
* risk for.
|
||||
*/
|
||||
if (rel->reloptkind != RELOPT_BASEREL)
|
||||
if (rel->reloptkind != RELOPT_BASEREL)// 如果 rel 不是基本关系,则不支持 DISTINCT
|
||||
return false;
|
||||
if (rel->rtekind == RTE_RELATION) {
|
||||
if (rel->rtekind == RTE_RELATION) {// 如果 rel 是关系表(RTE_RELATION)
|
||||
|
||||
/*
|
||||
* Examine the indexes to see if we have a matching unique index.
|
||||
* relation_has_unique_index_for automatically adds any usable
|
||||
* restriction clauses for the rel, so we needn't do that here.
|
||||
*/
|
||||
if (relation_has_unique_index_for(root, rel, clause_list, NIL, NIL))
|
||||
if (relation_has_unique_index_for(root, rel, clause_list, NIL, NIL))// 调用 relation_has_unique_index_for 函数判断是否有唯一索引支持 DISTINCT
|
||||
return true;
|
||||
} else if (rel->rtekind == RTE_SUBQUERY) {
|
||||
} else if (rel->rtekind == RTE_SUBQUERY) {// 如果 rel 是子查询,则判断子查询是否支持 DISTINCT
|
||||
Index relid = rel->relid;
|
||||
Query* subquery = root->simple_rte_array[relid]->subquery;
|
||||
List* colnos = NIL;
|
||||
|
|
@ -545,7 +567,7 @@ static bool rel_is_distinct_for(PlannerInfo* root, RelOptInfo* rel, List* clause
|
|||
* (XXX we are not considering restriction clauses attached to the
|
||||
* subquery; is that worth doing?)
|
||||
*/
|
||||
foreach (l, clause_list) {
|
||||
foreach (l, clause_list) { // 遍历子句列表,获取列号和操作符号
|
||||
RestrictInfo* rinfo = (RestrictInfo*)lfirst(l);
|
||||
Oid op;
|
||||
Var* var = NULL;
|
||||
|
|
@ -567,7 +589,7 @@ static bool rel_is_distinct_for(PlannerInfo* root, RelOptInfo* rel, List* clause
|
|||
else
|
||||
var = (Var*)get_leftop(rinfo->clause);
|
||||
|
||||
if (var != NULL) {
|
||||
if (var != NULL) { // 尝试找到兼容的 Var
|
||||
/* try to find compatible var */
|
||||
var = locate_distribute_var((Expr*)var);
|
||||
}
|
||||
|
|
@ -584,7 +606,7 @@ static bool rel_is_distinct_for(PlannerInfo* root, RelOptInfo* rel, List* clause
|
|||
opids = lappend_oid(opids, op);
|
||||
}
|
||||
|
||||
if (query_is_distinct_for(subquery, colnos, opids))
|
||||
if (query_is_distinct_for(subquery, colnos, opids))// 调用 query_is_distinct_for 函数判断子查询是否支持 DISTINCT
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -601,7 +623,7 @@ static bool rel_is_distinct_for(PlannerInfo* root, RelOptInfo* rel, List* clause
|
|||
* query_is_distinct_for()'s argument lists if the call could not possibly
|
||||
* succeed.
|
||||
*/
|
||||
bool query_supports_distinctness(Query* query)
|
||||
bool query_supports_distinctness(Query* query)// 判断查询是否支持 DISTINCT 操作
|
||||
{
|
||||
if (query->distinctClause != NIL || query->groupClause != NIL || query->hasAggs || query->havingQual ||
|
||||
query->setOperations)
|
||||
|
|
@ -628,11 +650,12 @@ bool query_supports_distinctness(Query* query)
|
|||
* should give trustworthy answers for all operators that we might need
|
||||
* to deal with here.)
|
||||
*/
|
||||
bool query_is_distinct_for(Query* query, List* colnos, List* opids)
|
||||
bool query_is_distinct_for(Query* query, List* colnos, List* opids)// 判断查询是否对给定列列表 colnos 和操作符列表 opids 支持 DISTINCT 操作
|
||||
{
|
||||
ListCell* l = NULL;
|
||||
Oid opid;
|
||||
|
||||
// 断言:列列表和操作符列表长度必须相等
|
||||
Assert(list_length(colnos) == list_length(opids));
|
||||
|
||||
/*
|
||||
|
|
@ -643,7 +666,7 @@ bool query_is_distinct_for(Query* query, List* colnos, List* opids)
|
|||
* specified columns, since those must be evaluated before de-duplication;
|
||||
* but it doesn't presently seem worth the complication to check that.)
|
||||
*/
|
||||
if (expression_returns_set((Node*)query->targetList))
|
||||
if (expression_returns_set((Node*)query->targetList))// 如果查询的目标列表返回集合,则不支持 DISTINCT
|
||||
return false;
|
||||
|
||||
/*
|
||||
|
|
@ -651,8 +674,9 @@ bool query_is_distinct_for(Query* query, List* colnos, List* opids)
|
|||
* columns in the DISTINCT clause appear in colnos and operator semantics
|
||||
* match.
|
||||
*/
|
||||
// 如果查询中存在 DISTINCT 子句
|
||||
if (query->distinctClause != NIL) {
|
||||
if (check_column_uniqueness(query->distinctClause, query->targetList, colnos, opids))
|
||||
if (check_column_uniqueness(query->distinctClause, query->targetList, colnos, opids))// 调用 check_column_uniqueness 函数检查列是否唯一
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -660,15 +684,15 @@ bool query_is_distinct_for(Query* query, List* colnos, List* opids)
|
|||
* Similarly, GROUP BY guarantees uniqueness if all the grouped columns
|
||||
* appear in colnos and operator semantics match.
|
||||
*/
|
||||
if (query->groupClause != NIL && query->groupingSets == NIL) {
|
||||
if (check_column_uniqueness(query->groupClause, query->targetList, colnos, opids))
|
||||
if (query->groupClause != NIL && query->groupingSets == NIL) {// 如果查询中存在 GROUP BY 子句且没有 GROUPING SETS
|
||||
if (check_column_uniqueness(query->groupClause, query->targetList, colnos, opids))// 调用 check_column_uniqueness 函数检查列是否唯一
|
||||
return true;
|
||||
} else if (query->groupingSets != NIL) {
|
||||
/*
|
||||
* If we have grouping sets with expressions, we probably don't have
|
||||
* uniqueness and analysis would be hard. Punt.
|
||||
*/
|
||||
if (query->groupClause != NIL)
|
||||
if (query->groupClause != NIL) // 如果存在 GROUPING SETS
|
||||
return false;
|
||||
|
||||
/*
|
||||
|
|
@ -677,13 +701,13 @@ bool query_is_distinct_for(Query* query, List* colnos, List* opids)
|
|||
* then we're returning only one row and are certainly unique. But
|
||||
* otherwise, we know we're certainly not unique.
|
||||
*/
|
||||
bool isTrue = list_length(query->groupingSets) == 1 &&
|
||||
bool isTrue = list_length(query->groupingSets) == 1 &&// 如果 GROUPING SETS 只包含一个空集合
|
||||
((GroupingSet*)linitial(query->groupingSets))->kind == GROUPING_SET_EMPTY;
|
||||
if (isTrue)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else {
|
||||
} else {// 如果查询中存在聚合函数或 HAVING 子句,则支持 DISTINCT
|
||||
/*
|
||||
* If we have no GROUP BY, but do have aggregates or HAVING, then the
|
||||
* result is at most one row so it's surely unique, for any operators.
|
||||
|
|
@ -696,18 +720,19 @@ bool query_is_distinct_for(Query* query, List* colnos, List* opids)
|
|||
* UNION, INTERSECT, EXCEPT guarantee uniqueness of the whole output row,
|
||||
* except with ALL.
|
||||
*/
|
||||
// 如果查询中存在集合操作
|
||||
if (query->setOperations != NULL) {
|
||||
SetOperationStmt* topop = (SetOperationStmt*)query->setOperations;
|
||||
|
||||
Assert(IsA(topop, SetOperationStmt));
|
||||
Assert(topop->op != SETOP_NONE);
|
||||
|
||||
if (!topop->all) {
|
||||
if (!topop->all) {// 如果不是 UNION ALL 操作
|
||||
ListCell* lg = NULL;
|
||||
|
||||
/* We're good if all the nonjunk output columns are in colnos */
|
||||
lg = list_head(topop->groupClauses);
|
||||
foreach (l, query->targetList) {
|
||||
foreach (l, query->targetList) {// 遍历查询的目标列表
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(l);
|
||||
SortGroupClause* sgc = NULL;
|
||||
|
||||
|
|
@ -718,12 +743,13 @@ bool query_is_distinct_for(Query* query, List* colnos, List* opids)
|
|||
Assert(lg != NULL);
|
||||
sgc = (SortGroupClause*)lfirst(lg);
|
||||
lg = lnext(lg);
|
||||
|
||||
// 调用 distinct_col_search 函数查找列的操作符
|
||||
opid = distinct_col_search(tle->resno, colnos, opids);
|
||||
if (!OidIsValid(opid) || !equality_ops_are_compatible(opid, sgc->eqop))
|
||||
break; /* exit early if no match */
|
||||
}
|
||||
if (l == NULL) /* had matches for all? */
|
||||
// 如果成功遍历了所有目标列,说明支持 DISTINCT
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -744,7 +770,7 @@ bool query_is_distinct_for(Query* query, List* colnos, List* opids)
|
|||
* Given group clause and targetlist, find if all the aggregated columns
|
||||
* in colnos, return true if so, else false.
|
||||
*/
|
||||
static bool check_column_uniqueness(List* groupClause, List* targetList, List* colnos, List* opids)
|
||||
static bool check_column_uniqueness(List* groupClause, List* targetList, List* colnos, List* opids)// 检查列是否唯一
|
||||
{
|
||||
ListCell* l = NULL;
|
||||
Oid opid;
|
||||
|
|
@ -754,9 +780,11 @@ static bool check_column_uniqueness(List* groupClause, List* targetList, List* c
|
|||
TargetEntry* tle = get_sortgroupclause_tle(sgc, targetList);
|
||||
|
||||
opid = distinct_col_search(tle->resno, colnos, opids);
|
||||
// 如果操作符无效或不兼容,则中断循环
|
||||
if (!OidIsValid(opid) || !equality_ops_are_compatible(opid, sgc->eqop))
|
||||
break; /* exit early if no match */
|
||||
}
|
||||
// 如果成功遍历了所有列,说明支持 DISTINCT
|
||||
if (l == NULL) /* had matches for all? */
|
||||
return true;
|
||||
|
||||
|
|
@ -770,15 +798,15 @@ static bool check_column_uniqueness(List* groupClause, List* targetList, List* c
|
|||
* else return InvalidOid. (Ordinarily colnos would not contain duplicates,
|
||||
* but if it does, we arbitrarily select the first match.)
|
||||
*/
|
||||
static Oid distinct_col_search(int colno, List* colnos, List* opids)
|
||||
static Oid distinct_col_search(int colno, List* colnos, List* opids)// 在列列表 colnos 中查找列的操作符
|
||||
{
|
||||
ListCell* lc1 = NULL;
|
||||
ListCell* lc2 = NULL;
|
||||
|
||||
forboth(lc1, colnos, lc2, opids)
|
||||
forboth(lc1, colnos, lc2, opids) // 同时遍历列列表和操作符列表
|
||||
{
|
||||
if (colno == lfirst_int(lc1))
|
||||
return lfirst_oid(lc2);
|
||||
}
|
||||
return InvalidOid;
|
||||
return InvalidOid;// 如果未找到,返回无效的操作符标识
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,18 +252,18 @@ FORCE_INLINE bool CanTransferInJoin(JoinType jointype)
|
|||
*
|
||||
* @return: void
|
||||
*/
|
||||
void set_plan_rows(Plan* plan, double globalRows, double multiple)
|
||||
void set_plan_rows(Plan* plan, double globalRows, double multiple)// 设置计划节点的行数估算值
|
||||
{
|
||||
plan->multiple = multiple;
|
||||
plan->multiple = multiple;// 设置计划节点的多倍数
|
||||
|
||||
/*
|
||||
* for global stats, We should reset global rows as localRows*u_sess->pgxc_cxt.NumDataNodes for replication except
|
||||
* RemoteQuery, because the local rows is equal to global rows.
|
||||
*/
|
||||
if (is_replicated_plan(plan) && is_execute_on_datanodes(plan)) {
|
||||
plan->plan_rows = get_global_rows(globalRows, multiple, ng_get_dest_num_data_nodes(plan));
|
||||
if (is_replicated_plan(plan) && is_execute_on_datanodes(plan)) {// 如果计划是复制计划并且在数据节点上执行
|
||||
plan->plan_rows = get_global_rows(globalRows, multiple, ng_get_dest_num_data_nodes(plan));// 调用 get_global_rows 函数获取全局行数估算值
|
||||
} else {
|
||||
plan->plan_rows = globalRows;
|
||||
plan->plan_rows = globalRows;// 否则,使用传入的全局行数估算值
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,10 +280,10 @@ void set_plan_rows(Plan* plan, double globalRows, double multiple)
|
|||
*
|
||||
* @return: void
|
||||
*/
|
||||
void set_plan_rows_from_plan(Plan* plan, double localRows, double multiple)
|
||||
void set_plan_rows_from_plan(Plan* plan, double localRows, double multiple)// 从另一个计划节点获取行数估算值,并设置多倍数
|
||||
{
|
||||
plan->multiple = multiple;
|
||||
plan->plan_rows = get_global_rows(localRows, plan->multiple, ng_get_dest_num_data_nodes(plan));
|
||||
plan->multiple = multiple;// 设置计划节点的多倍数
|
||||
plan->plan_rows = get_global_rows(localRows, plan->multiple, ng_get_dest_num_data_nodes(plan));// 调用 get_global_rows 函数获取全局行数估算值
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -301,23 +301,24 @@ void set_plan_rows_from_plan(Plan* plan, double localRows, double multiple)
|
|||
*
|
||||
* Returns a Plan tree.
|
||||
*/
|
||||
Plan* create_plan(PlannerInfo* root, Path* best_path)
|
||||
Plan* create_plan(PlannerInfo* root, Path* best_path)// 创建查询计划节点
|
||||
{
|
||||
Plan* plan = NULL;
|
||||
|
||||
/* plan_params should not be in use in current query level */
|
||||
Assert(root->plan_params == NIL);
|
||||
Assert(root->plan_params == NIL);// 断言:查询计划参数列表为空
|
||||
|
||||
/* Initialize this module's private workspace in PlannerInfo */
|
||||
// 清空当前外部关系和参数
|
||||
root->curOuterRels = NULL;
|
||||
root->curOuterParams = NIL;
|
||||
u_sess->opt_cxt.is_under_append_plan = false;
|
||||
|
||||
/* Recursively process the path tree */
|
||||
plan = create_plan_recurse(root, best_path);
|
||||
plan = create_plan_recurse(root, best_path);// 递归创建查询计划
|
||||
|
||||
/* Check we successfully assigned all NestLoopParams to plan nodes */
|
||||
if (root->curOuterParams != NIL)
|
||||
if (root->curOuterParams != NIL)// 检查是否成功为所有 NestLoopParams 分配了计划节点
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
||||
|
|
@ -329,6 +330,7 @@ Plan* create_plan(PlannerInfo* root, Path* best_path)
|
|||
root->plan_params = NIL;
|
||||
|
||||
/* Try to find the changed vars in chosed inlist2join path */
|
||||
// 如果在 QRW_INLIST2JOIN_CBO 模式下且存在变量映射,则查找 inlist2join 路径
|
||||
if (u_sess->opt_cxt.qrw_inlist2join_optmode == QRW_INLIST2JOIN_CBO && root->var_mappings != NIL) {
|
||||
find_inlist2join_path(root, best_path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,21 +26,22 @@
|
|||
#include "optimizer/streamplan.h"
|
||||
void InitDynamicSmp()
|
||||
{
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();// 调用 DISTRIBUTED_FEATURE_NOT_SUPPORTED 宏,表示不支持该功能
|
||||
}
|
||||
|
||||
void ChooseStartQueryDop(int hashTableCount)
|
||||
{
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();// 调用 DISTRIBUTED_FEATURE_NOT_SUPPORTED 宏,表示不支持该功能
|
||||
}
|
||||
|
||||
void OptimizePlanDop(PlannedStmt* plannedStmt)
|
||||
{
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED(); // 调用 DISTRIBUTED_FEATURE_NOT_SUPPORTED 宏,表示不支持该功能
|
||||
}
|
||||
|
||||
bool IsDynamicSmpEnabled()
|
||||
{
|
||||
// 使用一系列条件来检查是否启用了动态并行查询执行
|
||||
return IS_STREAM_PLAN && u_sess->opt_cxt.max_query_dop >= 0 && !u_sess->attr.attr_common.IsInplaceUpgrade &&
|
||||
!IsInitdb;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -72,38 +72,39 @@
|
|||
* If Stream is supported, a copy of the 'query' is returned as a backup in case generating a plan
|
||||
* with Stream fails.
|
||||
*/
|
||||
static Query* check_shippable(bool *stream_unsupport, Query* query, shipping_context* context)
|
||||
static Query* check_shippable(bool *stream_unsupport, Query* query, shipping_context* context)//函数用于检查查询是否支持流传输
|
||||
{
|
||||
// 根据配置选择部分或全局推送的模式,设置 stream_unsupport 标志
|
||||
if (u_sess->attr.attr_sql.rewrite_rule & PARTIAL_PUSH) {
|
||||
*stream_unsupport = !context->query_shippable;
|
||||
} else {
|
||||
*stream_unsupport = !context->global_shippable;
|
||||
}
|
||||
|
||||
// 根据是否启用 DN-Gather,设置 is_dngather_support 标志
|
||||
if (u_sess->attr.attr_sql.enable_dngather) {
|
||||
u_sess->opt_cxt.is_dngather_support = !context->disable_dn_gather;
|
||||
} else {
|
||||
u_sess->opt_cxt.is_dngather_support = false;
|
||||
}
|
||||
|
||||
/* single node do not support parallel query in cursor */
|
||||
// 如果查询包含声明游标操作,则强制 stream_unsupport 为 true
|
||||
if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt)) {
|
||||
*stream_unsupport = true;
|
||||
}
|
||||
|
||||
// 如果流传输不支持或不是流查询(非并行查询),则输出不支持流传输的日志,并关闭流传输
|
||||
if (*stream_unsupport || !IS_STREAM) {
|
||||
output_unshipped_log();
|
||||
set_stream_off();
|
||||
} else {
|
||||
/*
|
||||
* make a copy of query, so we can retry to create an unshippable plan
|
||||
* when we fail to generate a stream plan
|
||||
*/
|
||||
// 在流传输可行的情况下,复制查询,以备后续重试创建不可传输的计划
|
||||
return (Query*)copyObject(query);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
PlannedStmt* pgxc_planner(Query* query, int cursorOptions, ParamListInfo boundParams)
|
||||
{
|
||||
PlannedStmt* result = NULL;
|
||||
|
|
@ -288,63 +289,84 @@ PlannedStmt* pgxc_planner(Query* query, int cursorOptions, ParamListInfo boundPa
|
|||
|
||||
void stream_walker_context_init(shipping_context *context)
|
||||
{
|
||||
// 使用 memset_s 初始化 shipping_context 结构体,将所有字段置为0
|
||||
errno_t rc = EOK;
|
||||
|
||||
rc = memset_s(context, sizeof(shipping_context), 0, sizeof(shipping_context));
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
// 根据配置和流查询计划的标志,设置是否支持随机函数的标志
|
||||
context->is_randomfunc_shippable = u_sess->opt_cxt.is_randomfunc_shippable && IS_STREAM_PLAN;
|
||||
// 设置支持 EC 函数的标志为 true
|
||||
context->is_ecfunc_shippable = true;
|
||||
// 初始化查询列表为一个空列表
|
||||
context->query_list = NIL;
|
||||
// 初始化查询计数为0
|
||||
context->query_count = 0;
|
||||
// 初始化当前查询是否支持流传输的标志为 true
|
||||
context->current_shippable = true;
|
||||
// 初始化整体查询是否支持流传输的标志为 true
|
||||
context->query_shippable = true;
|
||||
// 初始化全局查询是否支持流传输的标志为 true
|
||||
context->global_shippable = true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Returns true if at least one temporary table is in use
|
||||
* in query (and its subqueries)
|
||||
*/
|
||||
bool contains_column_tables(List* rtable)
|
||||
{
|
||||
// 此函数暂时不支持分布式特性,因此输出一个不支持分布式的宏
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
|
||||
ListCell* item = NULL;
|
||||
|
||||
// 遍历关系表中的每个条目
|
||||
foreach (item, rtable) {
|
||||
RangeTblEntry* rte = (RangeTblEntry*)lfirst(item);
|
||||
|
||||
// 如果条目的类型是关系表(RTE_RELATION)
|
||||
if (rte->rtekind == RTE_RELATION) {
|
||||
// 检查关系表的存储方向是否为列存储或PAX存储
|
||||
if (REL_COL_ORIENTED == rte->orientation || REL_PAX_ORIENTED == rte->orientation)
|
||||
return true;
|
||||
} else if (rte->rtekind == RTE_SUBQUERY && contains_column_tables(rte->subquery->rtable))
|
||||
return true;
|
||||
return true; // 如果是列存储或PAX存储,返回true
|
||||
}
|
||||
// 如果条目的类型是子查询(RTE_SUBQUERY),则递归调用此函数检查子查询的关系表
|
||||
else if (rte->rtekind == RTE_SUBQUERY && contains_column_tables(rte->subquery->rtable))
|
||||
return true; // 如果子查询中包含列存储或PAX存储的表,返回true
|
||||
}
|
||||
|
||||
// 如果没有找到包含列存储或PAX存储的表的情况,返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
List* AddRemoteQueryNode(List* stmts, const char* queryString, RemoteQueryExecType remoteExecType, bool is_temp)
|
||||
{
|
||||
List* result = stmts;
|
||||
|
||||
/* If node is appplied on EXEC_ON_NONE, simply return the list unchanged */
|
||||
/* 如果远程执行类型是EXEC_ON_NONE,直接返回未更改的列表 */
|
||||
if (remoteExecType == EXEC_ON_NONE)
|
||||
return result;
|
||||
|
||||
/* Only a remote Coordinator is allowed to send a query to backend nodes */
|
||||
/* 只有远程协调节点(IS_PGXC_COORDINATOR)才能向后端节点发送查询 */
|
||||
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
|
||||
// 创建一个RemoteQuery节点用于表示远程查询
|
||||
RemoteQuery* step = makeNode(RemoteQuery);
|
||||
step->combine_type = COMBINE_TYPE_SAME;
|
||||
step->sql_statement = (char*)queryString;
|
||||
step->exec_type = remoteExecType;
|
||||
step->is_temp = is_temp;
|
||||
step->combine_type = COMBINE_TYPE_SAME; // 查询结果的合并方式
|
||||
step->sql_statement = (char*)queryString; // 查询字符串
|
||||
step->exec_type = remoteExecType; // 远程执行的类型
|
||||
step->is_temp = is_temp; // 是否是临时表
|
||||
|
||||
// 将RemoteQuery节点添加到查询计划列表中
|
||||
result = lappend(result, step);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool pgxc_query_contains_temp_tables(List* queries)
|
||||
{
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
|
|
@ -364,34 +386,44 @@ void pgxc_rqplan_adjust_tlist(PlannerInfo* root, RemoteQuery* rqplan, bool gensq
|
|||
|
||||
bool containing_ordinary_table(Node* node)
|
||||
{
|
||||
// 如果节点为空或者当前节点是PGXC数据节点,直接返回false
|
||||
if (node == NULL || IS_PGXC_DATANODE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果当前节点是RangeTblEntry类型
|
||||
if (IsA(node, RangeTblEntry)) {
|
||||
RangeTblEntry* rte = (RangeTblEntry*)node;
|
||||
|
||||
// 如果是普通表且不是系统表,返回true
|
||||
if (rte->relkind == RELKIND_RELATION && !is_sys_table(rte->relid)) {
|
||||
return true;
|
||||
} else if (rte->rtekind == RTE_SUBQUERY) {
|
||||
}
|
||||
// 如果是子查询(RTE_SUBQUERY),递归检查子查询的节点
|
||||
else if (rte->rtekind == RTE_SUBQUERY) {
|
||||
Query* subquery = rte->subquery;
|
||||
|
||||
// 如果子查询中包含普通表,返回true
|
||||
if (containing_ordinary_table((Node*)subquery)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 其他情况返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果当前节点是Query类型,使用query_tree_walker遍历查询计划树
|
||||
if (IsA(node, Query)) {
|
||||
bool result = false;
|
||||
result = query_tree_walker((Query*)node, (bool (*)())containing_ordinary_table, NULL, QTW_EXAMINE_RTES);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 对于其他类型的节点,使用expression_tree_walker进行遍历
|
||||
return expression_tree_walker(node, (bool (*)())containing_ordinary_table, NULL);
|
||||
}
|
||||
|
||||
|
||||
Plan* pgxc_make_modifytable(PlannerInfo* root, Plan* topplan)
|
||||
{
|
||||
ModifyTable* mt = (ModifyTable*)topplan;
|
||||
|
|
@ -445,20 +477,29 @@ bool contains_temp_tables(List* rtable)
|
|||
ListCell* item = NULL;
|
||||
char rel_persistence;
|
||||
|
||||
// 遍历查询的表列表(RangeTblEntry)
|
||||
foreach (item, rtable) {
|
||||
RangeTblEntry* rte = (RangeTblEntry*)lfirst(item);
|
||||
|
||||
// 如果当前表是普通关系(RTE_RELATION)
|
||||
if (rte->rtekind == RTE_RELATION) {
|
||||
// 获取关系的持久性信息
|
||||
rel_persistence = get_rel_persistence(rte->relid);
|
||||
|
||||
// 如果关系的持久性为临时表(RELPERSISTENCE_TEMP)或全局临时表(RELPERSISTENCE_GLOBAL_TEMP),返回true
|
||||
if (rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
|
||||
return true;
|
||||
} else if (rte->rtekind == RTE_SUBQUERY && contains_temp_tables(rte->subquery->rtable))
|
||||
}
|
||||
// 如果当前表是子查询(RTE_SUBQUERY),递归检查子查询的表列表
|
||||
else if (rte->rtekind == RTE_SUBQUERY && contains_temp_tables(rte->subquery->rtable))
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果遍历完所有表都没有找到临时表,返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Param* pgxc_make_param(int param_num, Oid param_type)
|
||||
{
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
|
|
|
|||
|
|
@ -73,54 +73,50 @@ static Oid fetch_agg_sort_op(Oid aggfnoid);
|
|||
* Note: we are passed the preprocessed targetlist separately, because it's
|
||||
* not necessarily equal to root->parse->targetList.
|
||||
*/
|
||||
/*
|
||||
* preprocess_minmax_aggregates: 预处理查询中的MIN/MAX聚合函数。
|
||||
此函数负责识别和准备查询中的MIN/MAX聚合函数,以便稍后进行优化。
|
||||
它检查各种条件,如查询是否涉及联接、GROUP BY 或窗口函数,并确保查询仅引用一个表。
|
||||
如果满足所有条件且聚合函数适合进行优化,它会<EFBFBD><EFBFBD><EFBFBD>建访问路径并检查其是否可索引。如果所有聚合函数都可索引,
|
||||
它会保存信息以供稍后进行优化
|
||||
* 输入参数:
|
||||
* - root:包含有关查询的信息的PlannerInfo结构。
|
||||
* - tlist:查询的目标列表。
|
||||
*/
|
||||
void preprocess_minmax_aggregates(PlannerInfo* root, List* tlist)
|
||||
{
|
||||
Query* parse = root->parse;
|
||||
FromExpr* jtnode = NULL;
|
||||
RangeTblRef* rtr = NULL;
|
||||
RangeTblEntry* rte = NULL;
|
||||
List* aggs_list = NIL;
|
||||
ListCell* lc = NULL;
|
||||
Query* parse = root->parse; // 指向正在计划的查询的指针。
|
||||
FromExpr* jtnode = NULL; // 指向查询的FROM子句。
|
||||
RangeTblRef* rtr = NULL; // 引用范围表条目的引用。
|
||||
RangeTblEntry* rte = NULL; // 范围表条目本身。
|
||||
List* aggs_list = NIL; // 查询中的MIN/MAX聚合函数列表。
|
||||
ListCell* lc = NULL; // 列表单元迭代器。
|
||||
|
||||
/* minmax_aggs list should be empty at this point */
|
||||
/* 验证minmax_aggs列表最初为空。 */
|
||||
AssertEreport(
|
||||
root->minmax_aggs == NIL, MOD_OPT, "The minmax_aggs is not empty when preprocessing MIN/MAX aggregates.");
|
||||
root->minmax_aggs == NIL, MOD_OPT, "在预处理MIN/MAX聚合函数时,minmax_aggs不为空。");
|
||||
|
||||
/* Nothing to do if query has no aggregates */
|
||||
/* 如果查询没有聚合函数,就没有什么可做的。 */
|
||||
if (!parse->hasAggs)
|
||||
return;
|
||||
|
||||
/* 检查查询是否涉及集合操作或行级锁定。 */
|
||||
AssertEreport(!parse->setOperations,
|
||||
MOD_OPT,
|
||||
"setOp is not allowed when preprocessing MIN/MAX aggregates."); /* shouldn't get here if a setop */
|
||||
"在预处理MIN/MAX聚合函数时不允许setOp。"); /* 如果有set操作,不应该到达这里 */
|
||||
AssertEreport(parse->rowMarks == NIL,
|
||||
MOD_OPT,
|
||||
"RowMarkClause is not allowd when preprocessing MIN/MAX aggregates."); /* nor if FOR UPDATE */
|
||||
"在预处理MIN/MAX聚合函数时不允许RowMarkClause。"); /* 也不允许使用FOR UPDATE */
|
||||
|
||||
/*
|
||||
* Reject unoptimizable cases.
|
||||
*
|
||||
* We don't handle GROUP BY or windowing, because our current
|
||||
* implementations of grouping require looking at all the rows anyway, and
|
||||
* so there's not much point in optimizing MIN/MAX. (Note: relaxing this
|
||||
* would likely require some restructuring in grouping_planner(), since it
|
||||
* performs assorted processing related to these features between calling
|
||||
* preprocess_minmax_aggregates and optimize_minmax_aggregates.)
|
||||
*
|
||||
* For example group by grouping sets(()); parse->groupClause is null and
|
||||
* the length of parse->groupingSets is 1. In this case, min/max may optimize.
|
||||
* 拒绝无法优化的情况。
|
||||
* 我们不处理GROUP BY或窗口函数,因为它们需要处理所有行并使MIN/MAX优化效果不佳。
|
||||
*/
|
||||
if (parse->groupClause || parse->hasWindowFuncs || list_length(parse->groupingSets) > 1)
|
||||
return;
|
||||
|
||||
/*
|
||||
* We also restrict the query to reference exactly one table, since join
|
||||
* conditions can't be handled reasonably. (We could perhaps handle a
|
||||
* query containing cartesian-product joins, but it hardly seems worth the
|
||||
* trouble.) However, the single table could be buried in several levels
|
||||
* of FromExpr due to subqueries. Note the "single" table could be an
|
||||
* inheritance parent, too, including the case of a UNION ALL subquery
|
||||
* that's been flattened to an appendrel.
|
||||
* 确保查询引用了正好一个表。我们无法合理处理联接条件。
|
||||
*/
|
||||
jtnode = parse->jointree;
|
||||
while (IsA(jtnode, FromExpr)) {
|
||||
|
|
@ -132,16 +128,17 @@ void preprocess_minmax_aggregates(PlannerInfo* root, List* tlist)
|
|||
return;
|
||||
rtr = (RangeTblRef*)jtnode;
|
||||
rte = planner_rt_fetch(rtr->rtindex, root);
|
||||
|
||||
// 检查表是普通关系或UNION ALL子查询。
|
||||
if (rte->rtekind == RTE_RELATION)
|
||||
/* ordinary relation, ok */;
|
||||
/* 普通关系,可以优化 */;
|
||||
else if (rte->rtekind == RTE_SUBQUERY && rte->inh)
|
||||
/* flattened UNION ALL subquery, ok */;
|
||||
/* 展平的UNION ALL子查询,可以优化 */;
|
||||
else
|
||||
return;
|
||||
|
||||
/*
|
||||
* Scan the tlist and HAVING qual to find all the aggregates and verify
|
||||
* all are MIN/MAX aggregates. Stop as soon as we find one that isn't.
|
||||
* 扫描目标列表(tlist)和HAVING条件,查找所有聚合函数并验证它们是否都是MIN/MAX聚合函数。一旦发现不是MIN/MAX聚合函数的情况,就停止。
|
||||
*/
|
||||
aggs_list = NIL;
|
||||
if (find_minmax_aggs_walker((Node*)tlist, &aggs_list))
|
||||
|
|
@ -150,12 +147,9 @@ void preprocess_minmax_aggregates(PlannerInfo* root, List* tlist)
|
|||
return;
|
||||
|
||||
/*
|
||||
* OK, there is at least the possibility of performing the optimization.
|
||||
* Build an access path for each aggregate. (We must do this now because
|
||||
* we need to call query_planner with a pristine copy of the current query
|
||||
* tree; it'll be too late when optimize_minmax_aggregates gets called.)
|
||||
* If any of the aggregates prove to be non-indexable, give up; there is
|
||||
* no point in optimizing just some of them.
|
||||
* 现在,有可能执行优化。
|
||||
* 为每个聚合函数构建一个访问路径,并检查它们是否可索引。
|
||||
* 如果有任何一个聚合函数无法索引,就放弃。
|
||||
*/
|
||||
foreach (lc, aggs_list) {
|
||||
MinMaxAggInfo* mminfo = (MinMaxAggInfo*)lfirst(lc);
|
||||
|
|
@ -163,37 +157,31 @@ void preprocess_minmax_aggregates(PlannerInfo* root, List* tlist)
|
|||
bool reverse = false;
|
||||
|
||||
/*
|
||||
* We'll need the equality operator that goes with the aggregate's
|
||||
* ordering operator.
|
||||
* 查找与聚合函数的排序运算符对应的相等运算符。
|
||||
*/
|
||||
eqop = get_equality_op_for_ordering_op(mminfo->aggsortop, &reverse);
|
||||
if (!OidIsValid(eqop)) /* shouldn't happen */
|
||||
if (!OidIsValid(eqop)) /* 不应该发生这种情况。 */
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_INVALID_OPERATION),
|
||||
(errmsg("could not find equality operator for ordering operator %u", mminfo->aggsortop))));
|
||||
(errmsg("无法找到排序运算符 %u 的相等运算符", mminfo->aggsortop))));
|
||||
|
||||
/*
|
||||
* We can use either an ordering that gives NULLS FIRST or one that
|
||||
* gives NULLS LAST; furthermore there's unlikely to be much
|
||||
* performance difference between them, so it doesn't seem worth
|
||||
* costing out both ways if we get a hit on the first one. NULLS
|
||||
* FIRST is more likely to be available if the operator is a
|
||||
* reverse-sort operator, so try that first if reverse.
|
||||
* 我们可以使用给出NULLS FIRST或NULLS LAST的排序方式;尝试两种方式,如果找到可索引的路径,则继续。
|
||||
*/
|
||||
if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, reverse))
|
||||
continue;
|
||||
if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, !reverse))
|
||||
continue;
|
||||
|
||||
/* No indexable path for this aggregate, so fail */
|
||||
/* 无法为此聚合函数构建可索引路径,因此失败。 */
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* We're done until path generation is complete. Save info for later.
|
||||
* (Setting root->minmax_aggs non-NIL signals we succeeded in making index
|
||||
* access paths for all the aggregates.)
|
||||
* 在路径生成完成之前,我们已经完成了。保存信息以供以后使用。
|
||||
* 设置root->minmax_aggs为非空表示我们成功地为所有聚合函数创建了索引访问路径。
|
||||
*/
|
||||
root->minmax_aggs = aggs_list;
|
||||
}
|
||||
|
|
@ -214,18 +202,32 @@ void preprocess_minmax_aggregates(PlannerInfo* root, List* tlist)
|
|||
* doing the aggregates the regular way, and the best path devised for
|
||||
* computing the input of a standard Agg node.
|
||||
*/
|
||||
/*
|
||||
* optimize_minmax_aggregates: 优化MIN/MAX聚合函数的执行计划。
|
||||
*
|
||||
* 此函数负责生成优化的执行计划,以处理查询中的MIN/MAX聚合函数。它与通用的聚合函数执行计划进行成本比较,如果优化的计划成本较低,则生成并返回优化的执行计划。
|
||||
*
|
||||
* 输入参数:
|
||||
* - root:PlannerInfo结构,包含查询的信息。
|
||||
* - tlist:查询的目标列表。
|
||||
* - aggcosts:聚合函数的成本信息。
|
||||
* - best_path:查询的最佳执行计划。
|
||||
*
|
||||
* 返回值:
|
||||
* 如果无法生成优化的执行计划,返回NULL;否则返回优化的执行计划。
|
||||
*/
|
||||
Plan* optimize_minmax_aggregates(PlannerInfo* root, List* tlist, const AggClauseCosts* aggcosts, Path* best_path)
|
||||
{
|
||||
Query* parse = root->parse;
|
||||
Cost total_cost;
|
||||
Path agg_path;
|
||||
Plan* plan = NULL;
|
||||
Plan* sub_plan = NULL;
|
||||
Node* hqual = NULL;
|
||||
Cost total_cost; // 总成本
|
||||
Path agg_path; // 聚合函数的路径
|
||||
Plan* plan = NULL; // 最终执行计划
|
||||
Plan* sub_plan = NULL; // 子查询的执行计划
|
||||
Node* hqual = NULL; // HAVING条件
|
||||
ListCell* lc = NULL;
|
||||
errno_t rc;
|
||||
|
||||
/* Nothing to do if preprocess_minmax_aggs rejected the query */
|
||||
/* 如果preprocess_minmax_aggs拒绝了查询,则无需执行任何操作 */
|
||||
if (root->minmax_aggs == NIL)
|
||||
return NULL;
|
||||
|
||||
|
|
@ -233,12 +235,9 @@ Plan* optimize_minmax_aggregates(PlannerInfo* root, List* tlist, const AggClause
|
|||
securec_check(rc, "\0", "\0");
|
||||
|
||||
/*
|
||||
* Now we have enough info to compare costs against the generic aggregate
|
||||
* implementation.
|
||||
* 现在,我们有足够的信息来比较成本与通用聚合函数实现。
|
||||
*
|
||||
* Note that we don't include evaluation cost of the tlist here; this is
|
||||
* OK since it isn't included in best_path's cost either, and should be
|
||||
* the same in either case.
|
||||
* 请注意,我们在这里不包括目标列表(tlist)的评估成本;这是可以接受的,因为在best_path的成本中也没有包括它,而且无论哪种情况下,它应该是一样的。
|
||||
*/
|
||||
total_cost = 0;
|
||||
foreach (lc, root->minmax_aggs) {
|
||||
|
|
@ -260,12 +259,12 @@ Plan* optimize_minmax_aggregates(PlannerInfo* root, List* tlist, const AggClause
|
|||
RELOPTINFO_LOCAL_FIELD(root, best_path->parent, rows));
|
||||
|
||||
if (total_cost > agg_path.total_cost)
|
||||
return NULL; /* too expensive */
|
||||
return NULL; /* 成本太高,无法优化 */
|
||||
|
||||
/*
|
||||
* OK, we are going to generate an optimized plan.
|
||||
* 好的,我们要生成一个优化的执行计划。
|
||||
*
|
||||
* First, generate a subplan and output Param node for each agg.
|
||||
* 首先,为每个聚合函数生成子查询和输出Param节点。
|
||||
*/
|
||||
foreach (lc, root->minmax_aggs) {
|
||||
MinMaxAggInfo* mminfo = (MinMaxAggInfo*)lfirst(lc);
|
||||
|
|
@ -273,26 +272,21 @@ Plan* optimize_minmax_aggregates(PlannerInfo* root, List* tlist, const AggClause
|
|||
}
|
||||
|
||||
/*
|
||||
* Modify the targetlist and HAVING qual to reference subquery outputs
|
||||
* 修改目标列表和HAVING条件<EFBFBD><EFBFBD><EFBFBD>以引用子查询的输出。
|
||||
*/
|
||||
tlist = (List*)replace_aggs_with_params_mutator((Node*)tlist, root);
|
||||
hqual = replace_aggs_with_params_mutator(parse->havingQual, root);
|
||||
|
||||
/*
|
||||
* We have to replace Aggrefs with Params in equivalence classes too, else
|
||||
* ORDER BY or DISTINCT on an optimized aggregate will fail. We don't
|
||||
* need to process child eclass members though, since they aren't of
|
||||
* interest anymore --- and replace_aggs_with_params_mutator isn't able
|
||||
* to handle Aggrefs containing translated child Vars, anyway.
|
||||
* 我们必须在等价类中用Param替换Aggrefs,否则优化的聚合函数上的ORDER BY或DISTINCT将失败。
|
||||
* 我们不需要处理子等价类成员,因为它们不再感兴趣,而且replace_aggs_with_params_mutator也不能处理包含已翻译子Vars的Aggrefs。
|
||||
*
|
||||
* Note: at some point it might become necessary to mutate other data
|
||||
* structures too, such as the query's sortClause or distinctClause. Right
|
||||
* now, those won't be examined after this point.
|
||||
* 注意:在某些时候,可能需要变异其他数据结构,如查询的sortClause或distinctClause。现在,这些数据在此之后不会再被检查。
|
||||
*/
|
||||
mutate_eclass_expressions(root, (Node* (*)()) replace_aggs_with_params_mutator, (void*)root, false);
|
||||
|
||||
/*
|
||||
* Generate the output plan --- basically just a Result
|
||||
* 生成输出计划 - 基本上只是一个Result节点
|
||||
*/
|
||||
plan = (Plan*)make_result(root, tlist, hqual, NULL);
|
||||
|
||||
|
|
@ -302,12 +296,13 @@ Plan* optimize_minmax_aggregates(PlannerInfo* root, List* tlist, const AggClause
|
|||
plan->exec_nodes = ng_get_default_computing_group_exec_node();
|
||||
}
|
||||
|
||||
/* Account for evaluation cost of the tlist (make_result did the rest) */
|
||||
/* 账户tlist的评估成本(make_result已经做了其余的工作) */
|
||||
add_tlist_costs_to_plan(root, plan, tlist);
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* find_minmax_aggs_walker
|
||||
* Recursively scan the Aggref nodes in an expression tree, and check
|
||||
|
|
@ -325,6 +320,23 @@ Plan* optimize_minmax_aggregates(PlannerInfo* root, List* tlist, const AggClause
|
|||
* reduction of sublinks to subplans. There mustn't be outer-aggregate
|
||||
* references either.
|
||||
*/
|
||||
/*
|
||||
* find_minmax_aggs_walker: 递归查找表达式树中的MIN/MAX聚合函数
|
||||
*
|
||||
* 此函数用于递归地遍历表达式树,查找其中的MIN/MAX聚合函数。如果找到MIN/MAX聚合函数,
|
||||
* 将其相关信息添加到上下文列表中以供后续处理。
|
||||
* 这个函数用于递归遍历查询表达式树,查找其中的MIN/MAX聚合函数。
|
||||
* 如果在表达式树中找到MIN/MAX聚合函数,它会提取相关信息并将其添加到上下文列表中以供后续处理。
|
||||
* 该函数通过检查每个节点的类型来判断是否是MIN/MAX聚合函数,并进行相应的处理。
|
||||
* 如果找到了MIN/MAX聚合函数,它会返回true,否则返回false
|
||||
*
|
||||
* 输入参数:
|
||||
* - node: 当前节点
|
||||
* - context: 保存MIN/MAX聚合函数信息的上下文列表
|
||||
*
|
||||
* 返回值:
|
||||
* 如果在表达式树中找到MIN/MAX聚合函数,则返回true;否则返回false。
|
||||
*/
|
||||
static bool find_minmax_aggs_walker(Node* node, List** context)
|
||||
{
|
||||
if (node == NULL)
|
||||
|
|
@ -341,22 +353,22 @@ static bool find_minmax_aggs_walker(Node* node, List** context)
|
|||
"The agg does not belong to current query"
|
||||
"when scaning the Aggref nodes in an expression tree recursively to find a MIN/MAX aggregate.");
|
||||
if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
|
||||
return true; /* it couldn't be MIN/MAX */
|
||||
/* note: we do not care if DISTINCT is mentioned ... */
|
||||
return true; /* 它不可能是MIN/MAX聚合函数 */
|
||||
/* 注意:我们不关心是否提到了 DISTINCT ... */
|
||||
curTarget = (TargetEntry*)linitial(aggref->args);
|
||||
|
||||
aggsortop = fetch_agg_sort_op(aggref->aggfnoid);
|
||||
if (!OidIsValid(aggsortop))
|
||||
return true; /* not a MIN/MAX aggregate */
|
||||
return true; /* 不是MIN/MAX聚合函数 */
|
||||
|
||||
if (contain_mutable_functions((Node*)curTarget->expr))
|
||||
return true; /* not potentially indexable */
|
||||
return true; /* 不是潜在可索引的 */
|
||||
|
||||
if (type_is_rowtype(exprType((Node*)curTarget->expr)))
|
||||
return true; /* IS NOT NULL would have weird semantics */
|
||||
return true; /* IS NOT NULL会有奇怪的语义 */
|
||||
|
||||
/*
|
||||
* Check whether it's already in the list, and add it if not.
|
||||
* 检查它是否已在列表中,如果不在,则添加它。
|
||||
*/
|
||||
foreach (l, *context) {
|
||||
mminfo = (MinMaxAggInfo*)lfirst(l);
|
||||
|
|
@ -369,7 +381,7 @@ static bool find_minmax_aggs_walker(Node* node, List** context)
|
|||
mminfo->aggsortop = aggsortop;
|
||||
mminfo->aggref = aggref;
|
||||
mminfo->target = curTarget->expr;
|
||||
mminfo->subroot = NULL; /* don't compute path yet */
|
||||
mminfo->subroot = NULL; /* 暂不计算路径 */
|
||||
mminfo->path = NULL;
|
||||
mminfo->pathcost = 0;
|
||||
mminfo->param = NULL;
|
||||
|
|
@ -377,8 +389,7 @@ static bool find_minmax_aggs_walker(Node* node, List** context)
|
|||
*context = lappend(*context, mminfo);
|
||||
|
||||
/*
|
||||
* We need not recurse into the argument, since it can't contain any
|
||||
* aggregates.
|
||||
* 由于参数中不可能包含任何聚合函数,因此我们不需要递归到参数中。
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
|
@ -388,33 +399,52 @@ static bool find_minmax_aggs_walker(Node* node, List** context)
|
|||
return expression_tree_walker(node, (bool (*)())find_minmax_aggs_walker, (void*)context);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* HasNOTNULLConstraint: 检查是否存在NOT NULL约束
|
||||
*
|
||||
* 此函数用于检查是否存在表达式中的变量(Var)对应的列具有NOT NULL约束。
|
||||
* 如果变量对应的列具有NOT NULL约束,则返回true,否则返回false。
|
||||
*
|
||||
* 输入参数:
|
||||
* - parse: 包含查询信息的Query结构
|
||||
* - ntest: NullTest节点,表示NULL检查操作
|
||||
*
|
||||
* 返回值:
|
||||
* 如果变量对应的列具有NOT NULL约束,返回true;否则返回false。
|
||||
*/
|
||||
static bool HasNOTNULLConstraint(Query* parse, NullTest* ntest)
|
||||
{
|
||||
if (IsA(ntest->arg, Var)) {
|
||||
/* Check whether NOT NULL check can be guaranteed by table defination */
|
||||
/* 检查是否可以通过表定义保证NOT NULL检查 */
|
||||
RangeTblEntry* rte = rt_fetch(((Var*)ntest->arg)->varno, parse->rtable);
|
||||
Oid reloid = rte->relid;
|
||||
AttrNumber attno = ((Var*)ntest->arg)->varoattno;
|
||||
|
||||
if (reloid != InvalidOid && attno != InvalidAttrNumber) {
|
||||
HeapTuple atttuple =
|
||||
SearchSysCacheCopy2(ATTNUM, ObjectIdGetDatum(reloid), Int16GetDatum(attno));
|
||||
if (!HeapTupleIsValid(atttuple)) {
|
||||
/* 查找缓存失败时报错 */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
|
||||
errmsg("cache lookup failed for attribute %u of relation %hd",
|
||||
attno, reloid)));
|
||||
errmsg("为关系 %hd 的属性 %u 查找缓存失败",
|
||||
reloid, attno)));
|
||||
}
|
||||
Form_pg_attribute attStruct = (Form_pg_attribute)GETSTRUCT(atttuple);
|
||||
if (attStruct->attnotnull) {
|
||||
/* 如果属性具有NOT NULL约束,则返回true */
|
||||
heap_freetuple_ext(atttuple);
|
||||
return true;
|
||||
}
|
||||
heap_freetuple_ext(atttuple);
|
||||
}
|
||||
}
|
||||
/* 变量对应的列没有NOT NULL约束 */
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* build_minmax_path
|
||||
* Given a MIN/MAX aggregate, try to build an indexscan Path it can be
|
||||
|
|
@ -423,6 +453,22 @@ static bool HasNOTNULLConstraint(Query* parse, NullTest* ntest)
|
|||
* If successful, stash the best path in *mminfo and return TRUE.
|
||||
* Otherwise, return FALSE.
|
||||
*/
|
||||
/*
|
||||
* build_minmax_path: 为MIN/MAX聚合生成路径
|
||||
*
|
||||
* 此函数生成适用于MIN/MAX聚合的查询路径,以便在查询优化中使用。
|
||||
* 生成的查询路径包括对聚合目标列的排序和NULL值检查。
|
||||
*
|
||||
* 输入参数:
|
||||
* - root: 查询规划器的上下文信息
|
||||
* - mminfo: MIN/MAX聚合的相关信息
|
||||
* - eqop: 相等性运算符的OID
|
||||
* - sortop: 排序运算符的OID
|
||||
* - nulls_first: 是否将NULL值排在前面
|
||||
*
|
||||
* 返回值:
|
||||
* 如果成功生成路径,则返回true;否则返回false。
|
||||
*/
|
||||
static bool build_minmax_path(PlannerInfo* root, MinMaxAggInfo* mminfo, Oid eqop, Oid sortop, bool nulls_first)
|
||||
{
|
||||
PlannerInfo* subroot = NULL;
|
||||
|
|
@ -440,34 +486,41 @@ static bool build_minmax_path(PlannerInfo* root, MinMaxAggInfo* mminfo, Oid eqop
|
|||
standard_qp_extra qp_extra;
|
||||
|
||||
/* ----------
|
||||
* Generate modified query of the form
|
||||
* (SELECT col FROM tab
|
||||
* WHERE col IS NOT NULL AND existing-quals
|
||||
* ORDER BY col ASC/DESC
|
||||
* LIMIT 1)
|
||||
* 生成修改后的查询,形式如下:
|
||||
* (SELECT col FROM tab
|
||||
* WHERE col IS NOT NULL AND existing-quals
|
||||
* ORDER BY col ASC/DESC
|
||||
* LIMIT 1)
|
||||
* ----------
|
||||
*/
|
||||
|
||||
/* 复制根查询规划器上下文 */
|
||||
subroot = (PlannerInfo*)palloc(sizeof(PlannerInfo));
|
||||
errorno = memcpy_s(subroot, sizeof(PlannerInfo), root, sizeof(PlannerInfo));
|
||||
securec_check_c(errorno, "\0", "\0");
|
||||
|
||||
/* 复制根查询的查询结构 */
|
||||
subroot->parse = parse = (Query*)copyObject(root->parse);
|
||||
/* make sure subroot planning won't change root->init_plans contents */
|
||||
|
||||
/* 确保子查询规划不会更改根查询的 init_plans 内容 */
|
||||
subroot->init_plans = list_copy(root->init_plans);
|
||||
/* There shouldn't be any OJ info to translate, as yet */
|
||||
|
||||
/* 没有联接信息需要转换 */
|
||||
AssertEreport(subroot->join_info_list == NIL,
|
||||
MOD_OPT,
|
||||
"join info list is not null when building an index path at a given MIN/MAX aggregate.");
|
||||
Assert(subroot->lateral_info_list == NIL);
|
||||
/* and we haven't created PlaceHolderInfos, either */
|
||||
|
||||
/* 也没有创建占位符信息 */
|
||||
AssertEreport(subroot->placeholder_list == NIL,
|
||||
MOD_OPT,
|
||||
"place holder list is not null when building an index path at a given MIN/MAX aggregate.");
|
||||
|
||||
/* single tlist entry that is the aggregate target */
|
||||
/* 创建一个单一的目标条目,对应于聚合目标 */
|
||||
tle = makeTargetEntry((Expr*)copyObject(mminfo->target), (AttrNumber)1, pstrdup("agg_target"), false);
|
||||
parse->targetList = list_make1(tle);
|
||||
|
||||
/* No HAVING, no DISTINCT, no aggregates, no grouping sets anymore */
|
||||
/* 没有HAVING,没有DISTINCT,没有聚合函数,没有分组集合 */
|
||||
parse->havingQual = NULL;
|
||||
subroot->hasHavingQual = false;
|
||||
parse->distinctClause = NIL;
|
||||
|
|
@ -475,70 +528,60 @@ static bool build_minmax_path(PlannerInfo* root, MinMaxAggInfo* mminfo, Oid eqop
|
|||
parse->hasAggs = false;
|
||||
parse->groupingSets = NULL;
|
||||
|
||||
/* Build "target IS NOT NULL" expression */
|
||||
/* 构建 "target IS NOT NULL" 表达式 */
|
||||
ntest = makeNode(NullTest);
|
||||
ntest->nulltesttype = IS_NOT_NULL;
|
||||
ntest->arg = (Expr*)copyObject(mminfo->target);
|
||||
/* we checked it wasn't a rowtype in find_minmax_aggs_walker */
|
||||
/* 我们已经在 find_minmax_aggs_walker 中检查过它不是复合类型 */
|
||||
ntest->argisrow = false;
|
||||
|
||||
/* User might have had that in WHERE already */
|
||||
/* 如果WHERE子句中没有 "target IS NOT NULL" 条件,则添加它 */
|
||||
if (!list_member((List*)parse->jointree->quals, ntest) && !HasNOTNULLConstraint(parse, ntest))
|
||||
parse->jointree->quals = (Node*)lcons(ntest, (List*)parse->jointree->quals);
|
||||
|
||||
/* Build suitable ORDER BY clause */
|
||||
/* 创建适当的ORDER BY 子句 */
|
||||
sortcl = makeNode(SortGroupClause);
|
||||
sortcl->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList);
|
||||
sortcl->eqop = eqop;
|
||||
sortcl->sortop = sortop;
|
||||
sortcl->nulls_first = nulls_first;
|
||||
sortcl->hashable = false; /* no need to make this accurate */
|
||||
sortcl->hashable = false; /* 不需要精确计算哈希值 */
|
||||
parse->sortClause = list_make1(sortcl);
|
||||
|
||||
/* set up expressions for LIMIT 1 */
|
||||
/* 设置 LIMIT 1 的表达式 */
|
||||
parse->limitOffset = NULL;
|
||||
parse->limitCount =
|
||||
(Node*)makeConst(INT8OID, -1, InvalidOid, sizeof(int64), Int64GetDatum(1), false, FLOAT8PASSBYVAL);
|
||||
|
||||
/* Initialize the pathkeys */
|
||||
/* 初始化路径键列表 */
|
||||
standard_qp_init(subroot, &qp_extra, parse->targetList, NULL, NULL);
|
||||
|
||||
/*
|
||||
* Generate the best paths for this query, telling query_planner that we
|
||||
* have LIMIT 1.
|
||||
* 为此查询生成最佳路径,告知 query_planner 我们有 LIMIT 1。
|
||||
*/
|
||||
|
||||
/* Make tuple_fraction, limit_tuples accessible to lower-level routines */
|
||||
/* 让 tuple_fraction 和 limit_tuples 可访问到更低级别的子例程 */
|
||||
subroot->tuple_fraction = 1.0;
|
||||
subroot->limit_tuples = 1.0;
|
||||
|
||||
/*
|
||||
* Generate pathlist by query_planner for final_rel and canonicalize
|
||||
* all the pathkeys.
|
||||
*/
|
||||
/* 生成 final_rel 的路径列表,并对路径键进行规范化 */
|
||||
final_rel = query_planner(subroot, parse->targetList,
|
||||
standard_qp_callback, &qp_extra);
|
||||
|
||||
/*
|
||||
* In the following, generate the best unsorted and presorted paths for
|
||||
* this Query (but note there may not be any presorted path).
|
||||
*/
|
||||
/* 生成此查询的最佳未排序路径和排序路径(注意可能没有排序路径) */
|
||||
bool has_groupby = true;
|
||||
|
||||
/* First of all, estimate the number of groups in the query. */
|
||||
/* 首先估算查询中的组数 */
|
||||
has_groupby = get_number_of_groups(subroot,
|
||||
final_rel,
|
||||
dNumGroups);
|
||||
|
||||
/* Then update the tuple_fraction by the number of groups in the query. */
|
||||
/* 然后根据查询中的组数更新 tuple_fraction */
|
||||
update_tuple_fraction(subroot,
|
||||
final_rel,
|
||||
dNumGroups);
|
||||
|
||||
/*
|
||||
* Finally, generate the best unsorted and presorted paths for
|
||||
* this Query.
|
||||
*/
|
||||
/* 最后生成此查询的最佳未排序和排序路径 */
|
||||
generate_cheapest_and_sorted_path(subroot,
|
||||
final_rel,
|
||||
&cheapest_path,
|
||||
|
|
@ -546,12 +589,10 @@ static bool build_minmax_path(PlannerInfo* root, MinMaxAggInfo* mminfo, Oid eqop
|
|||
dNumGroups,
|
||||
has_groupby);
|
||||
|
||||
|
||||
/*
|
||||
* Fail if no presorted path. However, if query_planner determines that
|
||||
* the presorted path is also the cheapest, it will set sorted_path to
|
||||
* NULL ... don't be fooled. (This is kind of a pain here, but it
|
||||
* simplifies life for grouping_planner, so leave it be.)
|
||||
* 如果没有排序路径,则返回失败。但是,如果 query_planner 确定排序路径也是最便宜的,
|
||||
* 它将将 sorted_path 设置为 NULL ... 不要被愚弄。(这在这里有点麻烦,
|
||||
* 但它简化了 grouping_planner 的工作,所以保留它。)
|
||||
*/
|
||||
if (sorted_path == NULL) {
|
||||
if (cheapest_path && pathkeys_contained_in(subroot->sort_pathkeys, cheapest_path->pathkeys))
|
||||
|
|
@ -561,10 +602,9 @@ static bool build_minmax_path(PlannerInfo* root, MinMaxAggInfo* mminfo, Oid eqop
|
|||
}
|
||||
|
||||
/*
|
||||
* Determine cost to get just the first row of the presorted path.
|
||||
* 确定获取排序路径的第一行的成本。
|
||||
*
|
||||
* Note: cost calculation here should
|
||||
* match compare_fractional_path_costs().
|
||||
* 注意:此处的成本计算应与 compare_fractional_path_costs() 相匹配。
|
||||
*/
|
||||
if (RELOPTINFO_LOCAL_FIELD(subroot, sorted_path->parent, rows) > 1.0)
|
||||
path_fraction = 1.0 / RELOPTINFO_LOCAL_FIELD(subroot, sorted_path->parent, rows);
|
||||
|
|
@ -573,7 +613,7 @@ static bool build_minmax_path(PlannerInfo* root, MinMaxAggInfo* mminfo, Oid eqop
|
|||
|
||||
path_cost = sorted_path->startup_cost + path_fraction * (sorted_path->total_cost - sorted_path->startup_cost);
|
||||
|
||||
/* Save state for further processing */
|
||||
/* 保存状态以供进一步处理 */
|
||||
mminfo->subroot = subroot;
|
||||
mminfo->path = sorted_path;
|
||||
mminfo->pathcost = path_cost;
|
||||
|
|
@ -581,9 +621,23 @@ static bool build_minmax_path(PlannerInfo* root, MinMaxAggInfo* mminfo, Oid eqop
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Construct a suitable plan for a converted aggregate query
|
||||
*/
|
||||
/*
|
||||
* make_agg_subplan: 生成聚合子查询计划
|
||||
*
|
||||
* 此函数生成用于MIN/MAX聚合的子查询计划,将该计划转换为InitPlan,并为其结果创建一个参数。
|
||||
* 该子查询计划执行对目标列的排序和限制以获得MIN/MAX值。
|
||||
*
|
||||
* 输入参数:
|
||||
* - root: 查询规划器的上下文信息
|
||||
* - mminfo: MIN/MAX聚合的相关信息
|
||||
*
|
||||
* 返回值:
|
||||
* 返回生成的子查询计划。
|
||||
*/
|
||||
static Plan* make_agg_subplan(PlannerInfo* root, MinMaxAggInfo* mminfo)
|
||||
{
|
||||
PlannerInfo* subroot = mminfo->subroot;
|
||||
|
|
@ -591,14 +645,13 @@ static Plan* make_agg_subplan(PlannerInfo* root, MinMaxAggInfo* mminfo)
|
|||
Plan* plan = NULL;
|
||||
|
||||
/*
|
||||
* Generate the plan for the subquery. We already have a Path, but we have
|
||||
* to convert it to a Plan and attach a LIMIT node above it.
|
||||
* 生成子查询的计划。我们已经有一个路径,但我们需要将其转换为计划并在其上方添加一个LIMIT节点。
|
||||
*/
|
||||
plan = create_plan(subroot, mminfo->path);
|
||||
|
||||
plan->targetlist = subparse->targetList;
|
||||
|
||||
/* For partition table, we should pass targetlist down to base table scan */
|
||||
/* 对于分区表,应将目标列表传递给基表扫描 */
|
||||
if (IsA(plan, PartIterator))
|
||||
plan->lefttree->targetlist = plan->targetlist;
|
||||
|
||||
|
|
@ -606,16 +659,15 @@ static Plan* make_agg_subplan(PlannerInfo* root, MinMaxAggInfo* mminfo)
|
|||
#ifdef STREAMPLAN
|
||||
if (IS_STREAM_PLAN) {
|
||||
if (is_execute_on_coordinator(plan) || is_execute_on_allnodes(plan)) {
|
||||
// local case or should we assert
|
||||
// 本地情况或者应该断言
|
||||
} else {
|
||||
bool stream_added = true;
|
||||
// all other case, broadcast plan to all nodes, get all result back and do final agg at the coordinator.
|
||||
// The final aggregate can also be done in any single node that's going to consume the results, but do that
|
||||
// we need to know the interested partition of final plan which we don't have it yet. One way to do this, is
|
||||
// to mark with floating target node (special node group of any_signle_node) and modify it whenever it
|
||||
// becomes known. Also add a final pass at the end of optimizer to convert floating partition to known
|
||||
// interested partition or coordinator which is always interested because all results should come to
|
||||
// coordinator before returning to client.
|
||||
// 所有其他情况,广播计划到所有节点,获取所有结果并在协调节点上进行最终聚合。
|
||||
// 最终聚合也可以在将要消耗结果的任何单个节点上执行,但是要做到这一点,
|
||||
// 我们需要知道最终计划的感兴趣分区,但我们尚未知道。完成此操作的一种方法是,
|
||||
// 使用浮动目标节点(任何_single_node 的特殊节点组)标记它,并在每次它变为已知时进行修改。
|
||||
// 此外,在优化器的最后添加最终传递,以将浮动分区转换为已知感兴趣分区或总协调器,
|
||||
// 因为所有结果都应在返回客户端之前到达协调器。
|
||||
if (root->query_level == 1) {
|
||||
plan = make_simple_RemoteQuery(plan, root, true);
|
||||
} else if (!is_replicated_plan(plan)) {
|
||||
|
|
@ -630,8 +682,7 @@ static Plan* make_agg_subplan(PlannerInfo* root, MinMaxAggInfo* mminfo)
|
|||
errno_t errorno = memset_s(&dummy_aggcosts, sizeof(AggClauseCosts), 0, sizeof(AggClauseCosts));
|
||||
securec_check(errorno, "\0", "\0");
|
||||
|
||||
// Ideally we should call make_agg, but this case is a bit special in handling the the tlist that's not
|
||||
// covered in make_agg shold clean up make agg. a bit.
|
||||
// 理论上我们应该调用make_agg,但是这种情况在处理未在make_agg中覆盖的tlist时有点特殊。
|
||||
TargetEntry* tle =
|
||||
makeTargetEntry((Expr*)copyObject(mminfo->aggref), (AttrNumber)1, pstrdup("agg_target"), false);
|
||||
|
||||
|
|
@ -683,72 +734,108 @@ static Plan* make_agg_subplan(PlannerInfo* root, MinMaxAggInfo* mminfo)
|
|||
#endif
|
||||
|
||||
/*
|
||||
* Convert the plan into an InitPlan, and make a Param for its result.
|
||||
* 将计划转换为InitPlan,并为其结果创建一个Param。
|
||||
*/
|
||||
mminfo->param = SS_make_initplan_from_plan(
|
||||
subroot, plan, exprType((Node*)mminfo->target), -1, exprCollation((Node*)mminfo->target));
|
||||
|
||||
/*
|
||||
* Make sure the initplan gets into the outer PlannerInfo, along with any
|
||||
* other initplans generated by the sub-planning run. We had to include
|
||||
* the outer PlannerInfo's pre-existing initplans into the inner one's
|
||||
* init_plans list earlier, so make sure we don't put back any duplicate
|
||||
* entries.
|
||||
* 确保InitPlan进入外部PlannerInfo,以及子规划运行生成的其他任何InitPlan。
|
||||
* 我们已经将外部PlannerInfo的现有InitPlan包括在内部PlannerInfo的init_plans列表中,
|
||||
* 因此确保我们不会放回任何重复的条目。
|
||||
*/
|
||||
root->init_plans = list_concat_unique_ptr(root->init_plans, subroot->init_plans);
|
||||
return plan;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Replace original aggregate calls with subplan output Params
|
||||
*/
|
||||
/*
|
||||
* replace_aggs_with_params_mutator: 用子查询输出参数替换原始聚合调用
|
||||
*
|
||||
* 此函数遍历表达式树,查找并替换原始聚合调用的引用,将其替换为子查询输出的参数。
|
||||
*
|
||||
* 输入参数:
|
||||
* - node: 当前要处理的节点
|
||||
* - root: 查询规划器的上下文信息,包含有关MIN/MAX聚合的信息
|
||||
*
|
||||
* 返回值:
|
||||
* 返回替换后的节点。
|
||||
*/
|
||||
static Node* replace_aggs_with_params_mutator(Node* node, PlannerInfo* root)
|
||||
{
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
// 如果当前节点是一个聚合函数调用(Aggref)
|
||||
if (IsA(node, Aggref)) {
|
||||
Aggref* aggref = (Aggref*)node;
|
||||
TargetEntry* curTarget = (TargetEntry*)linitial(aggref->args);
|
||||
ListCell* lc = NULL;
|
||||
|
||||
// 遍历已保存的 MIN/MAX 聚合信息列表
|
||||
foreach (lc, root->minmax_aggs) {
|
||||
MinMaxAggInfo* mminfo = (MinMaxAggInfo*)lfirst(lc);
|
||||
|
||||
// 如果找到与当前聚合函数匹配的信息
|
||||
if (mminfo->aggfnoid == aggref->aggfnoid && equal(mminfo->target, curTarget->expr))
|
||||
return (Node*)mminfo->param;
|
||||
return (Node*)mminfo->param; // 返回相应的参数节点
|
||||
}
|
||||
|
||||
// 如果未找到匹配的聚合信息,抛出错误
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("failed to re-find MinMaxAggInfo record")));
|
||||
}
|
||||
|
||||
// 处理其他类型的节点
|
||||
AssertEreport(!IsA(node, SubLink),
|
||||
MOD_OPT,
|
||||
"invalid node type when replaceing original aggregate calls with subplan output Params.");
|
||||
"invalid node type when replacing original aggregate calls with subplan output Params.");
|
||||
|
||||
// 递归处理节点的子节点
|
||||
return expression_tree_mutator(node, (Node* (*)(Node*, void*)) replace_aggs_with_params_mutator, (void*)root);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get the OID of the sort operator, if any, associated with an aggregate.
|
||||
* Returns InvalidOid if there is no such operator.
|
||||
*/
|
||||
/*
|
||||
* fetch_agg_sort_op: 获取聚合函数的排序操作符
|
||||
*
|
||||
* 该函数根据聚合函数的OID(对象标识符)从系统目录表 pg_aggregate 中检索相关信息,
|
||||
* 并返回聚合函数的排序操作符的OID。
|
||||
*
|
||||
* 输入参数:
|
||||
* - aggfnoid: 聚合函数的OID
|
||||
*
|
||||
* 返回值:
|
||||
* 返回排序操作符的OID,如果未找到则返回 InvalidOid。
|
||||
*/
|
||||
static Oid fetch_agg_sort_op(Oid aggfnoid)
|
||||
{
|
||||
HeapTuple aggTuple;
|
||||
Form_pg_aggregate aggform;
|
||||
Oid aggsortop;
|
||||
|
||||
/* fetch aggregate entry from pg_aggregate */
|
||||
/* 从系统目录表 pg_aggregate 中检索聚合函数的条目 */
|
||||
aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(aggfnoid));
|
||||
if (!HeapTupleIsValid(aggTuple))
|
||||
return InvalidOid;
|
||||
return InvalidOid; // 未找到匹配的聚合函数
|
||||
|
||||
aggform = (Form_pg_aggregate)GETSTRUCT(aggTuple);
|
||||
aggsortop = aggform->aggsortop;
|
||||
ReleaseSysCache(aggTuple);
|
||||
aggsortop = aggform->aggsortop; // 获取聚合函数的排序操作符
|
||||
ReleaseSysCache(aggTuple); // 释放系统缓存中的条目
|
||||
|
||||
return aggsortop;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @Description: Judge this agg if is Min/Max.
|
||||
* @in aggref - agg struct
|
||||
|
|
@ -756,6 +843,19 @@ static Oid fetch_agg_sort_op(Oid aggfnoid)
|
|||
* BTGreaterStrategyNumber is mean Max, BTLessStrategyNumber is mean Min.
|
||||
* @return - true is mean agg is Min/Max, false not.
|
||||
*/
|
||||
/*
|
||||
* check_agg_optimizable: 检查聚合函数是否可以进行优化
|
||||
*
|
||||
* 该函数用于检查聚合函数是否符合可以进行优化的条件。聚合函数必须是 MIN 或 MAX 聚合,
|
||||
* 且其参数必须为一个变量(Var),不能是系统列。
|
||||
*
|
||||
* 输入参数:
|
||||
* - aggref: 聚合函数的 Aggref 结构
|
||||
* - strategy: 返回聚合函数的优化策略,用于表示是 MIN 还是 MAX
|
||||
*
|
||||
* 返回值:
|
||||
* 如果聚合函数可以进行优化,返回 true,否则返回 false。如果返回 true,strategy 会被设置为适当的值。
|
||||
*/
|
||||
bool check_agg_optimizable(Aggref* aggref, int16* strategy)
|
||||
{
|
||||
TargetEntry* curTarget = NULL;
|
||||
|
|
@ -763,30 +863,37 @@ bool check_agg_optimizable(Aggref* aggref, int16* strategy)
|
|||
Oid opcintype = InvalidOid;
|
||||
Oid aggsortop = InvalidOid;
|
||||
|
||||
/* not a MIN/MAX aggregate. */
|
||||
/* 如果不是 MIN/MAX 聚合,返回 false */
|
||||
if (list_length(aggref->args) != 1 || aggref->aggorder != NIL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curTarget = (TargetEntry*)linitial(aggref->args);
|
||||
/* Parameter of agg only can be var type, and can not be system column. */
|
||||
|
||||
/* 参数只能是变量类型,且不能是系统列 */
|
||||
if (!IsA(curTarget->expr, Var) || ((Var*)(curTarget->expr))->varattno <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Oid var_oid = ((Var*)(curTarget->expr))->vartype;
|
||||
|
||||
|
||||
/* 如果变量类型是可优化的 MINMAXOPTIMIZATIONOID,则返回 false */
|
||||
if (MINMAXOPTIMIZATIONOID(var_oid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 获取聚合函数的排序操作符 */
|
||||
aggsortop = fetch_agg_sort_op(aggref->aggfnoid);
|
||||
/* not a MIN/MAX aggregate. */
|
||||
|
||||
/* 如果排序操作符无效,返回 false */
|
||||
if (!OidIsValid(aggsortop)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* see system table pg_amop, out put strategy sign this is min or max. */
|
||||
/*
|
||||
* 通过排序操作符获取优化策略,用于确定是 MIN 还是 MAX 聚合函数。
|
||||
* 如果获取失败,返回 false。
|
||||
*/
|
||||
if (!get_ordering_op_properties(aggsortop, &opfamily, &opcintype, strategy)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -794,3 +901,4 @@ bool check_agg_optimizable(Aggref* aggref, int16* strategy)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -87,7 +87,7 @@
|
|||
*
|
||||
* @Return: true: walk success false: failed
|
||||
***/
|
||||
static List* getSpecialPlanSubNodes(const Plan* node)
|
||||
static List* getSpecialPlanSubNodes(const Plan* node)
|
||||
{
|
||||
List* ps_list = NIL;
|
||||
|
||||
|
|
@ -95,57 +95,68 @@ static List* getSpecialPlanSubNodes(const Plan* node)
|
|||
return NIL;
|
||||
}
|
||||
|
||||
/* Find plan list in special plan nodes. */
|
||||
switch (nodeTag(node)) {
|
||||
case T_Append:
|
||||
case T_VecAppend: {
|
||||
// 如果是Append或VecAppend计划节点
|
||||
Append* append = (Append*)node;
|
||||
ListCell* lc = NULL;
|
||||
foreach (lc, append->appendplans) {
|
||||
// 遍历子计划节点列表
|
||||
ps_list = lappend(ps_list, lfirst(lc));
|
||||
}
|
||||
} break;
|
||||
case T_ModifyTable:
|
||||
case T_VecModifyTable: {
|
||||
// 如果是ModifyTable或VecModifyTable计划节点
|
||||
ModifyTable* mt = (ModifyTable*)node;
|
||||
ListCell* lc = NULL;
|
||||
foreach (lc, mt->plans) {
|
||||
// 遍历子计划节点列表
|
||||
ps_list = lappend(ps_list, lfirst(lc));
|
||||
}
|
||||
} break;
|
||||
case T_SubqueryScan:
|
||||
case T_VecSubqueryScan: {
|
||||
// 如果是SubqueryScan或VecSubqueryScan计划节点
|
||||
SubqueryScan* ss = (SubqueryScan*)node;
|
||||
if (ss->subplan) {
|
||||
// 如果有子查询计划节点,将其添加到列表中
|
||||
ps_list = lappend(ps_list, (void*)ss->subplan);
|
||||
}
|
||||
} break;
|
||||
case T_MergeAppend:
|
||||
case T_VecMergeAppend: {
|
||||
// 如果是MergeAppend或VecMergeAppend计划节点
|
||||
MergeAppend* ma = (MergeAppend*)node;
|
||||
ListCell* lc = NULL;
|
||||
foreach (lc, ma->mergeplans) {
|
||||
// 遍历子计划节点列表
|
||||
ps_list = lappend(ps_list, lfirst(lc));
|
||||
}
|
||||
} break;
|
||||
case T_BitmapAnd:
|
||||
case T_CStoreIndexAnd: {
|
||||
// 如果是BitmapAnd或CStoreIndexAnd计划节点
|
||||
BitmapAnd* ba = (BitmapAnd*)node;
|
||||
ListCell* lc = NULL;
|
||||
foreach (lc, ba->bitmapplans) {
|
||||
// 遍历子计划节点列表
|
||||
ps_list = lappend(ps_list, lfirst(lc));
|
||||
}
|
||||
} break;
|
||||
|
||||
case T_BitmapOr:
|
||||
case T_CStoreIndexOr: {
|
||||
// 如果是BitmapOr或CStoreIndexOr计划节点
|
||||
BitmapOr* bo = (BitmapOr*)node;
|
||||
ListCell* lc = NULL;
|
||||
foreach (lc, bo->bitmapplans) {
|
||||
// 遍历子计划节点列表
|
||||
ps_list = lappend(ps_list, lfirst(lc));
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
// 对于其他类型的计划节点,将列表置为空
|
||||
ps_list = NIL;
|
||||
} break;
|
||||
}
|
||||
|
|
@ -156,6 +167,7 @@ static List* getSpecialPlanSubNodes(const Plan* node)
|
|||
inline static void setPlanNodeId(Plan* node, RecursiveRefContext* context,
|
||||
const RecursiveUnion* runode)
|
||||
{
|
||||
// 设置计划节点的控制计划节点ID
|
||||
node->control_plan_nodeid = ((context->nested_stream_depth == 1) ?
|
||||
GET_PLAN_NODEID(runode) :
|
||||
GET_PLAN_NODEID(context->control_plan));
|
||||
|
|
@ -165,12 +177,9 @@ static void setRecursiveRteplanRefByType(Plan* node, RecursiveRefContext* contex
|
|||
const RecursiveUnion* runode)
|
||||
{
|
||||
switch (nodeTag(node)) {
|
||||
/*
|
||||
* If we found we have operators that do not support recursive-execution we
|
||||
* mark the stream-recursive unsupported
|
||||
*/
|
||||
case T_DfsScan:
|
||||
case T_DfsIndexScan: {
|
||||
// 如果是DfsScan或DfsIndexScan计划节点
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
"Recursive CTE contains DFS Scan is not shippable CTE plan node id[%d]",
|
||||
|
|
@ -179,95 +188,71 @@ static void setRecursiveRteplanRefByType(Plan* node, RecursiveRefContext* contex
|
|||
mark_stream_unsupport();
|
||||
} break;
|
||||
case T_Stream: {
|
||||
// 如果是Stream计划节点
|
||||
Stream* stream = (Stream*)node;
|
||||
elog(DEBUG1, "set plan ref for stream node %d", GET_PLAN_NODEID(stream));
|
||||
|
||||
/* Remove LOCAL GATHER for Recursive, which is just lefttree of another normal Stream */
|
||||
// 移除递归控制器的局部收集器,如果存在
|
||||
if (IsA(node->lefttree, Stream) && ((Stream*)node->lefttree)->is_recursive_local) {
|
||||
node->lefttree = node->lefttree->lefttree;
|
||||
}
|
||||
|
||||
/* If set control plan nodeid is required, we do reset Plan::control_plan_nodeid */
|
||||
// 如果需要设置控制计划节点ID,重置Plan::control_plan_nodeid
|
||||
if (context->set_control_plan_nodeid) {
|
||||
setPlanNodeId(node, context, runode);
|
||||
|
||||
/*
|
||||
* After set current plan node, we need reset the "set_control_plan_nodeid"
|
||||
* back to false.
|
||||
*/
|
||||
// 设置完当前计划节点后,重置"set_control_plan_nodeid"为false
|
||||
context->set_control_plan_nodeid = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* For stream node, we need set its first underlying plannode's
|
||||
* control nodeid.
|
||||
*/
|
||||
// 设置控制计划节点的标志为true
|
||||
context->set_control_plan_nodeid = true;
|
||||
context->nested_stream_depth++;
|
||||
context->control_plan = node;
|
||||
|
||||
/* The first node is mared as syncnode */
|
||||
// 如果没有指定同步生产者并且不是递归局部流,则设置计划节点为同步计划节点
|
||||
if (!context->is_syncup_producer_specified && !stream->is_recursive_local) {
|
||||
/* mark current stream node as sync-node (consumer) */
|
||||
node->is_sync_plannode = true;
|
||||
|
||||
/* mark current stream's input node as sync-node (producer) */
|
||||
node->lefttree->is_sync_plannode = true;
|
||||
|
||||
/* mark we've done with marking sync-plannode */
|
||||
context->is_syncup_producer_specified = true;
|
||||
}
|
||||
|
||||
/* Assign the stream level */
|
||||
// 设置流计划节点的层级
|
||||
stream->stream_level = context->nested_stream_depth;
|
||||
|
||||
/*
|
||||
* Mark the top-plannode under current Stream is controlled by RecursiveUnion
|
||||
* awk. 1st level of recursvie-controlling
|
||||
*/
|
||||
// 如果流计划节点的嵌套深度大于1,将计划节点标记为递归联合控制器
|
||||
if (context->nested_stream_depth > 1) {
|
||||
node->recursive_union_controller = true;
|
||||
}
|
||||
|
||||
/* set reference producer tree */
|
||||
// 递归设置左子树的计划节点引用
|
||||
set_recursive_cteplan_ref(node->lefttree, context);
|
||||
|
||||
/* Reset the nestted level back */
|
||||
// 减少嵌套流计划深度
|
||||
context->nested_stream_depth--;
|
||||
} break;
|
||||
case T_RecursiveUnion: {
|
||||
/* Mark current conttroller */
|
||||
// 如果是RecursiveUnion计划节点
|
||||
node->recursive_union_controller = true;
|
||||
|
||||
/*
|
||||
* Set ru_plan_nodeid for recursive part
|
||||
*
|
||||
* Note: we do not have to set the non-recursive part, the underlying
|
||||
* stream thread only runs one time, no need for cluster-step control.
|
||||
*/
|
||||
// 递归设置内部计划节点引用
|
||||
set_recursive_cteplan_ref(innerPlan(node), context);
|
||||
} break;
|
||||
default: {
|
||||
/* If set control plan nodeid is required, we do reset Plan::control_plan_nodeid */
|
||||
// 对于其他类型的计划节点,设置计划节点的引用
|
||||
if (context->set_control_plan_nodeid) {
|
||||
setPlanNodeId(node, context, runode);
|
||||
|
||||
/*
|
||||
* After set current plan node, we need reset the "set_control_plan_nodeid"
|
||||
* back to false.
|
||||
*/
|
||||
context->set_control_plan_nodeid = false;
|
||||
}
|
||||
|
||||
/* set recursive cteplan node id for its underlying left and right tree */
|
||||
Plan* lplan = outerPlan(node);
|
||||
Plan* rplan = innerPlan(node);
|
||||
|
||||
// 根据连接类型决定遍历左右子计划节点的顺序
|
||||
if (context->join_type == T_HashJoin || context->join_type == T_VecHashJoin) {
|
||||
if (lplan != NULL) {
|
||||
set_recursive_cteplan_ref(lplan, context);
|
||||
}
|
||||
|
||||
if (rplan != NULL) {
|
||||
set_recursive_cteplan_ref(rplan, context);
|
||||
}
|
||||
|
|
@ -275,7 +260,6 @@ static void setRecursiveRteplanRefByType(Plan* node, RecursiveRefContext* contex
|
|||
if (rplan != NULL) {
|
||||
set_recursive_cteplan_ref(rplan, context);
|
||||
}
|
||||
|
||||
if (lplan != NULL) {
|
||||
set_recursive_cteplan_ref(lplan, context);
|
||||
}
|
||||
|
|
@ -292,92 +276,97 @@ void set_recursive_cteplan_ref(Plan* node, RecursiveRefContext* context)
|
|||
|
||||
const RecursiveUnion* runode = context->ru_plan;
|
||||
Assert(runode != NULL && IsA(runode, RecursiveUnion) && context != NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* We won't build the recursive control flow as a correlated recursive UNION have
|
||||
* to be executed in one DN
|
||||
*/
|
||||
if (runode->is_correlated) {
|
||||
return;
|
||||
}
|
||||
// 检查是否为 correlated RecursiveUnion
|
||||
if (runode->is_correlated) {
|
||||
return; // 如果是 correlated,不需要执行后续操作
|
||||
}
|
||||
|
||||
/* Bind recursive union plannodeid for each underlying operators */
|
||||
node->recursive_union_plan_nodeid = GET_PLAN_NODEID(runode);
|
||||
// 设置当前节点的 PlanNodeID 为 RecursiveUnion 的 PlanNodeID
|
||||
node->recursive_union_plan_nodeid = GET_PLAN_NODEID(runode);
|
||||
|
||||
/* First, assign join type of nearest node */
|
||||
if (nodeTag(node) == T_HashJoin || nodeTag(node) == T_VecHashJoin || nodeTag(node) == T_MergeJoin ||
|
||||
nodeTag(node) == T_VecMergeJoin || nodeTag(node) == T_NestLoop || nodeTag(node) == T_VecNestLoop) {
|
||||
context->join_type = nodeTag(node);
|
||||
}
|
||||
// 检查当前节点的类型,如果是一些特定的关联节点,则设置 join_type
|
||||
if (nodeTag(node) == T_HashJoin || nodeTag(node) == T_VecHashJoin || nodeTag(node) == T_MergeJoin ||
|
||||
nodeTag(node) == T_VecMergeJoin || nodeTag(node) == T_NestLoop || nodeTag(node) == T_VecNestLoop) {
|
||||
context->join_type = nodeTag(node);
|
||||
}
|
||||
|
||||
/* Second, Process regular nodes */
|
||||
setRecursiveRteplanRefByType(node, context, runode);
|
||||
// 设置递归引用的 RTE 表达式引用关系
|
||||
setRecursiveRteplanRefByType(node, context, runode);
|
||||
|
||||
/* Third, process the plannode is not consists of left and right tree */
|
||||
List* special_subnodes = getSpecialPlanSubNodes(node);
|
||||
if (special_subnodes != NIL) {
|
||||
ListCell* lc = NULL;
|
||||
foreach (lc, special_subnodes) {
|
||||
Plan* subnode = (Plan*)lfirst(lc);
|
||||
set_recursive_cteplan_ref(subnode, context);
|
||||
}
|
||||
}
|
||||
|
||||
/* find all subplan exprs from main plan */
|
||||
List* subplan_list = check_subplan_list(node);
|
||||
// 获取当前节点的特殊子节点
|
||||
List* special_subnodes = getSpecialPlanSubNodes(node);
|
||||
if (special_subnodes != NIL) {
|
||||
ListCell* lc = NULL;
|
||||
|
||||
foreach (lc, subplan_list) {
|
||||
Node* localNode = (Node*)lfirst(lc);
|
||||
Plan* plan = NULL;
|
||||
SubPlan* subplan = NULL;
|
||||
|
||||
if (IsA(localNode, SubPlan)) {
|
||||
subplan = (SubPlan*)lfirst(lc);
|
||||
/* this is for the case that initplan hidden in testexpr of subplan */
|
||||
subplan_list = list_concat(subplan_list, check_subplan_expr(subplan->testexpr));
|
||||
} else {
|
||||
Assert(IsA(localNode, Param));
|
||||
Param* param = (Param*)localNode;
|
||||
ListCell* lc2 = NULL;
|
||||
foreach (lc2, context->initplans) {
|
||||
subplan = (SubPlan*)lfirst(lc2);
|
||||
if (list_member_int(subplan->setParam, param->paramid))
|
||||
break;
|
||||
}
|
||||
if (subplan == NULL || lc2 == NULL)
|
||||
continue;
|
||||
}
|
||||
|
||||
plan = (Plan*)list_nth(context->subplans, subplan->plan_id - 1);
|
||||
set_recursive_cteplan_ref(plan, context);
|
||||
foreach (lc, special_subnodes) {
|
||||
Plan* subnode = (Plan*)lfirst(lc);
|
||||
set_recursive_cteplan_ref(subnode, context);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前节点的子查询计划列表
|
||||
List* subplan_list = check_subplan_list(node);
|
||||
ListCell* lc = NULL;
|
||||
|
||||
foreach (lc, subplan_list) {
|
||||
Node* localNode = (Node*)lfirst(lc);
|
||||
Plan* plan = NULL;
|
||||
SubPlan* subplan = NULL;
|
||||
|
||||
if (IsA(localNode, SubPlan)) {
|
||||
subplan = (SubPlan*)lfirst(lc);
|
||||
|
||||
// 将 SubPlan 的测试表达式加入到 subplan_list 中
|
||||
subplan_list = list_concat(subplan_list, check_subplan_expr(subplan->testexpr));
|
||||
} else {
|
||||
Assert(IsA(localNode, Param));
|
||||
Param* param = (Param*)localNode;
|
||||
ListCell* lc2 = NULL;
|
||||
|
||||
// 查找 Param 对应的 SubPlan
|
||||
foreach (lc2, context->initplans) {
|
||||
subplan = (SubPlan*)lfirst(lc2);
|
||||
if (list_member_int(subplan->setParam, param->paramid))
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果找到 SubPlan,则继续处理
|
||||
if (subplan == NULL || lc2 == NULL)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据 SubPlan 的计划 ID 获取相应的计划节点
|
||||
plan = (Plan*)list_nth(context->subplans, subplan->plan_id - 1);
|
||||
set_recursive_cteplan_ref(plan, context);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断当前线程是否为 SyncUp Producer 线程
|
||||
bool IsSyncUpProducerThread()
|
||||
{
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* @Function: NeedSetupSyncUpController()
|
||||
*
|
||||
* @Brief: Invokded at ExecInitNode()funciton, to identify if we have to set up the step
|
||||
* controller to do step-syncup across the whole cluster
|
||||
*
|
||||
* @Input plan: the plan node need verify to create controller at plan-init stage
|
||||
*
|
||||
* @Return: True/False to indicate if we need set up controller
|
||||
***/
|
||||
// 判断是否需要设置 SyncUp Controller
|
||||
bool NeedSetupSyncUpController(Plan* plan)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// 如果是协调节点或者全局对象为空,则不需要设置 SyncUp Controller
|
||||
if (IS_PGXC_COORDINATOR || u_sess->stream_cxt.global_obj == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保当前节点是数据节点且全局对象不为空
|
||||
Assert(IS_PGXC_DATANODE && u_sess->stream_cxt.global_obj != NULL && plan != NULL);
|
||||
|
||||
switch (nodeTag(plan)) {
|
||||
|
|
@ -389,11 +378,9 @@ bool NeedSetupSyncUpController(Plan* plan)
|
|||
RecursiveUnion* ruplan = (RecursiveUnion*)plan;
|
||||
|
||||
if (ruplan->is_correlated) {
|
||||
/* No recursive controller should be setup in a correlated recursive CTE */
|
||||
result = false;
|
||||
Assert(!plan->recursive_union_controller);
|
||||
} else if (ruplan->has_inner_stream) {
|
||||
/* Setup controller if recursive-union's inner plan has stream operator */
|
||||
result = true;
|
||||
Assert(plan->recursive_union_controller);
|
||||
}
|
||||
|
|
@ -410,19 +397,10 @@ bool NeedSetupSyncUpController(Plan* plan)
|
|||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* @Function: NeedSyncUpRecursiveUnionStep()
|
||||
*
|
||||
* @Brief: Invokded at ExecRecursiveUnion(), to identify if we have to set up the step
|
||||
* controller to do step-syncup across the whole cluster
|
||||
*
|
||||
* @Input plan: the plan node need verify to create controller at plan-init stage
|
||||
*
|
||||
* @Return: True/False to indicate if we need do sync-up(Consumer)
|
||||
***/
|
||||
// 判断是否需要同步 RecursiveUnion 步骤
|
||||
bool NeedSyncUpRecursiveUnionStep(Plan* plan)
|
||||
{
|
||||
/* we don't have to do distributed step sync-up on coordinator node */
|
||||
// 如果是协调节点,不需要同步
|
||||
if (IS_PGXC_COORDINATOR) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -434,7 +412,6 @@ bool NeedSyncUpRecursiveUnionStep(Plan* plan)
|
|||
RecursiveUnion* ruplan = (RecursiveUnion*)plan;
|
||||
|
||||
if (ruplan->is_correlated) {
|
||||
/* We don't have to do recursive step syncup in correlated RCTE */
|
||||
result = false;
|
||||
Assert(!plan->recursive_union_controller);
|
||||
} else if (ruplan->has_inner_stream) {
|
||||
|
|
@ -450,18 +427,10 @@ bool NeedSyncUpRecursiveUnionStep(Plan* plan)
|
|||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* @Function: NeedSyncUpProducerStep()
|
||||
*
|
||||
* @Brief: Invokded at ExecutePlan(), to identify if current plan need sync-up
|
||||
*
|
||||
* @Input plan: the plan node need plan sync-up check
|
||||
*
|
||||
* @Return: True/False to indicate whether we need do sync-up
|
||||
***/
|
||||
// 判断是否需要同步 Producer 步骤
|
||||
bool NeedSyncUpProducerStep(Plan* top_plan)
|
||||
{
|
||||
/* we don't have to do distributed step sync-up on coordinator node */
|
||||
// 如果是协调节点,不需要同步
|
||||
if (IS_PGXC_COORDINATOR) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -469,6 +438,7 @@ bool NeedSyncUpProducerStep(Plan* top_plan)
|
|||
return EXEC_IN_RECURSIVE_MODE(top_plan);
|
||||
}
|
||||
|
||||
// 标记递归联合计划中的流计划
|
||||
void mark_stream_recursiveunion_plan(
|
||||
RecursiveUnion* runode, Plan* node, bool recursive_branch, List* subplans, List** initplans)
|
||||
{
|
||||
|
|
@ -476,10 +446,11 @@ void mark_stream_recursiveunion_plan(
|
|||
return;
|
||||
}
|
||||
|
||||
// 如果节点有初始化计划,将其添加到 initplans 中
|
||||
if (node->initPlan)
|
||||
*initplans = list_concat(*initplans, list_copy(node->initPlan));
|
||||
|
||||
/* if we found both side stream is marked we are done */
|
||||
// 如果 RecursiveUnion 已经包含内外流,不需要继续标记
|
||||
if (runode->has_inner_stream && runode->has_outer_stream) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -487,17 +458,18 @@ void mark_stream_recursiveunion_plan(
|
|||
Assert(IsA(runode, RecursiveUnion));
|
||||
|
||||
ListCell* lc = NULL;
|
||||
/* First, process the plannode is not consists of left and right tree */
|
||||
|
||||
// 获取当前节点的特殊子节点,通常是连接节点
|
||||
List* special_subnodes = getSpecialPlanSubNodes(node);
|
||||
if (special_subnodes != NIL) {
|
||||
foreach (lc, special_subnodes) {
|
||||
Plan* subnode = (Plan*)lfirst(lc);
|
||||
|
||||
// 递归标记特殊子节点
|
||||
mark_stream_recursiveunion_plan(runode, subnode, recursive_branch, subplans, initplans);
|
||||
}
|
||||
}
|
||||
|
||||
/* Sedondary, Process regular nodes */
|
||||
switch (nodeTag(node)) {
|
||||
case T_RecursiveUnion: {
|
||||
Plan* lplan = (Plan*)outerPlan(node);
|
||||
|
|
@ -505,40 +477,41 @@ void mark_stream_recursiveunion_plan(
|
|||
|
||||
Assert(lplan != NULL && rplan != NULL);
|
||||
|
||||
/* iterative left tree */
|
||||
// 标记递归联合计划的左子计划
|
||||
mark_stream_recursiveunion_plan((RecursiveUnion*)node, lplan, false, subplans, initplans);
|
||||
|
||||
/* iterative right tree */
|
||||
// 标记递归联合计划的右子计划
|
||||
mark_stream_recursiveunion_plan((RecursiveUnion*)node, rplan, true, subplans, initplans);
|
||||
} break;
|
||||
case T_Stream:
|
||||
case T_VecStream:
|
||||
if (recursive_branch && !runode->has_inner_stream) {
|
||||
/* mark iner side has stream */
|
||||
// 如果是递归联合的内流,标记内流存在
|
||||
runode->has_inner_stream = true;
|
||||
} else if (!recursive_branch && !runode->has_outer_stream) {
|
||||
/* mark outer side has stream */
|
||||
// 如果是递归联合的外流,标记外流存在
|
||||
runode->has_outer_stream = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
/* set recursive cteplan node id for its underlying left and right tree */
|
||||
Plan* lplan = outerPlan(node);
|
||||
Plan* rplan = innerPlan(node);
|
||||
|
||||
/* iterative left tree */
|
||||
// 递归标记左子计划
|
||||
mark_stream_recursiveunion_plan(runode, lplan, recursive_branch, subplans, initplans);
|
||||
|
||||
/* iterative right tree */
|
||||
// 递归标记右子计划
|
||||
mark_stream_recursiveunion_plan(runode, rplan, recursive_branch, subplans, initplans);
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, Process subplan and initplan */
|
||||
// 获取当前节点的子查询计划列表
|
||||
List* subplan_list = getSubPlan(node, subplans, *initplans);
|
||||
foreach (lc, subplan_list) {
|
||||
Plan* subnode = (Plan*)lfirst(lc);
|
||||
|
||||
// 递归标记子查询计划
|
||||
mark_stream_recursiveunion_plan(runode, subnode, recursive_branch, subplans, initplans);
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -58,33 +58,34 @@ typedef struct {
|
|||
} indexed_tlist; /* VARIABLE LENGTH STRUCT */
|
||||
|
||||
typedef struct {
|
||||
PlannerInfo* root;
|
||||
int rtoffset;
|
||||
PlannerInfo* root; // 指向查询规划器的信息结构的指针
|
||||
int rtoffset; // 偏移值,通常用于调整计划中的表达式或变量引用的引用号
|
||||
} fix_scan_expr_context;
|
||||
|
||||
typedef struct {
|
||||
PlannerInfo* root;
|
||||
indexed_tlist* outer_itlist;
|
||||
indexed_tlist* inner_itlist;
|
||||
Index acceptable_rel;
|
||||
int rtoffset;
|
||||
PlannerInfo* root; // 指向查询规划器的信息结构的指针
|
||||
indexed_tlist* outer_itlist; // 外部输入子查询的目标列表
|
||||
indexed_tlist* inner_itlist; // 内部输入子查询的目标列表
|
||||
Index acceptable_rel; // 可接受的关系索引
|
||||
int rtoffset; // 偏移值,通常用于调整计划中的表达式或变量引用的引用号
|
||||
} fix_join_expr_context;
|
||||
|
||||
typedef struct {
|
||||
PlannerInfo* root;
|
||||
indexed_tlist* subplan_itlist;
|
||||
Index newvarno;
|
||||
int rtoffset;
|
||||
PlannerInfo* root; // 指向查询规划器的信息结构的指针
|
||||
indexed_tlist* subplan_itlist; // 子查询计划的目标列表
|
||||
Index newvarno; // 新的变量编号
|
||||
int rtoffset; // 偏移值,通常用于调整计划中的表达式或变量引用的引用号
|
||||
} fix_upper_expr_context;
|
||||
|
||||
typedef struct {
|
||||
PlannerGlobal* glob;
|
||||
indexed_tlist* base_itlist;
|
||||
int rtoffset;
|
||||
Index relid;
|
||||
bool return_non_base_vars; /* Should we reject or return vars not found in base_itlist */
|
||||
PlannerGlobal* glob; // 指向全局查询规划器信息的指针
|
||||
indexed_tlist* base_itlist; // 基本查询的目标列表
|
||||
int rtoffset; // 偏移值,通常用于调整计划中的表达式或变量引用的引用号
|
||||
Index relid; // 关系标识符
|
||||
bool return_non_base_vars; // 是否应拒绝或返回不在基本目标列表中找到的变量
|
||||
} fix_remote_expr_context;
|
||||
|
||||
|
||||
/*
|
||||
* Check if a Const node is a regclass value. We accept plain OID too,
|
||||
* since a regclass Const will get folded to that type if it's an argument
|
||||
|
|
@ -200,30 +201,27 @@ static List* set_remote_returning_refs(PlannerInfo* root, List* rlist, Plan* top
|
|||
*/
|
||||
Plan* set_plan_references(PlannerInfo* root, Plan* plan)
|
||||
{
|
||||
PlannerGlobal* glob = root->glob;
|
||||
int rtoffset = list_length(glob->finalrtable);
|
||||
ListCell* lc = NULL;
|
||||
PlannerGlobal* glob = root->glob; // 获取查询规划器的全局信息
|
||||
int rtoffset = list_length(glob->finalrtable); // 获取最终范围表的长度作为偏移量
|
||||
ListCell* lc = NULL; // 定义一个链表元素迭代器
|
||||
|
||||
/*
|
||||
* In the flat rangetable, we zero out substructure pointers that are not
|
||||
* needed by the executor; this reduces the storage space and copying cost
|
||||
* for cached plans. We keep only the alias and eref Alias fields, which
|
||||
* are needed by EXPLAIN, and the selectedCols and modifiedCols bitmaps,
|
||||
* which are needed for executor-startup permissions checking and for
|
||||
* trigger event checking.
|
||||
* 在扁平化的范围表中,我们将不需要的子结构指针归零,以减少缓存计划的存储空间和复制成本。
|
||||
* 我们只保留别名和eref别名字段,这对于EXPLAIN是必需的,以及selectedCols和modifiedCols位图,
|
||||
* 这对于执行器启动权限检查和触发器事件检查是必需的。
|
||||
*/
|
||||
foreach (lc, root->parse->rtable) {
|
||||
RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc);
|
||||
RangeTblEntry* newrte = NULL;
|
||||
foreach (lc, root->parse->rtable) { // 遍历解析树的范围表
|
||||
RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc); // 获取范围表条目
|
||||
RangeTblEntry* newrte = NULL; // 用于存储新的范围表条目
|
||||
|
||||
/* flat copy to duplicate all the scalar fields */
|
||||
/* 对所有标量字段进行扁平复制 */
|
||||
newrte = (RangeTblEntry*)palloc(sizeof(RangeTblEntry));
|
||||
errno_t rc = memcpy_s(newrte, sizeof(RangeTblEntry), rte, sizeof(RangeTblEntry));
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
/*
|
||||
* zap unneeded sub-structure, keep nerte->securityQuals here
|
||||
* because we need this information when execute explain query.
|
||||
* 剔除不需要的子结构,但在这里保留nerte->securityQuals,
|
||||
* 因为我们在执行解释查询时需要这些信息。
|
||||
*/
|
||||
newrte->subquery = NULL;
|
||||
newrte->joinaliasvars = NIL;
|
||||
|
|
@ -237,28 +235,25 @@ Plan* set_plan_references(PlannerInfo* root, Plan* plan)
|
|||
newrte->ctecoltypmods = NIL;
|
||||
newrte->ctecolcollations = NIL;
|
||||
|
||||
glob->finalrtable = lappend(glob->finalrtable, newrte);
|
||||
glob->finalrtable = lappend(glob->finalrtable, newrte); // 添加新的范围表条目
|
||||
|
||||
/*
|
||||
* If it's a plain relation RTE, add the table to relationOids.
|
||||
* 如果它是一个普通的关系RTE,则将表添加到relationOids。
|
||||
*
|
||||
* We do this even though the RTE might be unreferenced in the plan
|
||||
* tree; this would correspond to cases such as views that were
|
||||
* expanded, child tables that were eliminated by constraint
|
||||
* exclusion, etc. Schema invalidation on such a rel must still force
|
||||
* rebuilding of the plan.
|
||||
* 即使在计划树中RTE可能未被引用,也要这样做;
|
||||
* 这对应于视图被展开,通过约束排除被消除的子表等情况。
|
||||
* 对于这样一个关系,其schema无效,必须仍然强制重新构建计划。
|
||||
*
|
||||
* Note we don't bother to avoid duplicate list entries. We could,
|
||||
* but it would probably cost more cycles than it would save.
|
||||
* 注意,我们不必费心避免重复的列表条目。我们可以这样做,
|
||||
* 但它可能消耗的计算周期比它所能节省的还要多。
|
||||
*/
|
||||
if (newrte->rtekind == RTE_RELATION)
|
||||
glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for RT index overflow; it's very unlikely, but if it did happen,
|
||||
* the executor would get confused by varnos that match the special varno
|
||||
* values.
|
||||
* 检查RT索引溢出;这非常不太可能发生,但如果发生了,
|
||||
* 执行器会因为变量号与特殊变量号值匹配而混淆。
|
||||
*/
|
||||
if (IS_SPECIAL_VARNO(list_length(glob->finalrtable)))
|
||||
ereport(ERROR,
|
||||
|
|
@ -267,29 +262,30 @@ Plan* set_plan_references(PlannerInfo* root, Plan* plan)
|
|||
errmsg("too many range table entries when set plan reference."))));
|
||||
|
||||
/*
|
||||
* Adjust RT indexes of PlanRowMarks and add to final rowmarks list
|
||||
* 调整PlanRowMarks的RT索引并添加到最终的rowmarks列表中
|
||||
*/
|
||||
foreach (lc, root->rowMarks) {
|
||||
PlanRowMark* rc = (PlanRowMark*)lfirst(lc);
|
||||
PlanRowMark* newrc = NULL;
|
||||
foreach (lc, root->rowMarks) { // 遍历行标记列表
|
||||
PlanRowMark* rc = (PlanRowMark*)lfirst(lc); // 获取行标记
|
||||
PlanRowMark* newrc = NULL; // 用于存储新的行标记
|
||||
|
||||
AssertEreport(IsA(rc, PlanRowMark), MOD_OPT, "type plan row mark is required.");
|
||||
|
||||
/* flat copy is enough since all fields are scalars */
|
||||
/* 扁平复制足够,因为所有字段都是标量 */
|
||||
newrc = (PlanRowMark*)palloc(sizeof(PlanRowMark));
|
||||
errno_t ret = memcpy_s(newrc, sizeof(PlanRowMark), rc, sizeof(PlanRowMark));
|
||||
securec_check(ret, "\0", "\0");
|
||||
/* adjust indexes ... but *not* the rowmarkId */
|
||||
/* 调整索引...但不调整rowmarkId */
|
||||
newrc->rti += rtoffset;
|
||||
newrc->prti += rtoffset;
|
||||
|
||||
glob->finalrowmarks = lappend(glob->finalrowmarks, newrc);
|
||||
glob->finalrowmarks = lappend(glob->finalrowmarks, newrc); // 添加新的行标记
|
||||
}
|
||||
|
||||
/* Now fix the Plan tree */
|
||||
return set_plan_refs(root, plan, rtoffset);
|
||||
/* 现在修复Plan树 */
|
||||
return set_plan_refs(root, plan, rtoffset); // 调用set_plan_refs函数,传递修复后的计划树和偏移量
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* set_plan_refs: recurse through the Plan nodes of a single subquery level
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -32,61 +32,91 @@
|
|||
#include "optimizer/streamplan.h"
|
||||
|
||||
|
||||
// 定义枚举类型 RedundantStreamType,表示冗余流操作的类型
|
||||
enum RedundantStreamType {
|
||||
EXTRA_OTHER_CASE,
|
||||
/* for stream, require redstribute or broadcast of select 1 from table where distribute_key = const scan */
|
||||
REDUNDANT_MANY_STREAMS,
|
||||
/* for limit, require limit 1. */
|
||||
REDUNDANT_MANY_STREAM_LIMITS,
|
||||
/* for subquery scan, require select 1 baseresult. */
|
||||
REDUNDANT_ONE_STREAM_AND_ONE_SUBQUERY_SCAN,
|
||||
REDUNDANT_ONE_STREAM_LIMIT_AND_ONE_SUBQUERY_SCAN
|
||||
EXTRA_OTHER_CASE, // 额外的其他情况
|
||||
REDUNDANT_MANY_STREAMS, // 需要对多个流进行冗余操作,例如 select 1 from table where distribute_key = const scan
|
||||
REDUNDANT_MANY_STREAM_LIMITS, // 需要对多个流限制操作进行冗余操作,例如 limit 1
|
||||
REDUNDANT_ONE_STREAM_AND_ONE_SUBQUERY_SCAN, // 需要对一个流和一个子查询扫描操作进行冗余操作
|
||||
REDUNDANT_ONE_STREAM_LIMIT_AND_ONE_SUBQUERY_SCAN // 需要对一个流限制操作和一个子查询扫描操作进行冗余操作
|
||||
};
|
||||
|
||||
// 定义 RedundantStreamInfo 结构体,用于存储冗余流操作的信息
|
||||
typedef struct {
|
||||
int count_stream;
|
||||
/* no double stream limit, we remove it in set_plan_reference */
|
||||
int count_stream_limit;
|
||||
int count_subquery_scan;
|
||||
RedundantStreamType redundant_stream_type;
|
||||
int count_stream; // 流操作的数量
|
||||
int count_stream_limit; // 流限制操作的数量
|
||||
int count_subquery_scan; // 子查询扫描操作的数量
|
||||
RedundantStreamType redundant_stream_type; // 冗余流操作的类型
|
||||
} RedundantStreamInfo;
|
||||
|
||||
// 判断 Limit 节点是否表示限制为 1 的操作
|
||||
|
||||
static bool is_limit_one(const Limit *limit)
|
||||
{
|
||||
Assert(limit != NULL);
|
||||
Assert(IsA(limit, Limit));
|
||||
Assert(limit != NULL); // 断言:确保 limit 参数不为 NULL
|
||||
Assert(IsA(limit, Limit)); // 断言:确保 limit 参数是 Limit 类型的节点
|
||||
|
||||
// 如果 limit 节点的 limitOffset 不为空,返回 false
|
||||
if (limit->limitOffset != NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果 limit 节点的 limitCount 为空,返回 false
|
||||
if (limit->limitCount == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果 limit 节点的 limitCount 不是 Const 类型的节点,返回 false
|
||||
if (!IsA(limit->limitCount, Const)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将 limitCount 节点强制转换为 Const 类型
|
||||
Const *limitCount = (Const *)limit->limitCount;
|
||||
|
||||
// 如果 limitCount 不是按值传递或其类型不是 INT8OID(int8 类型)或其值为 NULL,返回 false
|
||||
if (!limitCount->constbyval || limitCount->consttype != INT8OID || limitCount->constisnull) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取 limitCount 节点的值
|
||||
Datum count = ((Const *)limit->limitCount)->constvalue;
|
||||
|
||||
// 如果值不等于 1,返回 false;否则,返回 true
|
||||
if (count != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 判断限制条件是否是等于操作(=)
|
||||
static bool is_equal_operator(const List *qual, const List *distributed_keys)
|
||||
{
|
||||
// 如果限制条件列表长度不为 1,返回 false
|
||||
if (list_length(qual) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果分布键列表长度不为 1,返回 false
|
||||
if (list_length(distributed_keys) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取分布键列表中的第一个元素,假定它是一个 Var 节点
|
||||
Var *distribute_var = (Var *)lfirst(list_head(distributed_keys));
|
||||
|
||||
// 获取限制条件列表中的第一个元素,假定它是一个 OpExpr 节点
|
||||
OpExpr *opexpr = (OpExpr *)lfirst(list_head(qual));
|
||||
|
||||
// 如果分布键不是 Var 类型或限制条件不是 OpExpr 类型,返回 false
|
||||
if (!IsA(opexpr, OpExpr) || !IsA(distribute_var, Var)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 定义支持的等于操作的数据类型和对应的操作符 Oid
|
||||
const int SUPPORT_EQ = 8;
|
||||
const int VAR_OP = 2;
|
||||
const Oid var_eq_op_array[SUPPORT_EQ][VAR_OP] = {
|
||||
|
|
@ -98,161 +128,255 @@ static bool is_equal_operator(const List *qual, const List *distributed_keys)
|
|||
{INT8OID, INT8EQOID},
|
||||
{NUMERICOID, NUMERICEQOID},
|
||||
{TEXTOID, TEXTEQOID}};
|
||||
|
||||
// 遍历支持的等于操作的数据类型和对应的操作符 Oid
|
||||
for (int i = 0; i < SUPPORT_EQ; i++) {
|
||||
// 如果分布键的数据类型与当前支持的数据类型匹配,并且限制条件的操作符 Oid 与当前支持的操作符 Oid 匹配,返回 true
|
||||
if (distribute_var->vartype == var_eq_op_array[i][0] && opexpr->opno == var_eq_op_array[i][1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有匹配的情况,返回 false
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 判断限制条件是否为变量等于常量的情况
|
||||
static bool is_equal_const(const List *qual, const List *distributed_keys)
|
||||
{
|
||||
// 获取分布键列表中的第一个元素,假定它是一个 Var 节点
|
||||
Var *distribute_var = (Var *)lfirst(list_head(distributed_keys));
|
||||
|
||||
// 获取限制条件列表中的第一个元素,假定它是一个 OpExpr 节点
|
||||
OpExpr *opexpr = (OpExpr *)lfirst(list_head(qual));
|
||||
|
||||
// 获取限制条件中的表达式参数列表
|
||||
List *expr_args = opexpr->args;
|
||||
Assert(expr_args != NULL);
|
||||
|
||||
// 定义等于操作的参数长度为 2
|
||||
const int EQUAL_ARGS_LEN = 2;
|
||||
|
||||
// 如果表达式参数列表的长度不等于 2,返回 false
|
||||
if (list_length(expr_args) != EQUAL_ARGS_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Var *expr_var = NULL;
|
||||
Const *expr_const = NULL;
|
||||
ListCell *arg = NULL;
|
||||
foreach(arg, expr_args) {
|
||||
|
||||
// 遍历表达式参数列表
|
||||
foreach (arg, expr_args) {
|
||||
Expr *expr = (Expr *)lfirst(arg);
|
||||
|
||||
// 如果参数是一个 Var 节点,将其保存到 expr_var
|
||||
if (IsA(expr, Var)) {
|
||||
expr_var = (Var *)expr;
|
||||
} else if (IsA(expr, Const)) {
|
||||
}
|
||||
// 如果参数是一个 Const 节点,将其保存到 expr_const
|
||||
else if (IsA(expr, Const)) {
|
||||
expr_const = (Const *)expr;
|
||||
} else if (IsA(expr, RelabelType) && ((RelabelType *)expr)->relabelformat == COERCE_IMPLICIT_CAST) {
|
||||
}
|
||||
// 如果参数是一个 RelabelType 节点,并且其 relabelformat 为 COERCE_IMPLICIT_CAST,尝试提取其中的 Var 节点
|
||||
else if (IsA(expr, RelabelType) && ((RelabelType *)expr)->relabelformat == COERCE_IMPLICIT_CAST) {
|
||||
Var *var = (Var *)((RelabelType *)expr)->arg;
|
||||
if (var != NULL && IsA(var, Var)) {
|
||||
expr_var = (Var *)var;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* qual is var equal const */
|
||||
|
||||
// 如果没有找到合适的 Var 和 Const,返回 false
|
||||
if (expr_var == NULL || expr_const == NULL) {
|
||||
return false;
|
||||
}
|
||||
/* qual var equal distribute var */
|
||||
if (!(expr_var->varno == distribute_var->varno || expr_var->varnoold == distribute_var->varno)
|
||||
|| !(expr_var->varattno == distribute_var->varattno || expr_var->varoattno == distribute_var->varattno)) {
|
||||
|
||||
// 判断限制条件中的变量与分布键是否匹配
|
||||
if (!(expr_var->varno == distribute_var->varno || expr_var->varnoold == distribute_var->varno) ||
|
||||
!(expr_var->varattno == distribute_var->varattno || expr_var->varoattno == distribute_var->varattno)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果符合上述条件,返回 true,表示限制条件为变量等于常量的情况
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 判断限制条件是否为分布键等于常量的情况
|
||||
static bool is_distribute_key_eq_const_qual(const List *qual, const List *distributed_keys)
|
||||
{
|
||||
// 断言限制条件和分布键列表不为空,并且分布键列表是一个 List 类型
|
||||
Assert(qual != NULL);
|
||||
Assert(nodeTag(qual) == T_List);
|
||||
Assert(distributed_keys != NULL);
|
||||
Assert(IsA(distributed_keys, List));
|
||||
// 如果限制条件不是等于操作符,返回 false
|
||||
if (!is_equal_operator(qual, distributed_keys)) {
|
||||
return false;
|
||||
}
|
||||
// 如果限制条件不是分布键等于常量,返回 false
|
||||
if (!is_equal_const(qual, distributed_keys)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果限制条件同时满足上述两个条件,返回 true,表示是分布键等于常量的情况
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 从扫描节点中提取限制条件列表
|
||||
static List *fetch_qual_from_scan(const Scan *scan)
|
||||
{
|
||||
switch (nodeTag(scan)) {
|
||||
// 如果是普通的扫描节点(Scan 类型),返回 NULL,表示没有限制条件
|
||||
case T_Scan: {
|
||||
return scan->plan.qual;
|
||||
}
|
||||
case T_SeqScan: {
|
||||
return scan->plan.qual;
|
||||
}
|
||||
case T_IndexScan: {
|
||||
return ((IndexScan *)scan)->indexqual;
|
||||
return NULL;
|
||||
}
|
||||
// 如果是位图索引扫描节点(BitmapIndexScan 类型),返回索引限制条件列表
|
||||
case T_BitmapIndexScan: {
|
||||
return ((BitmapIndexScan *)scan)->indexqual;
|
||||
}
|
||||
// 如果是索引唯一扫描节点(IndexOnlyScan 类型),返回索引限制条件列表
|
||||
case T_IndexOnlyScan: {
|
||||
return ((IndexOnlyScan *)scan)->indexqual;
|
||||
}
|
||||
// 对于其他类型的扫描节点,默认返回 NULL,表示没有限制条件
|
||||
default: {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查是否为选择常量与分布条件计划
|
||||
static bool is_select_const_with_distribute_qual_plan(const Scan *scan)
|
||||
{
|
||||
// 断言扫描节点不为空
|
||||
Assert(scan != NULL);
|
||||
|
||||
// 如果左子树不为空,返回 false,不是选择常量与分布条件计划
|
||||
if (scan->plan.lefttree != NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取扫描节点的限制条件列表
|
||||
List *qual = fetch_qual_from_scan(scan);
|
||||
|
||||
// 如果限制条件列表为空或不是 List 类型,返回 false
|
||||
if (qual == NULL || !IsA(qual, List)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取扫描节点的分布键列表
|
||||
List *distributed_keys = scan->plan.distributed_keys;
|
||||
|
||||
// 如果分布键列表为空或不是 List 类型,返回 false
|
||||
if (distributed_keys == NULL || !IsA(distributed_keys, List)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查限制条件是否为分布键等于常量的限制条件
|
||||
if (!is_distribute_key_eq_const_qual(qual, distributed_keys)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取扫描节点的目标列表
|
||||
List *targetlist = scan->plan.targetlist;
|
||||
|
||||
// 如果目标列表为空,返回 false
|
||||
if (targetlist == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取目标列表的第一个目标项
|
||||
TargetEntry *targetentry = (TargetEntry *)lfirst(list_head(targetlist));
|
||||
|
||||
// 如果目标项为空,返回 false
|
||||
if (targetentry == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 断言目标项是 TargetEntry 类型
|
||||
Assert(IsA(targetentry, TargetEntry));
|
||||
|
||||
// 获取目标项的表达式,应该是常量
|
||||
Const *expr = (Const *)targetentry->expr;
|
||||
|
||||
// 如果表达式为空或不是 Const 类型,返回 false
|
||||
if (expr == NULL || !IsA(expr, Const)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 满足所有条件,返回 true,是选择常量与分布条件计划
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 检查是否为流限制计划
|
||||
static bool is_stream_limit_plan(const Stream *stream)
|
||||
{
|
||||
// 断言流节点不为空
|
||||
Assert(stream != NULL);
|
||||
Assert(nodeTag(stream) == T_Stream);
|
||||
|
||||
// 如果流类型不是重分布或广播,返回 false
|
||||
if (stream->type != STREAM_REDISTRIBUTE && stream->type != STREAM_BROADCAST) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取流节点的左子树计划
|
||||
Plan *plan = stream->scan.plan.lefttree;
|
||||
|
||||
// 如果左子树为空,返回 false
|
||||
if (plan == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Limit *limit = NULL;
|
||||
|
||||
// 如果左子树是 Limit 节点,将其赋值给 limit
|
||||
if (IsA(plan, Limit)) {
|
||||
limit = (Limit *)plan;
|
||||
}
|
||||
|
||||
// 如果左子树是 SubqueryScan 节点,且流类型是重分布
|
||||
if (IsA(plan, SubqueryScan)) {
|
||||
SubqueryScan *sub = (SubqueryScan *)plan;
|
||||
|
||||
// 如果流类型不是重分布,返回 false
|
||||
if (stream->type != STREAM_REDISTRIBUTE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果 SubqueryScan 子计划不为空,且子计划是 Limit 节点,将其赋值给 limit
|
||||
if (sub->subplan != NULL && IsA(sub->subplan, Limit)) {
|
||||
limit = (Limit *)sub->subplan;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 limit 为空,返回 false
|
||||
if (limit == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查 limit 是否为限制为 1
|
||||
if (!is_limit_one(limit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取 limit 的左子树计划
|
||||
Plan *lefttree = limit->plan.lefttree;
|
||||
|
||||
// 如果左子树为空,返回 false
|
||||
if (lefttree == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据左子树的类型进行不同的检查
|
||||
switch (nodeTag(lefttree)) {
|
||||
case T_Stream: {
|
||||
// 如果左子树是流节点,递归调用 is_stream_limit_plan
|
||||
return is_stream_limit_plan((Stream *)lefttree);
|
||||
}
|
||||
/* fall through, we dont care which type of the scan */
|
||||
|
|
@ -261,6 +385,7 @@ static bool is_stream_limit_plan(const Stream *stream)
|
|||
case T_IndexScan:
|
||||
case T_BitmapIndexScan:
|
||||
case T_IndexOnlyScan: {
|
||||
// 如果左子树是扫描类型的节点,检查是否为选择常量与分布条件计划
|
||||
return is_select_const_with_distribute_qual_plan((Scan *)lefttree);
|
||||
}
|
||||
default:
|
||||
|
|
@ -268,17 +393,29 @@ static bool is_stream_limit_plan(const Stream *stream)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查是否为流计划
|
||||
// 流计划涉及将查询操作分布到多个并行执行节点(例如,分布式数据库中的不同数据节点或计算节点),以加速查询的执行
|
||||
static bool is_stream_plan(const Stream *stream)
|
||||
{
|
||||
// 断言流节点不为空
|
||||
Assert(stream != NULL);
|
||||
Assert(nodeTag(stream) == T_Stream);
|
||||
|
||||
// 如果流类型不是重分布,返回 false
|
||||
if (stream->type != STREAM_REDISTRIBUTE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取流节点的左子树计划
|
||||
Plan *lefttree = (Plan *)stream->scan.plan.lefttree;
|
||||
|
||||
// 如果左子树为空,返回 false
|
||||
if (lefttree == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据左子树的类型进行不同的检查
|
||||
switch (nodeTag(lefttree)) {
|
||||
/* fall through, we dont care which type of the scan */
|
||||
case T_Scan:
|
||||
|
|
@ -286,6 +423,7 @@ static bool is_stream_plan(const Stream *stream)
|
|||
case T_IndexScan:
|
||||
case T_BitmapIndexScan:
|
||||
case T_IndexOnlyScan: {
|
||||
// 如果左子树是扫描类型的节点,检查是否为选择常量与分布条件计划
|
||||
return is_select_const_with_distribute_qual_plan((Scan *)lefttree);
|
||||
}
|
||||
default:
|
||||
|
|
@ -293,166 +431,262 @@ static bool is_stream_plan(const Stream *stream)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// 检测是否可以应用选择常量与哈希过滤器的查询计划
|
||||
static bool is_select_const_with_hashfilter_plan(const SubqueryScan *subqueryscan)
|
||||
{
|
||||
// 将子查询扫描计划转换为基础结果类型
|
||||
BaseResult *baseresult = (BaseResult *)subqueryscan->subplan;
|
||||
|
||||
// 如果基础结果为空或者不是BaseResult类型,则返回false
|
||||
if (baseresult == NULL || !IsA(baseresult, BaseResult)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果基础结果计划有左子树,则返回false
|
||||
if (baseresult->plan.lefttree != NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取子查询扫描计划的过滤条件列表
|
||||
List *qual = subqueryscan->scan.plan.qual;
|
||||
|
||||
// 如果过滤条件为空,或者条件数量不等于1,或者第一个条件不是HashFilter类型,则返回false
|
||||
if (qual == NULL || list_length(qual) != 1 || !IsA(linitial(qual), HashFilter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取基础结果计划中的目标列表
|
||||
List *targetlist = baseresult->plan.targetlist;
|
||||
|
||||
// 如果目标列表为空或者列表长度不等于1,则返回false
|
||||
if (targetlist == NULL || list_length(targetlist) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取目标列表中的第一个目标项
|
||||
TargetEntry *targetentry = (TargetEntry *)lfirst(list_head(targetlist));
|
||||
|
||||
// 如果目标项不是TargetEntry类型,则返回false
|
||||
if (!IsA(targetentry, TargetEntry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取目标项的表达式,通常是一个常量
|
||||
Const *expr = (Const *)targetentry->expr;
|
||||
|
||||
// 如果表达式为空或者不是Const类型,则返回false
|
||||
if (expr == NULL || !IsA(expr, Const)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果以上所有条件都通过了,则返回true,表示可以应用选择常量与哈希过滤器的查询计划
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 设置冗余流类型
|
||||
static void set_redundant_stream_type(RedundantStreamInfo *redundant_info)
|
||||
{
|
||||
/* we have subqueryscan of select const */
|
||||
/* 如果存在子查询扫描的情况 */
|
||||
if (redundant_info->count_subquery_scan != 0) {
|
||||
// 如果有多于一个子查询扫描
|
||||
if (redundant_info->count_subquery_scan > 1) {
|
||||
/* who will write sql like this */
|
||||
/* 谁会编写这样的 SQL 呢? */
|
||||
redundant_info->redundant_stream_type = EXTRA_OTHER_CASE;
|
||||
return;
|
||||
}
|
||||
// 如果只有一个子查询扫描且没有流操作且没有流上限
|
||||
if (redundant_info->count_stream == 1 && redundant_info->count_stream_limit == 0) {
|
||||
redundant_info->redundant_stream_type = REDUNDANT_ONE_STREAM_AND_ONE_SUBQUERY_SCAN;
|
||||
return;
|
||||
}
|
||||
// 如果没有流操作但有一个流上限
|
||||
if (redundant_info->count_stream == 0 && redundant_info->count_stream_limit == 1) {
|
||||
redundant_info->redundant_stream_type = REDUNDANT_ONE_STREAM_LIMIT_AND_ONE_SUBQUERY_SCAN;
|
||||
return;
|
||||
}
|
||||
// 其他情况都归类为 EXTRA_OTHER_CASE
|
||||
redundant_info->redundant_stream_type = EXTRA_OTHER_CASE;
|
||||
return;
|
||||
}
|
||||
// 如果没有子查询扫描但同时有流操作和流上限
|
||||
if (redundant_info->count_stream != 0 && redundant_info->count_stream_limit != 0) {
|
||||
/* mix type, dont support yet */
|
||||
/* 混合类型,暂不支持 */
|
||||
redundant_info->redundant_stream_type = EXTRA_OTHER_CASE;
|
||||
return;
|
||||
}
|
||||
// 如果有流操作但没有流上限
|
||||
if (redundant_info->count_stream != 0) {
|
||||
redundant_info->redundant_stream_type = REDUNDANT_MANY_STREAMS;
|
||||
return;
|
||||
}
|
||||
// 如果有流上限但没有流操作
|
||||
if (redundant_info->count_stream_limit != 0) {
|
||||
redundant_info->redundant_stream_type = REDUNDANT_MANY_STREAM_LIMITS;
|
||||
return;
|
||||
}
|
||||
/* the only case we go here is all three counter are 0, but it should never happen */
|
||||
/* 唯一可能到达的情况是所有三个计数器都为 0,但这应该永远不会发生 */
|
||||
redundant_info->redundant_stream_type = EXTRA_OTHER_CASE;
|
||||
return;
|
||||
}
|
||||
|
||||
static void lookup_redundant_streams_of_append_plan(const Append *append,
|
||||
RedundantStreamInfo *redundant_info)
|
||||
|
||||
// 查找并确定附加计划中的冗余流
|
||||
static void lookup_redundant_streams_of_append_plan(const Append *append, RedundantStreamInfo *redundant_info)
|
||||
{
|
||||
// 断言确保输入的指针不为空,且附加计划是一个有效的Append节点
|
||||
Assert(append != NULL);
|
||||
Assert(redundant_info != NULL);
|
||||
Assert(IsA(append, Append));
|
||||
|
||||
ListCell *subplan;
|
||||
|
||||
// 遍历附加计划中的子计划
|
||||
foreach(subplan, append->appendplans) {
|
||||
// 获取当前子计划
|
||||
Plan *plan = (Plan *)lfirst(subplan);
|
||||
|
||||
// 根据不同的计划类型进行不同的处理
|
||||
switch (nodeTag(plan)) {
|
||||
case T_Stream: {
|
||||
// 如果是流计划并且是流上限计划
|
||||
if (is_stream_limit_plan((Stream *)plan)) {
|
||||
redundant_info->count_stream_limit++;
|
||||
break;
|
||||
}
|
||||
// 如果是普通的流计划
|
||||
if (is_stream_plan((Stream *)plan)) {
|
||||
redundant_info->count_stream++;
|
||||
break;
|
||||
}
|
||||
// 其他情况都归类为 EXTRA_OTHER_CASE,返回
|
||||
redundant_info->redundant_stream_type = EXTRA_OTHER_CASE;
|
||||
return;
|
||||
}
|
||||
case T_SubqueryScan: {
|
||||
// 如果是子查询扫描并且符合选择常量与哈希过滤器的计划条件
|
||||
if (is_select_const_with_hashfilter_plan((SubqueryScan *)plan)) {
|
||||
redundant_info->count_subquery_scan++;
|
||||
break;
|
||||
}
|
||||
// 其他情况都归类为 EXTRA_OTHER_CASE,返回
|
||||
redundant_info->redundant_stream_type = EXTRA_OTHER_CASE;
|
||||
return;
|
||||
}
|
||||
default:
|
||||
// 其他计划类型都归类为 EXTRA_OTHER_CASE,返回
|
||||
redundant_info->redundant_stream_type = EXTRA_OTHER_CASE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 在遍历完成后,根据不同的计数情况来设置冗余流类型
|
||||
set_redundant_stream_type(redundant_info);
|
||||
}
|
||||
|
||||
|
||||
// 优化流计划
|
||||
static void optimize_stream_plan(Stream *stream)
|
||||
{
|
||||
// 如果流的类型不是STREAM_REDISTRIBUTE,直接返回,不进行优化
|
||||
if (stream->type != STREAM_REDISTRIBUTE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化限制计划
|
||||
Limit *limit = NULL;
|
||||
|
||||
// 获取流计划的左子树
|
||||
Plan *sub = stream->scan.plan.lefttree;
|
||||
|
||||
// 如果左子树是Limit计划,将其赋值给limit
|
||||
if (IsA(sub, Limit)) {
|
||||
limit = (Limit *)sub;
|
||||
} else if (IsA(sub, SubqueryScan)) {
|
||||
}
|
||||
// 如果左子树是SubqueryScan计划,尝试获取其子计划的Limit计划
|
||||
else if (IsA(sub, SubqueryScan)) {
|
||||
limit = (Limit *)((SubqueryScan *)sub)->subplan;
|
||||
}
|
||||
|
||||
// 断言确保limit不为空
|
||||
Assert(limit != NULL);
|
||||
|
||||
// 如果limit不是Limit类型,直接返回,不进行优化
|
||||
if (!IsA(limit, Limit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取下一个流计划,它应该是一个广播流计划
|
||||
Stream *next_stream = (Stream *)limit->plan.lefttree;
|
||||
|
||||
// 断言确保next_stream不为空
|
||||
Assert(next_stream != NULL);
|
||||
/* just work with double stream limit */
|
||||
|
||||
// 仅处理双重流限制情况
|
||||
if (!IsA(next_stream, Stream) || next_stream->type != STREAM_BROADCAST) {
|
||||
return;
|
||||
}
|
||||
/* actually we dont care what type the next_limit is */
|
||||
|
||||
// 获取下一个Limit计划,实际上不关心其类型
|
||||
Limit *next_limit = (Limit *)next_stream->scan.plan.lefttree;
|
||||
|
||||
// 断言确保next_limit不为空
|
||||
Assert(next_limit != NULL);
|
||||
|
||||
// 将流计划的左子树替换为下一个Limit计划
|
||||
stream->scan.plan.lefttree = (Plan *)next_limit;
|
||||
|
||||
// 更新下一个Limit计划的限制条件
|
||||
next_limit->limitCount = limit->limitCount;
|
||||
next_limit->limitOffset = limit->limitOffset;
|
||||
|
||||
// 释放内存资源
|
||||
pfree_ext(next_stream);
|
||||
pfree_ext(limit);
|
||||
pfree_ext(stream->scan.plan.exec_nodes);
|
||||
|
||||
// 复制下一个Limit计划的执行节点信息,并更新到流计划中
|
||||
stream->scan.plan.exec_nodes = (ExecNodes *)copyObject(next_limit->plan.exec_nodes);
|
||||
}
|
||||
|
||||
|
||||
/* delete double stream limit */
|
||||
// 删除附加计划中的冗余流
|
||||
void delete_redundant_streams_of_append_plan(const Append *append)
|
||||
{
|
||||
// 如果输入不是Append类型的计划,直接返回
|
||||
if (!IsA(append, Append)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 分配并初始化一个RedundantStreamInfo结构
|
||||
RedundantStreamInfo *redundant_info = (RedundantStreamInfo *)palloc0(sizeof(RedundantStreamInfo));
|
||||
|
||||
// 查找附加计划中的冗余流信息
|
||||
lookup_redundant_streams_of_append_plan(append, redundant_info);
|
||||
|
||||
// 如果冗余流类型为EXTRA_OTHER_CASE,直接返回
|
||||
if (redundant_info->redundant_stream_type == EXTRA_OTHER_CASE) {
|
||||
pfree_ext(redundant_info);
|
||||
return;
|
||||
}
|
||||
/* we deal with double stream limit here, other case not support yet */
|
||||
if (redundant_info->redundant_stream_type != REDUNDANT_ONE_STREAM_LIMIT_AND_ONE_SUBQUERY_SCAN &&
|
||||
|
||||
// 仅处理双重流限制和多个流上限的情况,其他情况不支持
|
||||
if (redundant_info->redundant_stream_type != REDUNDANT_ONE_STREAM_LIMIT_AND_ONE_SUBQUERY_SCAN &&
|
||||
redundant_info->redundant_stream_type != REDUNDANT_MANY_STREAM_LIMITS) {
|
||||
pfree_ext(redundant_info);
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历附加计划中的子计划
|
||||
ListCell *subplan;
|
||||
foreach(subplan, append->appendplans) {
|
||||
Plan *plan = (Plan *)lfirst(subplan);
|
||||
|
||||
// 根据不同的计划类型进行处理
|
||||
switch (nodeTag(plan)) {
|
||||
case T_Stream: {
|
||||
// 如果是流计划,进行流计划优化
|
||||
Stream *stream = (Stream *)plan;
|
||||
optimize_stream_plan(stream);
|
||||
break;
|
||||
|
|
@ -462,73 +696,109 @@ void delete_redundant_streams_of_append_plan(const Append *append)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 释放RedundantStreamInfo结构的内存资源
|
||||
pfree_ext(redundant_info);
|
||||
}
|
||||
|
||||
|
||||
// 复制节点列表
|
||||
void copy_nodelist(List *dst_node_list, const List *src_node_list)
|
||||
{
|
||||
// 断言确保目标节点列表和源节点列表都不为空
|
||||
Assert(dst_node_list != NULL);
|
||||
Assert(src_node_list != NULL);
|
||||
|
||||
// 初始化目标节点列表和源节点列表的迭代器
|
||||
ListCell *dst_cell;
|
||||
ListCell *src_cell;
|
||||
|
||||
// 将目标节点列表的长度扩展到与源节点列表相同
|
||||
while (list_length(dst_node_list) < list_length(src_node_list)) {
|
||||
(void)lappend_int(dst_node_list, -1);
|
||||
(void)lappend_int(dst_node_list, -1); // 添加一个占位符值为-1
|
||||
}
|
||||
|
||||
// 如果目标节点列表的长度大于源节点列表,则删除多余的元素
|
||||
while (list_length(dst_node_list) > list_length(src_node_list)) {
|
||||
(void)list_delete_first(dst_node_list);
|
||||
(void)list_delete_first(dst_node_list); // 删除目标节点列表的第一个元素
|
||||
}
|
||||
|
||||
// 遍历目标节点列表和源节点列表,逐个复制值
|
||||
forboth(dst_cell, dst_node_list, src_cell, src_node_list) {
|
||||
lfirst_int(dst_cell) = lfirst_int(src_cell);
|
||||
lfirst_int(dst_cell) = lfirst_int(src_cell); // 复制源节点列表的值到目标节点列表
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 复制执行节点信息
|
||||
void copy_exec_nodes(ExecNodes *dst_exec_nodes, const ExecNodes *src_exec_nodes)
|
||||
{
|
||||
// 断言确保目标执行节点信息和源执行节点信息都不为空
|
||||
Assert(dst_exec_nodes != NULL);
|
||||
Assert(src_exec_nodes != NULL);
|
||||
|
||||
// 复制源执行节点信息的基本定位类型到目标执行节点信息
|
||||
dst_exec_nodes->baselocatortype = src_exec_nodes->baselocatortype;
|
||||
|
||||
// 复制源执行节点信息的节点列表到目标执行节点信息
|
||||
copy_nodelist(dst_exec_nodes->nodeList, src_exec_nodes->nodeList);
|
||||
}
|
||||
|
||||
|
||||
// 删除一个流计划和一个子查询计划的冗余情况
|
||||
void delete_redundant_case_of_one_stream_and_one_subquery(const RemoteQuery *top_plan)
|
||||
{
|
||||
// 获取顶层计划中的基础结果
|
||||
BaseResult *result = (BaseResult *)top_plan->scan.plan.lefttree;
|
||||
Assert(result != NULL);
|
||||
|
||||
// 获取基础结果中的附加计划
|
||||
Append *append = (Append *)result->plan.lefttree;
|
||||
Assert(append != NULL);
|
||||
|
||||
ListCell *subplan;
|
||||
ListCell *stream_subplan = NULL;
|
||||
ListCell *subquery_subplan = NULL;
|
||||
|
||||
// 遍历附加计划中的子计划
|
||||
foreach(subplan, append->appendplans) {
|
||||
Plan *sub = (Plan *)lfirst(subplan);
|
||||
|
||||
// 根据子计划的类型进行处理
|
||||
switch (nodeTag(sub)) {
|
||||
case T_Stream: {
|
||||
// 如果子计划是流计划,记录下流计划的位置
|
||||
stream_subplan = subplan;
|
||||
break;
|
||||
}
|
||||
case T_SubqueryScan: {
|
||||
// 如果子计划是子查询扫描计划,记录下子查询扫描计划的位置
|
||||
subquery_subplan = subplan;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// 如果子计划不是流计划或子查询扫描计划,直接返回,不进行处理
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到流计划或子查询扫描计划,直接返回,不进行处理
|
||||
if (stream_subplan == NULL || subquery_subplan == NULL) {
|
||||
return;
|
||||
}
|
||||
/* delete redundant stream node */
|
||||
|
||||
// 删除冗余的流计划节点
|
||||
Stream *stream = (Stream *)lfirst(stream_subplan);
|
||||
Plan *plan = stream->scan.plan.lefttree;
|
||||
Assert(list_length(plan->exec_nodes->nodeList) == 1);
|
||||
Assert(list_length(plan->exec_nodes->nodeList) == 1); // 确保只有一个执行节点
|
||||
pfree_ext(stream);
|
||||
lfirst(stream_subplan) = (Plan *)plan;
|
||||
|
||||
/* modify execnode of brother node */
|
||||
// 修改兄弟节点的执行节点信息
|
||||
const ExecNodes *src_exec_nodes = plan->exec_nodes;
|
||||
|
||||
/* let this query exec on single node of src_exec_nodes */
|
||||
// 让子查询扫描计划和其子计划以单个节点执行
|
||||
SubqueryScan *subquery = (SubqueryScan *)lfirst(subquery_subplan);
|
||||
pfree_ext(subquery->scan.plan.qual);
|
||||
copy_exec_nodes(subquery->scan.plan.exec_nodes, src_exec_nodes);
|
||||
|
|
@ -536,50 +806,71 @@ void delete_redundant_case_of_one_stream_and_one_subquery(const RemoteQuery *top
|
|||
BaseResult *sub_result = (BaseResult *)subquery->subplan;
|
||||
copy_exec_nodes(sub_result->plan.exec_nodes, src_exec_nodes);
|
||||
|
||||
/* modify exec_node of father node */
|
||||
// 修改父节点的执行节点信息
|
||||
copy_exec_nodes(append->plan.exec_nodes, src_exec_nodes);
|
||||
copy_exec_nodes(result->plan.exec_nodes, src_exec_nodes);
|
||||
copy_exec_nodes(top_plan->exec_nodes, src_exec_nodes);
|
||||
}
|
||||
|
||||
|
||||
// 删除多个流计划的冗余情况
|
||||
void delete_redundant_case_of_many_stream(const RemoteQuery *top_plan)
|
||||
{
|
||||
// 获取顶层计划中的基础结果
|
||||
BaseResult *result = (BaseResult *)top_plan->scan.plan.lefttree;
|
||||
Assert(result != NULL);
|
||||
|
||||
// 获取基础结果中的附加计划
|
||||
Append *append = (Append *)result->plan.lefttree;
|
||||
Assert(append != NULL);
|
||||
|
||||
ListCell *subplan;
|
||||
ExecNodes *src_exec_nodes = NULL;
|
||||
|
||||
// 遍历附加计划中的子计划
|
||||
foreach(subplan, append->appendplans) {
|
||||
Plan *sub = (Plan *)lfirst(subplan);
|
||||
|
||||
// 根据子计划的类型进行处理
|
||||
switch (nodeTag(sub)) {
|
||||
case T_Stream: {
|
||||
// 如果子计划是流计划
|
||||
Stream *stream = (Stream *)lfirst(subplan);
|
||||
Plan *plan = stream->scan.plan.lefttree;
|
||||
Assert(list_length(plan->exec_nodes->nodeList) == 1);
|
||||
Assert(list_length(plan->exec_nodes->nodeList) == 1); // 确保只有一个执行节点
|
||||
|
||||
// 删除冗余的流计划节点
|
||||
pfree_ext(stream);
|
||||
lfirst(subplan) = (Plan *)plan;
|
||||
|
||||
// 如果src_exec_nodes为空,将其初始化为流计划的执行节点信息
|
||||
if (src_exec_nodes == NULL) {
|
||||
src_exec_nodes = (ExecNodes *)copyObject(plan->exec_nodes);
|
||||
} else {
|
||||
}
|
||||
// 否则,将流计划的执行节点信息合并到src_exec_nodes中
|
||||
else {
|
||||
(void)list_concat_unique_int(src_exec_nodes->nodeList, plan->exec_nodes->nodeList);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
/* we should never go here */
|
||||
/* 我们不应该到达这里 */
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 断言确保src_exec_nodes不为空
|
||||
Assert(src_exec_nodes != NULL);
|
||||
/* modify exec_node of father node */
|
||||
|
||||
// 修改父节点的执行节点信息
|
||||
copy_exec_nodes(append->plan.exec_nodes, src_exec_nodes);
|
||||
copy_exec_nodes(result->plan.exec_nodes, src_exec_nodes);
|
||||
copy_exec_nodes(top_plan->exec_nodes, src_exec_nodes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* for now, we support the specific union all sql which are list here
|
||||
*
|
||||
|
|
@ -600,36 +891,55 @@ void delete_redundant_case_of_many_stream(const RemoteQuery *top_plan)
|
|||
* UNION ALL
|
||||
* SELECT * FROM ( SELECT 1 FROM t WHERE t.distribute = 'yyy' );
|
||||
*/
|
||||
// 删除RemoteQuery计划中的冗余流节点
|
||||
void delete_redundant_streams_of_remotequery(RemoteQuery *top_plan)
|
||||
{
|
||||
// 如果输入的top_plan为空或不是RemoteQuery类型的计划,直接返回
|
||||
if (top_plan == NULL || !IsA(top_plan, RemoteQuery)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取顶层计划中的基础结果
|
||||
BaseResult *result = (BaseResult *)top_plan->scan.plan.lefttree;
|
||||
|
||||
// 如果基础结果为空或不是BaseResult类型的计划,直接返回
|
||||
if (result == NULL || !IsA(result, BaseResult)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取基础结果中的附加计划
|
||||
Append *append = (Append *)result->plan.lefttree;
|
||||
|
||||
// 如果附加计划为空或不是Append类型的计划,直接返回
|
||||
if (append == NULL || !IsA(append, Append)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 分配并初始化一个RedundantStreamInfo结构
|
||||
RedundantStreamInfo *redundant_info = (RedundantStreamInfo *)palloc0(sizeof(RedundantStreamInfo));
|
||||
|
||||
// 查找附加计划中的冗余流信息
|
||||
lookup_redundant_streams_of_append_plan(append, redundant_info);
|
||||
|
||||
// 如果冗余流类型为EXTRA_OTHER_CASE,直接返回
|
||||
if (redundant_info->redundant_stream_type == EXTRA_OTHER_CASE) {
|
||||
pfree_ext(redundant_info);
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据不同的冗余流类型进行不同的处理
|
||||
switch (redundant_info->redundant_stream_type) {
|
||||
case REDUNDANT_ONE_STREAM_AND_ONE_SUBQUERY_SCAN:
|
||||
/* fallthrough */
|
||||
/* do same thing, delete stream node, and modify execnodes of stream's brother and father to stream's son */
|
||||
case REDUNDANT_ONE_STREAM_LIMIT_AND_ONE_SUBQUERY_SCAN: {
|
||||
// 删除一个流计划和一个子查询计划的冗余情况
|
||||
delete_redundant_case_of_one_stream_and_one_subquery(top_plan);
|
||||
break;
|
||||
}
|
||||
case REDUNDANT_MANY_STREAM_LIMITS:
|
||||
case REDUNDANT_MANY_STREAMS:
|
||||
/* fallthrough */
|
||||
/* do same thing, delete stream node, and modify execnodes of stream's father to stream's son */
|
||||
case REDUNDANT_MANY_STREAMS: {
|
||||
case REDUNDANT_MANY_STREAM_LIMITS: {
|
||||
// 删除多个流计划的冗余情况
|
||||
delete_redundant_case_of_many_stream(top_plan);
|
||||
break;
|
||||
}
|
||||
|
|
@ -637,5 +947,7 @@ void delete_redundant_streams_of_remotequery(RemoteQuery *top_plan)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 释放RedundantStreamInfo结构的内存资源
|
||||
pfree_ext(redundant_info);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,24 +34,25 @@
|
|||
|
||||
/* only operator with qual supporting can use hashfilter */
|
||||
static int g_support_hashfilter_types[] = {
|
||||
T_SeqScan,
|
||||
T_CStoreScan,
|
||||
T_DfsScan,
|
||||
T_SeqScan, // 顺序扫描操作
|
||||
T_CStoreScan, // 列存储扫描操作
|
||||
T_DfsScan, // 分布式文件系统(DFS)扫描操作
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
T_TsStoreScan,
|
||||
T_TsStoreScan, // 时间序列存储扫描操作 (仅在启用多节点时可用)
|
||||
#endif /* ENABLE_MULTIPLE_NODES */
|
||||
T_ForeignScan,
|
||||
T_IndexScan,
|
||||
T_IndexOnlyScan,
|
||||
T_CStoreIndexScan,
|
||||
T_DfsIndexScan,
|
||||
T_TidScan,
|
||||
T_SubqueryScan,
|
||||
T_BitmapHeapScan,
|
||||
T_CStoreIndexHeapScan,
|
||||
T_CteScan
|
||||
T_ForeignScan, // 外部表扫描操作
|
||||
T_IndexScan, // 索引扫描操作
|
||||
T_IndexOnlyScan, // 仅索引扫描操作
|
||||
T_CStoreIndexScan, // 列存储索引扫描操作
|
||||
T_DfsIndexScan, // 分布式文件系统索引扫描操作
|
||||
T_TidScan, // 行标识符(TID)扫描操作
|
||||
T_SubqueryScan, // 子查询扫描操作
|
||||
T_BitmapHeapScan, // 位图堆扫描操作
|
||||
T_CStoreIndexHeapScan, // 列存储索引堆扫描操作
|
||||
T_CteScan // 通用表达式(CTE)扫描操作
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Simple Query like:
|
||||
* select version();
|
||||
|
|
@ -74,27 +75,46 @@ void output_unshipped_log()
|
|||
return;
|
||||
}
|
||||
|
||||
// 关闭流式处理的函数
|
||||
void set_stream_off()
|
||||
{
|
||||
// 将is_stream标志设置为false,表示关闭流式处理
|
||||
u_sess->opt_cxt.is_stream = false;
|
||||
}
|
||||
|
||||
|
||||
// 检查流式处理支持的函数
|
||||
bool check_stream_support()
|
||||
{
|
||||
// 返回is_stream_support标志的值,用于检查是否支持流式处理
|
||||
return u_sess->opt_cxt.is_stream_support;
|
||||
}
|
||||
|
||||
|
||||
/* Init the contain_func_context */
|
||||
/**
|
||||
* 初始化包含函数上下文的函数
|
||||
*
|
||||
* @ param funcids 包含的函数的ID列表
|
||||
* @ param find_all 是否查找所有匹配的函数
|
||||
*
|
||||
* @ return 包含函数上下文的初始化结果
|
||||
*/
|
||||
contain_func_context init_contain_func_context(List* funcids, bool find_all)
|
||||
{
|
||||
// 创建一个包含函数上下文的结构体变量
|
||||
contain_func_context context;
|
||||
context.funcids = funcids;
|
||||
context.func_exprs = NIL;
|
||||
context.find_all = find_all;
|
||||
|
||||
// 设置包含函数上下文的成员变量
|
||||
context.funcids = funcids; // 包含的函数的ID列表
|
||||
context.func_exprs = NIL; // 初始化为空列表
|
||||
context.find_all = find_all; // 是否查找所有匹配的函数
|
||||
|
||||
// 返回初始化后的包含函数上下文
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
ExecNodes* get_all_data_nodes(char locatortype)
|
||||
{
|
||||
ExecNodes* exec_nodes = ng_get_installation_group_exec_node();
|
||||
|
|
@ -105,34 +125,52 @@ ExecNodes* get_all_data_nodes(char locatortype)
|
|||
/*
|
||||
* Return a random index of datanode in current plan node's execution nodegroup
|
||||
*/
|
||||
/**
|
||||
* 从执行计划中随机选择一个数据节点的标识符
|
||||
*
|
||||
* @ param plan 要选择节点的执行计划
|
||||
* @ return 选择的节点的标识符
|
||||
*/
|
||||
int pickup_random_datanode_from_plan(Plan* plan)
|
||||
{
|
||||
/* Randomly pickup a node from target nodegroup */
|
||||
|
||||
int nodeId = 0;
|
||||
int nodeId = 0; // 用于存储选择的节点的标识符
|
||||
|
||||
// 获取目标执行节点(Datanode)
|
||||
ExecNodes* target_execnodes = ng_get_dest_execnodes(plan);
|
||||
|
||||
// 使用断言(Assert)和错误报告(Ereport)检查目标执行节点和节点列表是否为空
|
||||
AssertEreport(NULL != target_execnodes && NIL != target_execnodes->nodeList,
|
||||
MOD_OPT,
|
||||
"The target_execnodes or the node list is NULL");
|
||||
MOD_OPT,
|
||||
"The target_execnodes or the node list is NULL");
|
||||
|
||||
// 如果目标执行节点和节点列表都不为空,则从节点列表中随机选择一个节点
|
||||
if (NULL != target_execnodes && NIL != target_execnodes->nodeList) {
|
||||
int random = pickup_random_datanode(list_length(target_execnodes->nodeList));
|
||||
nodeId = list_nth_int(target_execnodes->nodeList, random);
|
||||
} else {
|
||||
// 如果目标执行节点或节点列表为空,则从所有数据节点中随机选择一个节点
|
||||
nodeId = pickup_random_datanode(u_sess->pgxc_cxt.NumDataNodes);
|
||||
}
|
||||
|
||||
return nodeId;
|
||||
return nodeId; // 返回选择的节点的标识符
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从计划中获取一个随机的数据节点,构建一个包含该节点的 ExecNodes 结构体并返回。
|
||||
*/
|
||||
ExecNodes* get_random_data_nodes(char locatortype, Plan* plan)
|
||||
{
|
||||
// 从计划中随机选择一个数据节点
|
||||
int nodeId = pickup_random_datanode_from_plan(plan);
|
||||
|
||||
// 获取目标数据分布
|
||||
Distribution* distribution = ng_get_dest_distribution(plan);
|
||||
|
||||
// 创建一个新的 ExecNodes 结构体
|
||||
ExecNodes* execNodes = makeNode(ExecNodes);
|
||||
|
||||
// 初始化 ExecNodes 结构体的各个字段
|
||||
execNodes->primarynodelist = NIL;
|
||||
execNodes->nodeList = list_make1_int(nodeId);
|
||||
ng_copy_distribution(&execNodes->distribution, distribution);
|
||||
|
|
@ -142,6 +180,7 @@ ExecNodes* get_random_data_nodes(char locatortype, Plan* plan)
|
|||
execNodes->accesstype = RELATION_ACCESS_READ;
|
||||
execNodes->en_dist_vars = NIL;
|
||||
|
||||
// 检查 plan->exec_nodes 是否为 NULL,如果是,抛出错误
|
||||
if (plan->exec_nodes == NULL) {
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
|
|
@ -149,9 +188,10 @@ ExecNodes* get_random_data_nodes(char locatortype, Plan* plan)
|
|||
errmsg("Invalid plan->exec_nodes object when get random data nodes.")));
|
||||
}
|
||||
|
||||
return execNodes;
|
||||
return execNodes; // 返回包含随机数据节点信息的 ExecNodes 结构体
|
||||
}
|
||||
|
||||
|
||||
/* If the nodeType of subplan support hashfilter return true, otherwise return false. */
|
||||
static bool is_support_hashfilter(int nodeType)
|
||||
{
|
||||
|
|
@ -163,12 +203,15 @@ static bool is_support_hashfilter(int nodeType)
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为复制操作添加哈希过滤器
|
||||
*/
|
||||
bool add_hashfilter_for_replication(PlannerInfo* root, Plan* plan, List* distribute_keys)
|
||||
{
|
||||
HashFilter* hashfilter = NULL;
|
||||
List* typeOidList = NIL;
|
||||
ListCell* key = NULL;
|
||||
Plan* tmpplan = NULL;
|
||||
HashFilter* hashfilter = NULL; // 哈希过滤器
|
||||
List* typeOidList = NIL; // 类型OID列表
|
||||
ListCell* key = NULL; // 列表元素迭代器
|
||||
Plan* tmpplan = NULL; // 临时执行计划指针
|
||||
|
||||
if (NULL == plan)
|
||||
return false;
|
||||
|
|
@ -176,80 +219,91 @@ bool add_hashfilter_for_replication(PlannerInfo* root, Plan* plan, List* distrib
|
|||
AssertEreport(NIL != distribute_keys, MOD_OPT, "The distribute keys are NIL");
|
||||
|
||||
/*
|
||||
* If plan node type is PartIterator, it should add hashfilter in the lefttree
|
||||
* 如果计划节点类型是PartIterator,应该在左子树中添加哈希过滤器
|
||||
*/
|
||||
tmpplan = plan;
|
||||
if (IsA(tmpplan, PartIterator))
|
||||
tmpplan = tmpplan->lefttree;
|
||||
if (IsA(tmpplan, DfsIndexScan))
|
||||
tmpplan = (Plan*)((DfsIndexScan*)tmpplan)->dfsScan;
|
||||
|
||||
if (IsA(tmpplan, Append)) {
|
||||
ListCell* appendPlan = NULL;
|
||||
|
||||
/* First check if all the children of append are satisfied */
|
||||
/* 首先检查附加节点的所有子节点是否都支持哈希过滤器 */
|
||||
foreach (appendPlan, ((Append*)tmpplan)->appendplans) {
|
||||
Plan* insidePlan = (Plan*)lfirst(appendPlan);
|
||||
|
||||
/* Once there is one node which doest not support hashfilter, then return false */
|
||||
/* 一旦有一个节点不支持哈希过滤器,则返回false */
|
||||
if (!is_support_hashfilter(nodeTag(insidePlan)))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Second add hash filter for each child node */
|
||||
/* 然后为每个子节点添加哈希过滤器 */
|
||||
foreach (appendPlan, ((Append*)tmpplan)->appendplans) {
|
||||
Plan* insidePlan = (Plan*)lfirst(appendPlan);
|
||||
(void)add_hashfilter_for_replication(root, insidePlan, distribute_keys);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_support_hashfilter(nodeTag(tmpplan)))
|
||||
return false;
|
||||
|
||||
/* Add every typeOid of distribute_keys into typeOidList */
|
||||
/* 将distribute_keys中的每个类型OID添加到typeOidList中 */
|
||||
foreach (key, distribute_keys) {
|
||||
Node* distkey = (Node*)lfirst(key);
|
||||
typeOidList = lappend_oid(typeOidList, exprType(distkey));
|
||||
}
|
||||
|
||||
/* Make Hashfilter expr node */
|
||||
/* 创建哈希过滤器表达式节点 */
|
||||
AssertEreport(plan->exec_nodes && plan->exec_nodes->nodeList, MOD_OPT, "The exec nodes or node list is NULL");
|
||||
List* nodeList = list_copy(plan->exec_nodes->nodeList);
|
||||
hashfilter = makeHashFilter(distribute_keys, typeOidList, nodeList);
|
||||
tmpplan->hasHashFilter = true;
|
||||
|
||||
/* Append Hashfilter expr to qual of tmpplan */
|
||||
/* 将哈希过滤器表达式附加到tmpplan的qual中 */
|
||||
tmpplan->qual = lappend(tmpplan->qual, hashfilter);
|
||||
|
||||
/* Set exec node list and Distribution */
|
||||
/* 设置执行节点列表和分布信息 */
|
||||
Distribution* distribution = ng_get_dest_distribution(tmpplan);
|
||||
tmpplan->exec_nodes = ng_convert_to_exec_nodes(distribution, LOCATOR_TYPE_HASH, RELATION_ACCESS_READ);
|
||||
tmpplan->exec_nodes->nodeList = list_copy(nodeList);
|
||||
|
||||
/* estimate plan rows for hashfilter. */
|
||||
/* 为哈希过滤器估算计划的行数 */
|
||||
tmpplan->plan_rows = PLAN_LOCAL_ROWS(tmpplan);
|
||||
tmpplan->multiple = get_multiple_by_distkey(root, distribute_keys, tmpplan->plan_rows);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为流式连接计划进行调整,以适应分布式查询的要求
|
||||
*/
|
||||
void stream_join_plan(PlannerInfo* root, Plan* join_plan, JoinPath* join_path)
|
||||
{
|
||||
Plan* inner_plan = innerPlan(join_plan);
|
||||
Plan* outer_plan = outerPlan(join_plan);
|
||||
|
||||
// 检查内部计划和外部计划是否在数据节点上执行,如果是,则需要进一步调整计划
|
||||
if (is_execute_on_datanodes(inner_plan) || is_execute_on_datanodes(outer_plan)) {
|
||||
Plan* child_plan = NULL;
|
||||
|
||||
// 如果内部计划在协调器上执行,需要检查外部计划的类型
|
||||
if (is_execute_on_coordinator(inner_plan)) {
|
||||
List* outerpathkeys = NIL;
|
||||
|
||||
// 如果外部计划是流式计划,获取其子计划作为新的外部计划
|
||||
if (IsA(outer_plan, Stream))
|
||||
child_plan = outerPlan(outer_plan);
|
||||
else
|
||||
child_plan = outer_plan;
|
||||
|
||||
// 创建一个简单的RemoteQuery计划作为新的外部计划
|
||||
outer_plan = make_simple_RemoteQuery(child_plan, root, false);
|
||||
|
||||
// 如果连接计划是MergeJoin,根据外部路径键进行排序
|
||||
if (IsA(join_plan, MergeJoin)) {
|
||||
MergePath* merge_path = (MergePath*)join_path;
|
||||
|
||||
|
|
@ -259,7 +313,9 @@ void stream_join_plan(PlannerInfo* root, Plan* join_plan, JoinPath* join_path)
|
|||
outerpathkeys = merge_path->jpath.outerjoinpath->pathkeys;
|
||||
|
||||
outer_plan = (Plan*)make_sort_from_pathkeys(root, outer_plan, outerpathkeys, -1.0);
|
||||
} else if (IsA(join_plan, NestLoop) && join_path->path.pathkeys) {
|
||||
}
|
||||
// 如果连接计划是NestLoop且有路径键,根据路径键进行排序
|
||||
else if (IsA(join_plan, NestLoop) && join_path->path.pathkeys) {
|
||||
AssertEreport(join_path->outerjoinpath->pathkeys, MOD_OPT, "The outer join path keys is NULL");
|
||||
|
||||
outerpathkeys = join_path->path.pathkeys;
|
||||
|
|
@ -267,28 +323,34 @@ void stream_join_plan(PlannerInfo* root, Plan* join_plan, JoinPath* join_path)
|
|||
}
|
||||
|
||||
outerPlan(join_plan) = outer_plan;
|
||||
} else if (is_execute_on_coordinator(outer_plan)) {
|
||||
}
|
||||
// 如果外部计划在协调器上执行,需要检查连接计划类型并调整内部计划
|
||||
else if (is_execute_on_coordinator(outer_plan)) {
|
||||
if (IsA(join_plan, HashJoin)) {
|
||||
AssertEreport(IsA(inner_plan, Hash), MOD_OPT, "The inner_plan is NOT a Hash");
|
||||
|
||||
// 如果内部计划的外部计划是流式计划,获取其子计划作为新的内部计划
|
||||
if (IsA(outerPlan(inner_plan), Stream))
|
||||
child_plan = outerPlan(outerPlan(inner_plan));
|
||||
else
|
||||
child_plan = outerPlan(inner_plan);
|
||||
|
||||
// 创建一个简单的RemoteQuery计划作为新的内部计划
|
||||
outerPlan(inner_plan) = make_simple_RemoteQuery(child_plan, root, false);
|
||||
|
||||
/* Modify locator information again */
|
||||
// 再次修改定位信息
|
||||
inherit_plan_locator_info(inner_plan, outerPlan(inner_plan));
|
||||
} else {
|
||||
List* innerpathkeys = NIL;
|
||||
|
||||
// 如果内部计划是流式计划,获取其子计划作为新的内部计划
|
||||
if (IsA(inner_plan, Stream))
|
||||
child_plan = outerPlan(inner_plan);
|
||||
else
|
||||
child_plan = inner_plan;
|
||||
inner_plan = make_simple_RemoteQuery(child_plan, root, false);
|
||||
|
||||
// 如果连接计划是MergeJoin,根据内部路径键进行排序
|
||||
if (IsA(join_plan, MergeJoin)) {
|
||||
MergePath* merge_path = (MergePath*)join_path;
|
||||
|
||||
|
|
@ -308,10 +370,10 @@ void stream_join_plan(PlannerInfo* root, Plan* join_plan, JoinPath* join_path)
|
|||
}
|
||||
}
|
||||
|
||||
// 根据计划的执行位置设置执行类型和执行节点信息
|
||||
if (is_execute_on_coordinator(inner_plan) || is_execute_on_coordinator(outer_plan)) {
|
||||
/*
|
||||
* If one side is executed on coordinator, the other side should be same
|
||||
* after the logic above
|
||||
* 如果一侧在协调器上执行,另一侧应该保持一致
|
||||
*/
|
||||
join_plan->exec_type = EXEC_ON_COORDS;
|
||||
Distribution* distribution = ng_get_single_node_group_distribution();
|
||||
|
|
@ -324,16 +386,15 @@ void stream_join_plan(PlannerInfo* root, Plan* join_plan, JoinPath* join_path)
|
|||
join_plan->exec_nodes = stream_merge_exec_nodes(outer_plan, inner_plan, ENABLE_PRED_PUSH(root));
|
||||
}
|
||||
|
||||
// 如果是HashJoin,设置streamBothSides标志用于执行HashJoin时的优化
|
||||
if (IsA(join_plan, HashJoin)) {
|
||||
HashJoin* hashjoin = (HashJoin*)join_plan;
|
||||
|
||||
/*
|
||||
* streamBothSides is used in ExecHashJoin for judge if we should probe the first tuple of outer
|
||||
* when both outer and inner don't contain stream or not.
|
||||
*/
|
||||
hashjoin->streamBothSides = contain_special_plan_node(outerPlan(hashjoin), T_Stream) ||
|
||||
contain_special_plan_node(innerPlan(hashjoin), T_Stream);
|
||||
} else if (IsA(join_plan, NestLoop)) {
|
||||
}
|
||||
// 如果是NestLoop,设置materialAll标志用于优化
|
||||
else if (IsA(join_plan, NestLoop)) {
|
||||
NestLoop* nestloop = (NestLoop*)join_plan;
|
||||
|
||||
if (nestloop->nestParams && IsA(nestloop->join.plan.righttree, RemoteQuery)) {
|
||||
|
|
@ -348,9 +409,11 @@ void stream_join_plan(PlannerInfo* root, Plan* join_plan, JoinPath* join_path)
|
|||
contain_special_plan_node(innerPlan(nestloop), T_Stream);
|
||||
}
|
||||
|
||||
// 设置连接计划的分布键信息
|
||||
join_plan->distributed_keys = join_path->path.distribute_keys;
|
||||
}
|
||||
|
||||
|
||||
void inherit_plan_locator_info(Plan* plan, Plan* subplan)
|
||||
{
|
||||
plan->exec_nodes = ng_get_dest_execnodes(subplan);
|
||||
|
|
|
|||
|
|
@ -72,123 +72,179 @@
|
|||
#include "utils/rel.h"
|
||||
#include "utils/rel_gs.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
void set_default_stream()
|
||||
/*
|
||||
这段代码的作用是根据当前的数据库操作情况以及查询的并行度来决定是否启用流式查询,
|
||||
并相应地设置流式查询的标志位。流式查询通常用于在多线程环境下执行查询,以提高查询性能。
|
||||
*/
|
||||
void set_default_stream()//函数用于设置默认的流式查询参数。
|
||||
{
|
||||
/* initdb could not use smp */
|
||||
// 如果当前操作是初始化数据库(IsInitdb),则不能使用多线程并行(smp)
|
||||
if (IsInitdb) {
|
||||
// 在初始化数据库操作中,流式查询被禁用,所以将相关标志位设置为false
|
||||
u_sess->opt_cxt.is_stream = false;
|
||||
u_sess->opt_cxt.is_stream_support = false;
|
||||
} else {
|
||||
// 如果不是初始化数据库操作
|
||||
// 根据查询的并行度(query_dop)来确定是否支持流式查询
|
||||
// 如果并行度大于1,表示支持流式查询,将相关标志位设置为true
|
||||
u_sess->opt_cxt.is_stream = (u_sess->opt_cxt.query_dop > 1);
|
||||
u_sess->opt_cxt.is_stream_support = (u_sess->opt_cxt.query_dop > 1);
|
||||
}
|
||||
}
|
||||
|
||||
int2vector* get_baserel_distributekey_no(Oid relid)
|
||||
|
||||
int2vector* get_baserel_distributekey_no(Oid relid)//获取指定关系的分布键,分布键是用于将数据分布到不同节点上的一组属性或属性组合。
|
||||
{
|
||||
/* while smp is not allowed, no need to generate distribute key */
|
||||
/* 当不支持多线程并行时,不需要生成分布键 */
|
||||
if (!check_stream_support()) {
|
||||
// 如果不支持多线程并行(流式查询),则返回NULL,表示不生成分布键
|
||||
return NULL;
|
||||
}
|
||||
AttrNumber attnum = 1;
|
||||
while (true) {
|
||||
HeapTuple tp;
|
||||
Form_pg_attribute att_tup;
|
||||
|
||||
// 在系统缓存中搜索表的属性信息,通过表的Oid和属性编号(attnum)来查找
|
||||
tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(relid), Int16GetDatum(attnum));
|
||||
|
||||
// 如果未找到匹配的元组,说明已经搜索完所有属性,跳出循环
|
||||
if (!HeapTupleIsValid(tp)) {
|
||||
attnum = 0;
|
||||
ReleaseSysCache(tp);
|
||||
break;
|
||||
}
|
||||
|
||||
// 获取属性元组的数据结构
|
||||
att_tup = (Form_pg_attribute)GETSTRUCT(tp);
|
||||
|
||||
// 如果属性没有被标记为已删除(attisdropped),表示该属性是有效的
|
||||
if (!att_tup->attisdropped) {
|
||||
ReleaseSysCache(tp);
|
||||
break;
|
||||
}
|
||||
|
||||
// 增加属性编号,继续搜索下一个属性
|
||||
++attnum;
|
||||
ReleaseSysCache(tp);
|
||||
}
|
||||
|
||||
// 如果attnum等于0,表示没有有效的属性编号,返回NULL
|
||||
if (attnum == 0)
|
||||
return NULL;
|
||||
|
||||
// 创建一个包含一个属性编号的int2vector结构
|
||||
int2 col[1] = { attnum };
|
||||
int2vector* attnumVec = buildint2vector(col, 1);
|
||||
|
||||
// 返回包含属性编号的int2vector
|
||||
return attnumVec;
|
||||
}
|
||||
|
||||
|
||||
/* in build_simple_rel used. Put it all back. Record it */
|
||||
List* build_baserel_distributekey(RangeTblEntry* rte, int relindex)
|
||||
List* build_baserel_distributekey(RangeTblEntry* rte, int relindex)//代码的作用是构建基本关系(表)的分布键列表
|
||||
{
|
||||
// 如果当前是PGXC数据节点(PGXC_DATANODE)或不是PGXC协调器(PGXC_COORDINATOR),
|
||||
// 或者关系的relid为空,或者关系的类型不是RELKIND_RELATION(普通表),
|
||||
// 则返回一个空的List(NIL)
|
||||
if (IS_PGXC_DATANODE || !IS_PGXC_COORDINATOR || !rte->relid || get_rel_relkind(rte->relid) != RELKIND_RELATION)
|
||||
return NIL;
|
||||
|
||||
// 分布特性不受支持,抛出错误
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
|
||||
// 返回一个空的List(NIL)
|
||||
return NIL;
|
||||
}
|
||||
|
||||
Plan* make_simple_RemoteQuery(Plan* lefttree, PlannerInfo* root, bool is_subplan, ExecNodes* target_exec_nodes)
|
||||
|
||||
Plan* make_simple_RemoteQuery(Plan* lefttree, PlannerInfo* root, bool is_subplan, ExecNodes* target_exec_nodes)//这段代码的作用是创建简单的远程查询计划
|
||||
{
|
||||
// 如果全局查询信息(glob)为空,抛出错误
|
||||
if (NULL == root->glob)
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||
(errmsg("Could not find globle planner info when make simple remote query."))));
|
||||
(errmsg("在创建简单远程查询时找不到全局规划器信息。"))));
|
||||
|
||||
// 如果当前查询处于递归内部,直接返回左子树的计划
|
||||
if (root->glob->insideRecursion)
|
||||
return lefttree;
|
||||
|
||||
// 如果左子树计划需要在协调节点上执行(is_execute_on_coordinator),
|
||||
// 或者需要在所有节点上执行(is_execute_on_allnodes),则直接返回左子树的计划
|
||||
if (is_execute_on_coordinator(lefttree) || is_execute_on_allnodes(lefttree))
|
||||
return lefttree;
|
||||
|
||||
// 如果左子树的并行度(dop)大于1,创建一个本地聚集计划(local gather),
|
||||
// 用于将多个并行计划的结果汇总到单个计划中
|
||||
if (lefttree->dop > 1) {
|
||||
lefttree = create_local_gather(lefttree);
|
||||
}
|
||||
|
||||
// 返回左子树的计划,经过可能的修改
|
||||
return lefttree;
|
||||
}
|
||||
|
||||
void add_remote_subplan(PlannerInfo* root, RemoteQuery* result_node)
|
||||
|
||||
void add_remote_subplan(PlannerInfo* root, RemoteQuery* result_node)//添加远程查询子计划
|
||||
{
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
}
|
||||
|
||||
ExecNodes* get_plan_max_ExecNodes(Plan* lefttree, List* subplans)
|
||||
ExecNodes* get_plan_max_ExecNodes(Plan* lefttree, List* subplans)//生成最终的执行节点信息(ExecNodes)并设置分布信息
|
||||
{
|
||||
// 创建一个用于存储最终执行节点信息的ExecNodes结构体指针
|
||||
ExecNodes* final_exec_nodes = NULL;
|
||||
|
||||
// 初始化final_exec_nodes结构体
|
||||
final_exec_nodes = makeNode(ExecNodes);
|
||||
final_exec_nodes->nodeList = NIL;
|
||||
final_exec_nodes->baselocatortype = LOCATOR_TYPE_REPLICATED;
|
||||
final_exec_nodes->accesstype = RELATION_ACCESS_READ;
|
||||
final_exec_nodes->primarynodelist = NIL;
|
||||
final_exec_nodes->en_expr = NULL;
|
||||
final_exec_nodes->nodeList = NIL; // 初始化节点列表为空
|
||||
final_exec_nodes->baselocatortype = LOCATOR_TYPE_REPLICATED; // 设置基本定位器类型为REPLICATED
|
||||
final_exec_nodes->accesstype = RELATION_ACCESS_READ; // 设置访问类型为读取
|
||||
final_exec_nodes->primarynodelist = NIL; // 初始化主节点列表为空
|
||||
final_exec_nodes->en_expr = NULL; // 初始化执行节点表达式为NULL
|
||||
|
||||
// 复制左子树的执行节点列表到final_exec_nodes中
|
||||
final_exec_nodes->nodeList = lefttree->exec_nodes->nodeList;
|
||||
|
||||
/* Set Distribution */
|
||||
Distribution* distribution = ng_convert_to_distribution(final_exec_nodes->nodeList);
|
||||
ng_set_distribution(&final_exec_nodes->distribution, distribution);
|
||||
// 设置分布信息
|
||||
Distribution* distribution = ng_convert_to_distribution(final_exec_nodes->nodeList); // 将节点列表转换为分布信息
|
||||
ng_set_distribution(&final_exec_nodes->distribution, distribution); // 设置final_exec_nodes的分布信息为计算得到的分布信息
|
||||
|
||||
// 返回最终的执行节点信息
|
||||
return final_exec_nodes;
|
||||
}
|
||||
|
||||
|
||||
bool is_replicated_plan(Plan* plan)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_hashed_plan(Plan* plan)
|
||||
bool is_hashed_plan(Plan* plan)//判断一个计划是否为散列计划
|
||||
{
|
||||
// 如果计划是Stream节点类型
|
||||
if (IsA(plan, Stream)) {
|
||||
// 如果是广播流(broadcast_stream)或聚集流(gather_stream),则不是散列计划,返回false
|
||||
if (is_broadcast_stream((Stream*)plan) || is_gather_stream((Stream*)plan))
|
||||
return false;
|
||||
// 如果是重新分布流(redistribute_stream),则是散列计划,返回true
|
||||
else if (is_redistribute_stream((Stream*)plan))
|
||||
return true;
|
||||
} else if (plan->exec_nodes != NULL)
|
||||
}
|
||||
// 如果计划具有执行节点信息(exec_nodes不为空)
|
||||
else if (plan->exec_nodes != NULL)
|
||||
// 检查执行节点的基本定位器类型是否是散列定位器类型,如果是则是散列计划,返回true
|
||||
return IsLocatorDistributedByHash(plan->exec_nodes->baselocatortype);
|
||||
|
||||
// 默认情况下,不是散列计划,返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool is_rangelist_plan(Plan* plan)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -215,72 +271,103 @@ void SerializePlan(Plan* node, PlannedStmt* planned_stmt, StringInfoData* str, i
|
|||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
}
|
||||
|
||||
Plan* mark_distribute_dml(
|
||||
Plan* mark_distribute_dml(//记分布式数据操作语言(DML)的执行计划
|
||||
PlannerInfo* root, Plan** sourceplan, ModifyTable* mt_plan, List** resultRelations, List* mergeActionList)
|
||||
{
|
||||
// 获取子计划,通常是一个修改表计划(ModifyTable)
|
||||
Plan* subplan = *sourceplan;
|
||||
/* We should avoid the ModifyTable Exec on Datanode When it followd by BaseResult */
|
||||
|
||||
/* 当修改表计划(ModifyTable)后跟随一个基本结果计划(BaseResult)时,应避免在数据节点上执行ModifyTable */
|
||||
if (is_single_baseresult_plan(subplan)) {
|
||||
// 继承子计划的定位器信息,将其应用于ModifyTable计划
|
||||
inherit_plan_locator_info((Plan*)mt_plan, *sourceplan);
|
||||
}
|
||||
|
||||
// 返回已经标记了分布信息的修改表计划
|
||||
return (Plan*)mt_plan;
|
||||
}
|
||||
|
||||
/*
|
||||
* Just execute the plan as a replicateion
|
||||
*/
|
||||
static void mark_distribute_setop_allnodes(Plan* plan)
|
||||
static void mark_distribute_setop_allnodes(Plan* plan)//标记一个计划以在所有节点上执行
|
||||
{
|
||||
// 清空计划的分布键列表
|
||||
plan->distributed_keys = NIL;
|
||||
|
||||
// 设置计划的执行类型为在所有节点上执行
|
||||
plan->exec_type = EXEC_ON_ALL_NODES;
|
||||
|
||||
// 获取默认的计算组分布信息
|
||||
Distribution* distribution = ng_get_default_computing_group_distribution();
|
||||
|
||||
// 将分布信息转换为执行节点信息,并设置计划的执行节点信息
|
||||
plan->exec_nodes = ng_convert_to_exec_nodes(distribution, LOCATOR_TYPE_REPLICATED, RELATION_ACCESS_READ);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* We should get distribute key for union all for two case:
|
||||
* 1. All subplan are hash and there have distkey;
|
||||
* 2. Some subplan are hash which have distkey and some are replication.
|
||||
*/
|
||||
static List* get_distkey_for_unionall(List** subPlanKeyArray, int subPlanNum)
|
||||
static List* get_distkey_for_unionall(List** subPlanKeyArray, int subPlanNum)//从一组子计划的分布键数组中获取共享的分布键列表
|
||||
{
|
||||
List* common_diskey = NIL;
|
||||
List* common_diskey = NIL; // 用于存储共享的分布键
|
||||
|
||||
// 遍历子计划的分布键数组
|
||||
for (int i = 0; i < subPlanNum; i++) {
|
||||
// 如果子计划的分布键列表不为空
|
||||
if (subPlanKeyArray[i] != NIL) {
|
||||
// 如果common_diskey为空,将当前子计划的分布键列表赋值给common_diskey
|
||||
if (common_diskey == NIL)
|
||||
common_diskey = subPlanKeyArray[i];
|
||||
// 如果common_diskey不为空,且当前子计划的分布键列表与common_diskey不相等,返回空列表
|
||||
else if (!equal(common_diskey, subPlanKeyArray[i]))
|
||||
return NIL;
|
||||
}
|
||||
}
|
||||
|
||||
/* All the distkey is same, we should get first for setop. */
|
||||
/* 所有子计划的分布键相同,应该返回第一个用于集合操作 */
|
||||
// 如果所有子计划的分布键都相同,返回common_diskey作为共享的分布键
|
||||
return common_diskey;
|
||||
}
|
||||
|
||||
/* For unionall case, with no replicated plan, we should judge if replicated plan can be redistributed */
|
||||
bool judge_redistribute_setop_support(PlannerInfo* root, List* subplanlist, Bitmapset* redistributePlanSet)
|
||||
{
|
||||
Index subPlanIndex = 0;
|
||||
ListCell* lc = NULL;
|
||||
|
||||
/* For unionall case, with no replicated plan, we should judge if replicated plan can be redistributed */
|
||||
bool judge_redistribute_setop_support(PlannerInfo* root, List* subplanlist, Bitmapset* redistributePlanSet)//判断是否支持重新分布的集合操作(如 UNION ALL)
|
||||
{
|
||||
Index subPlanIndex = 0; // 子计划的索引
|
||||
ListCell* lc = NULL; // 遍历子计划列表的迭代器
|
||||
|
||||
// 如果要重新分布的计划集合为空,表示支持重新分布,返回 true
|
||||
if (bms_is_empty(redistributePlanSet))
|
||||
return true;
|
||||
|
||||
// 遍历子计划列表
|
||||
foreach (lc, subplanlist) {
|
||||
// 如果当前子计划的索引在重新分布计划集合中
|
||||
if (bms_is_member(subPlanIndex, redistributePlanSet)) {
|
||||
Plan* subPlan = (Plan*)lfirst(lc);
|
||||
Plan* subPlan = (Plan*)lfirst(lc); // 获取当前子计划
|
||||
|
||||
// 调用 make_distkey_for_append 函数尝试为当前子计划创建分布键
|
||||
if (NULL != make_distkey_for_append(root, subPlan))
|
||||
return true;
|
||||
return true; // 如果成功创建分布键,表示支持重新分布,返回 true
|
||||
else
|
||||
return false;
|
||||
return false; // 如果无法创建分布键,表示不支持重新分布,返回 false
|
||||
}
|
||||
subPlanIndex++;
|
||||
subPlanIndex++; // 增加子计划的索引
|
||||
}
|
||||
|
||||
// 默认情况下,表示支持重新分布,返回 true
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
这段代码的主要功能是为查询计划中的子计划选择合适的数据重分发策略,以优化分布式查询性能。
|
||||
根据不同的情况,它选择是否需要重新分发子计划,并确定重分发的键和目标分布。
|
||||
*/
|
||||
static bool redistributeInfo(PlannerInfo* root, List* subPlans, Plan* plan, List** redistributeKey,
|
||||
Bitmapset** redistributePlanSet, Distribution** redistributeDistribution, bool isunionall, bool canDiskeyChange)
|
||||
{
|
||||
|
|
@ -297,8 +384,8 @@ static bool redistributeInfo(PlannerInfo* root, List* subPlans, Plan* plan, List
|
|||
bool norediskeyplan = false, redistributedplan = false;
|
||||
|
||||
/*
|
||||
* This is the case that no columns needed by append,
|
||||
* but with lower level of colstore table.
|
||||
* 这是一个特殊情况,对于Append操作,不需要任何列,
|
||||
* 但目标表可能具有较低级别的列存储表。
|
||||
*/
|
||||
if (plan->targetlist == NIL && !isunionall)
|
||||
return false;
|
||||
|
|
@ -307,29 +394,28 @@ static bool redistributeInfo(PlannerInfo* root, List* subPlans, Plan* plan, List
|
|||
subPlanKeyArray = (List**)palloc0(sizeof(List*) * subPlanNum);
|
||||
subPlanIndex = 0;
|
||||
|
||||
/* We get the best target node group for set op */
|
||||
/* 获取Set操作的最佳目标节点分组 */
|
||||
Distribution* target_distribution = ng_get_best_setop_distribution(subPlans, isunionall, root->is_correlated);
|
||||
|
||||
/*
|
||||
* Find redistribute key for each subplan.
|
||||
* 为每个子计划查找重分发键。
|
||||
*
|
||||
* We have three kinds of subplan here.
|
||||
* (1) replicate plan.
|
||||
* (2) redistributed plan.
|
||||
* (3) non-replicate plan that has no distribute key possible
|
||||
* 这里有三种类型的子计划。
|
||||
* (1) 复制计划。
|
||||
* (2) 重分发计划。
|
||||
* (3) 无法有分布键的非复制计划。
|
||||
*
|
||||
* Note, we can't have all replicate plan in subplans here, since it has been handled earlier.
|
||||
* Note, there may be all replicate plan in subplans, if their exec nodes have no overlap.
|
||||
* 请注意,这里的子计划不能都是复制计划,因为之前已经处理过它们。
|
||||
* 请注意,如果它们的执行节点没有重叠,子计划中可能全部都是复制计划。
|
||||
*
|
||||
* For union all, we need replicate plan to be redistributed, and find a common redistribute
|
||||
* key as possible (not a must). For non-union all, we need to find a common redistribute
|
||||
* key for all the plans.
|
||||
* 对于Union All操作,我们需要将复制计划重新分发,并尽量找到一个通用的重新分发键(不是必须的)。
|
||||
* 对于非Union All操作,我们需要为所有计划找到一个通用的重新分发键。
|
||||
*/
|
||||
foreach (cell, subPlans) {
|
||||
subPlan = (Plan*)lfirst(cell);
|
||||
Distribution* current_distribution = ng_get_dest_distribution(subPlan);
|
||||
|
||||
/* When the target and the current are all distributed by single node, ignore the distribution keys. */
|
||||
/* 当目标和当前都由单一节点组进行分发时,忽略分发键。 */
|
||||
if (!ng_is_single_node_group_distribution(current_distribution)) {
|
||||
subPlanKeyIndex = distributeKeyIndex(root, subPlan->distributed_keys, subPlan->targetlist);
|
||||
|
||||
|
|
@ -344,19 +430,15 @@ static bool redistributeInfo(PlannerInfo* root, List* subPlans, Plan* plan, List
|
|||
(list_length(en->nodeList) == 1 && bms_num_members(target_distribution->bms_data_nodeids) > 1);
|
||||
|
||||
/*
|
||||
* Whether a stream has been added for the plan only executes on one datanode
|
||||
* of multi-datanode group, we should redistribute it beforehand. Or it'll lead
|
||||
* duplicate results when pushing down exec_nodes to other datanodes, and we
|
||||
* can't prevent exec_nodes pushing down since executor needs all the consumer
|
||||
* to be same in one thread, or it'll hang.
|
||||
* 如果为只在多个数据节点组中的一个数据节点上执行的计划添加了流,
|
||||
* 我们应该提前重新分发它。否则,当将exec_nodes下推到其他数据节点时,将导致重复结果,并且我们无法阻止exec_nodes下推,因为执行器需要在一个线程中将所有消费者都相同,否则会挂起。
|
||||
*/
|
||||
if (partial_single_node) {
|
||||
ListCell* lc = NULL;
|
||||
List* distkeys = NIL;
|
||||
|
||||
/*
|
||||
* Since there's no stats info in append rel, we can only roughly
|
||||
* choose the distribute key to do redistribute
|
||||
* 由于附加关系中没有统计信息,我们只能粗略选择用于重新分发的分发键。
|
||||
*/
|
||||
foreach (lc, subPlan->targetlist) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(lc);
|
||||
|
|
@ -366,15 +448,14 @@ static bool redistributeInfo(PlannerInfo* root, List* subPlans, Plan* plan, List
|
|||
}
|
||||
}
|
||||
if (distkeys != NIL) {
|
||||
/* Found a valid distribute key, so use it */
|
||||
/* 找到一个有效的分发键,因此使用它 */
|
||||
subPlan = make_stream_plan(root, subPlan, distkeys, 1.0, target_distribution);
|
||||
subPlanKeyArray[subPlanIndex] =
|
||||
distributeKeyIndex(root, subPlan->distributed_keys, subPlan->targetlist);
|
||||
} else {
|
||||
/*
|
||||
* No suitable distribute key, and we don't support distribute on
|
||||
* roundrobin, so make a const to distribute on it. NOTE. We know
|
||||
* it's not a good idea, but no way in the moment. Can improve later
|
||||
* 没有合适的分发键,而且我们不支持在RoundRobin上进行分发,所以制作一个const来进行分发。
|
||||
* 注意。我们知道这不是一个好主意,但目前没有其他方法,可以以后改进。
|
||||
*/
|
||||
Const* con = makeConst(INT4OID, -1, InvalidOid, -2, (Datum)0, true, false);
|
||||
distkeys = list_make1(con);
|
||||
|
|
@ -409,19 +490,18 @@ static bool redistributeInfo(PlannerInfo* root, List* subPlans, Plan* plan, List
|
|||
subPlanIndex++;
|
||||
}
|
||||
|
||||
/* Sepcial process for union all. */
|
||||
/* Union All操作的特殊处理。 */
|
||||
if (isunionall) {
|
||||
/* if all the plans are hashed plan and in same node group, common distribute key is possible */
|
||||
/* 如果所有计划都是哈希计划,并且在同一节点组中,可能存在通用分发键 */
|
||||
if (!norediskeyplan) {
|
||||
redistributeKeyIndex = get_distkey_for_unionall(subPlanKeyArray, subPlanNum);
|
||||
} else if (!redistributedplan && !judge_redistribute_setop_support(root, subPlans, redistributePlanSetCopy)) {
|
||||
/* if no hashed plan, no distribute key is possible */
|
||||
/* 如果没有哈希计划,也没有分发键可能 */
|
||||
result = false;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Find no distribute key for subPlan original, we should generate distribute key from max
|
||||
* cost plan as the distribute key.
|
||||
* 对于原始的子计划找不到分发键,我们应该从具有最大代价的计划生成分发键。
|
||||
*/
|
||||
if (!redistributedplan) {
|
||||
redistributeKeyIndex = get_max_cost_distkey_for_nulldistkey(root, subPlans, subPlanNum, subPlanCostArray);
|
||||
|
|
@ -429,8 +509,8 @@ static bool redistributeInfo(PlannerInfo* root, List* subPlans, Plan* plan, List
|
|||
result = false;
|
||||
} else {
|
||||
/*
|
||||
* There has distribute key for subPlan original,
|
||||
* use subPlanKeyArray as the distribute key.
|
||||
* 子计划原始已有分发键,
|
||||
* 使用subPlanKeyArray作为分发键。
|
||||
*/
|
||||
redistributeKeyIndex = get_max_cost_distkey_for_hasdistkey(
|
||||
root, subPlans, subPlanNum, subPlanKeyArray, subPlanCostArray, &redistributePlanSetCopy);
|
||||
|
|
@ -447,54 +527,52 @@ static bool redistributeInfo(PlannerInfo* root, List* subPlans, Plan* plan, List
|
|||
return result;
|
||||
}
|
||||
|
||||
static ExecNodes* append_merge_exec_nodes(List* subplans, bool is_distributed)
|
||||
|
||||
static ExecNodes* append_merge_exec_nodes(List* subplans, bool is_distributed)//用于合并多个子计划的执行节点信息
|
||||
{
|
||||
ListCell* lc = NULL;
|
||||
ExecNodes* merged_en = (ExecNodes*)makeNode(ExecNodes);
|
||||
Distribution* merged_distribution = NULL;
|
||||
ExecNodes* merged_en = (ExecNodes*)makeNode(ExecNodes); // 创建一个合并后的执行节点信息
|
||||
Distribution* merged_distribution = NULL; // 合并后的分布信息
|
||||
|
||||
foreach (lc, subplans) {
|
||||
Plan* subplan = (Plan*)lfirst(lc);
|
||||
ExecNodes* en = subplan->exec_nodes;
|
||||
ExecNodes* en = subplan->exec_nodes; // 获取子计划的执行节点信息
|
||||
|
||||
if (IsA(subplan, Stream))
|
||||
en = ((Stream*)subplan)->consumer_nodes;
|
||||
en = ((Stream*)subplan)->consumer_nodes; // 如果子计划是Stream计划,获取其消费者节点信息
|
||||
|
||||
merged_en->nodeList = list_merge_int(merged_en->nodeList, en->nodeList);
|
||||
merged_en->nodeList = list_merge_int(merged_en->nodeList, en->nodeList); // 合并执行节点列表
|
||||
|
||||
/*
|
||||
* There are two callers of this function
|
||||
* (1) mark_distribute_setop : the en->distribution may not the same
|
||||
* (2) mark_distribute_setop_distribution : the en->distribution should be the same
|
||||
* So, we could not do this assert : ng_is_same_group(&merged_en->distribution, &en->distribution)
|
||||
* 有两个调用者调用了这个函数:
|
||||
* (1) mark_distribute_setop:en->distribution可能不相同
|
||||
* (2) mark_distribute_setop_distribution:en->distribution应该相同
|
||||
* 因此,我们不能断言:ng_is_same_group(&merged_en->distribution, &en->distribution)
|
||||
*/
|
||||
if (is_distributed &&
|
||||
(merged_distribution != NULL && !ng_is_same_group(merged_distribution, &en->distribution))) {
|
||||
elog(ERROR, "The distribution of merged and exec node are not the same\n"
|
||||
"merged distribution is %s\n"
|
||||
"supblan distribution is %s",
|
||||
dist_to_str(merged_distribution),
|
||||
dist_to_str(&en->distribution));
|
||||
}
|
||||
elog(ERROR, "合并后的执行节点和子计划的分布信息不同\n"
|
||||
"合并后的分布信息:%s\n"
|
||||
"子计划分布信息:%s",
|
||||
dist_to_str(merged_distribution),
|
||||
dist_to_str(&en->distribution));
|
||||
}
|
||||
|
||||
Distribution* new_merged_distribution = ng_get_union_distribution(merged_distribution, &en->distribution);
|
||||
DestroyDistribution(merged_distribution);
|
||||
merged_distribution = new_merged_distribution;
|
||||
Distribution* new_merged_distribution = ng_get_union_distribution(merged_distribution, &en->distribution); // 合并分布信息
|
||||
DestroyDistribution(merged_distribution); // 销毁旧的合并分布信息
|
||||
merged_distribution = new_merged_distribution; // 更新合并分布信息
|
||||
}
|
||||
ng_set_distribution(&merged_en->distribution, merged_distribution);
|
||||
ng_set_distribution(&merged_en->distribution, merged_distribution); // 设置合并后的执行节点的分布信息
|
||||
|
||||
foreach (lc, subplans) {
|
||||
Plan* subplan = (Plan*)lfirst(lc);
|
||||
ExecNodes* en = subplan->exec_nodes;
|
||||
|
||||
/*
|
||||
* If the subplan contains stream, we should pushdown the merged exec_nodes.
|
||||
* The reason why we do this is because different exec_nodes between top plan node and
|
||||
* stream consumer_nodes may cause hang up.
|
||||
*
|
||||
* And we must pushdown merged exec_nodes when subplan->dop > 1. because when add local
|
||||
* stream, we need the plan node on both sides of the local stream node have the same exec nodes.
|
||||
* pushdown merged exec_nodes can guarantee this.
|
||||
* 如果子计划包含Stream,我们应该下推合并后的执行节点信息。
|
||||
* 我们之所以这样做的原因是因为顶层计划节点和流消费者节点之间的不同执行节点可能会导致挂起。
|
||||
* 当子计划的dop > 1时,我们必须下推合并后的执行节点,因为在添加本地流时,我们需要本地流节点两侧的计划节点具有相同的执行节点。
|
||||
* 下推合并后的执行节点可以保证这一点。
|
||||
*/
|
||||
if (!contain_special_plan_node(subplan, T_Stream, CPLN_NO_IGNORE_MATERIAL) && subplan->dop == 1) {
|
||||
continue;
|
||||
|
|
@ -503,78 +581,73 @@ static ExecNodes* append_merge_exec_nodes(List* subplans, bool is_distributed)
|
|||
en = ((Stream*)subplan)->consumer_nodes;
|
||||
if (list_length(merged_en->nodeList) > list_length(en->nodeList)) {
|
||||
/*
|
||||
* Use a deep copy of 'merged_en', in case the subplan's
|
||||
* baselocatortype was changed by the assignment of
|
||||
* merged_en->baselocatortype.
|
||||
* 使用'merged_en'的深拷贝,以防子计划的baselocatortype在赋值merged_en->baselocatortype时发生更改。
|
||||
*/
|
||||
ExecNodes* temp_execnodes = (ExecNodes*)copyObject(merged_en);
|
||||
temp_execnodes->baselocatortype = en->baselocatortype;
|
||||
pushdown_execnodes(subplan, temp_execnodes, true);
|
||||
pushdown_execnodes(subplan, temp_execnodes, true); // 下推执行节点信息
|
||||
}
|
||||
}
|
||||
|
||||
merged_en->baselocatortype = LOCATOR_TYPE_HASH;
|
||||
return merged_en;
|
||||
merged_en->baselocatortype = LOCATOR_TYPE_HASH; // 设置合并后的执行节点的基本定位类型为哈希
|
||||
return merged_en; // 返回合并后的执行节点信息
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
代码用于标记分布式查询中的Set操作(例如Union、Union All)的执行计划的分布信息。
|
||||
它主要处理分布式Set操作的子计划列表,根据一些条件和规则,对子计划的执行计划进行修改和调整,以确保分布信息的一致性和正确性。
|
||||
*/
|
||||
static void mark_distribute_setop_distribution(PlannerInfo* root, Node* node, Plan* plan, List* subPlans,
|
||||
Bitmapset* redistributePlanSet, List* redistributeKey, Distribution* redistributeDistribution)
|
||||
{
|
||||
ListCell* cell = NULL;
|
||||
List* newSubPlans = NIL;
|
||||
Plan* subPlan = NULL;
|
||||
Index subPlanIndex = 0;
|
||||
TargetEntry* teEntry = NULL;
|
||||
MergeAppend* mergeAppend = NULL;
|
||||
Append* append = NULL;
|
||||
ListCell* attnumCell = NULL;
|
||||
AttrNumber attnum;
|
||||
RecursiveUnion* recursiveUnionPlan = NULL;
|
||||
Distribution *newDistribution = redistributeDistribution;
|
||||
List* newSubPlans = NIL; // 存储调整后的子计划列表
|
||||
Plan* subPlan = NULL; // 子计划
|
||||
Index subPlanIndex = 0; // 子计划索引
|
||||
TargetEntry* teEntry = NULL; // 目标条目
|
||||
MergeAppend* mergeAppend = NULL; // MergeAppend计划节点
|
||||
Append* append = NULL; // Append计划节点
|
||||
ListCell* attnumCell = NULL; // 属性编号的列表元素
|
||||
AttrNumber attnum; // 属性编号
|
||||
RecursiveUnion* recursiveUnionPlan = NULL; // RecursiveUnion计划节点
|
||||
Distribution *newDistribution = redistributeDistribution; // 新的分布信息,默认为原始分布信息
|
||||
|
||||
// 检查并确定计划节点的类型
|
||||
if (IsA(node, MergeAppend)) {
|
||||
mergeAppend = (MergeAppend*)node;
|
||||
} else if (IsA(node, Append)) {
|
||||
AssertEreport(IsA(node, Append), MOD_OPT, "The node is NOT a Append");
|
||||
AssertEreport(IsA(node, Append), MOD_OPT, "该节点不是一个Append节点");
|
||||
|
||||
append = (Append*)node;
|
||||
} else if (IsA(node, RecursiveUnion)) {
|
||||
recursiveUnionPlan = (RecursiveUnion*)node;
|
||||
}
|
||||
|
||||
// 如果存在需要重新分发的子计划
|
||||
if (!bms_is_empty(redistributePlanSet)) {
|
||||
foreach (cell, subPlans) {
|
||||
subPlan = (Plan*)lfirst(cell);
|
||||
List* distribute_keys = NIL;
|
||||
List* subplandistkey = redistributeKey;
|
||||
|
||||
/*
|
||||
* There are four cases enter the else branch below:
|
||||
* 1. All subplan are hash and there are no distkey;
|
||||
* 2. All subplan are hash which some have distkey and some have no distkey;
|
||||
* 3. Some subplan are hash which involve two cases above-mentioned and some are replication.
|
||||
* We will choose distkey for replication of union all.
|
||||
*/
|
||||
// 判断是否需要为子计划生成新的分发键
|
||||
if (subplandistkey == NIL)
|
||||
subplandistkey = make_distkey_for_append(root, subPlan);
|
||||
|
||||
/*
|
||||
* Add distribute node
|
||||
*/
|
||||
// 构建子计划的分发键
|
||||
foreach (attnumCell, subplandistkey) {
|
||||
attnum = lfirst_int(attnumCell);
|
||||
if ((attnum - 1) >= list_length(subPlan->targetlist)) {
|
||||
elog(ERROR, "attnum overflow the length of subplan targetlist");
|
||||
elog(ERROR, "属性编号溢出子计划目标列表的长度");
|
||||
}
|
||||
teEntry = (TargetEntry*)list_nth(subPlan->targetlist, attnum - 1);
|
||||
distribute_keys = lappend(distribute_keys, teEntry->expr);
|
||||
}
|
||||
|
||||
// 如果子计划在需要重新分发的计划集合中
|
||||
if (bms_is_member(subPlanIndex, redistributePlanSet)) {
|
||||
/*
|
||||
* If both sub plan are replicate plan and we could not get distribute keys for them,
|
||||
* we need to broadcast both of them to a single datanode from redistributeDistribution
|
||||
*/
|
||||
// 如果两个子计划都是复制计划,并且无法为它们获取分发键,则需要将它们广播到重新分发的数据节点
|
||||
bool noDistribute_keys = (distribute_keys == NIL &&
|
||||
bms_num_members(redistributeDistribution->bms_data_nodeids) > 1);
|
||||
if (noDistribute_keys) {
|
||||
|
|
@ -583,41 +656,36 @@ static void mark_distribute_setop_distribution(PlannerInfo* root, Node* node, Pl
|
|||
|
||||
Plan* newplan = subPlan;
|
||||
|
||||
// 如果查询是相关的并且启用了子查询参数化,不允许添加流操作符
|
||||
if (root->is_correlated && SUBQUERY_PREDPUSH(root))
|
||||
elog(ERROR, "Can not add stream operator on to parameterize plan.");
|
||||
elog(ERROR, "不能在参数化计划上添加流操作符。");
|
||||
|
||||
|
||||
/* Make stream node of redistribute. */
|
||||
// 创建重新分发的流计划节点
|
||||
bool partial_single_node = bms_num_members (newDistribution->bms_data_nodeids) == 1 &&
|
||||
bms_num_members(redistributeDistribution->bms_data_nodeids) > 1 &&
|
||||
distribute_keys == NIL;
|
||||
if (partial_single_node) {
|
||||
/*
|
||||
* If a stream plan only executes on one datanode of multi-datanode group,
|
||||
* we should redistribute it. Or it'll lead duplicate results when pushing
|
||||
* down exec_nodes to other datanodes
|
||||
*/
|
||||
// 如果一个流计划只在多数据节点组的一个数据节点上执行,需要重新分发它,否则会导致推送执行节点到其他数据节点时出现重复结果
|
||||
const int typeMod = -1;
|
||||
const int typeLen = -2;
|
||||
Const *con = makeConst(INT4OID, typeMod, InvalidOid, typeLen, (Datum)0, true, false);
|
||||
newplan = make_stream_plan(root, subPlan, list_make1(con), 0, redistributeDistribution);
|
||||
} else {
|
||||
newplan = make_stream_plan(root, subPlan, distribute_keys, 0, newDistribution);
|
||||
/* We should use the original redistributeDistribution as the
|
||||
* stream->consumer_nodes->distribution
|
||||
* to make sure all the subplans of append has the same distributeion.
|
||||
*/
|
||||
// 确保流计划的消费者节点分布与重新分发的数据节点分布一致
|
||||
if (newDistribution != redistributeDistribution) {
|
||||
ng_copy_distribution(&((Stream *)newplan)->consumer_nodes->distribution,
|
||||
redistributeDistribution);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置流计划节点的排序信息(如果有)
|
||||
if (IsA(newplan, Stream)) {
|
||||
Stream *streamNode = (Stream *)newplan;
|
||||
streamNode->is_sorted = IsA(node, MergeAppend) ? true : false;
|
||||
}
|
||||
|
||||
// 将新的子计划添加到新的子计划列表
|
||||
if (PointerIsValid(mergeAppend)) {
|
||||
newSubPlans = lappend(newSubPlans,
|
||||
make_sort(root,
|
||||
|
|
@ -632,53 +700,59 @@ static void mark_distribute_setop_distribution(PlannerInfo* root, Node* node, Pl
|
|||
newSubPlans = lappend(newSubPlans, newplan);
|
||||
}
|
||||
} else {
|
||||
// 如果子计划不需要重新分发,直接将其添加到新的子计划列表
|
||||
newSubPlans = lappend(newSubPlans, subPlan);
|
||||
}
|
||||
|
||||
subPlanIndex++;
|
||||
}
|
||||
|
||||
// 更新Set操作节点的子计划列表
|
||||
if (PointerIsValid(mergeAppend)) {
|
||||
mergeAppend->mergeplans = newSubPlans;
|
||||
} else if (PointerIsValid(recursiveUnionPlan)) {
|
||||
const int invalidLen = 2;
|
||||
AssertEreport(list_length(newSubPlans) == invalidLen, MOD_OPT, "Invalid subplan length");
|
||||
AssertEreport(list_length(newSubPlans) == invalidLen, MOD_OPT, "无效的子计划长度");
|
||||
|
||||
Plan* recursive_base_plan = (Plan*)recursiveUnionPlan;
|
||||
|
||||
recursive_base_plan->lefttree = (Plan*)linitial(newSubPlans);
|
||||
recursive_base_plan->righttree = (Plan*)lsecond(newSubPlans);
|
||||
} else {
|
||||
AssertEreport(PointerIsValid(append), MOD_OPT, "The append is NULL");
|
||||
AssertEreport(PointerIsValid(append), MOD_OPT, "Append节点为空");
|
||||
|
||||
append->appendplans = newSubPlans;
|
||||
}
|
||||
subPlans = newSubPlans;
|
||||
subPlans = newSubPlans; // 更新子计划列表
|
||||
}
|
||||
|
||||
// 根据重新分发的键更新计划节点的分发键
|
||||
foreach (attnumCell, redistributeKey) {
|
||||
attnum = lfirst_int(attnumCell);
|
||||
if (list_length(plan->targetlist) < attnum) {
|
||||
elog(ERROR, "target list is too short");
|
||||
elog(ERROR, "目标列表长度过短");
|
||||
}
|
||||
teEntry = (TargetEntry*)list_nth(plan->targetlist, attnum - 1);
|
||||
plan->distributed_keys = lappend(plan->distributed_keys, teEntry->expr);
|
||||
}
|
||||
|
||||
// 设置计划节点的执行类型和执行节点信息
|
||||
plan->exec_type = EXEC_ON_DATANODES;
|
||||
plan->exec_nodes = append_merge_exec_nodes(subPlans, true);
|
||||
}
|
||||
|
||||
|
||||
void mark_distribute_setop(PlannerInfo* root, Node* node, bool isunionall, bool canDiskeyChange)
|
||||
{
|
||||
List* subPlans = NIL;
|
||||
ListCell* planCell = NULL;
|
||||
Plan* plan = NULL;
|
||||
Bitmapset* execAllNodesPlanSet = NULL;
|
||||
Index subPlanIndex = 0;
|
||||
bool execOnCoords = false;
|
||||
|
||||
void mark_distribute_setop(PlannerInfo* root, Node* node, bool isunionall, bool canDiskeyChange)//代码用于标记分布式Set操作(例如Union、Union All)的执行计划
|
||||
{
|
||||
List* subPlans = NIL; // 子计划列表
|
||||
ListCell* planCell = NULL;
|
||||
Plan* plan = NULL; // 主计划节点
|
||||
Bitmapset* execAllNodesPlanSet = NULL; // 所有节点都执行的计划集合
|
||||
Index subPlanIndex = 0; // 子计划索引
|
||||
bool execOnCoords = false; // 是否在协调节点执行
|
||||
|
||||
// 根据不同的Set操作类型,获取子计划列表和主计划节点
|
||||
if (IsA(node, Append)) {
|
||||
Append* appendPlan = (Append*)node;
|
||||
|
||||
|
|
@ -693,7 +767,7 @@ void mark_distribute_setop(PlannerInfo* root, Node* node, bool isunionall, bool
|
|||
} else {
|
||||
MergeAppend* mergeAppendPlan = NULL;
|
||||
|
||||
AssertEreport(IsA(node, MergeAppend), MOD_OPT, "The node is NOT a MergeAppend");
|
||||
AssertEreport(IsA(node, MergeAppend), MOD_OPT, "该节点不是一个MergeAppend");
|
||||
|
||||
mergeAppendPlan = (MergeAppend*)node;
|
||||
|
||||
|
|
@ -701,9 +775,11 @@ void mark_distribute_setop(PlannerInfo* root, Node* node, bool isunionall, bool
|
|||
plan = &(mergeAppendPlan->plan);
|
||||
}
|
||||
|
||||
AssertEreport(PointerIsValid(subPlans), MOD_OPT, "The subPlan is NULL");
|
||||
AssertEreport(list_length(subPlans) >= 1, MOD_OPT, "The list length of subplan is 0");
|
||||
// 检查子计划列表的有效性
|
||||
AssertEreport(PointerIsValid(subPlans), MOD_OPT, "子计划为空");
|
||||
AssertEreport(list_length(subPlans) >= 1, MOD_OPT, "子计划列表长度为0");
|
||||
|
||||
// 检查子计划是否在协调节点上执行
|
||||
foreach (planCell, subPlans) {
|
||||
Plan* subPlan = (Plan*)lfirst(planCell);
|
||||
|
||||
|
|
@ -713,16 +789,17 @@ void mark_distribute_setop(PlannerInfo* root, Node* node, bool isunionall, bool
|
|||
}
|
||||
}
|
||||
|
||||
// 如果在协调节点上执行且查询是相关的,并且启用了子查询参数化,则不允许添加流操作符
|
||||
if (execOnCoords) {
|
||||
if (SUBQUERY_IS_PARAM(root) && root->is_correlated) {
|
||||
elog(ERROR, "Can not add stream operator on to parameterize plan.");
|
||||
elog(ERROR, "不能在参数化计划上添加流操作符。");
|
||||
}
|
||||
} else {
|
||||
subPlanIndex = 0;
|
||||
foreach (planCell, subPlans) {
|
||||
Plan* subPlan = (Plan*)lfirst(planCell);
|
||||
|
||||
/* make each branch replicated if there are subplan exprs in one branches */
|
||||
// 如果查询是相关的,但不启用子查询参数化,则将包含流计划节点的分支材料化
|
||||
if (root->is_correlated && !(SUBQUERY_PREDPUSH(root))) {
|
||||
if (contain_special_plan_node(subPlan, T_Stream)) {
|
||||
subPlan = materialize_finished_plan(subPlan, true, root->glob->vectorized);
|
||||
|
|
@ -730,38 +807,38 @@ void mark_distribute_setop(PlannerInfo* root, Node* node, bool isunionall, bool
|
|||
lfirst(planCell) = subPlan;
|
||||
}
|
||||
|
||||
// 如果子计划在所有节点上执行,则将其添加到执行所有节点的计划集合中
|
||||
if (is_execute_on_allnodes(subPlan)) {
|
||||
execAllNodesPlanSet = bms_add_member(execAllNodesPlanSet, subPlanIndex);
|
||||
|
||||
subPlanIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果子计划的执行节点列表为空,则记录调试信息
|
||||
if (subPlan->exec_nodes->nodeList == NIL) {
|
||||
elog(DEBUG1, "[mark_distribute_setop] empty node list");
|
||||
elog(DEBUG1, "[mark_distribute_setop] 执行节点列表为空");
|
||||
}
|
||||
|
||||
AssertEreport(PointerIsValid(subPlan->exec_nodes), MOD_OPT, "The subPlan's exec_nodes is NULL");
|
||||
AssertEreport(PointerIsValid(subPlan->exec_nodes), MOD_OPT, "子计划的执行节点列表为空");
|
||||
|
||||
subPlanIndex++;
|
||||
}
|
||||
|
||||
// 如果所有子计划都在所有节点上执行,则标记主计划节点以执行所有节点
|
||||
if (bms_num_members(execAllNodesPlanSet) == list_length(subPlans)) {
|
||||
mark_distribute_setop_allnodes(plan);
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Just redistribute subplans if there is no less than one subplan
|
||||
* that is distributed
|
||||
* 如果至少有一个子计划需要重新分发,
|
||||
* 则获取重新分发的信息并更新执行计划,否则将添加远程查询节点到每个子计划上
|
||||
*/
|
||||
Bitmapset* redistributePlanSet = NULL;
|
||||
List* redistributeKey = NIL;
|
||||
Distribution* redistributeDistribution = NULL;
|
||||
bool result = false;
|
||||
|
||||
/*
|
||||
* get redistribute information
|
||||
* return true if succeed; else return fail
|
||||
*/
|
||||
// 获取重新分发的信息,如果成功则返回true,否则返回false
|
||||
result = redistributeInfo(root,
|
||||
subPlans,
|
||||
plan,
|
||||
|
|
@ -772,23 +849,25 @@ void mark_distribute_setop(PlannerInfo* root, Node* node, bool isunionall, bool
|
|||
canDiskeyChange);
|
||||
|
||||
if (result) {
|
||||
// 更新执行计划以反映重新分发的信息
|
||||
mark_distribute_setop_distribution(
|
||||
root, node, plan, subPlans, redistributePlanSet, redistributeKey, redistributeDistribution);
|
||||
} else {
|
||||
/*
|
||||
* Add remote query node on top of each subplan if fail
|
||||
* to get redistribute information
|
||||
* 如果无法获取重新分发的信息,则在每个子计划上添加远程查询节点
|
||||
*/
|
||||
mark_distribute_setop_remotequery(root, node, plan, subPlans);
|
||||
}
|
||||
}
|
||||
|
||||
// 释放执行所有节点的计划集合
|
||||
if (PointerIsValid(execAllNodesPlanSet)) {
|
||||
pfree_ext(execAllNodesPlanSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// the name is_stream_support is used in function check_stream_support, which used as condition
|
||||
void mark_stream_unsupport()
|
||||
{
|
||||
|
|
@ -806,4 +885,3 @@ void SerializePlan(
|
|||
{
|
||||
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,10 +41,11 @@
|
|||
*/
|
||||
List* check_op_list_template(Plan* result_plan, List* (*check_eval)(Node*))
|
||||
{
|
||||
List* res_list = NIL;
|
||||
List* res_list = NIL; // 创建一个空的List用于存储检查结果
|
||||
|
||||
res_list = check_eval((Node*)result_plan->targetlist); // 检查目标列表(targetlist)
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)result_plan->qual)); // 检查过滤条件(qual)
|
||||
|
||||
res_list = check_eval((Node*)result_plan->targetlist);
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)result_plan->qual));
|
||||
switch (nodeTag(result_plan)) {
|
||||
case T_SeqScan:
|
||||
case T_CStoreScan:
|
||||
|
|
@ -60,13 +61,14 @@ List* check_op_list_template(Plan* result_plan, List* (*check_eval)(Node*))
|
|||
ForeignTable* ftbl = NULL;
|
||||
ForeignServer* fsvr = NULL;
|
||||
|
||||
// 获取外部表和外部服务器的信息
|
||||
ftbl = GetForeignTable(foreignScan->scan_relid);
|
||||
AssertEreport(NULL != ftbl, MOD_OPT, "The foreign table is NULL");
|
||||
fsvr = GetForeignServer(ftbl->serverid);
|
||||
AssertEreport(NULL != fsvr, MOD_OPT, "The foreign server is NULL");
|
||||
|
||||
/*
|
||||
* If the predicate is pushed down, must find subplan from item->hdfsQual struct.
|
||||
* 如果谓词条件被下推,则从item->hdfsQual结构中查找子计划。
|
||||
*/
|
||||
if (isObsOrHdfsTableFormSrvName(fsvr->servername)) {
|
||||
List* foreignPrivateList = (List*)foreignScan->fdw_private;
|
||||
|
|
@ -78,90 +80,92 @@ List* check_op_list_template(Plan* result_plan, List* (*check_eval)(Node*))
|
|||
case T_IndexScan: {
|
||||
IndexScan* splan = (IndexScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual)); // 检查索引条件
|
||||
} break;
|
||||
case T_IndexOnlyScan: {
|
||||
IndexOnlyScan* splan = (IndexOnlyScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual)); // 检查索引条件
|
||||
} break;
|
||||
case T_CStoreIndexScan: {
|
||||
CStoreIndexScan* splan = (CStoreIndexScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual)); // 检查索引条件
|
||||
} break;
|
||||
case T_DfsIndexScan: {
|
||||
DfsIndexScan* splan = (DfsIndexScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual)); // 检查索引条件
|
||||
} break;
|
||||
case T_BitmapIndexScan: {
|
||||
BitmapIndexScan* splan = (BitmapIndexScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual)); // 检查索引条件
|
||||
} break;
|
||||
case T_CStoreIndexCtidScan: {
|
||||
CStoreIndexCtidScan* splan = (CStoreIndexCtidScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->indexqual)); // 检查索引条件
|
||||
} break;
|
||||
case T_TidScan: {
|
||||
TidScan* splan = (TidScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->tidquals));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->tidquals)); // 检查TID条件
|
||||
} break;
|
||||
case T_FunctionScan: {
|
||||
FunctionScan* splan = (FunctionScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->funcexpr));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->funcexpr)); // 检查函数表达式条件
|
||||
} break;
|
||||
case T_ValuesScan: {
|
||||
ValuesScan* splan = (ValuesScan*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->values_lists));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->values_lists)); // 检查值表达式条件
|
||||
} break;
|
||||
case T_NestLoop:
|
||||
case T_VecNestLoop: {
|
||||
Join* splan = (Join*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->joinqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->joinqual)); // 检查连接条件
|
||||
} break;
|
||||
case T_MergeJoin:
|
||||
case T_VecMergeJoin: {
|
||||
MergeJoin* splan = (MergeJoin*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->join.joinqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->mergeclauses));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->join.joinqual)); // 检查连接条件
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->mergeclauses)); // 检查合并条件
|
||||
} break;
|
||||
case T_HashJoin:
|
||||
case T_VecHashJoin: {
|
||||
HashJoin* splan = (HashJoin*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->join.joinqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->hashclauses));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->join.joinqual)); // 检查连接条件
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->hashclauses)); // 检查哈希条件
|
||||
} break;
|
||||
case T_Limit:
|
||||
case T_VecLimit: {
|
||||
Limit* splan = (Limit*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->limitOffset));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->limitCount));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->limitOffset)); // 检查限制偏移
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->limitCount)); // 检查限制数量
|
||||
} break;
|
||||
case T_VecWindowAgg:
|
||||
case T_WindowAgg: {
|
||||
WindowAgg* splan = (WindowAgg*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->startOffset));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->endOffset));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->startOffset)); // 检查窗口聚合的起始偏移
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->endOffset)); // 检查窗口聚合的结束偏移
|
||||
} break;
|
||||
case T_BaseResult:
|
||||
case T_VecResult: {
|
||||
BaseResult* splan = (BaseResult*)result_plan;
|
||||
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->resconstantqual));
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->resconstantqual)); // 检查基本结果节点的常量条件
|
||||
} break;
|
||||
case T_ModifyTable: {
|
||||
ModifyTable* splan = (ModifyTable*)result_plan;
|
||||
|
||||
// 如果是更新操作(upsertAction为UPSERT_UPDATE)并且有更新目标列表(updateTlist),则检查更新目标列表
|
||||
if (splan->upsertAction == UPSERT_UPDATE && splan->updateTlist != NULL) {
|
||||
res_list = list_concat_unique(res_list, check_eval((Node*)splan->updateTlist));
|
||||
}
|
||||
|
|
@ -169,9 +173,10 @@ List* check_op_list_template(Plan* result_plan, List* (*check_eval)(Node*))
|
|||
default:
|
||||
break;
|
||||
}
|
||||
return res_list;
|
||||
return res_list; // 返回检查结果的List
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @Descarption: Search node and check funcid > FirstNormalObjectId.
|
||||
* @in node: Current node.
|
||||
|
|
@ -180,21 +185,26 @@ List* check_op_list_template(Plan* result_plan, List* (*check_eval)(Node*))
|
|||
static bool check_func_walker(Node* node, bool* found)
|
||||
{
|
||||
if (node == NULL) {
|
||||
return false;
|
||||
return false; // 如果节点为空,返回 false,表示没有找到目标函数
|
||||
} else if (IsA(node, FuncExpr)) {
|
||||
// 如果节点是函数表达式(FuncExpr)类型
|
||||
FuncExpr* fexpr = (FuncExpr*)node;
|
||||
|
||||
if (fexpr->funcid > FirstNormalObjectId) {
|
||||
*found = true;
|
||||
return true;
|
||||
// 如果函数的函数ID大于 FirstNormalObjectId,表示这是一个自定义函数
|
||||
*found = true; // 将 found 标志设置为 true,表示找到了目标函数
|
||||
return true; // 返回 true,表示已经找到了目标函数,停止遍历
|
||||
} else {
|
||||
return false;
|
||||
return false; // 函数ID不大于 FirstNormalObjectId,不是自定义函数,返回 false
|
||||
}
|
||||
}
|
||||
|
||||
// 继续遍历表达式树的子节点
|
||||
return expression_tree_walker(node, (bool (*)())check_func_walker, found);
|
||||
// 调用 expression_tree_walker 函数,继续遍历子节点,并将 found 标志传递下去
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @Description: Check this node if vartype > FirstNormalObjectId.
|
||||
* @in qual: Checked node.
|
||||
|
|
|
|||
|
|
@ -82,77 +82,96 @@ static uint unsupport_func[] = {
|
|||
bool stream_walker(Node* node, void* context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
return false; // 如果节点为空,返回 false
|
||||
|
||||
shipping_context *cxt = (shipping_context*)context;
|
||||
shipping_context *cxt = (shipping_context*)context; // 将上下文数据转换为 shipping_context 结构体类型
|
||||
|
||||
switch (nodeTag(node)) {
|
||||
case T_Query: {
|
||||
stream_walker_query((Query*)node, cxt);
|
||||
stream_walker_query((Query*)node, cxt); // 如果节点是 Query 类型,调用 stream_walker_query 函数处理
|
||||
} break;
|
||||
case T_TargetEntry: {
|
||||
stream_walker_target_entry((TargetEntry*)node, cxt);
|
||||
stream_walker_target_entry((TargetEntry*)node, cxt); // 如果节点是 TargetEntry 类型,调用 stream_walker_target_entry 函数处理
|
||||
} break;
|
||||
case T_FuncExpr: {
|
||||
stream_walker_func_expr((FuncExpr*)node, cxt);
|
||||
stream_walker_func_expr((FuncExpr*)node, cxt); // 如果节点是 FuncExpr 类型,调用 stream_walker_func_expr 函数处理
|
||||
} break;
|
||||
case T_Aggref: {
|
||||
stream_walker_aggref((Aggref*)node, cxt);
|
||||
stream_walker_aggref((Aggref*)node, cxt); // 如果节点是 Aggref 类型,调用 stream_walker_aggref 函数处理
|
||||
} break;
|
||||
case T_CoerceViaIO: {
|
||||
stream_walker_coerce((CoerceViaIO*) node, cxt);
|
||||
stream_walker_coerce((CoerceViaIO*) node, cxt); // 如果节点是 CoerceViaIO 类型,调用 stream_walker_coerce 函数处理
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return expression_tree_walker(node, (bool (*)())stream_walker, context);
|
||||
return expression_tree_walker(node, (bool (*)())stream_walker, context); // 递归遍历节点的子树,继续执行遍历操作
|
||||
}
|
||||
|
||||
|
||||
static bool containReplicatedTable(List *rtable)
|
||||
{
|
||||
ListCell *lc = NULL;
|
||||
foreach(lc, rtable) {
|
||||
RangeTblEntry *rte = (RangeTblEntry *)lfirst(lc);
|
||||
RangeTblEntry *rte = (RangeTblEntry *)lfirst(lc); // 获取范围表条目
|
||||
|
||||
// 如果范围表条目的 locator_type 表示为复制表(Replicated Table),则返回 true
|
||||
if (IsLocatorReplicated(rte->locator_type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果循环结束后没有找到复制表,返回 false
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_insertinto_rep(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 如果当前查询不支持流式传输,则直接返回
|
||||
if (!cxt->current_shippable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查查询类型是否为 INSERT,以及结果关系是否为 0,或结果关系不是复制表
|
||||
if (query->commandType != CMD_INSERT || query->resultRelation == 0 ||
|
||||
!IsLocatorReplicated(rt_fetch(query->resultRelation, query->rtable)->locator_type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ListCell *lc = NULL;
|
||||
int index = 0;
|
||||
|
||||
// 遍历查询的范围表中的每个条目
|
||||
foreach(lc, query->rtable) {
|
||||
index++;
|
||||
|
||||
// 跳过结果关系对应的条目
|
||||
if (index == query->resultRelation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RangeTblEntry *rte = rt_fetch(index, query->rtable);
|
||||
|
||||
// 如果当前条目不是子查询或子查询为空,则继续下一个条目的检查
|
||||
if (rte->rtekind != RTE_SUBQUERY || rte->subquery == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 如果子查询中包含窗口函数并且子查询的范围表中包含复制表,则不能进行流式传输
|
||||
if (rte->subquery->hasWindowFuncs && containReplicatedTable(rte->subquery->rtable)) {
|
||||
cxt->current_shippable = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Cannot shipping if there are junk tlists in replicated subquery */
|
||||
/* 如果复制子查询中存在无效的目标列表项,则不能进行流式传输 */
|
||||
if (check_replicated_junktlist(rte->subquery)) {
|
||||
cxt->current_shippable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不能进行流式传输,设置相应的错误信息
|
||||
if (!cxt->current_shippable) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -161,29 +180,32 @@ static void stream_walker_query_insertinto_rep(Query* query, shipping_context *c
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_update(Query* query, shipping_context *cxt)
|
||||
{
|
||||
/*
|
||||
* Concurrent update under stream mode is not yet supported.
|
||||
* When u_sess->attr.attr_sql.enable_stream_concurrent_update is off, we will return true to
|
||||
* generate non-stream plan for update statements.
|
||||
* 并发更新在流式传输模式下尚未支持。
|
||||
* 当 u_sess->attr.attr_sql.enable_stream_concurrent_update 为关闭状态时,
|
||||
* 我们将返回 true,以生成非流式传输的更新计划。
|
||||
*/
|
||||
if (query->commandType == CMD_UPDATE && !u_sess->attr.attr_sql.enable_stream_concurrent_update) {
|
||||
cxt->current_shippable = false;
|
||||
cxt->current_shippable = false; // 如果是 UPDATE 查询且不支持流式并发更新,设置不能进行流式传输
|
||||
}
|
||||
|
||||
if (query->hasForUpdate) {
|
||||
/* turn off dop for FOR UPDATE/SHARE query */
|
||||
/* 关闭 FOR UPDATE/SHARE 查询的并行执行,设置查询的 DOP 为 1 */
|
||||
u_sess->opt_cxt.query_dop = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_recursive(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 如果查询中包含递归查询(WITH RECURSIVE),则将查询的并行度(DOP)设置为 1,即不允许并行执行。
|
||||
if (query->hasRecursive) {
|
||||
/* If query contains recursive union, turn off dop */
|
||||
u_sess->opt_cxt.query_dop = 1;
|
||||
|
||||
|
||||
// 如果配置参数 enable_stream_recursive 为关闭状态,则不能进行流式传输,并设置相应的错误信息。
|
||||
if (!u_sess->attr.attr_sql.enable_stream_recursive) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -194,8 +216,10 @@ static void stream_walker_query_recursive(Query* query, shipping_context *cxt)
|
|||
}
|
||||
}
|
||||
|
||||
//函数处理包含 DISTINCT ON 子句的查询,如果查询中包含 DISTINCT ON 子句,则不能进行流式传输,并设置相应的错误信息。
|
||||
static void stream_walker_query_distinct(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 如果查询中包含 DISTINCT ON 子句,则不能进行流式传输,并设置相应的错误信息。
|
||||
if (query->hasDistinctOn) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -205,8 +229,10 @@ static void stream_walker_query_distinct(Query* query, shipping_context *cxt)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_returning(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 如果查询中包含 RETURNING 子句,则不能进行流式传输,并设置相应的错误信息。
|
||||
if (query->returningList) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -216,18 +242,24 @@ static void stream_walker_query_returning(Query* query, shipping_context *cxt)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_merge(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 如果查询的命令类型是 MERGE
|
||||
if (query->commandType == CMD_MERGE) {
|
||||
// 检查合并源目标列表中是否包含不支持流式传输的特性
|
||||
if (expression_tree_walker(
|
||||
(Node*)query->mergeSourceTargetList, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
|
||||
// 遍历合并操作列表
|
||||
ListCell* lc2 = NULL;
|
||||
foreach (lc2, query->mergeActionList) {
|
||||
MergeAction* mc = (MergeAction*)lfirst(lc2);
|
||||
// 如果合并操作是插入操作
|
||||
if (mc->commandType == CMD_INSERT) {
|
||||
// 检查目标表是否包含不支持流式传输的特性
|
||||
if (rel_contain_unshippable_feature(
|
||||
(RangeTblEntry*)list_nth(query->rtable, query->mergeTarget_relation - 1),
|
||||
cxt, mc->commandType)) {
|
||||
|
|
@ -237,6 +269,7 @@ static void stream_walker_query_merge(Query* query, shipping_context *cxt)
|
|||
bool saved_is_nextval_shippable = cxt->is_nextval_shippable;
|
||||
if (cxt->allow_func_in_targetlist) {
|
||||
cxt->is_nextval_shippable = true;
|
||||
// 检查目标列表中是否包含不支持流式传输的特性
|
||||
if (expression_tree_walker(
|
||||
(Node*)mc->targetList, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
|
|
@ -245,15 +278,18 @@ static void stream_walker_query_merge(Query* query, shipping_context *cxt)
|
|||
cxt->is_nextval_shippable = saved_is_nextval_shippable;
|
||||
cxt->allow_func_in_targetlist = false;
|
||||
} else {
|
||||
// 检查目标列表中是否包含不支持流式传输的特性
|
||||
if (expression_tree_walker((Node*)mc->targetList, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查合并操作的条件表达式是否包含不支持流式传输的特性
|
||||
if (expression_tree_walker((Node*)mc->qual, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
} else {
|
||||
// 如果合并操作不是插入操作,检查合并操作是否包含不支持流式传输的特性
|
||||
if (expression_tree_walker((Node*)mc, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
|
|
@ -262,36 +298,49 @@ static void stream_walker_query_merge(Query* query, shipping_context *cxt)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_upsert(Query *query, shipping_context *cxt)
|
||||
{
|
||||
// 如果查询的命令类型是 INSERT 且包含 UPSERT 子句
|
||||
if (query->commandType == CMD_INSERT && query->upsertClause != NULL) {
|
||||
/* For replicated table in stream, upsertClause cannot contain unshippable expression */
|
||||
// 对于流式传输中的复制表(replicated table),upsertClause 不能包含不支持流式传输的表达式
|
||||
if (query->resultRelation &&
|
||||
IsLocatorReplicated(rt_fetch(query->resultRelation, query->rtable)->locator_type)) {
|
||||
// 临时禁止处理不稳定函数的流式传输,以确保表达式的稳定性
|
||||
bool saved_disallow_volatile_func_shippable = cxt->disallow_volatile_func_shippable;
|
||||
cxt->disallow_volatile_func_shippable = true;
|
||||
|
||||
// 检查 upsertClause 是否包含不支持流式传输的表达式
|
||||
if (expression_tree_walker((Node *)query->upsertClause, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
|
||||
// 恢复处理不稳定函数的流式传输设置
|
||||
cxt->disallow_volatile_func_shippable = saved_disallow_volatile_func_shippable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_rtable(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 检查查询中是否包含不支持流式传输的表
|
||||
if (contains_unsupport_tables(query->rtable, query, cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
|
||||
// 如果查询的命令类型不是 SELECT
|
||||
if (query->commandType != CMD_SELECT &&
|
||||
query->resultRelation <= list_length(query->rtable) &&
|
||||
rel_contain_unshippable_feature((RangeTblEntry*)list_nth(query->rtable, query->resultRelation - 1),
|
||||
query->resultRelation <= list_length(query->rtable)) {
|
||||
// 检查结果表是否包含不支持流式传输的特性
|
||||
if (rel_contain_unshippable_feature((RangeTblEntry*)list_nth(query->rtable, query->resultRelation - 1),
|
||||
cxt, query->commandType)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_exec_direct(Query* query, shipping_context *cxt)
|
||||
{
|
||||
if (query->is_local) { /* execute direct */
|
||||
|
|
@ -302,153 +351,197 @@ static void stream_walker_query_exec_direct(Query* query, shipping_context *cxt)
|
|||
static void stream_walker_query_cte(Query* query, shipping_context *cxt)
|
||||
{
|
||||
/*
|
||||
* Random func is not allowed in CTE and limit.
|
||||
* EC func is not allowed in CTE
|
||||
* 不允许在 CTE 和 LIMIT 子句中使用随机函数(random func)。
|
||||
* 不允许在 CTE 中使用等值连接函数(EC func)。
|
||||
*/
|
||||
bool random_ori = cxt->is_randomfunc_shippable;
|
||||
bool ecfunc_ori = cxt->is_ecfunc_shippable;
|
||||
cxt->is_randomfunc_shippable = false;
|
||||
cxt->is_ecfunc_shippable = false;
|
||||
|
||||
/* walk the entire query tree to analyse the query */
|
||||
/* 遍历整个查询树以分析查询 */
|
||||
ListCell* lc = NULL;
|
||||
foreach (lc, query->cteList) {
|
||||
CommonTableExpr* cte = (CommonTableExpr*)lfirst(lc);
|
||||
if (cte->cterecursive) {
|
||||
/* Recursive cte does't support dn gather. */
|
||||
/* 递归 CTE 不支持分布式收集(dn gather) */
|
||||
((shipping_context*)cxt)->disable_dn_gather = true;
|
||||
}
|
||||
(void)stream_walker((Node*)cte->ctequery, (void *)cxt);
|
||||
}
|
||||
|
||||
// 恢复随机函数和等值连接函数的流式传输设置
|
||||
cxt->is_ecfunc_shippable = ecfunc_ori;
|
||||
cxt->is_randomfunc_shippable = random_ori;
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_limitoffset(Query* query, shipping_context *cxt)
|
||||
{
|
||||
/*
|
||||
* Random func is not allowed in CTE and limit.
|
||||
* 不允许在 LIMIT 子句中使用随机函数(random func)。
|
||||
*/
|
||||
bool random_ori = cxt->is_randomfunc_shippable;
|
||||
cxt->is_randomfunc_shippable = false;
|
||||
|
||||
// 分析 LIMIT 子句中的表达式
|
||||
(void)stream_walker((Node*)query->limitCount, (void *)cxt);
|
||||
|
||||
// 分析 OFFSET 子句中的表达式
|
||||
(void)stream_walker((Node*)query->limitOffset, (void *)cxt);
|
||||
|
||||
// 恢复随机函数的流式传输设置
|
||||
cxt->is_randomfunc_shippable = random_ori;
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_targetlist(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 如果允许在目标列表中使用函数,则设置 is_nextval_shippable 为 true,允许流式传输 nextval 函数
|
||||
if (cxt->allow_func_in_targetlist) {
|
||||
cxt->is_nextval_shippable = true;
|
||||
// 遍历目标列表中的表达式,检查是否可以进行流式传输
|
||||
if (expression_tree_walker((Node*)query->targetList, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
// 恢复 is_nextval_shippable 设置,并将 allow_func_in_targetlist 设置为 false
|
||||
cxt->is_nextval_shippable = false;
|
||||
cxt->allow_func_in_targetlist = false;
|
||||
} else {
|
||||
// 如果不允许在目标列表中使用函数,仅遍历目标列表中的表达式,检查是否可以进行流式传输
|
||||
if (expression_tree_walker((Node*)query->targetList, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_jointree(Query* query, shipping_context *cxt)
|
||||
{
|
||||
if (query->jointree != NULL &&
|
||||
expression_tree_walker((Node*)query->jointree->fromlist, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
if (query->jointree != NULL && stream_walker((Node*)query->jointree->quals, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
// 检查是否存在联接树(JOIN tree)
|
||||
if (query->jointree != NULL) {
|
||||
// 遍历联接树中的FROM子句(fromlist),检查是否可以进行流式传输
|
||||
if (expression_tree_walker((Node*)query->jointree->fromlist, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
// 遍历联接树中的WHERE子句(quals),检查是否可以进行流式传输
|
||||
if (stream_walker((Node*)query->jointree->quals, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_having(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 遍历 HAVING 子句(havingQual),检查是否可以进行流式传输
|
||||
if (stream_walker((Node*)query->havingQual, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query_window(Query* query, shipping_context *cxt)
|
||||
{
|
||||
// 遍历窗口函数(WINDOW)子句,检查是否可以进行流式传输
|
||||
if (expression_tree_walker((Node*)query->windowClause, (bool (*)())stream_walker, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_finalize_cxt(Query* query, shipping_context *cxt)
|
||||
{
|
||||
ListCell *lc = NULL;
|
||||
|
||||
// 如果当前查询可以进行流式传输
|
||||
if (cxt->current_shippable) {
|
||||
// 遍历查询的每个表(RangeTblEntry)
|
||||
foreach(lc, query->rtable) {
|
||||
RangeTblEntry *tmp_rte = (RangeTblEntry *) lfirst(lc);
|
||||
|
||||
// 如果表是子查询(RTE_SUBQUERY)且子查询不可推送(can_push = false)
|
||||
if (tmp_rte->rtekind == RTE_SUBQUERY && !tmp_rte->subquery->can_push) {
|
||||
// 当前查询不可进行流式传输
|
||||
cxt->current_shippable = false;
|
||||
break;
|
||||
break; // 跳出循环
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// 如果当前查询不可进行流式传输
|
||||
else {
|
||||
// 如果查询的表为空(rtable为空),或者表包含关系、函数或值表达式(RTE_RELATION、RTE_FUNCTION、RTE_VALUES)
|
||||
if (query->rtable == NIL) {
|
||||
// 当前查询不可进行流式传输
|
||||
cxt->query_shippable = false;
|
||||
} else {
|
||||
// 遍历查询的每个表(RangeTblEntry)
|
||||
foreach(lc, query->rtable) {
|
||||
RangeTblEntry *tmp_rte = (RangeTblEntry *) lfirst(lc);
|
||||
|
||||
// 如果表是关系、函数或值表达式
|
||||
if (tmp_rte->rtekind == RTE_RELATION ||
|
||||
tmp_rte->rtekind == RTE_FUNCTION ||
|
||||
tmp_rte->rtekind == RTE_VALUES) {
|
||||
// 当前查询不可进行流式传输
|
||||
cxt->query_shippable = false;
|
||||
break;
|
||||
break; // 跳出循环
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_query(Query* query, shipping_context *cxt)
|
||||
{
|
||||
/* Set default value of query's can_push. We will modify it according to conditions below. */
|
||||
/* 设置查询的默认 can_push 属性。后续将根据以下条件修改它。 */
|
||||
bool save_shippable = cxt->current_shippable;
|
||||
cxt->current_shippable = true;
|
||||
|
||||
/* 将查询添加到查询列表中,并更新查询计数器。 */
|
||||
cxt->query_list = lappend(cxt->query_list, query);
|
||||
cxt->query_count = cxt->query_count + 1;
|
||||
|
||||
stream_walker_query_update(query, cxt);
|
||||
stream_walker_query_recursive(query, cxt);
|
||||
stream_walker_query_distinct(query, cxt);
|
||||
stream_walker_query_returning(query, cxt);
|
||||
stream_walker_query_rtable(query, cxt);
|
||||
stream_walker_query_exec_direct(query, cxt);
|
||||
stream_walker_query_merge(query, cxt);
|
||||
stream_walker_query_upsert(query, cxt);
|
||||
stream_walker_query_cte(query, cxt);
|
||||
stream_walker_query_limitoffset(query, cxt);
|
||||
stream_walker_query_targetlist(query, cxt);
|
||||
stream_walker_query_jointree(query, cxt);
|
||||
stream_walker_query_having(query, cxt);
|
||||
stream_walker_query_window(query, cxt);
|
||||
/* 以下一系列函数调用用于检查和修改查询的可推送性属性。 */
|
||||
stream_walker_query_update(query, cxt); // 检查更新操作的条件
|
||||
stream_walker_query_recursive(query, cxt); // 检查是否包含递归查询
|
||||
stream_walker_query_distinct(query, cxt); // 检查是否包含 DISTINCT 子句
|
||||
stream_walker_query_returning(query, cxt); // 检查是否包含 RETURNING 子句
|
||||
stream_walker_query_rtable(query, cxt); // 检查查询的关系表
|
||||
stream_walker_query_exec_direct(query, cxt); // 检查是否包含 EXECUTE DIRECT 语句
|
||||
stream_walker_query_merge(query, cxt); // 检查 MERGE 操作的条件
|
||||
stream_walker_query_upsert(query, cxt); // 检查 UPSERT 操作的条件
|
||||
stream_walker_query_cte(query, cxt); // 检查公共表达式(CTE)的条件
|
||||
stream_walker_query_limitoffset(query, cxt); // 检查 LIMIT 和 OFFSET 子句
|
||||
stream_walker_query_targetlist(query, cxt); // 检查目标列表中的表达式
|
||||
stream_walker_query_jointree(query, cxt); // 检查连接树
|
||||
stream_walker_query_having(query, cxt); // 检查 HAVING 子句
|
||||
stream_walker_query_window(query, cxt); // 检查窗口函数
|
||||
|
||||
stream_walker_query_insertinto_rep(query, cxt);
|
||||
/* mark shippable flag based on rte shippbility */
|
||||
stream_walker_query_insertinto_rep(query, cxt); // 检查插入到复制表的条件
|
||||
|
||||
/* 根据 RangeTblEntry(表达式)的可推送性属性来标记 shippable 标志。 */
|
||||
stream_walker_finalize_cxt(query, cxt);
|
||||
|
||||
/* Mark query's can_push and global_shippable flag. */
|
||||
/* 设置查询的 can_push 和全局可推送性标志。 */
|
||||
query->can_push = cxt->current_shippable;
|
||||
cxt->global_shippable = cxt->global_shippable && cxt->current_shippable;
|
||||
|
||||
/* 从查询列表中删除当前查询,恢复之前的可推送性属性。 */
|
||||
cxt->query_list = list_delete(cxt->query_list, query);
|
||||
cxt->current_shippable = save_shippable;
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_target_entry(TargetEntry* te, shipping_context *cxt)
|
||||
{
|
||||
/* 检查目标条目的表达式是否包含不支持的表达式,如果包含则将可推送性标志设为 false。 */
|
||||
if (contain_unsupport_expression((Node*)te->expr, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
/* Handle case like 'select t from t;' */
|
||||
|
||||
/* 处理类似 'select t from t;' 这样的情况,其中目标是表的情况,不支持流式传输。 */
|
||||
if (IsA(te->expr, Var) && !te->resjunk && ((Var*)te->expr)->varattno == 0) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -457,6 +550,7 @@ static void stream_walker_target_entry(TargetEntry* te, shipping_context *cxt)
|
|||
cxt->current_shippable = false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ENABLE_MULTIPLE_NODES
|
||||
static bool vector_search_func_shippable(Oid funcid)
|
||||
{
|
||||
|
|
@ -467,12 +561,17 @@ static void stream_walker_func_expr(FuncExpr* func, shipping_context *cxt)
|
|||
{
|
||||
uint32 i = 0;
|
||||
|
||||
/*
|
||||
* 如果函数是可推送的函数,并且函数的参数中包含不支持的表达式,
|
||||
* 则将可推送性标志设为 false。
|
||||
*/
|
||||
if (pgxc_is_shippable_func_contain_any(func->funcid)) {
|
||||
/* the args type of concat() and concat_ws() contains ANY, that may cause unshippable */
|
||||
if (contain_unsupport_expression((Node*)func->args, (void *)cxt)) {
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* 如果函数不是可推送的函数,则将可推送性标志设为 false。 */
|
||||
if (!pgxc_is_func_shippable(func->funcid, cxt)) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -481,7 +580,11 @@ static void stream_walker_func_expr(FuncExpr* func, shipping_context *cxt)
|
|||
securec_check_ss_c(sprintf_rc, "\0", "\0");
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
/* EC function is of record type, but we ship it in some cases */
|
||||
|
||||
/*
|
||||
* 如果函数的返回类型是 RECORDOID 且不是可推送的向量搜索函数,
|
||||
* 则将可推送性标志设为 false。
|
||||
*/
|
||||
if (func->funcid != ECEXTENSIONFUNCOID && func->funcid != ECHADOOPFUNCOID &&
|
||||
func->funcresulttype == RECORDOID && !vector_search_func_shippable(func->funcid)) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
|
|
@ -491,6 +594,10 @@ static void stream_walker_func_expr(FuncExpr* func, shipping_context *cxt)
|
|||
securec_check_ss_c(sprintf_rc, "\0", "\0");
|
||||
cxt->current_shippable = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* 如果函数是不支持的函数之一,则将可推送性标志设为 false。
|
||||
*/
|
||||
for (i = 0; i < lengthof(unsupport_func); i++) {
|
||||
if (func->funcid == unsupport_func[i]) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
|
|
@ -502,6 +609,10 @@ static void stream_walker_func_expr(FuncExpr* func, shipping_context *cxt)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 如果函数是 Nextval() 函数且 'lastval_supported' 或 'enable_beta_features' 打开,
|
||||
* 则将可推送性标志设为 false。
|
||||
*/
|
||||
if (NEXTVALFUNCOID == func->funcid &&
|
||||
(g_instance.attr.attr_common.lastval_supported || u_sess->attr.attr_common.enable_beta_features)) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
|
|
@ -513,8 +624,13 @@ static void stream_walker_func_expr(FuncExpr* func, shipping_context *cxt)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_aggref(Aggref* aggref, shipping_context *cxt)
|
||||
{
|
||||
/*
|
||||
* 如果聚合引用使用了不支持的聚合函数,
|
||||
* 则将可推送性标志设为 false。
|
||||
*/
|
||||
if (contain_unsupport_function(aggref->aggfnoid)) {
|
||||
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -525,18 +641,19 @@ static void stream_walker_aggref(Aggref* aggref, shipping_context *cxt)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void stream_walker_coerce(CoerceViaIO* cvio, shipping_context *cxt)
|
||||
{
|
||||
/* Query like:
|
||||
/*
|
||||
* 对于类似以下的查询:
|
||||
* select (a.*)::text from view a;
|
||||
* can't be shipped, since the defination of VIEW doesn't exists on datanode.
|
||||
* 无法进行推送,因为 VIEW 的定义在数据节点上不存在。
|
||||
*/
|
||||
if (IsA(cvio->arg, Var)) {
|
||||
Var* var = (Var*)(cvio->arg);
|
||||
if (var->varattno == InvalidAttrNumber) {
|
||||
/*
|
||||
* Sometimes Var references outer relation, we find the corresponding Query according to the
|
||||
* context->query_list and the varlevelsup.
|
||||
* 有时 Var 引用外部关系,我们可以根据 context->query_list 和 varlevelsup 找到相应的查询。
|
||||
*/
|
||||
int len = list_length(cxt->query_list);
|
||||
int query_level = len - var->varlevelsup;
|
||||
|
|
@ -555,25 +672,37 @@ static void stream_walker_coerce(CoerceViaIO* cvio, shipping_context *cxt)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void inh_shipping_context(shipping_context *dst, shipping_context *src)
|
||||
{
|
||||
/* 如果源上下文中的 current_shippable 标志为 false,将其继承到目标上下文中 */
|
||||
if (!src->current_shippable) {
|
||||
dst->current_shippable = src->current_shippable;
|
||||
}
|
||||
|
||||
/* 如果源上下文中的 query_shippable 标志为 false,将其继承到目标上下文中 */
|
||||
if (!src->query_shippable) {
|
||||
dst->query_shippable = src->query_shippable;
|
||||
}
|
||||
|
||||
/* 如果源上下文中的 global_shippable 标志为 false,将其继承到目标上下文中 */
|
||||
if (!src->global_shippable) {
|
||||
dst->global_shippable = src->global_shippable;
|
||||
}
|
||||
|
||||
/* 如果源上下文中的 disable_dn_gather 标志为 true,将其继承到目标上下文中 */
|
||||
if (src->disable_dn_gather) {
|
||||
dst->disable_dn_gather = src->disable_dn_gather;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
函数的主要功能是遍历查询的范围表,检查其中的每个范围表项,
|
||||
以确定是否存在不支持的表或具有不支持特性的表。如果找到不支持的表,
|
||||
将会设置 context->current_shippable 为 false,并返回 true。
|
||||
否则,返回 false 表示没有发现不支持的表。
|
||||
|
||||
*/
|
||||
static bool contains_unsupport_tables(List* rtable, Query* query, shipping_context* context)
|
||||
{
|
||||
ListCell* item = NULL;
|
||||
|
|
@ -584,10 +713,7 @@ static bool contains_unsupport_tables(List* rtable, Query* query, shipping_conte
|
|||
errno_t rc = memcpy_s(&scontext, sizeof(scontext), context, sizeof(scontext));
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
/* random func and EC func can't be shippable when it appears in CTE,
|
||||
* we set context->is_randomfunc_shippable and context->is_ecfunc_shippable
|
||||
* be false in stream_walker when walker in CTE.
|
||||
*/
|
||||
/* 如果随机函数和 EC 函数出现在 CTE 中,则不支持,我们在 stream_walker 中将它们的标志设置为 false */
|
||||
scontext.is_randomfunc_shippable =
|
||||
u_sess->opt_cxt.is_randomfunc_shippable && context->is_randomfunc_shippable && IS_STREAM_PLAN;
|
||||
scontext.is_ecfunc_shippable = context->is_ecfunc_shippable && IS_STREAM_PLAN;
|
||||
|
|
@ -595,20 +721,25 @@ static bool contains_unsupport_tables(List* rtable, Query* query, shipping_conte
|
|||
scontext.query_shippable = true;
|
||||
scontext.global_shippable = true;
|
||||
scontext.disable_dn_gather = false;
|
||||
|
||||
/* 遍历查询的范围表 */
|
||||
foreach (item, rtable) {
|
||||
RangeTblEntry* rte = (RangeTblEntry*)lfirst(item);
|
||||
rIdx++;
|
||||
|
||||
switch (rte->rtekind) {
|
||||
case RTE_RELATION: {
|
||||
/* 检查表是否包含不支持的特性 */
|
||||
if (table_contain_unsupport_feature(rte->relid, query) &&
|
||||
!u_sess->attr.attr_sql.enable_cluster_resize) {
|
||||
context->current_shippable = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 获取表的分布类型 */
|
||||
rte->locator_type = GetLocatorType(rte->relid);
|
||||
/* SQLONHADOOP has to support RROBIN MODULO distribution mode */
|
||||
|
||||
/* 对于 SQLONHADOOP 表,必须支持 RROBIN MODULO 分布模式 */
|
||||
if (((rte->locator_type == LOCATOR_TYPE_RROBIN || rte->locator_type == LOCATOR_TYPE_MODULO) &&
|
||||
rte->relkind != RELKIND_FOREIGN_TABLE && rte->relkind != RELKIND_STREAM)) {
|
||||
sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
|
|
@ -619,6 +750,8 @@ static bool contains_unsupport_tables(List* rtable, Query* query, shipping_conte
|
|||
context->current_shippable = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 检查表是否具有继承关系,继承表不能被推送 */
|
||||
if (rte->inh && has_subclass(rte->relid)) {
|
||||
sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -629,6 +762,7 @@ static bool contains_unsupport_tables(List* rtable, Query* query, shipping_conte
|
|||
return true;
|
||||
}
|
||||
|
||||
/* 如果是插入操作的目标表,记录其分布类型 */
|
||||
if (query->commandType == CMD_INSERT && list_length(rtable) == 2 && rIdx == 1)
|
||||
target_table_loctype = rte->locator_type;
|
||||
|
||||
|
|
@ -636,15 +770,14 @@ static bool contains_unsupport_tables(List* rtable, Query* query, shipping_conte
|
|||
}
|
||||
case RTE_SUBQUERY: {
|
||||
/*
|
||||
* We allow to push the nextval and uuid_generate_v1 to DN for the following query:
|
||||
* insert into t1 select nextval('seq1'),* from t2;
|
||||
* insert into t1 select uuid_generate_v1, * from t2;
|
||||
* It fullfill the following conditions:
|
||||
* 1. Top level query is Insert.
|
||||
* 2. There are two RTE in rtable, the first one is the target table,
|
||||
* which should be hash/range/list distributed.
|
||||
* The second one is a subquery
|
||||
* We allow the the nextval and uuid_generate_v1 in the target list of the subquery.
|
||||
* 我们允许将 nextval 和 uuid_generate_v1 推送到 DN,例如下面的查询:
|
||||
* insert into t1 select nextval('seq1'),* from t2;
|
||||
* insert into t1 select uuid_generate_v1, * from t2;
|
||||
* 这满足以下条件:
|
||||
* 1. 顶级查询是 INSERT。
|
||||
* 2. 范围表中有两个 RTE,第一个是目标表,应该是哈希/范围/列表分布。
|
||||
* 第二个是子查询。
|
||||
* 我们允许在子查询的目标列表中使用 nextval 和 uuid_generate_v1。
|
||||
*/
|
||||
bool supportLoctype = (target_table_loctype == LOCATOR_TYPE_HASH ||
|
||||
IsLocatorDistributedBySlice(target_table_loctype) ||
|
||||
|
|
@ -654,8 +787,10 @@ static bool contains_unsupport_tables(List* rtable, Query* query, shipping_conte
|
|||
scontext.allow_func_in_targetlist = true;
|
||||
}
|
||||
|
||||
/* 递归检查子查询 */
|
||||
(void)stream_walker((Node*)rte->subquery, (void*)(&scontext));
|
||||
|
||||
/* 继承子查询的上下文信息 */
|
||||
inh_shipping_context(context, &scontext);
|
||||
|
||||
scontext.allow_func_in_targetlist = false;
|
||||
|
|
@ -663,15 +798,19 @@ static bool contains_unsupport_tables(List* rtable, Query* query, shipping_conte
|
|||
break;
|
||||
}
|
||||
case RTE_FUNCTION: {
|
||||
/* 递归检查函数表达式 */
|
||||
(void)stream_walker((Node*)rte->funcexpr, (void*)(&scontext));
|
||||
|
||||
/* 继承上下文信息 */
|
||||
inh_shipping_context(context, &scontext);
|
||||
|
||||
break;
|
||||
}
|
||||
case RTE_VALUES: {
|
||||
/* 递归检查值表达式 */
|
||||
(void)stream_walker((Node*)rte->values_lists, (void*)(&scontext));
|
||||
|
||||
/* 继承上下文信息 */
|
||||
inh_shipping_context(context, &scontext);
|
||||
|
||||
break;
|
||||
|
|
@ -685,13 +824,17 @@ static bool contains_unsupport_tables(List* rtable, Query* query, shipping_conte
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool rel_contain_unshippable_feature(RangeTblEntry* rte, shipping_context* context, CmdType commandType)
|
||||
{
|
||||
errno_t sprintf_rc = 0;
|
||||
|
||||
if (rte->rtekind == RTE_RELATION) {
|
||||
if (commandType == CMD_INSERT) {
|
||||
// 打开与范围表项关联的关系
|
||||
Relation rel = relation_open(rte->relid, AccessShareLock);
|
||||
/* if the result relation has oid column, go to old way */
|
||||
|
||||
// 如果目标表具有 OID 列,无法进行推送
|
||||
if (rel->rd_rel->relhasoids) {
|
||||
relation_close(rel, AccessShareLock);
|
||||
sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
|
|
@ -701,14 +844,17 @@ static bool rel_contain_unshippable_feature(RangeTblEntry* rte, shipping_context
|
|||
context->current_shippable = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 关闭关系
|
||||
relation_close(rel, AccessShareLock);
|
||||
|
||||
/*
|
||||
* Check if nextval and uuid_generate_v1 can be shipped to DN or not.
|
||||
* We don't allow FQS for nextval and uuid_generate_v1
|
||||
* But in order to increase the performance of bulkload, we allow streaming plan
|
||||
* if 1. the target table is hash/range/list distributed table
|
||||
* 2. the nextval and uuid_generate_v1 function existed in the target list of the result table
|
||||
* 检查是否可以将 nextval 和 uuid_generate_v1 推送到 DN。
|
||||
* 我们不允许将 FQS 用于 nextval 和 uuid_generate_v1,
|
||||
* 但是为了提高批量加载的性能,我们允许流式计划,
|
||||
* 如果满足以下条件:
|
||||
* 1. 目标表是哈希/范围/列表分布表
|
||||
* 2. nextval 和 uuid_generate_v1 函数存在于结果表的目标列表中
|
||||
*/
|
||||
if (rte->locator_type == LOCATOR_TYPE_HASH || IsLocatorDistributedBySlice(rte->locator_type) ||
|
||||
rte->locator_type == LOCATOR_TYPE_NONE) {
|
||||
|
|
@ -716,14 +862,16 @@ static bool rel_contain_unshippable_feature(RangeTblEntry* rte, shipping_context
|
|||
}
|
||||
}
|
||||
|
||||
/* Disallow volatile function shippable when the target relation is replicated. */
|
||||
/* 如果目标表是复制表,则禁止推送易失性函数 */
|
||||
if (IsLocatorReplicated(rte->locator_type)) {
|
||||
context->disallow_volatile_func_shippable = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Attempt to check there are all deferable triggers or not, if yes try to push it to datdanodes.
|
||||
* Then stream_walker could refer true under constraints DEFERABLE.
|
||||
|
|
@ -737,7 +885,7 @@ static bool check_trigger_deferable(Relation rel)
|
|||
|
||||
List *indexList = (List *)RelationGetIndexList(rel);
|
||||
|
||||
/* no constaint then retrun true directly. */
|
||||
/* 如果没有索引,则直接返回 true */
|
||||
if (indexList == NIL) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -745,6 +893,7 @@ static bool check_trigger_deferable(Relation rel)
|
|||
foreach (item, indexList) {
|
||||
Oid indexoid = lfirst_oid(item);
|
||||
|
||||
/* 在系统缓存中查找索引元组 */
|
||||
indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
|
||||
if (!HeapTupleIsValid(indexTuple)) {
|
||||
ereport(ERROR,
|
||||
|
|
@ -753,18 +902,22 @@ static bool check_trigger_deferable(Relation rel)
|
|||
}
|
||||
|
||||
indexStruct = (Form_pg_index)GETSTRUCT(indexTuple);
|
||||
|
||||
/* 检查索引是否有效,无效索引会被跳过 */
|
||||
if (!IndexIsValid(indexStruct)) {
|
||||
deferablesCheck = true;
|
||||
ReleaseSysCache(indexTuple);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 如果索引不是立即触发的,则将 deferablesCheck 设置为 true,并释放系统缓存 */
|
||||
if (!indexStruct->indimmediate) {
|
||||
deferablesCheck = true;
|
||||
ReleaseSysCache(indexTuple);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* if indexTuple is invalid or normal(not deferable), then cannot pushable. */
|
||||
/* 如果索引是立即触发的,将 deferablesCheck 设置为 true,并释放系统缓存 */
|
||||
deferablesCheck = true;
|
||||
ReleaseSysCache(indexTuple);
|
||||
}
|
||||
|
|
@ -772,19 +925,21 @@ static bool check_trigger_deferable(Relation rel)
|
|||
return deferablesCheck;
|
||||
}
|
||||
|
||||
static bool table_contain_unsupport_feature(Oid relid, Query* query)
|
||||
|
||||
static bool table_contain_unsupport_feature(Oid relid, Query* query)//该函数用于检查指定的表是否包含不支持流式操作的特性
|
||||
{
|
||||
Relation rel;
|
||||
errno_t sprintf_rc = 0;
|
||||
|
||||
// 尝试打开关系(表)
|
||||
rel = try_relation_open(relid, NoLock);
|
||||
if (rel != NULL) {
|
||||
/* If contains system relation, we will not output the not shipping reasion */
|
||||
// 如果关系属于系统命名空间,不记录不支持的原因
|
||||
if (rel->rd_rel->relnamespace == PG_CATALOG_NAMESPACE) {
|
||||
u_sess->opt_cxt.not_shipping_info->need_log = false;
|
||||
}
|
||||
|
||||
/* globel temp table could not ship */
|
||||
// 全局临时表不支持流操作
|
||||
if (rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) {
|
||||
sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
||||
NOTPLANSHIPPING_LENGTH,
|
||||
|
|
@ -794,7 +949,7 @@ static bool table_contain_unsupport_feature(Oid relid, Query* query)
|
|||
return true;
|
||||
}
|
||||
|
||||
/* Currently dml with trigger can not get stream plan. */
|
||||
// 当前带有触发器的 DML 操作不支持流操作
|
||||
if (rel->rd_rel->relhastriggers && NULL != rel->trigdesc &&
|
||||
((query->commandType == CMD_INSERT && pgxc_has_trigger_for_event(TRIGGER_TYPE_INSERT, rel->trigdesc)) ||
|
||||
(query->commandType == CMD_UPDATE && check_trigger_deferable(rel) &&
|
||||
|
|
@ -815,19 +970,24 @@ static bool table_contain_unsupport_feature(Oid relid, Query* query)
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool contain_unsupport_function(Oid funcId)
|
||||
{
|
||||
// 如果函数的 Oid 大于等于 FirstNormalObjectId,表示它是用户定义的函数,不支持流式操作
|
||||
if (funcId >= FirstNormalObjectId)
|
||||
return true;
|
||||
|
||||
// 遍历不支持的函数列表,如果给定的 funcId 匹配其中一个,则表示不支持流式操作
|
||||
for (uint i = 0; i < lengthof(unsupport_func); i++) {
|
||||
if (funcId == unsupport_func[i])
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果没有匹配的不支持函数,返回 false,表示支持流式操作
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool contain_unsupport_expression(Node* expr, void* context)
|
||||
{
|
||||
if (expr == NULL) {
|
||||
|
|
|
|||
|
|
@ -53,38 +53,38 @@
|
|||
extern void set_path_seed_factor_zero();
|
||||
|
||||
typedef struct convert_testexpr_context {
|
||||
PlannerInfo* root;
|
||||
List* subst_nodes; /* Nodes to substitute for Params */
|
||||
PlannerInfo* root; // 指向查询规划器的信息结构的指针
|
||||
List* subst_nodes; // 用于替换 Params 的节点列表
|
||||
} convert_testexpr_context;
|
||||
|
||||
typedef struct process_sublinks_context {
|
||||
PlannerInfo* root;
|
||||
bool isTopQual;
|
||||
PlannerInfo* root; // 指向查询规划器的信息结构的指针
|
||||
bool isTopQual; // 指示是否为顶层限定表达式的布尔值
|
||||
} process_sublinks_context;
|
||||
|
||||
typedef struct finalize_primnode_context {
|
||||
PlannerInfo* root;
|
||||
Bitmapset* paramids; /* Non-local PARAM_EXEC paramids found */
|
||||
PlannerInfo* root; // 指向查询规划器的信息结构的指针
|
||||
Bitmapset* paramids; // 找到的非本地 PARAM_EXEC 参数标识符的位图集合
|
||||
} finalize_primnode_context;
|
||||
|
||||
typedef struct varWalkerContext {
|
||||
int maxLevelsUp; /* Keep level up of var */
|
||||
int maxLevelsUp; // 保留变量的上一级层次
|
||||
} varWalkerContext;
|
||||
|
||||
typedef struct pull_node_clause
|
||||
{
|
||||
List *nodeList;
|
||||
List *nameList;
|
||||
char *name;
|
||||
bool recurse;
|
||||
int flag;
|
||||
typedef struct pull_node_clause {
|
||||
List* nodeList; // 节点列表
|
||||
List* nameList; // 名称列表
|
||||
char* name; // 名称
|
||||
bool recurse; // 是否递归
|
||||
int flag; // 标志
|
||||
} pull_node_clause;
|
||||
|
||||
typedef struct push_qual_context {
|
||||
int varno; /* Var no. */
|
||||
List* qual_list; /* Find qual list. */
|
||||
int varno; // 变量编号
|
||||
List* qual_list; // 查找条件列表
|
||||
} push_qual_context;
|
||||
|
||||
|
||||
/* flags bits for pull_expr_walker and pull expr_mutator */
|
||||
#define PE_OPEXPR 0x01 /* pull expr of op expr */
|
||||
#define PE_NULLTEST 0x02 /* pull expr of null test */
|
||||
|
|
@ -149,39 +149,43 @@ static bool contain_outer_selfref_walker(Node *node, Index *depth);
|
|||
|
||||
static char *denominate_sublink_name(int sublink_counter)
|
||||
{
|
||||
int nRet = 0;
|
||||
char subname[SUBLINK_COUNTER];
|
||||
int nRet = 0; // 用于存储sprintf_s函数的返回值,以检查是否成功
|
||||
char subname[SUBLINK_COUNTER]; // 用于存储子查询名称的字符数组
|
||||
|
||||
// 使用sprintf_s函数将子查询名称格式化为"sublink_x",其中x是子查询计数器的值
|
||||
nRet = sprintf_s(subname, sizeof(subname), "sublink_%d", sublink_counter);
|
||||
securec_check_ss_c(nRet, "\0", "\0");
|
||||
securec_check_ss_c(nRet, "\0", "\0"); // 检查sprintf_s函数是否成功执行
|
||||
|
||||
int newlen = strlen(subname) + 1;
|
||||
char *subquery_name = (char *)palloc0(newlen);
|
||||
nRet = strncpy_s(subquery_name, newlen, subname, strlen(subname));
|
||||
securec_check_ss_c(nRet, "\0", "\0");
|
||||
int newlen = strlen(subname) + 1; // 计算新的字符串长度,包括null终止符
|
||||
char *subquery_name = (char *)palloc0(newlen); // 分配新的字符串内存,并初始化为0
|
||||
nRet = strncpy_s(subquery_name, newlen, subname, strlen(subname)); // 复制子查询名称到新的字符串中
|
||||
securec_check_ss_c(nRet, "\0", "\0"); // 检查strncpy_s函数是否成功执行
|
||||
|
||||
return subquery_name;
|
||||
return subquery_name; // 返回新创建的子查询名称
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
all_replicate_table(Query *query)
|
||||
{
|
||||
Relids varnos = get_relids_in_jointree((Node *) query->jointree, false);
|
||||
int attnum = 0;
|
||||
Relids varnos = get_relids_in_jointree((Node *) query->jointree, false); // 获取查询中所有关系表的变量标识符
|
||||
int attnum = 0; // 用于迭代变量标识符的整数变量
|
||||
|
||||
while ((attnum = bms_first_member(varnos)) >= 0) {
|
||||
RangeTblEntry *r_table = (RangeTblEntry*)rt_fetch(attnum, query->rtable);
|
||||
if (r_table->rtekind == RTE_RELATION) {
|
||||
if (GetLocatorType(r_table->relid) == LOCATOR_TYPE_REPLICATED)
|
||||
continue;
|
||||
while ((attnum = bms_first_member(varnos)) >= 0) { // 遍历变量标识符集合
|
||||
RangeTblEntry *r_table = (RangeTblEntry*)rt_fetch(attnum, query->rtable); // 获取范围表中的条目
|
||||
|
||||
if (r_table->rtekind == RTE_RELATION) { // 如果范围表条目是关系表
|
||||
if (GetLocatorType(r_table->relid) == LOCATOR_TYPE_REPLICATED) // 检查关系表是否为复制表
|
||||
continue; // 如果是复制表,继续下一次迭代
|
||||
}
|
||||
|
||||
return false;
|
||||
return false; // 如果不是复制表,返回 false
|
||||
}
|
||||
|
||||
return true;
|
||||
return true; // 如果所有表都是复制表,返回 true
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Select a PARAM_EXEC number to identify the given Var as a parameter for
|
||||
* the current subquery, or for a nestloop's inner scan.
|
||||
|
|
@ -189,56 +193,61 @@ all_replicate_table(Query *query)
|
|||
*/
|
||||
static int assign_param_for_var(PlannerInfo* root, Var* var)
|
||||
{
|
||||
ListCell* ppl = NULL;
|
||||
PlannerParamItem* pitem = NULL;
|
||||
Index levelsup;
|
||||
ListCell* ppl = NULL; // 用于遍历参数列表的链表迭代器
|
||||
PlannerParamItem* pitem = NULL; // 用于存储参数项
|
||||
Index levelsup; // 变量所属查询级别的索引
|
||||
|
||||
/* Find the query level the Var belongs to */
|
||||
/* 找到变量所属的查询级别 */
|
||||
for (levelsup = var->varlevelsup; levelsup > 0; levelsup--)
|
||||
root = root->parent_root;
|
||||
|
||||
/* If there's already a matching PlannerParamItem there, just use it */
|
||||
/* 如果已经存在匹配的 PlannerParamItem,直接使用它 */
|
||||
foreach (ppl, root->plan_params) {
|
||||
pitem = (PlannerParamItem*)lfirst(ppl);
|
||||
if (IsA(pitem->item, Var)) {
|
||||
Var* pvar = (Var*)pitem->item;
|
||||
|
||||
/*
|
||||
* This comparison must match _equalVar(), except for ignoring
|
||||
* varlevelsup. Note that _equalVar() ignores the location.
|
||||
* 这个比较必须与 _equalVar() 匹配,除了忽略 varlevelsup。
|
||||
* 注意,_equalVar() 忽略了位置信息。
|
||||
*/
|
||||
if (pvar->varno == var->varno && pvar->varattno == var->varattno && pvar->vartype == var->vartype &&
|
||||
pvar->vartypmod == var->vartypmod && pvar->varcollid == var->varcollid &&
|
||||
pvar->varnoold == var->varnoold && pvar->varoattno == var->varoattno)
|
||||
return pitem->paramId;
|
||||
return pitem->paramId; // 返回现有参数标识符
|
||||
}
|
||||
}
|
||||
|
||||
/* Nope, so make a new one */
|
||||
var = (Var*)copyObject(var);
|
||||
/* 没有匹配的参数标识符,创建一个新的参数标识符 */
|
||||
var = (Var*)copyObject(var); // 复制变量对象,以便修改 varlevelsup 为 0
|
||||
var->varlevelsup = 0;
|
||||
|
||||
pitem = makeNode(PlannerParamItem);
|
||||
pitem->item = (Node*)var;
|
||||
pitem->paramId = root->glob->nParamExec++;
|
||||
pitem = makeNode(PlannerParamItem); // 创建新的 PlannerParamItem 结构
|
||||
pitem->item = (Node*)var; // 设置参数项的节点为新的 Var
|
||||
pitem->paramId = root->glob->nParamExec++; // 分配新的参数标识符
|
||||
|
||||
root->plan_params = lappend(root->plan_params, pitem);
|
||||
root->plan_params = lappend(root->plan_params, pitem); // 将新的参数项添加到参数列表中
|
||||
|
||||
return pitem->paramId;
|
||||
return pitem->paramId; // 返回新分配的参数标识符
|
||||
}
|
||||
|
||||
|
||||
static Param* initParam(Var* var, int i)
|
||||
{
|
||||
Param* retval = makeNode(Param);
|
||||
retval->paramkind = PARAM_EXEC;
|
||||
retval->paramid = i;
|
||||
retval->paramtype = var->vartype;
|
||||
retval->paramtypmod = var->vartypmod;
|
||||
retval->paramcollid = var->varcollid;
|
||||
retval->location = var->location;
|
||||
return retval;
|
||||
Param* retval = makeNode(Param); // 创建一个 Param 结构的新实例
|
||||
|
||||
// 设置 Param 结构的字段值
|
||||
retval->paramkind = PARAM_EXEC; // 参数种类为 PARAM_EXEC
|
||||
retval->paramid = i; // 参数标识符
|
||||
retval->paramtype = var->vartype; // 参数的<E695B0><E79A84><EFBFBD>据类型
|
||||
retval->paramtypmod = var->vartypmod; // 参数的类型修饰符
|
||||
retval->paramcollid = var->varcollid; // 参数的碰撞标识符
|
||||
retval->location = var->location; // 参数的位置信息(源代码中的位置)
|
||||
|
||||
return retval; // 返回初始化后的 Param 结构
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate a Param node to replace the given Var,
|
||||
* which is expected to have varlevelsup > 0 (ie, it is not local).
|
||||
|
|
@ -251,12 +260,19 @@ static Param* replace_outer_var(PlannerInfo* root, Var* var)
|
|||
MOD_OPT_SUBPLAN,
|
||||
"Replaced var should be within valid range");
|
||||
|
||||
/* Find the Var in the appropriate plan_params, or add it if not present */
|
||||
/*
|
||||
* 确保变量的 varlevelsup 在有效范围内。
|
||||
* 它应该大于 0(表示外部查询层级)且小于当前查询层级。
|
||||
*/
|
||||
|
||||
/* 找到变量在适当的 plan_params 中,如果不存在则分配一个新的参数标识符 */
|
||||
i = assign_param_for_var(root, var);
|
||||
|
||||
/* 初始化 Param 结构,并返回替代的参数 */
|
||||
return initParam(var, i);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate a Param node to replace the given Var, which will be supplied
|
||||
* from an upper NestLoop join node.
|
||||
|
|
@ -270,11 +286,19 @@ Param* assign_nestloop_param_var(PlannerInfo* root, Var* var)
|
|||
|
||||
AssertEreport(var->varlevelsup == 0, MOD_OPT_SUBPLAN, "Replaced var should be at current level");
|
||||
|
||||
/*
|
||||
* 确保变量的 varlevelsup 等于 0,
|
||||
* 这意味着要替换的变量必须位于当前查询层级。
|
||||
*/
|
||||
|
||||
/* 找到变量在适当的 plan_params 中,如果不存在则分配一个新的参数标识符 */
|
||||
i = assign_param_for_var(root, var);
|
||||
|
||||
/* 初始化 Param 结构,并返回替代的参数 */
|
||||
return initParam(var, i);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @@GaussDB@@
|
||||
* Target : data partition
|
||||
|
|
@ -286,19 +310,21 @@ Param* assign_nestloop_param_var(PlannerInfo* root, Var* var)
|
|||
*/
|
||||
int assignPartIteratorParam(PlannerInfo* root)
|
||||
{
|
||||
PlannerParamItem* pitem = NULL;
|
||||
Node* itrParam = NULL; /* just for specifying value to PlannerParamItem.item, represent nothing. */
|
||||
PlannerParamItem* pitem = NULL; // 用于存储参数项的指针
|
||||
Node* itrParam = NULL; // 用于表示参数值的节点,不代表具体值
|
||||
|
||||
itrParam = (Node*)palloc(sizeof(Node));
|
||||
pitem = makeNode(PlannerParamItem);
|
||||
itrParam = (Node*)palloc(sizeof(Node)); // 分配一个新的节点(通常用于表示参数值)
|
||||
pitem = makeNode(PlannerParamItem); // 创建一个新的 PlannerParamItem 结构
|
||||
|
||||
pitem->item = (Node*)itrParam;
|
||||
pitem->paramId = SS_assign_special_param(root);
|
||||
root->plan_params = lappend(root->plan_params, pitem);
|
||||
pitem->item = (Node*)itrParam; // 将参数项的节点字段设置为新的节点
|
||||
pitem->paramId = SS_assign_special_param(root); // 分配一个特殊参数标识符(paramId)
|
||||
|
||||
return pitem->paramId;
|
||||
root->plan_params = lappend(root->plan_params, pitem); // 将参数项添加到查询规划器的参数列表中
|
||||
|
||||
return pitem->paramId; // 返回分配的参数标识符
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Select a PARAM_EXEC number to identify the given PlaceHolderVar as a
|
||||
* parameter for the current subquery, or for a nestloop's inner scan.
|
||||
|
|
@ -308,54 +334,59 @@ int assignPartIteratorParam(PlannerInfo* root)
|
|||
*/
|
||||
static int assign_param_for_placeholdervar(PlannerInfo* root, PlaceHolderVar* phv)
|
||||
{
|
||||
ListCell* ppl = NULL;
|
||||
PlannerParamItem* pitem = NULL;
|
||||
ListCell* ppl = NULL; // 用于遍历参数列表的链表迭代器
|
||||
PlannerParamItem* pitem = NULL; // 用于存储参数项
|
||||
Index levelsup;
|
||||
|
||||
/* Find the query level the PHV belongs to */
|
||||
/* 找到 PHV 属于的查询级别 */
|
||||
for (levelsup = phv->phlevelsup; levelsup > 0; levelsup--)
|
||||
root = root->parent_root;
|
||||
|
||||
/* If there's already a matching PlannerParamItem there, just use it */
|
||||
/* 如果已经存在匹配的 PlannerParamItem,直接使用它 */
|
||||
foreach (ppl, root->plan_params) {
|
||||
pitem = (PlannerParamItem*)lfirst(ppl);
|
||||
if (IsA(pitem->item, PlaceHolderVar)) {
|
||||
PlaceHolderVar* pphv = (PlaceHolderVar*)pitem->item;
|
||||
|
||||
/* We assume comparing the PHIDs is sufficient */
|
||||
/* 我们假设比较 PHID(占位符的唯一标识符)足够了 */
|
||||
if (pphv->phid == phv->phid)
|
||||
return pitem->paramId;
|
||||
return pitem->paramId; // 返回现有参数标识符
|
||||
}
|
||||
}
|
||||
|
||||
/* Nope, so make a new one */
|
||||
phv = (PlaceHolderVar*)copyObject(phv);
|
||||
/* 没有匹配的参数标识符,创建一个新的参数标识符 */
|
||||
phv = (PlaceHolderVar*)copyObject(phv); // 复制占位符变量对象
|
||||
if (phv->phlevelsup != 0) {
|
||||
IncrementVarSublevelsUp((Node*)phv, -((int)phv->phlevelsup), 0);
|
||||
AssertEreport(phv->phlevelsup == 0, MOD_OPT_SUBPLAN, "Placeholder var should be at current level");
|
||||
}
|
||||
|
||||
pitem = makeNode(PlannerParamItem);
|
||||
pitem->item = (Node*)phv;
|
||||
pitem->paramId = root->glob->nParamExec++;
|
||||
pitem = makeNode(PlannerParamItem); // 创建新的 PlannerParamItem 结构
|
||||
pitem->item = (Node*)phv; // 设置参数项的节点为新的占位符变量
|
||||
pitem->paramId = root->glob->nParamExec++; // 分配新的参数标识符
|
||||
|
||||
root->plan_params = lappend(root->plan_params, pitem);
|
||||
root->plan_params = lappend(root->plan_params, pitem); // 将新的参数项添加到参数列表中
|
||||
|
||||
return pitem->paramId;
|
||||
return pitem->paramId; // 返回新分配的参数标识符
|
||||
}
|
||||
|
||||
|
||||
static Param* getParamPHV(PlaceHolderVar* phv, int i)
|
||||
{
|
||||
Param* retval = makeNode(Param);
|
||||
retval->paramkind = PARAM_EXEC;
|
||||
retval->paramid = i;
|
||||
retval->paramtype = exprType((Node*)phv->phexpr);
|
||||
retval->paramtypmod = exprTypmod((Node*)phv->phexpr);
|
||||
retval->paramcollid = exprCollation((Node*)phv->phexpr);
|
||||
retval->location = -1;
|
||||
return retval;
|
||||
Param* retval = makeNode(Param); // 创建一个 Param 结构的新实例
|
||||
|
||||
// 设置 Param 结构的字段值
|
||||
retval->paramkind = PARAM_EXEC; // 参数种类为 PARAM_EXEC
|
||||
retval->paramid = i; // 参数标识符
|
||||
retval->paramtype = exprType((Node*)phv->phexpr); // 参数的数据类型
|
||||
retval->paramtypmod = exprTypmod((Node*)phv->phexpr); // 参数的类型修饰符
|
||||
retval->paramcollid = exprCollation((Node*)phv->phexpr); // 参数的碰撞标识符
|
||||
retval->location = -1; // 参数的位置信息设置为 -1(未知位置)
|
||||
|
||||
return retval; // 返回初始化后的 Param 结构
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate a Param node to replace the given PlaceHolderVar,
|
||||
* which is expected to have phlevelsup > 0 (ie, it is not local).
|
||||
|
|
@ -370,12 +401,19 @@ static Param* replace_outer_placeholdervar(PlannerInfo* root, PlaceHolderVar* ph
|
|||
MOD_OPT_SUBPLAN,
|
||||
"Placeholder var should be within valid range");
|
||||
|
||||
/* Find the PHV in the appropriate plan_params, or add it if not present */
|
||||
/*
|
||||
* 确保占位符变量的 phlevelsup 在有效范围内。
|
||||
* 它应该大于 0(表示外部查询层级)且小于当前查询层级。
|
||||
*/
|
||||
|
||||
/* 找到占位符变量在适当的 plan_params 中,如果不存在则分配一个新的参数标识符 */
|
||||
i = assign_param_for_placeholdervar(root, phv);
|
||||
|
||||
/* 获取替代的参数并返回 */
|
||||
return getParamPHV(phv, i);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate a Param node to replace the given PlaceHolderVar, which will be
|
||||
* supplied from an upper NestLoop join node.
|
||||
|
|
@ -388,32 +426,45 @@ Param* assign_nestloop_param_placeholdervar(PlannerInfo* root, PlaceHolderVar* p
|
|||
|
||||
AssertEreport(phv->phlevelsup == 0, MOD_OPT_SUBPLAN, "Placeholder var should be at current level");
|
||||
|
||||
/*
|
||||
* 确保占位符变量的 phlevelsup 等于 0,
|
||||
* 这意味着要替换的占位符变量必须位于当前查询层级。
|
||||
*/
|
||||
|
||||
/* 找到占位符变量在适当的 plan_params 中,如果不存在则分配一个新的参数标识符 */
|
||||
i = assign_param_for_placeholdervar(root, phv);
|
||||
|
||||
/* 获取替代的参数并返回 */
|
||||
return getParamPHV(phv, i);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate a Param node to replace the given Aggref
|
||||
* which is expected to have agglevelsup > 0 (ie, it is not local).
|
||||
*/
|
||||
static Param* replace_outer_agg(PlannerInfo* root, Aggref* agg)
|
||||
{
|
||||
Param* retval = NULL;
|
||||
PlannerParamItem* pitem = NULL;
|
||||
Param* retval = NULL; // 用于存储结果参数
|
||||
PlannerParamItem* pitem = NULL; // 用于存储参数项
|
||||
Index levelsup;
|
||||
|
||||
AssertEreport(agg->agglevelsup > 0 && agg->agglevelsup < root->query_level,
|
||||
MOD_OPT_SUBPLAN,
|
||||
"Agg expr should be within valid range");
|
||||
|
||||
/* Find the query level the Aggref belongs to */
|
||||
/*
|
||||
* 确保聚合表达式的 agglevelsup 在有效范围内。
|
||||
* 它应该大于 0(表示外部查询层级)且小于当前查询层级。
|
||||
*/
|
||||
|
||||
/* 找到聚合表达式在适当的 plan_params 中,如果不存在则分配一个新的参数标识符 */
|
||||
for (levelsup = agg->agglevelsup; levelsup > 0; levelsup--)
|
||||
root = root->parent_root;
|
||||
|
||||
/*
|
||||
* It does not seem worthwhile to try to match duplicate outer aggs. Just
|
||||
* make a new slot every time.
|
||||
* 看起来不值得尝试匹配重复的外部聚合表达式。
|
||||
* 每次都创建一个新的参数。
|
||||
*/
|
||||
agg = (Aggref*)copyObject(agg);
|
||||
IncrementVarSublevelsUp((Node*)agg, -((int)agg->agglevelsup), 0);
|
||||
|
|
@ -433,30 +484,36 @@ static Param* replace_outer_agg(PlannerInfo* root, Aggref* agg)
|
|||
retval->paramcollid = agg->aggcollid;
|
||||
retval->location = agg->location;
|
||||
|
||||
return retval;
|
||||
return retval; // 返回初始化后的 Param 结构
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate a Param node to replace the given GroupingFunc expression which is
|
||||
* expected to have agglevelsup > 0 (ie, it is not local).
|
||||
*/
|
||||
static Param* replace_outer_grouping(PlannerInfo* root, GroupingFunc* grp)
|
||||
{
|
||||
Param* retval = NULL;
|
||||
PlannerParamItem* pitem = NULL;
|
||||
Param* retval = NULL; // 用于存储结果参数
|
||||
PlannerParamItem* pitem = NULL; // 用于存储参数项
|
||||
Index levelsup;
|
||||
|
||||
AssertEreport(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level,
|
||||
MOD_OPT_SUBPLAN,
|
||||
"Grouping expr should be within valid range");
|
||||
|
||||
/* Find the query level the GroupingFunc belongs to */
|
||||
/*
|
||||
* 确保 GroupingFunc 表达式的 agglevelsup 在有效范围内。
|
||||
* 它应该大于 0(表示外部查询层级)且小于当前查询层级。
|
||||
*/
|
||||
|
||||
/* 找到 GroupingFunc 表达式在适当的 plan_params 中,如果不存在则分配一个新的参数标识符 */
|
||||
for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--)
|
||||
root = root->parent_root;
|
||||
|
||||
/*
|
||||
* It does not seem worthwhile to try to match duplicate outer aggs. Just
|
||||
* make a new slot every time.
|
||||
* 看起来不值得尝试匹配重复的外部 GroupingFunc 表达式。
|
||||
* 每次都创建一个新的参数。
|
||||
*/
|
||||
grp = (GroupingFunc*)copyObject(grp);
|
||||
IncrementVarSublevelsUp((Node*)grp, -((int)grp->agglevelsup), 0);
|
||||
|
|
@ -476,9 +533,10 @@ static Param* replace_outer_grouping(PlannerInfo* root, GroupingFunc* grp)
|
|||
retval->paramcollid = InvalidOid;
|
||||
retval->location = grp->location;
|
||||
|
||||
return retval;
|
||||
return retval; // 返回初始化后的 Param 结构
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate a new Param node that will not conflict with any other.
|
||||
*
|
||||
|
|
@ -488,19 +546,21 @@ static Param* replace_outer_grouping(PlannerInfo* root, GroupingFunc* grp)
|
|||
*/
|
||||
static Param* generate_new_param(PlannerInfo* root, Oid paramtype, int32 paramtypmod, Oid paramcollation)
|
||||
{
|
||||
Param* retval = NULL;
|
||||
Param* retval = NULL; // 用于存储结果参数
|
||||
|
||||
// 创建一个新的 Param 结构
|
||||
retval = makeNode(Param);
|
||||
retval->paramkind = PARAM_EXEC;
|
||||
retval->paramid = root->glob->nParamExec++;
|
||||
retval->paramtype = paramtype;
|
||||
retval->paramtypmod = paramtypmod;
|
||||
retval->paramcollid = paramcollation;
|
||||
retval->location = -1;
|
||||
retval->paramkind = PARAM_EXEC; // 参数种类为 PARAM_EXEC
|
||||
retval->paramid = root->glob->nParamExec++; // 分配一个新的参数标识符
|
||||
retval->paramtype = paramtype; // 参数的数据类型
|
||||
retval->paramtypmod = paramtypmod; // 参数的类型修饰符
|
||||
retval->paramcollid = paramcollation; // 参数的碰撞标识符
|
||||
retval->location = -1; // 参数的位置信息设置为 -1(未知位置)
|
||||
|
||||
return retval;
|
||||
return retval; // 返回初始化后的 Param 结构
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Assign a (nonnegative) PARAM_EXEC ID for a special parameter (one that
|
||||
* is not actually used to carry a value at runtime). Such parameters are
|
||||
|
|
@ -525,23 +585,28 @@ int SS_assign_special_param(PlannerInfo* root)
|
|||
*/
|
||||
static void get_first_col_type(Plan* plan, Oid* coltype, int32* coltypmod, Oid* colcollation)
|
||||
{
|
||||
/* In cases such as EXISTS, tlist might be empty; arbitrarily use VOID */
|
||||
/* 在某些情况下,如 EXISTS,tlist 可能为空;随意使用 VOID 数据类型 */
|
||||
if (plan->targetlist) {
|
||||
TargetEntry* tent = (TargetEntry*)linitial(plan->targetlist);
|
||||
|
||||
AssertEreport(IsA(tent, TargetEntry), MOD_OPT_SUBPLAN, "Element of targetlist should be TargetEntry");
|
||||
|
||||
if (!tent->resjunk) {
|
||||
// 获取第一个非 junk 目标条目的数据类型、类型修饰符和碰撞标识符
|
||||
*coltype = exprType((Node*)tent->expr);
|
||||
*coltypmod = exprTypmod((Node*)tent->expr);
|
||||
*colcollation = exprCollation((Node*)tent->expr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有非 junk 目标条目,将数据类型设置为 VOIDOID,类型修饰符设置为 -1,碰撞标识符设置为 InvalidOid
|
||||
*coltype = VOIDOID;
|
||||
*coltypmod = -1;
|
||||
*colcollation = InvalidOid;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Description: subquery is initplan or subplan.
|
||||
* Parameters:
|
||||
|
|
@ -551,18 +616,22 @@ static void get_first_col_type(Plan* plan, Oid* coltype, int32* coltypmod, Oid*
|
|||
*/
|
||||
static bool IsInitPlan(List* plan_params, SubLinkType subLinkType)
|
||||
{
|
||||
// 如果计划参数列表不为空,则不是初始化计划
|
||||
if (plan_params != NIL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果子查询类型为 EXISTS_SUBLINK、EXPR_SUBLINK、ARRAY_SUBLINK 或 ROWCOMPARE_SUBLINK,则是初始化计划
|
||||
if (subLinkType == EXISTS_SUBLINK || subLinkType == EXPR_SUBLINK || subLinkType == ARRAY_SUBLINK ||
|
||||
subLinkType == ROWCOMPARE_SUBLINK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 否则不是初始化计划
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert a SubLink (as created by the parser) into a SubPlan.
|
||||
*
|
||||
|
|
@ -980,55 +1049,85 @@ static Node* build_subplan(PlannerInfo* root, Plan* plan, PlannerInfo* subroot,
|
|||
*
|
||||
* We also return an integer list of the paramids of the Params.
|
||||
*/
|
||||
/*
|
||||
这个函数用于生成子查询中的参数列表,根据传递给函数的目标条目列表 tlist,
|
||||
遍历列表中的每个目标条目,生成对应的参数,并将参数添加到 result 列表中。
|
||||
同时,将生成参数的标识符列表存储在 ids 列表中。
|
||||
最后,通过 paramIds 指针返回生成参数的标识符列表,函数返回生成的参数列表。
|
||||
这个函数通常用于为子查询生成参数,以便在执行子查询时传递参数值。
|
||||
*/
|
||||
static List* generate_subquery_params(PlannerInfo* root, List* tlist, List** paramIds)
|
||||
{
|
||||
List* result = NIL;
|
||||
List* ids = NIL;
|
||||
ListCell* lc = NULL;
|
||||
List* result = NIL; // 用于存储生成的参数列表
|
||||
List* ids = NIL; // 用于存储生成参数的标识符列表
|
||||
ListCell* lc = NULL; // 用于遍历目标条目列表
|
||||
|
||||
// 初始化 result 和 ids 为空列表
|
||||
result = ids = NIL;
|
||||
|
||||
// 遍历目标条目列表
|
||||
foreach (lc, tlist) {
|
||||
TargetEntry* tent = (TargetEntry*)lfirst(lc);
|
||||
Param* param = NULL;
|
||||
|
||||
// 跳过 resjunk(不需要的目标条目)
|
||||
if (tent->resjunk)
|
||||
continue;
|
||||
|
||||
// 生成一个新的参数(Param)并添加到 result 列表中
|
||||
param = generate_new_param(
|
||||
root, exprType((Node*)tent->expr), exprTypmod((Node*)tent->expr), exprCollation((Node*)tent->expr));
|
||||
result = lappend(result, param);
|
||||
|
||||
// 将生成的参数的标识符添加到 ids 列表中
|
||||
ids = lappend_int(ids, param->paramid);
|
||||
}
|
||||
|
||||
// 将生成参数的标识符列表传递给 paramIds 指针
|
||||
*paramIds = ids;
|
||||
|
||||
// 返回生成的参数列表
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* generate_subquery_vars: build a list of Vars representing the output
|
||||
* columns of a sublink's sub-select, given the sub-select's targetlist.
|
||||
* The Vars have the specified varno (RTE index).
|
||||
*/
|
||||
/*
|
||||
这个函数用于生成子查询中的变量列表,根据传递给函数的目标条目列表 tlist 和变量的标识符 varno,
|
||||
遍历列表中的每个目标条目,从每个目标条目生成对应的变量,并将变量添加到 result 列表中。
|
||||
最后,函数返回生成的变量列表。这个函数通常用于为子查询生成变量,以便在执行子查询时引用这些变量。
|
||||
*/
|
||||
static List* generate_subquery_vars(PlannerInfo* root, List* tlist, Index varno)
|
||||
{
|
||||
List* result = NIL;
|
||||
ListCell* lc = NULL;
|
||||
List* result = NIL; // 用于存储生成的变量列表
|
||||
ListCell* lc = NULL; // 用于遍历目标条目列表
|
||||
|
||||
// 初始化 result 为空列表
|
||||
result = NIL;
|
||||
|
||||
// 遍历目标条目列表
|
||||
foreach (lc, tlist) {
|
||||
TargetEntry* tent = (TargetEntry*)lfirst(lc);
|
||||
Var* var = NULL;
|
||||
|
||||
// 跳过 resjunk(不需要的目标条目)
|
||||
if (tent->resjunk)
|
||||
continue;
|
||||
|
||||
// 从目标条目生成一个新的变量(Var)并添加到 result 列表中
|
||||
var = makeVarFromTargetEntry(varno, tent);
|
||||
result = lappend(result, var);
|
||||
}
|
||||
|
||||
// 返回生成的变量列表
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* convert_testexpr: convert the testexpr given by the parser into
|
||||
* actually executable form. This entails replacing PARAM_SUBLINK Params
|
||||
|
|
@ -1045,19 +1144,28 @@ static Node* convert_testexpr(PlannerInfo* root, Node* testexpr, List* subst_nod
|
|||
{
|
||||
convert_testexpr_context context;
|
||||
|
||||
context.root = root;
|
||||
context.subst_nodes = subst_nodes;
|
||||
// 创建测试表达式转换的上下文
|
||||
context.root = root; // 传递 PlannerInfo 上下文
|
||||
context.subst_nodes = subst_nodes; // 传递替换节点的列表
|
||||
|
||||
// 调用 convert_testexpr_mutator 函数进行实际的表达式转换
|
||||
return convert_testexpr_mutator(testexpr, &context);
|
||||
}
|
||||
|
||||
|
||||
static Node* convert_testexpr_mutator(Node* node, convert_testexpr_context* context)
|
||||
{
|
||||
// 如果节点为空,则返回空节点
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
// 如果节点是 Param 类型
|
||||
if (IsA(node, Param)) {
|
||||
Param* param = (Param*)node;
|
||||
|
||||
// 检查参数的类型是否是 PARAM_SUBLINK
|
||||
if (param->paramkind == PARAM_SUBLINK) {
|
||||
// 检查参数的标识符是否有效
|
||||
if (param->paramid <= 0 || param->paramid > list_length(context->subst_nodes))
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
|
|
@ -1065,83 +1173,82 @@ static Node* convert_testexpr_mutator(Node* node, convert_testexpr_context* cont
|
|||
errmsg("unexpected PARAM_SUBLINK ID: %d", param->paramid)));
|
||||
|
||||
/*
|
||||
* We copy the list item to avoid having doubly-linked
|
||||
* substructure in the modified parse tree. This is probably
|
||||
* unnecessary when it's a Param, but be safe.
|
||||
* 返回与 PARAM_SUBLINK 对应的替换节点。我们复制列表项,以避免在修改后的解析树中具有双向链接的子结构。
|
||||
* 这在它是 Param 时可能是不必要的,但为了安全起见,我们进行复制。
|
||||
*/
|
||||
return (Node*)copyObject(list_nth(context->subst_nodes, param->paramid - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// 如果节点是 SubLink 类型,直接返回,不需要进一步处理
|
||||
if (IsA(node, SubLink)) {
|
||||
/*
|
||||
* If we come across a nested SubLink, it is neither necessary nor
|
||||
* correct to recurse into it: any PARAM_SUBLINKs we might find inside
|
||||
* belong to the inner SubLink not the outer. So just return it as-is.
|
||||
*
|
||||
* This reasoning depends on the assumption that nothing will pull
|
||||
* subexpressions into or out of the testexpr field of a SubLink, at
|
||||
* least not without replacing PARAM_SUBLINKs first. If we did want
|
||||
* to do that we'd need to rethink the parser-output representation
|
||||
* altogether, since currently PARAM_SUBLINKs are only unique per
|
||||
* SubLink not globally across the query. The whole point of
|
||||
* replacing them with Vars or PARAM_EXEC nodes is to make them
|
||||
* globally unique before they escape from the SubLink's testexpr.
|
||||
*
|
||||
* Note: this can't happen when called during SS_process_sublinks,
|
||||
* because that recursively processes inner SubLinks first. It can
|
||||
* happen when called from convert_ANY_sublink_to_join, though.
|
||||
*/
|
||||
return node;
|
||||
}
|
||||
|
||||
// 对于其他类型的节点,递归调用 expression_tree_mutator 处理子节点
|
||||
return expression_tree_mutator(node, (Node* (*)(Node*, void*)) convert_testexpr_mutator, (void*)context);
|
||||
}
|
||||
|
||||
/*
|
||||
* subplan_is_hashable: can we implement an ANY subplan by hashing?
|
||||
*/
|
||||
/*
|
||||
这个函数主要用于评估子查询的结果大小是否适合于在工作内存 (work_mem) 中进行哈希操作。
|
||||
它首先计算子查询结果的预估大小,然后将其与工作内存限制进行比较。
|
||||
如果子查询结果的大小超过工作内存限制,函数返回 false,表示无法进行哈希操作。
|
||||
否则,返回 true,表示可以进行哈希操作。这个函数通常在查询规划阶段使用,用于决定是否使用哈希算法来处理子查询的结果。
|
||||
*/
|
||||
static bool subplan_is_hashable(Plan* plan)
|
||||
{
|
||||
double subquery_size;
|
||||
|
||||
/*
|
||||
* The estimated size of the subquery result must fit in work_mem. (Note:
|
||||
* we use sizeof(HeapTupleHeaderData) here even though the tuples will
|
||||
* actually be stored as MinimalTuples; this provides some fudge factor
|
||||
* for hashtable overhead.)
|
||||
* 子查询结果的预估大小必须适合于工作内存(work_mem)。
|
||||
* (注意:这里使用 sizeof(HeapTupleHeaderData) 来估算,即使实际上元组将以 MinimalTuples 存储;
|
||||
* 这为散列表开销提供了一些余地。)
|
||||
*/
|
||||
subquery_size = plan->plan_rows * (MAXALIGN(plan->plan_width) + MAXALIGN(sizeof(HeapTupleHeaderData)));
|
||||
|
||||
// 如果子查询结果的大小超过工作内存(work_mem)限制,则无法进行哈希操作
|
||||
if (subquery_size > u_sess->attr.attr_memory.work_mem * 1024L)
|
||||
return false;
|
||||
|
||||
// 可以进行哈希操作
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* testexpr_is_hashable: is an ANY SubLink's test expression hashable?
|
||||
*/
|
||||
/*
|
||||
这个函数用于检查测试表达式是否适合在哈希操作中使用。
|
||||
要满足条件,测试表达式必须是一个单一的 OpExpr,或者是一个仅包含 OpExpr 的 AND 子句。
|
||||
此外,其中涉及的操作符必须是可哈希的并且是严格的(strict)。
|
||||
这是因为哈希操作需要可哈希的操作符以及不会对非 NULL 输入产生 NULL 结果的操作符。
|
||||
这个函数通常在查询规划阶段使用,以确定是否可以使用哈希操作来处理测试表达式。
|
||||
*/
|
||||
static bool testexpr_is_hashable(Node* testexpr)
|
||||
{
|
||||
/*
|
||||
* The testexpr must be a single OpExpr, or an AND-clause containing only
|
||||
* OpExprs.
|
||||
* 测试表达式必须是单个 OpExpr,或者是只包含 OpExpr 的 AND 子句。
|
||||
*
|
||||
* The combining operators must be hashable and strict. The need for
|
||||
* hashability is obvious, since we want to use hashing. Without
|
||||
* strictness, behavior in the presence of nulls is too unpredictable. We
|
||||
* actually must assume even more than plain strictness: they can't yield
|
||||
* NULL for non-null inputs, either (see nodeSubplan.c). However, hash
|
||||
* indexes and hash joins assume that too.
|
||||
* 组合操作符必须是可哈希的并且是严格的。哈希性是显而易见的,因为我们希望使用哈希。
|
||||
* 没有严格性,在存在 NULL 的情况下,行为太不可预测了。实际上,我们需要比纯粹的严格性更多的假设:
|
||||
* 它们不能为非 NULL 的输入产生 NULL(请参见 nodeSubplan.c)。但是,哈希索引和哈希连接也假设如此。
|
||||
*/
|
||||
if (testexpr && IsA(testexpr, OpExpr)) {
|
||||
// 如果测试表达式是单个 OpExpr,则检查该操作符是否可哈希
|
||||
if (hash_ok_operator((OpExpr*)testexpr))
|
||||
return true;
|
||||
} else if (and_clause(testexpr)) {
|
||||
// 如果测试表达式是 AND 子句,则遍历每个 AND 子句
|
||||
ListCell* l = NULL;
|
||||
|
||||
foreach (l, ((BoolExpr*)testexpr)->args) {
|
||||
Node* andarg = (Node*)lfirst(l);
|
||||
|
||||
// 检查每个子表达式是否是 OpExpr,并且操作符是否可哈希
|
||||
if (!IsA(andarg, OpExpr))
|
||||
return false;
|
||||
if (!hash_ok_operator((OpExpr*)andarg))
|
||||
|
|
@ -1150,9 +1257,16 @@ static bool testexpr_is_hashable(Node* testexpr)
|
|||
return true;
|
||||
}
|
||||
|
||||
// 无法进行哈希操作
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
这个函数用于将连接条件(joinqual)转换为反连接条件(antiqual),以用于反连接操作。
|
||||
函数根据连接条件的类型进行不同的处理,支持 OpExpr 和 BoolExpr 类型的连接条件,
|
||||
并将它们转换为相应的反连接条件。这个函数通常在查询规划阶段用于构建反连接操作所需的条件表达式。
|
||||
*/
|
||||
static Node* convert_joinqual_to_antiqual(Node* node, Query* parse)
|
||||
{
|
||||
Node* antiqual = NULL;
|
||||
|
|
@ -1162,10 +1276,11 @@ static Node* convert_joinqual_to_antiqual(Node* node, Query* parse)
|
|||
|
||||
switch (nodeTag(node)) {
|
||||
case T_OpExpr:
|
||||
// 如果节点是 OpExpr,则将其转换为反连接条件
|
||||
antiqual = convert_opexpr_to_boolexpr_for_antijoin(node, parse);
|
||||
break;
|
||||
case T_BoolExpr: {
|
||||
/*Not IN, should be and clause.*/
|
||||
/* 不是 IN 连接条件,应该是 AND 子句。*/
|
||||
if (and_clause(node)) {
|
||||
BoolExpr* boolexpr = (BoolExpr*)node;
|
||||
List* andarglist = NIL;
|
||||
|
|
@ -1175,7 +1290,7 @@ static Node* convert_joinqual_to_antiqual(Node* node, Query* parse)
|
|||
Node* andarg = (Node*)lfirst(l);
|
||||
Node* expr = NULL;
|
||||
|
||||
/* The listcell type of args should be OpExpr. */
|
||||
/* args 的列表单元格类型应该是 OpExpr。 */
|
||||
expr = convert_opexpr_to_boolexpr_for_antijoin(andarg, parse);
|
||||
if (expr == NULL)
|
||||
return NULL;
|
||||
|
|
@ -1183,6 +1298,7 @@ static Node* convert_joinqual_to_antiqual(Node* node, Query* parse)
|
|||
andarglist = lappend(andarglist, expr);
|
||||
}
|
||||
|
||||
// 创建一个新的 BoolExpr,将所有 AND 子句连接起来
|
||||
antiqual = (Node*)makeBoolExpr(AND_EXPR, andarglist, boolexpr->location);
|
||||
} else
|
||||
return NULL;
|
||||
|
|
@ -1198,6 +1314,12 @@ static Node* convert_joinqual_to_antiqual(Node* node, Query* parse)
|
|||
return antiqual;
|
||||
}
|
||||
|
||||
/*
|
||||
这个函数的主要任务是将 OpExpr 转换为 BoolExpr,以用于反连接操作。
|
||||
它检查 OpExpr 的左右操作数是否可为 NULL,如果是,则为每个操作数添加一个 IS NULL 测试条件,
|
||||
并将它们用 OR 连接起来。最后,它返回生成的 BoolExpr 或保持 OpExpr 不变,
|
||||
具体取决于是否需要添加额外的测试条件。这个函数通常在查询规划阶段用于构建反连接操作所需的条件表达式。
|
||||
*/
|
||||
static Node* convert_opexpr_to_boolexpr_for_antijoin(Node* node, Query* parse)
|
||||
{
|
||||
Node* boolexpr = NULL;
|
||||
|
|
@ -1209,20 +1331,27 @@ static Node* convert_opexpr_to_boolexpr_for_antijoin(Node* node, Query* parse)
|
|||
else
|
||||
opexpr = (OpExpr*)node;
|
||||
|
||||
// 创建一个包含 OpExpr 的列表
|
||||
antiqual = (List*)list_make1(opexpr);
|
||||
|
||||
// 处理 OpExpr 的左操作数
|
||||
Node* larg = (Node*)linitial(opexpr->args);
|
||||
if (IsA(larg, RelabelType))
|
||||
larg = (Node*)((RelabelType*)larg)->arg;
|
||||
|
||||
// 如果左操作数可为 NULL,则添加一个 IS NULL 测试
|
||||
if (!check_var_nonnullable(parse, larg))
|
||||
antiqual = lappend(antiqual, makeNullTest(IS_NULL, (Expr*)copyObject(larg)));
|
||||
|
||||
// 处理 OpExpr 的右操作数,类似地添加 IS NULL 测试
|
||||
Node* rarg = (Node*)lsecond(opexpr->args);
|
||||
if (IsA(rarg, RelabelType))
|
||||
rarg = (Node*)((RelabelType*)rarg)->arg;
|
||||
|
||||
if (!check_var_nonnullable(parse, rarg))
|
||||
antiqual = lappend(antiqual, makeNullTest(IS_NULL, (Expr*)copyObject(rarg)));
|
||||
|
||||
// 如果生成了多个测试条件,则将它们用 OR 连接,否则保持 OpExpr 不变
|
||||
if (list_length(antiqual) > 1)
|
||||
boolexpr = (Node*)makeBoolExprTreeNode(OR_EXPR, antiqual);
|
||||
else
|
||||
|
|
@ -1231,6 +1360,7 @@ static Node* convert_opexpr_to_boolexpr_for_antijoin(Node* node, Query* parse)
|
|||
return boolexpr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* check_var_nonnullable
|
||||
* check if the node is nullable
|
||||
|
|
|
|||
|
|
@ -127,35 +127,42 @@ static bool contains_swctes(const PlannerInfo *root);
|
|||
* Unlike most other functions in this file, this function doesn't recurse;
|
||||
* we rely on other processing to invoke it on sub-queries at suitable times.
|
||||
*/
|
||||
/*
|
||||
函数主要功能是在查询解析树中检查联接树是否为空,如果为空,
|
||||
则创建一个特殊的范围表条目,然后将其添加到查询解析树的范围表中,
|
||||
并在联接树中插入一个对这个新范围表条目的引用。这通常用于处理一些特殊情况,
|
||||
以确保查询解析树的正确性。如果联接树已经非空或者在SET操作的顶层,就不做任何修改。
|
||||
*/
|
||||
void replace_empty_jointree(Query *parse)
|
||||
{
|
||||
RangeTblEntry *rte;
|
||||
Index rti;
|
||||
RangeTblRef *rtr;
|
||||
RangeTblEntry *rte; // 声明一个RangeTblEntry结构体指针,用于表示查询范围表条目
|
||||
Index rti; // 声明一个索引,用于表示范围表条目的索引
|
||||
RangeTblRef *rtr; // 声明一个RangeTblRef结构体指针,用于表示范围表引用
|
||||
|
||||
/* Nothing to do if jointree is already nonempty */
|
||||
/* 如果联接树(jointree)已经不为空,就不需要做任何操作 */
|
||||
if (parse->jointree->fromlist != NIL)
|
||||
return;
|
||||
|
||||
/* We mustn't change it in the top level of a setop tree, either */
|
||||
/* 如果在SET操作的顶层,我们也不能更改它 */
|
||||
if (parse->setOperations)
|
||||
return;
|
||||
|
||||
/* Create suitable RTE */
|
||||
rte = makeNode(RangeTblEntry);
|
||||
rte->rtekind = RTE_RESULT;
|
||||
rte->eref = makeAlias("*RESULT*", NIL);
|
||||
/* 创建适当的范围表条目(RangeTblEntry) */
|
||||
rte = makeNode(RangeTblEntry); // 创建一个新的范围表条目
|
||||
rte->rtekind = RTE_RESULT; // 设置范围表条目的类型为RTE_RESULT
|
||||
rte->eref = makeAlias("*RESULT*", NIL); // 为范围表条目创建一个别名为"*RESULT*"
|
||||
|
||||
/* Add it to rangetable */
|
||||
parse->rtable = lappend(parse->rtable, rte);
|
||||
rti = list_length(parse->rtable);
|
||||
/* 将范围表条目添加到范围表中 */
|
||||
parse->rtable = lappend(parse->rtable, rte); // 将新创建的范围表条目添加到查询解析树的范围表中
|
||||
rti = list_length(parse->rtable); // 获取新添加的范围表条目的索引
|
||||
|
||||
/* And jam a reference into the jointree */
|
||||
rtr = makeNode(RangeTblRef);
|
||||
rtr->rtindex = rti;
|
||||
parse->jointree->fromlist = list_make1(rtr);
|
||||
/* 并且将一个范围表引用插入到联接树中 */
|
||||
rtr = makeNode(RangeTblRef); // 创建一个新的范围表引用
|
||||
rtr->rtindex = rti; // 设置范围表引用的索引为新添加的范围表条目的索引
|
||||
parse->jointree->fromlist = list_make1(rtr); // 将范围表引用添加到查询解析树的联接树中
|
||||
}
|
||||
|
||||
|
||||
#ifndef ENABLE_MULTIPLE_NODES
|
||||
/*
|
||||
* helper function to check if SWCB ctes contaisn in current SubQuery, normally help us to
|
||||
|
|
@ -163,25 +170,29 @@ void replace_empty_jointree(Query *parse)
|
|||
*/
|
||||
static bool contains_swctes(const PlannerInfo *root)
|
||||
{
|
||||
// 如果查询解析树为空或者其中的CTE列表为空,返回false
|
||||
if (root->parse == NULL || root->parse->cteList == NIL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List *cteList = root->parse->cteList;
|
||||
ListCell *lc = NULL;
|
||||
bool found = false;
|
||||
foreach(lc, cteList) {
|
||||
CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc);
|
||||
List *cteList = root->parse->cteList; // 获取查询解析树中的CTE列表
|
||||
ListCell *lc = NULL; // 声明一个用于遍历列表的列表元素指针
|
||||
bool found = false; // 声明一个布尔值,用于表示是否找到特定类型的CTE
|
||||
|
||||
/* check if cte from parse->ctelist is a swcb converted */
|
||||
// 遍历CTE列表
|
||||
foreach(lc, cteList) {
|
||||
CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc); // 获取当前CTE
|
||||
|
||||
/* 检查parse->ctelist中的CTE是否是swcb转换的 */
|
||||
if (cte->swoptions != NULL) {
|
||||
found = true;
|
||||
break;
|
||||
found = true; // 如果CTE中包含swoptions,将found设置为true
|
||||
break; // 跳出循环,因为已经找到了符合条件的CTE
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
return found; // 返回是否找到特定类型的CTE的结果
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
@ -277,35 +288,42 @@ void pull_up_sublinks(PlannerInfo* root)
|
|||
*/
|
||||
Node* assign_qual_clause(Node* new_node, Node* old_node, Node* qual, Relids old_node_relids)
|
||||
{
|
||||
// 如果限定条件(qual)为空,则直接返回新节点(new_node),无需处理
|
||||
if (qual == NULL) {
|
||||
return new_node;
|
||||
}
|
||||
|
||||
// 从限定条件中获取相关的变量编号(varnos)
|
||||
Relids qual_varnos = pull_varnos(qual);
|
||||
|
||||
/*
|
||||
* We need add this quals to new_node if old_node_relids can not include qual_varnos.
|
||||
* than can happend when or_clause pull up.
|
||||
* 如果限定条件中的变量编号(qual_varnos)不能完全包含在旧节点的关系标识集合(old_node_relids)中,
|
||||
* 则需要将限定条件添加到新节点(new_node)中。
|
||||
* 这种情况可能会发生在OR条件的上升处理中。
|
||||
*/
|
||||
if (!bms_is_subset(qual_varnos, old_node_relids)) {
|
||||
if (IsA(new_node, FromExpr)) {
|
||||
((FromExpr*)new_node)->quals = qual;
|
||||
((FromExpr*)new_node)->quals = qual; // 将限定条件设置为新节点的条件
|
||||
} else {
|
||||
new_node = (Node*)makeFromExpr(list_make1(new_node), qual);
|
||||
new_node = (Node*)makeFromExpr(list_make1(new_node), qual); // 创建一个新的FromExpr节点,并将限定条件添加到其中
|
||||
}
|
||||
}
|
||||
/* Only need put qual to old node, this qual can be original(pull up before) qual.*/
|
||||
/* 否则,只需要将限定条件放在旧节点上,这个限定条件可能是原始的(在上升之前已经存在的)限定条件。 */
|
||||
else {
|
||||
if (IsA(old_node, FromExpr)) {
|
||||
((FromExpr*)old_node)->quals = qual;
|
||||
((FromExpr*)old_node)->quals = qual; // 将限定条件设置为旧节点的条件
|
||||
} else {
|
||||
((JoinExpr*)old_node)->quals = qual;
|
||||
((JoinExpr*)old_node)->quals = qual; // 将限定条件设置为联接节点的条件
|
||||
}
|
||||
}
|
||||
|
||||
// 释放变量编号集合的内存
|
||||
bms_free_ext(qual_varnos);
|
||||
|
||||
return new_node;
|
||||
return new_node; // 返回处理后的新节点
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Recurse through jointree nodes for pull_up_sublinks()
|
||||
*
|
||||
|
|
@ -506,12 +524,12 @@ static Node* pull_up_sublinks_jointree_recurse(PlannerInfo* root, Node* jtnode,
|
|||
static Node* pull_up_sublinks_qual_recurse(PlannerInfo* root, Node* node, Node** jtlink1, Relids *available_rels1,
|
||||
Node** jtlink2, Relids *available_rels2, Node* all_quals)
|
||||
{
|
||||
if (node == NULL)
|
||||
if (node == NULL)// 如果传入的节点为空,直接返回NULL
|
||||
return NULL;
|
||||
if (IsA(node, SubLink)) {
|
||||
SubLink* sublink = (SubLink*)node;
|
||||
JoinExpr* j = NULL;
|
||||
Relids child_rels;
|
||||
if (IsA(node, SubLink)) {// 如果节点是一个子查询(SubLink)
|
||||
SubLink* sublink = (SubLink*)node;// 将节点转换为子查询结构体
|
||||
JoinExpr* j = NULL;// 声明一个JoinExpr结构体指针,用于表示连接表达式
|
||||
Relids child_rels;// 用于存储子查询关联的关系标识集合
|
||||
|
||||
if (has_no_expand_hint((Query*)sublink->subselect)) {
|
||||
return node;
|
||||
|
|
@ -685,12 +703,13 @@ static Node* pull_up_sublinks_qual_recurse(PlannerInfo* root, Node* node, Node**
|
|||
ListCell* l = NULL;
|
||||
|
||||
foreach (l, ((BoolExpr*)node)->args) {
|
||||
Node* oldclause = (Node*)lfirst(l);
|
||||
Node* newclause = NULL;
|
||||
|
||||
Node* oldclause = (Node*)lfirst(l);// 获取当前参数作为旧的子句
|
||||
Node* newclause = NULL;// 用于存储处理后的新子句
|
||||
// 调用递归函数处理当前子句
|
||||
newclause = pull_up_sublinks_qual_recurse(
|
||||
root, oldclause, jtlink1, available_rels1, jtlink2, available_rels2, all_quals);
|
||||
if (newclause != NULL)
|
||||
// 如果处理后的新子句不为空
|
||||
if (newclause != NULL)// 将新子句添加到新子句列表中
|
||||
newclauses = lappend(newclauses, newclause);
|
||||
}
|
||||
/* We might have got back fewer clauses than we started with */
|
||||
|
|
@ -834,29 +853,34 @@ static Node* pull_up_sublinks_targetlist(PlannerInfo *root,
|
|||
*/
|
||||
void inline_set_returning_functions(PlannerInfo* root)
|
||||
{
|
||||
ListCell* rt = NULL;
|
||||
ListCell* rt = NULL; // 声明一个用于遍历查询的范围表条目的列表元素指针
|
||||
|
||||
// 遍历查询解析树的范围表条目
|
||||
foreach (rt, root->parse->rtable) {
|
||||
RangeTblEntry* rte = (RangeTblEntry*)lfirst(rt);
|
||||
RangeTblEntry* rte = (RangeTblEntry*)lfirst(rt); // 获取当前范围表条目
|
||||
|
||||
// 如果范围表条目的类型是函数(RTE_FUNCTION)
|
||||
if (rte->rtekind == RTE_FUNCTION) {
|
||||
Query* funcquery = NULL;
|
||||
Query* funcquery = NULL; // 用于存储函数展开后的查询
|
||||
|
||||
/* Check safety of expansion, and expand if possible */
|
||||
/* 检查展开的安全性,并在可能的情况下进行展开 */
|
||||
funcquery = inline_set_returning_function(root, rte);
|
||||
|
||||
// 如果成功展开函数
|
||||
if (funcquery != NULL) {
|
||||
/* Successful expansion, replace the rtable entry */
|
||||
/* 成功展开,替换范围表条目的类型为子查询(RTE_SUBQUERY) */
|
||||
rte->rtekind = RTE_SUBQUERY;
|
||||
rte->subquery = funcquery;
|
||||
rte->funcexpr = NULL;
|
||||
rte->funccoltypes = NIL;
|
||||
rte->funccoltypmods = NIL;
|
||||
rte->funccolcollations = NIL;
|
||||
rte->subquery = funcquery; // 将展开后的查询存储在范围表条目中
|
||||
rte->funcexpr = NULL; // 清空函数表达式
|
||||
rte->funccoltypes = NIL; // 清空函数列类型列表
|
||||
rte->funccoltypmods = NIL; // 清空函数列类型修改列表
|
||||
rte->funccolcollations = NIL; // 清空函数列的字符集列表
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This recursively processes the jointree and returns a modified jointree.
|
||||
*/
|
||||
|
|
@ -1530,18 +1554,20 @@ static Node* pull_up_simple_union_all(PlannerInfo* root, Node* jtnode, RangeTblE
|
|||
static void pull_up_union_leaf_queries(
|
||||
Node* setOp, PlannerInfo* root, int parentRTindex, Query* setOpQuery, int childRToffset)
|
||||
{
|
||||
// 如果 setOp 是 RangeTblRef 类型
|
||||
if (IsA(setOp, RangeTblRef)) {
|
||||
RangeTblRef* rtr = (RangeTblRef*)setOp;
|
||||
RangeTblRef* rtr = (RangeTblRef*)setOp; // 将 setOp 转换为 RangeTblRef 结构体
|
||||
|
||||
int childRTindex;
|
||||
AppendRelInfo* appinfo = NULL;
|
||||
|
||||
/*
|
||||
* Calculate the index in the parent's range table
|
||||
* 计算子查询在父查询范围表中的索引
|
||||
*/
|
||||
childRTindex = childRToffset + rtr->rtindex;
|
||||
|
||||
/*
|
||||
* Build a suitable AppendRelInfo, and attach to parent's list.
|
||||
* 创建适当的 AppendRelInfo 结构体,并附加到父查询的列表中
|
||||
*/
|
||||
appinfo = makeNode(AppendRelInfo);
|
||||
appinfo->parent_relid = parentRTindex;
|
||||
|
|
@ -1553,29 +1579,35 @@ static void pull_up_union_leaf_queries(
|
|||
root->append_rel_list = lappend(root->append_rel_list, appinfo);
|
||||
|
||||
/*
|
||||
* Recursively apply pull_up_subqueries to the new child RTE. (We
|
||||
* must build the AppendRelInfo first, because this will modify it.)
|
||||
* Note that we can pass NULL for containing-join info even if we're
|
||||
* actually under an outer join, because the child's expressions
|
||||
* aren't going to propagate up above the join.
|
||||
* 递归应用 pull_up_subqueries_recurse 函数到新的子查询 RTE。
|
||||
* 需要先构建 AppendRelInfo,因为这将会修改它。
|
||||
* 注意,即使我们实际上处于外连接下,也可以传递 NULL 作为包含连接信息,
|
||||
* 因为子查询的表达式不会向上传播到连接之上。
|
||||
*/
|
||||
rtr = makeNode(RangeTblRef);
|
||||
rtr->rtindex = childRTindex;
|
||||
(void)pull_up_subqueries_recurse(root, (Node*)rtr, NULL, NULL, appinfo);
|
||||
} else if (IsA(setOp, SetOperationStmt)) {
|
||||
}
|
||||
// 如果 setOp 是 SetOperationStmt 类型
|
||||
else if (IsA(setOp, SetOperationStmt)) {
|
||||
SetOperationStmt* op = (SetOperationStmt*)setOp;
|
||||
|
||||
/* Recurse to reach leaf queries */
|
||||
/* 递归处理左子查询 */
|
||||
pull_up_union_leaf_queries(op->larg, root, parentRTindex, setOpQuery, childRToffset);
|
||||
|
||||
/* 递归处理右子查询 */
|
||||
pull_up_union_leaf_queries(op->rarg, root, parentRTindex, setOpQuery, childRToffset);
|
||||
} else {
|
||||
}
|
||||
// 如果 setOp 是未知的节点类型
|
||||
else {
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
|
||||
errmsg("unrecognized node type: %d", (int)nodeTag(setOp))));
|
||||
errmsg("未知的节点类型: %d", (int)nodeTag(setOp))));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* make_setop_translation_list
|
||||
* Build the list of translations from parent Vars to child Vars for
|
||||
|
|
@ -1585,21 +1617,26 @@ static void pull_up_union_leaf_queries(
|
|||
*/
|
||||
static void make_setop_translation_list(Query* query, Index newvarno, List** translated_vars)
|
||||
{
|
||||
List* vars = NIL;
|
||||
ListCell* l = NULL;
|
||||
List* vars = NIL; // 声明一个用于存储变量的列表
|
||||
ListCell* l = NULL; // 声明一个用于遍历目标列表的列表元素指针
|
||||
|
||||
// 遍历查询的目标列表
|
||||
foreach (l, query->targetList) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(l);
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(l); // 获取当前目标条目
|
||||
|
||||
// 如果目标条目被标记为 "resjunk",则跳过它,不处理
|
||||
if (tle->resjunk)
|
||||
continue;
|
||||
|
||||
// 创建一个新的变量,表示目标条目,并将其添加到变量列表中
|
||||
vars = lappend(vars, makeVarFromTargetEntry(newvarno, tle));
|
||||
}
|
||||
|
||||
// 将构建的变量列表赋值给传入的 translated_vars 指针
|
||||
*translated_vars = vars;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* is_simple_lateral_subquery
|
||||
* If the subquery is LATERAL, check for pullup restrictions from that.
|
||||
|
|
@ -1660,7 +1697,7 @@ static bool is_simple_lateral_subquery(Query* subquery, JoinExpr *lowest_outer_j
|
|||
|
||||
static bool is_grouping_subquery(Query* subquery)
|
||||
{
|
||||
/*
|
||||
/*
|
||||
* Can't pull up a subquery involving grouping, aggregation, sorting,
|
||||
* limiting, or WITH. (XXX WITH could possibly be allowed later)
|
||||
*
|
||||
|
|
@ -1670,14 +1707,20 @@ static bool is_grouping_subquery(Query* subquery)
|
|||
* that case the locking was originally declared in the upper query
|
||||
* anyway.
|
||||
*/
|
||||
// 如果子查询中包含聚合函数(hasAggs)、窗口函数(hasWindowFuncs)、GROUP BY 子句(groupClause)、
|
||||
// GROUPING SETS 子句(groupingSets)、HAVING 子句(havingQual)、排序子句(sortClause)、
|
||||
// DISTINCT 子句(distinctClause)、LIMIT 偏移(limitOffset)、LIMIT 计数(limitCount)、
|
||||
// FOR UPDATE 子句(hasForUpdate)或者通用表达式列表(cteList),则认为它不是分组子查询。
|
||||
if (subquery->hasAggs || subquery->hasWindowFuncs || subquery->groupClause || subquery->groupingSets ||
|
||||
subquery->havingQual || subquery->sortClause || subquery->distinctClause || subquery->limitOffset ||
|
||||
subquery->limitCount || subquery->hasForUpdate || subquery->cteList)
|
||||
return false;
|
||||
|
||||
// 如果上述条件都不满足,则认为子查询是分组子查询。
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* is_simple_subquery
|
||||
* Check a subquery in the range table to see if it's simple enough
|
||||
|
|
@ -1788,64 +1831,72 @@ static bool is_simple_union_all(Query* subquery)
|
|||
{
|
||||
SetOperationStmt* topop = NULL;
|
||||
|
||||
/* Let's just make sure it's a valid subselect ... */
|
||||
/* 首先确保子查询是一个有效的子查询... */
|
||||
if (!IsA(subquery, Query) || subquery->commandType != CMD_SELECT || subquery->utilityStmt != NULL)
|
||||
ereport(
|
||||
ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE), (errmsg("subquery is bogus"))));
|
||||
ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE), (errmsg("子查询无效"))));
|
||||
|
||||
/* Is it a set-operation query at all? */
|
||||
/* 子查询是否是一个集合操作查询? */
|
||||
topop = (SetOperationStmt*)subquery->setOperations;
|
||||
if (topop == NULL)
|
||||
return false;
|
||||
AssertEreport(
|
||||
IsA(topop, SetOperationStmt), MOD_OPT_REWRITE, "subquery's setOperations mismatch in is_simple_union_all");
|
||||
IsA(topop, SetOperationStmt), MOD_OPT_REWRITE, "is_simple_union_all 中子查询的 setOperations 不匹配");
|
||||
|
||||
#ifndef ENABLE_MULTIPLE_NODES
|
||||
// 如果子查询包含 ROWNUM 过滤条件,就不是简单的 UNION ALL 查询
|
||||
if (ContainRownumQual(subquery)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Can't handle ORDER BY, LIMIT/OFFSET, locking, or WITH */
|
||||
/* 不能处理包含 ORDER BY、LIMIT/OFFSET、锁定或 WITH 子句的子查询 */
|
||||
if (subquery->sortClause || subquery->limitOffset || subquery->limitCount || subquery->rowMarks ||
|
||||
subquery->cteList)
|
||||
return false;
|
||||
|
||||
/* Recursively check the tree of set operations */
|
||||
/* 递归检查集合操作树 */
|
||||
return is_simple_union_all_recurse((Node*)topop, subquery, topop->colTypes);
|
||||
}
|
||||
|
||||
|
||||
static bool is_simple_union_all_recurse(Node* setOp, Query* setOpQuery, List* colTypes)
|
||||
{
|
||||
// 如果当前节点是 RangeTblRef 类型
|
||||
if (IsA(setOp, RangeTblRef)) {
|
||||
RangeTblRef* rtr = (RangeTblRef*)setOp;
|
||||
RangeTblEntry* rte = rt_fetch(rtr->rtindex, setOpQuery->rtable);
|
||||
Query* subquery = rte->subquery;
|
||||
RangeTblRef* rtr = (RangeTblRef*)setOp; // 将当前节点转换为 RangeTblRef 结构体
|
||||
RangeTblEntry* rte = rt_fetch(rtr->rtindex, setOpQuery->rtable); // 获取当前节点对应的范围表条目
|
||||
Query* subquery = rte->subquery; // 获取范围表条目中的子查询
|
||||
|
||||
AssertEreport(subquery != NULL, MOD_OPT_REWRITE, "subquery should not be NULL in is_simple_union_all_recurse");
|
||||
AssertEreport(subquery != NULL, MOD_OPT_REWRITE, "is_simple_union_all_recurse 中的子查询不应为 NULL");
|
||||
|
||||
/* Leaf nodes are OK if they match the toplevel column types */
|
||||
/* We don't have to compare typmods or collations here */
|
||||
/* 如果是叶子节点,需要检查其列类型是否与顶层查询的列类型匹配 */
|
||||
/* 我们不必在这里比较类型修饰符(typmods)或字符集(collations) */
|
||||
return tlist_same_datatypes(subquery->targetList, colTypes, true);
|
||||
} else if (IsA(setOp, SetOperationStmt)) {
|
||||
SetOperationStmt* op = (SetOperationStmt*)setOp;
|
||||
}
|
||||
// 如果当前节点是 SetOperationStmt 类型
|
||||
else if (IsA(setOp, SetOperationStmt)) {
|
||||
SetOperationStmt* op = (SetOperationStmt*)setOp; // 将当前节点转换为 SetOperationStmt 结构体
|
||||
|
||||
/* Must be UNION ALL */
|
||||
/* 必须是 UNION ALL 操作 */
|
||||
if (op->op != SETOP_UNION || !op->all)
|
||||
return false;
|
||||
|
||||
/* Recurse to check inputs */
|
||||
/* 递归检查左子树和右子树 */
|
||||
return is_simple_union_all_recurse(op->larg, setOpQuery, colTypes) &&
|
||||
is_simple_union_all_recurse(op->rarg, setOpQuery, colTypes);
|
||||
} else {
|
||||
}
|
||||
// 如果当前节点是未知的节点类型
|
||||
else {
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_OPT),
|
||||
errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
|
||||
errmsg("unrecognized node type: %d", (int)nodeTag(setOp))));
|
||||
return false; /* keep compiler quiet */
|
||||
errmsg("未知的节点类型: %d", (int)nodeTag(setOp))));
|
||||
return false; /* 使编译器保持安静 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* is_safe_append_member
|
||||
* Check a subquery that is a leaf of a UNION ALL appendrel to see if it's
|
||||
|
|
@ -1897,34 +1948,33 @@ jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
|
|||
if (jtnode == NULL)
|
||||
return false;
|
||||
if (IsA(jtnode, RangeTblRef))
|
||||
return false;
|
||||
return false; // 如果当前节点是范围表引用节点,不包含外部引用,返回 false
|
||||
else if (IsA(jtnode, FromExpr))
|
||||
{
|
||||
FromExpr *f = (FromExpr *) jtnode;
|
||||
ListCell *l = NULL;
|
||||
|
||||
/* First, recurse to check child joins */
|
||||
/* 首先递归检查子联接 */
|
||||
foreach(l, f->fromlist)
|
||||
{
|
||||
if (jointree_contains_lateral_outer_refs((Node *)lfirst(l),
|
||||
restricted,
|
||||
safe_upper_varnos))
|
||||
return true;
|
||||
return true; // 如果子联接包含外部引用,返回 true
|
||||
}
|
||||
|
||||
/* Then check the top-level quals */
|
||||
/* 然后检查顶层条件表达式 */
|
||||
if (restricted &&
|
||||
!bms_is_subset(pull_varnos_of_level(f->quals, 1),
|
||||
safe_upper_varnos))
|
||||
return true;
|
||||
return true; // 如果条件表达式中包含外部引用,且不在安全的上级变量范围内,返回 true
|
||||
}
|
||||
else if (IsA(jtnode, JoinExpr))
|
||||
{
|
||||
JoinExpr *j = (JoinExpr *) jtnode;
|
||||
|
||||
/*
|
||||
* If this is an outer join, we mustn't allow any upper lateral
|
||||
* references in or below it.
|
||||
* 如果这是一个外连接,我们不能允许在其内部或下方有任何上级的外部引用。
|
||||
*/
|
||||
if (j->jointype != JOIN_INNER)
|
||||
{
|
||||
|
|
@ -1932,28 +1982,29 @@ jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
|
|||
safe_upper_varnos = NULL;
|
||||
}
|
||||
|
||||
/* Check the child joins */
|
||||
/* 检查子联接 */
|
||||
if (jointree_contains_lateral_outer_refs(j->larg,
|
||||
restricted,
|
||||
safe_upper_varnos))
|
||||
return true;
|
||||
return true; // 如果左子联接包含外部引用,返回 true
|
||||
if (jointree_contains_lateral_outer_refs(j->rarg,
|
||||
restricted,
|
||||
safe_upper_varnos))
|
||||
return true;
|
||||
return true; // 如果右子联接包含外部引用,返回 true
|
||||
|
||||
/* Check the JOIN's qual clauses */
|
||||
/* 检查联接的条件表达式 */
|
||||
if (restricted &&
|
||||
!bms_is_subset(pull_varnos_of_level(j->quals, 1),
|
||||
safe_upper_varnos))
|
||||
return true;
|
||||
return true; // 如果条件表达式中包含外部引用,且不在安全的上级变量范围内,返回 true
|
||||
}
|
||||
else
|
||||
elog(ERROR, "unrecognized node type: %d",
|
||||
elog(ERROR, "未识别的节点类型: %d",
|
||||
(int) nodeTag(jtnode));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Helper routine for pull_up_subqueries: do pullup_replace_vars on every
|
||||
* expression in the jointree, without changing the jointree structure itself.
|
||||
|
|
@ -2057,10 +2108,20 @@ static void replace_vars_in_jointree(Node* jtnode, pullup_replace_vars_context*
|
|||
*/
|
||||
static Node* pullup_replace_vars(Node* expr, pullup_replace_vars_context* context)
|
||||
{
|
||||
// 使用 replace_rte_variables 函数替换表达式中的变量
|
||||
// 参数说明:
|
||||
// expr:待替换的表达式
|
||||
// context->varno:用于替换的范围表索引
|
||||
// 0:替换的层级深度(0 表示替换所有层级)
|
||||
// pullup_replace_vars_callback:用于处理替换的回调函数
|
||||
// (void*)context:回调函数的上下文数据
|
||||
// context->outer_hasSubLinks:是否包含子查询链接在外部查询中
|
||||
|
||||
return replace_rte_variables(
|
||||
expr, context->varno, 0, pullup_replace_vars_callback, (void*)context, context->outer_hasSubLinks);
|
||||
}
|
||||
|
||||
|
||||
static Node* pullup_replace_vars_callback(Var* var, replace_rte_variables_context* context)
|
||||
{
|
||||
pullup_replace_vars_context* rcon = (pullup_replace_vars_context*)context->callback_arg;
|
||||
|
|
@ -2220,7 +2281,18 @@ static Query *
|
|||
pullup_replace_vars_subquery(Query *query,
|
||||
pullup_replace_vars_context *context)
|
||||
{
|
||||
// 确保输入的 query 是一个有效的 Query 结构
|
||||
Assert(IsA(query, Query));
|
||||
|
||||
// 使用 replace_rte_variables 函数替换子查询中的变量
|
||||
// 参数说明:
|
||||
// (Node *) query:待替换的子查询
|
||||
// context->varno:用于替换的范围表索引
|
||||
// 1:替换的层级深度(1 表示只替换一层,即子查询中的变量)
|
||||
// pullup_replace_vars_callback:用于处理替换的回调函数
|
||||
// (void *) context:回调函数的上下文数据
|
||||
// NULL:不包含子查询链接在外部查询中
|
||||
|
||||
return (Query *) replace_rte_variables((Node *) query,
|
||||
context->varno, 1,
|
||||
pullup_replace_vars_callback,
|
||||
|
|
@ -2228,6 +2300,7 @@ pullup_replace_vars_subquery(Query *query,
|
|||
NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* flatten_simple_union_all
|
||||
* Try to optimize top-level UNION ALL structure into an appendrel
|
||||
|
|
|
|||
|
|
@ -48,35 +48,45 @@
|
|||
* ---------------------------------------------------------------------------------
|
||||
*/
|
||||
/* delete delta table definition */
|
||||
// 定义宏,表示pg_delete_delta关系的列数
|
||||
#define Natts_pg_delete_delta 3
|
||||
|
||||
// 定义列号常量,表示pg_delete_delta关系的各列
|
||||
#define Anum_pg_delete_delta_xcnodeid_and_dntableoid 1
|
||||
#define Anum_pg_delete_delta_tablebucketid_and_ctid 2
|
||||
|
||||
// 定义用于范围扫描的Redis字符串常量
|
||||
#define RANGE_SCAN_IN_REDIS "tidge+tid+pg_get_redis_rel_start_ctid+tidle+tid+pg_get_redis_rel_end_ctid+"
|
||||
|
||||
// 声明函数eval_dnstable_func_mutator,用于修改执行计划中的节点
|
||||
static Node* eval_dnstable_func_mutator(
|
||||
Relation rel, Node* node, StringInfo qual_str, RangeScanInRedis *rangeScanInRedis, bool isRoot);
|
||||
|
||||
// 内联函数,用于检查Redis元组ID检索函数的函数签名是否匹配
|
||||
static inline bool redis_tupleid_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs);
|
||||
|
||||
// 内联函数,用于检查Redis块号检索函数的函数签名是否匹配
|
||||
static inline bool redis_blocknum_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs);
|
||||
|
||||
// 内联函数,用于检查Redis偏移量检索函数的函数签名是否匹配
|
||||
static inline bool redis_offset_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs);
|
||||
|
||||
// 定义宏,用于检查Redis元组ID检索函数的函数签名是否匹配
|
||||
#define REDIS_TUPLEID_RETRIVE_FUNCSIG(rettype, argstype, nargs) \
|
||||
(((nargs) == 1 && (rettype) == TIDOID && (argstype)[0] == TEXTOID) || \
|
||||
((nargs) == 4 && (rettype) == TIDOID && (argstype)[0] == TEXTOID && (argstype)[1] == NAMEOID && \
|
||||
(argstype)[2] == INT4OID && (argstype)[3] == INT4OID))
|
||||
|
||||
// 内联函数,用于检查Redis元组ID检索函数的函数签名是否匹配
|
||||
static inline bool redis_tupleid_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs)
|
||||
|
||||
{
|
||||
// 检查函数名是否为pg_get_redis_rel_start_ctid,且函数签名匹配
|
||||
if (pg_strcasecmp(funcname, "pg_get_redis_rel_start_ctid") == 0 &&
|
||||
REDIS_TUPLEID_RETRIVE_FUNCSIG(rettype, argstype, nargs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查函数名是否为pg_get_redis_rel_end_ctid,且函数签名匹配
|
||||
if (pg_strcasecmp(funcname, "pg_get_redis_rel_end_ctid") == 0 &&
|
||||
REDIS_TUPLEID_RETRIVE_FUNCSIG(rettype, argstype, nargs)) {
|
||||
return true;
|
||||
|
|
@ -85,9 +95,10 @@ static inline bool redis_tupleid_retrive_function(const char* funcname, Oid rett
|
|||
return false;
|
||||
}
|
||||
|
||||
// 内联函数,用于检查Redis偏移量检索函数的函数签名是否匹配
|
||||
static inline bool redis_offset_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs)
|
||||
|
||||
{
|
||||
// 检查函数名是否为pg_tupleid_get_offset,且函数签名匹配
|
||||
if (pg_strcasecmp(funcname, "pg_tupleid_get_offset") == 0 &&
|
||||
(nargs == 1 && rettype == INT4OID && argstype[0] == TIDOID)) {
|
||||
return true;
|
||||
|
|
@ -96,9 +107,10 @@ static inline bool redis_offset_retrive_function(const char* funcname, Oid retty
|
|||
return false;
|
||||
}
|
||||
|
||||
// 内联函数,用于检查Redis块号检索函数的函数签名是否匹配
|
||||
static inline bool redis_blocknum_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs)
|
||||
|
||||
{
|
||||
// 检查函数名是否为pg_tupleid_get_blocknum,且函数签名匹配
|
||||
if (pg_strcasecmp(funcname, "pg_tupleid_get_blocknum") == 0 &&
|
||||
(nargs == 1 && rettype == INT8OID && argstype[0] == TIDOID)) {
|
||||
return true;
|
||||
|
|
@ -107,9 +119,10 @@ static inline bool redis_blocknum_retrive_function(const char* funcname, Oid ret
|
|||
return false;
|
||||
}
|
||||
|
||||
// 内联函数,用于检查Redis元组ID检索函数的函数签名是否匹配
|
||||
static inline bool redis_ctid_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs)
|
||||
|
||||
{
|
||||
// 检查函数名是否为pg_tupleid_get_ctid_to_bigint,且函数签名匹配
|
||||
if (pg_strcasecmp(funcname, "pg_tupleid_get_ctid_to_bigint") == 0 &&
|
||||
(nargs == 1 && rettype == INT8OID && argstype[0] == TIDOID)) {
|
||||
return true;
|
||||
|
|
@ -119,6 +132,7 @@ static inline bool redis_ctid_retrive_function(const char* funcname, Oid rettype
|
|||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: Record the given tuple's tupleid into pg_delete_delta table
|
||||
* - Parameter:
|
||||
|
|
@ -127,35 +141,50 @@ static inline bool redis_ctid_retrive_function(const char* funcname, Oid rettype
|
|||
* - Return:
|
||||
* no return value
|
||||
*/
|
||||
/*
|
||||
这个函数的主要作用是将一个删除操作的信息记录到名为delete_delta的关系中。
|
||||
函数接受关系的OID(relid)、存储桶ID(bucketid)、被删除元组的位置(tupleid),
|
||||
以及指向删除增量关系的指针(deldelta_rel)。它首先初始化一个用于存储插入元组的数组(values)和一个用于表示元组中的值是否为空的数组(nulls)。
|
||||
然后,它填充values数组中的各个列,构建一个堆元组,最后将该堆元组插入到delete_delta关系中。最后,函数释放堆元组的内存。
|
||||
*/
|
||||
void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, const Relation deldelta_rel)
|
||||
{
|
||||
Datum values[Natts_pg_delete_delta];
|
||||
bool nulls[Natts_pg_delete_delta];
|
||||
HeapTuple tup = NULL;
|
||||
Datum values[Natts_pg_delete_delta]; // 用于存储插入元组的值
|
||||
bool nulls[Natts_pg_delete_delta]; // 用于表示元组中的值是否为空
|
||||
HeapTuple tup = NULL; // 声明堆元组变量
|
||||
|
||||
Assert(deldelta_rel);
|
||||
/* In redistribution, table delete_delta has 3 or 2 column. */
|
||||
Assert(RelationGetDescr(deldelta_rel)->natts <= 3);
|
||||
Assert(deldelta_rel); // 断言:确保删除增量关系不为空
|
||||
|
||||
/* Iterate through attributes initializing nulls and values */
|
||||
/* 在重分布中,表 delete_delta 具有 3 或 2 列。*/
|
||||
Assert(RelationGetDescr(deldelta_rel)->natts <= 3); // 断言:确保删除增量关系的列数不超过 3
|
||||
|
||||
/* 遍历属性并初始化 nulls 和 values 数组 */
|
||||
for (int i = 0; i < Natts_pg_delete_delta; i++) {
|
||||
nulls[i] = false;
|
||||
values[i] = (Datum)0;
|
||||
nulls[i] = false; // 所有属性的初始值均为非空
|
||||
values[i] = (Datum)0; // 所有属性的初始值为 0
|
||||
}
|
||||
|
||||
// 设置 xcnodeid_and_dntableoid 列的值
|
||||
values[Anum_pg_delete_delta_xcnodeid_and_dntableoid - 1] =
|
||||
UInt64GetDatum(((uint64)u_sess->pgxc_cxt.PGXCNodeIdentifier << 32) | relid);
|
||||
|
||||
// 设置 tablebucketid_and_ctid 列的值
|
||||
values[Anum_pg_delete_delta_tablebucketid_and_ctid - 1] =
|
||||
UInt64GetDatum(((uint64)ItemPointerGetBlockNumber(tupleid) << 16) | ItemPointerGetOffsetNumber(tupleid));
|
||||
|
||||
// 如果 bucketid 有效,将其设置到 tablebucketid_and_ctid 列的高位
|
||||
if (BUCKET_NODE_IS_VALID(bucketid)) {
|
||||
values[Anum_pg_delete_delta_tablebucketid_and_ctid - 1] |= ((uint64)bucketid << 48);
|
||||
}
|
||||
/* Record delta */
|
||||
tup = heap_form_tuple(RelationGetDescr(deldelta_rel), values, nulls);
|
||||
(void)simple_heap_insert(deldelta_rel, tup);
|
||||
|
||||
tableam_tops_free_tuple(tup);
|
||||
/* 记录删除增量 */
|
||||
tup = heap_form_tuple(RelationGetDescr(deldelta_rel), values, nulls); // 创建堆元组
|
||||
(void)simple_heap_insert(deldelta_rel, tup); // 插入元组到关系中
|
||||
|
||||
tableam_tops_free_tuple(tup); // 释放堆元组的内存
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the relation is under cluster resizing operation
|
||||
* - Parameter:
|
||||
|
|
@ -166,15 +195,16 @@ void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, con
|
|||
*/
|
||||
bool RelationInClusterResizing(const Relation rel)
|
||||
{
|
||||
Assert(rel != NULL);
|
||||
Assert(rel != NULL); // 断言:确保传递的关系参数不为空
|
||||
|
||||
/* Check relation's append_mode status */
|
||||
/* 检查关系的 append_mode 状态 */
|
||||
if (!IsInitdb && RelationInRedistribute(rel))
|
||||
return true;
|
||||
return true; // 如果不是初始化数据库且关系正在重新分布,则返回 true
|
||||
|
||||
return false;
|
||||
return false; // 如果不满足上述条件,则返回 false
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the relation is under cluster resizing read only operation
|
||||
* - Parameter:
|
||||
|
|
@ -185,15 +215,16 @@ bool RelationInClusterResizing(const Relation rel)
|
|||
*/
|
||||
bool RelationInClusterResizingReadOnly(const Relation rel)
|
||||
{
|
||||
Assert(rel != NULL);
|
||||
Assert(rel != NULL); // 断言:确保传递的关系参数不为空
|
||||
|
||||
/* Check relation's append_mode status */
|
||||
/* 检查关系的 append_mode 状态 */
|
||||
if (!IsInitdb && RelationInRedistributeReadOnly(rel))
|
||||
return true;
|
||||
return true; // 如果不是初始化数据库且关系正在以只读方式重新分布,则返回 true
|
||||
|
||||
return false;
|
||||
return false; // 如果不满足上述条件,则返回 false
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the relation is under cluster resizing read only operation
|
||||
* - Parameter:
|
||||
|
|
@ -204,15 +235,16 @@ bool RelationInClusterResizingReadOnly(const Relation rel)
|
|||
*/
|
||||
bool RelationInClusterResizingEndCatchup(const Relation rel)
|
||||
{
|
||||
Assert(rel != NULL);
|
||||
Assert(rel != NULL); // 断言:确保传递的关系参数不为空
|
||||
|
||||
/* Check relation's append_mode status */
|
||||
/* 检查关系的 append_mode 状态 */
|
||||
if (!IsInitdb && RelationInRedistributeEndCatchup(rel))
|
||||
return true;
|
||||
return true; // 如果不是初始化数据库且关系处于重新分布的"EndCatchup"阶段,则返回 true
|
||||
|
||||
return false;
|
||||
return false; // 如果不满足上述条件,则返回 false
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @Description: check whether relation is in redistribution though range variable.
|
||||
* @in range_var: range variable which stored relation info.
|
||||
|
|
@ -224,26 +256,35 @@ bool CheckRangeVarInRedistribution(const RangeVar* range_var)
|
|||
Oid relid;
|
||||
bool in_redis = false;
|
||||
|
||||
// 获取范围变量对应的关系OID
|
||||
relid = RangeVarGetRelid(range_var, AccessShareLock, true);
|
||||
|
||||
if (OidIsValid(relid)) {
|
||||
// 打开关系
|
||||
relation = relation_open(relid, NoLock);
|
||||
/* If the relation is index, we should check the related table is resizing or not. */
|
||||
|
||||
// 如果关系是索引,则检查相关的表是否处于重新分布状态
|
||||
if (RelationIsIndex(relation)) {
|
||||
Oid heapOid = IndexGetRelation(relid, false);
|
||||
Relation heapRelation = relation_open(heapOid, AccessShareLock);
|
||||
in_redis = RelationInClusterResizing(heapRelation);
|
||||
relation_close(heapRelation, AccessShareLock);
|
||||
} else {
|
||||
// 否则,检查关系是否处于重新分布状态
|
||||
in_redis = RelationInClusterResizing(relation);
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>闭关系
|
||||
relation_close(relation, NoLock);
|
||||
|
||||
// 解锁关系OID
|
||||
UnlockRelationOid(relid, AccessShareLock);
|
||||
}
|
||||
return in_redis;
|
||||
|
||||
return in_redis; // 返回指示是否处于重新分布状态的布尔值
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the table name is delete_delta table.
|
||||
* - Parameter:
|
||||
|
|
@ -258,39 +299,44 @@ bool RelationIsDeleteDeltaTable(char* delete_delta_name)
|
|||
uint64 val;
|
||||
char* endptr = NULL;
|
||||
HeapTuple tuple;
|
||||
|
||||
// 如果是初始化数据库,直接返回 false,因为没有删除增量表
|
||||
if (IsInitdb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查表名是否以 "pg_delete_delta_" 开头
|
||||
if (strncmp(delete_delta_name, "pg_delete_delta_", 16) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将表名中的数字部分转换为 uint64
|
||||
val = strtoull(delete_delta_name + 16, &endptr, 0);
|
||||
|
||||
// 检查转换是否成功
|
||||
if ((errno == ERANGE) || (errno != 0 && val == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查表名是否有效,数字后面不应有其他字符
|
||||
if (endptr == delete_delta_name + 16 || *endptr != '\0') {
|
||||
return false;
|
||||
}
|
||||
// 获取表的OID
|
||||
relid = (Oid)val;
|
||||
|
||||
// 检查OID是否有效
|
||||
if (!OidIsValid(relid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 搜索系统缓存以获取表的信息
|
||||
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
|
||||
// 如果未找到表信息,发出警告并返回 false
|
||||
if (!HeapTupleIsValid(tuple)) {
|
||||
elog(WARNING, "Table %u related to %s does not exists.", relid, delete_delta_name);
|
||||
elog(WARNING, "Table %u related to %s does not exist.", relid, delete_delta_name);
|
||||
return false;
|
||||
}
|
||||
// 释放系统缓存
|
||||
ReleaseSysCache(tuple);
|
||||
// 返回 true 表示表是删除增量表
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the Progress is under cluster resizing status
|
||||
* - Return:
|
||||
|
|
@ -299,35 +345,46 @@ bool RelationIsDeleteDeltaTable(char* delete_delta_name)
|
|||
*/
|
||||
bool ClusterResizingInProgress()
|
||||
{
|
||||
Relation pgxc_group_rel = NULL;
|
||||
Relation pgxc_group_rel = NULL; // 定义一个关系变量
|
||||
TableScanDesc scan;
|
||||
HeapTuple tup = NULL;
|
||||
Datum datum;
|
||||
bool isNull = false;
|
||||
bool result = false;
|
||||
|
||||
// 打开 pgxc_group 关系以获取有关集群状态的信息
|
||||
pgxc_group_rel = heap_open(PgxcGroupRelationId, AccessShareLock);
|
||||
|
||||
// 如果打开失败,发出 PANIC 错误
|
||||
if (!pgxc_group_rel) {
|
||||
ereport(PANIC, (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("can not open pgxc_group")));
|
||||
}
|
||||
|
||||
// 开始对 pgxc_group 表进行扫描
|
||||
scan = tableam_scan_begin(pgxc_group_rel, SnapshotNow, 0, NULL);
|
||||
|
||||
// 遍历扫描结果
|
||||
while ((tup = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
|
||||
// 获取表中 "in_redistribution" 列的值
|
||||
datum = heap_getattr(tup, Anum_pgxc_group_in_redistribution, RelationGetDescr(pgxc_group_rel), &isNull);
|
||||
|
||||
// 如果值为 'y',表示集群正在进行调整大小,将结果设置为 true 并跳出循环
|
||||
if ('y' == DatumGetChar(datum)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 结束表扫描
|
||||
tableam_scan_end(scan);
|
||||
|
||||
// 关闭 pgxc_group 关系
|
||||
heap_close(pgxc_group_rel, AccessShareLock);
|
||||
|
||||
return result;
|
||||
return result; // 返回 true 表示集群正在进行调整大小,否则返回 false
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: get the name of delete_delta table
|
||||
* - Parameter:
|
||||
|
|
@ -341,25 +398,26 @@ static inline void RelationGetDeleteDeltaTableName(Relation rel, char* delete_de
|
|||
{
|
||||
int rc = 0;
|
||||
|
||||
/* Check if output parameter it not palloc()-ed from caller side */
|
||||
/* 检查输出参数是否由调用方分配内存 */
|
||||
if (delete_delta_name == NULL || rel == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid parameter in function '%s'", __FUNCTION__)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up Relation's reloptions to get table's cnoid to
|
||||
* form the name of delete_delta table
|
||||
* 查找关系的 reloptions 以获取表的 cnoid 以形成 delete_delta 表的名称
|
||||
*/
|
||||
if (!IsInitdb) {
|
||||
if (RelationInClusterResizing(rel) && !RelationInClusterResizingReadOnly(rel)) {
|
||||
if (isMultiCatchup) {
|
||||
// 构建多重赶回(multi-catchup)删除增量表的名称
|
||||
rc = snprintf_s(delete_delta_name,
|
||||
NAMEDATALEN,
|
||||
NAMEDATALEN - 1,
|
||||
REDIS_MULTI_CATCHUP_DELETE_DELTA_TABLE_PREFIX "%u",
|
||||
RelationGetRelCnOid(rel));
|
||||
} else {
|
||||
// 构建删除增量表的名称
|
||||
rc = snprintf_s(delete_delta_name,
|
||||
NAMEDATALEN,
|
||||
NAMEDATALEN - 1,
|
||||
|
|
@ -368,6 +426,7 @@ static inline void RelationGetDeleteDeltaTableName(Relation rel, char* delete_de
|
|||
}
|
||||
securec_check_ss(rc, "\0", "\0");
|
||||
} else {
|
||||
// 如果关系不在重新分布中,使用关系的名称构建删除增量表的名称
|
||||
elog(LOG, "rel %s doesn't exist in redistributing", RelationGetRelationName(rel));
|
||||
rc = snprintf_s(delete_delta_name,
|
||||
NAMEDATALEN,
|
||||
|
|
@ -380,6 +439,7 @@ static inline void RelationGetDeleteDeltaTableName(Relation rel, char* delete_de
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: get and open delete_delta rel
|
||||
* - Parameter:
|
||||
|
|
@ -397,28 +457,32 @@ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool is
|
|||
Oid data_redis_namespace;
|
||||
errno_t errorno;
|
||||
|
||||
// 初始化变量并清零
|
||||
errorno = memset_s(delete_delta_tablename, NAMEDATALEN, 0, NAMEDATALEN);
|
||||
securec_check_c(errorno, "\0", "\0");
|
||||
|
||||
// 获取删除增量表的名称
|
||||
RelationGetDeleteDeltaTableName(rel, (char*)delete_delta_tablename, isMultiCatchup);
|
||||
|
||||
// 获取 "data_redis" 命名空间的OID
|
||||
data_redis_namespace = get_namespace_oid("data_redis", false);
|
||||
|
||||
/* We are going to fetch the delete delta relation under data_redis schema. */
|
||||
/* 我们将在 data_redis 模式下获取删除增量关系。 */
|
||||
deldelta_relid = get_relname_relid(delete_delta_tablename, data_redis_namespace);
|
||||
|
||||
if (!OidIsValid(deldelta_relid)) {
|
||||
/*
|
||||
* If multi catchup delta table is not there, just return NULL. We should not
|
||||
* report error, because it is a valid case. Multi catchup delta table is
|
||||
* dropped in each catchup iteration.
|
||||
* 如果多重赶回(multi-catchup)删除增量表不存在,只需返回 NULL。不应报告错误,因为这是有效的情况。
|
||||
* 多重赶回(multi-catchup)删除增量表在每次赶回迭代中都会被删除。
|
||||
*/
|
||||
if (isMultiCatchup) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* To support Update or Delete during extension, we need to add 2 more columns.
|
||||
* more columns. Limited by MaxHeapAttributeNumber, if the table already contains too many columns,
|
||||
* we don't allow update or delete anymore, but insert statement can still proceed.
|
||||
* 为了支持扩展期间的更新或删除,我们需要添加2个额外的列。
|
||||
* 受 MaxHeapAttributeNumber 限制,如果表已经包含太多列,
|
||||
* 我们将不再允许更新或删除,但插入语句仍然可以执行。
|
||||
*/
|
||||
if (((rel->rd_att->natts > (MaxHeapAttributeNumber - (Natts_pg_delete_delta - 1))) &&
|
||||
!RELATION_IS_PARTITIONED(rel)) ||
|
||||
|
|
@ -429,22 +493,28 @@ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool is
|
|||
RelationGetRelationName(rel)),
|
||||
errdetail("Can not support online extension, if the table contains too many columns")));
|
||||
}
|
||||
/* ERROR case, should never come here */
|
||||
|
||||
/* 错误情况,不应该到达这里 */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_TABLE),
|
||||
errmsg("delete delta table %s is not found when do cluster resizing table \"%s\"",
|
||||
delete_delta_tablename,
|
||||
RelationGetRelationName(rel))));
|
||||
}
|
||||
|
||||
// 打开删除增量表并返回其关系对象
|
||||
deldelta_rel = relation_open(deldelta_relid, lockmode);
|
||||
|
||||
// 输出调试信息,表示删除增量表有效
|
||||
elog(DEBUG1,
|
||||
"Delete_delta table %s for relation %s being under cluster resizing is valid.",
|
||||
delete_delta_tablename,
|
||||
RelationGetRelationName(rel));
|
||||
|
||||
return deldelta_rel;
|
||||
return deldelta_rel; // 返回删除增量表的关系对象
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: Check the stmtment during online expansion, block unsupported ddl in cluster resizing.
|
||||
* - Parameter:
|
||||
|
|
@ -927,19 +997,21 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
*/
|
||||
bool redis_func_shippable(Oid funcid)
|
||||
{
|
||||
const char* func_name = get_func_name(funcid);
|
||||
Oid* argstype = NULL;
|
||||
int nargs;
|
||||
Oid rettype = InvalidOid;
|
||||
bool result = false;
|
||||
const char* func_name = get_func_name(funcid); // 获取函数的名称
|
||||
Oid* argstype = NULL; // 函数的参数类型数组
|
||||
int nargs; // 函数的参数数量
|
||||
Oid rettype = InvalidOid; // 函数的返回类型
|
||||
bool result = false; // 结果标志,初始值为 false
|
||||
|
||||
// 如果函数名称为 NULL,报告错误,表示函数不存在
|
||||
if (func_name == NULL) {
|
||||
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function with OID %u does not exist", funcid)));
|
||||
}
|
||||
|
||||
/* Fetch function signatures */
|
||||
// 获取函数的签名,包括返回类型和参数类型
|
||||
rettype = get_func_signature(funcid, &argstype, &nargs);
|
||||
|
||||
// 检查函数是否是Redis环境中可运行的函数类型
|
||||
if (redis_tupleid_retrive_function(func_name, rettype, argstype, nargs)) {
|
||||
/* tupleid retrive functions is shippable to datanodes */
|
||||
result = true;
|
||||
|
|
@ -951,15 +1023,16 @@ bool redis_func_shippable(Oid funcid)
|
|||
result = true;
|
||||
}
|
||||
|
||||
/* pfree */
|
||||
// 释放参数类型数组的内存
|
||||
if (argstype != NULL) {
|
||||
pfree_ext(argstype);
|
||||
argstype = NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
return result; // 返回函数是否可在Redis环境中运行的结果
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: determine if given funcid reflects a dn-stable function
|
||||
* - Parameter:
|
||||
|
|
@ -969,29 +1042,32 @@ bool redis_func_shippable(Oid funcid)
|
|||
*/
|
||||
bool redis_func_dnstable(Oid funcid)
|
||||
{
|
||||
const char* func_name = get_func_name(funcid);
|
||||
Oid* argstype = NULL;
|
||||
int nargs;
|
||||
Oid rettype = InvalidOid;
|
||||
bool result = false;
|
||||
const char* func_name = get_func_name(funcid); // 获取函数的名称
|
||||
Oid* argstype = NULL; // 函数的参数类型数组
|
||||
int nargs; // 函数的参数数量
|
||||
Oid rettype = InvalidOid; // 函数的返回类型
|
||||
bool result = false; // 结果标志,初始值为 false
|
||||
|
||||
// 如果函数名称为 NULL,报告错误,表示函数不存在
|
||||
if (func_name == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
||||
errmsg("function with OID %u does not exist when checking function dnstable", funcid)));
|
||||
}
|
||||
|
||||
/* Fetch function signatures */
|
||||
// 获取函数的签名,包括返回类型和参数类型
|
||||
rettype = get_func_signature(funcid, &argstype, &nargs);
|
||||
|
||||
// 检查函数是否属于"dnstable"函数类型,这里是通过检查与元组ID(tuple ID)相关的函数来判断
|
||||
if (redis_tupleid_retrive_function(func_name, rettype, argstype, nargs)) {
|
||||
/* tupleid retrive functions is dnstable */
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
return result; // 返回函数是否属于"dnstable"函数类型的结果
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: evaluate ctid functions into a const value to avoid per-scanning
|
||||
* tuple invokation in seqscan.
|
||||
|
|
@ -1004,12 +1080,11 @@ bool redis_func_dnstable(Oid funcid)
|
|||
*/
|
||||
List* eval_ctid_funcs(Relation rel, List* original_quals, RangeScanInRedis *rangeScanInRedis)
|
||||
{
|
||||
|
||||
StringInfo qual_str = makeStringInfo();
|
||||
|
||||
/*
|
||||
* we have to make a copy of the original quals, since the eval_dnstable_func_mutator
|
||||
* will modify the it. the original qual will be needed again and again in later
|
||||
* to be re-eval in partition table scans.
|
||||
* 我们必须复制原始的 quals,因为 eval_dnstable_func_mutator
|
||||
* 会修改它。原始 quals 会在后续的分区表扫描中再次需要。
|
||||
*/
|
||||
List* new_quals = (List*)copyObject((const void*)(original_quals));
|
||||
|
||||
|
|
@ -1018,12 +1093,14 @@ List* eval_ctid_funcs(Relation rel, List* original_quals, RangeScanInRedis *rang
|
|||
rangeScanInRedis->sliceIndex = 0;
|
||||
(void)eval_dnstable_func_mutator(rel, (Node*)new_quals, qual_str, rangeScanInRedis, true);
|
||||
|
||||
// 释放资源
|
||||
pfree_ext(qual_str->data);
|
||||
pfree_ext(qual_str);
|
||||
|
||||
return new_quals;
|
||||
}
|
||||
|
||||
|
||||
static int32 get_expr_const_val(Node *val){
|
||||
if (IsA(val, Const) && !((Const*)val)->constisnull && ((Const*)val)->consttype == INT4OID) {
|
||||
return DatumGetInt32(((Const*)val)->constvalue);
|
||||
|
|
@ -1047,40 +1124,53 @@ static int32 get_expr_const_val(Node *val){
|
|||
static Node* eval_dnstable_func_mutator(
|
||||
Relation rel, Node* node, StringInfo qual_str, RangeScanInRedis *rangeScanInRedis, bool isRoot)
|
||||
{
|
||||
// 如果节点为空,直接返回 NULL
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
// 如果运行在 PGXC 协调器节点,不做任何处理,直接返回原始节点
|
||||
if (IS_PGXC_COORDINATOR)
|
||||
return node;
|
||||
|
||||
// 根据节点类型进行处理
|
||||
switch (nodeTag(node)) {
|
||||
case T_FuncExpr: {
|
||||
FuncExpr* expr = (FuncExpr*)node;
|
||||
|
||||
/* flatten dn stable function into const value */
|
||||
/* 将 "dnstable" 函数替换为常量值 */
|
||||
if (redis_func_dnstable(expr->funcid)) {
|
||||
Node* new_const = NULL;
|
||||
char* funcname = get_func_name(expr->funcid);
|
||||
|
||||
// 如果函数名不存在,报错
|
||||
if (funcname == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
||||
errmsg("operation expression function with OID %u does not exist.", expr->funcid)));
|
||||
}
|
||||
|
||||
// 检查函数是否为特定的两个函数
|
||||
bool is_func_get_start_ctid = pg_strcasecmp(funcname, "pg_get_redis_rel_start_ctid") == 0;
|
||||
bool is_func_get_end_ctid = pg_strcasecmp(funcname, "pg_get_redis_rel_end_ctid") == 0;
|
||||
|
||||
// 根据函数类型进行不同的处理
|
||||
if (is_func_get_start_ctid || is_func_get_end_ctid){
|
||||
// 获取两个额外的参数值
|
||||
int32 numSlices = get_expr_const_val((Node*)list_nth(expr->args, 2));
|
||||
int32 idxSlices = get_expr_const_val((Node*)list_nth(expr->args, 3));
|
||||
// 调用函数获取新的常量值
|
||||
new_const = eval_redis_func_direct(rel, is_func_get_start_ctid, numSlices, idxSlices);
|
||||
// 更新范围扫描信息
|
||||
rangeScanInRedis->sliceIndex = idxSlices;
|
||||
rangeScanInRedis->sliceTotal = numSlices;
|
||||
} else {
|
||||
// 对于其他的 dnstable 函数,简单地将其替换为常量表达式的值
|
||||
new_const = eval_const_expressions(NULL, node);
|
||||
}
|
||||
// 将函数名和 "+" 添加到查询字符串中
|
||||
appendStringInfoString(qual_str, get_func_name(expr->funcid));
|
||||
appendStringInfoString(qual_str, "+");
|
||||
// 返回新的常量值
|
||||
return new_const;
|
||||
}
|
||||
|
||||
|
|
@ -1088,13 +1178,14 @@ static Node* eval_dnstable_func_mutator(
|
|||
}
|
||||
case T_List: {
|
||||
List* l = (List*)node;
|
||||
// 遍历列表中的每个表达式节点
|
||||
for (int i = 0; i < list_length(l); i++) {
|
||||
Node* expr = (Node*)list_nth(l, i);
|
||||
// 递归调用自身,替换列表中的节点
|
||||
Node* new_expr = eval_dnstable_func_mutator(rel, expr, qual_str, rangeScanInRedis, false);
|
||||
|
||||
/*
|
||||
* If a FuncExpr node is evalated into a T_Const value, we are hitting
|
||||
* the point so replace it in qual list.
|
||||
* 如果一个 FuncExpr 节点被评估为 T_Const 值,我们将其替换为 quals 列表中的相应节点。
|
||||
*/
|
||||
if (expr && IsA(expr, FuncExpr) && new_expr && IsA(new_expr, Const)) {
|
||||
l = list_delete_ptr(l, expr);
|
||||
|
|
@ -1103,8 +1194,7 @@ static Node* eval_dnstable_func_mutator(
|
|||
}
|
||||
|
||||
/*
|
||||
* If the predicate at root is something like "where ctid between pg_get_redis_rel_start_ctid('xx')
|
||||
* and pg_get_redis_rel_end_ctid('xx')" on DN, we will pushdown the predicate at scan node.
|
||||
* 如果是根节点,并且查询字符串中包含特定字符串 "RANGE_SCAN_IN_REDIS",则将范围扫描标志设置为 true。
|
||||
*/
|
||||
if (isRoot && pg_strcasecmp(qual_str->data, RANGE_SCAN_IN_REDIS) == 0) {
|
||||
rangeScanInRedis->isRangeScanInRedis = true;
|
||||
|
|
@ -1116,20 +1206,23 @@ static Node* eval_dnstable_func_mutator(
|
|||
OpExpr* opexpr = (OpExpr*)node;
|
||||
char* funcname = get_func_name(opexpr->opfuncid);
|
||||
|
||||
// 如果函数名不存在,报错
|
||||
if (funcname == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
||||
errmsg("operation expression function with OID %u does not exist.", opexpr->opfuncid)));
|
||||
}
|
||||
// 将函数名添加到查询字符串中
|
||||
appendStringInfoString(qual_str, funcname);
|
||||
appendStringInfoString(qual_str, "+");
|
||||
// 递归处理操作表达式的参数节点
|
||||
eval_dnstable_func_mutator(rel, (Node*)opexpr->args, qual_str, rangeScanInRedis, false);
|
||||
|
||||
break;
|
||||
}
|
||||
case T_Var: {
|
||||
Var* var = (Var*)node;
|
||||
/* we only expect tid column in the predicate */
|
||||
// 如果变量的类型是 TIDOID,将 "tid" 添加到查询字符串中
|
||||
if (var->vartype == TIDOID) {
|
||||
appendStringInfoString(qual_str, "tid");
|
||||
appendStringInfoString(qual_str, "+");
|
||||
|
|
@ -1137,6 +1230,7 @@ static Node* eval_dnstable_func_mutator(
|
|||
break;
|
||||
}
|
||||
default: {
|
||||
// 对于其他节点类型,将节点的类型名称添加到查询字符串中
|
||||
appendStringInfoString(qual_str, nodeTagToString(nodeTag(node)));
|
||||
appendStringInfoString(qual_str, "+");
|
||||
break;
|
||||
|
|
@ -1146,6 +1240,7 @@ static Node* eval_dnstable_func_mutator(
|
|||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: get and open new_table rel
|
||||
* - Parameter:
|
||||
|
|
@ -1155,26 +1250,36 @@ static Node* eval_dnstable_func_mutator(
|
|||
*/
|
||||
Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode)
|
||||
{
|
||||
// 声明变量并初始化为初始值
|
||||
Relation newtable_rel = NULL;
|
||||
Oid newtable_relid = InvalidOid;
|
||||
Oid data_redis_namespace;
|
||||
char new_tablename[NAMEDATALEN];
|
||||
errno_t errorno = EOK;
|
||||
|
||||
// 使用 memset_s 函数将 new_tablename 数组清零
|
||||
errorno = memset_s(new_tablename, NAMEDATALEN, 0, NAMEDATALEN);
|
||||
securec_check_c(errorno, "\0", "\0");
|
||||
|
||||
// 调用 RelationGetNewTableName 函数获取新表的名称
|
||||
RelationGetNewTableName(rel, (char*)new_tablename);
|
||||
|
||||
// 获取 data_redis 命名空间的 OID
|
||||
data_redis_namespace = get_namespace_oid("data_redis", false);
|
||||
|
||||
// 根据新表名和命名空间获取新表的 OID
|
||||
newtable_relid = get_relname_relid(new_tablename, data_redis_namespace);
|
||||
|
||||
// 如果新表的 OID 无效,报错,不应该出现在正常情况下
|
||||
if (!OidIsValid(newtable_relid)) {
|
||||
/* ERROR case, should never come here */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("new table %s is not found when do cluster resizing table \"%s\"",
|
||||
new_tablename,
|
||||
RelationGetRelationName(rel))));
|
||||
}
|
||||
|
||||
// 打开新表并返回新表的关系对象
|
||||
newtable_rel = relation_open(newtable_relid, lockmode);
|
||||
elog(LOG,
|
||||
"New temp table %s for relation %s under cluster resizing is valid.",
|
||||
|
|
@ -1184,6 +1289,7 @@ Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode)
|
|||
return newtable_rel;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: get the name of new table
|
||||
* - Parameter:
|
||||
|
|
@ -1196,7 +1302,7 @@ void RelationGetNewTableName(Relation rel, char* newtable_name)
|
|||
{
|
||||
int rc = 0;
|
||||
|
||||
/* Check if output parameter it not palloc()-ed from caller side */
|
||||
// 检查输出参数 newtable_name 是否为 NULL,以及传入的 rel 是否为 NULL
|
||||
if (newtable_name == NULL || rel == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
|
|
@ -1204,24 +1310,30 @@ void RelationGetNewTableName(Relation rel, char* newtable_name)
|
|||
}
|
||||
|
||||
/*
|
||||
* Look up relaion's reloptions to get table's cnoid to
|
||||
* form the name of new table
|
||||
* 查找关系的 reloptions 以获取表的 cnoid,
|
||||
* 以形成新表的名称
|
||||
*/
|
||||
if (!IsInitdb) {
|
||||
// 获取关系的 cnoid
|
||||
Oid rel_cn_oid = RelationGetRelCnOid(rel);
|
||||
|
||||
// 如果 cnoid 有效,使用它创建新表的名称
|
||||
if (OidIsValid(rel_cn_oid)) {
|
||||
rc = snprintf_s(newtable_name, NAMEDATALEN, NAMEDATALEN - 1, "data_redis_tmp_%u", rel_cn_oid);
|
||||
} else {
|
||||
// 如果 cnoid 无效,记录日志并使用关系的名称创建新表的名称
|
||||
elog(LOG, "rel %s doesn't exist in redistributing", RelationGetRelationName(rel));
|
||||
rc = snprintf_s(
|
||||
newtable_name, NAMEDATALEN, NAMEDATALEN - 1, "data_redis_tmp_%s", RelationGetRelationName(rel));
|
||||
}
|
||||
/* check the return value of security function */
|
||||
|
||||
// 检查 snprintf_s 函数的返回值以确保没有溢出
|
||||
securec_check_ss(rc, "\0", "\0");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the relation is under cluster resizing write error mode
|
||||
* - Parameter:
|
||||
|
|
@ -1232,7 +1344,9 @@ void RelationGetNewTableName(Relation rel, char* newtable_name)
|
|||
*/
|
||||
bool RelationInClusterResizingWriteErrorMode(const Relation rel)
|
||||
{
|
||||
// 检查关系是否在只读模式下进行集群调整,或者在结束追赶阶段并且没有成功获取 Redis 锁
|
||||
return RelationInClusterResizingReadOnly(rel) ||
|
||||
(RelationInClusterResizingEndCatchup(rel) && !pg_try_advisory_lock_for_redis(rel));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@ static ScanState* search_plan_tree(PlanState *node, Oid table_oid);
|
|||
* legal situation in inheritance cases). Raises error if cursor is not a
|
||||
* valid updatable scan of the specified table.
|
||||
*/
|
||||
/*
|
||||
函数的主要目的是根据 CURRENT OF 表达式获取游标的当前位置,
|
||||
并返回相关的 TID(Tuple ID)。函数根据游标是否使用 FOR UPDATE/SHARE 分为两种不同的策略来执行此操作,
|
||||
并返回 true 表示成功获取 TID,或返回 false 表示未能获取 TID。函数也会检查游标的合法性,
|
||||
如果游标不存在、不是 SELECT 查询、或处于无效状态,则会引发错误。
|
||||
*/
|
||||
bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relation, ItemPointer current_tid,
|
||||
RelationPtr partitionOfCursor_tid)
|
||||
{
|
||||
|
|
@ -55,14 +61,14 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
QueryDesc *query_desc = NULL;
|
||||
Oid table_oid = RelationGetRelid(relation);
|
||||
|
||||
/* Get the cursor name --- may have to look up a parameter reference */
|
||||
// 获取游标名称,可能需要查找参数引用
|
||||
if (cexpr->cursor_name) {
|
||||
cursor_name = cexpr->cursor_name;
|
||||
} else {
|
||||
cursor_name = fetch_cursor_param_value(econtext, cexpr->cursor_param);
|
||||
}
|
||||
|
||||
/* Find the cursor's portal */
|
||||
// 查找游标的 Portal
|
||||
portal = GetPortalByName(cursor_name);
|
||||
if (!PortalIsValid(portal)) {
|
||||
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_CURSOR),
|
||||
|
|
@ -70,8 +76,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* We have to watch out for non-SELECT queries as well as held cursors,
|
||||
* both of which may have null query_desc.
|
||||
* 我们必须注意非SELECT查询以及持有的游标,两者都可能没有查询描述。
|
||||
*/
|
||||
if (portal->strategy != PORTAL_ONE_SELECT) {
|
||||
ereport(ERROR,
|
||||
|
|
@ -85,26 +90,23 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* We have two different strategies depending on whether the cursor uses
|
||||
* FOR UPDATE/SHARE or not. The reason for supporting both is that the
|
||||
* FOR UPDATE code is able to identify a target table in many cases where
|
||||
* the other code can't, while the non-FOR-UPDATE case allows use of WHERE
|
||||
* CURRENT OF with an insensitive cursor.
|
||||
* 根据游标是否使用 FOR UPDATE/SHARE,我们有两种不同的策<EFBFBD><EFBFBD><EFBFBD>。
|
||||
* 支持这两种的原因是,FOR UPDATE 代码在很多情况下可以识别目标表,而其他代码无法识别,
|
||||
* 而非 FOR UPDATE 的情况允许在不敏感游标上使用 WHERE CURRENT OF。
|
||||
*/
|
||||
if (query_desc->estate->es_rowMarks) {
|
||||
ExecRowMark *erm = NULL;
|
||||
ListCell *lc = NULL;
|
||||
|
||||
/*
|
||||
* Here, the query must have exactly one FOR UPDATE/SHARE reference to
|
||||
* the target table, and we dig the ctid info out of that.
|
||||
* 在这里,查询必须对目标表具有一次 FOR UPDATE/SHARE 引用,并且我们从中找到 ctid 信息。
|
||||
*/
|
||||
erm = NULL;
|
||||
foreach (lc, query_desc->estate->es_rowMarks) {
|
||||
ExecRowMark *thiserm = (ExecRowMark *)lfirst(lc);
|
||||
|
||||
if (!RowMarkRequiresRowShareLock(thiserm->markType)) {
|
||||
continue; /* ignore non-FOR UPDATE/SHARE items */
|
||||
continue; /* 忽略非 FOR UPDATE/SHARE 项 */
|
||||
}
|
||||
|
||||
if (RelationGetRelid(thiserm->relation) == table_oid) {
|
||||
|
|
@ -124,15 +126,14 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* The cursor must have a current result row: per the SQL spec, it's
|
||||
* an error if not.
|
||||
* 游标必须有当前结果行:根据 SQL 规范,如果没有,则出错。
|
||||
*/
|
||||
if (portal->atStart || portal->atEnd) {
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
|
||||
errmsg("cursor \"%s\" is not positioned on a row when the cursor uses for UPDATE/SHARE", cursor_name)));
|
||||
}
|
||||
|
||||
/* Return the currently scanned TID, if there is one */
|
||||
/* 返回当前扫描的 TID,如果存在的话 */
|
||||
if (ItemPointerIsValid(&(erm->curCtid))) {
|
||||
*current_tid = erm->curCtid;
|
||||
|
||||
|
|
@ -144,9 +145,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* This table didn't produce the cursor's current row; some other
|
||||
* inheritance child of the same parent must have. Signal caller to
|
||||
* do nothing on this table.
|
||||
* 这个表没有生成游标的当前行;同一父级的其他继承子表可能已经生成了。告诉调用者在此表上不执行任何操作。
|
||||
*/
|
||||
return false;
|
||||
} else {
|
||||
|
|
@ -156,9 +155,8 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
ItemPointer tuple_tid;
|
||||
|
||||
/*
|
||||
* Without FOR UPDATE, we dig through the cursor's plan to find the
|
||||
* scan node. Fail if it's not there or buried underneath
|
||||
* aggregation.
|
||||
* 在没有 FOR UPDATE 的情况下,我们通过游标的计划来查找扫描节点。
|
||||
* 如果节点不存在或被深层聚合包裹,则会失败。
|
||||
*/
|
||||
scanstate = search_plan_tree(query_desc->planstate, table_oid);
|
||||
if (scanstate == NULL) {
|
||||
|
|
@ -168,23 +166,21 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* The cursor must have a current result row: per the SQL spec, it's
|
||||
* an error if not. We test this at the top level, rather than at the
|
||||
* scan node level, because in inheritance cases any one table scan
|
||||
* could easily not be on a row. We want to return false, not raise
|
||||
* error, if the passed-in table OID is for one of the inactive scans.
|
||||
* 游标必须有当前结果行:根据 SQL 规范,如果没有,则出错。我们在顶层测试这一点,而不是在扫描节点级别测试,
|
||||
* 因为在继承情况下,任何一个表扫描都可能轻松不在一行上。我们想在找到非活动扫描的表 OID 时返回 false,
|
||||
* 而不是引发错误。
|
||||
*/
|
||||
if (portal->atStart || portal->atEnd) {
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg(
|
||||
"cursor \"%s\" is not positioned on a row when the cursor doesn't use for UPDATE/SHARE", cursor_name)));
|
||||
}
|
||||
|
||||
/* Now OK to return false if we found an inactive scan */
|
||||
/* 如果我们找到非活动扫描,则现在可以返回 false */
|
||||
if (TupIsNull(scanstate->ss_ScanTupleSlot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Use slot_getattr to catch any possible mistakes */
|
||||
/* 使用 slot_getattr 捕获任何可能的错误 */
|
||||
tuple_tableoid = DatumGetObjectId(tableam_tslot_getattr(scanstate->ss_ScanTupleSlot, TableOidAttributeNumber, &lisnull));
|
||||
Assert(!lisnull);
|
||||
tuple_tid = (ItemPointer)DatumGetPointer(
|
||||
|
|
@ -203,6 +199,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* fetch_cursor_param_value
|
||||
*
|
||||
|
|
@ -212,31 +209,35 @@ static char *fetch_cursor_param_value(ExprContext *econtext, int paramId)
|
|||
{
|
||||
ParamListInfo paramInfo = econtext->ecxt_param_list_info;
|
||||
|
||||
// 检查参数列表信息是否有效,以及参数标识是否在有效范围内
|
||||
if (paramInfo && paramId > 0 && paramId <= paramInfo->numParams) {
|
||||
ParamExternData *prm = ¶mInfo->params[paramId - 1];
|
||||
|
||||
/* give hook a chance in case parameter is dynamic */
|
||||
// 如果参数的数据类型无效且有参数提取钩子,则尝试提取参数
|
||||
if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) {
|
||||
(*paramInfo->paramFetch)(paramInfo, paramId);
|
||||
}
|
||||
|
||||
// 如果参数的数据类型有效且不为 NULL
|
||||
if (OidIsValid(prm->ptype) && !prm->isnull) {
|
||||
/* safety check in case hook did something unexpected */
|
||||
// 安全性检查,确保参数的数据类型与准备计划时的数据类型匹配
|
||||
if (prm->ptype != REFCURSOROID) {
|
||||
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)", paramId,
|
||||
format_type_be(prm->ptype), format_type_be(REFCURSOROID))));
|
||||
}
|
||||
|
||||
/* We know that refcursor uses text's I/O routines */
|
||||
// 我们知道 refcursor 使用 text 的 I/O 例程
|
||||
return TextDatumGetCString(prm->value);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果未找到参数值,引发错误
|
||||
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("no value found for parameter %d", paramId)));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* search_plan_tree
|
||||
*
|
||||
|
|
@ -249,6 +250,7 @@ ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
||||
#endif
|
||||
{
|
||||
// 如果节点为空,直接返回 NULL
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -261,15 +263,16 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
return sstate;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* scan nodes can all be treated alike
|
||||
*/
|
||||
/*
|
||||
* 所有扫描节点都可以被统一对待
|
||||
*/
|
||||
case T_SeqScanState:
|
||||
case T_IndexScanState:
|
||||
case T_IndexOnlyScanState:
|
||||
case T_BitmapHeapScanState:
|
||||
case T_TidScanState: {
|
||||
ScanState *sstate = (ScanState *)node;
|
||||
// 如果当前关系的 OID 与指定的表 OID 匹配,返回当前扫描节点
|
||||
if (RelationGetRelid(sstate->ss_currentRelation) == table_oid) {
|
||||
return sstate;
|
||||
}
|
||||
|
|
@ -278,15 +281,15 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
case T_ExtensiblePlanState: {
|
||||
ScanState *sstate = (ScanState *)node;
|
||||
ScanState *result = NULL;
|
||||
// 如果当前关系的 OID 与指定的表 OID 匹配,将结果设置为当前扫描节点
|
||||
if (RelationGetRelid(sstate->ss_currentRelation) == table_oid) {
|
||||
result = sstate;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/*
|
||||
* For Append, we must look through the members; watch out for
|
||||
* multiple matches (possible if it was from UNION ALL)
|
||||
*/
|
||||
/*
|
||||
* 对于 Append,我们必须查看其成员;注意可能存在多个匹配项(如果来自 UNION ALL)
|
||||
*/
|
||||
case T_AppendState: {
|
||||
AppendState *astate = (AppendState *)node;
|
||||
ScanState *result = NULL;
|
||||
|
|
@ -297,15 +300,15 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
if (elem == NULL)
|
||||
continue;
|
||||
if (result != NULL)
|
||||
return NULL; /* multiple matches */
|
||||
return NULL; /* 多个匹配项 */
|
||||
|
||||
result = elem;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/*
|
||||
* Similarly for MergeAppend
|
||||
*/
|
||||
/*
|
||||
* 类似于 MergeAppend
|
||||
*/
|
||||
case T_MergeAppendState: {
|
||||
MergeAppendState *mstate = (MergeAppendState *)node;
|
||||
ScanState *result = NULL;
|
||||
|
|
@ -318,15 +321,14 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
continue;
|
||||
}
|
||||
if (result != NULL) {
|
||||
return NULL; /* multiple matches */
|
||||
return NULL; /* 多个匹配项 */
|
||||
}
|
||||
result = elem;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/*
|
||||
* Result and Limit can be descended through (these are safe
|
||||
* because they always return their input's current row)
|
||||
* 对于 Result 和 Limit,可以继续向下查找(这些是安全的,因为它们总是返回其输入的当前行)
|
||||
*/
|
||||
#ifdef PGXC
|
||||
case T_MaterialState:
|
||||
|
|
@ -336,15 +338,16 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
case T_PartIteratorState:
|
||||
return search_plan_tree(node->lefttree, table_oid);
|
||||
|
||||
/*
|
||||
* SubqueryScan too, but it keeps the child in a different place
|
||||
*/
|
||||
/*
|
||||
* SubqueryScan 也可以,但它将子节点保存在不同的位置
|
||||
*/
|
||||
case T_SubqueryScanState:
|
||||
return search_plan_tree(((SubqueryScanState *)node)->subplan, table_oid);
|
||||
|
||||
default:
|
||||
/* Otherwise, assume we can't descend through it */
|
||||
/* 否则,假设我们无法继续向下查找 */
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,17 +55,16 @@ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols,
|
|||
bool result = false;
|
||||
int i;
|
||||
|
||||
/* Reset and switch into the temp context. */
|
||||
/* 重置并切换到临时内存上下文中 */
|
||||
MemoryContextReset(evalContext);
|
||||
oldContext = MemoryContextSwitchTo(evalContext);
|
||||
|
||||
/*
|
||||
/*
|
||||
* We cannot report a match without checking all the fields, but we can
|
||||
* report a non-match as soon as we find unequal fields. So, start
|
||||
* comparing at the last field (least significant sort key). That's the
|
||||
* most likely to be different if we are dealing with sorted input.
|
||||
*/
|
||||
result = true;
|
||||
result = true; /* 假设匹配 */
|
||||
|
||||
for (i = numCols; --i >= 0;) {
|
||||
AttrNumber att = matchColIdx[i];
|
||||
|
|
@ -73,31 +72,37 @@ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols,
|
|||
bool isNull1 = false;
|
||||
bool isNull2 = false;
|
||||
|
||||
/* 从第一个槽中获取属性值和是否为 NULL */
|
||||
attr1 = tableam_tslot_getattr(slot1, att, &isNull1);
|
||||
|
||||
/* 从第二个槽中获取属性值和是否为 NULL */
|
||||
attr2 = tableam_tslot_getattr(slot2, att, &isNull2);
|
||||
|
||||
/* 如果一个为 NULL,一个不为 NULL,则它们不相等 */
|
||||
if (isNull1 != isNull2) {
|
||||
result = false; /* one null and one not; they aren't equal */
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 如果都为 NULL,则视为相等 */
|
||||
if (isNull1) {
|
||||
continue; /* both are null, treat as equal */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Apply the type-specific equality function */
|
||||
/* 应用特定类型的相等性函数 */
|
||||
if (!DatumGetBool(FunctionCall2(&eqfunctions[i], attr1, attr2))) {
|
||||
result = false; /* they aren't equal */
|
||||
result = false; /* 它们不相等 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 切换回原有内存上下文 */
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* execTuplesUnequal
|
||||
* Return true if two tuples are definitely unequal in the indicated
|
||||
|
|
@ -117,17 +122,16 @@ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols
|
|||
|
||||
Assert(slot1->tts_tupleDescriptor->tdTableAmType == slot2->tts_tupleDescriptor->tdTableAmType);
|
||||
|
||||
/* Reset and switch into the temp context. */
|
||||
/* 重置并切换到临时内存上下文中 */
|
||||
MemoryContextReset(evalContext);
|
||||
oldContext = MemoryContextSwitchTo(evalContext);
|
||||
|
||||
/*
|
||||
* We cannot report a match without checking all the fields, but we can
|
||||
* report a non-match as soon as we find unequal fields. So, start
|
||||
* comparing at the last field (least significant sort key). That's the
|
||||
* most likely to be different if we are dealing with sorted input.
|
||||
* 我们不能在不检查所有字段的情况下报告匹配,但是如果我们找到不相等的字段,我们可以立即报告不匹配。
|
||||
* 因此,从最后一个字段(最不重要的排序键)开始比较。如果处理的是排序输入,
|
||||
* 则最有可能不相等的字段是最后一个字段。
|
||||
*/
|
||||
result = false;
|
||||
result = false; /* 假设相等 */
|
||||
|
||||
for (i = numCols; --i >= 0;) {
|
||||
AttrNumber att = matchColIdx[i];
|
||||
|
|
@ -135,30 +139,36 @@ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols
|
|||
bool isNull1 = false;
|
||||
bool isNull2 = false;
|
||||
|
||||
/* 从第一个槽中获取属性值和是否为 NULL */
|
||||
attr1 = tableam_tslot_getattr(slot1, att, &isNull1);
|
||||
|
||||
/* 如果为 NULL,则不能判断是否不相等,继续下一个属性的比较 */
|
||||
if (isNull1) {
|
||||
continue; /* can't prove anything here */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 从第二个槽中获取属性值和是否为 NULL */
|
||||
attr2 = tableam_tslot_getattr(slot2, att, &isNull2);
|
||||
|
||||
/* 如果为 NULL,则不能判断是否不相等,继续下一个属性的比较 */
|
||||
if (isNull2) {
|
||||
continue; /* can't prove anything here */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Apply the type-specific equality function */
|
||||
/* 应用特定类型的相等性函数,如果结果为 false,则它们不相等 */
|
||||
if (!DatumGetBool(FunctionCall2(&eqfunctions[i], attr1, attr2))) {
|
||||
result = true; /* they are unequal */
|
||||
result = true; /* 它们不相等 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 切换回原有内存上下文 */
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* execTuplesMatchPrepare
|
||||
* Look up the equality functions needed for execTuplesMatch or
|
||||
|
|
@ -175,13 +185,17 @@ FmgrInfo* execTuplesMatchPrepare(int numCols, Oid* eqOperators)
|
|||
Oid eq_opr = eqOperators[i];
|
||||
Oid eq_function;
|
||||
|
||||
/* 获取操作符的对应函数的 OID */
|
||||
eq_function = get_opcode(eq_opr);
|
||||
|
||||
/* 获取函数的详细信息并存储在 FmgrInfo 结构中 */
|
||||
fmgr_info(eq_function, &eqFunctions[i]);
|
||||
}
|
||||
|
||||
return eqFunctions;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* execTuplesHashPrepare
|
||||
* Look up the equality and hashing functions needed for a TupleHashTable.
|
||||
|
|
@ -192,10 +206,11 @@ FmgrInfo* execTuplesMatchPrepare(int numCols, Oid* eqOperators)
|
|||
*
|
||||
* Note: we expect that the given operators are not cross-type comparisons.
|
||||
*/
|
||||
void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions, FmgrInfo** hashFunctions)
|
||||
void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions, FmgrInfo** hashFunctions)//该函数的主要任务是为每个要哈希的属性找到相应的相等性比较函数和哈希函数,并将这些函数的信息存储在 FmgrInfo 结构数组中。
|
||||
{
|
||||
int i;
|
||||
|
||||
/* 为相等性比较函数和哈希函数分配内存 */
|
||||
*eqFunctions = (FmgrInfo*)palloc(numCols * sizeof(FmgrInfo));
|
||||
*hashFunctions = (FmgrInfo*)palloc(numCols * sizeof(FmgrInfo));
|
||||
|
||||
|
|
@ -205,7 +220,10 @@ void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions
|
|||
Oid left_hash_function;
|
||||
Oid right_hash_function;
|
||||
|
||||
/* 获取操作符的对应函数的 OID */
|
||||
eq_function = get_opcode(eq_opr);
|
||||
|
||||
/* 获取哈希函数的 OID */
|
||||
if (!get_op_hash_functions(eq_opr, &left_hash_function, &right_hash_function))
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_EXECUTOR),
|
||||
|
|
@ -216,13 +234,16 @@ void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions
|
|||
i,
|
||||
numCols)));
|
||||
|
||||
/* We're not supporting cross-type cases here */
|
||||
/* 我们不支持跨类型的情况 */
|
||||
Assert(left_hash_function == right_hash_function);
|
||||
|
||||
/* 获取相等性比较函数和哈希函数的详细信息并存储在 FmgrInfo 结构中 */
|
||||
fmgr_info(eq_function, &(*eqFunctions)[i]);
|
||||
fmgr_info(right_hash_function, &(*hashFunctions)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* Utility routines for all-in-memory hash tables
|
||||
*
|
||||
|
|
@ -257,28 +278,31 @@ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo*
|
|||
Assert(nbuckets > 0);
|
||||
Assert(entrysize >= sizeof(TupleHashEntryData));
|
||||
|
||||
/* Limit initial table size request to not more than work_mem */
|
||||
/* 限制初始表大小请求不超过 work_mem */
|
||||
nbuckets = Min(nbuckets, (long)((workMem * 1024L) / entrysize));
|
||||
if (u_sess->attr.attr_sql.hashagg_table_size != 0)
|
||||
nbuckets = Min(nbuckets, u_sess->attr.attr_sql.hashagg_table_size);
|
||||
|
||||
/* 分配元组哈希表结构 */
|
||||
hashtable = (TupleHashTable)MemoryContextAlloc(tablecxt, sizeof(TupleHashTableData));
|
||||
|
||||
hashtable->numCols = numCols;
|
||||
hashtable->keyColIdx = keyColIdx;
|
||||
hashtable->tab_hash_funcs = hashfunctions;
|
||||
hashtable->tab_eq_funcs = eqfunctions;
|
||||
hashtable->tablecxt = tablecxt;
|
||||
hashtable->tempcxt = tempcxt;
|
||||
hashtable->entrysize = entrysize;
|
||||
hashtable->tableslot = NULL; /* will be made on first lookup */
|
||||
hashtable->inputslot = NULL;
|
||||
hashtable->in_hash_funcs = NULL;
|
||||
hashtable->cur_eq_funcs = NULL;
|
||||
hashtable->width = 0;
|
||||
hashtable->add_width = true;
|
||||
hashtable->causedBySysRes = false;
|
||||
/* 初始化元组哈希表的各个字段 */
|
||||
hashtable->numCols = numCols; // 哈希键的数量
|
||||
hashtable->keyColIdx = keyColIdx; // 哈希键的属性列索引
|
||||
hashtable->tab_hash_funcs = hashfunctions; // 哈希函数数组
|
||||
hashtable->tab_eq_funcs = eqfunctions; // 相等性比较函数数组
|
||||
hashtable->tablecxt = tablecxt; // 元组哈希表内存上下文
|
||||
hashtable->tempcxt = tempcxt; // 临时内存上下文
|
||||
hashtable->entrysize = entrysize; // 表条目的大小
|
||||
hashtable->tableslot = NULL; // 表槽,首次查找时创建
|
||||
hashtable->inputslot = NULL; // 输入槽
|
||||
hashtable->in_hash_funcs = NULL; // 输入哈希函数数组
|
||||
hashtable->cur_eq_funcs = NULL; // 当前相等性比较函数数组
|
||||
hashtable->width = 0; // 哈希表宽度
|
||||
hashtable->add_width = true; // 是否添加宽度
|
||||
hashtable->causedBySysRes = false; // 是否由系统资源引起
|
||||
|
||||
/* 初始化哈希表控制参数 */
|
||||
errno_t rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
|
||||
securec_check(rc, "\0", "\0");
|
||||
hash_ctl.keysize = sizeof(TupleHashEntryData);
|
||||
|
|
@ -286,12 +310,15 @@ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo*
|
|||
hash_ctl.hash = TupleHashTableHash;
|
||||
hash_ctl.match = TupleHashTableMatch;
|
||||
hash_ctl.hcxt = tablecxt;
|
||||
|
||||
/* 创建哈希表 */
|
||||
hashtable->hashtab =
|
||||
hash_create("TupleHashTable", nbuckets, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
|
||||
|
||||
return hashtable;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Find or create a hashtable entry for the tuple group containing the
|
||||
* given tuple. The tuple must be the same type as the hashtable entries.
|
||||
|
|
@ -317,29 +344,28 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl
|
|||
TupleHashEntryData dummy;
|
||||
bool found = false;
|
||||
|
||||
/* If first time through, clone the input slot to make table slot */
|
||||
/* 如果是第一次调用,克隆输入槽以创建表槽 */
|
||||
if (hashtable->tableslot == NULL) {
|
||||
TupleDesc tupdesc;
|
||||
|
||||
oldContext = MemoryContextSwitchTo(hashtable->tablecxt);
|
||||
|
||||
/*
|
||||
* We copy the input tuple descriptor just for safety --- we assume
|
||||
* all input tuples will have equivalent descriptors.
|
||||
* 为了安全起见,我们复制输入元组描述符 --- 我们假设所有输入元组都具有等效的描述符。
|
||||
*/
|
||||
tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
|
||||
hashtable->tableslot = MakeSingleTupleTableSlot(tupdesc);
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
/* Need to run the hash functions in short-lived context */
|
||||
/* 需要在临时内存上下文中运行哈希和匹配函数 */
|
||||
oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
|
||||
|
||||
/*
|
||||
* Set up data needed by hash and match functions
|
||||
* 设置哈希和匹配函数所需的数据
|
||||
*
|
||||
* We save and restore u_sess->exec_cxt.cur_tuple_hash_table just in case someone manages to
|
||||
* invoke this code re-entrantly.
|
||||
* 为了安全起见,我们保存和恢复 u_sess->exec_cxt.cur_tuple_hash_table,以防某人设法
|
||||
* 重入此代码。
|
||||
*/
|
||||
hashtable->inputslot = slot;
|
||||
hashtable->in_hash_funcs = hashtable->tab_hash_funcs;
|
||||
|
|
@ -348,34 +374,33 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl
|
|||
saveCurHT = u_sess->exec_cxt.cur_tuple_hash_table;
|
||||
u_sess->exec_cxt.cur_tuple_hash_table = hashtable;
|
||||
|
||||
/* Search the hash table */
|
||||
dummy.firstTuple = NULL; /* flag to reference inputslot */
|
||||
/* 在哈希表中查找条目 */
|
||||
dummy.firstTuple = NULL; /* 用于引用 inputslot */
|
||||
|
||||
if (isinserthashtbl) {
|
||||
entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, isnew ? HASH_ENTER : HASH_FIND, &found);
|
||||
} else {
|
||||
/* this slot will be insert into temp file instead of hash table if it is not found in hash table */
|
||||
/* 如果在哈希表中找不到,此槽将被插入到临时文件中而不是哈希表中 */
|
||||
entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, HASH_FIND, &found);
|
||||
}
|
||||
|
||||
if (isnew != NULL) {
|
||||
if (found) {
|
||||
/* found pre-existing entry */
|
||||
/* 找到现有条目 */
|
||||
*isnew = false;
|
||||
} else {
|
||||
if (entry) {
|
||||
Assert(isinserthashtbl);
|
||||
/*
|
||||
* created new entry
|
||||
* 创建新条目
|
||||
*
|
||||
* Zero any caller-requested space in the entry. (This zaps the
|
||||
* "key data" dynahash.c copied into the new entry, but we don't
|
||||
* care since we're about to overwrite it anyway.)
|
||||
* 在条目中清零任何由调用方请求的空间。 (这会清除 dynahash.c 复制到新条目的
|
||||
* "key data" ,但我们不关心,因为我们马上就要覆盖它。)
|
||||
*/
|
||||
errno_t errorno = memset_s(entry, hashtable->entrysize, 0, hashtable->entrysize);
|
||||
securec_check(errorno, "\0", "\0");
|
||||
|
||||
/* Copy the first tuple into the table context */
|
||||
/* 将第一个元组复制到表上下文中 */
|
||||
MemoryContextSwitchTo(hashtable->tablecxt);
|
||||
entry->firstTuple = ExecCopySlotMinimalTuple(slot);
|
||||
if (hashtable->add_width)
|
||||
|
|
@ -393,6 +418,7 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl
|
|||
return entry;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Search for a hashtable entry matching the given tuple. No entry is
|
||||
* created if there's not a match. This is similar to the non-creating
|
||||
|
|
@ -410,14 +436,14 @@ TupleHashEntry FindTupleHashEntry(
|
|||
TupleHashTable saveCurHT;
|
||||
TupleHashEntryData dummy;
|
||||
|
||||
/* Need to run the hash functions in short-lived context */
|
||||
// 需要在临时内存上下文中运行哈希函数
|
||||
oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
|
||||
|
||||
/*
|
||||
* Set up data needed by hash and match functions
|
||||
* 设置哈希和匹配函数所需的数据
|
||||
*
|
||||
* We save and restore u_sess->exec_cxt.cur_tuple_hash_table just in case someone manages to
|
||||
* invoke this code re-entrantly.
|
||||
* 为了安全起见,我们保存和恢复 u_sess->exec_cxt.cur_tuple_hash_table,以防某人设法
|
||||
* 重入此代码。
|
||||
*/
|
||||
hashtable->inputslot = slot;
|
||||
hashtable->in_hash_funcs = hashfunctions;
|
||||
|
|
@ -426,17 +452,23 @@ TupleHashEntry FindTupleHashEntry(
|
|||
saveCurHT = u_sess->exec_cxt.cur_tuple_hash_table;
|
||||
u_sess->exec_cxt.cur_tuple_hash_table = hashtable;
|
||||
|
||||
/* Search the hash table */
|
||||
dummy.firstTuple = NULL; /* flag to reference inputslot */
|
||||
// 创建一个用于查找的 dummy 条目,用于引用 inputslot
|
||||
dummy.firstTuple = NULL;
|
||||
|
||||
// 在哈希表中查找条目,如果找到,将返回该条目,否则返回 NULL
|
||||
entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, HASH_FIND, NULL);
|
||||
|
||||
// 恢复原来的哈希表上下文
|
||||
u_sess->exec_cxt.cur_tuple_hash_table = saveCurHT;
|
||||
|
||||
// 切换回之前的内存上下文
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
// 返回查找到的条目或 NULL
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Compute the hash value for a tuple
|
||||
*
|
||||
|
|
@ -455,38 +487,43 @@ TupleHashEntry FindTupleHashEntry(
|
|||
*/
|
||||
static uint32 TupleHashTableHash(const void* key, Size keysize)
|
||||
{
|
||||
// 获取条目的 MinimalTuple,这是用于计算哈希值的元组数据
|
||||
MinimalTuple tuple = ((const TupleHashEntryData*)key)->firstTuple;
|
||||
TupleTableSlot* slot = NULL;
|
||||
TupleHashTable hashtable = u_sess->exec_cxt.cur_tuple_hash_table;
|
||||
int numCols = hashtable->numCols;
|
||||
AttrNumber* keyColIdx = hashtable->keyColIdx;
|
||||
FmgrInfo* hashfunctions = NULL;
|
||||
uint32 hashkey = 0;
|
||||
|
||||
// 初始化变量
|
||||
TupleTableSlot* slot = NULL; // 用于存储元组数据的槽
|
||||
TupleHashTable hashtable = u_sess->exec_cxt.cur_tuple_hash_table; // 当前的元组哈希表
|
||||
int numCols = hashtable->numCols; // 哈希键的列数
|
||||
AttrNumber* keyColIdx = hashtable->keyColIdx; // 哈希键的列索引
|
||||
FmgrInfo* hashfunctions = NULL; // 用于计算哈希值的哈希函数信息
|
||||
uint32 hashkey = 0; // 初始哈希值
|
||||
int i;
|
||||
|
||||
if (tuple == NULL) {
|
||||
/* Process the current input tuple for the table */
|
||||
/* 处理哈希表中的当前输入元组 */
|
||||
slot = hashtable->inputslot;
|
||||
hashfunctions = hashtable->in_hash_funcs;
|
||||
} else {
|
||||
/* Process a tuple already stored in the table */
|
||||
/* (this case never actually occurs in current dynahash.c code) */
|
||||
/* 处理已存储在哈希表中的元组 */
|
||||
/* (实际上,这种情况在当前的 dynahash.c 代码中不会发生) */
|
||||
slot = hashtable->tableslot;
|
||||
ExecStoreMinimalTuple(tuple, slot, false);
|
||||
hashfunctions = hashtable->tab_hash_funcs;
|
||||
}
|
||||
|
||||
/* Get the Table Accessor Method*/
|
||||
/* 循环处理哈希键的每一列 */
|
||||
for (i = 0; i < numCols; i++) {
|
||||
AttrNumber att = keyColIdx[i];
|
||||
Datum attr;
|
||||
bool isNull = false;
|
||||
|
||||
/* rotate hashkey left 1 bit at each step */
|
||||
/* 将哈希键左移 1 位,以便每一列都会影响哈希值的每一位 */
|
||||
hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
|
||||
|
||||
// 获取元组中的属性值
|
||||
attr = tableam_tslot_getattr(slot, att, &isNull);
|
||||
/* treat nulls as having hash key 0 */
|
||||
|
||||
/* 如果属性值不为空,则使用相应的哈希函数计算哈希键的哈希值,并与哈希值合并 */
|
||||
if (!isNull) {
|
||||
uint32 hkey;
|
||||
hkey = DatumGetUInt32(FunctionCall1(&hashfunctions[i], attr));
|
||||
|
|
@ -494,11 +531,13 @@ static uint32 TupleHashTableHash(const void* key, Size keysize)
|
|||
}
|
||||
}
|
||||
|
||||
// 最后,将哈希值应用 hash_uint32 函数以获得最终的哈希值
|
||||
hashkey = DatumGetUInt32(hash_uint32(hashkey));
|
||||
|
||||
return hashkey;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* See whether two tuples (presumably of the same hash value) match
|
||||
*
|
||||
|
|
@ -512,20 +551,22 @@ static uint32 TupleHashTableHash(const void* key, Size keysize)
|
|||
*/
|
||||
static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize)
|
||||
{
|
||||
// 获取第一个条目的 MinimalTuple
|
||||
MinimalTuple tuple1 = ((const TupleHashEntryData*)key1)->firstTuple;
|
||||
|
||||
#ifdef USE_ASSERT_CHECKING
|
||||
// 获取第二个条目的 MinimalTuple(用于断言检查)
|
||||
MinimalTuple tuple2 = ((const TupleHashEntryData*)key2)->firstTuple;
|
||||
#endif
|
||||
|
||||
TupleTableSlot* slot1 = NULL;
|
||||
TupleTableSlot* slot2 = NULL;
|
||||
TupleHashTable hashtable = u_sess->exec_cxt.cur_tuple_hash_table;
|
||||
|
||||
/*
|
||||
* We assume that dynahash.c will only ever call us with the first
|
||||
* argument being an actual table entry, and the second argument being
|
||||
* LookupTupleHashEntry's dummy TupleHashEntryData. The other direction
|
||||
* could be supported too, but is not currently used by dynahash.c.
|
||||
* 我们假设 dynahash.c 只会使用实际的表条目作为第一个参数,
|
||||
* 并且使用 LookupTupleHashEntry 的虚拟 TupleHashEntryData 作为第二个参数。
|
||||
* 反过来的情况也可以支持,但当前 dynahash.c 未使用。
|
||||
*/
|
||||
Assert(tuple1 != NULL);
|
||||
slot1 = hashtable->tableslot;
|
||||
|
|
@ -533,10 +574,10 @@ static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize)
|
|||
Assert(tuple2 == NULL);
|
||||
slot2 = hashtable->inputslot;
|
||||
|
||||
/* For crosstype comparisons, the inputslot must be first */
|
||||
/* 对于跨类型比较,输入槽必须位于第一个位置 */
|
||||
if (execTuplesMatch(
|
||||
slot2, slot1, hashtable->numCols, hashtable->keyColIdx, hashtable->cur_eq_funcs, hashtable->tempcxt))
|
||||
return 0;
|
||||
return 0; // 条目匹配,返回0
|
||||
else
|
||||
return 1;
|
||||
return 1; // 条目不匹配,返回1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,45 +62,44 @@
|
|||
*/
|
||||
JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* slot, TableAmType tam)
|
||||
{
|
||||
JunkFilter* junkfilter = NULL;
|
||||
TupleDesc cleanTupType;
|
||||
int cleanLength;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
ListCell* t = NULL;
|
||||
AttrNumber cleanResno;
|
||||
JunkFilter* junkfilter = NULL; // 用于存储 JunkFilter 结构体的指针
|
||||
TupleDesc cleanTupType; // 存储清理后的元组描述符
|
||||
int cleanLength; // 清理后的元组的长度
|
||||
AttrNumber* cleanMap = NULL; // 清理后的属性映射
|
||||
ListCell* t = NULL; // 遍历 targetList 的 ListCell 指针
|
||||
AttrNumber cleanResno; // 清理后的属性编号
|
||||
|
||||
/*
|
||||
* Compute the tuple descriptor for the cleaned tuple.
|
||||
* 计算清理后元组的元组描述符。
|
||||
*/
|
||||
cleanTupType = ExecCleanTypeFromTL(targetList, hasoid, tam);
|
||||
|
||||
/*
|
||||
* Use the given slot, or make a new slot if we weren't given one.
|
||||
* 使用给定的槽位(slot),或者如果未提供槽位,则创建一个新槽位。
|
||||
*/
|
||||
if (slot != NULL)
|
||||
ExecSetSlotDescriptor(slot, cleanTupType);
|
||||
else
|
||||
slot = MakeSingleTupleTableSlot(cleanTupType);
|
||||
|
||||
/*
|
||||
* Now calculate the mapping between the original tuple's attributes and
|
||||
* the "clean" tuple's attributes.
|
||||
*
|
||||
* The "map" is an array of "cleanLength" attribute numbers, i.e. one
|
||||
* entry for every attribute of the "clean" tuple. The value of this entry
|
||||
* is the attribute number of the corresponding attribute of the
|
||||
* "original" tuple. (Zero indicates a NULL output attribute, but we do
|
||||
* not use that feature in this routine.)
|
||||
*/
|
||||
cleanLength = cleanTupType->natts;
|
||||
/*
|
||||
* Now calculate the mapping between the original tuple's attributes and
|
||||
* the "clean" tuple's attributes.
|
||||
*
|
||||
* The "map" is an array of "cleanLength" attribute numbers, i.e. one
|
||||
* entry for every attribute of the "clean" tuple. The value of this entry
|
||||
* is the attribute number of the corresponding attribute of the
|
||||
* "original" tuple. (Zero indicates a NULL output attribute, but we do
|
||||
* not use that feature in this routine.)
|
||||
*/
|
||||
cleanLength = cleanTupType->natts; // 清理后的元组的属性数目
|
||||
if (cleanLength > 0) {
|
||||
cleanMap = (AttrNumber*)palloc(cleanLength * sizeof(AttrNumber));
|
||||
cleanResno = 1;
|
||||
cleanMap = (AttrNumber*)palloc(cleanLength * sizeof(AttrNumber)); // 分配清理后的属性映射数组的内存空间
|
||||
cleanResno = 1; // 初始化清理后的属性编号
|
||||
foreach (t, targetList) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(t);
|
||||
|
||||
if (!tle->resjunk) {
|
||||
cleanMap[cleanResno - 1] = tle->resno;
|
||||
cleanMap[cleanResno - 1] = tle->resno; // 映射属性编号
|
||||
cleanResno++;
|
||||
}
|
||||
}
|
||||
|
|
@ -109,18 +108,19 @@ JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* sl
|
|||
}
|
||||
|
||||
/*
|
||||
* Finally create and initialize the JunkFilter struct.
|
||||
* 最后创建并初始化 JunkFilter 结构体。
|
||||
*/
|
||||
junkfilter = makeNode(JunkFilter);
|
||||
junkfilter = makeNode(JunkFilter); // 创建一个 JunkFilter 结构体
|
||||
|
||||
junkfilter->jf_targetList = targetList;
|
||||
junkfilter->jf_cleanTupType = cleanTupType;
|
||||
junkfilter->jf_cleanMap = cleanMap;
|
||||
junkfilter->jf_resultSlot = slot;
|
||||
junkfilter->jf_targetList = targetList; // 设置目标列表
|
||||
junkfilter->jf_cleanTupType = cleanTupType; // 设置清理后的元组描述符
|
||||
junkfilter->jf_cleanMap = cleanMap; // 设置清理后的属性映射
|
||||
junkfilter->jf_resultSlot = slot; // 设置结果槽位
|
||||
|
||||
return junkfilter;
|
||||
return junkfilter; // 返回初始化后的 JunkFilter 结构体指针
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecInitJunkFilterConversion
|
||||
*
|
||||
|
|
@ -133,14 +133,14 @@ JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* sl
|
|||
*/
|
||||
JunkFilter* ExecInitJunkFilterConversion(List* targetList, TupleDesc cleanTupType, TupleTableSlot* slot)
|
||||
{
|
||||
JunkFilter* junkfilter = NULL;
|
||||
int cleanLength;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
ListCell* t = NULL;
|
||||
JunkFilter* junkfilter = NULL; // 用于存储 JunkFilter 结构体的指针
|
||||
int cleanLength; // 清理后的元组的长度
|
||||
AttrNumber* cleanMap = NULL; // 清理后的属性映射
|
||||
ListCell* t = NULL; // 遍历 targetList 的 ListCell 指针
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Use the given slot, or make a new slot if we weren't given one.
|
||||
* 使用给定的槽位(slot),或者如果未提供槽位,则创建一个新槽位。
|
||||
*/
|
||||
if (slot != NULL)
|
||||
ExecSetSlotDescriptor(slot, cleanTupType);
|
||||
|
|
@ -148,28 +148,25 @@ JunkFilter* ExecInitJunkFilterConversion(List* targetList, TupleDesc cleanTupTyp
|
|||
slot = MakeSingleTupleTableSlot(cleanTupType);
|
||||
|
||||
/*
|
||||
* Calculate the mapping between the original tuple's attributes and the
|
||||
* "clean" tuple's attributes.
|
||||
* 计算原始元组属性与“清理”元组属性之间的映射关系。
|
||||
*
|
||||
* The "map" is an array of "cleanLength" attribute numbers, i.e. one
|
||||
* entry for every attribute of the "clean" tuple. The value of this entry
|
||||
* is the attribute number of the corresponding attribute of the
|
||||
* "original" tuple. We store zero for any deleted attributes, marking
|
||||
* that a NULL is needed in the output tuple.
|
||||
* “映射”是一个数组,包含“cleanLength”属性编号,即“清理”元组的每个属性对应一个条目。
|
||||
* 此条目的值是“原始”元组对应属性的属性编号。
|
||||
* 对于任何已删除的属性,我们存储零,表示在输出元组中需要一个 NULL。
|
||||
*/
|
||||
cleanLength = cleanTupType->natts;
|
||||
if (cleanLength > 0) {
|
||||
cleanMap = (AttrNumber*)palloc0(cleanLength * sizeof(AttrNumber));
|
||||
cleanMap = (AttrNumber*)palloc0(cleanLength * sizeof(AttrNumber)); // 分配清理后的属性映射数组的内存空间
|
||||
t = list_head(targetList);
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
if (cleanTupType->attrs[i]->attisdropped)
|
||||
continue; /* map entry is already zero */
|
||||
continue; /* 映射条目已经是零 */
|
||||
for (;;) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(t);
|
||||
|
||||
t = lnext(t);
|
||||
if (!tle->resjunk) {
|
||||
cleanMap[i] = tle->resno;
|
||||
cleanMap[i] = tle->resno; // 映射属性编号
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -179,16 +176,16 @@ JunkFilter* ExecInitJunkFilterConversion(List* targetList, TupleDesc cleanTupTyp
|
|||
}
|
||||
|
||||
/*
|
||||
* Finally create and initialize the JunkFilter struct.
|
||||
* 最后创建并初始化 JunkFilter 结构体。
|
||||
*/
|
||||
junkfilter = makeNode(JunkFilter);
|
||||
junkfilter = makeNode(JunkFilter); // 创建一个 JunkFilter 结构体
|
||||
|
||||
junkfilter->jf_targetList = targetList;
|
||||
junkfilter->jf_cleanTupType = cleanTupType;
|
||||
junkfilter->jf_cleanMap = cleanMap;
|
||||
junkfilter->jf_resultSlot = slot;
|
||||
junkfilter->jf_targetList = targetList; // 设置目标列表
|
||||
junkfilter->jf_cleanTupType = cleanTupType; // 设置清理后的元组描述符
|
||||
junkfilter->jf_cleanMap = cleanMap; // 设置清理后的属性映射
|
||||
junkfilter->jf_resultSlot = slot; // 设置结果槽位
|
||||
|
||||
return junkfilter;
|
||||
return junkfilter; // 返回初始化后的 JunkFilter 结构体指针
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -210,21 +207,24 @@ AttrNumber ExecFindJunkAttribute(JunkFilter* junkfilter, const char* attrName)
|
|||
*/
|
||||
List* ExecFindJunkPrimaryKeys(List* targetlist)
|
||||
{
|
||||
List* jk_primary_keys = NIL;
|
||||
ListCell* cell = NULL;
|
||||
List* jk_primary_keys = NIL; // 用于存储 "xc_primary_key" 属性的表达式列表
|
||||
ListCell* cell = NULL; // 遍历目标列表的 ListCell 指针
|
||||
|
||||
foreach (cell, targetlist) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(cell);
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(cell); // 获取目标列表中的当前元素
|
||||
|
||||
// 检查是否是垃圾属性(resjunk 为 true)、是否有属性名(resname 不为空),
|
||||
// 并且属性名为 "xc_primary_key"
|
||||
if (tle->resjunk && tle->resname && (strcmp(tle->resname, "xc_primary_key") == 0)) {
|
||||
/* We found it ! */
|
||||
/* 找到了 "xc_primary_key" 属性!将其表达式添加到 jk_primary_keys 列表中 */
|
||||
jk_primary_keys = lappend(jk_primary_keys, tle->expr);
|
||||
}
|
||||
}
|
||||
|
||||
return jk_primary_keys;
|
||||
return jk_primary_keys; // 返回包含 "xc_primary_key" 属性表达式的列表
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecFindJunkAttributeInTlist
|
||||
*
|
||||
|
|
@ -233,20 +233,23 @@ List* ExecFindJunkPrimaryKeys(List* targetlist)
|
|||
*/
|
||||
AttrNumber ExecFindJunkAttributeInTlist(List* targetlist, const char* attrName)
|
||||
{
|
||||
ListCell* t = NULL;
|
||||
ListCell* t = NULL; // 用于遍历目标列表的 ListCell 指针
|
||||
|
||||
foreach (t, targetlist) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(t);
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(t); // 获取目标列表中的当前元素
|
||||
|
||||
// 检查是否是垃圾属性(resjunk 为 true)、是否有属性名(resname 不为空),
|
||||
// 并且属性名与指定的 attrName 匹配
|
||||
if (tle->resjunk && tle->resname && (strcmp(tle->resname, attrName) == 0)) {
|
||||
/* We found it ! */
|
||||
/* 找到了指定名称的垃圾属性!返回该属性的属性号(resno) */
|
||||
return tle->resno;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidAttrNumber;
|
||||
return InvalidAttrNumber; // 如果未找到指定名称的垃圾属性,返回无效属性号
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecGetJunkAttribute
|
||||
*
|
||||
|
|
@ -256,12 +259,14 @@ AttrNumber ExecFindJunkAttributeInTlist(List* targetlist, const char* attrName)
|
|||
*/
|
||||
Datum ExecGetJunkAttribute(TupleTableSlot* slot, AttrNumber attno, bool* isNull)
|
||||
{
|
||||
Assert(attno > 0);
|
||||
Assert(slot != NULL);
|
||||
Assert(attno > 0); // 确保属性号大于 0
|
||||
Assert(slot != NULL); // 确保提供了有效的 TupleTableSlot
|
||||
|
||||
// 使用 tableam_tslot_getattr 函数从 TupleTableSlot 中获取指定属性的值和是否为 NULL
|
||||
return tableam_tslot_getattr(slot, attno, isNull);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecFilterJunk
|
||||
*
|
||||
|
|
@ -269,43 +274,43 @@ Datum ExecGetJunkAttribute(TupleTableSlot* slot, AttrNumber attno, bool* isNull)
|
|||
*/
|
||||
TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot)
|
||||
{
|
||||
TupleTableSlot* resultSlot = NULL;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
TupleDesc cleanTupType;
|
||||
int cleanLength;
|
||||
TupleTableSlot* resultSlot = NULL; // 存储结果的 TupleTableSlot
|
||||
AttrNumber* cleanMap = NULL; // 清洗属性映射
|
||||
TupleDesc cleanTupType; // 清洗后的元组描述
|
||||
int cleanLength; // 清洗后的属性数量
|
||||
int i;
|
||||
Datum* values = NULL;
|
||||
bool* isnull = NULL;
|
||||
Datum* old_values = NULL;
|
||||
bool* old_isnull = NULL;
|
||||
Datum* values = NULL; // 存储属性值的数组
|
||||
bool* isnull = NULL; // 属性是否为 NULL 的标志数组
|
||||
Datum* old_values = NULL; // 原始元组的属性值数组
|
||||
bool* old_isnull = NULL; // 原始元组的属性是否为 NULL 的标志数组
|
||||
|
||||
/*
|
||||
* Extract all the values of the old tuple.
|
||||
* 提取原始元组的所有属性值和是否为 NULL 标志。
|
||||
*/
|
||||
|
||||
/* Get the Table Accessor Method*/
|
||||
/* 获取表访问方法的元组描述 */
|
||||
Assert(slot != NULL && slot->tts_tupleDescriptor != NULL);
|
||||
tableam_tslot_getallattrs(slot);
|
||||
old_values = slot->tts_values;
|
||||
old_isnull = slot->tts_isnull;
|
||||
|
||||
/*
|
||||
* get info from the junk filter
|
||||
* 从 JunkFilter 中获取相关信息
|
||||
*/
|
||||
cleanTupType = junkfilter->jf_cleanTupType;
|
||||
cleanLength = cleanTupType->natts;
|
||||
cleanMap = junkfilter->jf_cleanMap;
|
||||
resultSlot = junkfilter->jf_resultSlot;
|
||||
cleanTupType = junkfilter->jf_cleanTupType; // 清洗后的元组描述
|
||||
cleanLength = cleanTupType->natts; // 清洗后的属性数量
|
||||
cleanMap = junkfilter->jf_cleanMap; // 清洗属性映射
|
||||
resultSlot = junkfilter->jf_resultSlot; // 存储结果的 TupleTableSlot
|
||||
|
||||
/*
|
||||
* Prepare to build a virtual result tuple.
|
||||
* 准备构建虚拟结果元组。
|
||||
*/
|
||||
(void)ExecClearTuple(resultSlot);
|
||||
values = resultSlot->tts_values;
|
||||
isnull = resultSlot->tts_isnull;
|
||||
values = resultSlot->tts_values; // 存储属性值的数组
|
||||
isnull = resultSlot->tts_isnull; // 属性是否为 NULL 的标志数组
|
||||
|
||||
/*
|
||||
* Transpose data into proper fields of the new tuple.
|
||||
* 将数据转置到新元组的适当字段中。
|
||||
*/
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
int j = cleanMap[i];
|
||||
|
|
@ -320,11 +325,12 @@ TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot)
|
|||
}
|
||||
|
||||
/*
|
||||
* And return the virtual tuple.
|
||||
* 返回虚拟元组。
|
||||
*/
|
||||
return ExecStoreVirtualTuple(resultSlot);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* BatchExecFilterJunk
|
||||
*
|
||||
|
|
@ -332,58 +338,61 @@ TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot)
|
|||
*/
|
||||
VectorBatch* BatchExecFilterJunk(_in_ JunkFilter* junkfilter, __inout VectorBatch* batch)
|
||||
{
|
||||
AttrNumber* cleanMap = NULL;
|
||||
TupleDesc cleanTupType;
|
||||
int cleanLength;
|
||||
AttrNumber* cleanMap = NULL; // 清洗属性映射
|
||||
TupleDesc cleanTupType; // 清洗后的元组描述
|
||||
int cleanLength; // 清洗后的属性数量
|
||||
int i;
|
||||
ScalarVector* columns = NULL;
|
||||
ScalarVector* columns = NULL; // 列数据
|
||||
|
||||
// Get info from the junk filter
|
||||
// 从 JunkFilter 中获取相关信息
|
||||
//
|
||||
cleanTupType = junkfilter->jf_cleanTupType;
|
||||
cleanLength = cleanTupType->natts;
|
||||
cleanMap = junkfilter->jf_cleanMap;
|
||||
columns = batch->m_arr;
|
||||
cleanTupType = junkfilter->jf_cleanTupType; // 清洗后的元组描述
|
||||
cleanLength = cleanTupType->natts; // 清洗后的属性数量
|
||||
cleanMap = junkfilter->jf_cleanMap; // 清洗属性映射
|
||||
columns = batch->m_arr; // 列数据
|
||||
|
||||
// Transpose data into proper fields of the new tuple.
|
||||
// 将数据转置到新元组的适当字段中。
|
||||
//
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
int j = cleanMap[i];
|
||||
|
||||
if (j == 0) {
|
||||
// 如果清洗属性对应的映射值为0,表示该属性为垃圾属性,需要设置为 NULL
|
||||
for (int k = 0; k < columns[i].m_rows; k++) {
|
||||
columns[i].SetNull(k);
|
||||
}
|
||||
} else {
|
||||
// 否则,将属性的数据从对应的位置复制到新的位置
|
||||
columns[i] = columns[j - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Return the modified batch without changing the column count
|
||||
// as the column count is early decided at compile time.
|
||||
// 返回修改后的批处理数据,列数不变,因为列数在编译时已经确定。
|
||||
//
|
||||
return batch;
|
||||
}
|
||||
|
||||
void ExecSetjunkFilteDescriptor(JunkFilter* junkfilter, TupleDesc tupdesc)
|
||||
|
||||
void ExecSetJunkFilterDescriptor(JunkFilter* junkfilter, TupleDesc tupdesc)
|
||||
{
|
||||
TupleDesc resultslotTupType;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
int cleanLength;
|
||||
TupleDesc resultslotTupType; // 用于存储结果集的TupleDesc
|
||||
AttrNumber* cleanMap = NULL; // 存储属性映射关系的数组
|
||||
int cleanLength; // 属性映射的长度
|
||||
int i;
|
||||
|
||||
cleanLength = junkfilter->jf_cleanTupType->natts;
|
||||
cleanMap = junkfilter->jf_cleanMap;
|
||||
cleanLength = junkfilter->jf_cleanTupType->natts; // 获取属性映射关系的长度
|
||||
cleanMap = junkfilter->jf_cleanMap; // 获取属性映射关系数组
|
||||
|
||||
resultslotTupType = junkfilter->jf_resultSlot->tts_tupleDescriptor;
|
||||
resultslotTupType = junkfilter->jf_resultSlot->tts_tupleDescriptor; // 获取结果集的TupleDesc
|
||||
|
||||
/*
|
||||
* Transpose tupdesc into proper fields of the new tupdesc.
|
||||
* 将tupdesc的属性映射到新的tupdesc中。
|
||||
*/
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
int j = cleanMap[i];
|
||||
int j = cleanMap[i]; // 获取属性映射关系中的目标位置
|
||||
if (j > 0)
|
||||
resultslotTupType->attrs[i]->atttypid = tupdesc->attrs[j - 1]->atttypid;
|
||||
// 将目标位置上的属性类型ID更新为tupdesc中相应位置上的属性类型ID
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -396,18 +405,24 @@ void ExecSetjunkFilteDescriptor(JunkFilter* junkfilter, TupleDesc tupdesc)
|
|||
*/
|
||||
void BatchCheckNodeIdentifier(JunkFilter* junkfilter, VectorBatch* batch)
|
||||
{
|
||||
ScalarVector* xc_node_id_col = NULL;
|
||||
uint32 xc_node_id = 0;
|
||||
int counter = 0;
|
||||
ScalarVector* xc_node_id_col = NULL; // 用于存储节点标识符列
|
||||
uint32 xc_node_id = 0; // 用于存储节点标识符的临时变量
|
||||
int counter = 0; // 用于循环计数的变量
|
||||
|
||||
// 检查是否需要进行节点标识符的检查
|
||||
if (InvalidAttrNumber == junkfilter->jf_xc_node_id) {
|
||||
return;
|
||||
return; // 如果不需要检查节点标识符,直接返回
|
||||
}
|
||||
|
||||
// 获取节点标识符列
|
||||
xc_node_id_col = &(batch->m_arr[junkfilter->jf_xc_node_id - 1]);
|
||||
|
||||
// 遍历节点标识符列中的每一行
|
||||
for (counter = 0; counter < xc_node_id_col->m_rows; counter++) {
|
||||
// 获取当前行的节点标识符值,并将其转换为无符号整数类型
|
||||
xc_node_id = DatumGetUInt32(xc_node_id_col->m_vals[counter]);
|
||||
|
||||
// 检查当前节点的标识符是否与批处理中的值匹配,如果不匹配则抛出错误
|
||||
if (u_sess->pgxc_cxt.PGXCNodeIdentifier != xc_node_id) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
|
|
|
|||
|
|
@ -47,6 +47,16 @@
|
|||
* reloptions: relation options used to define new relation
|
||||
* mainTblStmt: Some statement of the query when create the new relation.
|
||||
*/
|
||||
/*
|
||||
* create_hist_relation: 为给定的用户表创建历史链关系表。
|
||||
*
|
||||
* 参数:
|
||||
* - rel:用户表的 Relation 对象。
|
||||
* - reloptions:历史表的附加选项。
|
||||
* - mainTblStmt:指向主用户表的 CreateStmt 的指针。
|
||||
*
|
||||
* 此函数为用户表创建历史链表。历史链表存储了用户表的所有列,以及每个块的命令类型、块编号和哈希值。
|
||||
*/
|
||||
void create_hist_relation(Relation rel, Datum reloptions, CreateStmt *mainTblStmt)
|
||||
{
|
||||
errno_t rc;
|
||||
|
|
@ -59,60 +69,59 @@ void create_hist_relation(Relation rel, Datum reloptions, CreateStmt *mainTblStm
|
|||
int16 coloptions[1];
|
||||
bool shared_relation = rel->rd_rel->relisshared;
|
||||
|
||||
// 生成历史链表的名称
|
||||
get_hist_name(relid, get_rel_name(relid), hist_name);
|
||||
|
||||
/*
|
||||
* history chain table contains all the columns from the origin user table, and then need to
|
||||
* record the command type, blocknum, and hash value of last block record.
|
||||
*/
|
||||
// 创建历史链表的描述
|
||||
TupleDesc chain_desc = CreateTemplateTupleDesc(USERCHAIN_COLUMN_NUM, false);
|
||||
|
||||
/* Now consider the additional columns and initilize the description */
|
||||
// 初始化描述中的额外列
|
||||
TupleDescInitEntry(chain_desc, USERCHAIN_COLUMN_REC_NUM + 1, "rec_num", INT8OID, -1, 0);
|
||||
TupleDescInitEntry(chain_desc, USERCHAIN_COLUMN_HASH_INS + 1, "hash_ins", HASH16OID, -1, 0);
|
||||
TupleDescInitEntry(chain_desc, USERCHAIN_COLUMN_HASH_DEL + 1, "hash_del", HASH16OID, -1, 0);
|
||||
TupleDescInitEntry(chain_desc, USERCHAIN_COLUMN_PREVHASH + 1, "pre_hash", HASH32OID, -1, 0);
|
||||
|
||||
// 添加内部选项以进行历史记录
|
||||
reloptions = AddInternalOption(reloptions, INTERNAL_MASK_DALTER | INTERNAL_MASK_DDELETE |
|
||||
INTERNAL_MASK_DINSERT | INTERNAL_MASK_DUPDATE);
|
||||
|
||||
// 创建历史链表
|
||||
hist_oid = heap_create_with_catalog(hist_name, nsp_oid, rel->rd_rel->reltablespace, InvalidOid,
|
||||
InvalidOid, InvalidOid, rel->rd_rel->relowner, chain_desc, NIL, 'r',
|
||||
(rel->rd_rel->relpersistence == 't') ? 'u' : rel->rd_rel->relpersistence,
|
||||
shared_relation, false, true, 0, ONCOMMIT_NOOP, reloptions, false, true,
|
||||
NULL, REL_CMPRS_NOT_SUPPORT, NULL, false);
|
||||
|
||||
/* make the history chain relation visible, else heap_open will fail */
|
||||
// 使历史链表可见,否则 heap_open 将失败
|
||||
CommandCounterIncrement();
|
||||
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
bool is_initdb_on_dn = false;
|
||||
/* Add to pgxc_class */
|
||||
/* When the sum of shmemNumDataNodes and shmemNumCoords equals to one,
|
||||
* the create table command is executed on datanode during initialization .
|
||||
* In this case, we do not write created table info in pgxc_class.
|
||||
/* 添加到 pgxc_class */
|
||||
/* 当 shmemNumDataNodes 和 shmemNumCoords 的总和等于一时,
|
||||
* 在初始化期间在数据节点上执行创建表命令。
|
||||
* 在这种情况下,我们不会将创建的表信息写入 pgxc_class。
|
||||
*/
|
||||
if ((*t_thrd.pgxc_cxt.shmemNumDataNodes + *t_thrd.pgxc_cxt.shmemNumCoords) == 1) {
|
||||
is_initdb_on_dn = true;
|
||||
}
|
||||
|
||||
/* only support normal table, do not support foreign table (can be supported in the future) */
|
||||
// 仅支持普通表,不支持外部表(未来可能支持)
|
||||
if ((!u_sess->attr.attr_common.IsInplaceUpgrade || !IsSystemNamespace(nsp_oid)) &&
|
||||
(IS_PGXC_COORDINATOR || (isRestoreMode && mainTblStmt->distributeby != NULL && !is_initdb_on_dn))) {
|
||||
AddRelationDistribution(hist_name, hist_oid, NULL, mainTblStmt->subcluster,
|
||||
InvalidOid, chain_desc, true);
|
||||
CommandCounterIncrement();
|
||||
/* Make sure locator info gets rebuilt */
|
||||
/* 确保定位器信息得到重建 */
|
||||
RelationCacheInvalidateEntry(hist_oid);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* now create index for this new history table */
|
||||
// 为新历史表创建索引
|
||||
char hist_index_name[NAMEDATALEN];
|
||||
rc = snprintf_s(hist_index_name, NAMEDATALEN, NAMEDATALEN - 1, "gs_hist_%u_index", relid);
|
||||
securec_check_ss(rc, "", "");
|
||||
|
||||
/* open the previous created history chain table */
|
||||
// 打开先前创建的历史链表
|
||||
Relation hist_rel = heap_open(hist_oid, ShareLock);
|
||||
IndexInfo *hist_index = makeNode(IndexInfo);
|
||||
hist_index->ii_NumIndexAttrs = 1;
|
||||
|
|
@ -140,6 +149,7 @@ void create_hist_relation(Relation rel, Datum reloptions, CreateStmt *mainTblStm
|
|||
extra.isPartitionedIndex = false;
|
||||
extra.isGlobalPartitionedIndex = false;
|
||||
|
||||
// 创建索引
|
||||
index_create(hist_rel, hist_index_name, InvalidOid, InvalidOid,
|
||||
hist_index, list_make1((void *)"rec_num"), BTREE_AM_OID,
|
||||
rel->rd_rel->reltablespace, collationObjectId, classObjectId,
|
||||
|
|
@ -148,7 +158,7 @@ void create_hist_relation(Relation rel, Datum reloptions, CreateStmt *mainTblStm
|
|||
|
||||
heap_close(hist_rel, NoLock);
|
||||
|
||||
/* Specify dependent between history table and origin table with depend option audo. */
|
||||
// 指定历史表与原始表之间的依赖关系,自动处理依赖关系
|
||||
ObjectAddress myself;
|
||||
ObjectAddress referenced;
|
||||
myself.classId = RelationRelationId;
|
||||
|
|
@ -159,10 +169,10 @@ void create_hist_relation(Relation rel, Datum reloptions, CreateStmt *mainTblStm
|
|||
referenced.objectSubId = 0;
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
|
||||
|
||||
// 释放资源
|
||||
pfree_ext(chain_desc);
|
||||
/*
|
||||
* Make changes visible
|
||||
*/
|
||||
|
||||
// 使更改可见
|
||||
CommandCounterIncrement();
|
||||
}
|
||||
|
||||
|
|
@ -175,21 +185,33 @@ void create_hist_relation(Relation rel, Datum reloptions, CreateStmt *mainTblStm
|
|||
* Note: This function is used after origin user table renamed, and then caller
|
||||
* can use this function to rename the corresponding hist table name.
|
||||
*/
|
||||
/*
|
||||
* rename_hist_by_usertable: 根据用户表的 OID 重命名相关的历史表。
|
||||
*
|
||||
* 参数:
|
||||
* - relid: 用户表的 OID。
|
||||
* - new_usertable_name: 新的用户表名称。
|
||||
*
|
||||
* 此函数用于重命名与给定用户表相关的历史表。
|
||||
*/
|
||||
void rename_hist_by_usertable(Oid relid, const char *new_usertable_name)
|
||||
{
|
||||
Oid hist_oid = get_hist_oid(relid);
|
||||
char new_hist_name[NAMEDATALEN];
|
||||
get_hist_name(relid, new_usertable_name, new_hist_name);
|
||||
|
||||
/* Do rename hist table. */
|
||||
/* 执行历史表重命名。 */
|
||||
RenameRelationInternal(hist_oid, new_hist_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* rename_hist_by_newnsp -- rename one hist table while altering schema name
|
||||
* rename_hist_by_newnsp: 在更改模式名称时重命名一个历史表。
|
||||
*
|
||||
* user_relid: relation oid of user table
|
||||
* new_nsp_name: the new schema name of user table
|
||||
* 参数:
|
||||
* - user_relid: 用户表的关系 OID。
|
||||
* - new_nsp_name: 用户表的新模式名称。
|
||||
*
|
||||
* 此函数在更改用户表的模式名称时重命名一个历史表。
|
||||
*/
|
||||
void rename_hist_by_newnsp(Oid user_relid, const char *new_nsp_name)
|
||||
{
|
||||
|
|
@ -198,20 +220,25 @@ void rename_hist_by_newnsp(Oid user_relid, const char *new_nsp_name)
|
|||
char new_hist_name[NAMEDATALEN] = {0};
|
||||
get_hist_name(user_relid, get_rel_name(user_relid), old_hist_name);
|
||||
hist_oid = get_relname_relid(old_hist_name, PG_BLOCKCHAIN_NAMESPACE);
|
||||
/* Some especial tables such as foreign tables have no hist table. So make sure hist exists. */
|
||||
|
||||
/* 某些特殊的表,如外部表,没有历史表。因此请确保历史表存在。 */
|
||||
if (!OidIsValid(hist_oid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
get_hist_name(user_relid, get_rel_name(user_relid), new_hist_name, get_rel_namespace(user_relid), new_nsp_name);
|
||||
|
||||
RenameRelationInternal(hist_oid, new_hist_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* rename_histlist_by_newnsp -- rename a list of hist table while altering schema name
|
||||
* rename_histlist_by_newnsp: 在更改模式名称时重命名一组历史表。
|
||||
*
|
||||
* usertable_oid_list: relation oid list of user tables
|
||||
* new_nsp_name: the new schema name of user table
|
||||
* 参数:
|
||||
* - usertable_oid_list: 用户表关系 OID 列表。
|
||||
* - new_nsp_name: 用户表的新模式名称。
|
||||
*
|
||||
* 此函数在更改用户表的模式名称时重命名一组历史表。
|
||||
*/
|
||||
void rename_histlist_by_newnsp(List *usertable_oid_list, const char *new_nsp_name)
|
||||
{
|
||||
|
|
@ -224,14 +251,20 @@ void rename_histlist_by_newnsp(List *usertable_oid_list, const char *new_nsp_nam
|
|||
}
|
||||
|
||||
/*
|
||||
* user_hash_attrno -- get the attribute number of user table's hash column.
|
||||
* user_hash_attrno: 获取用户表哈希列的属性编号。
|
||||
*
|
||||
* rd_att: tuple description of user table
|
||||
* 参数:
|
||||
* - rd_att: 用户表的元组描述。
|
||||
*
|
||||
* 返回值:哈希列的属性编号。
|
||||
*
|
||||
* 此函数用于获取用户表的哈希列的属性编号。
|
||||
*/
|
||||
int user_hash_attrno(const TupleDesc rd_att)
|
||||
{
|
||||
int hash_natt = -1;
|
||||
Form_pg_attribute rel_attr = NULL;
|
||||
|
||||
for (int i = rd_att->natts - 1; i >= 0; i--) {
|
||||
rel_attr = rd_att->attrs[i];
|
||||
if (strcmp(rel_attr->attname.data, "hash") == 0) {
|
||||
|
|
@ -239,6 +272,7 @@ int user_hash_attrno(const TupleDesc rd_att)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hash_natt;
|
||||
}
|
||||
|
||||
|
|
@ -251,29 +285,39 @@ int user_hash_attrno(const TupleDesc rd_att)
|
|||
*/
|
||||
static void hash_combine_tuple_data(char *buf, int buf_size, TupleDesc tabledesc, HeapTuple tuple)
|
||||
{
|
||||
int natts = tabledesc->natts;
|
||||
int buflen = 0;
|
||||
errno_t rc = EOK;
|
||||
char hash_str[UINT64STRSIZE + 1] = {0};
|
||||
Datum *values = (Datum *) palloc0(natts * sizeof(Datum));
|
||||
bool *nulls = (bool *) palloc0(natts * sizeof(bool));
|
||||
heap_deform_tuple(tuple, tabledesc, values, nulls);
|
||||
for (int i = 0; i < natts - 1; ++i) { /* except 'hash' column. */
|
||||
int natts = tabledesc->natts; // 获取表的列数
|
||||
|
||||
int buflen = 0; // 初始化缓冲区长度
|
||||
errno_t rc = EOK; // 用于错误处理的变量
|
||||
|
||||
char hash_str[UINT64STRSIZE + 1] = {0}; // 用于存储列的哈希值的字符串数组
|
||||
|
||||
Datum *values = (Datum *) palloc0(natts * sizeof(Datum)); // 分配空间用于存储列的数据值
|
||||
bool *nulls = (bool *) palloc0(natts * sizeof(bool)); // 分配空间用于存储列的NULL值信息
|
||||
|
||||
heap_deform_tuple(tuple, tabledesc, values, nulls); // 将堆元组解析成列数据和NULL信息
|
||||
|
||||
for (int i = 0; i < natts - 1; ++i) { // 遍历每一列,排除 'hash' 列
|
||||
if (nulls[i]) {
|
||||
continue;
|
||||
continue; // 如果该列为NULL,跳过
|
||||
}
|
||||
|
||||
uint64 col_hash = compute_hash(tabledesc->attrs[i]->atttypid, values[i], LOCATOR_TYPE_HASH);
|
||||
rc = snprintf_s(hash_str, UINT64STRSIZE + 1, UINT64STRSIZE, "%lu", col_hash);
|
||||
securec_check_ss(rc, "", "");
|
||||
rc = snprintf_s(buf + buflen, buf_size - buflen, buf_size - buflen - 1, "%s", hash_str);
|
||||
securec_check_ss(rc, "", "");
|
||||
buflen += strlen(hash_str);
|
||||
uint64 col_hash = compute_hash(tabledesc->attrs[i]->atttypid, values[i], LOCATOR_TYPE_HASH); // 计算列的哈希值
|
||||
|
||||
rc = snprintf_s(hash_str, UINT64STRSIZE + 1, UINT64STRSIZE, "%lu", col_hash); // 将哈希值转换成字符串
|
||||
securec_check_ss(rc, "", ""); // 检查 snprintf_s 是否成功
|
||||
|
||||
rc = snprintf_s(buf + buflen, buf_size - buflen, buf_size - buflen - 1, "%s", hash_str); // 将哈希值字符串添加到缓冲区
|
||||
securec_check_ss(rc, "", ""); // 检查 snprintf_s 是否成功
|
||||
|
||||
buflen += strlen(hash_str); // 更新缓冲区长度
|
||||
}
|
||||
pfree_ext(values);
|
||||
pfree_ext(nulls);
|
||||
|
||||
pfree_ext(values); // 释放列数据值内存
|
||||
pfree_ext(nulls); // 释放NULL信息内存
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_user_tuple_hash -- get the hash value of usertable's tuple.
|
||||
*
|
||||
|
|
@ -282,14 +326,20 @@ static void hash_combine_tuple_data(char *buf, int buf_size, TupleDesc tabledesc
|
|||
*/
|
||||
uint64 get_user_tuple_hash(HeapTuple tuple, TupleDesc desc)
|
||||
{
|
||||
Datum value;
|
||||
bool isnull = false;
|
||||
int hash_attno = user_hash_attrno(desc);
|
||||
Datum value; // 用于存储列数据值的变量
|
||||
bool isnull = false; // 用于存储是否为NULL的标志
|
||||
int hash_attno = user_hash_attrno(desc); // 获取哈希列的列号
|
||||
|
||||
// 获取最后一列的数据值(根据列号),并将是否为NULL的信息存储在 isnull 中
|
||||
value = heap_getattr(tuple, hash_attno + 1, desc, &isnull); /* get last column. */
|
||||
Assert(!isnull);
|
||||
|
||||
Assert(!isnull); // 使用断言确保数据值不为NULL,否则抛出错误
|
||||
|
||||
// 将数据值转换为 uint64 并返回
|
||||
return DatumGetUInt64(value);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* gen_user_tuple_hash -- generate hash of each user table's tuple.
|
||||
*
|
||||
|
|
@ -298,27 +348,36 @@ uint64 get_user_tuple_hash(HeapTuple tuple, TupleDesc desc)
|
|||
*/
|
||||
static uint64 gen_user_tuple_hash(Relation rel, HeapTuple tuple)
|
||||
{
|
||||
TupleDesc tabledesc = RelationGetDescr(rel);
|
||||
int data_size = UINT64STRSIZE * tabledesc->natts + 1;
|
||||
char *data_string = (char *)palloc0(data_size * sizeof(char));
|
||||
TupleDesc tabledesc = RelationGetDescr(rel); // 获取表的元组描述符
|
||||
|
||||
int data_size = UINT64STRSIZE * tabledesc->natts + 1; // 计算存储数据的字符串所需的缓冲区大小
|
||||
char *data_string = (char *)palloc0(data_size * sizeof(char)); // 分配存储数据的字符串的内存空间,并初始化为0
|
||||
|
||||
// 调用 hash_combine_tuple_data 函数将元组数据计算为字符串形式并存储在 data_string 中
|
||||
hash_combine_tuple_data(data_string, data_size, tabledesc, tuple);
|
||||
|
||||
uint8 sum[16];
|
||||
if (pg_md5_binary(data_string, strlen(data_string), sum) == false) {
|
||||
pfree_ext(data_string);
|
||||
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory")));
|
||||
}
|
||||
uint64 result = 0;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
result |= sum[4 + i];
|
||||
result = (result << 8);
|
||||
}
|
||||
result |= sum[11];
|
||||
uint8 sum[16]; // 用于存储 MD5 哈希的结果
|
||||
|
||||
pfree_ext(data_string);
|
||||
return result;
|
||||
// 使用 pg_md5_binary 函数计算 data_string 的 MD5 哈希值,结果存储在 sum 中
|
||||
if (pg_md5_binary(data_string, strlen(data_string), sum) == false) {
|
||||
pfree_ext(data_string); // 释放 data_string 内存
|
||||
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); // 如果计算失败,则抛出内存不足的错误
|
||||
}
|
||||
|
||||
uint64 result = 0; // 用于存储最终的哈希结果
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
result |= sum[4 + i]; // 将 sum 中的字节按位合并到 result 中
|
||||
result = (result << 8); // 左移 8 位,为下一个字节腾出位置
|
||||
}
|
||||
|
||||
result |= sum[11]; // 将最后一个字节合并到 result 中
|
||||
|
||||
pfree_ext(data_string); // 释放 data_string 内存
|
||||
return result; // 返回最终的哈希结果
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* set_user_tuple_hash -- calculate and fill the hash attribute of user table's tuple.
|
||||
*
|
||||
|
|
@ -331,34 +390,52 @@ static uint64 gen_user_tuple_hash(Relation rel, HeapTuple tuple)
|
|||
*/
|
||||
HeapTuple set_user_tuple_hash(HeapTuple tup, Relation rel, bool hash_exists)
|
||||
{
|
||||
// 计算元组的哈希值
|
||||
uint64 row_hash = gen_user_tuple_hash(rel, tup);
|
||||
|
||||
// 获取哈希列的列号
|
||||
int hash_attrno = user_hash_attrno(rel->rd_att);
|
||||
|
||||
if (hash_exists) {
|
||||
bool is_null;
|
||||
Datum hash = heap_getattr(tup, hash_attrno + 1, rel->rd_att, &is_null);
|
||||
|
||||
// 如果哈希值列为NULL或者与计算得到的哈希值不匹配,则抛出错误
|
||||
if (is_null || row_hash != DatumGetUInt64(hash)) {
|
||||
ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Invalid tuple hash.")));
|
||||
}
|
||||
return tup;
|
||||
|
||||
return tup; // 如果哈希值已存在且匹配,直接返回原始元组
|
||||
}
|
||||
|
||||
Datum *values = NULL;
|
||||
bool *nulls = NULL;
|
||||
bool *replaces = NULL;
|
||||
/* Build modified tuple */
|
||||
|
||||
// 获取表的属性数量
|
||||
int2 nattrs = RelationGetNumberOfAttributes(rel);
|
||||
|
||||
// 分配内存用于存储修改后的元组的数据值、NULL信息和替换标志
|
||||
values = (Datum*)palloc0(nattrs * sizeof(Datum));
|
||||
nulls = (bool*)palloc0(nattrs * sizeof(bool));
|
||||
replaces = (bool*)palloc0(nattrs * sizeof(bool));
|
||||
|
||||
// 设置哈希值列的数据值和替换标志
|
||||
values[hash_attrno] = UInt64GetDatum(row_hash);
|
||||
replaces[hash_attrno] = true;
|
||||
|
||||
// 使用 heap_modify_tuple 函数创建修改后的元组
|
||||
HeapTuple newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces);
|
||||
|
||||
// 释放分配的内存
|
||||
pfree_ext(values);
|
||||
pfree_ext(nulls);
|
||||
pfree_ext(replaces);
|
||||
return newtup;
|
||||
|
||||
return newtup; // 返回修改后的元组
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_hist_oid -- get the oid of history table by oid, name and namespace name of user table
|
||||
*
|
||||
|
|
@ -369,12 +446,18 @@ HeapTuple set_user_tuple_hash(HeapTuple tup, Relation rel, bool hash_exists)
|
|||
Oid get_hist_oid(Oid relid, const char *rel_name, Oid rel_nsp)
|
||||
{
|
||||
if (rel_name == NULL) {
|
||||
// 如果传入的表名为空,根据 relid 获取表名
|
||||
rel_name = get_rel_name(relid);
|
||||
}
|
||||
char hist_name[NAMEDATALEN];
|
||||
char hist_name[NAMEDATALEN]; // 用于存储历史表的名称
|
||||
|
||||
// 获取历史表的名称,这个函数的实现没有提供,但可以推测它根据传入的参数生成历史表的名称
|
||||
get_hist_name(relid, rel_name, hist_name, rel_nsp);
|
||||
|
||||
// 根据历史表的名称和命名空间获取历史表的 OID
|
||||
Oid hist_oid = get_relname_relid(hist_name, PG_BLOCKCHAIN_NAMESPACE);
|
||||
return hist_oid;
|
||||
|
||||
return hist_oid; // 返回历史表的 OID
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -394,31 +477,36 @@ uint64 get_user_tupleid_hash(Relation relation, ItemPointer tupleid)
|
|||
TupleDesc tabledescr;
|
||||
uint64 result;
|
||||
|
||||
// 获取表的元组描述符
|
||||
tabledescr = RelationGetDescr(relation);
|
||||
/* get tuple use tupleid */
|
||||
block = ItemPointerGetBlockNumber(tupleid);
|
||||
buffer = ReadBuffer(relation, block);
|
||||
|
||||
/* 根据 tupleid 获取元组数据 */
|
||||
block = ItemPointerGetBlockNumber(tupleid); // 获取元组所在的数据块号
|
||||
buffer = ReadBuffer(relation, block); // 读取数据块的缓冲区
|
||||
page = BufferGetPage(buffer);
|
||||
|
||||
// 如果数据块中的所有元组都可见,则锁定可见性映射
|
||||
if (PageIsAllVisible(page)) {
|
||||
visibilitymap_pin(relation, block, &vmbuffer);
|
||||
}
|
||||
|
||||
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
|
||||
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); // 锁定数据块的缓冲区,以便访问其中的元组
|
||||
|
||||
lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tupleid));
|
||||
tp.t_tableOid = RelationGetRelid(relation);
|
||||
tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
|
||||
tp.t_len = ItemIdGetLength(lp);
|
||||
tp.t_self = *tupleid;
|
||||
lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tupleid)); // 获取元组在数据块中的位置
|
||||
tp.t_tableOid = RelationGetRelid(relation); // 获取表的 OID
|
||||
tp.t_data = (HeapTupleHeader) PageGetItem(page, lp); // 获取元组的数据
|
||||
tp.t_len = ItemIdGetLength(lp); // 获取元组的长度
|
||||
tp.t_self = *tupleid; // 设置元组的位置信息
|
||||
|
||||
// 计算元组的哈希值
|
||||
result = get_user_tuple_hash(&tp, tabledescr);
|
||||
|
||||
UnlockReleaseBuffer(buffer);
|
||||
UnlockReleaseBuffer(buffer); // 解锁并释放数据块的缓冲区
|
||||
if (vmbuffer != InvalidBuffer) {
|
||||
ReleaseBuffer(vmbuffer);
|
||||
ReleaseBuffer(vmbuffer); // 释放可见性映射的缓冲区
|
||||
}
|
||||
|
||||
return result;
|
||||
return result; // 返回计算得到的哈希值
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -434,23 +522,38 @@ void gen_hist_tuple_hash(Oid relid, char *current_block_data, bool pre_row_exist
|
|||
hash32_t *pre_row_hash, hash32_t *hash)
|
||||
{
|
||||
errno_t rc;
|
||||
|
||||
// 计算缓冲区的大小,用于存储当前块数据和哈希值
|
||||
int buf_size = strlen(current_block_data) + NAMEDATALEN + 1;
|
||||
|
||||
// 分配内存用于存储数据字符串
|
||||
char *data_string = (char *)palloc0(buf_size * sizeof(char));
|
||||
|
||||
if (pre_row_exist) {
|
||||
// 如果前一行存在,则将前一行的哈希值转换为字符串
|
||||
char *pre_hash_str = DatumGetCString(DirectFunctionCall1(hash32out, HASH32GetDatum(pre_row_hash)));
|
||||
|
||||
// 将当前块数据和前一行哈希值字符串拼接到一起
|
||||
rc = snprintf_s(data_string, buf_size, buf_size - 1, "%s%s", current_block_data, pre_hash_str);
|
||||
|
||||
// 释放前一行哈希值字符串的内存
|
||||
pfree_ext(pre_hash_str);
|
||||
} else {
|
||||
// 如果前一行不存在,则根据 relid 获取表名
|
||||
char *rel_name = get_rel_name(relid);
|
||||
|
||||
// 将当前块数据和表名字符串拼接到一起
|
||||
rc = snprintf_s(data_string, buf_size, buf_size - 1, "%s%s", current_block_data, rel_name);
|
||||
}
|
||||
securec_check_ss(rc, "", "");
|
||||
securec_check_ss(rc, "", ""); // 检查 snprintf_s 是否成功
|
||||
|
||||
// 使用 pg_md5_binary 计算数据字符串的 MD5 哈希值,结果存储在 hash->data 中
|
||||
if (!pg_md5_binary(data_string, strlen(data_string), hash->data)) {
|
||||
pfree_ext(data_string);
|
||||
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory")));
|
||||
pfree_ext(data_string); // 释放数据字符串内存
|
||||
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); // 如果计算失败,则抛出内存不足的错误
|
||||
}
|
||||
pfree_ext(data_string);
|
||||
|
||||
pfree_ext(data_string); // 释放数据字符串内存
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -463,16 +566,20 @@ void gen_hist_tuple_hash(Oid relid, char *current_block_data, bool pre_row_exist
|
|||
*/
|
||||
static void fill_hist_block(Oid histoid, uint64 hash_ins, uint64 hash_del, HistBlock *block)
|
||||
{
|
||||
char data[NAMEDATALEN] = {0};
|
||||
char data[NAMEDATALEN] = {0}; // 初始化一个字符数组用于存储数据
|
||||
|
||||
block->rec_num = get_next_recnum(histoid);
|
||||
/* Before generate previous hash, we should get current block information */
|
||||
block->rec_num = get_next_recnum(histoid); // 获取下一个记录号
|
||||
|
||||
/* 在生成前一块哈希之前,我们应该获取当前块的信息 */
|
||||
// 将记录号、插入哈希值和删除哈希值格式化为字符串并存储在 data 中
|
||||
error_t rc = sprintf_s(data, NAMEDATALEN, "%lu%lu%lu", block->rec_num, hash_ins, hash_del);
|
||||
securec_check_ss(rc, "", "");
|
||||
securec_check_ss(rc, "", ""); // 检查 sprintf_s 是否成功
|
||||
|
||||
// 调用 gen_hist_tuple_hash 函数计算当前块的哈希值,并存储在 block->prev_hash 中
|
||||
gen_hist_tuple_hash(histoid, data, false, NULL, &block->prev_hash);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* hist_table_record_internal -- append record to history table when user table is modified
|
||||
*
|
||||
|
|
@ -482,22 +589,25 @@ static void fill_hist_block(Oid histoid, uint64 hash_ins, uint64 hash_del, HistB
|
|||
*/
|
||||
bool hist_table_record_internal(Oid hist_oid, const uint64 *hash_ins, const uint64 *hash_del)
|
||||
{
|
||||
Datum values[USERCHAIN_COLUMN_NUM] = {0};
|
||||
bool nulls[USERCHAIN_COLUMN_NUM] = {false};
|
||||
bool ins_null = hash_ins == NULL;
|
||||
bool del_null = hash_del == NULL;
|
||||
uint64 t_ins = ins_null ? 0 : *hash_ins;
|
||||
uint64 t_del = del_null ? 0 : *hash_del;
|
||||
HistBlock block;
|
||||
Datum values[USERCHAIN_COLUMN_NUM] = {0}; // 用于存储要插入的列的数据值
|
||||
bool nulls[USERCHAIN_COLUMN_NUM] = {false}; // 用于存储要插入的列的 NULL 信息
|
||||
bool ins_null = hash_ins == NULL; // 判断插入哈希值是否为 NULL
|
||||
bool del_null = hash_del == NULL; // 判断删除哈希值是否为 NULL
|
||||
uint64 t_ins = ins_null ? 0 : *hash_ins; // 获取插入哈希值,如果为 NULL 则为 0
|
||||
uint64 t_del = del_null ? 0 : *hash_del; // 获取删除哈希值,如果为 NULL 则为 0
|
||||
HistBlock block; // 用于存储历史块的信息
|
||||
|
||||
if (!OidIsValid(hist_oid)) {
|
||||
// 如果历史表的 OID 无效,抛出错误并返回 false
|
||||
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("could not find history table")));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Before generate previous hash, we should get current block information */
|
||||
/* 在生成前一块哈希之前,我们应该获取当前块的信息 */
|
||||
// 调用 fill_hist_block 函数填充历史块的信息
|
||||
fill_hist_block(hist_oid, t_ins, t_del, &block);
|
||||
|
||||
// 填充要插入的列的数据值和 NULL 信息
|
||||
values[USERCHAIN_COLUMN_REC_NUM] = UInt64GetDatum(block.rec_num);
|
||||
values[USERCHAIN_COLUMN_HASH_INS] = UInt64GetDatum(t_ins);
|
||||
values[USERCHAIN_COLUMN_HASH_DEL] = UInt64GetDatum(t_del);
|
||||
|
|
@ -507,17 +617,26 @@ bool hist_table_record_internal(Oid hist_oid, const uint64 *hash_ins, const uint
|
|||
nulls[USERCHAIN_COLUMN_HASH_DEL] = del_null;
|
||||
nulls[USERCHAIN_COLUMN_PREVHASH] = false;
|
||||
|
||||
// 打开历史表
|
||||
Relation hist_rel = heap_open(hist_oid, RowExclusiveLock);
|
||||
TupleDesc hist_desc = RelationGetDescr(hist_rel);
|
||||
|
||||
// 使用 heap_form_tuple 函数创建要插入的元组
|
||||
HeapTuple tuple = heap_form_tuple(hist_desc, values, nulls);
|
||||
|
||||
// 将元组插入历史表
|
||||
simple_heap_insert(hist_rel, tuple);
|
||||
|
||||
// 释放元组内存
|
||||
heap_freetuple(tuple);
|
||||
|
||||
// 关闭历史表
|
||||
heap_close(hist_rel, RowExclusiveLock);
|
||||
|
||||
return true;
|
||||
return true; // 返回 true 表示插入成功
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* hist_table_record_insert -- append a record while inserting into user table
|
||||
*
|
||||
|
|
@ -527,17 +646,25 @@ bool hist_table_record_internal(Oid hist_oid, const uint64 *hash_ins, const uint
|
|||
*/
|
||||
bool hist_table_record_insert(Relation rel, HeapTuple tup, uint64 *res_hash)
|
||||
{
|
||||
/* check all inputs are avaliable */
|
||||
/* 检查所有输入是否有效 */
|
||||
if (tup == NULL || rel == NULL) {
|
||||
return false; /* Do some thing */
|
||||
return false; /* 做一些处理 */
|
||||
}
|
||||
|
||||
// 计算插入哈希值
|
||||
uint64 hash_ins = get_user_tuple_hash(tup, rel->rd_att);
|
||||
|
||||
// 获取历史表的 OID
|
||||
Oid hist_oid = get_hist_oid(RelationGetRelid(rel), RelationGetRelationName(rel), RelationGetNamespace(rel));
|
||||
|
||||
// 将插入哈希值存储在 res_hash 中
|
||||
*res_hash = hash_ins;
|
||||
|
||||
// 调用 hist_table_record_internal 函数插入历史记录
|
||||
return hist_table_record_internal(hist_oid, &hash_ins, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* hist_table_record_delete -- append a record while deleting from user table
|
||||
*
|
||||
|
|
@ -547,12 +674,18 @@ bool hist_table_record_insert(Relation rel, HeapTuple tup, uint64 *res_hash)
|
|||
*/
|
||||
bool hist_table_record_delete(Relation rel, uint64 hash_del, uint64 *res_hash)
|
||||
{
|
||||
// 获取历史表的 OID
|
||||
Oid hist_oid = get_hist_oid(RelationGetRelid(rel), RelationGetRelationName(rel), RelationGetNamespace(rel));
|
||||
|
||||
// 计算删除哈希值,并将其存储在 res_hash 中(取负值)
|
||||
*res_hash = -hash_del;
|
||||
/* insert history record into userchain table */
|
||||
|
||||
/* 将历史记录插入用户链表表 */
|
||||
// 调用 hist_table_record_internal 函数删除历史记录
|
||||
return hist_table_record_internal(hist_oid, NULL, &hash_del);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* hist_table_record_update -- append a record while updating user table
|
||||
*
|
||||
|
|
@ -563,13 +696,21 @@ bool hist_table_record_delete(Relation rel, uint64 hash_del, uint64 *res_hash)
|
|||
*/
|
||||
bool hist_table_record_update(Relation rel, HeapTuple newtup, uint64 hash_del, uint64 *res_hash)
|
||||
{
|
||||
// 计算插入哈希值
|
||||
uint64 hash_ins = get_user_tuple_hash(newtup, rel->rd_att);
|
||||
|
||||
// 获取历史表的 OID
|
||||
Oid hist_oid = get_hist_oid(RelationGetRelid(rel), RelationGetRelationName(rel), RelationGetNamespace(rel));
|
||||
|
||||
// 计算更新哈希值(插入哈希值减去删除哈希值)并存储在 res_hash 中
|
||||
*res_hash = hash_ins - hash_del;
|
||||
/* insert history record into userchain table */
|
||||
|
||||
/* 将历史记录插入用户链表表 */
|
||||
// 调用 hist_table_record_internal 函数更新历史记录
|
||||
return hist_table_record_internal(hist_oid, &hash_ins, &hash_del);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_copyfrom_line_relhash -- extract hash from each copyfrom line
|
||||
*
|
||||
|
|
@ -582,22 +723,30 @@ bool hist_table_record_update(Relation rel, HeapTuple newtup, uint64 hash_del, u
|
|||
bool get_copyfrom_line_relhash(const char *row_data, int len, int hash_colno, char split, uint64 *hash)
|
||||
{
|
||||
int pos;
|
||||
/* Not found hash column. */
|
||||
|
||||
/* 如果未找到哈希列,返回 false */
|
||||
if (hash_colno == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 遍历行数据,寻找哈希列的位置
|
||||
for (pos = 0; pos < len && hash_colno > 0; ++pos) {
|
||||
if (row_data[pos] == split) {
|
||||
--hash_colno;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到了哈希列
|
||||
if (hash_colno == 0) {
|
||||
int remain_len = len - pos;
|
||||
const char *hash_str = row_data + pos;
|
||||
int remain_len = len - pos; // 哈希值字符串的长度
|
||||
const char *hash_str = row_data + pos; // 哈希值字符串的起始位置
|
||||
|
||||
// 如果剩余长度大于 0,将哈希值字符串转换为 uint64 类型的哈希值
|
||||
if (remain_len > 0) {
|
||||
*hash = DatumGetUInt64(DirectFunctionCall1(hash16in, CStringGetDatum(hash_str)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -170,44 +170,69 @@ int PgPolicyPrivilegesAccessStruct::operator - (const PgPolicyPrivilegesAccessSt
|
|||
// 处理来自解析器树的新筛选器,并将它们转化为字符串
|
||||
bool process_new_filters(const List *policy_filters, gs_stl::gs_string *flat_tree)
|
||||
{
|
||||
// 如果策略过滤器为空,直接返回true
|
||||
if (!policy_filters)
|
||||
return true;
|
||||
|
||||
// 清空输出的扁平化树字符串
|
||||
flat_tree->clear();
|
||||
ListCell *policy_filter_item = NULL;
|
||||
|
||||
// 用于存储策略树节点的堆栈
|
||||
gs_stl::gs_vector<PolicyFilterNode *> nodes;
|
||||
|
||||
// 遍历策略过滤器列表
|
||||
ListCell *policy_filter_item = NULL;
|
||||
foreach(policy_filter_item, policy_filters) {
|
||||
PolicyFilterNode *root = (PolicyFilterNode *) lfirst(policy_filter_item);
|
||||
nodes.push_back(root);
|
||||
|
||||
// 迭代处理策略树节点
|
||||
while (nodes.size() > 0) {
|
||||
PolicyFilterNode* n = nodes.back();
|
||||
nodes.pop_back();
|
||||
/* operator type node */
|
||||
|
||||
// 如果节点类型为操作符节点
|
||||
if (!strcmp(n->node_type, "op")) {
|
||||
if (!strcmp(n->op_value, "and")) {
|
||||
// 将逻辑与操作符 "*" 添加到扁平化树中
|
||||
(void)flat_tree->append("*");
|
||||
} else if (!strcmp(n->op_value, "or")) {
|
||||
// 将逻辑或操作符 "+" 添加到扁平化树中
|
||||
(void)flat_tree->append("+");
|
||||
} else { /* unsupported operator */
|
||||
} else {
|
||||
// 不支持的操作符类型,返回false表示处理失败
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将右子节点和左子节点压入堆栈,以便继续处理
|
||||
nodes.push_back((PolicyFilterNode *)n->right);
|
||||
nodes.push_back((PolicyFilterNode *)n->left);
|
||||
} else if (!strcmp(n->node_type, "filter")) { /* value type node */
|
||||
} else if (!strcmp(n->node_type, "filter")) { // 如果节点类型为值节点
|
||||
if (n->has_not_operator == true) {
|
||||
// 如果节点带有 "!" 表示否定操作符,则将 "!" 添加到扁平化树中
|
||||
(void)flat_tree->append("!");
|
||||
}
|
||||
|
||||
// 添加策略过滤器的类型到扁平化树中
|
||||
(void)flat_tree->append(n->filter_type);
|
||||
(void)flat_tree->append("[");
|
||||
List *filter_item_objects = (List *) n->values;
|
||||
ListCell *filter_obj = NULL;
|
||||
|
||||
// 遍历策略过滤器的值列表
|
||||
foreach(filter_obj, filter_item_objects) {
|
||||
const char *filter_value = (const char *)(((Value*)lfirst(filter_obj))->val.str);
|
||||
|
||||
// 验证策略过滤器的值是否有效,如果无效则返回false表示处理失败
|
||||
if (!verify_ip_role_app(n->filter_type, filter_value, flat_tree)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加策略过滤器的值到扁平化树中,并用逗号分隔
|
||||
(void)flat_tree->append(",");
|
||||
}
|
||||
|
||||
// 如果扁平化树的最后一个字符是逗号,则删除它
|
||||
if (flat_tree->back() == ',') {
|
||||
flat_tree->pop_back();
|
||||
}
|
||||
|
|
@ -215,9 +240,12 @@ bool process_new_filters(const List *policy_filters, gs_stl::gs_string *flat_tre
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理完毕,返回true表示处理成功
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool scan_to_delete_from_relation(long long row_id, Relation relation, unsigned int index_id)
|
||||
{// 如果关系为空,返回false
|
||||
if (relation == NULL) {
|
||||
|
|
@ -372,12 +400,20 @@ bool handle_target(ListCell *target,
|
|||
const policy_labels_map *existing_labels,
|
||||
const GsPolicyStruct *policy, const char *acc_action_type)
|
||||
{
|
||||
// 用于返回操作结果的变量
|
||||
bool ret = false;
|
||||
|
||||
// 根据策略选项类型进行不同的处理
|
||||
switch (opt_type) {
|
||||
case POLICY_OPT_PRIVILEGES: {
|
||||
// 获取目标对象的RangeVar
|
||||
RangeVar *rel = (RangeVar*)lfirst(target);
|
||||
|
||||
// 构建目标对象的名称字符串
|
||||
gs_stl::gs_string target_name_s;
|
||||
construct_resource_name((const RangeVar*)rel, &target_name_s);
|
||||
|
||||
// 根据操作类型(添加或删除)调用add_privileges_access函数,处理特权访问控制
|
||||
if (is_add) {
|
||||
ret = add_privileges_access(acc_action_type, target_name_s.c_str(), privs_to_add,
|
||||
existing_labels, policy, err_msg);
|
||||
|
|
@ -388,9 +424,14 @@ bool handle_target(ListCell *target,
|
|||
}
|
||||
break;
|
||||
case POLICY_OPT_ACCESS: {
|
||||
// 获取目标对象的RangeVar
|
||||
RangeVar *rel = (RangeVar*)lfirst(target);
|
||||
|
||||
// 构建目标对象的名称字符串
|
||||
gs_stl::gs_string target_name_s;
|
||||
construct_resource_name((const RangeVar*)rel, &target_name_s);
|
||||
|
||||
// 根据操作类型(添加或删除)调用add_privileges_access函数,处理访问控制
|
||||
if (is_add) {
|
||||
ret = add_privileges_access(acc_action_type, target_name_s.c_str(), access_to_add, existing_labels,
|
||||
policy, err_msg);
|
||||
|
|
@ -400,12 +441,15 @@ bool handle_target(ListCell *target,
|
|||
}
|
||||
}
|
||||
break;
|
||||
// : this is not handled here...
|
||||
// : 这里未处理其他策略选项类型
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 返回操作结果
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 这个函数用于解析逗号分隔的值,并验证它们的有效性。
|
||||
static bool parse_values(const gs_stl::gs_string logical_expr_str, int *offset, const char* obj_type)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace gs_stl {
|
|||
MemoryContext GetStringMemory()
|
||||
{
|
||||
if (t_thrd.security_policy_cxt.StringMemoryContext == NULL) {
|
||||
// 如果字符串内存上下文尚未创建,则创建一个新的字符串内存上下文
|
||||
t_thrd.security_policy_cxt.StringMemoryContext =
|
||||
AllocSetContextCreate(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), "StringMemory",
|
||||
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
||||
|
|
@ -42,6 +43,7 @@ MemoryContext GetStringMemory()
|
|||
void DeleteStringMemory()
|
||||
{
|
||||
if (t_thrd.security_policy_cxt.StringMemoryContext != NULL) {
|
||||
// 如果字符串内存上下文存在,则删除它
|
||||
MemoryContextDelete(t_thrd.security_policy_cxt.StringMemoryContext);
|
||||
t_thrd.security_policy_cxt.StringMemoryContext = NULL;
|
||||
}
|
||||
|
|
@ -50,6 +52,7 @@ void DeleteStringMemory()
|
|||
MemoryContext GetVectorMemory()
|
||||
{
|
||||
if (t_thrd.security_policy_cxt.VectorMemoryContext == NULL) {
|
||||
// 如果向量内存上下文尚未创建,则创建一个新的向量内存上下文
|
||||
t_thrd.security_policy_cxt.VectorMemoryContext =
|
||||
AllocSetContextCreate(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), "VectorMemory",
|
||||
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
||||
|
|
@ -60,6 +63,7 @@ MemoryContext GetVectorMemory()
|
|||
void DeleteVectorMemory()
|
||||
{
|
||||
if (t_thrd.security_policy_cxt.VectorMemoryContext != NULL) {
|
||||
// 如果向量内存上下文存在,则删除它
|
||||
MemoryContextDelete(t_thrd.security_policy_cxt.VectorMemoryContext);
|
||||
t_thrd.security_policy_cxt.VectorMemoryContext = NULL;
|
||||
}
|
||||
|
|
@ -68,6 +72,7 @@ void DeleteVectorMemory()
|
|||
MemoryContext GetMapMemory()
|
||||
{
|
||||
if (!t_thrd.security_policy_cxt.MapMemoryContext) {
|
||||
// 如果映射内存上下文尚未创建,则创建一个新的映射内存上下文
|
||||
t_thrd.security_policy_cxt.MapMemoryContext = AllocSetContextCreate(TopMemoryContext, "MapMemory",
|
||||
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
||||
}
|
||||
|
|
@ -77,6 +82,7 @@ MemoryContext GetMapMemory()
|
|||
void DeleteMapMemory()
|
||||
{
|
||||
if (t_thrd.security_policy_cxt.MapMemoryContext) {
|
||||
// 如果映射内存上下文存在,则删除它
|
||||
MemoryContextDelete(t_thrd.security_policy_cxt.MapMemoryContext);
|
||||
t_thrd.security_policy_cxt.MapMemoryContext = nullptr;
|
||||
}
|
||||
|
|
@ -84,12 +90,14 @@ void DeleteMapMemory()
|
|||
|
||||
void *_HashMapAllocFunc(Size request)
|
||||
{
|
||||
// 使用映射内存上下文分配内存
|
||||
return MemoryContextAlloc(GetMapMemory(), request);
|
||||
}
|
||||
|
||||
MemoryContext GetSetMemory()
|
||||
{
|
||||
if (!t_thrd.security_policy_cxt.SetMemoryContext) {
|
||||
// 如果集合内存上下文尚未创建,则创建一个新的集合内存上下文
|
||||
t_thrd.security_policy_cxt.SetMemoryContext = AllocSetContextCreate(TopMemoryContext, "SetMemory",
|
||||
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
||||
}
|
||||
|
|
@ -99,6 +107,7 @@ MemoryContext GetSetMemory()
|
|||
void DeleteSetMemory()
|
||||
{
|
||||
if (t_thrd.security_policy_cxt.SetMemoryContext) {
|
||||
// 如果集合内存上下文存在,则删除它
|
||||
MemoryContextDelete(t_thrd.security_policy_cxt.SetMemoryContext);
|
||||
t_thrd.security_policy_cxt.SetMemoryContext = nullptr;
|
||||
}
|
||||
|
|
@ -106,16 +115,19 @@ void DeleteSetMemory()
|
|||
|
||||
void *_HashSetAllocFunc(Size request)
|
||||
{
|
||||
// 使用集合内存上下文分配内存
|
||||
return MemoryContextAlloc(GetSetMemory(), request);
|
||||
}
|
||||
|
||||
int matchStr(const void *key1, const void *key2, Size keysize)
|
||||
{
|
||||
// 比较两个字符串是否相等(不区分大小写)
|
||||
return strncasecmp((const char *)key1, (const char *)key2, keysize - 1);
|
||||
}
|
||||
|
||||
int gs_stringCompareKeyFunc(const void *keyA, const void *keyB)
|
||||
{
|
||||
// 比较两个gs_string对象作为键的大小
|
||||
if (*(const gs_string *)keyA < *(const gs_string *)keyB) {
|
||||
return -1;
|
||||
} else if (*(const gs_string *)keyB < *(const gs_string *)keyA) {
|
||||
|
|
@ -125,23 +137,28 @@ int gs_stringCompareKeyFunc(const void *keyA, const void *keyB)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// string implementation
|
||||
// 初始化字符串缓冲区
|
||||
inline bool gs_string::InitBuff(const char *str, size_t len)
|
||||
{
|
||||
if (m_buff == NULL) {
|
||||
size_t init_len = (len > 0) ? (len + 1) : (strlen(str) + 1);
|
||||
m_capacity = Max(MIN_STR_CAPACITY, init_len);
|
||||
m_buff = AllocFunc(m_capacity);
|
||||
errno_t ret = snprintf_s(m_buff, m_capacity, init_len - 1, "%.*s", (int)(init_len - 1), str);
|
||||
securec_check_ss(ret, "\0", "\0");
|
||||
m_len = (size_t)ret;
|
||||
return true;
|
||||
// 如果字符串缓冲区为空,执行初始化
|
||||
size_t init_len = (len > 0) ? (len + 1) : (strlen(str) + 1); // 计算初始化长度
|
||||
m_capacity = Max(MIN_STR_CAPACITY, init_len); // 设置容量,至少为MIN_STR_CAPACITY
|
||||
m_buff = AllocFunc(m_capacity); // 分配内存
|
||||
errno_t ret = snprintf_s(m_buff, m_capacity, init_len - 1, "%.*s", (int)(init_len - 1), str); // 格式化字符串
|
||||
securec_check_ss(ret, "\0", "\0"); // 检查 snprintf_s 的返回值
|
||||
m_len = (size_t)ret; // 设置字符串长度
|
||||
return true; // 返回初始化成功
|
||||
}
|
||||
return false;
|
||||
return false; // 如果缓冲区不为空,返回初始化失败
|
||||
}
|
||||
|
||||
// 构造函数,用于创建 gs_string 对象
|
||||
gs_string::gs_string(const char *str, size_t len) : m_buff(NULL), m_len(0), m_capacity(0)
|
||||
{
|
||||
(void)InitBuff(str, len);
|
||||
(void)InitBuff(str, len); // 调用 InitBuff 进行初始化
|
||||
}
|
||||
|
||||
gs_string::~gs_string()
|
||||
|
|
@ -161,18 +178,20 @@ gs_string::~gs_string()
|
|||
}
|
||||
}
|
||||
|
||||
// 构造函数,从另一个 gs_string 对象拷贝构造
|
||||
gs_string::gs_string(const gs_string &arg) : m_buff(NULL), m_len(0), m_capacity(0)
|
||||
{
|
||||
operator = (arg);
|
||||
operator = (arg); // 调用赋值运算符函数来复制内容
|
||||
}
|
||||
|
||||
// 赋值运算符函数,将一个 gs_string 对象的内容赋值给另一个对象
|
||||
gs_string &gs_string::operator = (const gs_string &arg)
|
||||
{
|
||||
if (&arg == this) {
|
||||
return *this;
|
||||
return *this; // 避免自赋值
|
||||
}
|
||||
|
||||
/* m_buff should always be free if not NULL as will be taken place with arg */
|
||||
// 释放当前对象的缓冲区内存
|
||||
if (m_buff != NULL) {
|
||||
pfree(m_buff);
|
||||
m_buff = NULL;
|
||||
|
|
@ -180,13 +199,14 @@ gs_string &gs_string::operator = (const gs_string &arg)
|
|||
|
||||
size_t len = arg.size();
|
||||
if (len > 0) {
|
||||
(void)InitBuff(arg.c_str(), arg.size());
|
||||
(void)InitBuff(arg.c_str(), arg.size()); // 初始化当前对象的缓冲区
|
||||
} else {
|
||||
(void)InitBuff("", 0);
|
||||
(void)InitBuff("", 0); // 如果源字符串为空,则初始化为一个空字符串
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 重载运算符 -,用于字符串比较
|
||||
int gs_string::operator - (const gs_string &arg) const
|
||||
{
|
||||
if (this == &arg) {
|
||||
|
|
@ -201,45 +221,50 @@ int gs_string::operator - (const gs_string &arg) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
// 追加一个 gs_string 对象到当前对象
|
||||
gs_string &gs_string::append(const gs_string &str)
|
||||
{
|
||||
return append(str.c_str(), str.size());
|
||||
}
|
||||
|
||||
// 追加一个字符串到当前对象
|
||||
gs_string &gs_string::append(const char *str, size_t len)
|
||||
{
|
||||
if (!InitBuff(str)) {
|
||||
size_t init_len = (len > 0) ? (len + 1) : (strlen(str) + 1);
|
||||
if (init_len > (m_capacity - m_len)) {
|
||||
m_buff = ReallocFunc(m_capacity + init_len);
|
||||
m_buff = ReallocFunc(m_capacity + init_len); // 如果空间不足,重新分配更大的空间
|
||||
}
|
||||
errno_t ret = snprintf_s(m_buff + m_len, m_capacity - m_len, init_len - 1, "%.*s", (int)(init_len - 1), str);
|
||||
securec_check_ss(ret, "\0", "\0");
|
||||
m_len += (size_t)ret;
|
||||
m_len += (size_t)ret; // 更新字符串长度
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 在字符串末尾添加一个字符
|
||||
void gs_string::push_back(char ch)
|
||||
{
|
||||
char t_chr[2] = {0};
|
||||
t_chr[1] = ch;
|
||||
if (!InitBuff(t_chr)) {
|
||||
if ((m_len + 1) >= m_capacity) {
|
||||
m_buff = ReallocFunc(m_capacity * 2);
|
||||
m_buff = ReallocFunc(m_capacity * 2); // 如果空间不足,扩展为当前的两倍
|
||||
}
|
||||
m_buff[m_len++] = ch;
|
||||
m_buff[m_len] = '\0';
|
||||
m_buff[m_len++] = ch; // 添加字符到缓冲区末尾
|
||||
m_buff[m_len] = '\0'; // 添加字符串结束符
|
||||
}
|
||||
}
|
||||
|
||||
// 删除字符串末尾的字符
|
||||
void gs_string::pop_back()
|
||||
{
|
||||
if (m_len > 0) {
|
||||
m_buff[--m_len] = '\0';
|
||||
m_buff[--m_len] = '\0'; // 删除最后一个字符并添加结束符
|
||||
}
|
||||
}
|
||||
|
||||
// 访问字符串的字符,类似于数组下标访问
|
||||
char gs_string::operator[](int idx) const
|
||||
{
|
||||
if (idx > (int)m_len) {
|
||||
|
|
@ -248,14 +273,16 @@ char gs_string::operator[](int idx) const
|
|||
return m_buff[idx];
|
||||
}
|
||||
|
||||
// 清空字符串
|
||||
void gs_string::clear()
|
||||
{
|
||||
if (m_buff != NULL) {
|
||||
m_buff[0] = '\0';
|
||||
m_len = 0;
|
||||
m_buff[0] = '\0'; // 将字符串清空
|
||||
m_len = 0; // 长度设为0
|
||||
}
|
||||
}
|
||||
|
||||
// 在字符串中查找字符的位置
|
||||
size_t gs_string::find(char arg, size_t start) const
|
||||
{
|
||||
for (; start < m_len; ++start) {
|
||||
|
|
@ -263,28 +290,31 @@ size_t gs_string::find(char arg, size_t start) const
|
|||
return start;
|
||||
}
|
||||
}
|
||||
return npos;
|
||||
return npos; // 如果未找到,返回npos(无效位置)
|
||||
}
|
||||
|
||||
// 返回字符串的最后一个字符
|
||||
char gs_string::back() const
|
||||
{
|
||||
if (m_len > 0) {
|
||||
return m_buff[m_len - 1];
|
||||
return m_buff[m_len - 1]; // 返回最后一个字符
|
||||
}
|
||||
return m_buff[0];
|
||||
return m_buff[0]; // 如果字符串为空,返回第一个字符
|
||||
}
|
||||
|
||||
// 截取字符串的子串
|
||||
gs_string gs_string::substr(size_t pos, size_t len) const
|
||||
{
|
||||
if ((pos + len) < m_len) {
|
||||
return gs_string((const char *)(m_buff + pos), len);
|
||||
return gs_string((const char *)(m_buff + pos), len); // 返回指定位置和长度的子串
|
||||
}
|
||||
if (pos < m_len) {
|
||||
return gs_string((const char *)(m_buff + pos), m_len - pos);
|
||||
return gs_string((const char *)(m_buff + pos), m_len - pos); // 返回从指定位置到末尾的子串
|
||||
}
|
||||
return gs_string((const char *)m_buff, m_len);
|
||||
return gs_string((const char *)m_buff, m_len); // 返回整个字符串
|
||||
}
|
||||
|
||||
// 替换字符串的一部分
|
||||
gs_string &gs_string::replace(size_t pos, size_t len, const char *s)
|
||||
{
|
||||
if (pos < m_len) {
|
||||
|
|
@ -293,7 +323,7 @@ gs_string &gs_string::replace(size_t pos, size_t len, const char *s)
|
|||
size_t jump = (rep_len - len);
|
||||
errno_t ret = EOK;
|
||||
if ((m_len + jump) >= m_capacity) {
|
||||
(void)ReallocFunc(m_capacity + jump);
|
||||
(void)ReallocFunc(m_capacity + jump); // 如果空间不足,重新分配更大的空间
|
||||
}
|
||||
|
||||
if (replace_len < m_len) {
|
||||
|
|
@ -315,6 +345,7 @@ gs_string &gs_string::replace(size_t pos, size_t len, const char *s)
|
|||
return *this;
|
||||
}
|
||||
|
||||
// 删除字符串的一部分
|
||||
void gs_string::erase(size_t pos, size_t len)
|
||||
{
|
||||
if (m_len == 0 || (pos >= m_len)) {
|
||||
|
|
@ -325,48 +356,51 @@ void gs_string::erase(size_t pos, size_t len)
|
|||
while (idx < m_len) {
|
||||
m_buff[pos++] = m_buff[idx++];
|
||||
}
|
||||
m_len = pos;
|
||||
m_len = pos; // 更新字符串长度
|
||||
} else {
|
||||
m_len = pos;
|
||||
m_len = pos; // 删除指定位置后的所有字符
|
||||
}
|
||||
m_buff[m_len] = '\0';
|
||||
m_buff[m_len] = '\0'; // 添加字符串结束符
|
||||
}
|
||||
|
||||
// 比较两个字符串是否相等
|
||||
bool gs_string::operator == (const gs_string &arg) const
|
||||
{
|
||||
if (m_len != arg.m_len) {
|
||||
return false;
|
||||
return false; // 长度不同,字符串不等
|
||||
}
|
||||
if (m_len > 0) {
|
||||
return (strcasecmp(m_buff, arg.m_buff) == 0);
|
||||
return (strcasecmp(m_buff, arg.m_buff) == 0); // 大小写不敏感比较字符串
|
||||
}
|
||||
return m_len == 0;
|
||||
return m_len == 0; // 空字符串相等
|
||||
}
|
||||
|
||||
// 比较两个字符串的大小
|
||||
bool gs_string::operator < (const gs_string &arg) const
|
||||
{
|
||||
return strcasecmp(m_buff, arg.m_buff) < 0;
|
||||
return strcasecmp(m_buff, arg.m_buff) < 0; // 大小写不敏感比较字符串大小
|
||||
}
|
||||
|
||||
// 分配内存并返回指向内存的指针
|
||||
inline char *gs_string::AllocFunc(size_t _size) const
|
||||
{
|
||||
return (char *)MemoryContextAlloc(GetStringMemory(), _size);
|
||||
return (char *)MemoryContextAlloc(GetStringMemory(), _size); // 分配内存,使用GetStringMemory()分配
|
||||
}
|
||||
|
||||
// 重新分配内存
|
||||
inline char *gs_string::ReallocFunc(size_t _size)
|
||||
{
|
||||
m_capacity = _size;
|
||||
m_capacity = _size; // 更新容量
|
||||
|
||||
char *buff = AllocFunc(m_capacity);
|
||||
/* copy old data */
|
||||
char *buff = AllocFunc(m_capacity); // 分配新内存
|
||||
/* 复制旧数据 */
|
||||
if (m_buff != NULL) {
|
||||
errno_t ret = snprintf_s(buff, m_capacity, strlen(m_buff), "%s", m_buff);
|
||||
errno_t ret = snprintf_s(buff, m_capacity, strlen(m_buff), "%s", m_buff); // 复制旧数据
|
||||
securec_check_ss(ret, "\0", "\0");
|
||||
m_len = (size_t)ret;
|
||||
pfree(m_buff);
|
||||
m_len = (size_t)ret; // 更新字符串长度
|
||||
pfree(m_buff); // 释放旧内存
|
||||
}
|
||||
|
||||
m_buff = buff;
|
||||
m_buff = buff; // 更新缓冲区指针
|
||||
return m_buff;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,8 +126,12 @@ IPRange::~IPRange()
|
|||
void IPRange::net_ipv6_to_host_order(IPV6 *ip, const struct sockaddr_in6 *sa) const
|
||||
{
|
||||
IPV6 tmp_ip;
|
||||
|
||||
// 将 IPv6 地址从网络字节顺序转换为主机字节顺序
|
||||
int rc = memcpy_s(&(tmp_ip.ip_64), sizeof(tmp_ip.ip_64), &(sa->sin6_addr), sizeof(tmp_ip.ip_64));
|
||||
securec_check(rc, "\0", "\0");
|
||||
|
||||
// 使用 ntohl 函数将 32 位整数从网络字节顺序转换为主机字节顺序
|
||||
ip->ip_32.a = ntohl(tmp_ip.ip_32.d);
|
||||
ip->ip_32.b = ntohl(tmp_ip.ip_32.c);
|
||||
ip->ip_32.c = ntohl(tmp_ip.ip_32.b);
|
||||
|
|
@ -136,7 +140,10 @@ void IPRange::net_ipv6_to_host_order(IPV6 *ip, const struct sockaddr_in6 *sa) co
|
|||
|
||||
void IPRange::net_ipv4_to_host_order(IPV6 *ip, const struct in_addr *addr) const
|
||||
{
|
||||
// 使用 ntohl 函数将 IPv4 地址从网络字节顺序转换为主机字节顺序
|
||||
ip->ip_32.a = ntohl(addr->s_addr);
|
||||
|
||||
// 设置其他三个 32 位整数的值
|
||||
ip->ip_32.b = 0x0000FFFF;
|
||||
ip->ip_32.c = ip->ip_32.d = 0;
|
||||
}
|
||||
|
|
@ -146,15 +153,15 @@ bool IPRange::str_to_ip(const char* ip_str, IPV6 *ip)
|
|||
struct in_addr addr;
|
||||
struct sockaddr_in6 sa;
|
||||
|
||||
// 尝试将 IP 地址字符串解析为 IPv6 地址
|
||||
if (inet_pton(AF_INET6, ip_str, &sa.sin6_addr) > 0) {
|
||||
net_ipv6_to_host_order(ip, &sa);
|
||||
} else if (inet_pton(AF_INET, ip_str, &addr) > 0) {
|
||||
}
|
||||
// 如果解析为 IPv6 失败,则尝试解析为 IPv4 地址
|
||||
else if (inet_pton(AF_INET, ip_str, &addr) > 0) {
|
||||
net_ipv4_to_host_order(ip, &addr);
|
||||
} else {
|
||||
/*
|
||||
* Note that even the format keep the same as ipv6 or ipv4
|
||||
* still recognize it as invalid ip if ip exceed the valid range
|
||||
*/
|
||||
// 如果解析失败,设置错误消息并返回 false
|
||||
m_err_str = "invalid ip: " + std::string(ip_str);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -169,6 +176,7 @@ bool IPRange::mask_range(Range *range, unsigned short cidr)
|
|||
return false;
|
||||
}
|
||||
|
||||
// 使用预定义的掩码 LUT 更新 IPv4 范围
|
||||
range->from.ip_32.a &= MASK_FROM_LUT[cidr];
|
||||
range->to.ip_32.a |= MASK_TO_LUT[cidr];
|
||||
} else { /* ipv6 */
|
||||
|
|
@ -176,7 +184,7 @@ bool IPRange::mask_range(Range *range, unsigned short cidr)
|
|||
m_err_str = "invalid cidr for ipv6: " + cidr;
|
||||
return false;
|
||||
}
|
||||
unsigned short complement = cidr % 32; /* the result is less or equal to 31 */
|
||||
unsigned short complement = cidr % 32; /* 结果小于或等于 31 */
|
||||
|
||||
if (cidr > 96) {
|
||||
range->from.ip_32.a &= MASK_FROM_LUT[complement];
|
||||
|
|
@ -207,6 +215,7 @@ bool IPRange::mask_range(Range *range, unsigned short cidr)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* parse the ip with mask into range sturst , format is as below:
|
||||
* x.x.x.x|x, ptr is the postion of "|"
|
||||
|
|
@ -222,12 +231,17 @@ bool IPRange::parse_mask(const char* range, size_t range_len, const char *ptr, R
|
|||
char mask_ip_str[IP_MAX_LEN] = {0};
|
||||
size_t first_ip_str_len = ptr - range;
|
||||
size_t mask_ip_str_len = range_len - 1 - first_ip_str_len;
|
||||
/* copy the first ip */
|
||||
|
||||
// 复制第一个 IP 地址部分
|
||||
copy_without_spaces(first_ip_str, sizeof(first_ip_str), range, first_ip_str_len);
|
||||
/* get the other ip */
|
||||
|
||||
// 获取掩码 IP 地址部分
|
||||
copy_without_spaces(mask_ip_str, sizeof(mask_ip_str), ptr + 1, mask_ip_str_len);
|
||||
|
||||
IPV6 ip;
|
||||
IPV6 mask_ip;
|
||||
|
||||
// 将第一个 IP 地址和掩码 IP 地址解析为 IPV6 结构体
|
||||
if (!str_to_ip(first_ip_str, &ip)) {
|
||||
m_err_str = "failed to convert ip: " + std::string(first_ip_str);
|
||||
return false;
|
||||
|
|
@ -236,7 +250,9 @@ bool IPRange::parse_mask(const char* range, size_t range_len, const char *ptr, R
|
|||
m_err_str = "failed to convert mask ip: " + std::string(mask_ip_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mask_ip.ip_32.b == 0x0000FFFF) { /* ipv4 */
|
||||
// 更新 IPv4 范围
|
||||
new_range->from.ip_32.a = ip.ip_32.a & mask_ip.ip_32.a;
|
||||
new_range->from.ip_32.b = 0x0000FFFF;
|
||||
new_range->from.ip_64.upper = 0;
|
||||
|
|
@ -244,9 +260,11 @@ bool IPRange::parse_mask(const char* range, size_t range_len, const char *ptr, R
|
|||
new_range->to.ip_32.b = 0x0000FFFF;
|
||||
new_range->to.ip_64.upper = 0;
|
||||
} else {
|
||||
// 更新 IPv6 范围
|
||||
new_range->from = ip & mask_ip;
|
||||
new_range->to = ip | ~mask_ip;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -256,14 +274,20 @@ bool IPRange::parse_single(const char* range, size_t range_len, Range *new_range
|
|||
m_err_str = "the range string length is not valid: " + range_len;
|
||||
return false;
|
||||
}
|
||||
|
||||
char buf[IP_MAX_LEN] = {0};
|
||||
/* copy the ip part to buf */
|
||||
|
||||
// 复制 IP 地址部分
|
||||
copy_without_spaces(buf, sizeof(buf), range, range_len);
|
||||
|
||||
if (!str_to_ip(buf, &(new_range->from))) {
|
||||
m_err_str = "failed to convert ip: " + std::string(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置范围的结束 IP 为起始 IP
|
||||
new_range->to = new_range->from;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -273,18 +297,25 @@ bool IPRange::parse_slash(const char* range, size_t range_len, const char *ptr,
|
|||
m_err_str = "the range string length is not valid: " + range_len;
|
||||
return false;
|
||||
}
|
||||
|
||||
char buf[IP_MAX_LEN] = {0};
|
||||
size_t real_range_len = ptr - range;
|
||||
/* copy the ip part to buf */
|
||||
|
||||
// 复制 IP 地址部分
|
||||
copy_without_spaces(buf, sizeof(buf), range, real_range_len);
|
||||
/* get the CIDR */
|
||||
|
||||
// 获取 CIDR
|
||||
unsigned short cidr = (unsigned short)atoi(ptr + 1);
|
||||
|
||||
if (!str_to_ip(buf, &(new_range->from))) {
|
||||
m_err_str = "failed to convert ip: " + std::string(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置结束 IP 为起始 IP,并应用掩码
|
||||
new_range->to = new_range->from;
|
||||
(void)mask_range(new_range, cidr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -294,29 +325,36 @@ bool IPRange::parse_hyphen(const char* range, size_t range_len, const char *ptr,
|
|||
m_err_str = "the range string length is not valid: " + range_len;
|
||||
return false;
|
||||
}
|
||||
/* clean white spaces */
|
||||
|
||||
/* 清除空格 */
|
||||
char first_ip[IP_MAX_LEN] = {0};
|
||||
char second_ip[IP_MAX_LEN] = {0};
|
||||
|
||||
size_t first_ip_len = ptr - range;
|
||||
size_t second_ip_len = range_len - 1 - first_ip_len;
|
||||
/* copy the first ip */
|
||||
|
||||
/* 复制第一个 IP */
|
||||
copy_without_spaces(first_ip, sizeof(first_ip), range, first_ip_len);
|
||||
/* get the other ip */
|
||||
|
||||
/* 获取另一个 IP */
|
||||
copy_without_spaces(second_ip, sizeof(second_ip), ptr + 1, second_ip_len);
|
||||
|
||||
if (!str_to_ip(first_ip, &(new_range->from))) {
|
||||
m_err_str = "failed to parse ip: " + std::string(first_ip);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!str_to_ip(second_ip, &(new_range->to))) {
|
||||
m_err_str = "failed to parse ip: " + std::string(second_ip);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_range->from > new_range->to) {
|
||||
m_err_str =
|
||||
"the first ip (" + std::string(first_ip) + ") is bigger than the other (" + std::string(second_ip) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -324,31 +362,34 @@ void IPRange::handle_remove_intersection(Ranges_t *new_ranges, const Range *remo
|
|||
{
|
||||
IPV6 range_min = std::max(remove_range->from, exist_range->from);
|
||||
IPV6 range_max = std::min(remove_range->to, exist_range->to);
|
||||
|
||||
if (range_min > range_max) {
|
||||
/* no intersaction */
|
||||
/* 没有交集,将存在的范围添加到新范围列表中 */
|
||||
new_ranges->push_back(*exist_range);
|
||||
return;
|
||||
}
|
||||
/* there is an intersaction the remove_range includes the exist_range */
|
||||
|
||||
/* 存在交集 */
|
||||
if ((remove_range->from <= exist_range->from) && (remove_range->to >= exist_range->to)) {
|
||||
/* remove the exist range */
|
||||
/* 移除的范围包含了存在的范围,不添加任何范围 */
|
||||
return;
|
||||
}
|
||||
// example:
|
||||
// exist_range: 2 - 5
|
||||
// remove_range: 1 - 3
|
||||
// expected result: 4 - 5
|
||||
|
||||
if (remove_range->from <= exist_range->from) {
|
||||
/* 移除的范围起始于存在的范围的起始位置之前,更新存在的范围的起始位置 */
|
||||
exist_range->from = remove_range->to + 1;
|
||||
new_ranges->push_back(*exist_range);
|
||||
return;
|
||||
}
|
||||
|
||||
if (remove_range->to >= exist_range->to) {
|
||||
/* 移除的范围结束于存在的范围的结束位置之后,更新存在的范围的结束位置 */
|
||||
exist_range->to = remove_range->from - 1;
|
||||
new_ranges->push_back(*exist_range);
|
||||
return;
|
||||
}
|
||||
/* the remove range is inside the exist one */
|
||||
|
||||
/* 移除的范围在存在的范围内部,将更新后的存在的范围分为两个部分 */
|
||||
new_ranges->push_back({exist_range->from, remove_range->from - 1});
|
||||
new_ranges->push_back({remove_range->to + 1, exist_range->to});
|
||||
}
|
||||
|
|
@ -357,15 +398,19 @@ bool IPRange::handle_add_intersection(Range *new_range, const Range *exist_range
|
|||
{
|
||||
IPV6 range_min = std::max(new_range->from, exist_range->from);
|
||||
IPV6 range_max = std::min(new_range->to, exist_range->to);
|
||||
|
||||
if (range_min > range_max) {
|
||||
/* 没有交集 */
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* 存在交集,更新新范围以包含两个范围的合并 */
|
||||
new_range->from = std::min(new_range->from, exist_range->from);
|
||||
new_range->to = std::max(new_range->to, exist_range->to);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* parse the ip range support below format
|
||||
* single ip: 127.0.0.1
|
||||
|
|
@ -375,7 +420,7 @@ bool IPRange::handle_add_intersection(Range *new_range, const Range *exist_range
|
|||
*/
|
||||
bool IPRange::parse_range(const char *range, size_t range_len, Range *new_range)
|
||||
{
|
||||
/* handle format of "ip/cidr" */
|
||||
// 处理格式为 "ip/cidr"
|
||||
const char *ptr = (const char *)memchr(range, '/', range_len);
|
||||
if (ptr != NULL) {
|
||||
if (!parse_slash(range, range_len, ptr, new_range)) {
|
||||
|
|
@ -384,7 +429,7 @@ bool IPRange::parse_range(const char *range, size_t range_len, Range *new_range)
|
|||
}
|
||||
return true;
|
||||
}
|
||||
/* handle format of "ip from-ip to" */
|
||||
// 处理格式为 "ip from-ip to"
|
||||
ptr = (const char *)memchr(range, '-', range_len);
|
||||
if (ptr != NULL) {
|
||||
if (!parse_hyphen(range, range_len, ptr, new_range)) {
|
||||
|
|
@ -393,7 +438,7 @@ bool IPRange::parse_range(const char *range, size_t range_len, Range *new_range)
|
|||
}
|
||||
return true;
|
||||
}
|
||||
/* handle format of "ip | ip mask" */
|
||||
// 处理格式为 "ip | ip mask"
|
||||
ptr = (const char *)memchr(range, '|', range_len);
|
||||
if (ptr != NULL) {
|
||||
if (!parse_mask(range, range_len, ptr, new_range)) {
|
||||
|
|
@ -401,7 +446,7 @@ bool IPRange::parse_range(const char *range, size_t range_len, Range *new_range)
|
|||
return false;
|
||||
}
|
||||
return true;
|
||||
} else { /* handle single ip address as range */
|
||||
} else { // 处理单个 IP 地址作为范围
|
||||
if (!parse_single(range, range_len, new_range)) {
|
||||
m_err_str = "failed with parsing the range: " + std::string(range);
|
||||
return false;
|
||||
|
|
@ -412,102 +457,131 @@ bool IPRange::parse_range(const char *range, size_t range_len, Range *new_range)
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool IPRange::add_ranges(const std::unordered_set<std::string> ranges)
|
||||
{
|
||||
// 遍历传入的IP地址范围集合
|
||||
for (const std::string range : ranges) {
|
||||
// 调用add_range函数尝试添加每个IP地址范围
|
||||
if (!add_range(range.c_str(), range.length())) {
|
||||
// 如果添加失败,返回false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 所有IP地址范围添加成功,返回true
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPRange::remove_ranges(const std::unordered_set<std::string> ranges)
|
||||
{
|
||||
// 遍历传入的IP地址范围集合
|
||||
for (const std::string range : ranges) {
|
||||
// 调用remove_range函数尝试移除每个IP地址范围
|
||||
if (!remove_range(range.c_str(), range.length())) {
|
||||
// 如果移除失败,返回false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 所有IP地址范围移除成功,返回true
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPRange::add_range(Range *new_range)
|
||||
{
|
||||
/* adding the new range */
|
||||
/* 添加新范围 */
|
||||
if (m_ranges.size() == 0 || new_range->to < m_ranges[0].from) {
|
||||
// 如果范围为空或新范围的结束地址小于第一个范围的开始地址
|
||||
// 将新范围插入到范围列表的开头
|
||||
(void)m_ranges.insert(m_ranges.begin(), *new_range);
|
||||
return true;
|
||||
} else if (new_range->from > m_ranges.back().to) {
|
||||
// 如果新范围的开始地址大于范围列表中最后一个范围的结束地址
|
||||
// 将新范围追加到范围列表的末尾
|
||||
m_ranges.push_back(*new_range);
|
||||
return true;
|
||||
}
|
||||
Ranges_t new_ranges;
|
||||
bool we_had_intersection = false;
|
||||
uint32_t i = 0;
|
||||
/* interate over the ranges and check for intersection or the place to add the new range */
|
||||
/* 遍历范围并检查交叉或添加新范围的位置 */
|
||||
while (i < m_ranges.size()) {
|
||||
/* in case of intersaction update the new range */
|
||||
/* 在交叉的情况下更新新范围 */
|
||||
if (handle_add_intersection(new_range, &m_ranges[i])) {
|
||||
we_had_intersection = true;
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
/* just insert the intersaction */
|
||||
/* 只插入交叉部分 */
|
||||
if (we_had_intersection) {
|
||||
we_had_intersection = false;
|
||||
new_ranges.push_back(*new_range);
|
||||
break;
|
||||
} else if (new_range->to < m_ranges[i].from) {
|
||||
/* we got the plcae to put the new range */
|
||||
/* 找到插入新范围的位置 */
|
||||
new_ranges.push_back(*new_range);
|
||||
break;
|
||||
}
|
||||
/* just add the old range */
|
||||
/* 只添加旧范围 */
|
||||
new_ranges.push_back(m_ranges[i]);
|
||||
++i;
|
||||
}
|
||||
/* if the intersection was until the end of the list - add it now */
|
||||
/* 如果交叉一直到列表的末尾 - 现在添加它 */
|
||||
if (we_had_intersection) {
|
||||
new_ranges.push_back(*new_range);
|
||||
}
|
||||
/* copy the rest of the list if exist */
|
||||
/* 复制剩余的列表(如果存在) */
|
||||
while (i < m_ranges.size()) {
|
||||
new_ranges.push_back(m_ranges[i]);
|
||||
++i;
|
||||
}
|
||||
// 用新的范围列表替换旧的范围列表
|
||||
m_ranges.swap(new_ranges);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPRange::is_range_valid(const std::string range)
|
||||
{
|
||||
// 创建临时IPRange对象
|
||||
IPRange tmp;
|
||||
// 创建新的范围对象
|
||||
Range new_range;
|
||||
// 调用parse_range函数来解析传入的IP地址范围,并将结果存储在new_range中
|
||||
return tmp.parse_range(range.c_str(), range.size(), &new_range);
|
||||
}
|
||||
|
||||
bool IPRange::add_range(const char* range, size_t range_len)
|
||||
{
|
||||
// 创建新的范围对象
|
||||
Range new_range;
|
||||
// 清除错误字符串
|
||||
m_err_str.clear();
|
||||
// 调用parse_range函数来解析传入的IP地址范围,并将结果存储在new_range中
|
||||
if (!parse_range(range, range_len, &new_range)) {
|
||||
// 如果解析失败,返回false
|
||||
return false;
|
||||
}
|
||||
// 调用add_range(&new_range)来添加新的范围
|
||||
return add_range(&new_range);
|
||||
}
|
||||
|
||||
bool IPRange::remove_range(const char *range, size_t range_len)
|
||||
{
|
||||
// 创建新的范围列表
|
||||
Ranges_t new_ranges;
|
||||
// 创建要移除的范围对象
|
||||
Range remove_range;
|
||||
// 清除错误字符串
|
||||
m_err_str.clear();
|
||||
// 调用parse_range函数来解析传入的IP地址范围,并将结果存储在remove_range中
|
||||
if (!parse_range(range, range_len, &remove_range)) {
|
||||
// 如果解析失败,返回false
|
||||
return false;
|
||||
}
|
||||
// 遍历当前范围列表,处理交叉情况,将不交叉的范围添加到新列表中
|
||||
for (Range exist_range : m_ranges) {
|
||||
handle_remove_intersection(&new_ranges, &remove_range, &exist_range);
|
||||
}
|
||||
// 用新的范围列表替换旧的范围列表
|
||||
m_ranges.swap(new_ranges);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -515,11 +589,13 @@ bool IPRange::remove_range(const char *range, size_t range_len)
|
|||
std::string IPRange::ip_to_str(const IPV6 *ip) const
|
||||
{
|
||||
char ip_str[INET6_ADDRSTRLEN];
|
||||
/* now get it back and print it */
|
||||
/* 获取IP地址字符串表示 */
|
||||
if (IPRANGE_IS_IPV4(*ip)) {
|
||||
// 如果是IPv4地址,将其转换为字符串表示
|
||||
uint32_t tmp = htonl(ip->ip_32.a);
|
||||
(void)inet_ntop(AF_INET, &tmp, ip_str, INET_ADDRSTRLEN);
|
||||
} else {
|
||||
// 如果是IPv6地址,将其转换为字符串表示
|
||||
IPV6 tmp_ip = *ip;
|
||||
tmp_ip.ip_32.a = htonl(ip->ip_32.d);
|
||||
tmp_ip.ip_32.b = htonl(ip->ip_32.c);
|
||||
|
|
@ -527,18 +603,20 @@ std::string IPRange::ip_to_str(const IPV6 *ip) const
|
|||
tmp_ip.ip_32.d = htonl(ip->ip_32.a);
|
||||
(void)inet_ntop(AF_INET6, &tmp_ip, ip_str, INET6_ADDRSTRLEN);
|
||||
}
|
||||
// 返回IP地址的字符串表示
|
||||
return std::string(ip_str);
|
||||
}
|
||||
|
||||
bool IPRange::binary_search(const IPV6 ip) const
|
||||
{
|
||||
/* do a binary search */
|
||||
/* 进行二进制搜索 */
|
||||
size_t i = 0;
|
||||
size_t j = m_ranges.size() - 1;
|
||||
size_t mid = 0;
|
||||
while (i != j) {
|
||||
mid = (i + j) / 2;
|
||||
if (ip >= m_ranges[mid].from && ip <= m_ranges[mid].to) {
|
||||
// 如果IP地址在范围中,返回true
|
||||
return true;
|
||||
}
|
||||
if (ip < m_ranges[mid].from) {
|
||||
|
|
@ -547,24 +625,29 @@ bool IPRange::binary_search(const IPV6 ip) const
|
|||
i = mid + 1;
|
||||
}
|
||||
}
|
||||
// 检查最后一个范围
|
||||
return (ip >= m_ranges[i].from && ip <= m_ranges[i].to);
|
||||
}
|
||||
|
||||
bool IPRange::is_intersect(const IPRange *arg)
|
||||
{
|
||||
// 检查当前IP范围与另一个IP范围是否相交
|
||||
for (size_t i = 0; i < m_ranges.size(); ++i) {
|
||||
Range tmp(m_ranges[i].from, m_ranges[i].to);
|
||||
for (size_t j = 0 ; j < arg->m_ranges.size(); ++j) {
|
||||
if (handle_add_intersection(&tmp, &arg->m_ranges[j])) {
|
||||
// 如果有交集,返回true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 没有交集,返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IPRange::is_in_range(const char *ip_str)
|
||||
{
|
||||
// 将IP地址字符串转换为IPv6对象并检查是否在范围内
|
||||
IPV6 ip;
|
||||
if (!str_to_ip(ip_str, &ip)) {
|
||||
return false;
|
||||
|
|
@ -574,19 +657,23 @@ bool IPRange::is_in_range(const char *ip_str)
|
|||
|
||||
bool IPRange::is_in_range(const IPV6 *ip)
|
||||
{
|
||||
// 检查IPv6地址是否在范围内
|
||||
if (m_ranges.size() == 0) {
|
||||
m_err_str = "there are no ranges in this object";
|
||||
return false;
|
||||
}
|
||||
m_err_str.clear();
|
||||
if (*ip == localhost_ipv4 || *ip == localhost_ipv6) {
|
||||
// 如果是本地主机地址,检查是否包含在范围内
|
||||
return binary_search(localhost_ipv4) || binary_search(localhost_ipv6);
|
||||
}
|
||||
// 检查IPv6地址是否在范围内
|
||||
return binary_search(*ip);
|
||||
}
|
||||
|
||||
bool IPRange::is_in_range(const uint32_t ipv4)
|
||||
{
|
||||
// 将IPv4地址转换为IPv6对象并检查是否在范围内
|
||||
IPV6 ip;
|
||||
net_ipv4_to_host_order(&ip, (struct in_addr*)&ipv4);
|
||||
return is_in_range(&ip);
|
||||
|
|
@ -594,6 +681,7 @@ bool IPRange::is_in_range(const uint32_t ipv4)
|
|||
|
||||
std::unordered_set<std::string> IPRange::get_ranges_set()
|
||||
{
|
||||
// 返回范围的字符串表示形式的无序集合
|
||||
std::unordered_set<std::string> rslt;
|
||||
for (Range range : m_ranges) {
|
||||
if (ip_to_str(&range.from).compare(ip_to_str(&range.to)) == 0) {
|
||||
|
|
@ -607,6 +695,7 @@ std::unordered_set<std::string> IPRange::get_ranges_set()
|
|||
|
||||
void IPRange::copy_without_spaces(char buf[], size_t buf_len, const char *original, size_t original_len) const
|
||||
{
|
||||
// 从原始字符串中复制字符,忽略空格
|
||||
if (original_len == 0 || original_len > buf_len) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -621,6 +710,8 @@ void IPRange::copy_without_spaces(char buf[], size_t buf_len, const char *origin
|
|||
|
||||
bool IPRange::empty() const
|
||||
{
|
||||
// 检查IP范围是否为空
|
||||
return m_ranges.empty();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue