database/api2sql/execute.go

346 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package api2sql ...
//
// Description : api2sql ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2024-08-21 20:45
package api2sql
import (
"context"
"errors"
"fmt"
"git.zhangdeman.cn/zhangdeman/consts"
"git.zhangdeman.cn/zhangdeman/database"
"git.zhangdeman.cn/zhangdeman/database/abstract"
"git.zhangdeman.cn/zhangdeman/database/define"
"git.zhangdeman.cn/zhangdeman/wrapper"
"gorm.io/gorm"
)
var (
Exec = &execute{}
)
type execute struct {
databaseClientManager abstract.IWrapperClient // 全部数据库管理的实例
baseDao database.BaseDao // 基础dao
}
// SetDatabaseClientManager 设置数据库连接管理实例
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:06 2024/8/21
func (e *execute) SetDatabaseClientManager(databaseClientManager abstract.IWrapperClient) {
e.databaseClientManager = databaseClientManager
}
// Run 执行
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:48 2024/8/21
func (e *execute) Run(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) {
if err := e.formatAndValidateInputParam(inputParam); nil != err {
return nil, err
}
if len(inputParam.InputSql) > 0 {
// 基于表达式执行
return e.Express(ctx, inputParam)
}
switch inputParam.SqlType {
case consts.SqlTypeCount:
return e.Count(ctx, inputParam)
case consts.SqlTypeDetail:
return e.Detail(ctx, inputParam)
case consts.SqlTypeList:
return e.List(ctx, inputParam)
case consts.SqlTypeInsert:
return e.Insert(ctx, inputParam)
case consts.SqlTypeUpdate:
return e.Update(ctx, inputParam)
case consts.SqlTypeDelete:
return e.Delete(ctx, inputParam)
default:
return nil, errors.New(inputParam.SqlType + " : sql type is not support")
}
}
// List 方法用于从数据库中查询数据列表。
//
// 参数:
// - ctx: 上下文对象,用于传递请求的上下文信息。
// - inputParam: 查询参数,包含 SQL 语句、列列表等信息。
//
// 返回值:
// - any: 查询结果,返回一个结构体切片,其中包含查询到的数据。
// - error: 错误信息,如果查询过程中发生错误,将返回相应的错误对象。
//
// 使用示例:
// result, err := e.List(ctx, inputParam)
// if err!= nil {
// // 处理错误
// }
// // 处理查询结果
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:52 2024/8/21
func (e *execute) List(ctx context.Context, inputParam *define.Api2SqlParam) ([]map[string]any, error) {
var (
err error
tx *gorm.DB
optionList []define.SetOption
val []map[string]any
)
if tx, err = e.getTx(ctx, inputParam); nil != err {
return nil, err
}
if optionList, err = e.getOptionList(ctx, inputParam); nil != err {
return nil, err
}
if err = e.baseDao.List(tx, &val, optionList...); nil != err {
return nil, err
}
return val, nil
}
// Detail 方法用于获取单个数据的详细信息。
// 它接受一个上下文对象 ctx 和一个指向输入参数结构体 Api2SqlParam 的指针 inputParam。
// 首先,它会将 inputParam 的 Limit 属性设置为 1以限制查询结果为一条记录。
// 然后,它会调用 e.List 方法来执行查询操作,并将结果存储在 res 变量中。
// 如果查询过程中发生错误,它会将错误返回。
// 如果查询成功但结果集为空,它会返回一个错误,表示未找到记录。
// 如果查询成功且结果集不为空,它会返回结果集中的第一条记录。
//
// 参数 :
// - ctx : 上下文对象,用于传递请求的上下文信息。
// - inputParam : 查询参数,包含了查询所需的条件和限制。
//
// 返回值 :
// - 返回查询到的单个数据的详细信息
// - 如果未找到数据则返回错误
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:52 2024/8/21
func (e *execute) Detail(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) {
inputParam.Limit = 1 // 限制查询一条
var (
list []map[string]any
err error
)
if list, err = e.List(ctx, inputParam); nil != err {
return nil, err
}
if len(list) == 0 {
return nil, gorm.ErrRecordNotFound
}
return list[0], nil
}
// Insert 插入
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:52 2024/8/21
func (e *execute) Insert(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) {
var (
err error
tx *gorm.DB
)
if tx, err = e.getTx(ctx, inputParam); nil != err {
return nil, err
}
// 动态生成结果解析结构体
insertVal := map[string]any{}
for _, itemColumn := range inputParam.ConditionList {
insertVal[itemColumn.Column] = itemColumn.Value
}
if err = e.baseDao.Create(tx, &insertVal, database.WithTable(inputParam.Table)); nil != err {
return nil, err
}
return insertVal, nil
}
// Update 更新
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:52 2024/8/21
func (e *execute) Update(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) {
return nil, nil
}
// Count 数量
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:52 2024/8/21
func (e *execute) Count(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) {
return nil, nil
}
// Delete 删除
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:51 2024/8/21
func (e *execute) Delete(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) {
return nil, nil
}
// Express 基于表达式执行
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 20:51 2024/8/21
func (e *execute) Express(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) {
if nil == e.databaseClientManager {
return nil, errors.New("database client is nil, please use `SetDatabaseClientManager` set instance")
}
// 格式化 inputParam
return nil, nil
}
// formatAndValidateInputParam 格式化并校验输入参数
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 10:38 2024/8/22
func (e *execute) formatAndValidateInputParam(inputParam *define.Api2SqlParam) error {
if nil == inputParam {
return errors.New("inputParam is nil")
}
if len(inputParam.DatabaseFlag) == 0 {
return errors.New("databaseFlag is empty")
}
if len(inputParam.Table) == 0 {
return errors.New("table is empty")
}
if len(inputParam.SqlType) == 0 {
return errors.New("sqlType is empty")
}
databaseClient, err := e.databaseClientManager.GetDBClient(inputParam.DatabaseFlag)
if nil != err {
return errors.New(inputParam.DatabaseFlag + " : database client get fail -> " + err.Error())
}
// 尝试获取表结构, api 转 sql 要求必须开启表结构缓存, 否则无法确定相关数据如何解析
if inputParam.TableColumnConfig, err = databaseClient.GetTableFieldList(inputParam.Table); nil != err {
return errors.New(inputParam.DatabaseFlag + " : get table field list fail -> " + err.Error())
}
if len(inputParam.TableColumnConfig) == 0 {
return errors.New(inputParam.DatabaseFlag + " : table field list is empty, please enable `CacheDataTableStructureConfig` or `SetTableColumnConfig`")
}
// 操作字段列表为空
if err = e.validateColumn(inputParam); nil != err {
return err
}
// 验证 force no limit
if inputParam.ForceNoLimit {
inputParam.Limit = 0
inputParam.WithCount = false
}
return nil
}
// validateColumn 验证表字段相关
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 11:48 2024/8/22
func (e *execute) validateColumn(inputParam *define.Api2SqlParam) error {
if len(inputParam.ColumnList) == 0 && wrapper.ArrayType[string]([]string{
consts.SqlTypeList, consts.SqlTypeDetail,
}).Has(inputParam.SqlType) >= 0 {
return errors.New("column list is empty")
}
// 验证字段是否都正确
tableColumnTable := make(map[string]bool)
for _, itemColumn := range inputParam.TableColumnConfig {
tableColumnTable[itemColumn.Column] = true
}
for _, columnConfig := range inputParam.ColumnList {
if !tableColumnTable[columnConfig.Column] {
return errors.New(columnConfig.Column + " : input column not found in table column list")
}
if len(columnConfig.Alias) == 0 {
columnConfig.Alias = columnConfig.Column
}
}
return nil
}
// getTx 获取数据库连接
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 12:20 2024/8/23
func (e *execute) getTx(ctx context.Context, inputParam *define.Api2SqlParam) (*gorm.DB, error) {
if nil != inputParam.Tx {
return inputParam.Tx, nil
}
if inputParam.ForceMaster || // 强制操作主库
wrapper.ArrayType[string]([]string{consts.SqlTypeDelete, consts.SqlTypeInsert, consts.SqlTypeUpdate}).Has(inputParam.SqlType) >= 0 { // 写操作
return e.databaseClientManager.GetMasterClient(ctx, inputParam.DatabaseFlag)
}
return e.databaseClientManager.GetSlaveClient(ctx, inputParam.DatabaseFlag)
}
// getOptionList 设置where条件
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 12:31 2024/8/23
func (e *execute) getOptionList(ctx context.Context, inputParam *define.Api2SqlParam) ([]define.SetOption, error) {
optionList := []define.SetOption{
database.WithTable(inputParam.Table),
}
// 设置 limit offset
if !inputParam.ForceNoLimit && inputParam.Limit > 0 {
optionList = append(optionList, database.WithLimit(inputParam.Limit, inputParam.Offset))
}
for _, item := range inputParam.ConditionList {
optionFunc, err := database.WithAnyCondition(item.Column, item.Operate, item.Value)
if nil != err {
return nil, err
}
optionList = append(optionList, optionFunc)
}
return optionList, nil
}
// dynamicGenerateStruct 生成动态结构体
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 14:19 2024/8/30
func (e *execute) dynamicGenerateStruct(columnList []*define.ColumnConfig) *wrapper.DynamicStruct {
st := wrapper.NewDynamic()
for _, columnConfig := range columnList {
tag := fmt.Sprintf(`%v`, columnConfig.Alias)
column := wrapper.String(columnConfig.Column).SnakeCaseToCamel()
switch columnConfig.Type {
case "int", "int8", "int16", "int32", "int64":
st.AddInt(column, tag, "")
case "uint", "uint8", "uint16", "uint32", "uint64":
st.AddUint(column, tag, "")
case "bool":
st.AddBool(column, tag, "")
case "float":
st.AddBool(column, tag, "")
case "string":
st.AddString(column, tag, "")
case "map":
st.AddMap(column, tag, "")
case "slice":
st.AddSlice(column, tag, "")
}
}
return st
}