當mysql提供的內置函數(count,min,max等)沒法知足需求時,udf用於擴展自定義函數,知足特定查詢需求。
在這裏,假定一種db應用場景,有一個 t_quest_db的table,有2個字段 roleid(INT), data(blob)。data爲本身編碼的二進制數據字段,當想要經過select查詢出肉眼可見的內容時,就須要用到UDF了。mysql
示例代碼用到cgo,附上完整示例代碼:https://files.cnblogs.com/files/fitness/udftest.zipgolang
// 本文件實現t_quest_db的data字段的序列化和反序列化 package udfdll import ( "bytes" "encoding/gob" ) type PlayerDoingQuestDbData struct { TaskId int TaskDbId int64 AcceptTime int32 ProgressMap map[int]string IsProgressFinish bool } type PlayerDoneQuestDbData struct { TaskId int LastDoneTimestamp int32 // 上次完成任務的時間戳 PeriodDoneTimes int // 任務週期內完成的次數(天/周) } // 這個結構存db, 對應t_quest_db的data字段 type PlayerAllQuestDbData struct { DoneTaskMap map[int]*PlayerDoneQuestDbData DoingTaskMap map[int64]*PlayerDoingQuestDbData } // 把this編碼爲二進制,存入data字段 func (this *PlayerAllQuestDbData) ToDbBlob() ([]byte, error) { buf := bytes.NewBuffer([]byte{}) enc := gob.NewEncoder(buf) if err := enc.Encode(this); err != nil { return nil, err } return buf.Bytes(), nil } // 解析data字段 func (this *PlayerAllQuestDbData) FromDbBlob(data []byte) error { if len(data) > 0 { dec := gob.NewDecoder(bytes.NewBuffer(data)) if err := dec.Decode(this); err != nil { return err } } if this.DoneTaskMap == nil { this.DoneTaskMap = make(map[int]*PlayerDoneQuestDbData) } if this.DoingTaskMap == nil { this.DoingTaskMap = make(map[int64]*PlayerDoingQuestDbData) } return nil }
// 本文件實現UDF擴展 package main /* #cgo CFLAGS: -Iinclude #include <mysql.h> #include <string.h> #include <stdlib.h> */ import "C" import ( "bytes" _ "encoding/gob" "encoding/json" "fmt" "strings" "unsafe" ) func main() {} func getUintPointerValue(pointer *uint32, offset int) *C.uint { return (*C.uint)(unsafe.Pointer(uintptr(unsafe.Pointer(pointer)) + uintptr(offset)*uintptr(unsafe.Sizeof(C.uint(0))))) } func getCharPointerValue(pointer **C.char, offset int) **C.char { var c C.char return (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(pointer)) + uintptr(offset)*uintptr(unsafe.Sizeof(&c)))) } func getUlongPointerValue(pointer *C.ulong, offset int) *C.ulong { return (*C.ulong)(unsafe.Pointer(uintptr(unsafe.Pointer(pointer)) + uintptr(offset)*uintptr(unsafe.Sizeof(C.ulong(0))))) } func getBytePointerValue(pointer *C.char, offset int) *C.char { return (*C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(pointer)) + uintptr(offset)*uintptr(unsafe.Sizeof(C.char(0))))) } //export questblob_init func questblob_init(init *C.UDF_INIT, args *C.UDF_ARGS, msg *C.char) C.my_bool { init.maybe_null = 1 if args.arg_count < 2 { s := C.CString("UnExpected err: args count len < 1") C.strcpy(msg, s) C.free(unsafe.Pointer(s)) return 1 } *getUintPointerValue(args.arg_type, 0) = C.STRING_RESULT *getUintPointerValue(args.arg_type, 1) = C.STRING_RESULT return 0 } //export questblob_deinit func questblob_deinit(init *C.UDF_INIT) { C.free(unsafe.Pointer(init.ptr)) } //export questblob func questblob(init *C.UDF_INIT, args *C.UDF_ARGS, result *C.char, length *C.ulong, is_null *C.char, error *C.char) *C.char { *is_null = 1 chars := getCharPointerValue(args.args, 0) arglen := getUlongPointerValue(args.lengths, 0) names := getCharPointerValue(args.args, 1) namelen := getUlongPointerValue(args.lengths, 1) rb := C.GoBytes(unsafe.Pointer(*chars), C.int(*arglen)) name := C.GoStringN(*names, C.int(*namelen)) nameInfos := strings.Split(name, ".") _ = nameInfos[0] // 數據庫表名 _ = nameInfos[1] // 數據庫字段名,實際狀況中可根據這兩個string,決定構造那個對象 errStringPrefix := fmt.Sprintf("[Error]table:'%s' column:'%s' ", nameInfos[0], nameInfos[1]) data := &PlayerAllQuestDbData{} err := data.FromDbBlob(bytes.NewBuffer(rb).Bytes()) if err != nil { *is_null = 0 errString := errStringPrefix + err.Error() *length = C.ulong(len([]byte(errString))) errS := C.CString(errString) init.ptr = errS return errS } rb, err = json.Marshal(data) if err != nil { *is_null = 0 errString := errStringPrefix + " Marshal error: " + err.Error() *length = C.ulong(len([]byte(errString))) errS := C.CString(errString) init.ptr = errS return errS } *is_null = 0 *length = C.ulong(len(rb)) s := C.CString(string(rb)) init.ptr = s return s }
windows運行build.bat,生成libquestblob.dll, 拷貝到mysql的plugin目錄下。sql
CREATE FUNCTION questblob RETURNS STRING SONAME 'libquestblob.dll'; #此語句執行一次便可 SELECT roleid, cast(questblob(t_quest_db.data, 't_quest_db.data') AS CHAR) FROM t_quest_db;