openGauss 系統函式新增指導
1、函式架構簡介
openGauss 內函式的可以分為兩個部分:
身份註冊宣告:openGauss 中存在一個系統表 pg_proc,這個表存放了所有函式的基本元資訊,相當於函式的“戶口本”,只有在其中可以查到的函式,才可以在 SQL 語句中進行呼叫,才有“資料庫函式”的身份。常見的註冊方式有:builtin、升級指令碼、CREATE FUNCTION 語句、EXTENSION。
底層功能實現:實現其功能的具體邏輯程式碼,可以根據其所用的語言分為四類:INTERNAL, SQL, PLPGSQL、C。
四中常見的函式註冊建立方式,分別對應著著不同的場景:
builtin:原始碼中存在一個名為 builtin_funcs.ini 的檔案,存放著一系列內建函式的元資訊,在初始化安裝資料庫時,會透過某些方式,全量掃描此檔案,將裡面羅列的函式批次註冊到 pg_proc 系統表。
升級指令碼:資料庫由老版本升級到新版本的場景下,不會也不能遍歷重刷 builtin_funcs.ini 到 pg_proc,因此若新版本有新增函式,就需要編寫一個升級指令碼,在升級過程中透過升級指令碼將新增函式註冊到 pg_proc 之中。
CREATE FUNCTION: 透過CREATE FUNCTION ... BEGIN ... END語句,一把完成註冊和實現。
EXTENSION:隨著 extension 進行註冊和載入。
四類語言實現方案分別有不同的註冊宣告方式以及實現特徵:
INTERNAL: 透過 builtin 或升級指令碼進行註冊,底層功能透過 C 語言實現的函式,也是資料庫最常見的內建函式,如 pg_sleep()。其底層功能函式函式名可以再 pg_proc 的 prosrc 列查到。
SQL: 透過 builtin 或者升級指令碼進行註冊,底層功能透過一句 SQL 實現的函式,也是資料庫內建函式的一種。如 to_char() ,在資料庫底層會轉換為一句select CAST(... AS VARCHAR2);,這一句在 pg_proc 的 prosrc 列可以查到,通常是為了複用已有功能模組來適配新介面而採用這種實現方案。
PLPGSQL: 這個就是我們所熟知的,使用 plpgsql 進行編寫建立的函式了,透過語句一次完成宣告與實現。pg_proc 的 prosrc 列存放了這個語句的原始碼。
C: 出現在各種 extension 之中,內部功能使用 C 語言實現。這個和 INTERNAL 比較類似,區別在於其具體註冊方式為透過 extension 進行註冊,並且底層程式碼是在外部 lib 之中,而 INTERNAL 是在 gaussdb 二進位制內的。可以在 pg_proc 的 prosrc、probin 列查到其 lib 路徑以及函式符號資訊。
其中 INTERNAL、SQL 類的函式,因為都可以透過 builtin 的方式整,因此也都常被統稱為 builtin 函式。
一個普通函式呼叫流程大致為:
1、解析 SQL 語句,獲取到函式名以及引數值與型別等資訊。
2、根據以上資訊,在 pg_proc 中檢索到這個函式的後設資料,後設資料中包含預設值、實現語言、底層函式、估算代價等所有資訊。
3、根據其實現語言,呼叫其具體底層介面模組。如:INTERNAL 型別會直接呼叫其後設資料中的底層 C 語言程式碼函式;C 型別會根據後設資料資訊載入相關 lib 後呼叫 lib 中的 C 語言程式碼函式;SQL 型別會直接轉而執行後設資料 prosrc 中存放的 sql 語句;PLPGSQL 型別會轉而走過程語言模組解釋執行 prosrc 中存放的原始碼。
另外還有一種稍微特殊的函式——聚集函式,它其實是在普通函式的架構基礎上做的功能變更與擴充套件,其架構和新增流程與普通函式有些差異。我們分兩章介紹如何新增普通的 INTERNAL 函式和聚集函式。
2、如何新增一個普通的 INTERNAL 函式
瞭解了上面的架構與流程後,不難得出,新增一個普通的 INTERNAL 函式,可分為四個步驟:
1、宣告函式身份。將我們已經提前設計好的函式的各種屬性,如引數數量型別、返回值型別、穩定性等等,按照特定的格式新增進 buitin_funcs.ini 檔案之中。
2、實現功能程式碼。在核心程式碼合適位置,實現一個 C 語言的函式,來實現對應的功能。
3、關聯宣告實現。將上一個步驟編寫函式的函式名,新增到 builtin_funcs.ini 對應條目的對應位置。
4、編寫升級指令碼。用於在升級流程之中註冊 SQL 函式身份。
2.1 宣告函式身份
在這之前我們需要已經提前設計好自己的函式屬性以及功能,如引數數量型別、返回值型別、穩定性等等,將這些資訊按照特定的格式和順序填寫到./src/common/backend/catalog/builtin_funcs.ini 檔案之中。
這個檔案中需要按照如下結構進行書寫:
AddFuncGroup(
"pg_sleep", 1,
AddBuiltinFunc(_0(2626), _1("pg_sleep"), _2(1), _3(true), _4(false), _5(pg_sleep), _6(2278), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(1, 701), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_sleep"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false))
),
可以看到其有內外兩層組成。
外層為AddFuncGroup("pg_sleep", 1, AddBuiltinFunc(...)),其第一個成員變數為函式名,第二個成員變數為過載數量,後面的 AddBuiltinFunc 結構為函式元資訊。這個結構會匹配核心程式碼中的結構體struct FuncGroup。需要十分注意的是,這個結構需要按照第一個成員也就是函式名的 ASCII 大小升序,新增到對應的位置。
內層AddBuiltinFunc為函式元資訊,其目前一共含有 38 個屬性,與核心程式碼結構體struct Builtin_func和系統表 pg_proc 都有對應關係。我們根據如下表每個屬性含義,完善 AddBuiltinFunc 結構(可暫不關注屬性 5 與 25)。
編號 含義 對應 Builtin_func
0 oid,函式的唯一標識 id,需要小於 10000 且不可以和已有函式重複。 Oid foid;
1 函式名 const char* funcName
2 引數數量 int2 nargs
3 是否 STRICT ( NULL IN NULL OUT,即若入參有 NULL,則不執行,直接返回一個 NULL) bool strict
4 是否返回一個集合,就是返回多行的意思。 bool retset
5 底層 C 語言功能程式碼函式名。 PGFunction func
6 返回值型別 oid Oid rettype
7 所屬 schema Oid pronamespace
8 owner Oid proowner
9 內部實現語言,填 INTERNALlanguageId 或 SQLlanguageId Oid prolang
10 如果返回一個集合的話,估算的每行執行代價,否則是 0; float4 procost
11 如果返回一個集合的話,估算的返回行數,否則是 0; float4 prorows
12 存在變長引數,這裡是變長引數 oid Oid provariadic
13 此函式的簡化呼叫方式。 regproc protransform
14 是否是一個聚集引數。 bool proisagg
15 是否是一個視窗函式。 bool proiswindow
16 是否是一個安全定義器(也就是一個“setuid”函式) bool prosecdef
17 函式沒副作用。如果函式沒有對引數進行防洩露處理,則會丟擲錯誤 bool proleakproof
18 函式穩定性。描述該函式的結果是否只依賴於它的輸入引數。 i:最穩(immutable),對於相同的輸入總是產生相同的結果。 s:較穩(stable),對於相同的輸入其結果在一次掃描裡不變。 v:不穩(volatile),其結果可能在任何時候變化,也用於那些有副作用的函式。 char provolatile
19 預設引數數量 int2 pronargdefaults
20 入引數量以及型別,僅包含 IN、INOUT、VARIADIC 引數。 格式如:_20(count, typeoid1, typeoid2, typeoid3...) ArrayOid proargtypes
21 所有引數的數量以及型別。 格式如:_21(count, typeoid1, typeoid2, ...) ArrayOid* proallargtypes
22 所有引數的數量以及模式,要和 21 對應。模式含義 i-IN, o-OUT, b-INOUT, v-VARIADIC 格式如:_22(count, 'i', 'o', 'v',...)。 注意,若所有引數都是 i,這個域為空。 ArrayChar* proargmodes
23 所有引數的數量以及名字,要和 21 對應。若沒有引數有名字,則這個域為空。 格式如:_23(count, name1, name2, name3....) ArrayCStr* proargnames
24 若含有預設引數,則這裡是預設值的表示式樹(按照nodeToString()的表現方式),不含則為空 const char* proargdefaults
25 對於 INTERNAL 這裡是底層函式名,對於 SQL 這裡是一句 sql,對於 PLPGSQL 這裡是其原始碼。 const char* prosrc;
26 關於如何呼叫函式的附加資訊。 const char* probin;
27 函式針對執行時配置變數的本地設定 ArrayCStr* proconfig;
28 訪問許可權 ArrayAcl* proacl
29 函式具有預設值的入參的位置。 ArrayInt2* prodefaultargpos
30 函式的執行模式,表示函式是在 fence 還是 not fence 模式下執行,如果是 fence 執行模式,函式的執行會在重新 fork 的程序中執行。分散式屬性,單機不涉及,false 即可。 bool* fencedmode
31 表示該函式是否可以下推到 DN 上執行。分散式屬性,單機不涉及,false 即可。 bool* proshippable
32 是否支援過載。 bool* propackage
33 函式描述註釋。 const char* descr
34 函式型別。builtin_funcs 中都是 f 型別。 char prokind
35 function\procdure 的入參字串。 const char* proargsrc
36 如果屬於某個 package 的話,這裡填 package 的 oid,不屬於的話填 0. Oid propackageid
37 是否是一個私有函式。 bool proisprivate
訣竅:builtin_funcs.ini 內已經有三千多個函式,總有那麼一些與自己要加的比較像,可以找出來對照著進行填寫新增。
函式過載
一個函式可以有多個過載,以 generate_series 為例,外層的第二個引數填寫過載數量,後面對每一種過載都正常寫一個 AddBuiltinFunc,其中我們可以看到屬性 5 都是不一樣的,每一個過載版本,都對應不同的底層實現函式。入參肯定也是不一樣的。
AddFuncGroup(
"generate_series", 8,
AddBuiltinFunc(_0(938), _1("generate_series"), ..., _5(generate_series_timestamp), ..., _20(3, 1114, 1114, 1186), ..., _25("generate_series_timestamp"), ...),
AddBuiltinFunc(_0( 939), _1("generate_series"),..., _5(generate_series_timestamptz), ..._20(3, 1184, 1184, 1186), ..., _25("generate_series_timestamptz"), ...),
AddBuiltinFunc(_0(1066), _1("generate_series"),...,_5(generate_series_step_int4),..., _20(3, 23, 23, 23), ..., _25("generate_series_step_int4"),...),
...
),
變長引數
含有變長引數的函式,我們需要注意屬性 2、12、20、21、22。
2 為入引數量,變長引數算一個;12 為變長引數 oid;20、21、22 照常填寫,注意 22 中變長引數模式為'v'
可透過 \df 元命令快速檢視,引數前面有 VARIADIC 表示變長。例如 concat_ws
openGauss=# \df concat_ws
List of functions
Schema | Name | Result data type | Argument data types | Type | fencedmode | propackage | prokind
------------+-----------+------------------+----------------------+--------+------------+------------+---------
pg_catalog | concat_ws | text | text, VARIADIC "any" | normal | f | f | f
(1 row)
預設值引數
含有預設值的引數,我們需要注意 2、19、24、29
2 為入引數量,有預設值的引數算一個
19 為預設引數的數量。
24 為預設引數的值,裡面存放放核心中的 nodeToString 打出來的字串表示式。
29 為預設引數的位置,填寫格式與 20、21 等差不多,_29(num, pos),其中 pos 表示引數下標。
可透過\df 元命令快速檢視,變數後有個 DEFAULT,如
openGauss=# \df pg_start_backup
List of functions
Schema | Name | Result data type | Argument data types | Type | fencedmode | propackage | prokind
------------+-----------------+------------------+---------------------------------------------+--------+------------+------------+---------
pg_catalog | pg_start_backup | text | label text, fast boolean DEFAULT false | normal | f | f | f
pg_catalog | pg_start_backup | text | label text, fast boolean, exclusive boolean | normal | f | f | f
(2 rows)
2.2 實現功能程式碼
在核心程式碼合適的.cpp 檔案中,使用一些約定的介面,實現一個函式來完成相關功能。以 pg_terminate_session 為例
Datum pg_terminate_session(PG_FUNCTION_ARGS)
{
ThreadId tid = PG_GETARG_INT64(0);
uint64 sid = PG_GETARG_INT64(1);
int r = -1;
if (tid == sid) {
r = kill_backend(tid);
} else if (ENABLE_THREAD_POOL) {
ThreadPoolSessControl *sess_ctrl = g_threadPoolControler->GetSessionCtrl();
int ctrl_idx = sess_ctrl->FindCtrlIdxBySessId(sid);
r = sess_ctrl->SendSignal((int)ctrl_idx, SIGTERM);
}
PG_RETURN_BOOL(r == 0);
}
函式返回型別都需要為 Datum。
入參必須為 FunctionCallInfo fcinfo,但為了簡化書寫以及保持介面形式統一,我們將其 define 成了一個宏 PG_FUNCTION_ARGS。
獲取實際引數則需要使用專門的宏介面來獲取,例如 PGGETARG_INT64(0) ,這一套介面一般都以 PG_GETARG為字首,之後為資料型別,宏引數為函式引數下標。例如假如函式第三個引數為 bool,則需要使用 PG_GETARG_BOOL(2)來獲取。
return 語句也有封裝好的介面,一般都以 PGRETURN為字首,後面緊跟型別,宏引數為實際值。
上面是一個返回一行單列的函式,有時候我們還有返回多行多列的函式,以 gs_threadpool_status 為例,以下是部分簡化程式碼:
Datum gs_threadpool_status(PG_FUNCTION_ARGS)
{
FuncCallContext* funcctx = NULL; // 函式呼叫上下文
ThreadPoolStat* entry = NULL; // 用於儲存單行的值
...
// 第一次呼叫需要提前初始化函式呼叫上下文的相關資訊。
if (SRF_IS_FIRSTCALL()) {
// 在fcinfo中建立函式呼叫上下文
funcctx = SRF_FIRSTCALL_INIT();
// 初始化列描述資訊,並儲存到函式呼叫上下文。
TupleDesc tupdesc = CreateTemplateTupleDesc(NUM_THREADPOOL_STATUS_ELEM, false, TAM_HEAP);
TupleDescInitEntry(tupdesc, (AttrNumber)1, "nodename", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)2, "groupid", INT4OID, -1, 0);
....
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
/* 計算總行數,並獲取所有源資料 */
funcctx->user_fctx = (void*)g_threadPoolControler->GetThreadPoolStat(&(funcctx->max_calls));
}
// 在fcinfo中找到函式呼叫上下文。
funcctx = SRF_PERCALL_SETUP();
// 呼叫計數 < 總行數
if (funcctx->call_cntr < funcctx->max_calls) {
Datum values[NUM_THREADPOOL_STATUS_ELEM];
bool nulls[NUM_THREADPOOL_STATUS_ELEM] = {false};
// 在上下文中找到當前行的源資料
entry = (((ThreadPoolStat*)funcctx->user_fctx) + funcctx->call_cntr);
// 將當前行源資料填充到values、nulls陣列中,並form成一個tuple。
values[0] = CStringGetTextDatum(g_instance.attr.attr_common.PGXCNodeName);
nulls[0] = false;
values[1] = Int32GetDatum(entry->groupId);
...
HeapTuple tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
// 返回當前行,並標註還會有下一行,同時將呼叫計數加一。後續還會繼續呼叫此函式獲取資料。
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
} else {
// 返回空,並標註當前已經返回所有行了。後續不會再呼叫此函式獲取資料了
SRF_RETURN_DONE(funcctx);
}
}
訣竅:INTERNAL 函式已經有三千多個,總有那麼一些與自己要加的比較像,可以找出來對照著進行編寫,如變長引數的獲取等。
這裡的底層功能實現函式與我們要加的資料庫函式名稱是可以不一樣的,例如過載的時候,一個資料庫函式,對應好幾個內部實現函式,每個都是不一樣的。但一般為了便於開發與除錯,能命名一樣就儘量一樣。
2.3 關聯宣告實現
我們需要找一個 builtin_funcs.ini 能訪問到的合適的 .h 標頭檔案,將我們寫的 c 功能函式宣告進去。
在 builtin_funcs.ini 的屬性 5 和 25 中, 將我們第二步實現的功能函式名寫進去。
2.4 編寫升級指令碼
1:佔用小版本號
開啟 ./src/common/backend/utils/init/globals.cpp,找到如下程式碼:
/* hard-wired binary version number */
const uint32 GRAND_VERSION_NUM = 92423;
這個值就是小版本號,每次提交之中若有涉及到需要版本升級差異的動作,都需要佔用一個版本號。
如我們現在需要使用升級指令碼來完成註冊函式,需要將這個值修改加一到 92424,而這個 92424 就是我們佔用的版本號。
2:編寫 upgrade 指令碼
進入 src/include/catalog/upgrade_sql/upgrade_catalog_maindb 資料夾。
可以發現裡面有類似 upgrade_catalog_maindb_92_000.sql 與 upgrade-post_catalog_maindb_92_000.sql 兩種檔案,主要區別在一個帶-post,一個不帶。區別在於在升級流程中,我們必然會將舊的 gaussdb 二進位制檔案更新為新的,而帶 post 的在新的上執行,不帶的在舊的上執行。
我們新增了 INTERNAL 函式,其底層實現函式肯定在新版二進位制上,因此我們用我們佔用的版本號建立一個檔案:upgrade-post_catalog_maindb_92_424.sql
對於每個新增的函式(以 new_function_name 為例),都按照如下三句的形式,新增到新建的檔案中。
-- 首先需要先DROP一下,防止例如我們第一次升級意外失敗,殘留資料沒回滾乾淨又升了第二次等函式早就存在了的情況
DROP FUNCTION IF EXISTS pg_catalog.new_function_name(text, text) CASCADE;
-- 設定下一個建立物件的OID,此處0000要替換成我們這個函式的oid。
SET LOCAL inplace_upgrade_next_system_object_oids=IUO_PROC, 0000;
-- 建立函式。
-- 類似透過PLPGSQL建立函式語法,但區別在於AS後並不是類似 "$$...begin...end $$"的函式體,而直接直接指定了其底層功能實現函式的函式名,同時LANGUAGE也不是plpgsql, 而是設定為internal。
-- 其餘的部分與plpgsql就一致了,如returns、各種屬性指定方式等。
CREATE FUNCTION pg_catalog.new_function_name(text, text)
RETURNS int8
as 'cfunc_of_new_function'
LANGUAGE INTERNAL
IMMUTABLE COST 10000 STRICT;
可以看到其實升級指令碼的功能其實就是利用 create 語句在升級流程中對函式進行宣告註冊,新增到 pg_proc 的過程。
最後還需要將這個檔案複製到 src/include/catalog/upgrade_sql/upgrade_catalog_otherdb 資料夾下,並重新命名為 upgrade-post_catalog_otherdb_92_424.sql
3:編寫 rollback 指令碼
rollback 指令碼是用來在升級前進行環境清理、預升級完成後但又不想升了,進行回滾操作用到的指令碼。
進入 src/include/catalog/upgrade_sql/rollback_catalog_maindb 資料夾,與 upgrade 類似,我們也建立一個 rollback-post_catalog_maindb_92_424.sql 檔案
對每個函式都按照如下格式進行新增
-- 刪除。
DROP FUNCTION IF EXISTS pg_catalog.new_function_name(text, text) CASCADE;
同理,我們也需要將其複製到 src/include/catalog/upgrade_sql/rollback_catalog_otherdb 並重新命名。
3、如何新增一個 INTERNAL 聚集函式
3.1 聚集函式的架構
聚集函式與普通函式不同,其執行並非直接呼叫底層功能程式碼實現,而是分成了好幾個環節,每個環節各對應一個功能程式碼。
其可以分為三個執行環節:
transition:收集值計算中間變數。
collectition:這個步驟不是必須的,在分散式或者並行查詢下用,用於收集多箇中間變數,總合成一箇中間變數
final:將中間變數計算為結果。
其中每個環節都由一個功能函式來進行。
例如 avg(int4) 函式由 int4_avg_accum、 int8_avg_collect、int8_avg 來完成,其中間變數為一個長度為 2 的 int8 陣列,用來記錄 sum 與 count。
其執行流程為:
int4_avg_accum 函式依次接收每一行的值,並計算 sum,增加 count
int8_avg_collect 函式將分散式每個節點,或者並行查詢的多執行緒下,所有的 sum、count 收集起來,計算出一個總的 sum 和 count
int8_avg 函式計算 sum / count 得到最終結果。
其所有資訊都在系統表 pg_aggregate 能查到。
openGauss=# select * from pg_aggregate where aggfnoid=2101;
aggfnoid | aggtransfn | aggcollectfn | aggfinalfn | aggsortop | aggtranstype | agginitval | agginitcollect | aggkind | aggnumdirectargs
----------------+----------------+------------------+------------+-----------+--------------+------------+----------------+---------+------------------
pg_catalog.avg | int4_avg_accum | int8_avg_collect | int8_avg | 0 | 1016 | {0,0} | {0,0} | n | 0
(1 row)
當然並非所有聚集函式都需要上述三步,例如 median 函式,無法計算中間值,因此也沒有 collectition 函式。count()函式中間值就是結果值,因此不需要 final 函式。
3.2 如何新增
1、首先我們需要設計好聚集函式的功能,還需根據其功能按需設計三個階段函式以及中間變數。因此我們最多一共需要設計四個函式,一聚集三普通。
2、對於三個階段函式,按照新增正常 INTERNAL 普通函式一樣一樣的流程,將其新增成普通的 INTERNAL 函式。
3、編寫聚集函式的 builtin
聚集函式也需要有函式身份,因此我們需要像普通函式一樣透過 builtin_funcs.ini 將其註冊到 pg_proc。但填寫時需要注意如下幾個值。以 avg 為例:
AddFuncGroup(
"avg", 8,
AddBuiltinFunc(_0(2100), _1("avg"), _2(1), _3(false), _4(false), _5(aggregate_dummy), _6(1700), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(true), _15(false), _16(false), _17(false), _18('i'), _19(0), _20(1, 20), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("aggregate_dummy"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)),
...
)
屬性 5 的底層功能程式碼函式名,但聚集函式並沒有單一的底層程式碼,因此這裡寫啥都不會被執行,但為了格式形式等的統一與錯誤場景下除錯,我們在這裡用一個沒啥用的假的函式 aggregate_dummy。
屬性 14 表示是否是一個聚集函式,這裡一定是 true。
屬性 25 保持統一,填假的函式的函式名。
4、新增 pg_aggregate 聚集函式元資訊
我們需要在./src/include/catalog/pg_aggregate.h,按照如下格式將聚集函式與三個階段函式關聯起來,並設定中間變數。
DATA(insert ( 2101 int4_avg_accum int8_avg_collect int8_avg 0 1016 "{0,0}" "{0,0}" n 0));
其共有 10 列,和系統表 pg_aggregate 一一對應,第一列是聚集函式 oid,後三列是三個階段函式,在之後是中間變數以及其初始化值等,具體含義可以參照官網文件 pg_aggregate 的說明。
同 builtin 一樣,這些資料也只會在初始化安裝資料庫時被統一匯入到系統表 pg_aggregate,升級時並不會重刷,因此我們還需要額外寫升級指令碼。
5、新增升級指令碼
三個階段函式按照正常普通 INTERNAL 的格式新增至 upgrade、rollback 指令碼之中。聚集函式我們使用 create aggregate 語法和 drop aggregate 語法新增刪除。例如:
upgrade.sql
-- 先建立階段函式。json_agg的階段函式只有兩個。
DROP FUNCTION IF EXISTS pg_catalog.json_agg_finalfn(internal) CASCADE;
SET LOCAL inplace_upgrade_next_system_object_oids = IUO_PROC, 3125;
CREATE FUNCTION pg_catalog.json_agg_finalfn ...;
DROP FUNCTION IF EXISTS pg_catalog.json_agg_transfn(internal, anyelement) CASCADE;
SET LOCAL inplace_upgrade_next_system_object_oids = IUO_PROC, 3126;
CREATE FUNCTION pg_catalog.json_agg_transfn ...;
-- 同樣先執行drop清理環境,之後設定oid,最後使用CREATE AGGREGATE語句註冊聚集函式。
drop aggregate if exists pg_catalog.json_agg(anyelement);
SET LOCAL inplace_upgrade_next_system_object_oids=IUO_PROC, 3124;
create aggregate pg_catalog.json_agg(anyelement)
(SFUNC=json_agg_transfn, STYPE= internal, finalfunc = json_agg_finalfn);
rollback.sql
-- 先drop聚集函式,在drop階段函式。
drop aggregate if exists pg_catalog.json_object_agg("any", "any");
DROP FUNCTION IF EXISTS pg_catalog.json_object_agg_finalfn(internal) CASCADE;
DROP FUNCTION IF EXISTS pg_catalog.json_object_agg_transfn(internal, "any", "any") CASCADE;
5、如何新增一個 SQL\PLPGSQL\C 函式
SQL 語言的函式和 INTERNAL 的比較類似,但新增起來更簡單。不需要便攜具體的底層實現,直接將 builtin_funcs.ini 檔案的屬性 5 置為 NULL,屬性 9 填寫 SQLlanguageId,屬性 25 填寫對應的 SQL 查詢,升級指令碼函式體寫成對應的 SQL 就好了。可以參考 to_char()。
PLPGSQL 語言實現的函式,就是 CREATE FUNCTION ... BEGIN ... END 方式,大家都會,不再贅述。
C 實現的函式,主要是作為 EXTENSION 的一部分,參考 EXTENSION 的寫作方式。
聚集函式也差不多按照聚集函式的規則與實現語言的規則綜合來就可以了。
6、如何驗證功能的正確
對於我們新增的 builtin 函式或者在 system_views.sql 之中使用 plpgsql 新增的函式等,都算是我們資料庫的系統函式,出問題都算是資料可的責任,因此我們需要去進行測試。至於資料庫執行期間我們透過 plpgsql 或者 extension 新增的使用者自己的函式就不用了。
builtin 函式分兩種註冊方式,builtin 與升級指令碼,對應的也是正常安裝與版本升級兩種場景,因此我們業主要針對兩種場景入手測試。
6.1 驗證功能的正確
(1)編譯安裝啟動資料庫,自行編寫測試用例測試功能正確。
(2)執行 fastcheck 測試集,測試資料庫其他功能正常。若有失敗用例,需要分析是否是改壞了,若不是則可修改用力預期,例如有些用例維護了一個全量函式列表,因此新增函式的話,這個用例一定會失敗,可以修改預期。
(3)推薦將自身編寫的測試集新增到 fastcheck 測試集,方便功能看護。
6.2 驗證升級正確性
(1)安裝一個老版本資料庫。
(2)使用自己程式碼編譯出一個安裝包。
(3)使用自己的安裝包,將老版本資料庫預升級到新版本,並驗證函式功能正確,資料庫正常
(4)回滾版本到老版本,驗證回滾完全無殘留,資料庫正常。
(5)重新執行升級,並提交升級。驗證功能正確,資料庫正常。
升級流程指導參照官網相關 wiki 與文件。