PostgreSQL數據庫中有許多內部函數,此次對系統表pg_proc以及函數代碼進行分析記錄(這裏是針對9.3進行介紹的)。 html
數據庫中全部內部函數信息都存儲在系統表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
在數據庫中函數的使用是很是簡單的。
用法爲:
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。 安全
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反向出來的。
這個能夠去看文檔。
若是要進行學習函數的源碼學習,那麼必須首先要閱讀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;
上面是很複雜的一個結構體,這就是調用函數生成的結構體。
如今我以一個函數使用的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;
上面這一系列函數都是對函數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是真正執行用戶須要的函數。他的大致步驟如下圖爲例:
這樣,一個簡單函數的調用結束了。最主要的兩步爲查詢分析與查詢執行。