PostgreSQL的系統函數分析記錄

        PostgreSQL數據庫中有許多內部函數,此次對系統表pg_proc以及函數代碼進行分析記錄(這裏是針對9.3進行介紹的)。 html

 1、數據庫系統表pg_proc

        數據庫中全部內部函數信息都存儲在系統表pg_proc.
        內部函數都是在編譯以前寫好並存儲在pg_proc.h文件中。
        下面來看一下pg_proc的表結構,首先是看源碼中的結構體:
node

CATALOG(pg_proc,1255) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81) BKI_SCHEMA_MACRO
   37 {
   38     NameData    proname;        /* procedure name */
   39     Oid         pronamespace;   /* OID of namespace containing this proc */
   40     Oid         proowner;       /* procedure owner */
   41     Oid         prolang;        /* OID of pg_language entry */
   42     float4      procost;        /* estimated execution cost */
   43     float4      prorows;        /* estimated # of rows out (if proretset) */
   44     Oid         provariadic;    /* element type of variadic array, or 0 */
   45     regproc     protransform;   /* transforms calls to it during planning */
   46     bool        proisagg;       /* is it an aggregate? */
   47     bool        proiswindow;    /* is it a window function? */
   48     bool        prosecdef;      /* security definer */
   49     bool        proleakproof;   /* is it a leak-proof function? */
   50     bool        proisstrict;    /* strict with respect to NULLs? */
   51     bool        proretset;      /* returns a set? */
   52     char        provolatile;    /* see PROVOLATILE_ categories below */
   53     int16       pronargs;       /* number of arguments */
   54     int16       pronargdefaults;    /* number of arguments with defaults */
   55     Oid         prorettype;     /* OID of result type */
   56 
   57     /*
   58      * variable-length fields start here, but we allow direct access to
   59      * proargtypes
   60      */
   61     oidvector   proargtypes;    /* parameter types (excludes OUT params) */
   62 
   63 #ifdef CATALOG_VARLEN
   64     Oid         proallargtypes[1];      /* all param types (NULL if IN only) */
   65     char        proargmodes[1]; /* parameter modes (NULL if IN only) */
   66     text        proargnames[1]; /* parameter names (NULL if no names) */
   67     pg_node_tree proargdefaults;/* list of expression trees for argument
   68                                  * defaults (NULL if none) */
   69     text        prosrc;         /* procedure source text */
   70     text        probin;         /* secondary procedure info (can be NULL) */
   71     text        proconfig[1];   /* procedure-local GUC settings */
   72     aclitem     proacl[1];      /* access permissions */
   73 #endif
   74 } FormData_pg_proc;
         下面來簡單介紹pg_type的各個字段含義:

        proname、pronamespace、proowner分別是函數名(sql調用的名字)、存在的模式(oid)、所屬用戶(oid),這裏就很少說了。
        prolang:實現語言或該函數的調用接口,目前在系統中定義的爲(internal,12),(c、13),(sql,14),數據庫中主要用的是internal和sql。
        procost:估計執行成本,這裏和執行計劃相關聯。
        prorows:結果行估計數。
        provariadic:可變數組參數類型,這是9.1以後加入的,這是可以然函數定義再也不受限於參數個數。這個類型能夠參照一下函數concat和concat_ws這兩個函數。這個地方在這裏對concat說明,在函數concat這個參數是這樣寫的2276,這                                 個函數是拼接字符串,而2276正是any,在這裏填寫後,表示這個函數能夠接收多個any類型的參數,而不用像之前那樣每多一個參數就得寫一個定義。
        protransform:能夠替代被調用的簡化函數。能夠參看varbit函數。這裏寫的是varbit_transform,而經過查看代碼,能夠知道varbit_transform只有一個參數,也就是說當只有一個參數的時候調用varbit實際上執行的是varbit_transform。
        proisagg:這是否是一個彙集函數。
        proiswindow:是否爲窗口函數。窗口函數(RANK,SUM等) 能夠對一組相關的記錄進行操做。
        prosecdef:函數是一個安全定義器(也就是一個"setuid"函數)。
        proleakproof:有無其餘影響。
        proisstrict:遇到NULL值是否直接返回NULL,這裏要說明的是,數據庫中有一個數組專門來存儲這個值,當爲true時,數據庫對參數爲NULL的qi
        proretset:函數返回一個集合(也就是說,指定數據類型的多個數值)。
        provolatile:告訴該函數的結果是否只倚賴於它的輸入參數,或者還會被外接因素影響。對於"不可變的"(immutable)函數它是 i ,這樣的函數對於相同的輸入老是產生相同的結果。對於"穩定的"(stable)函數它是 s ,(對於固定輸入)其結果在一次掃描裏不變。對於"易變"(volatile)函數它是 v ,其結果可能在任什麼時候候變化。v 也用於那些有反作用的函數,所以調用它們沒法獲得優化。
        pronargs:參數個數

        pronargdefaults:默認參數的個數。
        prorettype:返回參數類型的oid。
        proargtypes:一個存放函數參數的數據類型的數組。
        proargmodes:一個保存函數參數模式的數組,編碼以下:i 表示 IN 參數, o 表示 OUT 參數, b 表示 INOUT 參數。若是全部參數都是 IN 參數,那麼這個字段爲空。請注意,下標對應的是 proallargtypes 的位置,而不是 proargtypes。
        proargnames:一個保存函數參數的名字的數組。沒有名字的參數在數組裏設置爲空字符串。若是沒有一個參數有名字,這個字段將是空。請注意,此數組的下標對應 proallargtypes 而不是 proargtypes。
        proargdefaults:表達式樹(以nodeToString()形式表示)的默認值。這是pronargdefaults元素的列表,對應的最後N個輸入參數(即最後N proargtypes位置)。若是沒有的參數有默認值,這個領域將是空的。
        prosrc:這個字段告訴函數處理器如何調用該函數。它實際上對於解釋語言來講就是函數的源程序,或者一個連接符號,一個文件名,或者是任何其它的東西,具體取決於語言/調用習慣的實現。
        probin:關於如何調用該函數的附加信息。一樣,其含義也是和語言相關的。
        proconfig:在運行時配置變量函數的局部設置。
        proacl:訪問權限。 sql

        以上就是對系統表pg_proc的介紹,下面對如何閱讀和編寫內部函數做一下介紹。 shell

 2、函數基礎

        一、函數的使用:

            在數據庫中函數的使用是很是簡單的。
            用法爲:
            select FunctionName(args);
            select FunctionName(columnname) from tablename;
            ……
            (具體能夠去查找文檔,這裏不作一一介紹了) 數據庫

        二、使用的函數名

             這裏的函數名(Functionname)就是系統表pg_proc中的proname了。 express

        三、函數的定義

             通常能看到的定義有兩種。 後端

            第一種:

CREATE OR REPLACE FUNCTION date_part(text, time with time zone)
  RETURNS double precision AS
'timetz_part'
  LANGUAGE internal IMMUTABLE STRICT
  COST 1;

            data_part就是咱們調用的函數的名稱。
            (text, time with time zone)即咱們輸入參數的類型。
            double precision是咱們返回的數據類型。
            'timetz_part'是咱們源碼中命名的函數名,調用date_part實際上是調用函數timetz_part。
            internal是咱們規定的函數語言。
            1是咱們估計的時間成本。 數組

            第二種:

CREATE OR REPLACE FUNCTION date_part(text, abstime)
  RETURNS double precision AS
'select pg_catalog.date_part($1, cast($2 as timestamp with time zone))'
  LANGUAGE sql STABLE STRICT
  COST 1;
ALTER FUNCTION date_part(text, abstime) OWNER TO highgo;
COMMENT ON FUNCTION date_part(text, abstime) IS 'extract field from abstime';
            這裏基本和第一種相同。不一樣之處在於:
            這裏沒有寫源碼中命名的函數,而是用一條SQL語句替代了,在這裏執行的時候又在執行的上邊的date_part,而後再去調用的 timetz_part。

          這裏的函數語言是SQL。 安全

            第三種:

CREATE OR REPLACE FUNCTION concat(VARIADIC "any")
  RETURNS text AS
'text_concat'
  LANGUAGE internal STABLE
  COST 1;
ALTER FUNCTION concat("any")
  OWNER TO postgres;
COMMENT ON FUNCTION concat("any") IS 'concatenate values';

        這裏不一樣的就是在參數上添加了VARIADIC,這是說明這個類型是一個可變數組。其餘的都相似,就不說明了。 函數

            第四種:

CREATE OR REPLACE FUNCTION varbit(bit varying, integer, boolean)
  RETURNS bit varying AS
'varbit'
  LANGUAGE internal IMMUTABLE STRICT
  COST 1;
        這裏是看起來和第一種是同樣的,這裏拿過來主要是說明一下,pg_proc中的 protransform字段,應該不能經過SQL定義的方式填寫。這個函數在proc中protransform的定義有varbit_transform。這段定義是admin反向出來的。


        四、定義本身的函數(主要指的用SQL定義)

            這個能夠去看文檔。

        五、函數的源碼

             若是要進行學習函數的源碼學習,那麼必須首先要閱讀src/include/fmgr.h,這裏對函數的制定了一攬子的宏定義。
             首先呢,要說明的是,可以直接用SQL語句調用的函數(prosrc),他的參數必須是PG_FUNCTION_ARGS
             下面是對PG_FUNCTION_ARGS的定義:

#define PG_FUNCTION_ARGS    FunctionCallInfo fcinfo
typedef struct FunctionCallInfoData *FunctionCallInfo;
typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);
typedef struct Node *fmNodePtr; typedef uintptr_t Datum;
typedef struct Node
{
 NodeTag		type; //NodeTag 這是一個枚舉類型
} Node;

 
 
 
 

  
  
  
  
  

 
typedef struct FmgrInfo {         PGFunction fn_addr; /* pointer to function or handler to be called */          Oid fn_oid; /* OID of function (NOT of handler, if any) */         short fn_nargs; /* number of input args (0..FUNC_MAX_ARGS) */          bool fn_strict; /* function is "strict" (NULL in => NULL out) */         bool fn_retset; /* function returns a set */         unsigned char fn_stats; /* collect stats if track_functions > this */         void   *fn_extra; /* extra space for use by handler */         MemoryContext fn_mcxt; /* memory context to store fn_extra in */         fmNodePtr fn_expr; /* expression parse tree for call, or NULL */ } FmgrInfo; typedef struct FunctionCallInfoData { FmgrInfo *flinfo; /* ptr to lookup info used for this call */ fmNodePtr context; /* pass info about context of call */ fmNodePtr resultinfo; /* pass or return extra info about result */ Oid fncollation; /* collation for function to use */ bool isnull; /* function must set true if result is NULL */ short nargs; /* # arguments actually passed */ Datum arg[FUNC_MAX_ARGS]; /* Arguments passed to function */ bool argnull[FUNC_MAX_ARGS]; /* T if arg[i] is actually NULL */ } FunctionCallInfoData;

            上面是很複雜的一個結構體,這就是調用函數生成的結構體。


3、函數在數據庫中的歷程

        如今我以一個函數使用的SQL語句去解讀一下函數。

        一、執行函數

        首先,在命令行下輸入一條SQL語句,在此主要介紹函數,主要對函數運行進行介紹(其餘的內存上下文、執行計劃之類的,這裏就不作介紹了,在下才疏學淺,有待進一步的學習後會作相應介紹),因此直接輸入參數做爲介紹,爲了更好地說明,這裏用concat做爲函數例子進行介紹。進入客戶端,調用函數。

postgres=# select concat('su','re');

        二、進入到服務端

        數據庫客戶端會根據先後端協議將用戶查詢將信息發送到服務端,進入函數PostgresMain,而後進入exec_simple_query,exec_simple_query函數主要分爲兩部分,第一部分是查詢分析,第二部分是查詢執行,下面如下圖進行說明查詢分析:

        (1)首先exec_simple_query函數會將獲得的SQL語句經過調用pg_parse_query進入詞法和語法分析的主題處理過程,而後函數pg_parse_query調用詞法和語法分析的入口函數raw_parse生成分析樹。 raw_parse函數經過分詞與語法引擎進行對SQL語句的識別,其中執行函數時會調用makeFuncCall,初始化FuncCall。這是執行函數所必須調用的。 raw_parse函數經過分詞與語法引擎進行對SQL語句的識別,其中執行函數時會調用makeFuncCall,初始化FuncCall。這是執行函數所必須調用的。

typedef struct FuncCall
{
	NodeTag		type;
	List	   *funcname;		/* qualified name of function */
	List	   *args;			/* the arguments (list of exprs) */
	List	   *agg_order;		/* ORDER BY (list of SortBy) */
	Node	   *agg_filter;		/* FILTER clause, if any */
	bool		agg_star;		/* argument was really '*' */
	bool		agg_distinct;	/* arguments were labeled DISTINCT */
	bool		func_variadic;	/* last argument was labeled VARIADIC */
	struct WindowDef *over;		/* OVER clause, if any */
	int			location;		/* token location, or -1 if unknown */
} FuncCall;

(2)函數pg_parse_query返回分析樹給外部函數。
(3)exec_simple_query接着調用函數pg_analyze_and_rewrite進行語義分析和查詢重寫。首先調用parse_analyze進行語義分析並生成查詢樹,其中parse_analyze會調用transformTopLevelStmt等(見下圖)
進行一系列的轉化。 數pg_rewrite_querye對查詢進行重寫,對執行計劃進行優化。




        上面這一系列函數都是對函數pg_parse_query返回的分析樹,進行一系列的轉化,經過斷定和選擇對應函數,最終經過對系統表pg_proc進行查找、斷定最優函數,並執行函數ParseFuncOrColumn來確認並找到函數,添加到執行計劃中不然返回錯誤,告知用戶並沒有此函數(這裏吐槽一下pg,函數的定義的很是死板,不夠靈活,經常發生有對應函數,卻找不到的狀況,問題在於,數據庫查找用戶執行的函數時,會對參數類型進行確認,而後去尋找,固然這裏主要是數據類型沒法隱式轉化的緣由,當參數類型沒法轉化時,數據庫就會報錯,沒法找到函數)。這裏的transformTopLevelStmt、transformStmt、transformSelectStmt、transformTargetList、transformTargetList、transformExpr、transformExprRecurse、transformFuncCall都是進行轉化的,而ParseFuncOrColumn函數的功能是詳細尋找函數,而make_const是對參數進行處理的。
如下圖來詳細說明ParseFuncOrColumn的工做原理:


        (1)ParseFuncOrColumn調用函數func_get_detail來確認函數是否存在,存在則返回函數oid號,不然返回錯誤。
            (a)
func_get_detail函數調用FuncnameGetCandidates經過函數名、參數個數在系統表pg_proc中獲得候選函數列表。沒有則返回錯誤。
            (b)
func_get_detail函數調用func_match_argtypes對參數類型進行匹配,其中會調用can_coerce_type來斷定當前參數類型可否進行隱式轉換。進而縮小範圍。
            (c)func_get_detail函數調用func_select_candidate最終確認函數參數類型(可轉換的),返回類型、函數oid。
        (2)ParseFuncOrColumn會調用coerce_type對參數表達式進行轉換。
        (3)ParseFuncOrColumn調用函數make_fn_arguments對參數進行轉化,變爲函數可以使用的參數。

         上述過程是建立並優化執行計劃,這裏僅僅是計劃,真正執行的地方是查詢執行。下面以圖簡單說明一下:


        這裏有一個很重要的結構體Portal:

typedef struct PortalData *Portal;

typedef struct PortalData
{
	/* Bookkeeping data */
	const char *name;			/* portal's name */
	const char *prepStmtName;	/* source prepared statement (NULL if none) */
	MemoryContext heap;			/* subsidiary memory for portal */
	ResourceOwner resowner;		/* resources owned by portal */
	void		(*cleanup) (Portal portal);		/* cleanup hook */
	SubTransactionId createSubid;		/* the ID of the creating subxact */

	/*
	 * if createSubid is InvalidSubTransactionId, the portal is held over from
	 * a previous transaction
	 */

	/* The query or queries the portal will execute */
	const char *sourceText;		/* text of query (as of 8.4, never NULL) */
	const char *commandTag;		/* command tag for original query */
	List	   *stmts;			/* PlannedStmts and/or utility statements */
	CachedPlan *cplan;			/* CachedPlan, if stmts are from one */

	ParamListInfo portalParams; /* params to pass to query */

	/* Features/options */
	PortalStrategy strategy;	/* see above */
	int			cursorOptions;	/* DECLARE CURSOR option bits */

	/* Status data */
	PortalStatus status;		/* see above */
	bool		portalPinned;	/* a pinned portal can't be dropped */

	/* If not NULL, Executor is active; call ExecutorEnd eventually: */
	QueryDesc  *queryDesc;		/* info needed for executor invocation */

	/* If portal returns tuples, this is their tupdesc: */
	TupleDesc	tupDesc;		/* descriptor for result tuples */
	/* and these are the format codes to use for the columns: */
	int16	   *formats;		/* a format code for each column */

	/*
	 * Where we store tuples for a held cursor or a PORTAL_ONE_RETURNING or
	 * PORTAL_UTIL_SELECT query.  (A cursor held past the end of its
	 * transaction no longer has any active executor state.)
	 */
	Tuplestorestate *holdStore; /* store for holdable cursors */
	MemoryContext holdContext;	/* memory containing holdStore */

	/*
	 * atStart, atEnd and portalPos indicate the current cursor position.
	 * portalPos is zero before the first row, N after fetching N'th row of
	 * query.  After we run off the end, portalPos = # of rows in query, and
	 * atEnd is true.  If portalPos overflows, set posOverflow (this causes us
	 * to stop relying on its value for navigation).  Note that atStart
	 * implies portalPos == 0, but not the reverse (portalPos could have
	 * overflowed).
	 */
	bool		atStart;
	bool		atEnd;
	bool		posOverflow;
	long		portalPos;

	/* Presentation data, primarily used by the pg_cursors system view */
	TimestampTz creation_time;	/* time at which this portal was defined */
	bool		visible;		/* include this portal in pg_cursors? */
}	PortalData;
        這是查詢執行中所必需的Portal ,存儲的信息爲查詢計劃樹鏈表以及最後選中的執行策略等信息。上圖中大部分都是在進行策略的選擇。

調用CreatePortal建立空白的Portal,調用PortalStart進行初始化,調用函數PortalRun執行Portal,清理Portal。
      其中PortalRun是真正執行用戶須要的函數。他的大致步驟如下圖爲例:

 

        這樣,一個簡單函數的調用結束了。最主要的兩步爲查詢分析與查詢執行。

相關文章
相關標籤/搜索