### 第27章:擴展MySQL
### 27.1.?MySQL內部控件
[27.1.1. MySQL線程](#)[27.1.2. MySQL測試套件](#)
??? 本章包含許多在你處理MySQL代碼時需要了解的你事情。如果你想投入到MySQL的開發中,或想要接觸到最新的中間版本的代碼,或者就是想了解開發的進度,請參閱[2.8.3節,“從開發源代碼樹安裝”](# "2.8.3.?Installing from the Development Source Tree")的說明。如果你對MySQL的內部插件感興趣,你也可以訂閱我們的內部插件郵件列表。這個列表的流量相對低一些。欲知如何訂閱的詳情,請參閱[1.7.1.1節,“MySQL郵件列表”](# "1.7.1.1.?The MySQL Mailing Lists")。在MySQL AB 的所有開發人員都在內部插件列表里, 此外,我們幫助那些正在處理MySQL代碼的人。請隨意使用這個郵件列表來問代碼有關的問題,也可用它來發送你想奉獻給MySQL項目的 補丁!
### 27.1.1.?MySQL線程
??? MySQL服務器創建如下線程:
-
TCP/IP 連接線程處理所有連接請求,并為每一個連接創建一個新的專用線程來處理認證和SQL查詢處理。
-
Windows NT 平臺上有一個名為管道處理程序(pipe handler)的線程,它和名為管道連接請求(pipe connect requests)的TCP/IP連接線程做同樣的工作。
-
信號線程處理所有的信號,這個線程通常也處理報警和調用process_alarm() 函數來強制使得空閑時間太長的連接超時。
-
若**mysqld**是與DUSE_ALARM_THREAD線程一起編譯的,這個專用線程是處理 創建的警報的。這個線程用在一些sigwait()函數有問題的系統上,或者用在你想在應用程序中使用thr_alarm()代碼而不帶專用信號處理線程之時。
-
若想使用flush_time=_val_選項,會創建一個專用線程以給定的時間間隔刷新所有表格。
-
每個連接都有它自己的線程。
-
每個被使用INSERT DELAYED 的不同表格都會有自己的線程。
-
若使用了master-host, 則會創建一個從屬的復制線程從主線程讀取并實施更新。
**mysqladmin processlist** 僅顯示連接,INSERT DELAYED, 及復制線程
### 27.1.2.?MySQL測試套件
[27.1.2.1. 運行MySQL測試套件](#)[27.1.2.2. 擴展MySQL測試套件](#)[27.1.2.3. MySQL測試套件報告缺陷](#)
???? 包含在Unix源碼和二進制分發版中的測試系統可以讓用戶和開發人員對MySQL代碼施行回歸測試。這些測試可以在Unix上進行,目前它們還不能在原生的Windows環境下進行。
????? 當前的測試案例套件不能在MySQL中測試所有東西,但是它能發現SQL處理代碼,OS/library文件中大多數明顯的缺陷,并且在測試復件方面也是非常徹底的。我們的終極目標是對100%的代碼進行測試。我們歡迎大家給我們的測試套件添加內容。你可能會特別想貢獻出那些檢查你系統里功能性危機的測試,因為這將確保未來所有發行版的MySQL會與你的應用程序一起更好地運行。
#### 27.1.2.1.?運行MySQL測試套件
??? 測試系統包括一個測試語言解釋器(**mysqltest**),一個運行所有測試的外殼腳本(**mysql-test-run**),用專用語言編寫的測試案例,以及它們的預期結果。在系統上編譯好之后,在源代碼的root下鍵入**make test** 或mysql-test/mysql-test-run。如果安裝了一個二進制分發版, cd 到安裝root (如 /usr/local/mysql), 然后鍵入 scripts/mysql-test-run。所有測試應該都通過,假使有沒通過的,若是一個MySQL里的缺陷,你可以試著找找是因為什么,并且報告這個問題。請參閱[27.1.2.3節,“在MySQL測試套件里報告缺陷”](# "27.1.2.3.?Reporting Bugs in the MySQL Test Suite")。
如果你想要運行測試套件的機器上已經運行了一個 **mysqld** ,只要它不占用9306 和 9307端口,就不用停掉它。如果占用了其中的一個,以可以編輯**mysql-test-run**把主端口和(或)從端口號改為其它可用的。.
可使用下面指令運行單個測試案例 mysql-test/mysql-test-run test_name.
若一個測試未通過,你可以用--force選項來檢查運行著的**mysql-test-run**看是否是別的測試未通過。
#### 27.1.2.2.?擴展MySQL測試套件
你可以用**mysqltest** 語言編寫你自己的測試案例。不幸地是,我們還沒有寫完相關方面完整地文檔。但是,你可以查看我們現有的測試案例,并將它們作為范例。下面幾點將有助于你入手:
-
測試位于 mysql-test/t/*.test
-
測試案例包括終止聲明,測試案例類似于**mysql**命令行客戶端的輸入。 默認的聲明是一個被發送到MySQL服務器的查詢,除非這個聲明被識別為內部命令(如**sleep**)。
-
所有產生結果的查詢,例如SELECT, SHOW, EXPLAIN等,必須在 _@/path/to/result/file_之前。那個文件必須包含期望的結果。生成結果文件的一個簡單辦法是在mysql-test目錄運行**mysqltest -r < t/test-case-name.test** ,然后編輯生成的結果文件,如果需要,可將它們調整到想要的輸出端。在那種情況下,要小心避免添加或刪除任何不可見的字符,確保只改變文本和(或)刪除行。如果插入一行,要確保插入的區域被一個硬標識隔開,且在行尾有一個硬標識。你可能會想要使用**od -c**來確保你的文本編輯器在編輯 步驟中沒有搞亂任何東西。當你發現一個缺陷而不得不編輯**mysqltest -r**的輸出時,我們真希望你不要編輯它。
-
為和我們的設置一致,你應該把你的結果文件放在mysql-test/r 目錄,并取名為test_name.result。如果測試產生不止一個結果,你應該使用諸如 test_name.a.result,test_name.b.result等這樣的名字。
-
如果聲明返回一個錯誤,你可以在聲明的前一行使用--error error-number來詳細說明它。錯誤號可能是由“,”分開的可能錯誤號的列表。
-
如果你正編寫一個重復的測試案例,你應該在測試文件的第一行寫:source include/master-slave.inc;。用connection master; 和 connection slave;來切換主案例和從案例。如果你需要對一個替換的連接做點什么,對于主連接,用connection master1;,對于從連接,用connection slave1;。
-
如果需要在一個循環里做點什么,可以用些這樣的內容:
let $1=1000;
while ($1)
{
# do your queries here
dec $1;
}
-
在查詢之間休眠,使用**sleep**命令。此命令支持幾分之幾秒,所以,例如你想要休眠1.3秒,你可以使用**sleep 1.3;** 命令。
-
對你的測試案例要運行帶附加選項的從案例,以命令行方式把它們放在mysql-test/t/test_name-slave.opt。對于主案例,把它們放在mysql-test/t/test_name-master.opt。
-
如果對測試套件有問題,和想要獻出一個測試案例,發送郵件信息到MySQL 內部插件 郵件列表。請參閱[1.7.1.1節,“MySQL郵件列表”](# "1.7.1.1.?The MySQL Mailing Lists")。 雖然這個列表不接受附件,你可以把相關文件通過ftp上傳到:[ftp://ftp.mysql.com/pub/mysql/upload/](#)
#### 27.1.2.3.?在MySQL測試套件中報告缺陷
如果你的MySQL的版本沒有通過測試套件,你可以采取如下措施:
-
在盡可能多地找到出錯之時的錯誤之前,不要發送缺陷報告。查找之時,請使用**mysqlbug**腳本比便我們能獲取你的系統和MySQL版本信息,參閱[1.7.1.3節 ,“如何報告缺陷或問題](# "1.7.1.3.?How to Report Bugs or Problems")[”](#)。
-
確保包含了**mysql-test-run**的輸出,以及? mysql-test/r目錄下所有.reject文件的內容。
-
如果測試套件里的測試未通過,用如下命令檢查一下看它自己運行時是否通過測試:
cd mysql-test
mysql-test-run --local test-name
如果未能通過,你應該用 --with-debug 配置MySQL并使用--debug選項來運行**mysql-test-run**。如果這樣也未能通過,請把追蹤文件var/tmp/master.trace 上傳到 [ftp://ftp.mysql.com/pub/mysql/upload/](#) 以便我們能檢查它。請記得也要包含你系統的完整描述,**mysqld** 二進制文件的版本,以及你是如何編譯它的。
-
也試著帶--force選項運行一下**mysql-test-run** ,看是否還有別的測試未通過。
-
如果你是自己編譯的MySQL,查看我們的手冊看看如何在你的平臺上編譯MySQL,最好用一個在[http://dev.mysql.com/downloads/](http://dev.mysql.com/downloads/)上我們已經為你編譯好的二進制版本。我們所有標準的二進制版本都能通過測試套件的測試!
-
如果錯誤是Result length mismatch 或 Result content mismatch ,這意味測試的輸出于期望的輸出不匹配,這可能是在MySQL或你的**mysqld** 版本里的缺陷在某些環境下產生稍有不同的結果。
未通過的測試結果放在和結果文件同主名但擴展名為.reject的文件里。如果測試案例未通過,你應該對兩個文件做diff操作。如果你不能發現它們是如何不同,用od -c 命令檢查它們,也檢查一下文件長度。
-
如果測試完全未通過,你應該檢查mysql-test/var/log目錄下的日志文件以獲得有關錯誤的一些提示。
-
如果你是為調試而編譯MySQL,試一下帶--gdb和(或)--debug參數運行**mysql-test-run** 。請參閱[E.1.2節,“創建跟蹤文件”](# "E.1.2.?Creating Trace Files")。
如果你沒有為調試而編譯MySQL,這應該是你可能去做的。只要帶--with-debug參數運行**configure**。 請參閱[2.8節,“使用源碼分發版安裝MySQL ”](# "2.8.?MySQL Installation Using a Source Distribution")。
### 27.2.?為MySQL添加新函數
[27.2.1. 自定義函數接口的特性](#)[27.2.2. CREATE FUNCTION/DROP FUNCTION 語法](#)[27.2.3. 添加新的自定義函數](#)[27.2.4. 添加新的固有函數](#)
有兩個途徑來為MySQL添加新函數:
-
你可以通過自行醫函數接口 (UDF)來添加函數。自定義函數被編譯為目標文件,然后用CREATE FUNCTION 和DROP FUNCTION 聲明動態地添入到服務器中及從服務器中移出。參閱[27.2.2節,“CREATE FUNCTION/DROP FUNCTION 語法”](# "27.2.2.?CREATE FUNCTION/DROP FUNCTION Syntax")。
-
你可以將函數添加為MySQL固有(內建)函數。固有函數被編譯進**mysqld**服務器中,成為永久可用的。
每種途徑都有其優點和缺點:
-
如果你編寫自定義函數,你除了安裝服務器本身之外還要安裝目標文件。如果將你的函數編譯進服務器中,你就不需要這么做了。
-
你可以給二進制版本的MySQL分發版添加UDF。固有函數需要你去修正源碼分發版。.
-
如果你升級你的MySQL分發版,你可以繼續使用先前安裝了的UDF, 除非你升級到一個UDF接口改變了的新版本。對固有函數而言,每次升級你都必須重復一次修正。
無論你使用哪種方法去添加新函數,它們都可以被SQL聲明調用,就像 ABS() 或 SOUNDEX()這樣的固有函數一樣。
另一個添加函數的方法時創建存儲函數。這些函數時用SQL聲明編寫的,而不是編譯目標代碼。編寫存儲函數的語法在[第20章:](#)[存儲程序和函數 ](# "Chapter?20.?Stored Procedures and Functions")中描述。
下面的小節描述UDF接口的特性,給出編寫UDF的指令,并討論MySQL為防止UDF被誤用而采取的安全預防措施。
給出源代碼的例子來說明如何編寫UDF,看一看MySQL源碼分發版中提供的sql/udf_example.cc 文件。
### 27.2.1.?自定義函數接口的特性
MySQL自定義函數接口有如下特性和功能:
-
函數能分÷返回字符串,整數或實數。
-
你可以定義一次作用于一行的簡單函數,或作用于多行的組的集合函數。
-
提供給函數的信息使得函數可以檢查傳遞給它們的參量的數目和類型。
-
你可以讓MySQL在將某參量傳遞給函數之前強制其為某一類型。
-
你可以表示函數返回NULL 或發生錯誤。
### 27.2.2.?CREATE FUNCTION/DROP FUNCTION 語法
CREATE [AGGREGATE] FUNCTION function_name RETURNS {STRING|INTEGER|REAL}
SONAME shared_library_name
DROP FUNCTION function_name
一個自定義函數 (UDF)就是用一個象ABS() 或 CONCAT()這樣的固有(內建)函數一樣作用的新函數去擴展MySQL。
_function_name_ 是 用在SQL聲明中以備調用的函數名字。RETURNS 子句說明函數返回值的類型。 _shared_library_name_ 是共享目標文件的基本名,共享目標文件含有實現函數的代碼。該文件必須位于一個能被你系統的動態連接者搜索的目錄里。
你必須有mysql 數據庫的INSERT 權限才能創建一個函數,你必須有mysql 數據庫的DELETE權限才能撤銷一個函數。這是因為CREATE FUNCTION 往記錄函數名字,類型和共享名的mysql.func系統表里添加了一行,而DROP FUNCTION則是從表中刪掉這一行。如果你沒有這個系統表,你應該運行**mysql_fix_privilege_tables**腳本來創建一個。請參閱[2.10.2節,“升級授權表”](# "2.10.2.?Upgrading the Grant Tables")。
一個有效的函數是一個用CREATE FUNCTION加載且沒有用DROP FUNCTION移除的函數。每次服務器啟動的時候會重新加載所有有效函數,除非你使用--skip-grant-tables參數啟動**mysqld**。在這種情況下, 將跳過UDF的初始化,UDF不可用。
要了解編寫自定義函數的說明,請參閱[27.2.3節,“添加新的自定義函數”](# "27.2.3.?Adding a New User-Defined Function")。要使得UDF機制能夠起作用,必須使用C或者C++編寫函數,你的系統必須支持動態加載,而且你必須是動態編譯的**mysqld**(非靜態)。
一個AGGREGATE函數就像一個MySQL固有的集合(總和)函數一樣起作用,比如,SUM或COUNT()函數。要使得AGGREGATE 起作用,你的mysql.func表必須包括一個type列。如果你的mysql.func表沒有這一 列,你應該運行**mysql_fix_privilege_tables**腳本來創建此 列。
### 27.2.3.?添加新的自定義函數
[27.2.3.1. UDF對簡單函數的調用順序](#)[27.2.3.2. UDF對集合函數的調用順序](#)[27.2.3.3. UDF參量處理](#)[27.2.3.4. UDF返回值和錯誤處理](#)[27.2.3.5. 編譯和安裝自定義函數](#)[27.2.3.6. 自定義函數安全預報措施](#)
要使得UDF機制能夠起作用,必須使用C或者C++編寫函數,你的系統必須支持動態加載。MySQL 源碼分發版包括一個sql/udf_example.cc 文件,此文件定義了5個新函數。可以參考這個文件,看UDF是如何調用常規工作。
為了能使用UDF,你需要動態鏈接**mysqld**。不要配置MySQL使用--with-mysqld-ldflags=-all-static參數。如果你想使用一個需要從**mysqld** 訪問符號的UDF(例如在使用default_charset_info的sql/udf_example.cc文件中的metaphone函數),你必須使用-rdynamic參數來鏈接程序(參閱man dlopen)。如果你計劃使用UDF, 一個經驗法則就是,用with-mysqld-ldflags=-rdynamic設定MySQL,除非你有很好的理由不去這么做。
如果你使用的是預編譯分發版的MySQL, 請使用MySQL-Max,其中含有一個動態鏈接了的服務器,它可以支持動態加載。
對于每個你想要使用在SQL聲明中的函數,你應該定義相應的C (或 C++)函數。在下面的討論中,xxx用來表示范例函數的名字,為了區分使用SQL還是C/C++,xxx()(上標)表示SQL函數調用,xxx()(下標)表示C/C++函數調用。
你為xxx()編寫來實現接口的C/C++ 函數如下:
-
xxx() (必有)
主函數。 這是函數結果被計算的地方。SQL函數數據類型與C/C++函數返回類型的對應關系如下:
| **SQL 類型** | **C/C++ 類型** |
|-----|-----|
| STRING | char * |
| INTEGER | long long |
| REAL | double |
-
xxx_init() (可選)
對xxx()的初始化函數。它可以被用來:
-
檢查傳遞給xxx()的參量數目。
-
檢查參量是否為必需的類型,或者,除此之外,在主函數被調用的時候告訴MySQL將參量強制為想要的類型。
-
分配主函數需要的內存。
-
指定結果的最大長度。
-
指定(對于REAL 函數)小數的最多位數。
-
指定結果是否可以為 NULL。
-
xxx_deinit() (可選)
對xxx()的去初始化函數。它釋放初始化函數分配的內存。
當SQL聲明調用XXX()時,MySQL調用初始化函數xxx_init(),讓它執行必要的設置,比如,檢查 參量或分配內存。如果xxx_init() 返回一個錯誤,SQL聲明會退出并給出錯誤信息,而主函數和去初始化函數并沒有被調用。 否則,主函數xxx() 對每一行都被調用一次。所有行都處理完之后,調用去初始化函數xxx_deinit() 執行必要的清除。
對于象SUM()一樣工作的集合函數,你也必須提供如下的函數:
-
xxx_clear() (在5.1節中必須)
對一個新組重置當前集合值為初試集合值,但不插入任何參量。
-
xxx_add() (必須)
添加參量到當前集合值。
MySQL 按下列操作來處理集合UDF:
1.
調用 xxx_init() 讓集合函數分配它需要用來存儲結果的內存。
1.
按照GROUP BY表達式來排序表。
1.
為每個新組中的第一行調用xxx_clear()函數。
1.
為屬于同組的每一個新行調用xxx_add()函數。
1.
當組改變時或每組的最后一行被處理完之后,調用xxx()來獲取集合結果。
1.
重復,以上3-步直到所有行被處理完。
1.
調用xxx_deinit() 函數去釋放UDF分配的內存。.
所有函數必須時線程安全的,這不僅對主函數,對初始化和去初始化函數也一樣,也包括集合函數要求的附加函數。這個要求的一個結果就是,你不能分配任何變化的全局或靜態變量。如果你需要內存,你可以在xxx_init()函數分配內存,然后在xxx_deinit()函數釋放掉。
#### 27.2.3.1.?UDF 對簡單函數的調用順序
下面介紹創建簡單UDF時需要定義的不同函數。[27.2.3節,“添加新的自定義函數”](# "27.2.3.?Adding a New User-Defined Function")中介紹了MySQL調用這些函數的順序。
如本節所示,應該說明主函數xxx()。注意返回值和參數會有所不同,這取決于你說明的SQL函數xxx()在CREATE FUNCTION聲明中返回的是STRING,INTEGER類型還是REAL類型示:
對于STRING 型函數:
char *xxx(UDF_INIT *initid, UDF_ARGS *args,
char *result, unsigned long *length,
char *is_null, char *error);
對于INTEGER型函數:
long long xxx(UDF_INIT *initid, UDF_ARGS *args,
char *is_null, char *error);
對于REAL型函數:
double xxx(UDF_INIT *initid, UDF_ARGS *args,
char *is_null, char *error);
初始化和去初始化函數如下說明:
my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
void xxx_deinit(UDF_INIT *initid);
initid 參數被傳遞給所有的三個函數。它指向一個UDF_INIT 結構,這個結構被用來在函數之間交換信息。UDF_INIT 結構項跟隨著。初始化函數應該給任何它想要改變的項賦值。(要使用項的默認值,就讓它不被改變)
-
my_bool maybe_null
如果xxx() 能返回NULL,xxx_init()應maybe_null 為 1 。如果任一參量被說明了 maybe_null值,其 默認值是1 。
-
unsigned int decimals
小數位數。默認值是傳到主函數的參量里小數的最大位數。(例如,如果函數傳遞 1.34, 1.345, 和1.3, 那么默認值為,因為1.345 有3位小數。
-
unsigned int max_length
結果的最大長度。max_length 的默認值因函數的結果類型而異。對字符串函數,默認值是最長參量的長度。對整型函數,默認是21位。對實型函數,默認是13再加上initid->decimals指示的小數位數。(對數字函數,長度包含正負號或者小數點符)。
如果想返回團值,你可以把max_length 設為從65KB到16MB。這個內存不會被分配,但是如果有臨時數據需要存儲,這個設置了的值被用來決定使用哪種 列的類型。
-
char *ptr
函數可以用作本身目的的指針。比如,函數可以用initid->ptr 來在分配了的內存內部通訊。 xxx_init() 應該分配內存,并指派給這個指針:
initid->ptr = allocated_memory;
在 xxx() 和 xxx_deinit()中,借用 initid->ptr 來使用或分配內存。
#### 27.2.3.2.?UDF對集合函數的調用順序
本節介紹創建集合UDF之時需要定義的不同函數。[27.2.3節,“添加新的自定義函數”](# "27.2.3.?Adding a New User-Defined Function") 介紹了MySQL調用這些函數的順序。
-
xxx_reset()
當MySQL在一個新組中發現第一行時調用這個函數。它對這個組重置任何內部總和變量,然后使用給定的UDF_ARGS參量作為內部總和值的第一個值。如下說明 xxx_reset() 函數:
char *xxx_reset(UDF_INIT *initid, UDF_ARGS *args,
char *is_null, char *error);
?在MySQL5.1版中UDF接口不需要或不使用xxx_reset()函數,而是使用xxx_clear()函數作為替代。但是如果你想讓UDF也能在老版本的服務器上運行,你也可以定義 xxx_reset() 和 xxx_clear() 函數。(如果你使用了這兩個函數,xxx_reset()函數在很多情況下可以通過調用函數來內部實現,即調用xxx_clear()函數重置所有變量,然后添加UDF_ARGS參量作為組的第一個值。)
-
xxx_clear()
當MySQL需要重置總和結果時調用此函數。對每一個新組,在開始之時調用它,但是它也可以被調用來為一個沒有匹配行在其中的查詢重置值。如下說明xxx_clear():
char *xxx_clear(UDF_INIT *initid, char *is_null, char *error);
在調用xxx_clear()之前is_null 被設置指向 CHAR(0) 。
如果發生錯誤,你可以存儲一個值在 error參量指向的變量中。error指向一單字節變量,而不是一個字符串緩沖區。
xxx_clear() 是MySQL 5.1必須的。
-
xxx_add()
為同組除了第一行之外,所有的行調用這個函數。你應該用它在UDF_ARGS參量中向內部總和變量加值。.
char *xxx_add(UDF_INIT *initid, UDF_ARGS *args,
char *is_null, char *error);
對集合UDF而言xxx() 函數應該用與非集合UDF一樣的方法來說明。請參閱[27.2.3.1節,“UDF調用簡單函數的順序”](# "27.2.3.1.?UDF Calling Sequences for Simple Functions")。
對一個集合UDF,MySQL 在組內所有行被處理之后調用xxx()函數。這里你應該一般不會接觸到它的UDF_ARGS參量,但是取而代之地根據內部總和變量返回給你值。
在xxx()中處理的返回值應該用與對非集合UDF一樣的方法來操作。請參閱[27.2.3.4節,“UDF返回值和錯誤處理”](# "27.2.3.4.?UDF Return Values and Error Handling")。
xxx_reset() 和 xxx_add() 函數用與非集合UDF一樣的方法來處理它們的UDF_ARGS 參量。請參閱[27.2.3.3節,“UDF參量處理”](# "27.2.3.3.?UDF Argument Processing")。
到is_null和error的指針 參量和所有到xxx_reset(), xxx_clear(), xxx_add() 和 xxx()調用一樣。你可以用這個來提醒你獲取一個錯誤或無論xxx()是否返回NULL的一個結果。你不能把一個字符串存到*error!error指向單字節變量而不是字符串緩沖區。
*is_null 對每一個組都重置(調用xxx_clear()前), *error 從不重置。
如果 xxx()返回時,*is_null 或 *error 被設置,MySQL返回 NULL作為組函數的結果。
#### 27.2.3.3.?UDF參量處理
args 參數指向列著結構元的 UDF_ARGS 結構:
-
unsigned int arg_count
參量個數。如果你需要你的函數帶著某個數目的參量被調用,在初始化函數檢查這個值,例如:
if (args->arg_count != 2)
{
strcpy(message,"XXX() requires two arguments");
return 1;
}
-
enum Item_result *arg_type
一個指針,對每個參量指向包含類型的一個數列。可能的類型值是STRING_RESULT, INT_RESULT 和 REAL_RESULT。
要確信一個參量是給定類型的,并且如果不是的話就返回一個錯誤,請檢查初始化函數中的arg_type數列。比如:
if (args->arg_type[0] != STRING_RESULT ||
args->arg_type[1] != INT_RESULT)
{
strcpy(message,"XXX() requires a string and an integer");
return 1;
}
要求你函數的參量是某一類型的另一方法是,使用初始化函數設置arg_type元素為你想要的類型。對所有對xxx()的調用而言,這會導致MySQL強制參量為這些類型。比如,要指定投兩個參量強制成字符串和整數,在xxx_init()中分別:
args->arg_type[0] = STRING_RESULT;
args->arg_type[1] = INT_RESULT;
-
char **args
args->args 與初始化函數做有關傳到你函數的參量的一般情況做通訊。對于常參量i,args->args[i] 指向參量值。(看下面的說明了解如何妥善地訪問這個值)。對非-常參量,args->args[i] 為 0。一個常參量為僅使用參量的表達式,如 3 或 4*7-2 或 SIN(3.14)。一個非常 參量是一個行與行不同的表達式,如,列名或帶非-常參量調用的函數。
對主函數的每次調用,args->args 包含為每個當前處理的行傳遞的實際參量。
如下使用參量i的函數:
-
給一個STRING_RESULT 型的參量作為一個字符串加一個長度,可以允許所有二進制數或任意長度的數處理。字符串內容作為args->args[i] 而字符串長度為args->lengths[i]。你不能采用null結尾的字符串。
-
對一個INT_RESULT型的參量,你必須轉換args->args[i] 為一個long long 值:
long long int_val;
int_val = *((long long*) args->args[i]);
-
對一個REAL_RESULT型參量,你必須轉換args->args[i]為一個雙精度值:
double real_val;
real_val = *((double*) args->args[i]);
-
unsigned long *lengths
對初始化函數,lengths數列表示對每個參量的最大字符串長度。你不要改變它。對主函數的每次調用,lengths包含了對當前處理行傳遞的任何字符串 參量的實際長度。對于INT_RESULT 或 REAL_RESULT類型的參量,lengths 仍包含參量的最大長度(對初始化函數)。
#### 27.2.3.4.?UDF返回值和錯誤處理
如果沒有錯誤發生,初始化函數應該返回0,否則就返回1。如果有錯誤發生,xxx_init() 應該在message 參數存儲一個以null結尾的錯誤消息。該消息被返回給客戶端。消息緩沖區是 MYSQL_ERRMSG_SIZE 字符長度,但你應該試著把消息保持在少于80個字符,以便它能適合標準終端屏幕的寬度。
對于long long 和 double 類型的函數,主函數 xxx()的返回返回值是函數值。字符函數返回一個指向結果的指針,并且設置 *result 和 *length? 為返回值的內容和長度。例如:
memcpy(result, "result string", 13);
*length = 13;
被傳給 xxx() 函數的結果緩沖區是 255 字節長。如果你的結果適合這個長度,你就不需要擔心對結果的內存分配。
如果字符串函數需要返回一個超過255字節的字符串,你必須用 malloc() 在你的 xxx_init() 函數或者 xxx() 函數里為字符串分配空間,并且在 xxx_deinit() 函數里釋放此空間。你可以將已分配內存存儲在 UDF_INIT? 結構里的 ptr? 位置以備將來 xxx() 調用。請參閱[27.2.3.1節,“UDF 對簡單函數的調用順序”](# "27.2.3.1.?UDF Calling Sequences for Simple Functions")。
要在主函數中指明一個 NULL 的返回值,設置 *is_null 為 1 :
*is_null = 1;
要在主函數中指明錯誤返回,設置 *error 為 1 :
*error = 1;
如果 xxx() 對任意行設置 *error 為 1? ,對于任何 XXX()被調用的語句處理的當前行和隨后的任意行,該函數值為 NULL (甚至都不為隨后的行調用 xxx())。
#### 27.2.3.5.?編譯和安裝自定義函數
實現UDF的文件必須在運行服務器的主機上編譯和安裝。這個步驟在下面介紹,以包含在MySQL源碼分發版里的UDF文件sql/udf_example.cc 為例。
緊接著下面的指令是對Unix的,對Windows的指令在本節稍后給出。
?udf_example.cc 文件包含下列函數:
-
metaphon() 返回字符串參量的一個變音位(metaphon)字符串,這有點象一個探測法(soundex)字符串,但是它英語更協調。
-
myfunc_double()返回在其參量中所有字符的ASCII值的和,除以其 參量長度之和。
-
myfunc_int()返回其參量長度之和。
-
sequence([const int]) 返回一個序列,從給定數開始,若沒有給定數則從1開始。
-
lookup() 返回對應主機名的IP數。
-
reverse_lookup() 返回對應一個IP數的主機名。函數可以帶'xxx.xxx.xxx.xxx'形式的一個單字符串 參量調用,要么帶4個數字調用。
一個可動態加載的文件應使用如下這樣的命令編譯為一個可共享的對象文件:
shell> gcc -shared -o udf_example.so udf_example.cc
如果你使用**gcc**,你應該能用一個更簡單的命令創建udf_example.so :
shell> make udf_example.so
通過運行MySQL源碼樹下sql里的如下命令,你可以容易地為你的系統決定正確的編譯器選項:
shell> make udf_example.o
你應該運行一個類似于**make**所顯示那樣的編譯命令,除了要在行尾附近刪除-c選項,并在行尾加上加上 -o udf_example.so。(在某些系統上,你可能需要在命令行留著-c 選項)。
編譯好一個包含有UDF的共享目標后,你必須安裝它并通知MySQL。從udf_example.cc編譯一個共享目標文件產生一個名字類似于udf_example.so 的文件(確切名字可能因平臺而異)。把這個文件復制到 /usr/lib 這樣被你系統的動態(運行時)鏈接器搜索到的目錄下,或者 把你放共享目標文件的目錄添加到鏈接器配置文件(如,/etc/ld.so.conf )。
動態鏈接器的名字時系統特定的(如,在FreeBSD上是**ld-elf.so.1 ** ,在Linux上是 **ld.so**,在Mac OS X上是**dyld** )。查看一下你系統的文檔,看看鏈接器的名字是什么及如何配置鏈接器。
在許多系統上,你也可以設置環境變量LD_LIBRARY 或 LD_LIBRARY_PATH 指向你放UDF的目錄。dlopen 手冊會告訴你,在你系統上用哪個變量名。你可以在**mysql.server** 或 **mysqld_safe** 啟動腳本里設置這個然后重啟 **mysqld**。
在一些系統上,配置動態鏈接器的**ldconfig**不能識別不是以lib做名字開頭的共享目標。在這種情況下,你應該把udf_example.so 改名為 libudf_example.so。
在Windows系統上,你可以通過下列步驟編譯自定義函數:
1.
你需要獲得BitKeeper source repository for MySQL 5.1。 請參閱[?2.8.3節,“從開發源樹安裝”](# "2.8.3.?Installing from the Development Source Tree")。
1.
在源數據倉里的VC++Files/examples/udf_example目錄下,有名為udf_example.def, udf_example.dsp, 和 udf_example.dsw? 的文件。
1.
在數據倉的sql目錄下,復制 udf_example.cc 文件到 VC++Files/examples/udf_example 目錄,并改其名為udf_example.cpp。
1.
Visual Studio VC++用打開 udf_example.dsw 文件,用它把UDF編譯為一個一般項目。
共享目標文件安裝完以后,為新函數信息修改 **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刪除函數:
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 數據庫中的func 系統表。函數名,類型和共享庫名存進表中。你必須有mysql 數據庫的INSERT 和DELETE 權限來創建和移除函數。
你不能使用 CREATE FUNCTION 去田間一個先前已經被創建的函數。如果你需要重新安裝一個函數,你可以用DROP FUNCTION移除它,然后再用CREATE FUNCTION重新安裝它。你可能會需要這么做,比如你重新編譯新版本的函數以便**mysqld**得到這個新版本。不然,服務器還繼續使用舊的版本。
一個有效程序是已被 CREATE FUNCTION加載且沒有被DROP FUNCTION移除的函數。所有有效函數在每次服務器啟動時重新加載,除非你使用--skip-grant-tables選項來啟動**mysqld**。這種情況下,UDF的初始化將被跳過,UDF不可用。
#### 27.2.3.6.?自定義函數安全預防措施
MySQL 采取下列措施來防止誤用自定義函數。
你必須有 INSERT 權限才能使用 CREATE FUNCTION 及有 DELETE 權限才能使用 DROP FUNCTION。這是很必要的,因為這些聲明在mysql.func表里添加合刪除行。
除了對應主 xxx()函數的xxx 符號,UDF應該至少定義一個符號。這些輔助符號對應 xxx_init(), xxx_deinit(), xxx_reset(), xxx_clear() 和 xxx_add() 函數。**mysqld** 也支持一個控制僅有一個xxx符號的UDF是否被加載的--allow-suspicious-udfs。這個選項 默認是關,以防止從共享目標文件而不是從這些已包含的合法UDF加載的企圖。如果你有僅含xxx符號的老版本UDF,以及不能重編譯來包含輔助符號的老版本UDF,那就有必要選--allow-suspicious-udfs 選項。否則,你應該避免打開這個選項。
UDF 目標文件不能放在任意目錄。它們必須位于動態鏈接器被配置來搜索到的一些系統目錄。為強制執行這個限制并防止指定被動態鏈接器搜索到的目錄之外的路徑,MySQL在加載函數的時候檢查在CREATE FUNCTION 中指定的共享目標文件名,以及存在mysql.func表中的文件的路徑分隔符。這防止通過直接操作mysql.func表指定非法路徑名。有關UDF和運行時鏈接器,請參閱[27.2.3.5節,“編譯和安裝自定義函數”](# "27.2.3.5.?Compiling and Installing User-Defined Functions")。
### 27.2.4.?添加新的固有函數
下面介紹添加新固有函數的步驟。要注意你不能添加固有函數到二進制分發版里,因為這個步驟包含修改MySQL源代碼。你必須從源碼分發版自己編譯MySQL。另外要注意,如果你把MySQL移植到另一個版本(比如新版本放出來的時候),你需要用新版本重復這個添加 步驟。
采取下列步驟來添加MySQL新的固有函數:
1.
在定義函數名的lex.h文件中的sql_functions[]數列里添加一行。
1.
如果函數原型是簡單的(只有零個,一個,二個或三個參量),你應該在lex.h中指定 SYM(FUNC_ARG_N_) (其中_N_ 是參量的個數)作為sql_functions[]數列中的第二個 參量,并添加一個在item_create.cc中創建函數目標的函數。可以看看 "ABS" 和 create_funcs_abs() 作為舉例說明。
如果函數原型是復雜的(舉例,如果函數有多種參量),你應該給sql_yacc.yy添加兩行。一行表示**yacc**應該定義的預處理程序記號,(這應該在文件的開始添加)。然后定義函數 參數,并添加一個帶這些參數的項到simple_expr分析規則中。舉一個例子,你可以檢查 sql_yacc.yy 中所有出現的ATAN 看看這個定義是什么樣子的。
1.
在 item_func.h中說明一個繼承自Item_num_func 還是 Item_str_func的類,取決于你的函數是返回一個數還是一個字符串。
1.
在 item_func.cc中是否添加下列說明之一,取決于你是定義一個數字函數還是字符函數:
double Item_func_newname::val()
longlong Item_func_newname::val_int()
String *Item_func_newname::Str(String *str)
如果你從任何標準項繼承了你的目標(類似于Item_num_func),你或許只要定義這些函數中的一個,然后讓父目標照管別的函數。比如,Item_str_func 類定義了一個 val() 函數,它這個函數對::str()返回的值進行 atof()操作。
1.
你或許也定義了下列目標函數:
void Item_func_newname::fix_length_and_dec()
這個函數至少應該計算基于給定參量的max_length。 max_length 是函數可能返回字符的最大個數。如果主函數不能返回 NULL值,這個函數也應該設置 maybe_null = 0。函數可以通過檢查函數的maybe_null值來檢查是否有函數 參量能返回NULL值。你可以看一下Item_func_mod::fix_length_and_dec 作為典型的例子來說明這個問題。
所有函數都必須是線程安全的,換句話說就是,如果沒有互斥體保護,不要在函數中使用任何全局或靜態變量。
如果你想要從函數::val(), ::val_int()或::str()返回NULL,你應該設null_value為1,并返回0。
對于目標函數 ::str() 有一些需要而外考慮之處::
-
字符串參量*str 提供一個字符串緩沖可以用來保持結果(更多關于字符串類型的信息請參閱 sql_string.h文件)。?
-
如果結果為NULL,::str() 函數應該返回保持這個結果的字符串或(char*) 0。
-
除非有絕對地需要,所有當前的字符串函數要避免分配內存!
### 27.3.?為MySQL添加新步驟
[27.3.1. 步驟分析](#)[27.3.2. 編寫步驟](#)
在MySQL中,你可以用C++定義一個步驟,在一個查詢被發送到客戶端之前訪問和修改其中的數據。修改可以一行接一行地做,或者按照級別成組(GROUP)地做。
我們創建一個范例步驟來演示你可以做的。
此外,我們推薦你看一下mylua。通過它你可以用? LUA語言把運行時里的一個 步驟加載到**mysqld**中。
### 27.3.1.?步驟分析
analyse([_max_elements_,[_max_memory_]])
這個步驟在sql/sql_analyse.cc定義,這個步驟檢查你查詢的結果,并且返回對此結果的一個分析:
-
_max_elements_ (默認值 256) 是analyse注意到每 列不同值的最高數目。analyse使用此 參數來檢查是否最優化的列的類型是ENUM類型。
-
_max_memory_ (默認值 8192) 是analyse在查找所有不同值時分配給每 列的最大內存數。i
SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]])
### 27.3.2.?編寫步驟
當前來說,相關的文檔只有源碼。
檢查下列文件可以獲得關于步驟的所有信息:
-
sql/sql_analyse.cc
-
sql/procedure.h
-
sql/procedure.cc
-
sql/sql_select.cc
這是MySQL參考手冊的翻譯版本,關于MySQL參考手冊,請訪問[dev.mysql.com](http://dev.mysql.com/doc/mysql/en)。 原始參考手冊為英文版,與英文版參考手冊相比,本翻譯版可能不是最新的。
- 前言
- 1. 一般信息
- 2. 安裝MySQL
- 3. 教程
- 4. MySQL程序概述
- 5. 數據庫管理
- 6. MySQL中的復制
- 7. 優化
- 8. 客戶端和實用工具程序
- 9. 語言結構
- 10. 字符集支持
- 11. 列類型
- 12. 函數和操作符
- 13. SQL語句語法
- 14. 插件式存儲引擎體系結構
- 15. 存儲引擎和表類型
- 16. 編寫自定義存儲引擎
- 17. MySQL簇
- 18. 分區
- 19. MySQL中的空間擴展
- 20. 存儲程序和函數
- 21. 觸發程序
- 22. 視圖
- 23. INFORMATION_SCHEMA信息數據庫
- 24. 精度數學
- 25. API和庫
- 26. 連接器
- 27. 擴展MySQL
- A. 問題和常見錯誤
- B. 錯誤代碼和消息
- C. 感謝
- D. MySQL變更史
- E. 移植到其他系統
- F. 環境變量
- G. MySQL正則表達式
- H. MySQL中的限制
- I. 特性限制
- J. GNU通用公共許可
- K. MySQL FLOSS許可例外
- 索引