返回顶部
首页 > 资讯 > 数据库 >PostgreSQL中用于初始化查询执行计划函数是哪个
  • 622
分享到

PostgreSQL中用于初始化查询执行计划函数是哪个

2024-04-02 19:04:59 622人浏览 八月长安
摘要

本篇内容主要讲解“postgresql中用于初始化查询执行计划函数是哪个”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Postgresql中用于初始化查询执行计

本篇内容主要讲解“postgresql中用于初始化查询执行计划函数是哪个”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Postgresql中用于初始化查询执行计划函数是哪个”吧!

一、数据结构

EState
执行器在调用时的主要工作状态


typedef struct EState
{
    nodeTag     type;//标记

    
    //所有查询类型的基础状态
    ScanDirection es_direction; 
    Snapshot    es_snapshot;    
    Snapshot    es_crosscheck_snapshot; 
    List       *es_range_table; 
    struct RangeTblEntry **es_range_table_array;    
    Index       es_range_table_size;    
    Relation   *es_relations;   
    struct ExecRowMark **es_rowmarks;   
    PlannedStmt *es_plannedstmt;    
    const char *es_sourceText;  

    JunkFilter *es_junkFilter;  

    
    //如查询可以插入/删除元组,这里记录了命令ID
    CommandId   es_output_cid;

    
    //insert/update/delete 目标表信息
    ResultRelInfo *es_result_relations; 
    int         es_num_result_relations;    
    ResultRelInfo *es_result_relation_info; 

    
    ResultRelInfo *es_root_result_relations;    
    int         es_num_root_result_relations;   

    
    List       *es_tuple_routing_result_relations;

    
    //用于触发触发器的信息
    List       *es_trig_target_relations;   
    TupleTableSlot *es_trig_tuple_slot; 
    TupleTableSlot *es_trig_oldtup_slot;    
    TupleTableSlot *es_trig_newtup_slot;    

    
    //参数信息
    ParamListInfo es_param_list_info;   
    ParamExecData *es_param_exec_vals;  

    QueryEnvironment *es_queryEnv;  

    
    //其他工作状态
    MemoryContext es_query_cxt; 

    List       *es_tupleTable;  

    uint64      es_processed;   
    Oid         es_lastoid;     

    int         es_top_eflags;  
    int         es_instrument;  
    bool        es_finished;    

    List       *es_exprcontexts;    

    List       *es_subplanstates;   

    List       *es_auxmodifytables; 

    
    ExprContext *es_per_tuple_exprcontext;

    
    HeapTuple  *es_epQtuple;    
    bool       *es_epqTupleSet; 
    bool       *es_epqScanDone; 

    bool        es_use_parallel_mode;   

    
    //用于并行执行的每个查询共享内存区域。
    struct dsa_area *es_query_dsa;

    
    int         es_jit_flags;
    struct JitContext *es_jit;
    struct JitInstrumentation *es_jit_worker_instr;
} EState;

PlanState
PlanState是所有PlanState-type节点的虚父类(请参照面向对象的虚类)


typedef struct PlanState
{
    NodeTag     type;//节点类型

    Plan       *plan;           

    EState     *state;          

    ExecProcNodeMtd ExecProcNode;   
    ExecProcNodeMtd ExecProcNodeReal;   

    Instrumentation *instrument;    
    WorkerInstrumentation *worker_instrument;   

    
    //worker相应的JIT instrumentation
    struct SharedJitInstrumentation *worker_jit_instrument;

    
    ExprState  *qual;           
    struct PlanState *lefttree; 
    struct PlanState *righttree;

    List       *initPlan;       
    List       *subPlan;        

    
    Bitmapset  *chgParam;       

    
    TupleDesc ps_ResultTupleDesc;   
    TupleTableSlot *ps_ResultTupleSlot; 
    ExprContext *ps_ExprContext;    
    ProjectionInfo *ps_ProjInfo;    

    
    TupleDesc   scandesc;
} PlanState;

二、源码解读

InitPlan函数初始化查询执行计划:打开文件/分配存储空间并启动规则管理器.


static void
InitPlan(QueryDesc *queryDesc, int eflags)
{
    CmdType     operation = queryDesc->operation;//命令类型
    PlannedStmt *plannedstmt = queryDesc->plannedstmt;//已规划的语句指针
    Plan       *plan = plannedstmt->planTree;//计划树
    List       *rangeTable = plannedstmt->rtable;//RTE链表
    EState     *estate = queryDesc->estate;//参见数据结构
    PlanState  *planstate;//参见数据结构
    TupleDesc   tupType;//参见数据结构
    ListCell   *l;
    int         i;

    
    ExecCheckRTPerms(rangeTable, true);

    
    ExecInitRangeTable(estate, rangeTable);

    estate->es_plannedstmt = plannedstmt;

    
    if (plannedstmt->resultRelations)//存在resultRelations
    {
        List       *resultRelations = plannedstmt->resultRelations;//结果Relation链表
        int         numResultRelations = list_length(resultRelations);//链表大小
        ResultRelInfo *resultRelInfos;//ResultRelInfo数组
        ResultRelInfo *resultRelInfo;//ResultRelInfo指针

        resultRelInfos = (ResultRelInfo *)
            palloc(numResultRelations * sizeof(ResultRelInfo));//分配空间
        resultRelInfo = resultRelInfos;//指针赋值
        foreach(l, resultRelations)//遍历链表
        {
            Index       resultRelationIndex = lfirst_int(l);
            Relation    resultRelation;

            resultRelation = ExecGetRangeTableRelation(estate,
                                                       resultRelationIndex);//获取结果Relation
            InitResultRelInfo(resultRelInfo,
                              resultRelation,
                              resultRelationIndex,
                              NULL,
                              estate->es_instrument);//初始化ResultRelInfo
            resultRelInfo++;//处理下一个ResultRelInfo
        }
        estate->es_result_relations = resultRelInfos;//赋值
        estate->es_num_result_relations = numResultRelations;

        
        //设置es_result_relation_info为NULL,除了ModifyTable
        estate->es_result_relation_info = NULL;

        
        if (plannedstmt->rootResultRelations)//存在rootResultRelations(并行处理)
        {
            int         num_roots = list_length(plannedstmt->rootResultRelations);

            resultRelInfos = (ResultRelInfo *)
                palloc(num_roots * sizeof(ResultRelInfo));
            resultRelInfo = resultRelInfos;
            foreach(l, plannedstmt->rootResultRelations)
            {
                Index       resultRelIndex = lfirst_int(l);
                Relation    resultRelDesc;

                resultRelDesc = ExecGetRangeTableRelation(estate,
                                                          resultRelIndex);
                InitResultRelInfo(resultRelInfo,
                                  resultRelDesc,
                                  resultRelIndex,
                                  NULL,
                                  estate->es_instrument);
                resultRelInfo++;
            }

            estate->es_root_result_relations = resultRelInfos;
            estate->es_num_root_result_relations = num_roots;
        }
        else
        {
            estate->es_root_result_relations = NULL;
            estate->es_num_root_result_relations = 0;
        }
    }
    else//不存在resultRelations
    {
        
        estate->es_result_relations = NULL;
        estate->es_num_result_relations = 0;
        estate->es_result_relation_info = NULL;
        estate->es_root_result_relations = NULL;
        estate->es_num_root_result_relations = 0;
    }

    
    if (plannedstmt->rowMarks)//如存在rowMarks
    {
        estate->es_rowmarks = (ExecRowMark **)
            palloc0(estate->es_range_table_size * sizeof(ExecRowMark *));
        foreach(l, plannedstmt->rowMarks)
        {
            PlanRowMark *rc = (PlanRowMark *) lfirst(l);
            Oid         relid;
            Relation    relation;
            ExecRowMark *erm;

            
            if (rc->isParent)
                continue;

            
            relid = exec_rt_fetch(rc->rti, estate)->relid;

            
            switch (rc->markType)
            {
                case ROW_MARK_EXCLUSIVE:
                case ROW_MARK_NOKEYEXCLUSIVE:
                case ROW_MARK_SHARE:
                case ROW_MARK_KEYSHARE:
                case ROW_MARK_REFERENCE:
                    relation = ExecGetRangeTableRelation(estate, rc->rti);
                    break;
                case ROW_MARK_COPY:
                    
                    relation = NULL;
                    break;
                default:
                    elog(ERROR, "unrecognized markType: %d", rc->markType);
                    relation = NULL;    
                    break;
            }

            
            if (relation)
                CheckValidRowMarkRel(relation, rc->markType);

            erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
            erm->relation = relation;
            erm->relid = relid;
            erm->rti = rc->rti;
            erm->prti = rc->prti;
            erm->rowmarkId = rc->rowmarkId;
            erm->markType = rc->markType;
            erm->strength = rc->strength;
            erm->waitPolicy = rc->waitPolicy;
            erm->erMactive = false;
            ItemPointerSetInvalid(&(erm->curCtid));
            erm->ermExtra = NULL;

            Assert(erm->rti > 0 && erm->rti <= estate->es_range_table_size &&
                   estate->es_rowmarks[erm->rti - 1] == NULL);

            estate->es_rowmarks[erm->rti - 1] = erm;
        }
    }

    
    estate->es_tupleTable = NIL;
    estate->es_trig_tuple_slot = NULL;
    estate->es_trig_oldtup_slot = NULL;
    estate->es_trig_newtup_slot = NULL;

    
    //标记EvalPlanQual为非活动模式
    estate->es_epqTuple = NULL;
    estate->es_epqTupleSet = NULL;
    estate->es_epqScanDone = NULL;

    
    Assert(estate->es_subplanstates == NIL);
    i = 1;                      
    foreach(l, plannedstmt->subplans)//遍历subplans
    {
        Plan       *subplan = (Plan *) lfirst(l);
        PlanState  *subplanstate;
        int         sp_eflags;

        
        sp_eflags = eflags
            & (EXEC_FLAG_EXPLAIN_ONLY | EXEC_FLAG_WITH_NO_DATA);//设置sp_eflags
        if (bms_is_member(i, plannedstmt->rewindPlanIDs))
            sp_eflags |= EXEC_FLAG_REWIND;

        subplanstate = ExecInitNode(subplan, estate, sp_eflags);//执行Plan节点初始化过程

        estate->es_subplanstates = lappend(estate->es_subplanstates,
                                           subplanstate);

        i++;
    }

    
    planstate = ExecInitNode(plan, estate, eflags);//执行Plan节点初始化过程

    
    tupType = ExecGetResultType(planstate);

    
    if (operation == CMD_SELECT)//SELECT命令
    {
        bool        junk_filter_needed = false;
        ListCell   *tlist;

        foreach(tlist, plan->targetlist)//遍历tlist
        {
            TargetEntry *tle = (TargetEntry *) lfirst(tlist);

            if (tle->resjunk)//如需要垃圾过滤器
            {
                junk_filter_needed = true;//设置为T
                break;
            }
        }

        if (junk_filter_needed)
        {
            JunkFilter *j;

            j = ExecInitJunkFilter(planstate->plan->targetlist,
                                   tupType->tdhasoid,
                                   ExecInitExtraTupleSlot(estate, NULL));//初始化
            estate->es_junkFilter = j;

            
            //期望返回已清理的元组类型
            tupType = j->jf_cleanTupType;
        }
    }
    //赋值
    queryDesc->tupDesc = tupType;
    queryDesc->planstate = planstate;
}

三、跟踪分析

测试脚本如下

testdb=# explain select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je 
testdb-# from t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je 
testdb(#                         from t_grxx gr inner join t_jfxx jf 
testdb(#                                        on gr.dwbh = dw.dwbh 
testdb(#                                           and gr.grbh = jf.grbh) grjf
testdb-# order by dw.dwbh;
                                        QUERY PLAN                                        
------------------------------------------------------------------------------------------
 Sort  (cost=20070.93..20320.93 rows=100000 width=47)
   Sort Key: dw.dwbh
   ->  Hash Join  (cost=3754.00..8689.61 rows=100000 width=47)
         Hash Cond: ((gr.dwbh)::text = (dw.dwbh)::text)
         ->  Hash Join  (cost=3465.00..8138.00 rows=100000 width=31)
               Hash Cond: ((jf.grbh)::text = (gr.grbh)::text)
               ->  Seq Scan on t_jfxx jf  (cost=0.00..1637.00 rows=100000 width=20)
               ->  Hash  (cost=1726.00..1726.00 rows=100000 width=16)
                     ->  Seq Scan on t_grxx gr  (cost=0.00..1726.00 rows=100000 width=16)
         ->  Hash  (cost=164.00..164.00 rows=10000 width=20)
               ->  Seq Scan on t_dwxx dw  (cost=0.00..164.00 rows=10000 width=20)
(11 rows)

启动gdb,设置断点,进入InitPlan

(gdb) b InitPlan
Breakpoint 1 at 0x6d9df2: file execMain.c, line 808.
(gdb) c
Continuing.

Breakpoint 1, InitPlan (queryDesc=0x20838b8, eflags=16) at execMain.c:808
warning: Source file is more recent than executable.
808     CmdType     operation = queryDesc->operation;

输入参数

(gdb) p *queryDesc
$1 = {operation = CMD_SELECT, plannedstmt = 0x207e6c0, 
  sourceText = 0x1f96eb8 "select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je \nfrom t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je \n", ' ' <repeats 24 times>, "from t_grxx gr inner join t_jfxx jf \n", ' ' <repeats 34 times>..., 
  snapshot = 0x1fba8e0, crosscheck_snapshot = 0x0, dest = 0xf8f280 <donothingDR>, params = 0x0, queryEnv = 0x0, 
  instrument_options = 0, tupDesc = 0x0, estate = 0x207f898, planstate = 0x0, already_executed = false, totaltime = 0x0}
(gdb) p eflags
$2 = 16

变量赋值,PlannedStmt的详细数据结构先前章节已有解释,请参见相关章节.

(gdb) n
809     PlannedStmt *plannedstmt = queryDesc->plannedstmt;
(gdb) 
810     Plan       *plan = plannedstmt->planTree;
(gdb) 
811     List       *rangeTable = plannedstmt->rtable;
(gdb) n
812     EState     *estate = queryDesc->estate;
(gdb) 
821     ExecCheckRTPerms(rangeTable, true);
(gdb) p *plannedstmt
$3 = {type = T_PlannedStmt, commandType = CMD_SELECT, queryId = 0, hasReturning = false, hasModifyinGCTE = false, 
  canSetTag = true, transientPlan = false, dependsOnRole = false, parallelModeNeeded = false, jitFlags = 0, 
  planTree = 0x20788f8, rtable = 0x207c568, resultRelations = 0x0, nonleafResultRelations = 0x0, rootResultRelations = 0x0, 
  subplans = 0x0, rewindPlanIDs = 0x0, rowMarks = 0x0, relationOids = 0x207c5c8, invalItems = 0x0, paramExecTypes = 0x0, 
  utilityStmt = 0x0, stmt_location = 0, stmt_len = 313}
(gdb) p *plan
$4 = {type = T_Sort, startup_cost = 20070.931487218411, total_cost = 20320.931487218411, plan_rows = 100000, 
  plan_width = 47, parallel_aware = false, parallel_safe = true, plan_node_id = 0, targetlist = 0x207cc28, qual = 0x0, 
  lefttree = 0x207c090, righttree = 0x0, initPlan = 0x0, extParam = 0x0, allParam = 0x0}
(gdb) p *estate
$5 = {type = T_EState, es_direction = ForwardScanDirection, es_snapshot = 0x1fba8e0, es_crosscheck_snapshot = 0x0, 
  es_range_table = 0x0, es_plannedstmt = 0x0, 
  es_sourceText = 0x1f96eb8 "select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je \nfrom t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je \n", ' ' <repeats 24 times>, "from t_grxx gr inner join t_jfxx jf \n", ' ' <repeats 34 times>..., 
  es_junkFilter = 0x0, es_output_cid = 0, es_result_relations = 0x0, es_num_result_relations = 0, 
  es_result_relation_info = 0x0, es_root_result_relations = 0x0, es_num_root_result_relations = 0, 
  es_tuple_routing_result_relations = 0x0, es_trig_target_relations = 0x0, es_trig_tuple_slot = 0x0, 
  es_trig_oldtup_slot = 0x0, es_trig_newtup_slot = 0x0, es_param_list_info = 0x0, es_param_exec_vals = 0x0, 
  es_queryEnv = 0x0, es_query_cxt = 0x207f780, es_tupleTable = 0x0, es_rowMarks = 0x0, es_processed = 0, es_lastoid = 0, 
  es_top_eflags = 16, es_instrument = 0, es_finished = false, es_exprcontexts = 0x0, es_subplanstates = 0x0, 
  es_auxmodifytables = 0x0, es_per_tuple_exprcontext = 0x0, es_epqTuple = 0x0, es_epqTupleSet = 0x0, es_epqScanDone = 0x0, 
  es_use_parallel_mode = false, es_query_dsa = 0x0, es_jit_flags = 0, es_jit = 0x0, es_jit_worker_instr = 0x0}

RTE链表,一共有5个Item,3个基础关系RTE_RELATION(1/3/4),1个RTE_SUBQUERY(2号),1个RTE_JOIN(5号)

(gdb) n
826     estate->es_range_table = rangeTable;
(gdb) p *rangeTable
$6 = {type = T_List, length = 5, head = 0x207c540, tail = 0x207cb28}
(gdb) p *(RangeTblEntry *)rangeTable->head->data.ptr_value
$9 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relid = 16734, relkind = 114 'r', tablesample = 0x0, subquery = 0x0, 
  security_barrier = false, jointype = JOIN_INNER, joinaliasvars = 0x0, functions = 0x0, funcordinality = false, 
  tablefunc = 0x0, values_lists = 0x0, ctename = 0x0, ctelevelsup = 0, self_reference = false, coltypes = 0x0, 
  coltypmods = 0x0, colcollations = 0x0, enrname = 0x0, enrtuples = 0, alias = 0x1f98448, eref = 0x1fbdd20, 
  lateral = false, inh = false, inFromCl = true, requiredPerms = 2, checkAsUser = 0, selectedCols = 0x2054de8, 
  insertedCols = 0x0, updatedCols = 0x0, securityQuals = 0x0}
...

没有结果Relation,执行相应的处理逻辑

(gdb) 
835     if (plannedstmt->resultRelations)
(gdb) p plannedstmt->resultRelations
$14 = (List *) 0x0
(gdb) n
922         estate->es_result_relations = NULL;
(gdb) 
923         estate->es_num_result_relations = 0;
(gdb) 
924         estate->es_result_relation_info = NULL;
(gdb) 
925         estate->es_root_result_relations = NULL;
(gdb) 
926         estate->es_num_root_result_relations = 0;

不存在rowMarks(for update)

(gdb) n
939     foreach(l, plannedstmt->rowMarks)
(gdb) p plannedstmt->rowMarks
$15 = (List *) 0x0
(gdb) p plannedstmt->rowMarks
$16 = (List *) 0x0
(gdb) n
1000        estate->es_tupleTable = NIL;

执行赋值

(gdb) n
1001        estate->es_trig_tuple_slot = NULL;
(gdb) 
1002        estate->es_trig_oldtup_slot = NULL;
...

没有subplans

(gdb) n
1017        foreach(l, plannedstmt->subplans)
(gdb) p *plannedstmt->subplans
Cannot access memory at address 0x0
(gdb) n

初始化节点(执行ExecInitNode)和获取TupleType

(gdb) n
1046        planstate = ExecInitNode(plan, estate, eflags);
(gdb) n
1051        tupType = ExecGetResultType(planstate);
(gdb) 
1057        if (operation == CMD_SELECT)
(gdb) p *planstate
$17 = {type = T_SortState, plan = 0x20788f8, state = 0x207f898, ExecProcNode = 0x6e41bb <ExecProcNodeFirst>, 
  ExecProcNodeReal = 0x716144 <ExecSort>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0, 
  qual = 0x0, lefttree = 0x207fbc8, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, 
  ps_ResultTupleSlot = 0x2090dc0, ps_ExprContext = 0x0, ps_ProjInfo = 0x0, scandesc = 0x208e920}
(gdb) p tupType
$18 = (TupleDesc) 0x20909a8
(gdb) p *tupType
$19 = {natts = 7, tdtypeid = 2249, tdtypmod = -1, tdhasoid = false, tdrefcount = -1, constr = 0x0, attrs = 0x20909c8}

判断是否需要垃圾过滤器

(gdb) n
1059            bool        junk_filter_needed = false;
(gdb) 
1062            foreach(tlist, plan->targetlist)

不需要junk filter

(gdb) 
1073            if (junk_filter_needed)

赋值,结束调用

(gdb) 
1087        queryDesc->tupDesc = tupType;
(gdb) 
1088        queryDesc->planstate = planstate;
(gdb) 
1089    }
(gdb) 
standard_ExecutorStart (queryDesc=0x20838b8, eflags=16) at execMain.c:267
267     MemoryContextSwitchTo(oldcontext);
(gdb) p *queryDesc
$21 = {operation = CMD_SELECT, plannedstmt = 0x207e6c0, 
  sourceText = 0x1f96eb8 "select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je \nfrom t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je \n", ' ' <repeats 24 times>, "from t_grxx gr inner join t_jfxx jf \n", ' ' <repeats 34 times>..., 
  snapshot = 0x1fba8e0, crosscheck_snapshot = 0x0, dest = 0xf8f280 <donothingDR>, params = 0x0, queryEnv = 0x0, 
  instrument_options = 0, tupDesc = 0x20909a8, estate = 0x207f898, planstate = 0x207fab0, already_executed = false, 
  totaltime = 0x0}

到此,相信大家对“PostgreSQL中用于初始化查询执行计划函数是哪个”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

您可能感兴趣的文档:

--结束END--

本文标题: PostgreSQL中用于初始化查询执行计划函数是哪个

本文链接: https://lsjlt.com/news/64778.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • PostgreSQL中用于初始化查询执行计划函数是哪个
    本篇内容主要讲解“PostgreSQL中用于初始化查询执行计划函数是哪个”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“PostgreSQL中用于初始化查询执行计...
    99+
    2024-04-02
  • PostgreSQL中什么函数通过递归调用初始化计划树中的所有Plan节点
    这篇文章主要介绍“PostgreSQL中什么函数通过递归调用初始化计划树中的所有Plan节点”,在日常操作中,相信很多人在PostgreSQL中什么函数通过递归调用初始化计划树中的所有Plan节点问题上存在...
    99+
    2024-04-02
  • 计算机中一个c程序的执行是从什么函数开始
    小编给大家分享一下计算机中一个c程序的执行是从什么函数开始,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一个c程序的执行是从本程序的main函数开始,到main函...
    99+
    2023-06-14
  • PostgreSQL中使用动态规划算法构造连接路径的实现函数是哪个
    这篇文章主要介绍“PostgreSQL中使用动态规划算法构造连接路径的实现函数是哪个”,在日常操作中,相信很多人在PostgreSQL中使用动态规划算法构造连接路径的实现函数是哪个问题上存在疑惑,小编查阅了...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作