您好,登錄后才能下訂單哦!
這篇文章主要介紹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的描述符必須已設置.
*/
void
ExecAssignScanProjectionInfo(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而不是構建投影信息
* ----------------
*/
void
ExecConditionalAssignProjectionInfo(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值
* ----------------
*/
void
ExecAssignProjectionInfo(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 ExecBuildProjectionInfo
Breakpoint 1 at 0x6c5377: file execExpr.c, line 355.
(gdb) c
Continuing.
Breakpoint 1, ExecBuildProjectionInfo (targetList=0x1c93498, econtext=0x1c883a8, slot=0x1c887c8, parent=0x1c88190,
inputDesc=0x1c884a0) at execExpr.c:355
355 ProjectionInfo *projInfo = makeNode(ProjectionInfo);
(gdb)
1.初始化
(gdb) n
357 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) n
374 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) n
376 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) n
452 ExecInitExprRec(tle->expr, state,
(gdb)
進入ExecInitExprRec,Node節點為OpExpr,執行ExprEvalPushStep壓入步驟中
(gdb) step
ExecInitExprRec (node=0x1c9a930, state=0x1c8f7d8, resv=0x1c8f7e0, resnull=0x1c8f7dd) at execExpr.c:645
645 ExprEvalStep scratch = {0};
(gdb) n
648 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) n
893 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:459
459 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) n
463 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) n
376 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) n
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)
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) n
374 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) n
468 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) n
473 return projInfo;
(gdb)
474 }
(gdb)
ExecAssignProjectionInfo (planstate=0x1c8f1f0, inputDesc=0x7f8386bb6ab8) at execUtils.c:467
467 planstate->ps_ProjInfo =
(gdb)
以上是“PostgreSQL如何構建表達式解析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。