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了。
主要代码实现
示例代码用到cgo,附上完整示例代码:https://files.cnblogs.com/files/fitness/udftest.zip
// 本文件实现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目录下。
使用
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;

golang实现mysql udf的更多相关文章
- MySql UDF 调用外部程序和系统命令
1.mysql利用mysqludf的一个mysql插件可以实现调用外部程序和系统命令 下载lib_mysqludf_sys程序:https://github.com/mysqludf/lib_mysq ...
- Gearman + Nodejs + MySQL UDF异步实现 MySQL 到 Redis 的数据同步
[TOC] 1, 环境 CentOS, MySQL, Redis, Nodejs 2, Redis简介 Redis是一个开源的K-V内存数据库,它的key可以是string/set/hash/list ...
- [转帖]golang操作mysql使用总结
golang操作mysql使用总结 https://www.cnblogs.com/hanyouchun/ 讲解的很详细~ 前言 Golang 提供了database/sql包用于对SQL数据库的访问 ...
- golang 的 mysql 操作
goLang的mysql操作,大致可分为三个步骤: 1.下载mysql驱动:go get github.com/go-sql-driver/mysql 2.建立连接:sql.Open("my ...
- 【API】Mysql UDF BackDoor
1.MySQL UDF是什么 UDF是Mysql提供给用户实现自己功能的一个接口,为了使UDF机制起作用,函数必须用C或C ++编写,并且操作系统必须支持动态加载.这篇文章主要介绍UDF开发和利用的方 ...
- golang操作mysql数据库
golang操作mysql数据库 代码: mysql的增.删.改.查 package main import ( "database/sql" "fmt" &q ...
- mysql udf提权实战测试
根据前天对大牛们的资料学习,进行一次mysql udf提权测试. 测试环境: 受害者系统:centos 7.7 ,docker部署mysql5.6.46, IP:192.168.226.128 攻击者 ...
- Golang操作MySQL的正确姿势
封装原因: 查看了很多网上提供的ORM类型的数据库操作,觉得比较麻烦,需要提前配置很多的表结构体,然后才能使用,对于数据表很多的项目就配置起来就比较麻烦,所以对golang的mysql包进行了外层包装 ...
- 使用golang理解mysql的两阶段提交
使用golang理解mysql的两阶段提交 文章源于一个问题:如果我们现在有两个mysql实例,在我们要尽量简单地完成分布式事务,怎么处理? 场景重现 比如我们现在有两个数据库,mysql3306和m ...
随机推荐
- 4G DTU是什么?
要从任何设备(个人计算机.平板电脑或智能手机)访问Internet,需要DTU或热点.大多数宽带和移动DTU在"4G"或第四代网络系统上运行.虽然互联网连接的许多基本原则与4G D ...
- 我叫MongoDb,不懂我的看完我的故事您就入门啦!
这是mongo基础篇,后续会连续更新4篇 大家好我叫MongoDb,自从07年10月10gen团队把我带到这个世界来,我已经13岁多啦,现在越来越多的小伙伴在拥抱我,我很高兴.我是NoSQL大家族的一 ...
- vscode实现远程linux服务器上Python开发
最近需要训练一个生成对抗网络模型,然后开发接口,不得不在一台有显卡的远程linux服务器上进行,所以,趁着这个机会研究了下怎么使用vscode来进行远程开发. 1 配置免密登录¶ (1)在 ...
- Blog.Core 项目已完成升级.NET5.0
(是时候拿出来这种图了) 本文首发于公众号,但是会有新的内容加进来,所以就在博客园新开了一篇,望见谅.截止发稿,Blog.Core项目Master分支已经迁移到了5.0,新建了3.1的分支. 开心的锣 ...
- reids 入门
1.reids 服务的安装有两种 1.1 exe文件安装,安装完成后,就直接在 "服务"列表中可以查看,并可以停止或启动 1.2 命令行安装:将文件解压至指定文件夹,CMD命令进入 ...
- JS中的Array之方法(1)
a=[2,4,5,6,7,90]; [1]. a.toString(); // 返回字符串表示的数组,逗号分隔 "2,4,5,6,7,90" [2]. a.join('||'); ...
- 315. Count of Smaller Numbers After Self(二分或者算法导论中的归并求逆序数对)
You are given an integer array nums and you have to return a new counts array. The counts array has ...
- KafkaProducer 发送消息流程
Kafka 的 Producer 发送消息采用的是异步发送的方式.在消息发送的过程中,涉及到了 两个线程--main 线程和 Sender 线程,以及一个线程共享变量--RecordAccumulat ...
- menuconfig
1. menuconfig 的存在意义 原由是 项目的 config 项太多了,需要一个人性化的方式设置. menuconfig 背后是一个应用程序,用户和该应用程序交互,完成 config 设置. ...
- ip rule 策略路由
1. 工具安装 yum install iproute 查看工具是否安装 ip -V 2. ip rule 和 ip route ip命令中和策略路由相关的OBJECT有 rule 和 route. ...