一、Hook函式總結
(一)初探hook
1.pg中的hook函式及其功能
Post_parse_analyze_hook //對查詢樹的分析
Post_rewriter_hook query_list_hook //此hook功能為重寫查詢樹,對查詢樹Query的重寫
Permission_processUtility_hook //主要對DDL語句做一些許可權控制
Pgaudit_ExecutorEnd_hook //取得sql執行時間和影響行數資訊
Pgaudit_ProcessUtility_hook //取得DDL相關資訊
Pgaudit_object_hook //取得物件資訊
executorStart_hook //處理查詢開始
executorRun_hook //處理查詢執行
executorFinish_hook //處理查詢結束
executorEnd_hook //處理查詢完成後
......
2.設定hook的優點
不需要在原本核心程式碼很大的改動,只需要把hook函式掛到上面,對於核心的擴充套件非常的方便。Hook函式可移植性較高,在pg版本迭代中,核心或多或少都有變動,如果在核心中直接做的修改可能版本不同就會導致各種錯誤,更換版本後需要做大量的修改,而hook函式不需要做很多修改,甚至直接拿過去,把hook啟動就可以直接用,所以說hook的可移植性高。
(二)、簡單Hook的嘗試
- 在pg原始碼中沒有顯示登入資訊的hook,因此我仿照pg自帶外掛的hook對pg14進行嘗試。
-
首先在passwordcheck下定義login_message_display_hook
-
在初始化_PG_init中將hook指向定義的hook函式
-
在postgres.c檔案下的PostgresMain()函式下將此hook掛上
-
配置makefie 等檔案,重新對passwordcheck進行編譯安裝,將hook啟用,create exection passwordcheck(或者是vi data/postgresql.conf,編輯配置檔案將passwordcheck啟用)。重啟資料庫進入即可看到登入資訊
(三)、hook原理
擴充套件控制檔案解析執行呼叫過程
parse_extension_control_file():解析控制檔案,取出呼叫具體版本,檔名等資訊。
InsertExtensionTuple():把控制檔案的資訊儲存到Tuple中。
execute_sql_string():解析sql檔案。先呼叫pg_parse_query將sql轉換成parsetree_list。
然後執行pg_analyze_and_rewrite(),pg_plan_queries(),ExecutorRun()對每一個parse tree重寫執行。
1.擴充套件庫檔案解析執行呼叫過程
ProcedureCreate():在pg_proc.c中,主要是建立函式和儲存過程。
OidFunctionCall():呼叫fmgr提供的介面。所有的函式呼叫都會呼叫此函式。
Internal_load_library():先判斷是否load,如果沒有則透過呼叫dlopen()函式進行load,然後dlsym()查詢相關函式。
Dlsym():獲取函式Magic_func()和_pg_init()函式的地址,如果沒獲取到Magic_func()則會報error。對於_PG_init()可有可無,也可用load直接載入。
Fetch_finfo_record():獲取指定函式的地址,判斷函式是否存在,不存在會報error。
2.hook可以透過create extension 起作用的原理
首先在檔案中定義hook的全域性指標變數,並且初始為null,在程式執行前就在系統中定義完成。
經過create extension後,透過Dlsym();函式獲取magic_func和_PG_init的函式地址。
經過_PG_init後將hook指向定義好的相應函式,此時hook全域性指標不為空,所以執行到判斷是否為空程式碼塊時,執行hook函式,所以hook就起作用了,完成我們預期功能的實現。
二、Makefile檔案解析
在外掛的makefile檔案中通常包含幾個必要變數分別是MODULE_big、EXTENTION、OBJS、PGFILEDESE、DATA、REGRESS、PG_CONFIG……
(一)、主要常用各引數的具體含義
1. MODULE_big
一個要從多個原始檔中構建的共享庫(在OBJS中列出物件檔案)在pgxs.mk檔案中定義,包含共享庫引數。
2. EXTENTION
擴充套件的名稱,這個是必要的變數,否則擴充套件安裝時找不到,一個擴充套件一個名,並且要提供一個control檔案,將會被安裝到prefix/share/extension中。
3.DATA
指定.Sql擴充套件指令碼檔案,要安裝到prefix/share/$MODULEDIR中的隨機檔案。其中•"1.0" :代表版本號,這個用於後續外掛升級。例如我們引入了2.0版本,則可以建立一個 common_func--2.0.sql (用於直接安裝2.0版本的該外掛),建立一個 common_func--1.0--2.0.sql (用於將1.0版本的外掛升級為2.0版本,遵循 extension--oldversion--newversion.sql 命名原則,具體在下述sql版本升級機制中詳細陳述)。
4.REGRESS
測試指令碼檔案,不帶.sql字尾(makefile中可以不新增此變數)。
5.USE_PGXS
此變數定義在src/makefiles/下的pgxs.mk檔案中,是針對編譯extension的解決方案。PostgreSQL安裝為擴充套件提供了一個構建基礎設施,稱為PGXS。
因此,簡單的擴充套件模組可以簡單地建立在已經安裝的伺服器上。
PGXS主要用於包括C程式碼的擴充套件,儘管它也可以用於純SQL擴充套件。
6.PG_CONFIG
要在其中構建的PostgreSQL安裝的pg_config程式的路徑(通常只用在你的PATH中的第一個pg_config)
把這個 makefile 作為Makefile放在儲存你擴充套件的目錄中。然後你可以執行make進行編譯,並且接著make install來安裝你的模組。預設情況下,該模組會為在你的PATH中找到的第一個pg_config程式所對應的PostgreSQL安裝編譯和安裝。你可以透過在 makefile 中或者make命令列中設定PG_CONFIG指向另一個pg_config程式來使用一個不同的安裝。
在PG_CONFIG中擁有多個變數引數,BINDIR = /usr/local/postgres/bin
BINDIR 說明你的POSTGRESQL的 執行程式檔案都安裝在哪裡
INCLUDEDIR = /usr/local/postgres/includes
客戶端 C 程式的標頭檔案的存放地
PKGINCLUDEDIR = /usr/local/postgres/includes
其他客戶C程式標頭檔案的存放地
INCLUDEDIR-SERVER = /usr/local/postgres/includes/server
server 端C頭程式的目錄
LIBDIR = /usr/local/postgres/libs
系統動態載入庫
PKGLIBDIR = /usr/local/postgres/libs
動態載入庫位置
LOCALEDIR = /pgdata/root/locale
本地動態載入庫
MANDIR = /pgdata/root/man
SHAREDIR = /pgdata/postgresql
共享檔案存放地 有的時候經常是安裝某些 EXTENSION 無法工作的一個問題點
SYSCONFDIR = /etc/postgresql 系統配置檔案存放地
PGXS = /usr/local/postgres/libs/pgxs/src/makefiles/pgxs.mk
本地擴充套件的檔案的makefile
CONFIGURE = '--prefix=/usr/local/postgres' '--bindir=/usr/local/postgres/bin' '--sysconfdir=/etc' '--lth-pam' '--with-systemd' '--with-libxml' '--with-segsize=4'
POSTGRESQL 啟動帶有的引數
(二)、makefile總結
Makefile 檔案最底部幾行,作用在於幫助找到PG的PGHOME路徑,通常只需要修改 subdir 一項,其他直接複製即可。這幾行的含義是:
1.我們編譯(make 或 make install)時,如果不加 USE_PGXS=1 ,則認為本資料夾放在了 subdir 路徑下,這通常是我們把外掛放在PG的原始碼 contrib 路徑下編譯,往上返回兩層即為 PGHOME
2.否則,則透過環境變數中查詢 pg_config 命令 (通常在 PGHOME/bin 路徑下,如果是abase預設安裝,本命令可直接使用),透過 pg_config 來查詢 PGHOME
三、control檔案解析
(一)、擴充套件控制引數含義
一個擴充套件控制檔案的格式與postgresql.conf檔案相同,即是一個parameter_name = value賦值的列表,每行一個。允許空行和#引入的註釋。注意對任何不是單一詞或數字的值需要加上引號。
控制檔案可以設定下列引數:
1.directory(string):包含擴充套件的SQL指令碼檔案的目錄。除非給出一個絕對路徑,這個目錄名是相對於安裝的SHAREDIR目錄。預設行為等效於指定directory = extension
2.default_version(string):擴充套件的預設版本,如果create extension中沒有指定,將預設使用這個引數
3.comment(string):關於該擴充套件的註釋。該註釋會在初始建立擴充套件時應用,但是擴充套件更新時不會引用該註釋(因為可能會覆蓋使用者增加的註釋)。擴充套件的註釋也可以透過在指令碼檔案中寫上COMMENT命令來設定。
4.encoding(string):該指令碼檔案使用的字符集編碼。當指令碼檔案包含任何非 ASCII 字元時,可以指定這個引數。否則檔案都會被假定為資料庫編碼。
5.module_pathname(string):該引數告訴PG在執行到使用者自定義函式的時候去這個路徑下找庫檔案。
6.requires(string): 這個擴充套件依賴的其他副檔名的一個列表,例如requires = 'foo, bar'。被依賴的擴充套件必須先於這個擴充套件安裝
7.superuser(boolean):預設情況下為true,只有超級使用者能夠建立該擴充套件或者將它更新到一個新版本
8.trusted(boolean):預設值為false,如果設定為true,則允許非超級管理員許可權的使用者安裝superuser已設定為true的擴充套件,即對於在當前資料庫上具有CREATE特權的任何人,都允許安裝。 當執行CREATE EXTENSION的使用者不是超級使用者,但允許透過此引數安裝時,則此安裝或更新指令碼作為引導超級使用者執行,而不是作為呼叫使用者
9.relocatable(boolean):擴充套件的可再定位性,如果支援擴充套件能被重定位到另一個模式則為true,預設為false。三種支援的可定位性級別:
(1)一個完全可重定位的擴充套件能在任何時候被移動到另一個模式中,即使在它被載入到一個資料庫中之後。這種移動透過ALTER EXTENSION SET SCHEMA命令完成,該命令會自動地把所有成員物件重新命名到新的模式中,它的控制檔案中需要設定relocatable = true
(2)一個擴充套件可能在安裝過程中是可重定位的,但是安裝完後就不再可重定位。典型的情況是擴充套件的指令碼檔案需要顯式地引用目標模式,例如為 SQL 函式設定search_path屬性。對於這樣一種擴充套件,在其控制檔案中設定relocatable = false,並且使用@extschema@在指令碼檔案中引用目標模式。在指令碼被執行前,所有這個字串的出現都將被替換為實際的目標模式名。使用者可以使用CREATE EXTENSION的SCHEMA選項設定目標模式名。
(3)如果擴充套件根本就不支援重定位,在它的控制檔案中設定relocatable = false,並且還設定schema為想要的目標模式的名稱。這將阻止使用CREATE EXTENSION的SCHEMA選項修改目標模式,除非它指定的是和控制檔案中相同的模式。如果該擴充套件包括關於模式名的內部假設且模式名不能使用@extschema@的方法替換,這種選擇通常是必須的。@extschema@替換機制在這種情況中也是可用的,不過由於模式名已經被控制檔案所決定,它的使用受到了很大的限制。
10.schema(string) : 這個引數只能為非可重定位擴充套件設定。它強制擴充套件被載入到給定的模式中而非其他模式中。只有在初始建立一個擴充套件時才會參考schema引數,擴充套件更新時則不會參考這個引數
(三)、控制檔案引數補充說明:
1. 關於relocatable,所有的情況下,指令碼檔案將被用search_path執行,它會被設定為指向目標模式,也就是說CREATE EXTENSION做的也是等效的工作:SET LOCAL search_path TO @extschema@, pg_temp;
2. 如果控制檔案中給出了schema引數,目標模式就由該引數決定,否則目標模式由CREATE EXTENSION的SCHEMA選項給出,如果以上兩者都沒有給出則會用當前預設的物件建立模式(在呼叫者search_path中的第一個)。當使用擴充套件檔案的schema引數時,如果目標模式還不存在將建立它,但是在另外兩種情況下它必須已經存在。
3. 如果在控制檔案中的requires中列舉了任何先導擴充套件,它們的目標模式會被追加到search_path的初始設定中,遵循新擴充套件的目標模式。 這允許新擴充套件的指令碼檔案能夠看到它們的物件。
四、sql指令碼擴充套件版本更新機制
可以透過擴充套件的安裝指令碼的每一個發行版本關聯一個版本名稱或者版本號來實現動態的將資料庫從一個版本更新到下一個版本,指令碼的名稱遵循extension--old_version--target_version.sql模式 (例如demo--1.0--1.1.sql包含著把擴充套件demo的版本1.0修改成版本1.1的命令)
命令ALTER EXTENSION UPDATE可以將把一個已安裝的擴充套件更新到指定的新版本。此外,此命令能夠根據更新指令碼的版本序列實現版本更新,例如只有demo--1.0--1.1.sql和demo--1.1--2.0.sql可用,當前安裝了1.0版本並且要求更新到版本2.0,ALTER EXTENSION 命令將將依次應用它們。
PostgreSQL不對更新指令碼的版本名稱的屬性做任何假設,例如demo--1.0--1.1.sql,它不知道是否1.1遵循1.0,它只是匹配可用的版本名稱並遵循需要應用最少更新指令碼的路徑。(版本名稱實際上可以是不包含--或字首或字尾的任何字串-)
Question: 那麼問題就來了,pgsql無法識別更新指令碼的命名規範是否真實有效,是如何透過匹配可用的版本名稱來搜尋出可用的更新指令碼的最短路徑?
Answer: 透過pgsql的內建系統函式pg_extension_update_paths來檢索擴充套件版本之間的可用更新路徑。
pg_extension_update_paths 函式輸出顯示了擴充套件版本的每個組合,以及從source版本升級到目標版本時將執行的擴充套件指令碼組合// 如果路徑為空,則無法升級,如下圖所示:
postgresql並不對更新指令碼的版本名稱的屬性做任何假設,因此即可以實現版本升級,也可以實現“版本降級”,例如,將指令碼命名為demo--1.1--1.0.sql。降級更新可能會導致的問題在於,降級指令碼存在被意外使用的可能性,因為降級指令碼會得到一個較短的路徑,如果降級版本刪除了任何不可替代的物件,這將會得到意想不到的結果。
一個已經存在一段時間的擴充套件可能存在多個版本。例如,如果你已經發布了擴充套件demo的1.0、1.1和1.2版本,就應該有更新指令碼demo--1.0--1.1.sql和demo--1.1--1.2.sql。CREATE EXTENSION能夠自動遵循更新鏈。例如,如果只有指令碼檔案demo--1.0.sql、demo--1.0--1.1.sql和demo--1.1--1.2.sql可用,那麼安裝版本1.2的請求會透過按順序執行上述三個指令碼來實現。這種處理和先安裝1.0然後更新到1.2是一樣的(和ALTER EXTENSION UPDATE一樣,如果有多條路徑可用則優先選擇最短的)
在PostgreSQL10之前,還有必要建立新的指令碼檔案demo--1.1.sql和demo--1.2.sql,它們直接構建比較新的擴充套件版本,或者新的版本無法被直接安裝,而是透過先安裝1.0然後更新。
如果以這種風格維護的擴充套件中使用了二級(版本相關的)控制檔案,記住每個版本都需要一個控制檔案,即使它沒有單獨的安裝指令碼,因為該控制檔案將決定如何執行到這個版本的隱式更新。例如,如果demo--1.0.control指定有requires = 'bar',但demo的其他控制檔案沒有這樣做,在從1.0更新到另一個版本時,該擴充套件對bar的依賴將被刪除。
五、簡單自定義擴充套件開發
(一)、擴充套件開發基本步驟
在原始碼目錄的contrib/目錄下建立與副檔名同名的資料夾,在該資料夾記憶體放開發好的擴充套件原始碼檔案,包含MakeFile檔案、.sql檔案、.control控制檔案等
1.在該目錄下,執行make和make install命令,進行編譯將編譯生成的so檔案和sql檔案遷移至安裝目錄的lib和extension下
2.將so檔案和sql檔案修改許可權和使用者組最後再在資料庫中新增擴充套件:create extension 副檔名;
(二)、擴充套件開發使用的語言
PostgreSQL 支援使用PL/pgSQL語言或者原生的C語言開發擴充套件。PL/pgSQL開發相對簡單,但效能上較原生的C語言要差。只需要透過指定sql檔案的CREATE FUNCTION的LANGUAGE引數即可。
例項一:使用SQL語言實現自定義擴充套件
1、在原始碼目錄的contrib/目錄下建立與副檔名同名的資料夾,在該資料夾內,建立pgtest1--1.0.sql檔案,定義test_add_fun1函式,實現數值相加,其中LANGUAGE引數設定為SQL
如果指令碼是由 psql 而不是 CREATE EXTENSION 執行,則報錯
\echo開始的行,會被擴充套件機制認為是註釋行,如果指令碼檔案被送給psql而不是由CREATE EXTENSION載入,這種機制通常被用來丟擲錯誤
2.建立pgtest1.control檔案
3.建立Makefile檔案
4.進入檔案目錄執行make和make install命令編譯安裝
5.重啟資料庫create extention
6.測試驗證是否能正常使用
(注:可以正常使用結果不對是因為我在核心數值處理中進行了修改,新增了使得所有整形資料+1的操作,並非此擴充套件的問題。)
7.為pgtest1擴充套件新增multi函式(乘法演算法),並將擴充套件版本更新為1.1
當前pgtest1擴充套件安裝的版本為1.0,可透過pg_extension檢視
在pgtest1擴充套件目錄下新增pgtest1--1.0--1.1.sql升級指令碼,並更改Makefile的DATA 引數:增加pgtest1--1.0--1.1.sql
8.重新編譯執行
9.執行ALTER EXTENSION命令將demo擴充套件版本更新到1.1
10.測試驗證升級版本
驗證成功。(注:可以正常使用結果不對是因為我在核心數值處理中進行了修改,新增了使得所有整形資料+1的操作,並非此擴充套件的問題。)
還可以透過pg_extension_update_paths函式檢視版本升級途徑
例項二:使用C語言自定義開發擴充套件
1.在原始碼目錄的contrib/目錄下建立與副檔名同名的資料夾,並建立相關檔案
2.pgtest--1.0.sql宣告擴充套件的函式、該函式的字串常量以及使用的語言
3.pgtest.c:編寫c指令碼,實現擴充套件的自定義函式邏輯
4.pgtest.control:編寫控制檔案,定義預設版本號以及設定是否可重定向
5.編寫Makefile檔案
6.進入原始碼資料夾下執行make與make install 命令編譯安裝
7.重啟資料庫並create extension
8.測試驗證是否成功
驗證成功。(注:可以正常使用結果不對是因為我在核心數值處理中進行了修改,新增了使得所有整形資料+1的操作,並非此擴充套件的問題。)