27.2. 為MySQL添加新函數(shù)有兩個途徑來為MySQL添加新函數(shù):
每種途徑都有其優(yōu)點和缺點:
無論你使用哪種方法去添加新函數(shù),它們都可以被SQL聲明調(diào)用,就像 ABS() 或 SOUNDEX()這樣的固有函數(shù)一樣。 另一個添加函數(shù)的方法時創(chuàng)建存儲函數(shù)。這些函數(shù)時用SQL聲明編寫的,而不是編譯目標代碼。編寫存儲函數(shù)的語法在第20章:存儲程序和函數(shù) 中描述。 下面的小節(jié)描述UDF接口的特性,給出編寫UDF的指令,并討論MySQL為防止UDF被誤用而采取的安全預(yù)防措施。 給出源代碼的例子來說明如何編寫UDF,看一看MySQL源碼分發(fā)版中提供的sql/udf_example.cc 文件。 MySQL自定義函數(shù)接口有如下特性和功能:
CREATE [AGGREGATE] FUNCTION function_name RETURNS {STRING|INTEGER|REAL}
SONAME shared_library_name
DROP FUNCTION function_name
一個自定義函數(shù) (UDF)就是用一個象ABS() 或 CONCAT()這樣的固有(內(nèi)建)函數(shù)一樣作用的新函數(shù)去擴展MySQL。 function_name 是 用在SQL聲明中以備調(diào)用的函數(shù)名字。RETURNS 子句說明函數(shù)返回值的類型。 shared_library_name 是共享目標文件的基本名,共享目標文件含有實現(xiàn)函數(shù)的代碼。該文件必須位于一個能被你系統(tǒng)的動態(tài)連接者搜索的目錄里。 你必須有mysql 數(shù)據(jù)庫的INSERT 權(quán)限才能創(chuàng)建一個函數(shù),你必須有mysql 數(shù)據(jù)庫的DELETE權(quán)限才能撤銷一個函數(shù)。這是因為CREATE FUNCTION 往記錄函數(shù)名字,類型和共享名的mysql.func系統(tǒng)表里添加了一行,而DROP FUNCTION則是從表中刪掉這一行。如果你沒有這個系統(tǒng)表,你應(yīng)該運行mysql_fix_privilege_tables腳本來創(chuàng)建一個。請參閱2.10.2節(jié),“升級授權(quán)表”。 一個有效的函數(shù)是一個用CREATE FUNCTION加載且沒有用DROP FUNCTION移除的函數(shù)。每次服務(wù)器啟動的時候會重新加載所有有效函數(shù),除非你使用--skip-grant-tables參數(shù)啟動mysqld。在這種情況下, 將跳過UDF的初始化,UDF不可用。 要了解編寫自定義函數(shù)的說明,請參閱27.2.3節(jié),“添加新的自定義函數(shù)”。要使得UDF機制能夠起作用,必須使用C或者C++編寫函數(shù),你的系統(tǒng)必須支持動態(tài)加載,而且你必須是動態(tài)編譯的mysqld(非靜態(tài))。 一個AGGREGATE函數(shù)就像一個MySQL固有的集合(總和)函數(shù)一樣起作用,比如,SUM或COUNT()函數(shù)。要使得AGGREGATE 起作用,你的mysql.func表必須包括一個type列。如果你的mysql.func表沒有這一 列,你應(yīng)該運行mysql_fix_privilege_tables腳本來創(chuàng)建此 列。 要使得UDF機制能夠起作用,必須使用C或者C++編寫函數(shù),你的系統(tǒng)必須支持動態(tài)加載。MySQL 源碼分發(fā)版包括一個sql/udf_example.cc 文件,此文件定義了5個新函數(shù)??梢詤⒖歼@個文件,看UDF是如何調(diào)用常規(guī)工作。 為了能使用UDF,你需要動態(tài)鏈接mysqld。不要配置MySQL使用--with-mysqld-ldflags=-all-static參數(shù)。如果你想使用一個需要從mysqld 訪問符號的UDF(例如在使用default_charset_info的sql/udf_example.cc文件中的metaphone函數(shù)),你必須使用-rdynamic參數(shù)來鏈接程序(參閱man dlopen)。如果你計劃使用UDF, 一個經(jīng)驗法則就是,用with-mysqld-ldflags=-rdynamic設(shè)定MySQL,除非你有很好的理由不去這么做。 如果你使用的是預(yù)編譯分發(fā)版的MySQL, 請使用MySQL-Max,其中含有一個動態(tài)鏈接了的服務(wù)器,它可以支持動態(tài)加載。 對于每個你想要使用在SQL聲明中的函數(shù),你應(yīng)該定義相應(yīng)的C (或 C++)函數(shù)。在下面的討論中,xxx用來表示范例函數(shù)的名字,為了區(qū)分使用SQL還是C/C++,xxx()(上標)表示SQL函數(shù)調(diào)用,xxx()(下標)表示C/C++函數(shù)調(diào)用。 你為xxx()編寫來實現(xiàn)接口的C/C++ 函數(shù)如下:
當SQL聲明調(diào)用XXX()時,MySQL調(diào)用初始化函數(shù)xxx_init(),讓它執(zhí)行必要的設(shè)置,比如,檢查 參量或分配內(nèi)存。如果xxx_init() 返回一個錯誤,SQL聲明會退出并給出錯誤信息,而主函數(shù)和去初始化函數(shù)并沒有被調(diào)用。 否則,主函數(shù)xxx() 對每一行都被調(diào)用一次。所有行都處理完之后,調(diào)用去初始化函數(shù)xxx_deinit() 執(zhí)行必要的清除。 對于象SUM()一樣工作的集合函數(shù),你也必須提供如下的函數(shù):
MySQL 按下列操作來處理集合UDF:
所有函數(shù)必須時線程安全的,這不僅對主函數(shù),對初始化和去初始化函數(shù)也一樣,也包括集合函數(shù)要求的附加函數(shù)。這個要求的一個結(jié)果就是,你不能分配任何變化的全局或靜態(tài)變量。如果你需要內(nèi)存,你可以在xxx_init()函數(shù)分配內(nèi)存,然后在xxx_deinit()函數(shù)釋放掉。 下面介紹創(chuàng)建簡單UDF時需要定義的不同函數(shù)。27.2.3節(jié),“添加新的自定義函數(shù)”中介紹了MySQL調(diào)用這些函數(shù)的順序。 如本節(jié)所示,應(yīng)該說明主函數(shù)xxx()。注意返回值和參數(shù)會有所不同,這取決于你說明的SQL函數(shù)xxx()在CREATE FUNCTION聲明中返回的是STRING,INTEGER類型還是REAL類型示: 對于STRING 型函數(shù): char *xxx(UDF_INIT *initid, UDF_ARGS *args,
char *result, unsigned long *length,
char *is_null, char *error);
對于INTEGER型函數(shù): long long xxx(UDF_INIT *initid, UDF_ARGS *args,
char *is_null, char *error);
對于REAL型函數(shù): double xxx(UDF_INIT *initid, UDF_ARGS *args,
char *is_null, char *error);
初始化和去初始化函數(shù)如下說明: my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message); void xxx_deinit(UDF_INIT *initid); initid 參數(shù)被傳遞給所有的三個函數(shù)。它指向一個UDF_INIT 結(jié)構(gòu),這個結(jié)構(gòu)被用來在函數(shù)之間交換信息。UDF_INIT 結(jié)構(gòu)項跟隨著。初始化函數(shù)應(yīng)該給任何它想要改變的項賦值。(要使用項的默認值,就讓它不被改變)
本節(jié)介紹創(chuàng)建集合UDF之時需要定義的不同函數(shù)。27.2.3節(jié),“添加新的自定義函數(shù)” 介紹了MySQL調(diào)用這些函數(shù)的順序。
對集合UDF而言xxx() 函數(shù)應(yīng)該用與非集合UDF一樣的方法來說明。請參閱27.2.3.1節(jié),“UDF調(diào)用簡單函數(shù)的順序”。 對一個集合UDF,MySQL 在組內(nèi)所有行被處理之后調(diào)用xxx()函數(shù)。這里你應(yīng)該一般不會接觸到它的UDF_ARGS參量,但是取而代之地根據(jù)內(nèi)部總和變量返回給你值。 在xxx()中處理的返回值應(yīng)該用與對非集合UDF一樣的方法來操作。請參閱27.2.3.4節(jié),“UDF返回值和錯誤處理”。 xxx_reset() 和 xxx_add() 函數(shù)用與非集合UDF一樣的方法來處理它們的UDF_ARGS 參量。請參閱27.2.3.3節(jié),“UDF參量處理”。 到is_null和error的指針 參量和所有到xxx_reset(), xxx_clear(), xxx_add() 和 xxx()調(diào)用一樣。你可以用這個來提醒你獲取一個錯誤或無論xxx()是否返回NULL的一個結(jié)果。你不能把一個字符串存到*error!error指向單字節(jié)變量而不是字符串緩沖區(qū)。 *is_null 對每一個組都重置(調(diào)用xxx_clear()前), *error 從不重置。 如果 xxx()返回時,*is_null 或 *error 被設(shè)置,MySQL返回 NULL作為組函數(shù)的結(jié)果。 args 參數(shù)指向列著結(jié)構(gòu)元的 UDF_ARGS 結(jié)構(gòu):
如果沒有錯誤發(fā)生,初始化函數(shù)應(yīng)該返回0,否則就返回1。如果有錯誤發(fā)生,xxx_init() 應(yīng)該在message 參數(shù)存儲一個以null結(jié)尾的錯誤消息。該消息被返回給客戶端。消息緩沖區(qū)是 MYSQL_ERRMSG_SIZE 字符長度,但你應(yīng)該試著把消息保持在少于80個字符,以便它能適合標準終端屏幕的寬度。 對于long long 和 double 類型的函數(shù),主函數(shù) xxx()的返回返回值是函數(shù)值。字符函數(shù)返回一個指向結(jié)果的指針,并且設(shè)置 *result 和 *length 為返回值的內(nèi)容和長度。例如: memcpy(result, "result string", 13); *length = 13; 被傳給 xxx() 函數(shù)的結(jié)果緩沖區(qū)是 255 字節(jié)長。如果你的結(jié)果適合這個長度,你就不需要擔(dān)心對結(jié)果的內(nèi)存分配。 如果字符串函數(shù)需要返回一個超過255字節(jié)的字符串,你必須用 malloc() 在你的 xxx_init() 函數(shù)或者 xxx() 函數(shù)里為字符串分配空間,并且在 xxx_deinit() 函數(shù)里釋放此空間。你可以將已分配內(nèi)存存儲在 UDF_INIT 結(jié)構(gòu)里的 ptr 位置以備將來 xxx() 調(diào)用。請參閱27.2.3.1節(jié),“UDF 對簡單函數(shù)的調(diào)用順序”。 要在主函數(shù)中指明一個 NULL 的返回值,設(shè)置 *is_null 為 1 : *is_null = 1; 要在主函數(shù)中指明錯誤返回,設(shè)置 *error 為 1 : *error = 1; 如果 xxx() 對任意行設(shè)置 *error 為 1 ,對于任何 XXX()被調(diào)用的語句處理的當前行和隨后的任意行,該函數(shù)值為 NULL (甚至都不為隨后的行調(diào)用 xxx())。 實現(xiàn)UDF的文件必須在運行服務(wù)器的主機上編譯和安裝。這個步驟在下面介紹,以包含在MySQL源碼分發(fā)版里的UDF文件sql/udf_example.cc 為例。 緊接著下面的指令是對Unix的,對Windows的指令在本節(jié)稍后給出。 udf_example.cc 文件包含下列函數(shù):
一個可動態(tài)加載的文件應(yīng)使用如下這樣的命令編譯為一個可共享的對象文件: shell> gcc -shared -o udf_example.so udf_example.cc 如果你使用gcc,你應(yīng)該能用一個更簡單的命令創(chuàng)建udf_example.so : shell> make udf_example.so 通過運行MySQL源碼樹下sql里的如下命令,你可以容易地為你的系統(tǒng)決定正確的編譯器選項: shell> make udf_example.o 你應(yīng)該運行一個類似于make所顯示那樣的編譯命令,除了要在行尾附近刪除-c選項,并在行尾加上加上 -o udf_example.so。(在某些系統(tǒng)上,你可能需要在命令行留著-c 選項)。 編譯好一個包含有UDF的共享目標后,你必須安裝它并通知MySQL。從udf_example.cc編譯一個共享目標文件產(chǎn)生一個名字類似于udf_example.so 的文件(確切名字可能因平臺而異)。把這個文件復(fù)制到 /usr/lib 這樣被你系統(tǒng)的動態(tài)(運行時)鏈接器搜索到的目錄下,或者 把你放共享目標文件的目錄添加到鏈接器配置文件(如,/etc/ld.so.conf )。 動態(tài)鏈接器的名字時系統(tǒng)特定的(如,在FreeBSD上是ld-elf.so.1 ,在Linux上是 ld.so,在Mac OS X上是dyld )。查看一下你系統(tǒng)的文檔,看看鏈接器的名字是什么及如何配置鏈接器。 在許多系統(tǒng)上,你也可以設(shè)置環(huán)境變量LD_LIBRARY 或 LD_LIBRARY_PATH 指向你放UDF的目錄。dlopen 手冊會告訴你,在你系統(tǒng)上用哪個變量名。你可以在mysql.server 或 mysqld_safe 啟動腳本里設(shè)置這個然后重啟 mysqld。 在一些系統(tǒng)上,配置動態(tài)鏈接器的ldconfig不能識別不是以lib做名字開頭的共享目標。在這種情況下,你應(yīng)該把udf_example.so 改名為 libudf_example.so。 在Windows系統(tǒng)上,你可以通過下列步驟編譯自定義函數(shù):
共享目標文件安裝完以后,為新函數(shù)信息修改 mysqld ,做如下聲明: mysql> CREATE FUNCTION metaphon RETURNS STRING SONAME 'udf_example.so';
mysql> CREATE FUNCTION myfunc_double RETURNS REAL SONAME 'udf_example.so';
mysql> CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME 'udf_example.so';
mysql> CREATE FUNCTION lookup RETURNS STRING SONAME 'udf_example.so';
mysql> CREATE FUNCTION reverse_lookup
-> RETURNS STRING SONAME 'udf_example.so';
mysql> CREATE AGGREGATE FUNCTION avgcost
-> RETURNS REAL SONAME 'udf_example.so';
可以使用DROP FUNCTION刪除函數(shù): mysql> DROP FUNCTION metaphon; mysql> DROP FUNCTION myfunc_double; mysql> DROP FUNCTION myfunc_int; mysql> DROP FUNCTION lookup; mysql> DROP FUNCTION reverse_lookup; mysql> DROP FUNCTION avgcost; CREATE FUNCTION 和 DROP FUNCTION 聲明更新mysql 數(shù)據(jù)庫中的func 系統(tǒng)表。函數(shù)名,類型和共享庫名存進表中。你必須有mysql 數(shù)據(jù)庫的INSERT 和DELETE 權(quán)限來創(chuàng)建和移除函數(shù)。 你不能使用 CREATE FUNCTION 去田間一個先前已經(jīng)被創(chuàng)建的函數(shù)。如果你需要重新安裝一個函數(shù),你可以用DROP FUNCTION移除它,然后再用CREATE FUNCTION重新安裝它。你可能會需要這么做,比如你重新編譯新版本的函數(shù)以便mysqld得到這個新版本。不然,服務(wù)器還繼續(xù)使用舊的版本。 一個有效程序是已被 CREATE FUNCTION加載且沒有被DROP FUNCTION移除的函數(shù)。所有有效函數(shù)在每次服務(wù)器啟動時重新加載,除非你使用--skip-grant-tables選項來啟動mysqld。這種情況下,UDF的初始化將被跳過,UDF不可用。 MySQL 采取下列措施來防止誤用自定義函數(shù)。 你必須有 INSERT 權(quán)限才能使用 CREATE FUNCTION 及有 DELETE 權(quán)限才能使用 DROP FUNCTION。這是很必要的,因為這些聲明在mysql.func表里添加合刪除行。 除了對應(yīng)主 xxx()函數(shù)的xxx 符號,UDF應(yīng)該至少定義一個符號。這些輔助符號對應(yīng) xxx_init(), xxx_deinit(), xxx_reset(), xxx_clear() 和 xxx_add() 函數(shù)。mysqld 也支持一個控制僅有一個xxx符號的UDF是否被加載的--allow-suspicious-udfs。這個選項 默認是關(guān),以防止從共享目標文件而不是從這些已包含的合法UDF加載的企圖。如果你有僅含xxx符號的老版本UDF,以及不能重編譯來包含輔助符號的老版本UDF,那就有必要選--allow-suspicious-udfs 選項。否則,你應(yīng)該避免打開這個選項。 UDF 目標文件不能放在任意目錄。它們必須位于動態(tài)鏈接器被配置來搜索到的一些系統(tǒng)目錄。為強制執(zhí)行這個限制并防止指定被動態(tài)鏈接器搜索到的目錄之外的路徑,MySQL在加載函數(shù)的時候檢查在CREATE FUNCTION 中指定的共享目標文件名,以及存在mysql.func表中的文件的路徑分隔符。這防止通過直接操作mysql.func表指定非法路徑名。有關(guān)UDF和運行時鏈接器,請參閱27.2.3.5節(jié),“編譯和安裝自定義函數(shù)”。 下面介紹添加新固有函數(shù)的步驟。要注意你不能添加固有函數(shù)到二進制分發(fā)版里,因為這個步驟包含修改MySQL源代碼。你必須從源碼分發(fā)版自己編譯MySQL。另外要注意,如果你把MySQL移植到另一個版本(比如新版本放出來的時候),你需要用新版本重復(fù)這個添加 步驟。 采取下列步驟來添加MySQL新的固有函數(shù):
所有函數(shù)都必須是線程安全的,換句話說就是,如果沒有互斥體保護,不要在函數(shù)中使用任何全局或靜態(tài)變量。 如果你想要從函數(shù)::val(), ::val_int()或::str()返回NULL,你應(yīng)該設(shè)null_value為1,并返回0。 對于目標函數(shù) ::str() 有一些需要而外考慮之處::
在MySQL中,你可以用C++定義一個步驟,在一個查詢被發(fā)送到客戶端之前訪問和修改其中的數(shù)據(jù)。修改可以一行接一行地做,或者按照級別成組(GROUP)地做。 我們創(chuàng)建一個范例步驟來演示你可以做的。 此外,我們推薦你看一下mylua。通過它你可以用 LUA語言把運行時里的一個 步驟加載到mysqld中。 analyse([max_elements,[max_memory]]) 這個步驟在sql/sql_analyse.cc定義,這個步驟檢查你查詢的結(jié)果,并且返回對此結(jié)果的一個分析:
SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]]) |
|
|
來自: 322yangxinxing > 《MySQL》