这篇文章主要介绍PostgreSQL如何构建表达式解析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
EEO_XXX宏定义
opcode分发器宏定义
/*
* Macros for opcode dispatch.
* opcode分发器宏定义
*
* EEO_SWITCH - just hides the switch if not in use.
* EEO_SWITCH - 如未使用,则隐藏switch
*
* EEO_CASE - labels the implementation of named expression step type.
* EEO_CASE - 标签化已命名的表达式步骤类型的实现
*
* EEO_DISPATCH - jump to the implementation of the step type for 'op'.
* EEO_DISPATCH - 跳到'op'指定的步骤类型的实现
*
* EEO_OPCODE - compute opcode required by used expression evaluation method.
* - 通过请求的表达式解析方法计算opcode
*
* EEO_NEXT - increment 'op' and jump to correct next step type.
* - 'op'++并跳转到下一个步骤类型
*
* EEO_JUMP - jump to the specified step number within the current expression.
* EEO_JUMP - 在当前表达式中跳转到指定的步骤编号
*/
#if defined(EEO_USE_COMPUTED_GOTO)
//--------------- 定义了EEO_USE_COMPUTED_GOTO
/* struct for jump target -> opcode lookup table */
//跳转target -> opcode搜索表结构体
typedef struct ExprEvalOpLookup
{
const void *opcode;
ExprEvalOp op;
} ExprEvalOpLookup;
/* to make dispatch_table accessible outside ExecInterpExpr() */
static const void **dispatch_table = NULL;
/* jump target -> opcode lookup table */
static ExprEvalOpLookup reverse_dispatch_table[EEOP_LAST];
#define EEO_SWITCH()
#define EEO_CASE(name) CASE_##name:
#define EEO_DISPATCH() goto *((void *) op->opcode)
#define EEO_OPCODE(opcode) ((intptr_t) dispatch_table[opcode])
#else /* !EEO_USE_COMPUTED_GOTO */
//--------------- 没有定义EEO_USE_COMPUTED_GOTO
#define EEO_SWITCH() starteval: switch ((ExprEvalOp) op->opcode)
#define EEO_CASE(name) case name:
#define EEO_DISPATCH() goto starteval
#define EEO_OPCODE(opcode) (opcode)
#endif /* EEO_USE_COMPUTED_GOTO */
#define EEO_NEXT() \
do { \
op++; \
EEO_DISPATCH(); \
} while (0)
#define EEO_JUMP(stepno) \
do { \
op = &state->steps[stepno]; \
EEO_DISPATCH(); \
} while (0)
ExprState
解析表达式中运行期状态节点
/* Bits in ExprState->flags (see also execExpr.h for private flag bits): */
/* expression is for use with ExecQual() */
#define EEO_FLAG_IS_QUAL (1 << 0)
typedef struct ExprState
{
//节点tag
Node tag;
//EEO_FLAG_IS_QUAL
uint8 flags; /* bitmask of EEO_FLAG_* bits, see above */
/*
* Storage for result value of a scalar expression, or for individual
* column results within expressions built by ExecBuildProjectionInfo().
* 存储scalar expression表达式
* 和通过ExecBuildProjectionInfo()函数创建的expressions单列的结果.
*/
#define FIELDNO_EXPRSTATE_RESNULL 2
bool resnull;
#define FIELDNO_EXPRSTATE_RESVALUE 3
Datum resvalue;
/*
* If projecting a tuple result, this slot holds the result; else NULL.
* 如果投影元组结果,该slot存储结果,或者为NULL.
*/
#define FIELDNO_EXPRSTATE_RESULTSLOT 4
TupleTableSlot *resultslot;
/*
* Instructions to compute expression's return value.
* 计算表达式返回结果的基础"架构"
*/
struct ExprEvalStep *steps;
/*
* Function that actually evaluates the expression. This can be set to
* different values depending on the complexity of the expression.
* 实际解析表达式的函数.
* 根据表达式的复杂程度,可以设置为不同的值.
*/
ExprStateEvalFunc evalfunc;
/* original expression tree, for debugging only */
//原始的表达式树,仅用于debugging
Expr *expr;
/* private state for an evalfunc */
//evalfunc的私有状态
void *evalfunc_private;
/*
* XXX: following fields only needed during "compilation" (ExecInitExpr);
* could be thrown away afterwards.
* XXX: 接下来的字段在"compilation" (ExecInitExpr)期间需要,之后可被"扔掉".
*/
//当前的步数
int steps_len; /* number of steps currently */
//steps数组已分配的长度
int steps_alloc; /* allocated length of steps array */
//父PlanState节点(如存在)
struct PlanState *parent; /* parent PlanState node, if any */
//用于编译PARAM_EXTERN节点
ParamListInfo ext_params; /* for compiling PARAM_EXTERN nodes */
//
Datum *innermost_caseval;
bool *innermost_casenull;
Datum *innermost_domainval;
bool *innermost_domainnull;
} ExprState;
ExprEvalStep
表达式解析步骤结构体
typedef struct ExprEvalStep
{
/*
* Instruction to be executed. During instruction preparation this is an
* enum ExprEvalOp, but later it can be changed to some other type, e.g. a
* pointer for computed goto (that's why it's an intptr_t).
* 待执行指令.
* 在指令准备期间这是枚举型的ExprEvalOp,
* 但后续会被改变为某些其他类型,比如用于goto的指针,因此被定义为intprt_t类型
*/
intptr_t opcode;
/* where to store the result of this step */
//存储该步骤的结果
Datum *resvalue;
bool *resnull;
/*
* Inline data for the operation. Inline data is faster to access, but
* also bloats the size of all instructions. The union should be kept to
* no more than 40 bytes on 64-bit systems (so that the entire struct is
* no more than 64 bytes, a single cacheline on common systems).
* 操作的内联数据.
* 内联数据用于更快的访问,但同时会导致指令的盘膨胀.
* 联合体在64-bit系统上应保持在40字节范围内
* (因此整个结构体不应大于64字节,普通系统上的单个缓存线大小)
*/
union
{
/* for EEOP_INNER/OUTER/SCAN_FETCHSOME */
//用于EEOP_INNER/OUTER/SCAN_FETCHSOME
struct
{
/* attribute number up to which to fetch (inclusive) */
//获取到的属性编号
int last_var;
TupleDesc known_desc;
} fetch;
/* for EEOP_INNER/OUTER/SCAN_[SYS]VAR[_FIRST] */
struct
{
/* attnum is attr number - 1 for regular VAR ... */
//attnum是常规VAR的attr number - 1
/* but it's just the normal (negative) attr number for SYSVAR */
//对于SYSVAR,该值是常规的attr number
int attnum;
Oid vartype; /* type OID of variable */
} var;
/* for EEOP_WHOLEROW */
struct
{
Var *var; /* original Var node in plan tree */
bool first; /* first time through, need to initialize? */
bool slow; /* need runtime check for nulls? */
TupleDesc tupdesc; /* descriptor for resulting tuples */
JunkFilter *junkFilter; /* JunkFilter to remove resjunk cols */
} wholerow;
/* for EEOP_ASSIGN_*_VAR */
struct
{
/* target index in ExprState->resultslot->tts_values/nulls */
int resultnum;
/* source attribute number - 1 */
int attnum;
} assign_var;
/* for EEOP_ASSIGN_TMP[_MAKE_RO] */
struct
{
/* target index in ExprState->resultslot->tts_values/nulls */
int resultnum;
} assign_tmp;
/* for EEOP_CONST */
struct
{
/* constant's value */
Datum value;
bool isnull;
} constval;
/* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */
//对于EEOP_FUNCEXPR_* / NULLIF / DISTINCT
struct
{
//函数的检索数据
FmgrInfo *finfo; /* function's lookup data */
//参数信息等
FunctionCallInfo fcinfo_data; /* arguments etc */
/* faster to access without additional indirection: */
//无需额外的指向,更快速的访问
PGFunction fn_addr; /* actual call address */
int nargs; /* number of arguments */
} func;
/* for EEOP_BOOL_*_STEP */
struct
{
bool *anynull; /* track if any input was NULL */
int jumpdone; /* jump here if result determined */
} boolexpr;
/* for EEOP_QUAL */
struct
{
int jumpdone; /* jump here on false or null */
} qualexpr;
/* for EEOP_JUMP[_CONDITION] */
struct
{
int jumpdone; /* target instruction's index */
} jump;
/* for EEOP_NULLTEST_ROWIS[NOT]NULL */
struct
{
/* cached tupdesc pointer - filled at runtime */
TupleDesc argdesc;
} nulltest_row;
/* for EEOP_PARAM_EXEC/EXTERN */
struct
{
int paramid; /* numeric ID for parameter */
Oid paramtype; /* OID of parameter's datatype */
} param;
/* for EEOP_PARAM_CALLBACK */
struct
{
ExecEvalSubroutine paramfunc; /* add-on evaluation subroutine */
void *paramarg; /* private data for same */
int paramid; /* numeric ID for parameter */
Oid paramtype; /* OID of parameter's datatype */
} cparam;
/* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */
struct
{
Datum *value; /* value to return */
bool *isnull;
} casetest;
/* for EEOP_MAKE_READONLY */
struct
{
Datum *value; /* value to coerce to read-only */
bool *isnull;
} make_readonly;
/* for EEOP_IOCOERCE */
struct
{
/* lookup and call info for source type's output function */
FmgrInfo *finfo_out;
FunctionCallInfo fcinfo_data_out;
/* lookup and call info for result type's input function */
FmgrInfo *finfo_in;
FunctionCallInfo fcinfo_data_in;
} iocoerce;
/* for EEOP_SQLVALUEFUNCTION */
struct
{
SQLValueFunction *svf;
} sqlvaluefunction;
/* for EEOP_NEXTVALUEEXPR */
//EEOP_NEXTVALUEEXPR
struct
{
Oid seqid;
Oid seqtypid;
} nextvalueexpr;
/* for EEOP_ARRAYEXPR */
struct
{
Datum *elemvalues; /* element values get stored here */
bool *elemnulls;
int nelems; /* length of the above arrays */
Oid elemtype; /* array element type */
int16 elemlength; /* typlen of the array element type */
bool elembyval; /* is the element type pass-by-value? */
char elemalign; /* typalign of the element type */
bool multidims; /* is array expression multi-D? */
} arrayexpr;
/* for EEOP_ARRAYCOERCE */
struct
{
ExprState *elemexprstate; /* null if no per-element work */
Oid resultelemtype; /* element type of result array */
struct ArrayMapState *amstate; /* workspace for array_map */
} arraycoerce;
/* for EEOP_ROW */
struct
{
TupleDesc tupdesc; /* descriptor for result tuples */
/* workspace for the values constituting the row: */
Datum *elemvalues;
bool *elemnulls;
} row;
/* for EEOP_ROWCOMPARE_STEP */
struct
{
/* lookup and call data for column comparison function */
FmgrInfo *finfo;
FunctionCallInfo fcinfo_data;
PGFunction fn_addr;
/* target for comparison resulting in NULL */
int jumpnull;
/* target for comparison yielding inequality */
int jumpdone;
} rowcompare_step;
/* for EEOP_ROWCOMPARE_FINAL */
struct
{
RowCompareType rctype;
} rowcompare_final;
/* for EEOP_MINMAX */
struct
{
/* workspace for argument values */
Datum *values;
bool *nulls;
int nelems;
/* is it GREATEST or LEAST? */
MinMaxOp op;
/* lookup and call data for comparison function */
FmgrInfo *finfo;
FunctionCallInfo fcinfo_data;
} minmax;
/* for EEOP_FIELDSELECT */
struct
{
AttrNumber fieldnum; /* field number to extract */
Oid resulttype; /* field's type */
/* cached tupdesc pointer - filled at runtime */
TupleDesc argdesc;
} fieldselect;
/* for EEOP_FIELDSTORE_DEFORM / FIELDSTORE_FORM */
struct
{
/* original expression node */
FieldStore *fstore;
/* cached tupdesc pointer - filled at runtime */
/* note that a DEFORM and FORM pair share the same tupdesc */
TupleDesc *argdesc;
/* workspace for column values */
Datum *values;
bool *nulls;
int ncolumns;
} fieldstore;
/* for EEOP_ARRAYREF_SUBSCRIPT */
struct
{
/* too big to have inline */
struct ArrayRefState *state;
int off; /* 0-based index of this subscript */
bool isupper; /* is it upper or lower subscript? */
int jumpdone; /* jump here on null */
} arrayref_subscript;
/* for EEOP_ARRAYREF_OLD / ASSIGN / FETCH */
struct
{
/* too big to have inline */
struct ArrayRefState *state;
} arrayref;
/* for EEOP_DOMAIN_NOTNULL / DOMAIN_CHECK */
struct
{
/* name of constraint */
char *constraintname;
/* where the result of a CHECK constraint will be stored */
Datum *checkvalue;
bool *checknull;
/* OID of domain type */
Oid resulttype;
} domaincheck;
/* for EEOP_CONVERT_ROWTYPE */
struct
{
ConvertRowtypeExpr *convert; /* original expression */
/* these three fields are filled at runtime: */
TupleDesc indesc; /* tupdesc for input type */
TupleDesc outdesc; /* tupdesc for output type */
TupleConversionMap *map; /* column mapping */
bool initialized; /* initialized for current types? */
} convert_rowtype;
/* for EEOP_SCALARARRAYOP */
struct
{
/* element_type/typlen/typbyval/typalign are filled at runtime */
Oid element_type; /* InvalidOid if not yet filled */
bool useOr; /* use OR or AND semantics? */
int16 typlen; /* array element type storage info */
bool typbyval;
char typalign;
FmgrInfo *finfo; /* function's lookup data */
FunctionCallInfo fcinfo_data; /* arguments etc */
/* faster to access without additional indirection: */
PGFunction fn_addr; /* actual call address */
} scalararrayop;
/* for EEOP_XMLEXPR */
struct
{
XmlExpr *xexpr; /* original expression node */
/* workspace for evaluating named args, if any */
Datum *named_argvalue;
bool *named_argnull;
/* workspace for evaluating unnamed args, if any */
Datum *argvalue;
bool *argnull;
} xmlexpr;
/* for EEOP_AGGREF */
struct
{
/* out-of-line state, modified by nodeAgg.c */
AggrefExprState *astate;
} aggref;
/* for EEOP_GROUPING_FUNC */
struct
{
AggState *parent; /* parent Agg */
List *clauses; /* integer list of column numbers */
} grouping_func;
/* for EEOP_WINDOW_FUNC */
struct
{
/* out-of-line state, modified by nodeWindowFunc.c */
WindowFuncExprState *wfstate;
} window_func;
/* for EEOP_SUBPLAN */
struct
{
/* out-of-line state, created by nodeSubplan.c */
SubPlanState *sstate;
} subplan;
/* for EEOP_ALTERNATIVE_SUBPLAN */
struct
{
/* out-of-line state, created by nodeSubplan.c */
AlternativeSubPlanState *asstate;
} alternative_subplan;
/* for EEOP_AGG_*DESERIALIZE */
struct
{
AggState *aggstate;
FunctionCallInfo fcinfo_data;
int jumpnull;
} agg_deserialize;
/* for EEOP_AGG_STRICT_INPUT_CHECK */
struct
{
bool *nulls;
int nargs;
int jumpnull;
} agg_strict_input_check;
/* for EEOP_AGG_INIT_TRANS */
struct
{
AggState *aggstate;
AggStatePerTrans pertrans;
ExprContext *aggcontext;
int setno;
int transno;
int setoff;
int jumpnull;
} agg_init_trans;
/* for EEOP_AGG_STRICT_TRANS_CHECK */
struct
{
AggState *aggstate;
int setno;
int transno;
int setoff;
int jumpnull;
} agg_strict_trans_check;
/* for EEOP_AGG_{PLAIN,ORDERED}_TRANS* */
struct
{
AggState *aggstate;
AggStatePerTrans pertrans;
ExprContext *aggcontext;
int setno;
int transno;
int setoff;
} agg_trans;
} d;
} ExprEvalStep;
ExprEvalOp
ExprEvalSteps的鉴频器,定义哪个操作将被执行并且联合体ExprEvalStep->d中的哪个struct将被使用.
/*
* Discriminator for ExprEvalSteps.
* ExprEvalSteps的鉴频器
*
* Identifies the operation to be executed and which member in the
* ExprEvalStep->d union is valid.
* 定义哪个操作将被执行并且联合体ExprEvalStep->d中的哪个struct将被使用.
*
* The order of entries needs to be kept in sync with the dispatch_table[]
* array in execExprInterp.c:ExecInterpExpr().
* 条目的排序需要与execExprInterp.c:ExecInterpExpr()中dispatch_table[]数组的元素保持一致
*/
typedef enum ExprEvalOp
{
/* entire expression has been evaluated completely, return */
//整个表达式已被解析,返回
EEOP_DONE,
/* apply slot_getsomeattrs on corresponding tuple slot */
//在相应的元组slot上应用了slot_getsomeattrs方法
EEOP_INNER_FETCHSOME,
EEOP_OUTER_FETCHSOME,
EEOP_SCAN_FETCHSOME,
/* compute non-system Var value */
//计算非系统Var变量值
EEOP_INNER_VAR,
EEOP_OUTER_VAR,
EEOP_SCAN_VAR,
/* compute system Var value */
//计算系统Var变量值
EEOP_INNER_SYSVAR,
EEOP_OUTER_SYSVAR,
EEOP_SCAN_SYSVAR,
/* compute wholerow Var */
//计算整行Var
EEOP_WHOLEROW,
/*
* Compute non-system Var value, assign it into ExprState's resultslot.
* These are not used if a CheckVarSlotCompatibility() check would be
* needed.
* 计算非系统Var值,分配到ExprState's的resultslot字段中.
* 如果CheckVarSlotCompatibility()需要时,这些都不需要.
*/
EEOP_ASSIGN_INNER_VAR,
EEOP_ASSIGN_OUTER_VAR,
EEOP_ASSIGN_SCAN_VAR,
/* assign ExprState's resvalue/resnull to a column of its resultslot */
//分配ExprState's resvalue/resnull到该列的resultslot中
EEOP_ASSIGN_TMP,
/* ditto, applying MakeExpandedObjectReadOnly() */
//同上,应用MakeExpandedObjectReadOnly()
EEOP_ASSIGN_TMP_MAKE_RO,
/* evaluate Const value */
//解析常量值
EEOP_CONST,
/*
* Evaluate function call (including OpExprs etc). For speed, we
* distinguish in the opcode whether the function is strict and/or
* requires usage stats tracking.
* 解析函数调用(包括OpExprs等等).
* 出于性能的考虑,需要区分opcode是strict函数还是非strict函数,以及是否需要统计跟踪.
*/
EEOP_FUNCEXPR,
EEOP_FUNCEXPR_STRICT,
EEOP_FUNCEXPR_FUSAGE,
EEOP_FUNCEXPR_STRICT_FUSAGE,
/*
* Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
* subexpressions are special-cased for performance. Since AND always has
* at least two subexpressions, FIRST and LAST never apply to the same
* subexpression.
* 解析布尔AND表达式,每一个子表达式一个步骤.
* FIRST/LAST子表达式是性能上的特例.
* 由于AND通常至少有两个子表达式,FIRST和LAST永远都不会应用在同一个子表达式上.
*/
EEOP_BOOL_AND_STEP_FIRST,
EEOP_BOOL_AND_STEP,
EEOP_BOOL_AND_STEP_LAST,
/* similarly for boolean OR expression */
//与布尔OR表达式类似
EEOP_BOOL_OR_STEP_FIRST,
EEOP_BOOL_OR_STEP,
EEOP_BOOL_OR_STEP_LAST,
/* evaluate boolean NOT expression */
//解析布尔NOT表达式
EEOP_BOOL_NOT_STEP,
/* simplified version of BOOL_AND_STEP for use by ExecQual() */
//用于ExecQual()中的BOOL_AND_STEP简化版本
EEOP_QUAL,
/* unconditional jump to another step */
//无条件跳转到另外一个步骤
EEOP_JUMP,
/* conditional jumps based on current result value */
//基于当前结果值的条件跳转
EEOP_JUMP_IF_NULL,
EEOP_JUMP_IF_NOT_NULL,
EEOP_JUMP_IF_NOT_TRUE,
/* perform NULL tests for scalar values */
//为scalar值执行NULL测试
EEOP_NULLTEST_ISNULL,
EEOP_NULLTEST_ISNOTNULL,
/* perform NULL tests for row values */
//为行值执行NULL测试
EEOP_NULLTEST_ROWISNULL,
EEOP_NULLTEST_ROWISNOTNULL,
/* evaluate a BooleanTest expression */
//解析BooleanTest表达式
EEOP_BOOLTEST_IS_TRUE,
EEOP_BOOLTEST_IS_NOT_TRUE,
EEOP_BOOLTEST_IS_FALSE,
EEOP_BOOLTEST_IS_NOT_FALSE,
/* evaluate PARAM_EXEC/EXTERN parameters */
//解析PARAM_EXEC/EXTERN参数
EEOP_PARAM_EXEC,
EEOP_PARAM_EXTERN,
EEOP_PARAM_CALLBACK,
/* return CaseTestExpr value */
//返回CaseTestExpr值
EEOP_CASE_TESTVAL,
/* apply MakeExpandedObjectReadOnly() to target value */
//对目标值应用MakeExpandedObjectReadOnly()
EEOP_MAKE_READONLY,
/* evaluate assorted special-purpose expression types */
//解析各种特殊用途的表达式类型
EEOP_IOCOERCE,
EEOP_DISTINCT,
EEOP_NOT_DISTINCT,
EEOP_NULLIF,
EEOP_SQLVALUEFUNCTION,
EEOP_CURRENTOFEXPR,
EEOP_NEXTVALUEEXPR,
EEOP_ARRAYEXPR,
EEOP_ARRAYCOERCE,
EEOP_ROW,
/*
* Compare two individual elements of each of two compared ROW()
* expressions. Skip to ROWCOMPARE_FINAL if elements are not equal.
* 给出两个需要对比的ROW()表达式,两两比较行中的元素.
* 如果元素不相等,则跳转到ROWCOMPARE_FINAL
*/
EEOP_ROWCOMPARE_STEP,
/* evaluate boolean value based on previous ROWCOMPARE_STEP operations */
//基于上一步的ROWCOMPARE_STEP操作解析布尔值
EEOP_ROWCOMPARE_FINAL,
/* evaluate GREATEST() or LEAST() */
//解析GREATEST()和LEAST()
EEOP_MINMAX,
/* evaluate FieldSelect expression */
//解析FieldSelect表达式
EEOP_FIELDSELECT,
/*
* Deform tuple before evaluating new values for individual fields in a
* FieldStore expression.
* 在解析FieldStore表达式中的独立列新值前重构元组
*/
EEOP_FIELDSTORE_DEFORM,
/*
* Form the new tuple for a FieldStore expression. Individual fields will
* have been evaluated into columns of the tuple deformed by the preceding
* DEFORM step.
* 为FieldStore表达式构成新元组.
* 单独的字段会解析到元组的列中(行已被上一个步骤EEOP_FIELDSTORE_DEFORM析构)
*/
EEOP_FIELDSTORE_FORM,
/* Process an array subscript; short-circuit expression to NULL if NULL */
//处理数组子脚本.如为NULL则短路表达式为NULL
EEOP_ARRAYREF_SUBSCRIPT,
/*
* Compute old array element/slice when an ArrayRef assignment expression
* contains ArrayRef/FieldStore subexpressions. Value is accessed using
* the CaseTest mechanism.
* 在ArrayRef分配表达式包含ArrayRef/FieldStore子表达式时计算旧的数组元素/片.
* 通过CaseTest机制访问Value
*/
EEOP_ARRAYREF_OLD,
/* compute new value for ArrayRef assignment expression */
//为ArrayRef分配118
EEOP_ARRAYREF_ASSIGN,
/* compute element/slice for ArrayRef fetch expression */
//为ArrayRef提取表达式计算element/slice
EEOP_ARRAYREF_FETCH,
/* evaluate value for CoerceToDomainValue */
//为CoerceToDomainValue解析值
EEOP_DOMAIN_TESTVAL,
/* evaluate a domain's NOT NULL constraint */
//解析域 NOT NULL 约束
EEOP_DOMAIN_NOTNULL,
/* evaluate a single domain CHECK constraint */
//解析单个域CHECK约束
EEOP_DOMAIN_CHECK,
/* evaluate assorted special-purpose expression types */
//解析特殊目的的表达式类型
EEOP_CONVERT_ROWTYPE,
EEOP_SCALARARRAYOP,
EEOP_XMLEXPR,
EEOP_AGGREF,
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
EEOP_ALTERNATIVE_SUBPLAN,
/* aggregation related nodes */
//聚合相关节点
EEOP_AGG_STRICT_DESERIALIZE,
EEOP_AGG_DESERIALIZE,
EEOP_AGG_STRICT_INPUT_CHECK,
EEOP_AGG_INIT_TRANS,
EEOP_AGG_STRICT_TRANS_CHECK,
EEOP_AGG_PLAIN_TRANS_BYVAL,
EEOP_AGG_PLAIN_TRANS,
EEOP_AGG_ORDERED_TRANS_DATUM,
EEOP_AGG_ORDERED_TRANS_TUPLE,
/* non-existent operation, used e.g. to check array lengths */
//不存在的操作,比如用于检测数组长度
EEOP_LAST
} ExprEvalOp;
ExecInitIndexScan
初始化Index Scan运行期状态信息,调用ExecAssignScanProjectionInfo->…->ExecBuildProjectionInfo函数构建投影信息.
/* ----------------------------------------------------------------
* ExecInitIndexScan
*
* Initializes the index scan's state information, creates
* scan keys, and opens the base and index relations.
*
* Note: index scans have 2 sets of state information because
* we have to keep track of the base relation and the
* index relation.
* ----------------------------------------------------------------
*/
IndexScanState *
ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
{
...
/*
* Initialize result slot, type and projection.
* 初始化结果slot,类型和投影
*/
ExecInitResultTupleSlotTL(estate, &indexstate->ss.ps);
ExecAssignScanProjectionInfo(&indexstate->ss);
...
}
ExecBuildProjectionInfo
为给定的econtext中的tlist构建ProjectionInfo,并把结果存储在tuple slot中.(调用者必须确保元组slot与此tlist匹配)
其主要逻辑是:
1.初始化
2.如需要,插入EEOP*_FETCHSOME步骤(调用ExecInitExprSlots)
3.遍历targetList,处理targetList(目标列)中的每一个列
3.1.对于”安全”Var则只需要生成EEOP_ASSIGN*_VAR步骤
3.2对于非”安全”VAr,使用常规办法处理列表达式,调用ExecInitExprRec函数处理,并通过ExprEvalPushStep压步骤
4.压入EEOP_DONE步骤
/* * ExecAssignScanProjectionInfo * Set up projection info for a scan node, if necessary. * ExecAssignScanProjectionInfo * 为扫描节点配置投影信息. * * We can avoid a projection step if the requested tlist exactly matches * the underlying tuple type. If so, we just set ps_ProjInfo to NULL. * Note that this case occurs not only for simple "SELECT * FROM ...", but * also in most cases where there are joins or other processing nodes above * the scan node, because the planner will preferentially generate a matching * tlist. * 如果请求的tlist刚好与潜在的tuple类型相匹配,则可以避免投影这一步骤. * 因此,只需要简单的把ps_ProjInfo设置为NULL即可. * 注意这种情况不单在"SELECT * FROM ..."时会出现, * 而且在存在连接或者其他处理节点在扫描节点的上层时也会发生, * 因为规划器会优先生成匹配的tlist. * * The scan slot's descriptor must have been set already. * 扫描slot的描述符必须已设置. */voidExecAssignScanProjectionInfo(ScanState *node){ //扫描节点 Scan *scan = (Scan *) node->ps.plan; //元组描述符 TupleDesc tupdesc = node->ss_ScanTupleSlot->tts_tupleDescriptor; //执行ExecConditionalAssignProjectionInfo ExecConditionalAssignProjectionInfo(&node->ps, tupdesc, scan->scanrelid);}/* ---------------- * ExecConditionalAssignProjectionInfo * * as ExecAssignProjectionInfo, but store NULL rather than building projection * info if no projection is required * 与ExecAssignProjectionInfo类似,但在不需要投影操作时只需要存储NULL而不是构建投影信息 * ---------------- */voidExecConditionalAssignProjectionInfo(PlanState *planstate, TupleDesc inputDesc, Index varno){ //正好匹配元组类型,则设计为NULL if (tlist_matches_tupdesc(planstate, planstate->plan->targetlist, varno, inputDesc)) planstate->ps_ProjInfo = NULL; else //否则执行ExecAssignProjectionInfo ExecAssignProjectionInfo(planstate, inputDesc);}/* ---------------- * ExecAssignProjectionInfo * * forms the projection information from the node's targetlist * 通过节点的targetlist构造投影信息. * * Notes for inputDesc are same as for ExecBuildProjectionInfo: supply it * for a relation-scan node, can pass NULL for upper-level nodes * 注意inputDesc与ExecBuildProjectionInfo的一样: * 为relation-scan节点提供该描述符,可能为高层的节点传递NULL值 * ---------------- */voidExecAssignProjectionInfo(PlanState *planstate, TupleDesc inputDesc){ //直接调用ExecBuildProjectionInfo planstate->ps_ProjInfo = ExecBuildProjectionInfo(planstate->plan->targetlist, planstate->ps_ExprContext, planstate->ps_ResultTupleSlot, planstate, inputDesc);}/* * ExecBuildProjectionInfo * * Build a ProjectionInfo node for evaluating the given tlist in the given * econtext, and storing the result into the tuple slot. (Caller must have * ensured that tuple slot has a descriptor matching the tlist!) * 为给定的econtext中的tlist构建ProjectionInfo,并把结果存储在tuple slot中. * (调用者必须确保元组slot与此tlist匹配) * * inputDesc can be NULL, but if it is not, we check to see whether simple * Vars in the tlist match the descriptor. It is important to provide * inputDesc for relation-scan plan nodes, as a cross check that the relation * hasn't been changed since the plan was made. At higher levels of a plan, * there is no need to recheck. * inputDesc可以为NULL,但如果不是,检查tlist中的简单Vars是否与描述符匹配. * 对于relation-scan节点来说,提供inputDesc是十分重要的, * 交叉检查在plan已生成的情况下relation没有出现变化. * 而plan更高的层次,则不需要重新检查. * * This is implemented by internally building an ExprState that performs the * whole projection in one go. * 通过内部构造ExprState(在一轮执行中完成整个投影操作)来实现. * * Caution: before PG v10, the targetList was a list of ExprStates; now it * should be the planner-created targetlist, since we do the compilation here. * 注意:在PG v10前,targetlist是ExprState链表,现在tlist应该是planner创建的targetlist, * 如不是则需报错. */ProjectionInfo *ExecBuildProjectionInfo(List *targetList, ExprContext *econtext, TupleTableSlot *slot, PlanState *parent, TupleDesc inputDesc){ //生成ProjectionInfo节点 ProjectionInfo *projInfo = makeNode(ProjectionInfo); //表达式状态节点 ExprState *state; //表达式解析步骤 ExprEvalStep scratch = {0}; //临时变量 ListCell *lc; //expr上下文 projInfo->pi_exprContext = econtext; /* We embed ExprState into ProjectionInfo instead of doing extra palloc */ //集成ExprState到ProjectionInfo中,而不需要另外的palloc projInfo->pi_state.tag.type = T_ExprState; state = &projInfo->pi_state; state->expr = (Expr *) targetList; state->parent = parent; state->ext_params = NULL; state->resultslot = slot; /* Insert EEOP_*_FETCHSOME steps as needed */ //如需要,插入EEOP_*_FETCHSOME步骤 ExecInitExprSlots(state, (Node *) targetList); /* Now compile each tlist column */ //现在"编译"tlist中的每一个列 foreach(lc, targetList) { TargetEntry *tle = lfirst_node(TargetEntry, lc); Var *variable = NULL; AttrNumber attnum = 0; bool isSafeVar = false; /* * If tlist expression is a safe non-system Var, use the fast-path * ASSIGN_*_VAR opcodes. "Safe" means that we don't need to apply * CheckVarSlotCompatibility() during plan startup. If a source slot * was provided, we make the equivalent tests here; if a slot was not * provided, we assume that no check is needed because we're dealing * with a non-relation-scan-level expression. * 如果tlist表达式是安全的非系统Var,使用快速路径ASSIGN_*_VAR opcodes. * "Safe"的意思是在计划启动执行时我们不需要应用CheckVarSlotCompatibility(). * 如果提供了源slot,假定不需要再做检查,因为我们正在处理的是非关系扫描级别的表达式. */ if (tle->expr != NULL && IsA(tle->expr, Var) && ((Var *) tle->expr)->varattno > 0) { /* Non-system Var, but how safe is it? */ //非系统Var,但有多安全呢? variable = (Var *) tle->expr; attnum = variable->varattno; if (inputDesc == NULL) //无法检查,假定是OK的. isSafeVar = true; /* can't check, just assume OK */ else if (attnum <= inputDesc->natts) { //---- 属性编号小于输入的属性个数 Form_pg_attribute attr = TupleDescAttr(inputDesc, attnum - 1); /* * If user attribute is dropped or has a type mismatch, don't * use ASSIGN_*_VAR. Instead let the normal expression * machinery handle it (which'll possibly error out). * 如果用户属性已被清除或者有类型不匹配,不要使用ASSIGN_*_VAR. * 让常规的表达式匹配处理这周情况(可能会出现错误) */ if (!attr->attisdropped && variable->vartype == attr->atttypid) { isSafeVar = true; } } } if (isSafeVar) { //--- 如为安全的Var /* Fast-path: just generate an EEOP_ASSIGN_*_VAR step */ //Fast-path: 只需要生成EEOP_ASSIGN_*_VAR步骤即可 switch (variable->varno) { case INNER_VAR: /* get the tuple from the inner node */ //内关系VAR,从inner节点获取元组 scratch.opcode = EEOP_ASSIGN_INNER_VAR; break; case OUTER_VAR: /* get the tuple from the outer node */ //从外关系获取元组 scratch.opcode = EEOP_ASSIGN_OUTER_VAR; break; /* INDEX_VAR is handled by default case */ //默认: INDEX_VAR default: /* get the tuple from the relation being scanned */ //从正在扫描的关系中获取元组 scratch.opcode = EEOP_ASSIGN_SCAN_VAR; break; } //EEOP_ASSIGN_*_VAR scratch.d.assign_var.attnum = attnum - 1; scratch.d.assign_var.resultnum = tle->resno - 1; ExprEvalPushStep(state, &scratch); } else { /* * Otherwise, compile the column expression normally. * 否则的话,使用常规办法"编译"列表达式 * * We can't tell the expression to evaluate directly into the * result slot, as the result slot (and the exprstate for that * matter) can change between executions. We instead evaluate * into the ExprState's resvalue/resnull and then move. * 我们不能够直接告知表达式进行解析到结果slot中, * 因为结果slot(以及这种情况下的exprstate)在执行期间会出现变化. * 我们只需要解析到ExprState's的resvalue/resnull中并进行移动即可. */ ExecInitExprRec(tle->expr, state, &state->resvalue, &state->resnull); /* * Column might be referenced multiple times in upper nodes, so * force value to R/O - but only if it could be an expanded datum. * 列可能在高层节点被依赖多次,因此强制值为R/O - 只在可扩展datum时才这样处理 */ //d.assign_tmp.resultnum/attnum if (get_typlen(exprType((Node *) tle->expr)) == -1) scratch.opcode = EEOP_ASSIGN_TMP_MAKE_RO; else scratch.opcode = EEOP_ASSIGN_TMP; scratch.d.assign_tmp.resultnum = tle->resno - 1; ExprEvalPushStep(state, &scratch); } } scratch.opcode = EEOP_DONE; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); //返回投影信息. return projInfo;}
测试脚本
testdb=# select 1+id,c2 from t_expr where id < 3;
调用栈
(gdb) bt#0 ExecBuildProjectionInfo (targetList=0x1cc7550, econtext=0x1c8f408, slot=0x1c8f710, parent=0x1c8f1f0, inputDesc=0x7f8386bb6ab8) at execExpr.c:355#1 0x00000000006e60d5 in ExecAssignProjectionInfo (planstate=0x1c8f1f0, inputDesc=0x7f8386bb6ab8) at execUtils.c:468#2 0x00000000006e613c in ExecConditionalAssignProjectionInfo (planstate=0x1c8f1f0, inputDesc=0x7f8386bb6ab8, varno=1) at execUtils.c:493#3 0x00000000006e23f5 in ExecAssignScanProjectionInfo (node=0x1c8f1f0) at execScan.c:240#4 0x0000000000700afc in ExecInitIndexScan (node=0x1ba8a18, estate=0x1c8efd8, eflags=16) at nodeIndexscan.c:962#5 0x00000000006e00cc in ExecInitNode (node=0x1ba8a18, estate=0x1c8efd8, eflags=16) at execProcnode.c:217#6 0x00000000006d6abe in InitPlan (queryDesc=0x1c94aa8, eflags=16) at execMain.c:1046#7 0x00000000006d58ad in standard_ExecutorStart (queryDesc=0x1c94aa8, eflags=16) at execMain.c:265#8 0x00000000006d5649 in ExecutorStart (queryDesc=0x1c94aa8, eflags=0) at execMain.c:147#9 0x00000000008c18d6 in PortalStart (portal=0x1c15608, params=0x0, eflags=0, snapshot=0x0) at pquery.c:520#10 0x00000000008bbe1b in exec_simple_query (query_string=0x1ba6d78 "select 1+id,c2 from t_expr where id < 3;") at postgres.c:1106#11 0x00000000008c0191 in PostgresMain (argc=1, argv=0x1bd4cb8, dbname=0x1bd4b20 "testdb", username=0x1ba3a98 "xdb") at postgres.c:4182#12 0x000000000081e06c in BackendRun (port=0x1bc8ae0) at postmaster.c:4361#13 0x000000000081d7df in BackendStartup (port=0x1bc8ae0) at postmaster.c:4033#14 0x0000000000819bd9 in ServerLoop () at postmaster.c:1706#15 0x000000000081948f in PostmasterMain (argc=1, argv=0x1ba1a50) at postmaster.c:1379#16 0x0000000000742931 in main (argc=1, argv=0x1ba1a50) at main.c:228
执行跟踪,进入函数ExecBuildProjectionInfo
(gdb) b ExecBuildProjectionInfoBreakpoint 1 at 0x6c5377: file execExpr.c, line 355.(gdb) cContinuing.Breakpoint 1, ExecBuildProjectionInfo (targetList=0x1c93498, econtext=0x1c883a8, slot=0x1c887c8, parent=0x1c88190, inputDesc=0x1c884a0) at execExpr.c:355355 ProjectionInfo *projInfo = makeNode(ProjectionInfo);(gdb)
1.初始化
(gdb) n357 ExprEvalStep scratch = {0};(gdb) 360 projInfo->pi_exprContext = econtext;(gdb) 362 projInfo->pi_state.tag.type = T_ExprState;(gdb) 363 state = &projInfo->pi_state;(gdb) 364 state->expr = (Expr *) targetList;(gdb) 365 state->parent = parent;(gdb) 366 state->ext_params = NULL;(gdb) 368 state->resultslot = slot;(gdb)
查看相关变量
(gdb) p *state$1 = {tag = {type = T_ExprState}, flags = 0 '\000', resnull = false, resvalue = 0, resultslot = 0x1c8f710, steps = 0x0, evalfunc = 0x0, expr = 0x1cc7550, evalfunc_private = 0x0, steps_len = 0, steps_alloc = 0, parent = 0x1c8f1f0, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}
目标列链表
(gdb) p targetList$2 = (List *) 0x1cc7550(gdb) p *targetList$3 = {type = T_List, length = 2, head = 0x1cc7528, tail = 0x1cc75e0}
第一个元素是1+id,第二个元素是c2
(gdb) p *(TargetEntry *)targetList->head->data.ptr_value$7 = {xpr = {type = T_TargetEntry}, expr = 0x1c9a930, resno = 1, resname = 0xbcf498 "?column?", ressortgroupref = 0, resorigtbl = 0, resorigcol = 0, resjunk = false}(gdb) p *(OpExpr *)((TargetEntry *)targetList->head->data.ptr_value)->expr$9 = {xpr = {type = T_OpExpr}, opno = 551, opfuncid = 177, opresulttype = 23, opretset = false, opcollid = 0, inputcollid = 0, args = 0x1c9a878, location = 8}(gdb) p *(Node *)targetList->head->next->data.ptr_value$10 = {type = T_TargetEntry}(gdb) p *(TargetEntry *)targetList->head->next->data.ptr_value$11 = {xpr = {type = T_TargetEntry}, expr = 0x1c9aa40, resno = 2, resname = 0x1ba7a40 "c2", ressortgroupref = 0, resorigtbl = 237600, resorigcol = 2, resjunk = false}
2.如需要,插入EEOP_*_FETCHSOME步骤(调用ExecInitExprSlots)
(gdb) 371 ExecInitExprSlots(state, (Node *) targetList);
第一个步骤,opcode = 3,即EEOP_SCAN_FETCHSOME
(gdb) n374 foreach(lc, targetList)(gdb) p *state$13 = {tag = {type = T_ExprState}, flags = 0 '\000', resnull = false, resvalue = 0, resultslot = 0x1c8f710, steps = 0x1c8f868, evalfunc = 0x0, expr = 0x1cc7550, evalfunc_private = 0x0, steps_len = 1, steps_alloc = 16, parent = 0x1c8f1f0, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}(gdb) p state->steps[0]$14 = {opcode = 3, resvalue = 0x0, resnull = 0x0, d = {fetch = {last_var = 2, known_desc = 0x0}, var = {attnum = 2, vartype = 0}, wholerow = {var = 0x2, first = false, slow = false, tupdesc = 0x0, junkFilter = 0x0}, assign_var = { resultnum = 2, attnum = 0}, assign_tmp = {resultnum = 2}, constval = {value = 2, isnull = false}, func = { finfo = 0x2, fcinfo_data = 0x0, fn_addr = 0x0, nargs = 0}, boolexpr = {anynull = 0x2, jumpdone = 0}, qualexpr = { jumpdone = 2}, jump = {jumpdone = 2}, nulltest_row = {argdesc = 0x2}, param = {paramid = 2, paramtype = 0}, cparam = { paramfunc = 0x2, paramarg = 0x0, paramid = 0, paramtype = 0}, casetest = {value = 0x2, isnull = 0x0}, make_readonly = {value = 0x2, isnull = 0x0}, iocoerce = {finfo_out = 0x2, fcinfo_data_out = 0x0, finfo_in = 0x0, fcinfo_data_in = 0x0}, sqlvaluefunction = {svf = 0x2}, nextvalueexpr = {seqid = 2, seqtypid = 0}, arrayexpr = { elemvalues = 0x2, elemnulls = 0x0, nelems = 0, elemtype = 0, elemlength = 0, elembyval = false, elemalign = 0 '\000', multidims = false}, arraycoerce = {elemexprstate = 0x2, resultelemtype = 0, amstate = 0x0}, row = {tupdesc = 0x2, elemvalues = 0x0, elemnulls = 0x0}, rowcompare_step = {finfo = 0x2, fcinfo_data = 0x0, fn_addr = 0x0, jumpnull = 0, jumpdone = 0}, rowcompare_final = {rctype = ROWCOMPARE_LE}, minmax = {values = 0x2, nulls = 0x0, nelems = 0, op = IS_GREATEST, finfo = 0x0, fcinfo_data = 0x0}, fieldselect = {fieldnum = 2, resulttype = 0, argdesc = 0x0}, fieldstore = {fstore = 0x2, argdesc = 0x0, values = 0x0, nulls = 0x0, ncolumns = 0}, arrayref_subscript = {state = 0x2, off = 0, isupper = false, jumpdone = 0}, arrayref = {state = 0x2}, domaincheck = { constraintname = 0x2 <Address 0x2 out of bounds>, checkvalue = 0x0, checknull = 0x0, resulttype = 0}, convert_rowtype = {convert = 0x2, indesc = 0x0, outdesc = 0x0, map = 0x0, initialized = false}, scalararrayop = { element_type = 2, useOr = false, typlen = 0, typbyval = false, typalign = 0 '\000', finfo = 0x0, fcinfo_data = 0x0, fn_addr = 0x0}, xmlexpr = {xexpr = 0x2, named_argvalue = 0x0, named_argnull = 0x0, argvalue = 0x0, argnull = 0x0}, aggref = {astate = 0x2}, grouping_func = {parent = 0x2, clauses = 0x0}, window_func = {wfstate = 0x2}, subplan = { sstate = 0x2}, alternative_subplan = {asstate = 0x2}, agg_deserialize = {aggstate = 0x2, fcinfo_data = 0x0, jumpnull = 0}, agg_strict_input_check = {nulls = 0x2, nargs = 0, jumpnull = 0}, agg_init_trans = {aggstate = 0x2, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_strict_trans_check = { aggstate = 0x2, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_trans = {aggstate = 0x2, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0}}}(gdb)
3.遍历targetList,处理targetList(目标列)中的每一个列
3.1.对于”安全”Var则只需要生成EEOPASSIGN*_VAR步骤
3.2.对于非”安全”VAr,使用常规办法处理列表达式,调用ExecInitExprRec函数处理,并通过ExprEvalPushStep压步骤
(gdb) n376 TargetEntry *tle = lfirst_node(TargetEntry, lc);(gdb) 377 Var *variable = NULL;(gdb) 378 AttrNumber attnum = 0;(gdb) 379 bool isSafeVar = false;(gdb) 389 if (tle->expr != NULL &&(gdb) 390 IsA(tle->expr, Var) &&(gdb) 389 if (tle->expr != NULL &&(gdb) 415 if (isSafeVar)(gdb) p *tle$15 = {xpr = {type = T_TargetEntry}, expr = 0x1c9a930, resno = 1, resname = 0xbcf498 "?column?", ressortgroupref = 0, resorigtbl = 0, resorigcol = 0, resjunk = false}(gdb) n452 ExecInitExprRec(tle->expr, state,(gdb)
进入ExecInitExprRec,Node节点为OpExpr,执行ExprEvalPushStep压入步骤中
(gdb) stepExecInitExprRec (node=0x1c9a930, state=0x1c8f7d8, resv=0x1c8f7e0, resnull=0x1c8f7dd) at execExpr.c:645645 ExprEvalStep scratch = {0};(gdb) n648 check_stack_depth();(gdb) 651 Assert(resv != NULL && resnull != NULL);(gdb) 652 scratch.resvalue = resv;(gdb) 653 scratch.resnull = resnull;(gdb) 656 switch (nodeTag(node))(gdb) 891 OpExpr *op = (OpExpr *) node;(gdb) p *node$16 = {type = T_OpExpr}(gdb) n893 ExecInitFunc(&scratch, node,(gdb) 896 ExprEvalPushStep(state, &scratch);(gdb) 897 break;(gdb) 2122 }(gdb) ExecBuildProjectionInfo (targetList=0x1cc7550, econtext=0x1c8f408, slot=0x1c8f710, parent=0x1c8f1f0, inputDesc=0x7f8386bb6ab8) at execExpr.c:459459 if (get_typlen(exprType((Node *) tle->expr)) == -1)(gdb) 462 scratch.opcode = EEOP_ASSIGN_TMP;(gdb)
ExecInitExprRec调用完毕,增加了2个步骤,分别是:
1.opcode = 6,即EEOP_SCAN_VAR
2.opcode = 18,即EEOP_FUNCEXPR_STRICT
(gdb) p *state$17 = {tag = {type = T_ExprState}, flags = 0 '\000', resnull = false, resvalue = 0, resultslot = 0x1c8f710, steps = 0x1c8f868, evalfunc = 0x0, expr = 0x1cc7550, evalfunc_private = 0x0, steps_len = 3, steps_alloc = 16, parent = 0x1c8f1f0, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}(gdb) p state->steps[1]$18 = {opcode = 6, resvalue = 0x1c8fd00, resnull = 0x1c90019, d = {fetch = {last_var = 0, known_desc = 0x0}, var = { attnum = 0, vartype = 23}, wholerow = {var = 0x1700000000, first = false, slow = false, tupdesc = 0x0, junkFilter = 0x0}, assign_var = {resultnum = 0, attnum = 23}, assign_tmp = {resultnum = 0}, constval = { value = 98784247808, isnull = false}, func = {finfo = 0x1700000000, fcinfo_data = 0x0, fn_addr = 0x0, nargs = 0}, boolexpr = {anynull = 0x1700000000, jumpdone = 0}, qualexpr = {jumpdone = 0}, jump = {jumpdone = 0}, nulltest_row = { argdesc = 0x1700000000}, param = {paramid = 0, paramtype = 23}, cparam = {paramfunc = 0x1700000000, paramarg = 0x0, paramid = 0, paramtype = 0}, casetest = {value = 0x1700000000, isnull = 0x0}, make_readonly = {value = 0x1700000000, isnull = 0x0}, iocoerce = {finfo_out = 0x1700000000, fcinfo_data_out = 0x0, finfo_in = 0x0, fcinfo_data_in = 0x0}, sqlvaluefunction = {svf = 0x1700000000}, nextvalueexpr = {seqid = 0, seqtypid = 23}, arrayexpr = { elemvalues = 0x1700000000, elemnulls = 0x0, nelems = 0, elemtype = 0, elemlength = 0, elembyval = false, elemalign = 0 '\000', multidims = false}, arraycoerce = {elemexprstate = 0x1700000000, resultelemtype = 0, amstate = 0x0}, row = {tupdesc = 0x1700000000, elemvalues = 0x0, elemnulls = 0x0}, rowcompare_step = { finfo = 0x1700000000, fcinfo_data = 0x0, fn_addr = 0x0, jumpnull = 0, jumpdone = 0}, rowcompare_final = {rctype = 0}, minmax = {values = 0x1700000000, nulls = 0x0, nelems = 0, op = IS_GREATEST, finfo = 0x0, fcinfo_data = 0x0}, fieldselect = {fieldnum = 0, resulttype = 23, argdesc = 0x0}, fieldstore = {fstore = 0x1700000000, argdesc = 0x0, values = 0x0, nulls = 0x0, ncolumns = 0}, arrayref_subscript = {state = 0x1700000000, off = 0, isupper = false, jumpdone = 0}, arrayref = {state = 0x1700000000}, domaincheck = { constraintname = 0x1700000000 <Address 0x1700000000 out of bounds>, checkvalue = 0x0, checknull = 0x0, resulttype = 0}, convert_rowtype = {convert = 0x1700000000, indesc = 0x0, outdesc = 0x0, map = 0x0, initialized = false}, scalararrayop = {element_type = 0, useOr = 23, typlen = 0, typbyval = false, typalign = 0 '\000', finfo = 0x0, fcinfo_data = 0x0, fn_addr = 0x0}, xmlexpr = {xexpr = 0x1700000000, named_argvalue = 0x0, named_argnull = 0x0, argvalue = 0x0, argnull = 0x0}, aggref = {astate = 0x1700000000}, grouping_func = {parent = 0x1700000000, clauses = 0x0}, window_func = {wfstate = 0x1700000000}, subplan = { sstate = 0x1700000000}, alternative_subplan = {asstate = 0x1700000000}, agg_deserialize = {aggstate = 0x1700000000, fcinfo_data = 0x0, jumpnull = 0}, agg_strict_input_check = {nulls = 0x1700000000, nargs = 0, jumpnull = 0}, agg_init_trans = {aggstate = 0x1700000000, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_strict_trans_check = {aggstate = 0x1700000000, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, ---Type <return> to continue, or q <return> to quit--- agg_trans = {aggstate = 0x1700000000, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0}}}(gdb) p state->steps[2]$19 = {opcode = 18, resvalue = 0x1c8f7e0, resnull = 0x1c8f7dd, d = {fetch = {last_var = 29949056, known_desc = 0x1c8fcd8}, var = {attnum = 29949056, vartype = 0}, wholerow = {var = 0x1c8fc80, first = 216, slow = 252, tupdesc = 0x93d60c <int4pl>, junkFilter = 0x2}, assign_var = {resultnum = 29949056, attnum = 0}, assign_tmp = { resultnum = 29949056}, constval = {value = 29949056, isnull = 216}, func = {finfo = 0x1c8fc80, fcinfo_data = 0x1c8fcd8, fn_addr = 0x93d60c <int4pl>, nargs = 2}, boolexpr = {anynull = 0x1c8fc80, jumpdone = 29949144}, qualexpr = {jumpdone = 29949056}, jump = {jumpdone = 29949056}, nulltest_row = { argdesc = 0x1c8fc80}, param = {paramid = 29949056, paramtype = 0}, cparam = {paramfunc = 0x1c8fc80, paramarg = 0x1c8fcd8, paramid = 9688588, paramtype = 0}, casetest = {value = 0x1c8fc80, isnull = 0x1c8fcd8}, make_readonly = {value = 0x1c8fc80, isnull = 0x1c8fcd8}, iocoerce = {finfo_out = 0x1c8fc80, fcinfo_data_out = 0x1c8fcd8, finfo_in = 0x93d60c <int4pl>, fcinfo_data_in = 0x2}, sqlvaluefunction = { svf = 0x1c8fc80}, nextvalueexpr = {seqid = 29949056, seqtypid = 0}, arrayexpr = {elemvalues = 0x1c8fc80, elemnulls = 0x1c8fcd8, nelems = 9688588, elemtype = 0, elemlength = 2, elembyval = false, elemalign = 0 '\000', multidims = false}, arraycoerce = {elemexprstate = 0x1c8fc80, resultelemtype = 29949144, amstate = 0x93d60c <int4pl>}, row = {tupdesc = 0x1c8fc80, elemvalues = 0x1c8fcd8, elemnulls = 0x93d60c <int4pl>}, rowcompare_step = {finfo = 0x1c8fc80, fcinfo_data = 0x1c8fcd8, fn_addr = 0x93d60c <int4pl>, jumpnull = 2, jumpdone = 0}, rowcompare_final = {rctype = 29949056}, minmax = {values = 0x1c8fc80, nulls = 0x1c8fcd8, nelems = 9688588, op = IS_GREATEST, finfo = 0x2, fcinfo_data = 0x0}, fieldselect = {fieldnum = -896, resulttype = 0, argdesc = 0x1c8fcd8}, fieldstore = {fstore = 0x1c8fc80, argdesc = 0x1c8fcd8, values = 0x93d60c <int4pl>, nulls = 0x2, ncolumns = 0}, arrayref_subscript = {state = 0x1c8fc80, off = 29949144, isupper = false, jumpdone = 9688588}, arrayref = {state = 0x1c8fc80}, domaincheck = {constraintname = 0x1c8fc80 "\f֓", checkvalue = 0x1c8fcd8, checknull = 0x93d60c <int4pl>, resulttype = 2}, convert_rowtype = {convert = 0x1c8fc80, indesc = 0x1c8fcd8, outdesc = 0x93d60c <int4pl>, map = 0x2, initialized = false}, scalararrayop = {element_type = 29949056, useOr = false, typlen = 0, typbyval = 216, typalign = -4 '\374', finfo = 0x93d60c <int4pl>, fcinfo_data = 0x2, fn_addr = 0x0}, xmlexpr = {xexpr = 0x1c8fc80, named_argvalue = 0x1c8fcd8, named_argnull = 0x93d60c <int4pl>, argvalue = 0x2, argnull = 0x0}, aggref = {astate = 0x1c8fc80}, grouping_func = {parent = 0x1c8fc80, clauses = 0x1c8fcd8}, window_func = {wfstate = 0x1c8fc80}, subplan = {sstate = 0x1c8fc80}, alternative_subplan = { asstate = 0x1c8fc80}, agg_deserialize = {aggstate = 0x1c8fc80, fcinfo_data = 0x1c8fcd8, jumpnull = 9688588}, ---Type <return> to continue, or q <return> to quit--- agg_strict_input_check = {nulls = 0x1c8fc80, nargs = 29949144, jumpnull = 0}, agg_init_trans = {aggstate = 0x1c8fc80, pertrans = 0x1c8fcd8, aggcontext = 0x93d60c <int4pl>, setno = 2, transno = 0, setoff = 0, jumpnull = 0}, agg_strict_trans_check = {aggstate = 0x1c8fc80, setno = 29949144, transno = 0, setoff = 9688588, jumpnull = 0}, agg_trans = {aggstate = 0x1c8fc80, pertrans = 0x1c8fcd8, aggcontext = 0x93d60c <int4pl>, setno = 2, transno = 0, setoff = 0}}}(gdb)
压入对应该表达式列的编号,opcode = 14,即EEOP_ASSIGN_TMP
(gdb) n463 scratch.d.assign_tmp.resultnum = tle->resno - 1;(gdb) 464 ExprEvalPushStep(state, &scratch);(gdb) (gdb) 374 foreach(lc, targetList)(gdb) p *state$20 = {tag = {type = T_ExprState}, flags = 0 '\000', resnull = false, resvalue = 0, resultslot = 0x1c8f710, steps = 0x1c8f868, evalfunc = 0x0, expr = 0x1cc7550, evalfunc_private = 0x0, steps_len = 4, steps_alloc = 16, parent = 0x1c8f1f0, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}(gdb) p state->steps[3]$21 = {opcode = 14, resvalue = 0x0, resnull = 0x0, d = {fetch = {last_var = 0, known_desc = 0x0}, var = {attnum = 0, vartype = 0}, wholerow = {var = 0x0, first = false, slow = false, tupdesc = 0x0, junkFilter = 0x0}, assign_var = { resultnum = 0, attnum = 0}, assign_tmp = {resultnum = 0}, constval = {value = 0, isnull = false}, func = { finfo = 0x0, fcinfo_data = 0x0, fn_addr = 0x0, nargs = 0}, boolexpr = {anynull = 0x0, jumpdone = 0}, qualexpr = { jumpdone = 0}, jump = {jumpdone = 0}, nulltest_row = {argdesc = 0x0}, param = {paramid = 0, paramtype = 0}, cparam = { paramfunc = 0x0, paramarg = 0x0, paramid = 0, paramtype = 0}, casetest = {value = 0x0, isnull = 0x0}, make_readonly = {value = 0x0, isnull = 0x0}, iocoerce = {finfo_out = 0x0, fcinfo_data_out = 0x0, finfo_in = 0x0, fcinfo_data_in = 0x0}, sqlvaluefunction = {svf = 0x0}, nextvalueexpr = {seqid = 0, seqtypid = 0}, arrayexpr = { elemvalues = 0x0, elemnulls = 0x0, nelems = 0, elemtype = 0, elemlength = 0, elembyval = false, elemalign = 0 '\000', multidims = false}, arraycoerce = {elemexprstate = 0x0, resultelemtype = 0, amstate = 0x0}, row = {tupdesc = 0x0, elemvalues = 0x0, elemnulls = 0x0}, rowcompare_step = {finfo = 0x0, fcinfo_data = 0x0, fn_addr = 0x0, jumpnull = 0, jumpdone = 0}, rowcompare_final = {rctype = 0}, minmax = {values = 0x0, nulls = 0x0, nelems = 0, op = IS_GREATEST, finfo = 0x0, fcinfo_data = 0x0}, fieldselect = {fieldnum = 0, resulttype = 0, argdesc = 0x0}, fieldstore = { fstore = 0x0, argdesc = 0x0, values = 0x0, nulls = 0x0, ncolumns = 0}, arrayref_subscript = {state = 0x0, off = 0, isupper = false, jumpdone = 0}, arrayref = {state = 0x0}, domaincheck = {constraintname = 0x0, checkvalue = 0x0, checknull = 0x0, resulttype = 0}, convert_rowtype = {convert = 0x0, indesc = 0x0, outdesc = 0x0, map = 0x0, initialized = false}, scalararrayop = {element_type = 0, useOr = false, typlen = 0, typbyval = false, typalign = 0 '\000', finfo = 0x0, fcinfo_data = 0x0, fn_addr = 0x0}, xmlexpr = {xexpr = 0x0, named_argvalue = 0x0, named_argnull = 0x0, argvalue = 0x0, argnull = 0x0}, aggref = {astate = 0x0}, grouping_func = {parent = 0x0, clauses = 0x0}, window_func = {wfstate = 0x0}, subplan = {sstate = 0x0}, alternative_subplan = {asstate = 0x0}, agg_deserialize = {aggstate = 0x0, fcinfo_data = 0x0, jumpnull = 0}, agg_strict_input_check = {nulls = 0x0, nargs = 0, jumpnull = 0}, agg_init_trans = {aggstate = 0x0, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_strict_trans_check = {aggstate = 0x0, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_trans = {aggstate = 0x0, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0}}}(gdb)
继续处理下一个列,这是一个”安全”列,压入EEOP_ASSIGN_SCAN_VAR步骤
(gdb) n376 TargetEntry *tle = lfirst_node(TargetEntry, lc);(gdb) 377 Var *variable = NULL;(gdb) p *tle$22 = {xpr = {type = T_TargetEntry}, expr = 0x1c9aa40, resno = 2, resname = 0x1ba7a40 "c2", ressortgroupref = 0, resorigtbl = 237600, resorigcol = 2, resjunk = false}(gdb) n378 AttrNumber attnum = 0;(gdb) 379 bool isSafeVar = false;(gdb) 389 if (tle->expr != NULL &&(gdb) 390 IsA(tle->expr, Var) &&(gdb) 389 if (tle->expr != NULL &&(gdb) 391 ((Var *) tle->expr)->varattno > 0)(gdb) 390 IsA(tle->expr, Var) &&(gdb) 394 variable = (Var *) tle->expr;(gdb) 395 attnum = variable->varattno;(gdb) 397 if (inputDesc == NULL)(gdb) 399 else if (attnum <= inputDesc->natts)(gdb) 401 Form_pg_attribute attr = TupleDescAttr(inputDesc, attnum - 1);(gdb) 408 if (!attr->attisdropped && variable->vartype == attr->atttypid)(gdb) 410 isSafeVar = true;(gdb) 415 if (isSafeVar)(gdb) 418 switch (variable->varno)(gdb) 434 scratch.opcode = EEOP_ASSIGN_SCAN_VAR;(gdb) 435 break;(gdb) 438 scratch.d.assign_var.attnum = attnum - 1;(gdb) 439 scratch.d.assign_var.resultnum = tle->resno - 1;(gdb) 440 ExprEvalPushStep(state, &scratch);(gdb) p *state$23 = {tag = {type = T_ExprState}, flags = 0 '\000', resnull = false, resvalue = 0, resultslot = 0x1c8f710, steps = 0x1c8f868, evalfunc = 0x0, expr = 0x1cc7550, evalfunc_private = 0x0, steps_len = 4, steps_alloc = 16, parent = 0x1c8f1f0, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}(gdb) n374 foreach(lc, targetList)(gdb) p *state$24 = {tag = {type = T_ExprState}, flags = 0 '\000', resnull = false, resvalue = 0, resultslot = 0x1c8f710, steps = 0x1c8f868, evalfunc = 0x0, expr = 0x1cc7550, evalfunc_private = 0x0, steps_len = 5, steps_alloc = 16, parent = 0x1c8f1f0, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}(gdb) p state->steps[4]$25 = {opcode = 13, resvalue = 0x0, resnull = 0x0, d = {fetch = {last_var = 1, known_desc = 0x0}, var = {attnum = 1, vartype = 1}, wholerow = {var = 0x100000001, first = false, slow = false, tupdesc = 0x0, junkFilter = 0x0}, assign_var = {resultnum = 1, attnum = 1}, assign_tmp = {resultnum = 1}, constval = {value = 4294967297, isnull = false}, func = {finfo = 0x100000001, fcinfo_data = 0x0, fn_addr = 0x0, nargs = 0}, boolexpr = { anynull = 0x100000001, jumpdone = 0}, qualexpr = {jumpdone = 1}, jump = {jumpdone = 1}, nulltest_row = { argdesc = 0x100000001}, param = {paramid = 1, paramtype = 1}, cparam = {paramfunc = 0x100000001, paramarg = 0x0, paramid = 0, paramtype = 0}, casetest = {value = 0x100000001, isnull = 0x0}, make_readonly = {value = 0x100000001, isnull = 0x0}, iocoerce = {finfo_out = 0x100000001, fcinfo_data_out = 0x0, finfo_in = 0x0, fcinfo_data_in = 0x0}, sqlvaluefunction = {svf = 0x100000001}, nextvalueexpr = {seqid = 1, seqtypid = 1}, arrayexpr = { elemvalues = 0x100000001, elemnulls = 0x0, nelems = 0, elemtype = 0, elemlength = 0, elembyval = false, elemalign = 0 '\000', multidims = false}, arraycoerce = {elemexprstate = 0x100000001, resultelemtype = 0, amstate = 0x0}, row = {tupdesc = 0x100000001, elemvalues = 0x0, elemnulls = 0x0}, rowcompare_step = { finfo = 0x100000001, fcinfo_data = 0x0, fn_addr = 0x0, jumpnull = 0, jumpdone = 0}, rowcompare_final = { rctype = ROWCOMPARE_LT}, minmax = {values = 0x100000001, nulls = 0x0, nelems = 0, op = IS_GREATEST, finfo = 0x0, fcinfo_data = 0x0}, fieldselect = {fieldnum = 1, resulttype = 1, argdesc = 0x0}, fieldstore = {fstore = 0x100000001, argdesc = 0x0, values = 0x0, nulls = 0x0, ncolumns = 0}, arrayref_subscript = {state = 0x100000001, off = 0, isupper = false, jumpdone = 0}, arrayref = {state = 0x100000001}, domaincheck = { constraintname = 0x100000001 <Address 0x100000001 out of bounds>, checkvalue = 0x0, checknull = 0x0, resulttype = 0}, convert_rowtype = {convert = 0x100000001, indesc = 0x0, outdesc = 0x0, map = 0x0, initialized = false}, scalararrayop = {element_type = 1, useOr = true, typlen = 0, typbyval = false, typalign = 0 '\000', finfo = 0x0, fcinfo_data = 0x0, fn_addr = 0x0}, xmlexpr = {xexpr = 0x100000001, named_argvalue = 0x0, named_argnull = 0x0, argvalue = 0x0, argnull = 0x0}, aggref = {astate = 0x100000001}, grouping_func = {parent = 0x100000001, clauses = 0x0}, window_func = {wfstate = 0x100000001}, subplan = {sstate = 0x100000001}, alternative_subplan = { asstate = 0x100000001}, agg_deserialize = {aggstate = 0x100000001, fcinfo_data = 0x0, jumpnull = 0}, agg_strict_input_check = {nulls = 0x100000001, nargs = 0, jumpnull = 0}, agg_init_trans = {aggstate = 0x100000001, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_strict_trans_check = { aggstate = 0x100000001, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_trans = {aggstate = 0x100000001, ---Type <return> to continue, or q <return> to quit--- pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0}}}(gdb)
4.压入EEOP_DONE步骤
(gdb) n468 scratch.opcode = EEOP_DONE;(gdb) 469 ExprEvalPushStep(state, &scratch);(gdb) 471 ExecReadyExpr(state);(gdb) p state->steps[5]$26 = {opcode = 0, resvalue = 0x0, resnull = 0x0, d = {fetch = {last_var = 1, known_desc = 0x0}, var = {attnum = 1, vartype = 1}, wholerow = {var = 0x100000001, first = false, slow = false, tupdesc = 0x0, junkFilter = 0x0}, assign_var = {resultnum = 1, attnum = 1}, assign_tmp = {resultnum = 1}, constval = {value = 4294967297, isnull = false}, func = {finfo = 0x100000001, fcinfo_data = 0x0, fn_addr = 0x0, nargs = 0}, boolexpr = { anynull = 0x100000001, jumpdone = 0}, qualexpr = {jumpdone = 1}, jump = {jumpdone = 1}, nulltest_row = { argdesc = 0x100000001}, param = {paramid = 1, paramtype = 1}, cparam = {paramfunc = 0x100000001, paramarg = 0x0, paramid = 0, paramtype = 0}, casetest = {value = 0x100000001, isnull = 0x0}, make_readonly = {value = 0x100000001, isnull = 0x0}, iocoerce = {finfo_out = 0x100000001, fcinfo_data_out = 0x0, finfo_in = 0x0, fcinfo_data_in = 0x0}, sqlvaluefunction = {svf = 0x100000001}, nextvalueexpr = {seqid = 1, seqtypid = 1}, arrayexpr = { elemvalues = 0x100000001, elemnulls = 0x0, nelems = 0, elemtype = 0, elemlength = 0, elembyval = false, elemalign = 0 '\000', multidims = false}, arraycoerce = {elemexprstate = 0x100000001, resultelemtype = 0, amstate = 0x0}, row = {tupdesc = 0x100000001, elemvalues = 0x0, elemnulls = 0x0}, rowcompare_step = { finfo = 0x100000001, fcinfo_data = 0x0, fn_addr = 0x0, jumpnull = 0, jumpdone = 0}, rowcompare_final = { rctype = ROWCOMPARE_LT}, minmax = {values = 0x100000001, nulls = 0x0, nelems = 0, op = IS_GREATEST, finfo = 0x0, fcinfo_data = 0x0}, fieldselect = {fieldnum = 1, resulttype = 1, argdesc = 0x0}, fieldstore = {fstore = 0x100000001, argdesc = 0x0, values = 0x0, nulls = 0x0, ncolumns = 0}, arrayref_subscript = {state = 0x100000001, off = 0, isupper = false, jumpdone = 0}, arrayref = {state = 0x100000001}, domaincheck = { constraintname = 0x100000001 <Address 0x100000001 out of bounds>, checkvalue = 0x0, checknull = 0x0, resulttype = 0}, convert_rowtype = {convert = 0x100000001, indesc = 0x0, outdesc = 0x0, map = 0x0, initialized = false}, scalararrayop = {element_type = 1, useOr = true, typlen = 0, typbyval = false, typalign = 0 '\000', finfo = 0x0, fcinfo_data = 0x0, fn_addr = 0x0}, xmlexpr = {xexpr = 0x100000001, named_argvalue = 0x0, named_argnull = 0x0, argvalue = 0x0, argnull = 0x0}, aggref = {astate = 0x100000001}, grouping_func = {parent = 0x100000001, clauses = 0x0}, window_func = {wfstate = 0x100000001}, subplan = {sstate = 0x100000001}, alternative_subplan = { asstate = 0x100000001}, agg_deserialize = {aggstate = 0x100000001, fcinfo_data = 0x0, jumpnull = 0}, agg_strict_input_check = {nulls = 0x100000001, nargs = 0, jumpnull = 0}, agg_init_trans = {aggstate = 0x100000001, pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_strict_trans_check = { aggstate = 0x100000001, setno = 0, transno = 0, setoff = 0, jumpnull = 0}, agg_trans = {aggstate = 0x100000001, ---Type <return> to continue, or q <return> to quit--- pertrans = 0x0, aggcontext = 0x0, setno = 0, transno = 0, setoff = 0}}}(gdb)
结束调用
(gdb) n473 return projInfo;(gdb) 474 }(gdb) ExecAssignProjectionInfo (planstate=0x1c8f1f0, inputDesc=0x7f8386bb6ab8) at execUtils.c:467467 planstate->ps_ProjInfo =(gdb)
以上是“PostgreSQL如何构建表达式解析”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注亿速云行业资讯频道!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:http://blog.itpub.net/6906/viewspace-2640080/