golang實現mysql udf

UDF(user-defined function)

當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
}

生成dll

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;

相關文章
相關標籤/搜索