2024-01-12 13:15:30 +08:00
|
|
|
|
package dbi
|
2023-10-27 17:41:45 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
2023-12-07 01:07:34 +08:00
|
|
|
|
"context"
|
2023-10-27 17:41:45 +08:00
|
|
|
|
"database/sql"
|
2025-05-21 04:42:30 +00:00
|
|
|
|
"errors"
|
2023-10-27 17:41:45 +08:00
|
|
|
|
"fmt"
|
2024-12-08 13:04:23 +08:00
|
|
|
|
|
2023-12-07 01:07:34 +08:00
|
|
|
|
"mayfly-go/pkg/errorx"
|
2023-10-27 17:41:45 +08:00
|
|
|
|
"mayfly-go/pkg/logx"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2024-01-06 22:36:50 +08:00
|
|
|
|
// 游标遍历查询结果集处理函数
|
|
|
|
|
|
type WalkQueryRowsFunc func(row map[string]any, columns []*QueryColumn) error
|
|
|
|
|
|
|
2023-10-27 17:41:45 +08:00
|
|
|
|
// db实例连接信息
|
|
|
|
|
|
type DbConn struct {
|
|
|
|
|
|
Id string
|
|
|
|
|
|
Info *DbInfo
|
|
|
|
|
|
|
|
|
|
|
|
db *sql.DB
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-22 23:29:50 +08:00
|
|
|
|
/******************* pool.Conn impl *******************/
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭连接
|
|
|
|
|
|
func (d *DbConn) Close() error {
|
|
|
|
|
|
if d.db != nil {
|
|
|
|
|
|
logx.Debugf("dbm - conn close, connId: %s", d.Id)
|
|
|
|
|
|
if err := d.db.Close(); err != nil {
|
|
|
|
|
|
logx.Errorf("关闭数据库实例[%s]连接失败: %s", d.Id, err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
// TODO 关闭实例隧道会影响其他正在使用的连接,所以暂时不关闭
|
|
|
|
|
|
//if d.Info.useSshTunnel {
|
|
|
|
|
|
// mcm.CloseSshTunnelMachine(d.Info.SshTunnelMachineId, fmt.Sprintf("db:%d", d.Info.Id))
|
|
|
|
|
|
//}
|
|
|
|
|
|
d.db = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (d *DbConn) Ping() error {
|
2025-10-18 03:15:25 +00:00
|
|
|
|
// 首先检查d是否为nil
|
|
|
|
|
|
if d == nil {
|
|
|
|
|
|
return fmt.Errorf("d is nil")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 然后检查d.db是否为nil,这是避免空指针异常的关键
|
|
|
|
|
|
if d.db == nil {
|
|
|
|
|
|
return fmt.Errorf("db is nil")
|
|
|
|
|
|
}
|
2025-05-22 23:29:50 +08:00
|
|
|
|
return d.db.Ping()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-14 21:27:11 +08:00
|
|
|
|
// 执行数据库查询返回的列信息
|
|
|
|
|
|
type QueryColumn struct {
|
|
|
|
|
|
Name string `json:"name"` // 列名
|
2024-12-08 13:04:23 +08:00
|
|
|
|
Type string `json:"type"` // 数据类型
|
2024-11-26 17:32:44 +08:00
|
|
|
|
|
2024-12-08 13:04:23 +08:00
|
|
|
|
DbDataType *DbDataType `json:"-"`
|
|
|
|
|
|
valuer Valuer `json:"-"`
|
2024-11-26 17:32:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-08 13:04:23 +08:00
|
|
|
|
func NewQueryColumn(colName string, columnType *DbDataType) *QueryColumn {
|
2024-11-26 17:32:44 +08:00
|
|
|
|
return &QueryColumn{
|
2024-12-08 13:04:23 +08:00
|
|
|
|
Name: colName,
|
|
|
|
|
|
Type: columnType.DataType.Name,
|
|
|
|
|
|
DbDataType: columnType,
|
|
|
|
|
|
valuer: columnType.DataType.Valuer(),
|
2024-11-26 17:32:44 +08:00
|
|
|
|
}
|
2023-12-14 21:27:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-08 13:04:23 +08:00
|
|
|
|
func (qc *QueryColumn) getValuePtr() any {
|
|
|
|
|
|
return qc.valuer.NewValuePtr()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (qc *QueryColumn) value() any {
|
|
|
|
|
|
return qc.valuer.Value()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (qc *QueryColumn) SQLValue(val any) any {
|
|
|
|
|
|
return qc.DbDataType.DataType.SQLValue(val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-20 03:52:23 +00:00
|
|
|
|
func (d *DbConn) GetDb() *sql.DB {
|
|
|
|
|
|
return d.db
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-27 17:41:45 +08:00
|
|
|
|
// 执行查询语句
|
2023-12-14 21:27:11 +08:00
|
|
|
|
// 依次返回 列信息数组(顺序),结果map,错误
|
2024-01-05 12:09:12 +08:00
|
|
|
|
func (d *DbConn) Query(querySql string, args ...any) ([]*QueryColumn, []map[string]any, error) {
|
|
|
|
|
|
return d.QueryContext(context.Background(), querySql, args...)
|
2023-12-07 01:07:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行查询语句
|
2023-12-14 21:27:11 +08:00
|
|
|
|
// 依次返回 列信息数组(顺序),结果map,错误
|
2024-01-05 12:09:12 +08:00
|
|
|
|
func (d *DbConn) QueryContext(ctx context.Context, querySql string, args ...any) ([]*QueryColumn, []map[string]any, error) {
|
2023-11-26 21:21:35 +08:00
|
|
|
|
result := make([]map[string]any, 0, 16)
|
2024-04-17 21:28:28 +08:00
|
|
|
|
cols, err := d.WalkQueryRows(ctx, querySql, func(row map[string]any, columns []*QueryColumn) error {
|
2024-01-06 22:36:50 +08:00
|
|
|
|
result = append(result, row)
|
|
|
|
|
|
return nil
|
2024-01-05 12:09:12 +08:00
|
|
|
|
}, args...)
|
2024-01-06 22:36:50 +08:00
|
|
|
|
|
2025-03-11 12:42:20 +08:00
|
|
|
|
return cols, result, err
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将查询结果映射至struct,可具体参考sqlx库
|
2023-11-26 21:21:35 +08:00
|
|
|
|
func (d *DbConn) Query2Struct(execSql string, dest any) error {
|
|
|
|
|
|
rows, err := d.db.Query(execSql)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
// rows对象一定要close掉,如果出错,不关掉则会很迅速的达到设置最大连接数,
|
|
|
|
|
|
// 后面的链接过来直接报错或拒绝,实际上也没有起效果
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
if rows != nil {
|
|
|
|
|
|
rows.Close()
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
return scanAll(rows, dest, false)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-29 21:40:26 +08:00
|
|
|
|
// WalkQueryRows 游标方式遍历查询结果集, walkFn返回error不为nil, 则跳出遍历并取消查询
|
2024-04-17 21:28:28 +08:00
|
|
|
|
func (d *DbConn) WalkQueryRows(ctx context.Context, querySql string, walkFn WalkQueryRowsFunc, args ...any) ([]*QueryColumn, error) {
|
2025-03-11 12:42:20 +08:00
|
|
|
|
if qcs, err := d.walkQueryRows(ctx, querySql, walkFn, args...); err != nil {
|
|
|
|
|
|
// 如果是手动停止 则默认返回当前已遍历查询的数据即可
|
|
|
|
|
|
if _, ok := err.(*StopWalkQueryError); ok {
|
|
|
|
|
|
return qcs, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return qcs, wrapSqlError(err)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return qcs, nil
|
|
|
|
|
|
}
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-29 21:40:26 +08:00
|
|
|
|
// WalkTableRows 游标方式遍历指定表的结果集, walkFn返回error不为nil, 则跳出遍历并取消查询
|
2024-04-17 21:28:28 +08:00
|
|
|
|
func (d *DbConn) WalkTableRows(ctx context.Context, tableName string, walkFn WalkQueryRowsFunc) ([]*QueryColumn, error) {
|
2024-01-26 17:17:26 +08:00
|
|
|
|
return d.WalkQueryRows(ctx, fmt.Sprintf("SELECT * FROM %s", tableName), walkFn)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-27 17:41:45 +08:00
|
|
|
|
// 执行 update, insert, delete,建表等sql
|
|
|
|
|
|
// 返回影响条数和错误
|
2024-01-05 05:31:32 +00:00
|
|
|
|
func (d *DbConn) Exec(sql string, args ...any) (int64, error) {
|
|
|
|
|
|
return d.ExecContext(context.Background(), sql, args...)
|
2023-12-07 01:07:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-06 22:36:50 +08:00
|
|
|
|
// 事务执行 update, insert, delete,建表等sql,若tx == nil,则不使用事务
|
|
|
|
|
|
// 返回影响条数和错误
|
|
|
|
|
|
func (d *DbConn) TxExec(tx *sql.Tx, execSql string, args ...any) (int64, error) {
|
|
|
|
|
|
return d.TxExecContext(context.Background(), tx, execSql, args...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-07 01:07:34 +08:00
|
|
|
|
// 执行 update, insert, delete,建表等sql
|
|
|
|
|
|
// 返回影响条数和错误
|
2024-01-06 22:36:50 +08:00
|
|
|
|
func (d *DbConn) ExecContext(ctx context.Context, execSql string, args ...any) (int64, error) {
|
2024-01-07 21:46:25 +08:00
|
|
|
|
return d.TxExecContext(ctx, nil, execSql, args...)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-06 22:36:50 +08:00
|
|
|
|
// 事务执行 update, insert, delete,建表等sql,若tx == nil,则不适用事务
|
|
|
|
|
|
// 返回影响条数和错误
|
|
|
|
|
|
func (d *DbConn) TxExecContext(ctx context.Context, tx *sql.Tx, execSql string, args ...any) (int64, error) {
|
|
|
|
|
|
var res sql.Result
|
|
|
|
|
|
var err error
|
|
|
|
|
|
if tx != nil {
|
|
|
|
|
|
res, err = tx.ExecContext(ctx, execSql, args...)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
res, err = d.db.ExecContext(ctx, execSql, args...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return 0, wrapSqlError(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return res.RowsAffected()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-29 21:40:26 +08:00
|
|
|
|
// Begin 开启事务
|
2024-01-06 22:36:50 +08:00
|
|
|
|
func (d *DbConn) Begin() (*sql.Tx, error) {
|
|
|
|
|
|
return d.db.Begin()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-29 21:40:26 +08:00
|
|
|
|
// GetDialect 获取数据库dialect实现接口
|
2024-01-12 13:15:30 +08:00
|
|
|
|
func (d *DbConn) GetDialect() Dialect {
|
|
|
|
|
|
return d.Info.Meta.GetDialect(d)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-01 17:27:22 +08:00
|
|
|
|
// GetMetadata 获取数据库MetaData
|
|
|
|
|
|
func (d *DbConn) GetMetadata() Metadata {
|
|
|
|
|
|
return d.Info.Meta.GetMetadata(d)
|
2024-03-15 13:31:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-08 13:04:23 +08:00
|
|
|
|
// GetDbDataType 获取定义的数据库数据类型
|
|
|
|
|
|
func (d *DbConn) GetDbDataType(dataType string) *DbDataType {
|
|
|
|
|
|
return GetDbDataType(d.Info.Type, dataType)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-29 21:40:26 +08:00
|
|
|
|
// Stats 返回数据库连接状态
|
2024-01-23 09:27:05 +08:00
|
|
|
|
func (d *DbConn) Stats(ctx context.Context, execSql string, args ...any) sql.DBStats {
|
|
|
|
|
|
return d.db.Stats()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-06 22:36:50 +08:00
|
|
|
|
// 游标方式遍历查询rows, walkFn error不为nil, 则跳出遍历
|
2024-12-08 13:04:23 +08:00
|
|
|
|
func (d *DbConn) walkQueryRows(ctx context.Context, selectSql string, walkFn WalkQueryRowsFunc, args ...any) ([]*QueryColumn, error) {
|
2024-03-28 22:20:39 +08:00
|
|
|
|
cancelCtx, cancelFunc := context.WithCancel(ctx)
|
|
|
|
|
|
defer cancelFunc()
|
2023-12-07 01:07:34 +08:00
|
|
|
|
|
2024-12-08 13:04:23 +08:00
|
|
|
|
rows, err := d.db.QueryContext(cancelCtx, selectSql, args...)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
if err != nil {
|
2024-04-17 21:28:28 +08:00
|
|
|
|
return nil, err
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
// rows对象一定要close掉,如果出错,不关掉则会很迅速的达到设置最大连接数,
|
|
|
|
|
|
// 后面的链接过来直接报错或拒绝,实际上也没有起效果
|
2024-03-28 22:20:39 +08:00
|
|
|
|
defer rows.Close()
|
2023-10-27 17:41:45 +08:00
|
|
|
|
|
|
|
|
|
|
colTypes, err := rows.ColumnTypes()
|
|
|
|
|
|
if err != nil {
|
2024-04-17 21:28:28 +08:00
|
|
|
|
return nil, err
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
lenCols := len(colTypes)
|
|
|
|
|
|
// 列名用于前端表头名称按照数据库与查询字段顺序显示
|
2023-12-14 21:27:11 +08:00
|
|
|
|
cols := make([]*QueryColumn, lenCols)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
// 这里表示一行填充数据
|
|
|
|
|
|
scans := make([]any, lenCols)
|
|
|
|
|
|
// 这里表示一行所有列的值,用[]byte表示
|
|
|
|
|
|
for k, colType := range colTypes {
|
2024-01-29 04:20:23 +00:00
|
|
|
|
// 处理字段名,如果为空,则命名为匿名列
|
|
|
|
|
|
colName := colType.Name()
|
|
|
|
|
|
if colName == "" {
|
|
|
|
|
|
colName = fmt.Sprintf("<anonymous%d>", k+1)
|
|
|
|
|
|
}
|
2024-12-08 13:04:23 +08:00
|
|
|
|
qc := NewQueryColumn(colName, d.GetDbDataType(colType.DatabaseTypeName()))
|
2024-11-26 17:32:44 +08:00
|
|
|
|
cols[k] = qc
|
2024-12-08 13:04:23 +08:00
|
|
|
|
scans[k] = qc.getValuePtr()
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
|
// 不Scan也会导致等待,该链接实际处于未工作的状态,然后也会导致连接数迅速达到最大
|
|
|
|
|
|
if err := rows.Scan(scans...); err != nil {
|
2024-04-17 21:28:28 +08:00
|
|
|
|
return cols, err
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 每行数据
|
|
|
|
|
|
rowData := make(map[string]any, lenCols)
|
|
|
|
|
|
// 把values中的数据复制到row中
|
2024-12-08 13:04:23 +08:00
|
|
|
|
for i := range scans {
|
|
|
|
|
|
rowData[cols[i].Name] = cols[i].value()
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
2024-01-06 22:36:50 +08:00
|
|
|
|
if err = walkFn(rowData, cols); err != nil {
|
2024-11-26 17:32:44 +08:00
|
|
|
|
logx.ErrorfContext(ctx, "[%s] cursor traversal query result set error, exit traversal: %s", selectSql, err.Error())
|
2024-03-28 22:20:39 +08:00
|
|
|
|
cancelFunc()
|
2024-04-17 21:28:28 +08:00
|
|
|
|
return cols, err
|
2024-01-06 22:36:50 +08:00
|
|
|
|
}
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-17 21:28:28 +08:00
|
|
|
|
return cols, nil
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-07 11:48:17 +08:00
|
|
|
|
// 包装sql执行相关错误
|
|
|
|
|
|
func wrapSqlError(err error) error {
|
2025-05-21 04:42:30 +00:00
|
|
|
|
if errors.Is(err, context.Canceled) {
|
2024-11-26 17:32:44 +08:00
|
|
|
|
return errorx.NewBiz("execution cancel")
|
2023-12-07 11:48:17 +08:00
|
|
|
|
}
|
2025-05-21 04:42:30 +00:00
|
|
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
2024-11-26 17:32:44 +08:00
|
|
|
|
return errorx.NewBiz("execution timeout")
|
2023-12-07 11:48:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-03-11 12:42:20 +08:00
|
|
|
|
|
|
|
|
|
|
// StopWalkQueryError 自定义的停止遍历查询错误类型
|
|
|
|
|
|
type StopWalkQueryError struct {
|
|
|
|
|
|
Reason string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Error 实现 error 接口
|
|
|
|
|
|
func (e *StopWalkQueryError) Error() string {
|
|
|
|
|
|
return fmt.Sprintf("stop walk query: %s", e.Reason)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewStopWalkQueryError 创建一个带有reason的StopWalkQueryError
|
|
|
|
|
|
func NewStopWalkQueryError(reason string) *StopWalkQueryError {
|
|
|
|
|
|
return &StopWalkQueryError{Reason: reason}
|
|
|
|
|
|
}
|