溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PostgreSQL隱式類型轉(zhuǎn)換中選擇操作符的實(shí)現(xiàn)函數(shù)是什么

發(fā)布時(shí)間:2021-11-09 10:42:24 來源:億速云 閱讀:135 作者:iii 欄目:關(guān)系型數(shù)據(jù)庫(kù)

這篇文章主要講解了“PostgreSQL隱式類型轉(zhuǎn)換中選擇操作符的實(shí)現(xiàn)函數(shù)是什么”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“PostgreSQL隱式類型轉(zhuǎn)換中選擇操作符的實(shí)現(xiàn)函數(shù)是什么”吧!

一、數(shù)據(jù)結(jié)構(gòu)

FuncCandidateList
該結(jié)構(gòu)體存儲(chǔ)檢索得到的所有可能選中的函數(shù)或操作符鏈表.

/*
 *  This structure holds a list of possible functions or operators
 *  found by namespace lookup.  Each function/operator is identified
 *  by OID and by argument types; the list must be pruned by type
 *  resolution rules that are embodied in the parser, not here.
 *  See FuncnameGetCandidates's comments for more info.
 *  該結(jié)構(gòu)體存儲(chǔ)檢索得到的所有可能選中的函數(shù)或操作符鏈表.
 *  每一個(gè)函數(shù)/操作符通過OID和參數(shù)類型唯一確定,
 *  通過集成到分析器中的type resolution rules來確定裁剪該鏈表(但不是在這里實(shí)現(xiàn))
 *  詳細(xì)可參考FuncnameGetCandidates函數(shù).
 */
typedef struct _FuncCandidateList
{
    struct _FuncCandidateList *next;
    //用于namespace檢索內(nèi)部使用
    int         pathpos;        /* for internal use of namespace lookup */
    //OID
    Oid         oid;            /* the function or operator's OID */
    //參數(shù)個(gè)數(shù) 
    int         nargs;          /* number of arg types returned */
    //variadic array的參數(shù)個(gè)數(shù)
    int         nvargs;         /* number of args to become variadic array */
    //默認(rèn)參數(shù)個(gè)數(shù)
    int         ndargs;         /* number of defaulted args */
    //參數(shù)位置索引
    int        *argnumbers;     /* args' positional indexes, if named call */
    //參數(shù)類型
    Oid         args[FLEXIBLE_ARRAY_MEMBER];    /* arg types */
}          *FuncCandidateList;

二、源碼解讀

func_select_candidate
處理邏輯與PG文檔中的類型轉(zhuǎn)換規(guī)則一樣,其規(guī)則詳見參考資料中的Operator部分.

/*
create table t_tmp(c1 int,c2 int);
insert into t_tmp values(1,1);
create cast(integer as text) with inout as implicit;
testdb=# select c1||'-'||c2 from t_tmp;
psql: ERROR:  operator is not unique: integer || unknown
LINE 1: select c1||'-'||c2 from t_tmp;
                 ^
HINT:  Could not choose a best candidate operator. You might need to add explicit type casts.
*/
/* func_select_candidate()
 *      Given the input argtype array and more than one candidate
 *      for the function, attempt to resolve the conflict.
 *      給定參數(shù)類型和多于1個(gè)的候選函數(shù),嘗試解決沖突選中合適的函數(shù).
 *
 * Returns the selected candidate if the conflict can be resolved,
 * otherwise returns NULL.
 * 如沖突解決,則返回選中的函數(shù),否則返回NULL.
 *
 * Note that the caller has already determined that there is no candidate
 * exactly matching the input argtypes, and has pruned away any "candidates"
 * that aren't actually coercion-compatible with the input types.
 * 注意 : 調(diào)用者已確定沒有那個(gè)函數(shù)完全滿足輸入的參數(shù)類型,
 *        已清除了所有與輸入?yún)?shù)類型不兼容的函數(shù).
 *
 * This is also used for resolving ambiguous operator references.  Formerly
 * parse_oper.c had its own, essentially duplicate code for the purpose.
 * The following comments (formerly in parse_oper.c) are kept to record some
 * of the history of these heuristics.
 * 本例程同時(shí)用于解決模糊操作符引用.以前parse_oper.c有自己的代碼,本質(zhì)上是重復(fù)的代碼.
 * 接下來的注釋(先前在parse_oper.c中)保留用于記錄這些啟發(fā)式的歷史.
 *
 * OLD COMMENTS:
 *
 * This routine is new code, replacing binary_oper_select_candidate()
 * which dates from v4.2/v1.0.x days. It tries very hard to match up
 * operators with types, including allowing type coercions if necessary.
 * The important thing is that the code do as much as possible,
 * while _never_ doing the wrong thing, where "the wrong thing" would
 * be returning an operator when other better choices are available,
 * or returning an operator which is a non-intuitive possibility.
 * - thomas 1998-05-21
 * 本例程努力的通過類型與operators進(jìn)行匹配,包括在需要時(shí)允許類型強(qiáng)制轉(zhuǎn)換.
 *
 * The comments below came from binary_oper_select_candidate(), and
 * illustrate the issues and choices which are possible:
 * - thomas 1998-05-20
 *
 * current wisdom holds that the default operator should be one in which
 * both operands have the same type (there will only be one such
 * operator)
 * 當(dāng)前我們認(rèn)為 : 默認(rèn)操作符應(yīng)該是兩個(gè)操作數(shù)具有相同類型的操作符(只有一個(gè)這樣的操作符).
 *
 * 7.27.93 - I have decided not to do this; it's too hard to justify, and
 * it's easy enough to typecast explicitly - avi
 * [the rest of this routine was commented out since then - ay]
 *
 * 6/23/95 - I don't complete agree with avi. In particular, casting
 * floats is a pain for users. Whatever the rationale behind not doing
 * this is, I need the following special case to work.
 *
 * In the WHERE clause of a query, if a float is specified without
 * quotes, we treat it as float8. I added the float48* operators so
 * that we can operate on float4 and float8. But now we have more than
 * one matching operator if the right arg is unknown (eg. float
 * specified with quotes). This break some stuff in the regression
 * test where there are floats in quotes not properly casted. Below is
 * the solution. In addition to requiring the operator operates on the
 * same type for both operands [as in the code Avi originally
 * commented out], we also require that the operators be equivalent in
 * some sense. (see equivalentOpersAfterPromotion for details.)
 * - ay 6/95
 * 在WHERE語句中,如果float不帶引號(hào),PG會(huì)把該值視為float8類型.
 * 添加了float48*操作符的目的是可以處理float4和float8兩種類型.
 * 但如果右操作數(shù)的類型是unknown(如帶有引號(hào)的浮點(diǎn)數(shù))的話,會(huì)有超過一個(gè)匹配的operator存在.
 * 這會(huì)導(dǎo)致回歸測(cè)試中出現(xiàn)浮點(diǎn)數(shù)使用引號(hào)引住而沒有被正確轉(zhuǎn)換的情況而失敗.
 * 除了要求操作符在同樣類型的操作數(shù)外,還要求操作符在某些場(chǎng)景是等價(jià)的.
 */
FuncCandidateList
func_select_candidate(int nargs,
                      Oid *input_typeids,
                      FuncCandidateList candidates)
{
    FuncCandidateList current_candidate,
                first_candidate,
                last_candidate;
    Oid        *current_typeids;
    Oid         current_type;
    int         i;
    int         ncandidates;
    int         nbestMatch,
                nmatch,
                nunknowns;
    Oid         input_base_typeids[FUNC_MAX_ARGS];
    TYPCATEGORY slot_category[FUNC_MAX_ARGS],
                current_category;
    bool        current_is_preferred;
    bool        slot_has_preferred_type[FUNC_MAX_ARGS];
    bool        resolved_unknowns;
    /* protect local fixed-size arrays */
    //校驗(yàn)
    if (nargs > FUNC_MAX_ARGS)
        ereport(ERROR,
                (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
                 errmsg_plural("cannot pass more than %d argument to a function",
                               "cannot pass more than %d arguments to a function",
                               FUNC_MAX_ARGS,
                               FUNC_MAX_ARGS)));
    /*
     * If any input types are domains, reduce them to their base types. This
     * ensures that we will consider functions on the base type to be "exact
     * matches" in the exact-match heuristic; it also makes it possible to do
     * something useful with the type-category heuristics. Note that this
     * makes it difficult, but not impossible, to use functions declared to
     * take a domain as an input datatype.  Such a function will be selected
     * over the base-type function only if it is an exact match at all
     * argument positions, and so was already chosen by our caller.
     *
     * While we're at it, count the number of unknown-type arguments for use
     * later.
     * 計(jì)算unknown類型的參數(shù)個(gè)數(shù)
     */
    /*
    If any input argument is of a domain type, treat it as being of the domain's 
    base type for all subsequent steps. This ensures that domains act like their 
    base types for purposes of ambiguous-operator resolution.
    */
    nunknowns = 0;
    for (i = 0; i < nargs; i++)
    {
        if (input_typeids[i] != UNKNOWNOID)
            input_base_typeids[i] = getBaseType(input_typeids[i]);//基本類型
        else
        {
            //unknown 類型
            /* no need to call getBaseType on UNKNOWNOID */
            input_base_typeids[i] = UNKNOWNOID;
            nunknowns++;
        }
    }
    /*
     * Run through all candidates and keep those with the most matches on
     * exact types. Keep all candidates if none match.
     * 遍歷所有候選,保留那些類型一致的那些.如無匹配的,保留所有候選.
     */
    /*
    Run through all candidates and keep those with the most exact matches on input types.
    Keep all candidates if none have exact matches. If only one candidate remains, use it; 
    else continue to the next step.
    */
    ncandidates = 0;//候選數(shù)
    nbestMatch = 0;//最佳匹配數(shù)
    last_candidate = NULL;//最后一個(gè)候選
    for (current_candidate = candidates;
         current_candidate != NULL;
         current_candidate = current_candidate->next)//遍歷
    {
        //獲取候選函數(shù)的參數(shù)
        current_typeids = current_candidate->args;
        nmatch = 0;
        for (i = 0; i < nargs; i++)
        {
            //計(jì)算參數(shù)匹配個(gè)數(shù)
            if (input_base_typeids[i] != UNKNOWNOID &&
                current_typeids[i] == input_base_typeids[i])
                nmatch++;
        }
        /* take this one as the best choice so far? */
        //就拿這個(gè)作為最好的選擇?
        if ((nmatch > nbestMatch) || (last_candidate == NULL))
        {
            //1.比最佳參數(shù)匹配個(gè)數(shù)要大,調(diào)整最佳匹配數(shù)(參數(shù)個(gè)數(shù))
            //2.last_candidate == NULL,第一次循環(huán)
            nbestMatch = nmatch;
            candidates = current_candidate;
            last_candidate = current_candidate;
            ncandidates = 1;
        }
        /* no worse than the last choice, so keep this one too? */
        //不會(huì)比最后一個(gè)選項(xiàng)更糟,所以也保留這個(gè)選項(xiàng)
        else if (nmatch == nbestMatch)
        {
            //放到鏈表中
            last_candidate->next = current_candidate;
            last_candidate = current_candidate;
            ncandidates++;
        }
        /* otherwise, don't bother keeping this one... */
        //否則,無需保留
    }
    if (last_candidate)         /* terminate rebuilt list */
        last_candidate->next = NULL;
    if (ncandidates == 1)//只有一個(gè)候選,返回
        return candidates;
    /*
     * Still too many candidates? Now look for candidates which have either
     * exact matches or preferred types at the args that will require
     * coercion. (Restriction added in 7.4: preferred type must be of same
     * category as input type; give no preference to cross-category
     * conversions to preferred types.)  Keep all candidates if none match.
     * 仍有太多的候選?
     * 檢索args精確匹配或強(qiáng)制類型轉(zhuǎn)換后有首選類型的候選項(xiàng).
     * (首選類型必須與輸入類型是相同的目錄).
     * 如無匹配,保留所有候選.
     */
    /*
    Run through all candidates and keep those that accept preferred types 
    (of the input data type's type category) at the most positions where type 
    conversion will be required. Keep all candidates if none accept preferred types. 
    If only one candidate remains, use it; else continue to the next step.
    */
    for (i = 0; i < nargs; i++) /* avoid multiple lookups */
        slot_category[i] = TypeCategory(input_base_typeids[i]);//獲取類型目錄
    ncandidates = 0;
    nbestMatch = 0;
    last_candidate = NULL;
    for (current_candidate = candidates;
         current_candidate != NULL;
         current_candidate = current_candidate->next)//遍歷
    {
        current_typeids = current_candidate->args;//參數(shù)
        nmatch = 0;
        for (i = 0; i < nargs; i++)
        {
            if (input_base_typeids[i] != UNKNOWNOID)
            {
                if (current_typeids[i] == input_base_typeids[i] ||
                    IsPreferredType(slot_category[i], current_typeids[i]))
                    nmatch++;//不要求精確匹配,存在首選項(xiàng)一樣的參數(shù)也可以了
            }
        }
        if ((nmatch > nbestMatch) || (last_candidate == NULL))
        {
            //1.比最佳參數(shù)匹配個(gè)數(shù)要大,調(diào)整最佳匹配數(shù)(參數(shù)個(gè)數(shù))
            //2.last_candidate == NULL,第一次循環(huán)
            nbestMatch = nmatch;
            candidates = current_candidate;
            last_candidate = current_candidate;
            ncandidates = 1;
        }
        else if (nmatch == nbestMatch)
        {
            //保留跟最佳匹配一樣的候選
            last_candidate->next = current_candidate;
            last_candidate = current_candidate;
            ncandidates++;
        }
    }
    if (last_candidate)         /* terminate rebuilt list */
        last_candidate->next = NULL;
    if (ncandidates == 1)
        return candidates;//
    /*
     * Still too many candidates?  Try assigning types for the unknown inputs.
     * 仍有過多的候選?嘗試為unknown類型賦值
     *
     * If there are no unknown inputs, we have no more heuristics that apply,
     * and must fail.
     * 如無unknown類型,沒有更多的啟發(fā)式可用,就此失敗
     */
    if (nunknowns == 0)//失敗
        return NULL;            /* failed to select a best candidate */
    /*
     * The next step examines each unknown argument position to see if we can
     * determine a "type category" for it.  If any candidate has an input
     * datatype of STRING category, use STRING category (this bias towards
     * STRING is appropriate since unknown-type literals look like strings).
     * Otherwise, if all the candidates agree on the type category of this
     * argument position, use that category.  Otherwise, fail because we
     * cannot determine a category.
     * 下一步,檢查每一個(gè)unknown類型的參數(shù)位置來確定是否可以找到該參數(shù)的類型目錄.
     * 如果候選存在STRING目錄的輸入數(shù)據(jù)類型,使用STRING目錄(因?yàn)閡nknown literals看起來像是string)
     * 否則,如果所有候選認(rèn)同該參數(shù)位置上的類型目錄,則使用此目錄.
     * 不能確定目錄,則失敗.
     *
     * If we are able to determine a type category, also notice whether any of
     * the candidates takes a preferred datatype within the category.
     * 如果可以確定類型目錄,還要注意候選項(xiàng)中是否有一個(gè)在目錄中采用首選數(shù)據(jù)類型.
     *
     * Having completed this examination, remove candidates that accept the
     * wrong category at any unknown position.  Also, if at least one
     * candidate accepted a preferred type at a position, remove candidates
     * that accept non-preferred types.  If just one candidate remains, return
     * that one.  However, if this rule turns out to reject all candidates,
     * keep them all instead.
     * 完成該檢查后,去掉在unknown位置上接受錯(cuò)誤目錄的候選.
     * 同時(shí),如果至少有一個(gè)候選接受該位置上的首選類型,去掉無首選類型的候選.
     * 如果只有一個(gè)候選保留,則返回該候選.
     * 否則,拒絕所有的候選,保留所有.
     */
    /*
    If any input arguments are unknown, check the type categories accepted at 
    those argument positions by the remaining candidates. At each position, 
    select the string category if any candidate accepts that category. 
    (This bias towards string is appropriate since an unknown-type literal looks like a string.) 
    Otherwise, if all the remaining candidates accept the same type category, 
    select that category; otherwise fail because the correct choice cannot be 
    deduced without more clues. Now discard candidates that do not accept the 
    selected type category. Furthermore, if any candidate accepts a preferred 
    type in that category, discard candidates that accept non-preferred types 
    for that argument. Keep all candidates if none survive these tests. 
    If only one candidate remains, use it; else continue to the next step.
    */
    resolved_unknowns = false;//是否已解決unknown類型標(biāo)記
    for (i = 0; i < nargs; i++)//遍歷參數(shù)
    {
        bool        have_conflict;//是否存在沖突標(biāo)記
        if (input_base_typeids[i] != UNKNOWNOID)
            continue;//非unknown類型
        resolved_unknowns = true;   /* 假定可以搞掂 assume we can do it */
        slot_category[i] = TYPCATEGORY_INVALID;
        slot_has_preferred_type[i] = false;
        have_conflict = false;
        for (current_candidate = candidates;
             current_candidate != NULL;
             current_candidate = current_candidate->next)//遍歷所有候選
        {
            current_typeids = current_candidate->args;
            current_type = current_typeids[i];
            get_type_category_preferred(current_type,
                                        &current_category,
                                        &current_is_preferred);
            if (slot_category[i] == TYPCATEGORY_INVALID)
            {
                /* first candidate */
                //第一個(gè)候選
                slot_category[i] = current_category;
                slot_has_preferred_type[i] = current_is_preferred;
            }
            else if (current_category == slot_category[i])
            {
                /* more candidates in same category */
                //同樣的目錄有更多的候選
                slot_has_preferred_type[i] |= current_is_preferred;
            }
            else
            {
                /* category conflict! */
                //目錄沖突
                if (current_category == TYPCATEGORY_STRING)
                {
                    /* STRING always wins if available */
                    //如可能,首選STRING
                    slot_category[i] = current_category;
                    slot_has_preferred_type[i] = current_is_preferred;
                }
                else
                {
                    /*
                     * Remember conflict, but keep going (might find STRING)
                     * 存在沖突,但繼續(xù)處理
                     */
                    have_conflict = true;
                }
            }
        }
        if (have_conflict && slot_category[i] != TYPCATEGORY_STRING)
        {
            //存在沖突,并且目錄不是STRING
            /* Failed to resolve category conflict at this position */
            //無法解決沖突
            resolved_unknowns = false;
            break;
        }
    }
    if (resolved_unknowns)
    {
        //已解決了沖突
        /* Strip non-matching candidates */
        ncandidates = 0;
        first_candidate = candidates;
        last_candidate = NULL;
        for (current_candidate = candidates;
             current_candidate != NULL;
             current_candidate = current_candidate->next)//再次遍歷
        {
            bool        keepit = true;
            //如果至少有一個(gè)候選接受該位置上的首選類型,去掉無首選類型的候選.
            current_typeids = current_candidate->args;
            for (i = 0; i < nargs; i++)//遍歷參數(shù)
            {
                if (input_base_typeids[i] != UNKNOWNOID)
                    continue;//非unknown參數(shù),跳過
                current_type = current_typeids[i];//當(dāng)前類型
                get_type_category_preferred(current_type,
                                            &current_category,
                                            &current_is_preferred);//首選類型
                if (current_category != slot_category[i])
                {
                    //當(dāng)前目錄不等于slot中的目錄,退出參數(shù)循環(huán)
                    keepit = false;
                    break;
                }
                if (slot_has_preferred_type[i] && !current_is_preferred)
                {
                    //存在首選類型但當(dāng)前首選類型為NULL,退出參數(shù)循環(huán)
                    keepit = false;
                    break;
                }
            }
            if (keepit)
            {
                /* keep this candidate */
                //保留該候選
                last_candidate = current_candidate;
                ncandidates++;
            }
            else
            {
                /* forget this candidate */
                //
                if (last_candidate)
                    last_candidate->next = current_candidate->next;
                else
                    first_candidate = current_candidate->next;
            }
        }
        /* if we found any matches, restrict our attention to those */
        if (last_candidate)
        {
            candidates = first_candidate;
            /* terminate rebuilt list */
            last_candidate->next = NULL;
        }
        if (ncandidates == 1)
            return candidates;
    }
    /*
     * Last gasp: if there are both known- and unknown-type inputs, and all
     * the known types are the same, assume the unknown inputs are also that
     * type, and see if that gives us a unique match.  If so, use that match.
     * 最后 : 如果存在known和unknown類型同時(shí)存在,
     *        并且已知類型是一樣的,那么假定unknown輸入也是該類型,然后看看是否有唯一匹配,如有則使用該候選.
     *
     * NOTE: for a binary operator with one unknown and one non-unknown input,
     * we already tried this heuristic in binary_oper_exact().  However, that
     * code only finds exact matches, whereas here we will handle matches that
     * involve coercion, polymorphic type resolution, etc.
     * 注意 : 對(duì)于帶有一個(gè)unknown和一個(gè)已知類型參數(shù)的二元操作符,
     * 已在binary_oper_exact()函數(shù)中使用該啟發(fā)式.
     * 但是,那些代碼只能發(fā)現(xiàn)準(zhǔn)確的匹配,而這里我們將處理涉及強(qiáng)制、多態(tài)類型解析等的匹配。
     */
    /*
    If there are both unknown and known-type arguments, and all the known-type arguments 
    have the same type, assume that the unknown arguments are also of that type, and check 
    which candidates can accept that type at the unknown-argument positions. 
    If exactly one candidate passes this test, use it. Otherwise, fail.
    */
    if (nunknowns < nargs)
    {
        Oid         known_type = UNKNOWNOID;
        for (i = 0; i < nargs; i++)//找到基本類型,找不到則失敗
        {
            if (input_base_typeids[i] == UNKNOWNOID)
                continue;
            if (known_type == UNKNOWNOID)   /* first known arg? */
                known_type = input_base_typeids[i];
            else if (known_type != input_base_typeids[i])
            {
                /* oops, not all match */
                known_type = UNKNOWNOID;
                break;
            }
        }
        if (known_type != UNKNOWNOID)//找到了基本類型
        {
            /* okay, just one known type, apply the heuristic */
            for (i = 0; i < nargs; i++)
                input_base_typeids[i] = known_type;//使用該基本類型
            ncandidates = 0;
            last_candidate = NULL;
            for (current_candidate = candidates;
                 current_candidate != NULL;
                 current_candidate = current_candidate->next)//遍歷
            {
                current_typeids = current_candidate->args;
                if (can_coerce_type(nargs, input_base_typeids, current_typeids,
                                    COERCION_IMPLICIT))
                {
                    if (++ncandidates > 1)
                        break;  /* not unique, give up */
                    last_candidate = current_candidate;
                }
            }
            if (ncandidates == 1)
            {
                /* successfully identified a unique match */
                //成功!
                last_candidate->next = NULL;
                return last_candidate;
            }
        }
    }
    //返回NULL
    return NULL;                /* failed to select a best candidate */
}                               /* func_select_candidate() */

三、跟蹤分析

測(cè)試腳本

create cast(integer as text) with inout as implicit;
select id||'X' from t_cast;

跟蹤分析

Breakpoint 1, func_select_candidate (nargs=2, input_typeids=0x7fff5f3ac6c0, candidates=0x2daa6f0) at parse_func.c:1021
1021        if (nargs > FUNC_MAX_ARGS)
(gdb) p *input_typeids
$6 = 23
(gdb) p *candidates
$7 = {next = 0x2daa720, pathpos = 0, oid = 654, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa718}
(gdb)  p *candidates->next
$8 = {next = 0x2daa7e0, pathpos = 0, oid = 2779, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa748}
(gdb) p *candidates->next->next
$9 = {next = 0x2daa810, pathpos = 0, oid = 374, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa808}
(gdb) p *candidates->next->next->next
$10 = {next = 0x0, pathpos = 0, oid = 2780, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa838}
(gdb) p *candidates->next->next->next->next
Cannot access memory at address 0x0
(gdb) n
1042        nunknowns = 0;
(gdb) 
1043        for (i = 0; i < nargs; i++)
(gdb) 
1045            if (input_typeids[i] != UNKNOWNOID)
(gdb) 
1046                input_base_typeids[i] = getBaseType(input_typeids[i]);
(gdb) 
1043        for (i = 0; i < nargs; i++)
(gdb) p input_base_typeids[0]
$12 = 23
(gdb) n
1045            if (input_typeids[i] != UNKNOWNOID)
(gdb) 
1050                input_base_typeids[i] = UNKNOWNOID;
(gdb) p input_typeids[i]
$13 = 705
(gdb) p UNKNOWNOID
$14 = 705
(gdb) n
1051                nunknowns++;
(gdb) 
1043        for (i = 0; i < nargs; i++)
(gdb) 
1059        ncandidates = 0;
(gdb) 
1060        nbestMatch = 0;
(gdb) 
1061        last_candidate = NULL;
(gdb) 
1062        for (current_candidate = candidates;
(gdb) 
1066            current_typeids = current_candidate->args;
(gdb) 
1067            nmatch = 0;
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1071                    current_typeids[i] == input_base_typeids[i])
(gdb) p current_typeids[i]
$15 = 25
(gdb) p input_base_typeids[i]
$16 = 23
(gdb) n
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1076            if ((nmatch > nbestMatch) || (last_candidate == NULL))
(gdb) 
1078                nbestMatch = nmatch;
(gdb) 
1079                candidates = current_candidate;
(gdb) 
1080                last_candidate = current_candidate;
(gdb) 
1081                ncandidates = 1;
(gdb) 
1064             current_candidate = current_candidate->next)
(gdb) 
1062        for (current_candidate = candidates;
(gdb) 
1066            current_typeids = current_candidate->args;
(gdb) 
1067            nmatch = 0;
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1071                    current_typeids[i] == input_base_typeids[i])
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1076            if ((nmatch > nbestMatch) || (last_candidate == NULL))
(gdb) 
1084            else if (nmatch == nbestMatch)
(gdb) 
1086                last_candidate->next = current_candidate;
(gdb) p *last_candidate
$17 = {next = 0x2daa720, pathpos = 0, oid = 654, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa718}
(gdb) p *current_candidate
$18 = {next = 0x2daa7e0, pathpos = 0, oid = 2779, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa748}
(gdb) n
1087                last_candidate = current_candidate;
(gdb) 
1088                ncandidates++;
(gdb) 
1064             current_candidate = current_candidate->next)
(gdb) 
1062        for (current_candidate = candidates;
(gdb) 
1066            current_typeids = current_candidate->args;
(gdb) 
1067            nmatch = 0;
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1071                    current_typeids[i] == input_base_typeids[i])
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1076            if ((nmatch > nbestMatch) || (last_candidate == NULL))
(gdb) p *candidates
$19 = {next = 0x2daa720, pathpos = 0, oid = 654, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa718}
(gdb) n
1084            else if (nmatch == nbestMatch)
(gdb) 
1086                last_candidate->next = current_candidate;
(gdb) 
1087                last_candidate = current_candidate;
(gdb) 
1088                ncandidates++;
(gdb) n
1064             current_candidate = current_candidate->next)
(gdb) 
1062        for (current_candidate = candidates;
(gdb) 
1066            current_typeids = current_candidate->args;
(gdb) 
1067            nmatch = 0;
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1071                    current_typeids[i] == input_base_typeids[i])
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1070                if (input_base_typeids[i] != UNKNOWNOID &&
(gdb) 
1068            for (i = 0; i < nargs; i++)
(gdb) 
1076            if ((nmatch > nbestMatch) || (last_candidate == NULL))
(gdb) 
1084            else if (nmatch == nbestMatch)
(gdb) 
1086                last_candidate->next = current_candidate;
(gdb) 
1087                last_candidate = current_candidate;
(gdb) 
1088                ncandidates++;
(gdb) 
1064             current_candidate = current_candidate->next)
(gdb) 
1062        for (current_candidate = candidates;
(gdb) 
1093        if (last_candidate)         /* terminate rebuilt list */
(gdb) 
1094            last_candidate->next = NULL;
(gdb) 
1096        if (ncandidates == 1)
(gdb) p *last_candidate
$20 = {next = 0x0, pathpos = 0, oid = 2780, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa838}
(gdb) n
1106        for (i = 0; i < nargs; i++) /* avoid multiple lookups */
(gdb) 
1107            slot_category[i] = TypeCategory(input_base_typeids[i]);
(gdb) 
1106        for (i = 0; i < nargs; i++) /* avoid multiple lookups */
(gdb) p slot_category[i] 
$21 = 78 'N'
(gdb) n
1107            slot_category[i] = TypeCategory(input_base_typeids[i]);
(gdb) 
1106        for (i = 0; i < nargs; i++) /* avoid multiple lookups */
(gdb) p slot_category[i] 
$22 = 88 'X'
(gdb) n
1108        ncandidates = 0;
(gdb) 
1109        nbestMatch = 0;
(gdb) 
1110        last_candidate = NULL;
(gdb) 
1111        for (current_candidate = candidates;
(gdb) 
1115            current_typeids = current_candidate->args;
(gdb) 
1116            nmatch = 0;
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1119                if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1121                    if (current_typeids[i] == input_base_typeids[i] ||
(gdb) p current_typeids[i]
$23 = 25
(gdb) n
1122                        IsPreferredType(slot_category[i], current_typeids[i]))
(gdb) 
1121                    if (current_typeids[i] == input_base_typeids[i] ||
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1119                if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1127            if ((nmatch > nbestMatch) || (last_candidate == NULL))
(gdb) 
1129                nbestMatch = nmatch;
(gdb) 
1130                candidates = current_candidate;
(gdb) 
1131                last_candidate = current_candidate;
(gdb) 
1132                ncandidates = 1;
(gdb) 
1113             current_candidate = current_candidate->next)
(gdb) 
1111        for (current_candidate = candidates;
(gdb) 
1115            current_typeids = current_candidate->args;
(gdb) 
1116            nmatch = 0;
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1119                if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1121                    if (current_typeids[i] == input_base_typeids[i] ||
(gdb) 
1122                        IsPreferredType(slot_category[i], current_typeids[i]))
(gdb) 
1121                    if (current_typeids[i] == input_base_typeids[i] ||
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1119                if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1127            if ((nmatch > nbestMatch) || (last_candidate == NULL))
(gdb) 
1134            else if (nmatch == nbestMatch)
(gdb) 
1136                last_candidate->next = current_candidate;
(gdb) 
1137                last_candidate = current_candidate;
(gdb) 
1138                ncandidates++;
(gdb) 
1113             current_candidate = current_candidate->next)
(gdb) 
1111        for (current_candidate = candidates;
(gdb) 
1115            current_typeids = current_candidate->args;
(gdb) 
1116            nmatch = 0;
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1119                if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1121                    if (current_typeids[i] == input_base_typeids[i] ||
(gdb) 
1122                        IsPreferredType(slot_category[i], current_typeids[i]))
(gdb) p *last_candidate
$24 = {next = 0x2daa7e0, pathpos = 0, oid = 2779, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa748}
(gdb) n
1121                    if (current_typeids[i] == input_base_typeids[i] ||
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1119                if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1127            if ((nmatch > nbestMatch) || (last_candidate == NULL))
(gdb) 
1134            else if (nmatch == nbestMatch)
(gdb) 
1136                last_candidate->next = current_candidate;
(gdb) 
1137                last_candidate = current_candidate;
(gdb) 
1138                ncandidates++;
(gdb) 
1113             current_candidate = current_candidate->next)
(gdb) 
1111        for (current_candidate = candidates;
(gdb) 
1115            current_typeids = current_candidate->args;
(gdb) 
1116            nmatch = 0;
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1119                if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1121                    if (current_typeids[i] == input_base_typeids[i] ||
(gdb) 
1122                        IsPreferredType(slot_category[i], current_typeids[i]))
(gdb) 
1121                    if (current_typeids[i] == input_base_typeids[i] ||
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1119                if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1117            for (i = 0; i < nargs; i++)
(gdb) 
1127            if ((nmatch > nbestMatch) || (last_candidate == NULL))
(gdb) 
1134            else if (nmatch == nbestMatch)
(gdb) 
1136                last_candidate->next = current_candidate;
(gdb) 
1137                last_candidate = current_candidate;
(gdb) 
1138                ncandidates++;
(gdb) 
1113             current_candidate = current_candidate->next)
(gdb) 
1111        for (current_candidate = candidates;
(gdb) 
1142        if (last_candidate)         /* terminate rebuilt list */
(gdb) 
1143            last_candidate->next = NULL;
(gdb) 
1145        if (ncandidates == 1)
(gdb) 
1154        if (nunknowns == 0)
(gdb) 
1176        resolved_unknowns = false;
(gdb) 
1177        for (i = 0; i < nargs; i++)
(gdb) 
1181            if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1182                continue;
(gdb) 
1177        for (i = 0; i < nargs; i++)
(gdb) 
1181            if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1183            resolved_unknowns = true;   /* assume we can do it */
(gdb) 
1184            slot_category[i] = TYPCATEGORY_INVALID;
(gdb) 
1185            slot_has_preferred_type[i] = false;
(gdb) 
1186            have_conflict = false;
(gdb) 
1187            for (current_candidate = candidates;
(gdb) 
1191                current_typeids = current_candidate->args;
(gdb) 
1192                current_type = current_typeids[i];
(gdb) 
1193                get_type_category_preferred(current_type,
(gdb) 
1196                if (slot_category[i] == TYPCATEGORY_INVALID)
(gdb) p current_type
$25 = 25
(gdb) p current_category
$26 = 83 'S'
(gdb) p current_is_preferred
$27 = true
(gdb) p slot_category[i]
$28 = 0 '\000'
(gdb) n
1199                    slot_category[i] = current_category;
(gdb) 
1200                    slot_has_preferred_type[i] = current_is_preferred;
(gdb) 
1189                 current_candidate = current_candidate->next)
(gdb) p current_category
$29 = 83 'S'
(gdb) p current_is_preferred
$30 = true
(gdb) n
1187            for (current_candidate = candidates;
(gdb) 
1191                current_typeids = current_candidate->args;
(gdb) 
1192                current_type = current_typeids[i];
(gdb) 
1193                get_type_category_preferred(current_type,
(gdb) 
1196                if (slot_category[i] == TYPCATEGORY_INVALID)
(gdb) p current_category
$31 = 80 'P'
(gdb) p current_is_preferred
$32 = false
(gdb) n
1202                else if (current_category == slot_category[i])
(gdb) 
1210                    if (current_category == TYPCATEGORY_STRING)
(gdb) 
1221                        have_conflict = true;
(gdb) 
1189                 current_candidate = current_candidate->next)
(gdb) 
1187            for (current_candidate = candidates;
(gdb) 
1191                current_typeids = current_candidate->args;
(gdb) 
1192                current_type = current_typeids[i];
(gdb) 
1193                get_type_category_preferred(current_type,
(gdb) 
1196                if (slot_category[i] == TYPCATEGORY_INVALID)
(gdb) p current_type
$33 = 2277
(gdb) p current_is_preferred
$34 = false
(gdb) p current_category
$35 = 80 'P'
(gdb) n
1202                else if (current_category == slot_category[i])
(gdb) 
1210                    if (current_category == TYPCATEGORY_STRING)
(gdb) 
1221                        have_conflict = true;
(gdb) 
1189                 current_candidate = current_candidate->next)
(gdb) 
1187            for (current_candidate = candidates;
(gdb) 
1191                current_typeids = current_candidate->args;
(gdb) 
1192                current_type = current_typeids[i];
(gdb) 
1193                get_type_category_preferred(current_type,
(gdb) 
1196                if (slot_category[i] == TYPCATEGORY_INVALID)
(gdb) 
1202                else if (current_category == slot_category[i])
(gdb) 
1205                    slot_has_preferred_type[i] |= current_is_preferred;
(gdb) p current_category
$36 = 83 'S'
(gdb) n
1189                 current_candidate = current_candidate->next)
(gdb) 
1187            for (current_candidate = candidates;
(gdb) 
1225            if (have_conflict && slot_category[i] != TYPCATEGORY_STRING)
(gdb) 
1177        for (i = 0; i < nargs; i++)
(gdb) p resolved_unknowns
$37 = true
(gdb) n
1233        if (resolved_unknowns)
(gdb) 
1236            ncandidates = 0;
(gdb) 
1237            first_candidate = candidates;
(gdb) 
1238            last_candidate = NULL;
(gdb) 
1239            for (current_candidate = candidates;
(gdb) 
1243                bool        keepit = true;
(gdb) 
1245                current_typeids = current_candidate->args;
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1248                    if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1249                        continue;
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1248                    if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1250                    current_type = current_typeids[i];
(gdb) 
1251                    get_type_category_preferred(current_type,
(gdb) p current_type
$38 = 25
(gdb) n
1254                    if (current_category != slot_category[i])
(gdb) p current_category
$39 = 83 'S'
(gdb) p slot_category[i]
$40 = 83 'S'
(gdb) n
1259                    if (slot_has_preferred_type[i] && !current_is_preferred)
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1265                if (keepit)
(gdb) 
1268                    last_candidate = current_candidate;
(gdb) p *current_candidate
$41 = {next = 0x2daa720, pathpos = 0, oid = 654, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa718}
(gdb) n
1269                    ncandidates++;
(gdb) 
1241                 current_candidate = current_candidate->next)
(gdb) n
1239            for (current_candidate = candidates;
(gdb) 
1243                bool        keepit = true;
(gdb) 
1245                current_typeids = current_candidate->args;
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1248                    if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1249                        continue;
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1248                    if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1250                    current_type = current_typeids[i];
(gdb) 
1251                    get_type_category_preferred(current_type,
(gdb) 
1254                    if (current_category != slot_category[i])
(gdb) p current_type
$42 = 2776
(gdb) p current_category
$43 = 80 'P'
(gdb) n
1256                        keepit = false;
(gdb) 
1257                        break;
(gdb) 
1265                if (keepit)
(gdb) 
1274                    if (last_candidate)
(gdb) 
1275                        last_candidate->next = current_candidate->next;
(gdb) 
1241                 current_candidate = current_candidate->next)
(gdb) p *last_candidate
$44 = {next = 0x2daa7e0, pathpos = 0, oid = 654, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa718}
(gdb) n
1239            for (current_candidate = candidates;
(gdb) 
1243                bool        keepit = true;
(gdb) 
1245                current_typeids = current_candidate->args;
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1248                    if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1249                        continue;
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1248                    if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1250                    current_type = current_typeids[i];
(gdb) 
1251                    get_type_category_preferred(current_type,
(gdb) p current_type
$45 = 2277
(gdb) p current_category
$46 = 80 'P'
(gdb) n
1254                    if (current_category != slot_category[i])
(gdb) 
1256                        keepit = false;
(gdb) 
1257                        break;
(gdb) 
1265                if (keepit)
(gdb) 
1274                    if (last_candidate)
(gdb) 
1275                        last_candidate->next = current_candidate->next;
(gdb) 
1241                 current_candidate = current_candidate->next)
(gdb) 
1239            for (current_candidate = candidates;
(gdb) 
1243                bool        keepit = true;
(gdb) 
1245                current_typeids = current_candidate->args;
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1248                    if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1249                        continue;
(gdb) 
1246                for (i = 0; i < nargs; i++)
(gdb) 
1248                    if (input_base_typeids[i] != UNKNOWNOID)
(gdb) 
1250                    current_type = current_typeids[i];
(gdb) 
1251                    get_type_category_preferred(current_type,
(gdb) 
1254                    if (current_category != slot_category[i])
(gdb) 
1259                    if (slot_has_preferred_type[i] && !current_is_preferred)
(gdb) p current_category
$47 = 83 'S'
(gdb) p *current_candidate
$48 = {next = 0x0, pathpos = 0, oid = 2780, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa838}
(gdb) n
1246                for (i = 0; i < nargs; i++)
(gdb) 
1265                if (keepit)
(gdb) 
1268                    last_candidate = current_candidate;
(gdb) 
1269                    ncandidates++;
(gdb) 
1241                 current_candidate = current_candidate->next)
(gdb) 
1239            for (current_candidate = candidates;
(gdb) 
1282            if (last_candidate)
(gdb) 
1284                candidates = first_candidate;
(gdb) p *first_candidate
$49 = {next = 0x2daa810, pathpos = 0, oid = 654, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa718}
(gdb) p *last_candidate
$50 = {next = 0x0, pathpos = 0, oid = 2780, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa838}
(gdb) n
1286                last_candidate->next = NULL;
(gdb) n
1289            if (ncandidates == 1)
(gdb) 
1303        if (nunknowns < nargs)
(gdb) p *candidates
$51 = {next = 0x2daa810, pathpos = 0, oid = 654, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa718}
(gdb) p *candidates->next
$52 = {next = 0x0, pathpos = 0, oid = 2780, nargs = 2, nvargs = 0, ndargs = 0, argnumbers = 0x0, args = 0x2daa838}
(gdb) n
1305            Oid         known_type = UNKNOWNOID;
(gdb) 
1307            for (i = 0; i < nargs; i++)
(gdb) 
1309                if (input_base_typeids[i] == UNKNOWNOID)
(gdb) 
1311                if (known_type == UNKNOWNOID)   /* first known arg? */
(gdb) 
1312                    known_type = input_base_typeids[i];
(gdb) 
1307            for (i = 0; i < nargs; i++)
(gdb) p known_type
$53 = 23
(gdb) n
1309                if (input_base_typeids[i] == UNKNOWNOID)
(gdb) 
1310                    continue;
(gdb) 
1307            for (i = 0; i < nargs; i++)
(gdb) 
1321            if (known_type != UNKNOWNOID)
(gdb) 
1324                for (i = 0; i < nargs; i++)
(gdb) 
1325                    input_base_typeids[i] = known_type;
(gdb) 
1324                for (i = 0; i < nargs; i++)
(gdb) p known_type
$54 = 23
(gdb) n
1325                    input_base_typeids[i] = known_type;
(gdb) 
1324                for (i = 0; i < nargs; i++)
(gdb) 
1326                ncandidates = 0;
(gdb) 
1327                last_candidate = NULL;
(gdb) 
1328                for (current_candidate = candidates;
(gdb) 
1332                    current_typeids = current_candidate->args;
(gdb) 
1333                    if (can_coerce_type(nargs, input_base_typeids, current_typeids,
(gdb) 
1336                        if (++ncandidates > 1)
(gdb) n
1338                        last_candidate = current_candidate;
(gdb) 
1330                     current_candidate = current_candidate->next)
(gdb) 
1328                for (current_candidate = candidates;
(gdb) 
1332                    current_typeids = current_candidate->args;
(gdb) 
1333                    if (can_coerce_type(nargs, input_base_typeids, current_typeids,
(gdb) 
1336                        if (++ncandidates > 1)
(gdb) 
1337                            break;  /* not unique, give up */
(gdb) 
1341                if (ncandidates == 1)
(gdb) 
1350        return NULL;                /* failed to select a best candidate */
(gdb)

感謝各位的閱讀,以上就是“PostgreSQL隱式類型轉(zhuǎn)換中選擇操作符的實(shí)現(xiàn)函數(shù)是什么”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)PostgreSQL隱式類型轉(zhuǎn)換中選擇操作符的實(shí)現(xiàn)函數(shù)是什么這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI