feat(database): 添加 ClickHouse 数据库驱动支持
- 实现了完整的 ClickHouse 驱动,包括连接、查询、插入、更新、删除等基本操作 - 添加了 ClickHouse 特有的数据类型转换和 SQL 过滤功能 - 实现了表结构查询和字段信息获取功能 - 添加了 Ping 检查和错误处理机制 - 增加了对 UPDATE 和 DELETE 语句的语法转换以适配 ClickHouse - 添加了批量插入操作的支持 - 新增了 14 个测试用例文件用于验证数据库相关功能main 1.0.2011
parent
70ad831041
commit
398e732301
|
|
@ -0,0 +1,69 @@
|
|||
# GF-Source 自维护代码
|
||||
|
||||
这个目录包含了从 GoFrame (GF) 框架复制并修改的 DAO 生成工具源码,已将所有 `gdb` 相关引用替换为项目自定义的 `database` 包。
|
||||
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
gf-source/
|
||||
├── internal/ # 内部工具包
|
||||
│ ├── consts/ # 常量定义(从 GF internal/consts 复制)
|
||||
│ └── utility/ # 工具函数
|
||||
│ ├── mlog/ # 日志工具
|
||||
│ └── utils/ # 通用工具
|
||||
├── templates/ # 代码生成模板
|
||||
│ ├── consts_gen_dao_template_dao.go
|
||||
│ ├── consts_gen_dao_template_do.go
|
||||
│ ├── consts_gen_dao_template_entity.go
|
||||
│ └── consts_gen_dao_template_table.go
|
||||
├── gendao*.go # DAO 生成核心逻辑
|
||||
├── go.mod # 子模块依赖
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
## 🔧 主要修改
|
||||
|
||||
### 1. 数据库接口替换
|
||||
- ✅ `github.com/gogf/gf/v2/database/gdb` → `git.magicany.cc/black1552/gin-base/database`
|
||||
- ✅ `gdb.DB` → `database.DB`
|
||||
- ✅ `gdb.ConfigNode` → `database.ConfigNode`
|
||||
- ✅ `gdb.AddConfigNode()` → `database.AddConfigNode()`
|
||||
- ✅ `gdb.Instance()` → `database.Instance()`
|
||||
- ✅ `g.DB()` → `database.Database()`
|
||||
|
||||
### 2. 内部包路径修改
|
||||
- ✅ `github.com/gogf/gf/cmd/gf/v2/internal/consts` → `git.magicany.cc/black1552/gin-base/cmd/gf-source/internal/consts`
|
||||
- ✅ `github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog` → `git.magicany.cc/black1552/gin-base/cmd/gf-source/internal/utility/mlog`
|
||||
- ✅ `github.com/gogf/gf/cmd/gf/v2/internal/utility/utils` → `git.magicany.cc/black1552/gin-base/cmd/gf-source/internal/utility/utils`
|
||||
|
||||
### 3. 包名统一
|
||||
- ✅ 所有 gendao 相关文件统一为 `package gendao`
|
||||
- ✅ 模板文件移至 `templates/` 子目录(避免包名冲突)
|
||||
|
||||
## 📦 编译
|
||||
|
||||
```bash
|
||||
cd cmd/gf-source
|
||||
go build .
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **这是参考代码**:这个目录主要用于参考 GF 的 DAO 生成逻辑,实际使用的是 `cmd/gin-dao-gen/main.go`
|
||||
2. **不要直接导入**:这个子模块有独立的 go.mod,不应该被主项目直接导入
|
||||
3. **保持同步**:如果 GF 更新了 DAO 生成逻辑,需要手动同步这些文件并重新应用修改
|
||||
|
||||
## 🔄 更新流程
|
||||
|
||||
如果需要从 GF 更新代码:
|
||||
|
||||
1. 从 `D:\web-object\gf\cmd\gf\internal\cmd\gendao` 复制最新文件
|
||||
2. 运行批量替换脚本将 `gdb` 改为 `database`
|
||||
3. 运行批量替换脚本将 internal 路径改为本项目路径
|
||||
4. 测试编译确保没有错误
|
||||
|
||||
## 📝 相关文件
|
||||
|
||||
- 主程序:`cmd/gin-dao-gen/main.go`
|
||||
- 数据库包:`database/`
|
||||
- 配置文件:`config/config.toml`
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
module git.magicany.cc/black1552/gin-base/cmd/gf-source
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
git.magicany.cc/black1552/gin-base v0.0.0
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
golang.org/x/mod v0.33.0
|
||||
)
|
||||
|
||||
replace git.magicany.cc/black1552/gin-base => ../..
|
||||
|
|
@ -15,6 +15,7 @@ package {{.TplPackageName}}
|
|||
|
||||
import (
|
||||
"{{.TplImportPrefix}}/internal"
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// {{.TplTableNameCamelLowerCase}}Dao is the data access object for the table {{.TplTableName}}.
|
||||
|
|
@ -37,16 +38,16 @@ var (
|
|||
{{if .TplTableSharding -}}
|
||||
// {{.TplTableNameCamelLowerCase}}ShardingHandler is the handler for sharding operations.
|
||||
// You can fill this sharding handler with your custom implementation.
|
||||
func {{.TplTableNameCamelLowerCase}}ShardingHandler(m *gdb.Model) *gdb.Model {
|
||||
m = m.Sharding(gdb.ShardingConfig{
|
||||
Table: gdb.ShardingTableConfig{
|
||||
func {{.TplTableNameCamelLowerCase}}ShardingHandler(m *database.Model) *database.Model {
|
||||
m = m.Sharding(database.ShardingConfig{
|
||||
Table: database.ShardingTableConfig{
|
||||
Enable: true,
|
||||
Prefix: "{{.TplTableShardingPrefix}}",
|
||||
// Replace Rule field with your custom sharding rule.
|
||||
// Or you can use "&gdb.DefaultShardingRule{}" for default sharding rule.
|
||||
Rule: nil,
|
||||
},
|
||||
Schema: gdb.ShardingSchemaConfig{},
|
||||
Schema: database.ShardingSchemaConfig{},
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
|
@ -65,9 +66,7 @@ package internal
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// {{.TplTableNameCamelCase}}Dao is the data access object for the table {{.TplTableName}}.
|
||||
|
|
@ -75,7 +74,7 @@ type {{.TplTableNameCamelCase}}Dao struct {
|
|||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns {{.TplTableNameCamelCase}}Columns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
handlers []database.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// {{.TplTableNameCamelCase}}Columns defines and stores column names for the table {{.TplTableName}}.
|
||||
|
|
@ -89,7 +88,7 @@ var {{.TplTableNameCamelLowerCase}}Columns = {{.TplTableNameCamelCase}}Columns{
|
|||
}
|
||||
|
||||
// New{{.TplTableNameCamelCase}}Dao creates and returns a new DAO object for table data access.
|
||||
func New{{.TplTableNameCamelCase}}Dao(handlers ...gdb.ModelHandler) *{{.TplTableNameCamelCase}}Dao {
|
||||
func New{{.TplTableNameCamelCase}}Dao(handlers ...database.ModelHandler) *{{.TplTableNameCamelCase}}Dao {
|
||||
return &{{.TplTableNameCamelCase}}Dao{
|
||||
group: "{{.TplGroupName}}",
|
||||
table: "{{.TplTableName}}",
|
||||
|
|
@ -99,8 +98,8 @@ func New{{.TplTableNameCamelCase}}Dao(handlers ...gdb.ModelHandler) *{{.TplTable
|
|||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *{{.TplTableNameCamelCase}}Dao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
func (dao *{{.TplTableNameCamelCase}}Dao) DB() database.DB {
|
||||
return database.Database(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
|
|
@ -119,7 +118,7 @@ func (dao *{{.TplTableNameCamelCase}}Dao) Group() string {
|
|||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *{{.TplTableNameCamelCase}}Dao) Ctx(ctx context.Context) *gdb.Model {
|
||||
func (dao *{{.TplTableNameCamelCase}}Dao) Ctx(ctx context.Context) *database.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
|
|
@ -133,7 +132,7 @@ func (dao *{{.TplTableNameCamelCase}}Dao) Ctx(ctx context.Context) *gdb.Model {
|
|||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *{{.TplTableNameCamelCase}}Dao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
func (dao *{{.TplTableNameCamelCase}}Dao) Transaction(ctx context.Context, f func(ctx context.Context, tx database.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
`
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ package consts
|
|||
|
||||
const TemplateGenDaoEntityContent = `
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}}
|
||||
// Code generated and maintained by Gen CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}}
|
||||
// =================================================================================
|
||||
|
||||
package {{.TplPackageName}}
|
||||
|
|
|
|||
|
|
@ -15,20 +15,19 @@ package {{.TplPackageName}}
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// {{.TplTableNameCamelCase}} defines the fields of table "{{.TplTableName}}" with their properties.
|
||||
// This map is used internally by GoFrame ORM to understand table structure.
|
||||
var {{.TplTableNameCamelCase}} = map[string]*gdb.TableField{
|
||||
var {{.TplTableNameCamelCase}} = map[string]*database.TableField{
|
||||
{{.TplTableFields}}
|
||||
}
|
||||
|
||||
// Set{{.TplTableNameCamelCase}}TableFields registers the table fields definition to the database instance.
|
||||
// db: database instance that implements gdb.DB interface.
|
||||
// schema: optional schema/namespace name, especially for databases that support schemas.
|
||||
func Set{{.TplTableNameCamelCase}}TableFields(ctx context.Context, db gdb.DB, schema ...string) error {
|
||||
func Set{{.TplTableNameCamelCase}}TableFields(ctx context.Context, db database.DB, schema ...string) error {
|
||||
return db.GetCore().SetTableFields(ctx, "{{.TplTableName}}", {{.TplTableNameCamelCase}}, schema...)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ import (
|
|||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// {{.TplTableNameCamelCase}}Dao is the data access object for the table {{.TplTableName}}.
|
||||
|
|
@ -100,7 +99,7 @@ func New{{.TplTableNameCamelCase}}Dao(handlers ...database.ModelHandler) *{{.Tpl
|
|||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *{{.TplTableNameCamelCase}}Dao) DB() database.DB {
|
||||
return g.DB(dao.group)
|
||||
return database.Database(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
|
|
|
|||
|
|
@ -31,5 +31,4 @@ var {{.TplTableNameCamelCase}} = map[string]*database.TableField{
|
|||
func Set{{.TplTableNameCamelCase}}TableFields(ctx context.Context, db database.DB, schema ...string) error {
|
||||
return db.GetCore().SetTableFields(ctx, "{{.TplTableName}}", {{.TplTableNameCamelCase}}, schema...)
|
||||
}
|
||||
|
||||
`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,376 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/config"
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
_ "git.magicany.cc/black1552/gin-base/database/drivers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 加载配置
|
||||
cfg := config.GetAllConfig()
|
||||
if cfg == nil {
|
||||
fmt.Println("❌ 错误: 配置为空")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 检查数据库配置
|
||||
dbConfigMap, ok := cfg["database"].(map[string]any)
|
||||
if !ok || len(dbConfigMap) == 0 {
|
||||
fmt.Println("❌ 错误: 未找到数据库配置")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 获取默认数据库配置
|
||||
defaultDbConfig, ok := dbConfigMap["default"].(map[string]any)
|
||||
if !ok {
|
||||
fmt.Println("❌ 错误: 未找到 default 数据库配置")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 提取配置值
|
||||
host := getStringValue(defaultDbConfig, "host", "127.0.0.1")
|
||||
port := getStringValue(defaultDbConfig, "port", "3306")
|
||||
name := getStringValue(defaultDbConfig, "name", "test")
|
||||
dbType := getStringValue(defaultDbConfig, "type", "mysql")
|
||||
|
||||
fmt.Println("=== Gin-Base DAO 代码生成工具 ===")
|
||||
fmt.Printf("📊 数据库: %s\n", name)
|
||||
fmt.Printf("🔧 类型: %s\n", dbType)
|
||||
fmt.Printf("🌐 主机: %s:%s\n\n", host, port)
|
||||
|
||||
// 初始化数据库连接
|
||||
err := initDatabaseFromMap(dbConfigMap)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ 数据库初始化失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 获取数据库实例
|
||||
db := database.Database()
|
||||
|
||||
// 获取所有表
|
||||
tables, err := db.Tables(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ 获取表列表失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("📋 找到 %d 个表:\n", len(tables))
|
||||
for i, table := range tables {
|
||||
fmt.Printf(" %d. %s\n", i+1, table)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 询问用户要生成的表
|
||||
var selectedTables []string
|
||||
if len(os.Args) > 1 {
|
||||
// 从命令行参数获取表名
|
||||
selectedTables = os.Args[1:]
|
||||
} else {
|
||||
// 默认生成所有表
|
||||
selectedTables = tables
|
||||
fmt.Println("💡 提示: 可以通过命令行参数指定要生成的表")
|
||||
fmt.Println(" 例如: gin-dao-gen users orders")
|
||||
}
|
||||
|
||||
// 创建输出目录
|
||||
dirs := []string{
|
||||
"./internal/dao",
|
||||
"./internal/model/do",
|
||||
"./internal/model/entity",
|
||||
"./internal/model/table",
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
fmt.Printf("❌ 创建目录失败 %s: %v\n", dir, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个表生成代码
|
||||
for _, tableName := range selectedTables {
|
||||
fmt.Printf("\n🔨 正在生成表 [%s] 的代码...\n", tableName)
|
||||
|
||||
// 获取表字段信息
|
||||
fields, err := db.TableFields(ctx, tableName)
|
||||
if err != nil {
|
||||
fmt.Printf(" ⚠️ 获取表字段失败: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 生成 Entity
|
||||
entityName := tableNameToStructName(tableName)
|
||||
generateEntity(tableName, entityName, fields)
|
||||
|
||||
// 生成 DO
|
||||
generateDO(tableName, entityName, fields)
|
||||
|
||||
// 生成 DAO
|
||||
generateDAO(tableName, entityName)
|
||||
|
||||
// 生成 Table
|
||||
generateTable(tableName, entityName, fields)
|
||||
|
||||
fmt.Printf(" ✅ 完成\n")
|
||||
}
|
||||
|
||||
fmt.Println("\n🎉 代码生成完成!")
|
||||
fmt.Println("📁 生成的文件位于:")
|
||||
fmt.Println(" - ./internal/dao/")
|
||||
fmt.Println(" - ./internal/model/do/")
|
||||
fmt.Println(" - ./internal/model/entity/")
|
||||
fmt.Println(" - ./internal/model/table/")
|
||||
}
|
||||
|
||||
// 从 Map 初始化数据库
|
||||
func initDatabaseFromMap(dbConfigMap map[string]any) error {
|
||||
for name, nodeConfig := range dbConfigMap {
|
||||
nodeMap, ok := nodeConfig.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
configNode := database.ConfigNode{
|
||||
Host: getStringValue(nodeMap, "host", "127.0.0.1"),
|
||||
Port: getStringValue(nodeMap, "port", "3306"),
|
||||
User: getStringValue(nodeMap, "user", "root"),
|
||||
Pass: getStringValue(nodeMap, "pass", ""),
|
||||
Name: getStringValue(nodeMap, "name", ""),
|
||||
Type: getStringValue(nodeMap, "type", "mysql"),
|
||||
Role: database.Role(getStringValue(nodeMap, "role", "master")),
|
||||
Debug: getBoolValue(nodeMap, "debug", false),
|
||||
Prefix: getStringValue(nodeMap, "prefix", ""),
|
||||
Charset: getStringValue(nodeMap, "charset", "utf8"),
|
||||
}
|
||||
|
||||
if err := database.AddConfigNode(name, configNode); err != nil {
|
||||
return fmt.Errorf("add config node %s failed: %w", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 辅助函数:从 map 中获取字符串值
|
||||
func getStringValue(m map[string]any, key string, defaultValue string) string {
|
||||
if val, ok := m[key]; ok {
|
||||
if str, ok := val.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// 辅助函数:从 map 中获取布尔值
|
||||
func getBoolValue(m map[string]any, key string, defaultValue bool) bool {
|
||||
if val, ok := m[key]; ok {
|
||||
if b, ok := val.(bool); ok {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// 表名转结构体名
|
||||
func tableNameToStructName(tableName string) string {
|
||||
parts := strings.Split(tableName, "_")
|
||||
var result strings.Builder
|
||||
for _, part := range parts {
|
||||
if len(part) > 0 {
|
||||
result.WriteString(strings.ToUpper(part[:1]))
|
||||
result.WriteString(part[1:])
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// 生成 Entity 文件
|
||||
func generateEntity(tableName, entityName string, fields map[string]*database.TableField) {
|
||||
filename := fmt.Sprintf("./internal/model/entity/%s.go", tableName)
|
||||
|
||||
var content strings.Builder
|
||||
content.WriteString("package entity\n\n")
|
||||
content.WriteString("// Auto-generated by gin-base gen dao tool\n\n")
|
||||
content.WriteString(fmt.Sprintf("// %s represents the entity for table %s\n", entityName, tableName))
|
||||
content.WriteString(fmt.Sprintf("type %s struct {\n", entityName))
|
||||
|
||||
for _, field := range fields {
|
||||
fieldName := fieldNameToStructName(field.Name)
|
||||
goType := dbTypeToGoType(field.Type)
|
||||
jsonTag := field.Name
|
||||
|
||||
content.WriteString(fmt.Sprintf("\t%s %s `json:\"%s\" description:\"%s\"`\n",
|
||||
fieldName, goType, jsonTag, field.Comment))
|
||||
}
|
||||
|
||||
content.WriteString("}\n")
|
||||
|
||||
if err := os.WriteFile(filename, []byte(content.String()), 0644); err != nil {
|
||||
fmt.Printf(" ❌ 写入文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 📄 生成 Entity: %s\n", filename)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 DO 文件
|
||||
func generateDO(tableName, entityName string, fields map[string]*database.TableField) {
|
||||
filename := fmt.Sprintf("./internal/model/do/%s.go", tableName)
|
||||
|
||||
var content strings.Builder
|
||||
content.WriteString("package do\n\n")
|
||||
content.WriteString("import \"github.com/gogf/gf/v2/frame/g\"\n\n")
|
||||
content.WriteString("// Auto-generated by gin-base gen dao tool\n\n")
|
||||
content.WriteString(fmt.Sprintf("// %s represents the data object for table %s\n", entityName, tableName))
|
||||
content.WriteString(fmt.Sprintf("type %s struct {\n\tg.Meta `orm:\"table:%s, do:true\"`\n\n", entityName, tableName))
|
||||
|
||||
for _, field := range fields {
|
||||
fieldName := fieldNameToStructName(field.Name)
|
||||
goType := dbTypeToGoType(field.Type)
|
||||
|
||||
content.WriteString(fmt.Sprintf("\t%s *%s `json:\"%s,omitempty\"`\n",
|
||||
fieldName, goType, field.Name))
|
||||
}
|
||||
|
||||
content.WriteString("}\n")
|
||||
|
||||
if err := os.WriteFile(filename, []byte(content.String()), 0644); err != nil {
|
||||
fmt.Printf(" ❌ 写入文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 📄 生成 DO: %s\n", filename)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 DAO 文件
|
||||
func generateDAO(tableName, entityName string) {
|
||||
filename := fmt.Sprintf("./internal/dao/%s.go", tableName)
|
||||
lowerName := strings.ToLower(entityName[:1]) + entityName[1:]
|
||||
|
||||
var content strings.Builder
|
||||
content.WriteString("package dao\n\n")
|
||||
content.WriteString("import (\n")
|
||||
content.WriteString("\t\"git.magicany.cc/black1552/gin-base/database\"\n")
|
||||
content.WriteString(fmt.Sprintf("\t\"git.magicany.cc/black1552/gin-base/internal/model/entity\"\n"))
|
||||
content.WriteString(")\n\n")
|
||||
content.WriteString("// Auto-generated by gin-base gen dao tool\n\n")
|
||||
content.WriteString(fmt.Sprintf("// %s is the DAO for table %s\n", entityName, tableName))
|
||||
content.WriteString(fmt.Sprintf("var %s = New%s()\n\n", lowerName, entityName))
|
||||
content.WriteString(fmt.Sprintf("// %s creates and returns a new DAO instance\n", entityName))
|
||||
content.WriteString(fmt.Sprintf("func New%s() *%sDao {\n", entityName, entityName))
|
||||
content.WriteString(fmt.Sprintf("\treturn &%sDao{\n", entityName))
|
||||
content.WriteString("\t\ttable: \"" + tableName + "\",\n")
|
||||
content.WriteString("\t}\n")
|
||||
content.WriteString("}\n\n")
|
||||
content.WriteString(fmt.Sprintf("// %sDao is the data access object for %s\n", entityName, tableName))
|
||||
content.WriteString(fmt.Sprintf("type %sDao struct {\n", entityName))
|
||||
content.WriteString("\ttable string\n")
|
||||
content.WriteString("}\n\n")
|
||||
content.WriteString("// Table returns the table name\n")
|
||||
content.WriteString(fmt.Sprintf("func (d *%sDao) Table() string {\n", entityName))
|
||||
content.WriteString("\treturn d.table\n")
|
||||
content.WriteString("}\n\n")
|
||||
content.WriteString("// DB returns the database instance\n")
|
||||
content.WriteString("func (d *DB) DB() database.DB {\n")
|
||||
content.WriteString("\treturn database.Database()\n")
|
||||
content.WriteString("}\n")
|
||||
|
||||
if err := os.WriteFile(filename, []byte(content.String()), 0644); err != nil {
|
||||
fmt.Printf(" ❌ 写入文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 📄 生成 DAO: %s\n", filename)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 Table 文件
|
||||
func generateTable(tableName, entityName string, fields map[string]*database.TableField) {
|
||||
filename := fmt.Sprintf("./internal/model/table/%s.go", tableName)
|
||||
|
||||
var content strings.Builder
|
||||
content.WriteString("package table\n\n")
|
||||
content.WriteString("// Auto-generated by gin-base gen dao tool\n\n")
|
||||
content.WriteString("const (\n")
|
||||
content.WriteString(fmt.Sprintf("\t// %s is the table name\n", entityName))
|
||||
content.WriteString(fmt.Sprintf("\t%s = \"%s\"\n", entityName, tableName))
|
||||
content.WriteString(")\n\n")
|
||||
content.WriteString("// Columns defines all columns of the table\n")
|
||||
content.WriteString("var Columns = struct {\n")
|
||||
|
||||
for _, field := range fields {
|
||||
fieldName := fieldNameToConstName(field.Name)
|
||||
content.WriteString(fmt.Sprintf("\t%s string\n", fieldName))
|
||||
}
|
||||
content.WriteString("}{\n")
|
||||
|
||||
for _, field := range fields {
|
||||
fieldName := fieldNameToConstName(field.Name)
|
||||
content.WriteString(fmt.Sprintf("\t%s: \"%s\",\n", fieldName, field.Name))
|
||||
}
|
||||
content.WriteString("}\n")
|
||||
|
||||
if err := os.WriteFile(filename, []byte(content.String()), 0644); err != nil {
|
||||
fmt.Printf(" ❌ 写入文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 📄 生成 Table: %s\n", filename)
|
||||
}
|
||||
}
|
||||
|
||||
// 字段名转结构体字段名
|
||||
func fieldNameToStructName(fieldName string) string {
|
||||
parts := strings.Split(fieldName, "_")
|
||||
var result strings.Builder
|
||||
for _, part := range parts {
|
||||
if len(part) > 0 {
|
||||
result.WriteString(strings.ToUpper(part[:1]))
|
||||
result.WriteString(part[1:])
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// 字段名转常量名
|
||||
func fieldNameToConstName(fieldName string) string {
|
||||
parts := strings.Split(fieldName, "_")
|
||||
var result strings.Builder
|
||||
for i, part := range parts {
|
||||
if i > 0 {
|
||||
result.WriteString("_")
|
||||
}
|
||||
result.WriteString(strings.ToUpper(part))
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// 数据库类型转 Go 类型
|
||||
func dbTypeToGoType(dbType string) string {
|
||||
dbType = strings.ToLower(dbType)
|
||||
|
||||
switch {
|
||||
case strings.Contains(dbType, "int"):
|
||||
if strings.Contains(dbType, "bigint") {
|
||||
return "int64"
|
||||
}
|
||||
return "int"
|
||||
case strings.Contains(dbType, "float"), strings.Contains(dbType, "double"), strings.Contains(dbType, "decimal"):
|
||||
return "float64"
|
||||
case strings.Contains(dbType, "bool"):
|
||||
return "bool"
|
||||
case strings.Contains(dbType, "datetime"), strings.Contains(dbType, "timestamp"):
|
||||
return "*gtime.Time"
|
||||
case strings.Contains(dbType, "date"):
|
||||
return "*gtime.Time"
|
||||
case strings.Contains(dbType, "text"), strings.Contains(dbType, "char"), strings.Contains(dbType, "varchar"):
|
||||
return "string"
|
||||
case strings.Contains(dbType, "blob"), strings.Contains(dbType, "binary"):
|
||||
return "[]byte"
|
||||
default:
|
||||
return "string"
|
||||
}
|
||||
}
|
||||
81
cmd/go.mod
81
cmd/go.mod
|
|
@ -1,81 +0,0 @@
|
|||
module git.magicany.cc/black1552/gin-base/cmd/gen
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
git.magicany.cc/black1552/gin-base v1.0.2009
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
github.com/schollz/progressbar/v3 v3.19.0
|
||||
golang.org/x/mod v0.34.0
|
||||
golang.org/x/tools v0.43.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.10.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||
github.com/lib/pq v1.12.0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.21 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.7.1 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.2.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.8 // indirect
|
||||
github.com/paulmach/orb v0.7.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sijms/go-ora/v2 v2.7.10 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.41.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.48.0 // indirect
|
||||
)
|
||||
|
||||
replace git.magicany.cc/black1552/gin-base => ./..
|
||||
277
cmd/go.sum
277
cmd/go.sum
|
|
@ -1,277 +0,0 @@
|
|||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15 h1:lLAZliqrZEygkxosLaW1qHyeTb4Ho7fVCZ0WKCpLocU=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15/go.mod h1:Z21o82zD8FFqefOQDg93c0XITlxGbTsWQuRm588Azkk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
|
||||
github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogf/gf/v2 v2.10.0 h1:rzDROlyqGMe/eM6dCalSR8dZOuMIdLhmxKSH1DGhbFs=
|
||||
github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo=
|
||||
github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
|
||||
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/microsoft/go-mssqldb v1.7.1 h1:KU/g8aWeM3Hx7IMOFpiwYiUkU+9zeISb4+tx3ScVfsM=
|
||||
github.com/microsoft/go-mssqldb v1.7.1/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
|
||||
github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
|
||||
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
|
||||
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/paulmach/orb v0.7.1 h1:Zha++Z5OX/l168sqHK3k4z18LDvr+YAO/VjK0ReQ9rU=
|
||||
github.com/paulmach/orb v0.7.1/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
|
||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
|
||||
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sijms/go-ora/v2 v2.7.10 h1:GSLdj0PYYgSndhsnm7b6p32OqgnwnUZSkFb3j+htfhI=
|
||||
github.com/sijms/go-ora/v2 v2.7.10/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
||||
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.48.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4=
|
||||
modernc.org/sqlite v1.48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
32
cmd/main.go
32
cmd/main.go
|
|
@ -53,8 +53,14 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 获取数据库实例
|
||||
db := database.Database()
|
||||
// 获取数据库实例(使用 default 组)
|
||||
db := database.Database("default")
|
||||
|
||||
// 调试信息:打印当前使用的数据库名称
|
||||
config := db.GetConfig()
|
||||
if config != nil {
|
||||
fmt.Printf("🔍 调试: 当前数据库名 = %s, 类型 = %s\n", config.Name, config.Type)
|
||||
}
|
||||
|
||||
// 获取所有表
|
||||
tables, err := db.Tables(ctx)
|
||||
|
|
@ -63,6 +69,20 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 如果没找到表,尝试直接查询验证
|
||||
if len(tables) == 0 {
|
||||
fmt.Println("⚠️ 警告: 未找到任何表,尝试直接查询...")
|
||||
result, err := db.Query(ctx, "SHOW TABLES")
|
||||
if err != nil {
|
||||
fmt.Printf("❌ 直接查询失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("🔍 直接查询结果: %d 行\n", len(result))
|
||||
for _, row := range result {
|
||||
fmt.Printf(" - %v\n", row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("📋 找到 %d 个表:\n", len(tables))
|
||||
for i, table := range tables {
|
||||
fmt.Printf(" %d. %s\n", i+1, table)
|
||||
|
|
@ -104,7 +124,13 @@ func main() {
|
|||
fields, err := db.TableFields(ctx, tableName)
|
||||
if err != nil {
|
||||
fmt.Printf(" ⚠️ 获取表字段失败: %v\n", err)
|
||||
continue
|
||||
return // 使用 return 而不是 continue
|
||||
}
|
||||
|
||||
// 调试:打印字段信息
|
||||
fmt.Printf(" 🔍 调试: 找到 %d 个字段\n", len(fields))
|
||||
for name, field := range fields {
|
||||
fmt.Printf(" - %s: %s (%s)\n", name, field.Type, field.Comment)
|
||||
}
|
||||
|
||||
// 生成 Entity
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package consts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
)
|
||||
|
||||
type (
|
||||
Var = gvar.Var // Var is a universal variable interface, like generics.
|
||||
Ctx = context.Context // Ctx is alias of frequently-used type context.Context.
|
||||
Meta = gmeta.Meta // Meta is alias of frequently-used type gmeta.Meta.
|
||||
)
|
||||
|
||||
type (
|
||||
Map = map[string]any // Map is alias of frequently-used map type map[string]any.
|
||||
MapAnyAny = map[any]any // MapAnyAny is alias of frequently-used map type map[any]any.
|
||||
MapAnyStr = map[any]string // MapAnyStr is alias of frequently-used map type map[any]string.
|
||||
MapAnyInt = map[any]int // MapAnyInt is alias of frequently-used map type map[any]int.
|
||||
MapStrAny = map[string]any // MapStrAny is alias of frequently-used map type map[string]any.
|
||||
MapStrStr = map[string]string // MapStrStr is alias of frequently-used map type map[string]string.
|
||||
MapStrInt = map[string]int // MapStrInt is alias of frequently-used map type map[string]int.
|
||||
MapIntAny = map[int]any // MapIntAny is alias of frequently-used map type map[int]any.
|
||||
MapIntStr = map[int]string // MapIntStr is alias of frequently-used map type map[int]string.
|
||||
MapIntInt = map[int]int // MapIntInt is alias of frequently-used map type map[int]int.
|
||||
MapAnyBool = map[any]bool // MapAnyBool is alias of frequently-used map type map[any]bool.
|
||||
MapStrBool = map[string]bool // MapStrBool is alias of frequently-used map type map[string]bool.
|
||||
MapIntBool = map[int]bool // MapIntBool is alias of frequently-used map type map[int]bool.
|
||||
)
|
||||
|
||||
type (
|
||||
List = []Map // List is alias of frequently-used slice type []Map.
|
||||
ListAnyAny = []MapAnyAny // ListAnyAny is alias of frequently-used slice type []MapAnyAny.
|
||||
ListAnyStr = []MapAnyStr // ListAnyStr is alias of frequently-used slice type []MapAnyStr.
|
||||
ListAnyInt = []MapAnyInt // ListAnyInt is alias of frequently-used slice type []MapAnyInt.
|
||||
ListStrAny = []MapStrAny // ListStrAny is alias of frequently-used slice type []MapStrAny.
|
||||
ListStrStr = []MapStrStr // ListStrStr is alias of frequently-used slice type []MapStrStr.
|
||||
ListStrInt = []MapStrInt // ListStrInt is alias of frequently-used slice type []MapStrInt.
|
||||
ListIntAny = []MapIntAny // ListIntAny is alias of frequently-used slice type []MapIntAny.
|
||||
ListIntStr = []MapIntStr // ListIntStr is alias of frequently-used slice type []MapIntStr.
|
||||
ListIntInt = []MapIntInt // ListIntInt is alias of frequently-used slice type []MapIntInt.
|
||||
ListAnyBool = []MapAnyBool // ListAnyBool is alias of frequently-used slice type []MapAnyBool.
|
||||
ListStrBool = []MapStrBool // ListStrBool is alias of frequently-used slice type []MapStrBool.
|
||||
ListIntBool = []MapIntBool // ListIntBool is alias of frequently-used slice type []MapIntBool.
|
||||
)
|
||||
|
||||
type (
|
||||
Slice = []any // Slice is alias of frequently-used slice type []any.
|
||||
SliceAny = []any // SliceAny is alias of frequently-used slice type []any.
|
||||
SliceStr = []string // SliceStr is alias of frequently-used slice type []string.
|
||||
SliceInt = []int // SliceInt is alias of frequently-used slice type []int.
|
||||
)
|
||||
|
||||
type (
|
||||
Array = []any // Array is alias of frequently-used slice type []any.
|
||||
ArrayAny = []any // ArrayAny is alias of frequently-used slice type []any.
|
||||
ArrayStr = []string // ArrayStr is alias of frequently-used slice type []string.
|
||||
ArrayInt = []int // ArrayInt is alias of frequently-used slice type []int.
|
||||
)
|
||||
|
|
@ -4,58 +4,60 @@
|
|||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package clickhouse implements database.Driver for ClickHouse database.
|
||||
// Package clickhouse implements database.Driver, which supports operations for database ClickHouse.
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/ClickHouse/clickhouse-go/v2"
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
// Driver is the driver for ClickHouse database.
|
||||
// Driver is the driver for clickhouse database.
|
||||
type Driver struct {
|
||||
*database.Core
|
||||
}
|
||||
|
||||
var (
|
||||
errUnsupportedInsertIgnore = errors.New("unsupported method:InsertIgnore")
|
||||
errUnsupportedInsertGetId = errors.New("unsupported method:InsertGetId")
|
||||
errUnsupportedReplace = errors.New("unsupported method:Replace")
|
||||
errUnsupportedBegin = errors.New("unsupported method:Begin")
|
||||
errUnsupportedTransaction = errors.New("unsupported method:Transaction")
|
||||
)
|
||||
|
||||
const (
|
||||
quoteChar = `"`
|
||||
updateFilterPattern = `(?i)UPDATE[\s]+?(\w+[\.]?\w+)[\s]+?SET`
|
||||
deleteFilterPattern = `(?i)DELETE[\s]+?FROM[\s]+?(\w+[\.]?\w+)`
|
||||
filterTypePattern = `(?i)^UPDATE|DELETE`
|
||||
needParsedSqlInCtx gctx.StrKey = "NeedParsedSql"
|
||||
driverName = "clickhouse"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := database.Register("clickhouse", New()); err != nil {
|
||||
if err := database.Register(`clickhouse`, New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates and returns a driver that implements database.Driver for ClickHouse.
|
||||
// New create and returns a driver that implements database.Driver, which supports operations for clickhouse.
|
||||
func New() database.Driver {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
// New creates and returns a database object for ClickHouse.
|
||||
// New creates and returns a database object for clickhouse.
|
||||
// It implements the interface of database.Driver for extra database driver installation.
|
||||
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||
return &Driver{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetChars returns the security char for ClickHouse.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for ClickHouse.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf("clickhouse://%s:%s@%s:%s/%s",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name)
|
||||
func (d *Driver) injectNeedParsedSql(ctx context.Context) context.Context {
|
||||
if ctx.Value(needParsedSqlInCtx) != nil {
|
||||
return ctx
|
||||
}
|
||||
return sql.Open("clickhouse", source)
|
||||
return context.WithValue(ctx, needParsedSqlInCtx, true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// ConvertValueForField converts value to the type of the record field.
|
||||
func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fieldValue any) (any, error) {
|
||||
switch itemValue := fieldValue.(type) {
|
||||
case time.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return itemValue, nil
|
||||
|
||||
case uuid.UUID:
|
||||
return itemValue, nil
|
||||
|
||||
case *time.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue == nil || itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return itemValue, nil
|
||||
|
||||
case gtime.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
// for gtime type, needs to get time.Time
|
||||
return itemValue.Time, nil
|
||||
|
||||
case *gtime.Time:
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
if itemValue == nil || itemValue.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
// for gtime type, needs to get time.Time
|
||||
return itemValue.Time, nil
|
||||
|
||||
case decimal.Decimal:
|
||||
return itemValue, nil
|
||||
|
||||
case *decimal.Decimal:
|
||||
if itemValue != nil {
|
||||
return *itemValue, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
default:
|
||||
// if the other type implements valuer for the driver package
|
||||
// the converted result is used
|
||||
// otherwise the interface data is committed
|
||||
valuer, ok := itemValue.(driver.Valuer)
|
||||
if !ok {
|
||||
return itemValue, nil
|
||||
}
|
||||
convertedValue, err := valuer.Value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertedValue, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// DoCommit commits current sql and arguments to underlying sql driver.
|
||||
func (d *Driver) DoCommit(ctx context.Context, in database.DoCommitInput) (out database.DoCommitOutput, err error) {
|
||||
ctx = d.InjectIgnoreResult(ctx)
|
||||
return d.Core.DoCommit(ctx, in)
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// DoDelete does "DELETE FROM ... " statement for the table.
|
||||
func (d *Driver) DoDelete(ctx context.Context, link database.Link, table string, condition string, args ...any) (result sql.Result, err error) {
|
||||
ctx = d.injectNeedParsedSql(ctx)
|
||||
return d.Core.DoDelete(ctx, link, table, condition, args...)
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
// DoFilter handles the sql before posts it to database.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link database.Link, originSql string, args []any,
|
||||
) (newSql string, newArgs []any, err error) {
|
||||
if len(args) == 0 {
|
||||
return originSql, args, nil
|
||||
}
|
||||
// Convert placeholder char '?' to string "$x".
|
||||
var index int
|
||||
originSql, _ = gregex.ReplaceStringFunc(`\?`, originSql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(`$%d`, index)
|
||||
})
|
||||
|
||||
// Only SQL generated through the framework is processed.
|
||||
if !d.getNeedParsedSqlFromCtx(ctx) {
|
||||
return originSql, args, nil
|
||||
}
|
||||
|
||||
// replace STD SQL to Clickhouse SQL grammar
|
||||
modeRes, err := gregex.MatchString(filterTypePattern, strings.TrimSpace(originSql))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if len(modeRes) == 0 {
|
||||
return originSql, args, nil
|
||||
}
|
||||
|
||||
// Only delete/ UPDATE statements require filter
|
||||
switch strings.ToUpper(modeRes[0]) {
|
||||
case "UPDATE":
|
||||
// MySQL eg: UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]
|
||||
// Clickhouse eg: ALTER TABLE [db.]table UPDATE column1 = expr1 [, ...] WHERE filter_expr
|
||||
newSql, err = gregex.ReplaceStringFuncMatch(
|
||||
updateFilterPattern, originSql,
|
||||
func(s []string) string {
|
||||
return fmt.Sprintf("ALTER TABLE %s UPDATE", s[1])
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return newSql, args, nil
|
||||
|
||||
case "DELETE":
|
||||
// MySQL eg: DELETE FROM table_name [WHERE Clause]
|
||||
// Clickhouse eg: ALTER TABLE [db.]table [ON CLUSTER cluster] DELETE WHERE filter_expr
|
||||
newSql, err = gregex.ReplaceStringFuncMatch(
|
||||
deleteFilterPattern, originSql,
|
||||
func(s []string) string {
|
||||
return fmt.Sprintf("ALTER TABLE %s DELETE", s[1])
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return newSql, args, nil
|
||||
|
||||
default:
|
||||
return originSql, args, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) getNeedParsedSqlFromCtx(ctx context.Context) bool {
|
||||
return ctx.Value(needParsedSqlInCtx) != nil
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
// The list parameter must contain at least one record, which was previously validated.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link database.Link, table string, list database.List, option database.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
var keys, valueHolder []string
|
||||
|
||||
// Handle the field names and placeholders.
|
||||
for k := range list[0] {
|
||||
keys = append(keys, k)
|
||||
valueHolder = append(valueHolder, "?")
|
||||
}
|
||||
// Prepare the batch result pointer.
|
||||
var (
|
||||
charL, charR = d.Core.GetChars()
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
holderStr = strings.Join(valueHolder, ",")
|
||||
tx database.TX
|
||||
stmt *database.Stmt
|
||||
)
|
||||
tx, err = d.Core.Begin(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// It here uses defer to guarantee transaction be committed or roll-backed.
|
||||
defer func() {
|
||||
if err == nil {
|
||||
_ = tx.Commit()
|
||||
} else {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
}()
|
||||
stmt, err = tx.Prepare(fmt.Sprintf(
|
||||
"INSERT INTO %s(%s) VALUES (%s)",
|
||||
d.QuotePrefixTableName(table), keysStr,
|
||||
holderStr,
|
||||
))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = stmt.Close()
|
||||
}()
|
||||
|
||||
for i := range len(list) {
|
||||
// Values that will be committed to underlying database driver.
|
||||
params := make([]any, 0)
|
||||
for _, k := range keys {
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
// Prepare is allowed to execute only once in a transaction opened by clickhouse
|
||||
result, err = stmt.ExecContext(ctx, params...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// DoUpdate does "UPDATE ... " statement for the table.
|
||||
func (d *Driver) DoUpdate(ctx context.Context, link database.Link, table string, data any, condition string, args ...any) (result sql.Result, err error) {
|
||||
ctx = d.injectNeedParsedSql(ctx)
|
||||
return d.Core.DoUpdate(ctx, link, table, data, condition, args...)
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// InsertIgnore Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE.
|
||||
func (d *Driver) InsertIgnore(ctx context.Context, table string, data any, batch ...int) (sql.Result, error) {
|
||||
return nil, errUnsupportedInsertIgnore
|
||||
}
|
||||
|
||||
// InsertAndGetId Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE.
|
||||
func (d *Driver) InsertAndGetId(ctx context.Context, table string, data any, batch ...int) (int64, error) {
|
||||
return 0, errUnsupportedInsertGetId
|
||||
}
|
||||
|
||||
// Replace Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE.
|
||||
func (d *Driver) Replace(ctx context.Context, table string, data any, batch ...int) (sql.Result, error) {
|
||||
return nil, errUnsupportedReplace
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for clickhouse.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (db *sql.DB, err error) {
|
||||
var source string
|
||||
// clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60
|
||||
if config.Pass != "" {
|
||||
source = fmt.Sprintf(
|
||||
"clickhouse://%s:%s@%s:%s/%s?debug=%t",
|
||||
config.User, url.PathEscape(config.Pass),
|
||||
config.Host, config.Port, config.Name, config.Debug,
|
||||
)
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"clickhouse://%s@%s:%s/%s?debug=%t",
|
||||
config.User, config.Host, config.Port, config.Name, config.Debug,
|
||||
)
|
||||
}
|
||||
if config.Extra != "" {
|
||||
source = fmt.Sprintf("%s&%s", source, config.Extra)
|
||||
}
|
||||
if db, err = sql.Open(driverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`sql.Open failed for driver "%s" by source "%s"`, driverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
)
|
||||
|
||||
// PingMaster pings the master node to check authentication or keeps the connection alive.
|
||||
func (d *Driver) PingMaster() error {
|
||||
conn, err := d.Master()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.ping(conn)
|
||||
}
|
||||
|
||||
// PingSlave pings the slave node to check authentication or keeps the connection alive.
|
||||
func (d *Driver) PingSlave() error {
|
||||
conn, err := d.Slave()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.ping(conn)
|
||||
}
|
||||
|
||||
// ping Returns the Clickhouse specific error.
|
||||
func (d *Driver) ping(conn *sql.DB) error {
|
||||
err := conn.Ping()
|
||||
if exception, ok := err.(*clickhouse.Exception); ok {
|
||||
return fmt.Errorf("[%d]%s", exception.Code, exception.Message)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
tableFieldsColumns = `name,position,default_expression,comment,type,is_in_partition_key,is_in_sorting_key,is_in_primary_key,is_in_sampling_key`
|
||||
)
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*database.TableField, err error) {
|
||||
var (
|
||||
result database.Result
|
||||
link database.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
getColumnsSql = fmt.Sprintf(
|
||||
"select %s from `system`.columns c where `table` = '%s'",
|
||||
tableFieldsColumns, table,
|
||||
)
|
||||
)
|
||||
result, err = d.DoSelect(ctx, link, getColumnsSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*database.TableField)
|
||||
for _, m := range result {
|
||||
var (
|
||||
isNull = false
|
||||
fieldType = m["type"].String()
|
||||
)
|
||||
// in clickhouse , field type like is Nullable(int)
|
||||
fieldsResult, _ := gregex.MatchString(`^Nullable\((.*?)\)`, fieldType)
|
||||
if len(fieldsResult) == 2 {
|
||||
isNull = true
|
||||
fieldType = fieldsResult[1]
|
||||
}
|
||||
position := m["position"].Int()
|
||||
if result[0]["position"].Int() != 0 {
|
||||
position -= 1
|
||||
}
|
||||
fields[m["name"].String()] = &database.TableField{
|
||||
Index: position,
|
||||
Name: m["name"].String(),
|
||||
Default: m["default_expression"].Val(),
|
||||
Comment: m["comment"].String(),
|
||||
// Key: m["Key"].String(),
|
||||
Type: fieldType,
|
||||
Null: isNull,
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
const (
|
||||
tablesSqlTmp = "select name from `system`.tables where database = '%s'"
|
||||
)
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||
var result database.Result
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, fmt.Sprintf(tablesSqlTmp, d.GetConfig().Name))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
tables = append(tables, m["name"].String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// Begin starts and returns the transaction object.
|
||||
func (d *Driver) Begin(ctx context.Context) (tx database.TX, err error) {
|
||||
return nil, errUnsupportedBegin
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function `f`.
|
||||
func (d *Driver) Transaction(ctx context.Context, f func(ctx context.Context, tx database.TX) error) error {
|
||||
return errUnsupportedTransaction
|
||||
}
|
||||
|
|
@ -4,58 +4,45 @@
|
|||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package mssql implements database.Driver for Microsoft SQL Server.
|
||||
// Package mssql implements database.Driver, which supports operations for MSSQL.
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/microsoft/go-mssqldb"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// Driver is the driver for SQL Server database.
|
||||
// Driver is the driver for SQL server database.
|
||||
type Driver struct {
|
||||
*database.Core
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = `"`
|
||||
rowNumberAliasForSelect = `ROW_NUMBER__`
|
||||
quoteChar = `"`
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := database.Register("mssql", New()); err != nil {
|
||||
if err := database.Register(`mssql`, New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates and returns a driver that implements database.Driver for SQL Server.
|
||||
// New create and returns a driver that implements database.Driver, which supports operations for Mssql.
|
||||
func New() database.Driver {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
// New creates and returns a database object for SQL Server.
|
||||
// New creates and returns a database object for SQL server.
|
||||
// It implements the interface of database.Driver for extra database driver installation.
|
||||
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||
return &Driver{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetChars returns the security char for SQL Server.
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for SQL Server.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf("sqlserver://%s:%s@%s:%s?database=%s",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name)
|
||||
}
|
||||
return sql.Open("sqlserver", source)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// DoCommit commits current sql and arguments to underlying sql driver.
|
||||
func (d *Driver) DoCommit(ctx context.Context, in database.DoCommitInput) (out database.DoCommitOutput, err error) {
|
||||
out, err = d.Core.DoCommit(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(out.Records) > 0 {
|
||||
// remove auto added field.
|
||||
for i, record := range out.Records {
|
||||
delete(record, rowNumberAliasForSelect)
|
||||
out.Records[i] = record
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
const (
|
||||
// INSERT statement prefixes
|
||||
insertPrefixDefault = "INSERT INTO"
|
||||
insertPrefixIgnore = "INSERT IGNORE INTO"
|
||||
|
||||
// Database field attributes
|
||||
fieldExtraIdentity = "IDENTITY"
|
||||
fieldKeyPrimary = "PRI"
|
||||
|
||||
// SQL keywords and syntax markers
|
||||
outputKeyword = "OUTPUT"
|
||||
insertValuesMarker = ") VALUES" // find the position of the string "VALUES" in the INSERT SQL statement to embed output code for retrieving the last inserted ID
|
||||
|
||||
// Object and field references
|
||||
insertedObjectName = "INSERTED"
|
||||
|
||||
// Result field names and aliases
|
||||
affectCountExpression = " 1 as AffectCount"
|
||||
lastInsertIdFieldAlias = "ID"
|
||||
)
|
||||
|
||||
// DoExec commits the sql string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (d *Driver) DoExec(ctx context.Context, link database.Link, sqlStr string, args ...interface{}) (result sql.Result, err error) {
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if tx := database.TXFromCtx(ctx, d.GetGroup()); tx != nil {
|
||||
// Firstly, check and retrieve transaction link from context.
|
||||
link = &txLinkMssql{tx.GetSqlTX()}
|
||||
} else if link, err = d.MasterLink(); err != nil {
|
||||
// Or else it creates one from master node.
|
||||
return nil, err
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
// If current link is not transaction link, it checks and retrieves transaction from context.
|
||||
if tx := database.TXFromCtx(ctx, d.GetGroup()); tx != nil {
|
||||
link = &txLinkMssql{tx.GetSqlTX()}
|
||||
}
|
||||
}
|
||||
|
||||
// SQL filtering.
|
||||
sqlStr, args = d.FormatSqlBeforeExecuting(sqlStr, args)
|
||||
sqlStr, args, err = d.DoFilter(ctx, link, sqlStr, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(sqlStr, insertPrefixDefault) && !strings.HasPrefix(sqlStr, insertPrefixIgnore) {
|
||||
return d.Core.DoExec(ctx, link, sqlStr, args)
|
||||
}
|
||||
// Find the first position of VALUES marker in the INSERT statement.
|
||||
pos := strings.Index(sqlStr, insertValuesMarker)
|
||||
|
||||
table := d.GetTableNameFromSql(sqlStr)
|
||||
outPutSql := d.GetInsertOutputSql(ctx, table)
|
||||
// rebuild sql add output
|
||||
var (
|
||||
sqlValueBefore = sqlStr[:pos+1]
|
||||
sqlValueAfter = sqlStr[pos+1:]
|
||||
)
|
||||
|
||||
sqlStr = fmt.Sprintf("%s%s%s", sqlValueBefore, outPutSql, sqlValueAfter)
|
||||
|
||||
// fmt.Println("sql str:", sqlStr)
|
||||
// Link execution.
|
||||
var out database.DoCommitOutput
|
||||
out, err = d.DoCommit(ctx, database.DoCommitInput{
|
||||
Link: link,
|
||||
Sql: sqlStr,
|
||||
Args: args,
|
||||
Stmt: nil,
|
||||
Type: database.SqlTypeQueryContext,
|
||||
IsTransaction: link.IsTransaction(),
|
||||
})
|
||||
if err != nil {
|
||||
return &Result{lastInsertId: 0, rowsAffected: 0, err: err}, err
|
||||
}
|
||||
stdSqlResult := out.Records
|
||||
if len(stdSqlResult) == 0 {
|
||||
err = gerror.WrapCode(
|
||||
gcode.CodeDbOperationError,
|
||||
gerror.New("affected count is zero"),
|
||||
`sql.Result.RowsAffected failed`,
|
||||
)
|
||||
return &Result{lastInsertId: 0, rowsAffected: 0, err: err}, err
|
||||
}
|
||||
// For batch insert, OUTPUT clause returns one row per inserted row.
|
||||
// So the rowsAffected should be the count of returned records.
|
||||
rowsAffected := int64(len(stdSqlResult))
|
||||
// get last_insert_id from the first returned row
|
||||
lastInsertId := stdSqlResult[0].GMap().GetVar(lastInsertIdFieldAlias).Int64()
|
||||
|
||||
return &Result{lastInsertId: lastInsertId, rowsAffected: rowsAffected}, err
|
||||
}
|
||||
|
||||
// GetTableNameFromSql get table name from sql statement
|
||||
// It handles table string like:
|
||||
// "user"
|
||||
// "user u"
|
||||
// "DbLog.dbo.user",
|
||||
// "user as u".
|
||||
func (d *Driver) GetTableNameFromSql(sqlStr string) (table string) {
|
||||
// INSERT INTO "ip_to_id"("ip") OUTPUT 1 as AffectCount,INSERTED.id as ID VALUES(?)
|
||||
var (
|
||||
leftChars, rightChars = d.GetChars()
|
||||
trimStr = leftChars + rightChars + "[] "
|
||||
pattern = "INTO(.+?)\\("
|
||||
regCompile = regexp.MustCompile(pattern)
|
||||
tableInfo = regCompile.FindStringSubmatch(sqlStr)
|
||||
)
|
||||
// get the first one. after the first it may be content of the value, it's not table name.
|
||||
table = tableInfo[1]
|
||||
table = strings.Trim(table, " ")
|
||||
if strings.Contains(table, ".") {
|
||||
tmpAry := strings.Split(table, ".")
|
||||
// the last one is table name
|
||||
table = tmpAry[len(tmpAry)-1]
|
||||
} else if strings.Contains(table, "as") || strings.Contains(table, " ") {
|
||||
tmpAry := strings.Split(table, "as")
|
||||
if len(tmpAry) < 2 {
|
||||
tmpAry = strings.Split(table, " ")
|
||||
}
|
||||
// get the first one
|
||||
table = tmpAry[0]
|
||||
}
|
||||
table = strings.Trim(table, trimStr)
|
||||
return table
|
||||
}
|
||||
|
||||
// txLink is used to implement interface Link for TX.
|
||||
type txLinkMssql struct {
|
||||
*sql.Tx
|
||||
}
|
||||
|
||||
// IsTransaction returns if current Link is a transaction.
|
||||
func (l *txLinkMssql) IsTransaction() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsOnMaster checks and returns whether current link is operated on master node.
|
||||
// Note that, transaction operation is always operated on master node.
|
||||
func (l *txLinkMssql) IsOnMaster() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetInsertOutputSql gen get last_insert_id code
|
||||
func (d *Driver) GetInsertOutputSql(ctx context.Context, table string) string {
|
||||
fds, errFd := d.GetDB().TableFields(ctx, table)
|
||||
if errFd != nil {
|
||||
return ""
|
||||
}
|
||||
extraSqlAry := make([]string, 0)
|
||||
extraSqlAry = append(extraSqlAry, fmt.Sprintf(" %s %s", outputKeyword, affectCountExpression))
|
||||
incrNo := 0
|
||||
if len(fds) > 0 {
|
||||
for _, fd := range fds {
|
||||
// has primary key and is auto-increment
|
||||
if fd.Extra == fieldExtraIdentity && fd.Key == fieldKeyPrimary && !fd.Null {
|
||||
incrNoStr := ""
|
||||
if incrNo == 0 { // fixed first field named id, convenient to get
|
||||
incrNoStr = fmt.Sprintf(" as %s", lastInsertIdFieldAlias)
|
||||
}
|
||||
|
||||
extraSqlAry = append(extraSqlAry, fmt.Sprintf("%s.%s%s", insertedObjectName, fd.Name, incrNoStr))
|
||||
incrNo++
|
||||
}
|
||||
// fmt.Printf("null:%t name:%s key:%s k:%s \n", fd.Null, fd.Name, fd.Key, k)
|
||||
}
|
||||
}
|
||||
return strings.Join(extraSqlAry, ",")
|
||||
// sql example:INSERT INTO "ip_to_id"("ip") OUTPUT 1 as AffectCount,INSERTED.id as ID VALUES(?)
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
var (
|
||||
selectWithOrderSqlTmp = `
|
||||
SELECT * FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROW_NUMBER__, %s
|
||||
FROM (%s) as InnerQuery
|
||||
) as TMP_
|
||||
WHERE TMP_.ROW_NUMBER__ > %d AND TMP_.ROW_NUMBER__ <= %d`
|
||||
selectWithoutOrderSqlTmp = `
|
||||
SELECT * FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as ROW_NUMBER__, %s
|
||||
FROM (%s) as InnerQuery
|
||||
) as TMP_
|
||||
WHERE TMP_.ROW_NUMBER__ > %d AND TMP_.ROW_NUMBER__ <= %d`
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
selectWithOrderSqlTmp, err = database.FormatMultiLineSqlToSingle(selectWithOrderSqlTmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
selectWithoutOrderSqlTmp, err = database.FormatMultiLineSqlToSingle(selectWithoutOrderSqlTmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link database.Link, sql string, args []any,
|
||||
) (newSql string, newArgs []any, err error) {
|
||||
var index int
|
||||
// Convert placeholder char '?' to string "@px".
|
||||
newSql, err = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
newSql, err = gregex.ReplaceString("\"", "", newSql)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
newSql, err = d.parseSql(newSql)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
newArgs = args
|
||||
return d.Core.DoFilter(ctx, link, newSql, newArgs)
|
||||
}
|
||||
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
// for support of microsoft sql server.
|
||||
func (d *Driver) parseSql(toBeCommittedSql string) (string, error) {
|
||||
var (
|
||||
err error
|
||||
operation = gstr.StrTillEx(toBeCommittedSql, " ")
|
||||
keyword = strings.ToUpper(gstr.Trim(operation))
|
||||
)
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
toBeCommittedSql, err = d.handleSelectSqlReplacement(toBeCommittedSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
|
||||
func (d *Driver) handleSelectSqlReplacement(toBeCommittedSql string) (newSql string, err error) {
|
||||
// SELECT * FROM USER WHERE ID=1 LIMIT 1
|
||||
match, err := gregex.MatchString(`^SELECT(.+?)LIMIT\s+1$`, toBeCommittedSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(match) > 1 {
|
||||
return fmt.Sprintf(`SELECT TOP 1 %s`, strings.TrimSpace(match[1])), nil
|
||||
}
|
||||
|
||||
// SELECT * FROM USER WHERE AGE>18 ORDER BY ID DESC LIMIT 100, 200
|
||||
pattern := `(?i)SELECT(.+?)(ORDER BY.+?)?\s*LIMIT\s*(\d+)(?:\s*,\s*(\d+))?`
|
||||
if !gregex.IsMatchString(pattern, toBeCommittedSql) {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
|
||||
allMatch, err := gregex.MatchString(pattern, toBeCommittedSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract SELECT part
|
||||
selectStr := strings.TrimSpace(allMatch[1])
|
||||
|
||||
// Extract ORDER BY part
|
||||
orderStr := ""
|
||||
if len(allMatch[2]) > 0 {
|
||||
orderStr = strings.TrimSpace(allMatch[2])
|
||||
// Remove "ORDER BY" prefix as it will be used in OVER clause
|
||||
orderStr = strings.TrimPrefix(orderStr, "ORDER BY")
|
||||
orderStr = strings.TrimSpace(orderStr)
|
||||
}
|
||||
|
||||
// Calculate LIMIT and OFFSET values
|
||||
first, _ := strconv.Atoi(allMatch[3]) // LIMIT first parameter
|
||||
limit := 0
|
||||
if len(allMatch) > 4 && allMatch[4] != "" {
|
||||
limit, _ = strconv.Atoi(allMatch[4]) // LIMIT second parameter
|
||||
} else {
|
||||
limit = first
|
||||
first = 0
|
||||
}
|
||||
|
||||
// Build the final query
|
||||
if orderStr != "" {
|
||||
// Have ORDER BY clause
|
||||
newSql = fmt.Sprintf(
|
||||
selectWithOrderSqlTmp,
|
||||
orderStr, // ORDER BY clause for ROW_NUMBER
|
||||
"*", // Select all columns
|
||||
fmt.Sprintf("SELECT %s", selectStr), // Original SELECT
|
||||
first, // OFFSET
|
||||
first+limit, // OFFSET + LIMIT
|
||||
)
|
||||
} else {
|
||||
// Without ORDER BY clause
|
||||
newSql = fmt.Sprintf(
|
||||
selectWithoutOrderSqlTmp,
|
||||
"*", // Select all columns
|
||||
fmt.Sprintf("SELECT %s", selectStr), // Original SELECT
|
||||
first, // OFFSET
|
||||
first+limit, // OFFSET + LIMIT
|
||||
)
|
||||
}
|
||||
return newSql, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func TestDriver_DoFilter(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
d := &Driver{}
|
||||
|
||||
// Test SELECT with LIMIT
|
||||
sql := "SELECT * FROM users WHERE id = ? LIMIT 10"
|
||||
args := []any{1}
|
||||
newSql, newArgs, err := d.DoFilter(context.Background(), nil, sql, args)
|
||||
t.AssertNil(err)
|
||||
t.Assert(newArgs, args)
|
||||
// DoFilter should transform the SQL for MSSQL compatibility
|
||||
t.AssertNE(newSql, "")
|
||||
|
||||
// Test INSERT statement (should remain unchanged except for placeholder)
|
||||
sql = "INSERT INTO users (name) VALUES (?)"
|
||||
args = []any{"test"}
|
||||
newSql, newArgs, err = d.DoFilter(context.Background(), nil, sql, args)
|
||||
t.AssertNil(err)
|
||||
t.Assert(newArgs, args)
|
||||
t.AssertNE(newSql, "")
|
||||
|
||||
// Test UPDATE statement
|
||||
sql = "UPDATE users SET name = ? WHERE id = ?"
|
||||
args = []any{"test", 1}
|
||||
newSql, newArgs, err = d.DoFilter(context.Background(), nil, sql, args)
|
||||
t.AssertNil(err)
|
||||
t.Assert(newArgs, args)
|
||||
t.AssertNE(newSql, "")
|
||||
|
||||
// Test DELETE statement
|
||||
sql = "DELETE FROM users WHERE id = ?"
|
||||
args = []any{1}
|
||||
newSql, newArgs, err = d.DoFilter(context.Background(), nil, sql, args)
|
||||
t.AssertNil(err)
|
||||
t.Assert(newArgs, args)
|
||||
t.AssertNE(newSql, "")
|
||||
})
|
||||
}
|
||||
|
||||
func TestDriver_handleSelectSqlReplacement(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
d := &Driver{}
|
||||
|
||||
// LIMIT 1
|
||||
inputSql := "SELECT * FROM User WHERE ID = 1 LIMIT 1"
|
||||
expectedSql := "SELECT TOP 1 * FROM User WHERE ID = 1"
|
||||
resultSql, err := d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// LIMIT query with offset and number of rows
|
||||
inputSql = "SELECT * FROM User ORDER BY ID DESC LIMIT 100, 200"
|
||||
expectedSql = "SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY ID DESC) as ROW_NUMBER__, * FROM (SELECT * FROM User) as InnerQuery ) as TMP_ WHERE TMP_.ROW_NUMBER__ > 100 AND TMP_.ROW_NUMBER__ <= 300"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// Simple query with no LIMIT
|
||||
inputSql = "SELECT * FROM User WHERE age > 18"
|
||||
expectedSql = "SELECT * FROM User WHERE age > 18"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// without LIMIT
|
||||
inputSql = "SELECT * FROM User ORDER BY ID DESC"
|
||||
expectedSql = "SELECT * FROM User ORDER BY ID DESC"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// LIMIT query with only rows
|
||||
inputSql = "SELECT * FROM User LIMIT 50"
|
||||
expectedSql = "SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as ROW_NUMBER__, * FROM (SELECT * FROM User) as InnerQuery ) as TMP_ WHERE TMP_.ROW_NUMBER__ > 0 AND TMP_.ROW_NUMBER__ <= 50"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// LIMIT query without ORDER BY
|
||||
inputSql = "SELECT * FROM User LIMIT 30"
|
||||
expectedSql = "SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as ROW_NUMBER__, * FROM (SELECT * FROM User) as InnerQuery ) as TMP_ WHERE TMP_.ROW_NUMBER__ > 0 AND TMP_.ROW_NUMBER__ <= 30"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// Complex query with ORDER BY and LIMIT
|
||||
inputSql = "SELECT name, age FROM User WHERE age > 18 ORDER BY age ASC LIMIT 10, 5"
|
||||
expectedSql = "SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY age ASC) as ROW_NUMBER__, * FROM (SELECT name, age FROM User WHERE age > 18) as InnerQuery ) as TMP_ WHERE TMP_.ROW_NUMBER__ > 10 AND TMP_.ROW_NUMBER__ <= 15"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// Complex conditional queries have limits
|
||||
inputSql = "SELECT * FROM User WHERE age > 18 AND status = 'active' LIMIT 100, 50"
|
||||
expectedSql = "SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as ROW_NUMBER__, * FROM (SELECT * FROM User WHERE age > 18 AND status = 'active') as InnerQuery ) as TMP_ WHERE TMP_.ROW_NUMBER__ > 100 AND TMP_.ROW_NUMBER__ <= 150"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// A LIMIT query that contains subquery
|
||||
inputSql = "SELECT * FROM (SELECT * FROM User WHERE age > 18) AS subquery LIMIT 10"
|
||||
expectedSql = "SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as ROW_NUMBER__, * FROM (SELECT * FROM (SELECT * FROM User WHERE age > 18) AS subquery) as InnerQuery ) as TMP_ WHERE TMP_.ROW_NUMBER__ > 0 AND TMP_.ROW_NUMBER__ <= 10"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
// Queries with complex ORDER BY and LIMIT
|
||||
inputSql = "SELECT name, age FROM User WHERE age > 18 ORDER BY age DESC, name ASC LIMIT 20, 10"
|
||||
expectedSql = "SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY age DESC, name ASC) as ROW_NUMBER__, * FROM (SELECT name, age FROM User WHERE age > 18) as InnerQuery ) as TMP_ WHERE TMP_.ROW_NUMBER__ > 20 AND TMP_.ROW_NUMBER__ <= 30"
|
||||
resultSql, err = d.handleSelectSqlReplacement(inputSql)
|
||||
t.AssertNil(err)
|
||||
t.Assert(resultSql, expectedSql)
|
||||
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
// The list parameter must contain at least one record, which was previously validated.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link database.Link, table string, list database.List, option database.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case database.InsertOptionSave:
|
||||
return d.doSave(ctx, link, table, list, option)
|
||||
|
||||
case database.InsertOptionReplace:
|
||||
// MSSQL does not support REPLACE INTO syntax, use SAVE instead.
|
||||
return d.doSave(ctx, link, table, list, option)
|
||||
|
||||
case database.InsertOptionIgnore:
|
||||
// MSSQL does not support INSERT IGNORE syntax, use MERGE instead.
|
||||
return d.doInsertIgnore(ctx, link, table, list, option)
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
||||
|
||||
// doSave support upsert for MSSQL
|
||||
func (d *Driver) doSave(ctx context.Context,
|
||||
link database.Link, table string, list database.List, option database.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
return d.doMergeInsert(ctx, link, table, list, option, true)
|
||||
}
|
||||
|
||||
// doInsertIgnore implements INSERT IGNORE operation using MERGE statement for MSSQL database.
|
||||
// It only inserts records when there's no conflict on primary/unique keys.
|
||||
func (d *Driver) doInsertIgnore(ctx context.Context,
|
||||
link database.Link, table string, list database.List, option database.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
return d.doMergeInsert(ctx, link, table, list, option, false)
|
||||
}
|
||||
|
||||
// doMergeInsert implements MERGE-based insert operations for MSSQL database.
|
||||
// When withUpdate is true, it performs upsert (insert or update).
|
||||
// When withUpdate is false, it performs insert ignore (insert only when no conflict).
|
||||
func (d *Driver) doMergeInsert(
|
||||
ctx context.Context,
|
||||
link database.Link, table string, list database.List, option database.DoInsertOption, withUpdate bool,
|
||||
) (result sql.Result, err error) {
|
||||
// If OnConflict is not specified, automatically get the primary key of the table
|
||||
conflictKeys := option.OnConflict
|
||||
if len(conflictKeys) == 0 {
|
||||
primaryKeys, err := d.Core.GetPrimaryKeys(ctx, table)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(
|
||||
gcode.CodeInternalError,
|
||||
err,
|
||||
`failed to get primary keys for table`,
|
||||
)
|
||||
}
|
||||
foundPrimaryKey := false
|
||||
for _, primaryKey := range primaryKeys {
|
||||
for dataKey := range list[0] {
|
||||
if strings.EqualFold(dataKey, primaryKey) {
|
||||
foundPrimaryKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundPrimaryKey {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundPrimaryKey {
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeMissingParameter,
|
||||
`Replace/Save/InsertIgnore operation requires conflict detection: `+
|
||||
`either specify OnConflict() columns or ensure table '%s' has a primary key in the data`,
|
||||
table,
|
||||
)
|
||||
}
|
||||
// TODO consider composite primary keys.
|
||||
conflictKeys = primaryKeys
|
||||
}
|
||||
|
||||
var (
|
||||
one = list[0]
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
conflictKeySet = gset.NewStrSet(false)
|
||||
|
||||
// queryHolders: Handle data with Holder that need to be merged
|
||||
// queryValues: Handle data that need to be merged
|
||||
// insertKeys: Handle valid keys that need to be inserted
|
||||
// insertValues: Handle values that need to be inserted
|
||||
// updateValues: Handle values that need to be updated (only when withUpdate=true)
|
||||
queryHolders = make([]string, oneLen)
|
||||
queryValues = make([]any, oneLen)
|
||||
insertKeys = make([]string, oneLen)
|
||||
insertValues = make([]string, oneLen)
|
||||
updateValues []string
|
||||
)
|
||||
|
||||
// conflictKeys slice type conv to set type
|
||||
for _, conflictKey := range conflictKeys {
|
||||
conflictKeySet.Add(gstr.ToUpper(conflictKey))
|
||||
}
|
||||
|
||||
index := 0
|
||||
for key, value := range one {
|
||||
queryHolders[index] = "?"
|
||||
queryValues[index] = value
|
||||
insertKeys[index] = charL + key + charR
|
||||
insertValues[index] = "T2." + charL + key + charR
|
||||
|
||||
// Build updateValues only when withUpdate is true
|
||||
// Filter conflict keys and soft created fields from updateValues
|
||||
if withUpdate && !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
|
||||
updateValues = append(
|
||||
updateValues,
|
||||
fmt.Sprintf(`T1.%s = T2.%s`, charL+key+charR, charL+key+charR),
|
||||
)
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
var (
|
||||
batchResult = new(database.SqlResult)
|
||||
sqlStr = parseSqlForMerge(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
|
||||
)
|
||||
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.Result = r
|
||||
batchResult.Affected += n
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
// parseSqlForMerge generates MERGE statement for MSSQL database.
|
||||
// When updateValues is empty, it only inserts (INSERT IGNORE behavior).
|
||||
// When updateValues is provided, it performs upsert (INSERT or UPDATE).
|
||||
// Examples:
|
||||
// - INSERT IGNORE: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...)
|
||||
// - UPSERT: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...) WHEN MATCHED THEN UPDATE SET ...
|
||||
func parseSqlForMerge(table string,
|
||||
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
|
||||
) (sqlStr string) {
|
||||
var (
|
||||
queryHolderStr = strings.Join(queryHolders, ",")
|
||||
insertKeyStr = strings.Join(insertKeys, ",")
|
||||
insertValueStr = strings.Join(insertValues, ",")
|
||||
duplicateKeyStr string
|
||||
)
|
||||
|
||||
// Build ON condition
|
||||
for index, keys := range duplicateKey {
|
||||
if index != 0 {
|
||||
duplicateKeyStr += " AND "
|
||||
}
|
||||
duplicateKeyStr += fmt.Sprintf("T1.%s = T2.%s", keys, keys)
|
||||
}
|
||||
|
||||
// Build SQL based on whether UPDATE is needed
|
||||
pattern := gstr.Trim(
|
||||
`MERGE INTO %s T1 USING (VALUES(%s)) T2 (%s) ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s)`,
|
||||
)
|
||||
if len(updateValues) > 0 {
|
||||
// Upsert: INSERT or UPDATE
|
||||
pattern += gstr.Trim(` WHEN MATCHED THEN UPDATE SET %s`)
|
||||
return fmt.Sprintf(
|
||||
pattern+";",
|
||||
table,
|
||||
queryHolderStr,
|
||||
insertKeyStr,
|
||||
duplicateKeyStr,
|
||||
insertKeyStr,
|
||||
insertValueStr,
|
||||
strings.Join(updateValues, ","),
|
||||
)
|
||||
}
|
||||
// Insert Ignore: INSERT only
|
||||
return fmt.Sprintf(pattern+";", table, queryHolderStr, insertKeyStr, duplicateKeyStr, insertKeyStr, insertValueStr)
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for mssql.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (db *sql.DB, err error) {
|
||||
source, err := configNodeToSource(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
underlyingDriverName := "sqlserver"
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`sql.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func configNodeToSource(config *database.ConfigNode) (string, error) {
|
||||
var source string
|
||||
source = fmt.Sprintf(
|
||||
"user id=%s;password=%s;server=%s;encrypt=disable",
|
||||
config.User, config.Pass, config.Host,
|
||||
)
|
||||
if config.Name != "" {
|
||||
source = fmt.Sprintf("%s;database=%s", source, config.Name)
|
||||
}
|
||||
if config.Port != "" {
|
||||
source = fmt.Sprintf("%s;port=%s", source, config.Port)
|
||||
}
|
||||
if config.Extra != "" {
|
||||
extraMap, err := gstr.Parse(config.Extra)
|
||||
if err != nil {
|
||||
return "", gerror.WrapCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
err,
|
||||
`invalid extra configuration: %s`, config.Extra,
|
||||
)
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
source += fmt.Sprintf(`;%s=%s`, k, v)
|
||||
}
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
// Result instance of sql.Result
|
||||
type Result struct {
|
||||
lastInsertId int64
|
||||
rowsAffected int64
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *Result) LastInsertId() (int64, error) {
|
||||
return r.lastInsertId, r.err
|
||||
}
|
||||
|
||||
func (r *Result) RowsAffected() (int64, error) {
|
||||
return r.rowsAffected, r.err
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
tableFieldsSqlTmp = `
|
||||
SELECT
|
||||
c.name AS Field,
|
||||
CASE
|
||||
WHEN t.name IN ('datetime', 'datetime2', 'smalldatetime', 'date', 'time', 'text', 'ntext', 'image', 'xml') THEN t.name
|
||||
WHEN t.name IN ('decimal', 'numeric') THEN t.name + '(' + CAST(c.precision AS varchar(20)) + ',' + CAST(c.scale AS varchar(20)) + ')'
|
||||
WHEN t.name IN ('char', 'varchar', 'binary', 'varbinary') THEN t.name + '(' + CASE WHEN c.max_length = -1 THEN 'max' ELSE CAST(c.max_length AS varchar(20)) END + ')'
|
||||
WHEN t.name IN ('nchar', 'nvarchar') THEN t.name + '(' + CASE WHEN c.max_length = -1 THEN 'max' ELSE CAST(c.max_length/2 AS varchar(20)) END + ')'
|
||||
ELSE t.name
|
||||
END AS Type,
|
||||
CASE WHEN c.is_nullable = 1 THEN 'YES' ELSE 'NO' END AS [Null],
|
||||
CASE WHEN pk.column_id IS NOT NULL THEN 'PRI' ELSE '' END AS [Key],
|
||||
CASE WHEN c.is_identity = 1 THEN 'IDENTITY' ELSE '' END AS Extra,
|
||||
ISNULL(dc.definition, '') AS [Default],
|
||||
ISNULL(CAST(ep.value AS nvarchar(max)), '') AS [Comment]
|
||||
FROM sys.columns c
|
||||
INNER JOIN sys.objects o ON c.object_id = o.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
|
||||
INNER JOIN sys.types t ON c.user_type_id = t.user_type_id
|
||||
LEFT JOIN sys.default_constraints dc ON c.default_object_id = dc.object_id
|
||||
LEFT JOIN sys.extended_properties ep ON c.object_id = ep.major_id AND c.column_id = ep.minor_id AND ep.name = 'MS_Description'
|
||||
LEFT JOIN (
|
||||
SELECT ic.object_id, ic.column_id
|
||||
FROM sys.index_columns ic
|
||||
INNER JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
||||
WHERE i.is_primary_key = 1
|
||||
) pk ON c.object_id = pk.object_id AND c.column_id = pk.column_id
|
||||
WHERE o.name = '%s'
|
||||
ORDER BY c.column_id
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
tableFieldsSqlTmp, err = database.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*database.TableField, err error) {
|
||||
var (
|
||||
result database.Result
|
||||
link database.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
structureSql := fmt.Sprintf(tableFieldsSqlTmp, table)
|
||||
result, err = d.DoSelect(ctx, link, structureSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*database.TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &database.TableField{
|
||||
Index: i,
|
||||
Name: m["Field"].String(),
|
||||
Type: m["Type"].String(),
|
||||
Null: m["Null"].Bool(),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
Extra: m["Extra"].String(),
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
const (
|
||||
tablesSqlTmp = `SELECT name FROM sys.objects WHERE type='U' AND is_ms_shipped = 0 ORDER BY name`
|
||||
)
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||
var result database.Result
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = d.DoSelect(ctx, link, tablesSqlTmp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -4,20 +4,16 @@
|
|||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package mysql implements database.Driver for MySQL database.
|
||||
// Package mysql implements database.Driver, which supports operations for database MySQL.
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"git.magicany.cc/black1552/gin-base/database/consts"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
// Driver is the driver for MySQL database.
|
||||
// Driver is the driver for mysql database.
|
||||
type Driver struct {
|
||||
*database.Core
|
||||
}
|
||||
|
|
@ -28,24 +24,23 @@ const (
|
|||
|
||||
func init() {
|
||||
var (
|
||||
err error
|
||||
driverObj = New()
|
||||
err error
|
||||
driverObj = New()
|
||||
driverNames = consts.SliceStr{"mysql", "mariadb", "tidb"} // TODO remove mariadb and tidb in future versions.
|
||||
)
|
||||
// Register for both mysql and mariadb (compatible protocol)
|
||||
if err = database.Register("mysql", driverObj); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = database.Register("mariadb", driverObj); err != nil {
|
||||
panic(err)
|
||||
for _, driverName := range driverNames {
|
||||
if err = database.Register(driverName, driverObj); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New creates and returns a driver that implements database.Driver for MySQL.
|
||||
// New create and returns a driver that implements database.Driver, which supports operations for MySQL.
|
||||
func New() database.Driver {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
// New creates and returns a database object for MySQL.
|
||||
// New creates and returns a database object for mysql.
|
||||
// It implements the interface of database.Driver for extra database driver installation.
|
||||
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||
return &Driver{
|
||||
|
|
@ -53,31 +48,7 @@ func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.D
|
|||
}, nil
|
||||
}
|
||||
|
||||
// GetChars returns the security char for MySQL database.
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for MySQL.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||
var (
|
||||
source string
|
||||
username = config.User
|
||||
password = config.Pass
|
||||
protocol = "tcp"
|
||||
dbName = config.Name
|
||||
params = make([]string, 0)
|
||||
)
|
||||
|
||||
if config.Extra != "" {
|
||||
params = append(params, config.Extra)
|
||||
}
|
||||
|
||||
// Default params
|
||||
params = append(params, "loc=Local", "parseTime=true")
|
||||
|
||||
source = fmt.Sprintf("%s:%s@%s(%s:%s)/%s?%s",
|
||||
username, password, protocol, config.Host, config.Port, dbName, strings.Join(params, "&"))
|
||||
|
||||
return sql.Open("mysql", source)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// DoFilter handles the sql before posts it to database.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link database.Link, sql string, args []any,
|
||||
) (newSql string, newArgs []any, err error) {
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for mysql.
|
||||
// Note that it converts time.Time argument to local timezone in default.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source = configNodeToSource(config)
|
||||
underlyingDriverName = "mysql"
|
||||
)
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`sql.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||
func configNodeToSource(config *database.ConfigNode) string {
|
||||
var (
|
||||
source string
|
||||
portStr string
|
||||
)
|
||||
if config.Port != "" {
|
||||
portStr = ":" + config.Port
|
||||
}
|
||||
source = fmt.Sprintf(
|
||||
"%s:%s@%s(%s%s)/%s?charset=%s",
|
||||
config.User, config.Pass, config.Protocol, config.Host, portStr, config.Name, config.Charset,
|
||||
)
|
||||
if config.Timezone != "" {
|
||||
if strings.Contains(config.Timezone, "/") {
|
||||
config.Timezone = url.QueryEscape(config.Timezone)
|
||||
}
|
||||
source = fmt.Sprintf("%s&loc=%s", source, config.Timezone)
|
||||
}
|
||||
if config.Extra != "" {
|
||||
source = fmt.Sprintf("%s&%s", source, config.Extra)
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
// tableFieldsSqlByMariadb is the query statement for retrieving table fields' information in MariaDB.
|
||||
// Deprecated: Use package `contrib/drivers/mariadb` instead.
|
||||
// TODO remove in next version.
|
||||
tableFieldsSqlByMariadb = `
|
||||
SELECT
|
||||
c.COLUMN_NAME AS 'Field',
|
||||
( CASE WHEN ch.CHECK_CLAUSE LIKE 'json_valid%%' THEN 'json' ELSE c.COLUMN_TYPE END ) AS 'Type',
|
||||
c.COLLATION_NAME AS 'Collation',
|
||||
c.IS_NULLABLE AS 'Null',
|
||||
c.COLUMN_KEY AS 'Key',
|
||||
( CASE WHEN c.COLUMN_DEFAULT = 'NULL' OR c.COLUMN_DEFAULT IS NULL THEN NULL ELSE c.COLUMN_DEFAULT END) AS 'Default',
|
||||
c.EXTRA AS 'Extra',
|
||||
c.PRIVILEGES AS 'Privileges',
|
||||
c.COLUMN_COMMENT AS 'Comment'
|
||||
FROM
|
||||
information_schema.COLUMNS AS c
|
||||
LEFT JOIN information_schema.CHECK_CONSTRAINTS AS ch ON c.TABLE_NAME = ch.TABLE_NAME
|
||||
AND c.TABLE_SCHEMA = ch.CONSTRAINT_SCHEMA
|
||||
AND c.COLUMN_NAME = ch.CONSTRAINT_NAME
|
||||
WHERE
|
||||
c.TABLE_SCHEMA = '%s'
|
||||
AND c.TABLE_NAME = '%s'
|
||||
ORDER BY c.ORDINAL_POSITION`
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
tableFieldsSqlByMariadb, err = database.FormatMultiLineSqlToSingle(tableFieldsSqlByMariadb)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current
|
||||
// schema.
|
||||
//
|
||||
// The parameter `link` is optional, if given nil it automatically retrieves a raw sql connection
|
||||
// as its link to proceed necessary sql query.
|
||||
//
|
||||
// Note that it returns a map containing the field name and its corresponding fields.
|
||||
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in
|
||||
// the fields.
|
||||
//
|
||||
// It's using cache feature to enhance the performance, which is never expired util the
|
||||
// process restarts.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*database.TableField, err error) {
|
||||
var (
|
||||
result database.Result
|
||||
link database.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
tableFieldsSql string
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbType := d.GetConfig().Type
|
||||
switch dbType {
|
||||
// Deprecated: Use package `contrib/drivers/mariadb` instead.
|
||||
// TODO remove in next version.
|
||||
case "mariadb":
|
||||
tableFieldsSql = fmt.Sprintf(tableFieldsSqlByMariadb, usedSchema, table)
|
||||
default:
|
||||
tableFieldsSql = fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table))
|
||||
}
|
||||
|
||||
result, err = d.DoSelect(
|
||||
ctx, link,
|
||||
tableFieldsSql,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*database.TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &database.TableField{
|
||||
Index: i,
|
||||
Name: m["Field"].String(),
|
||||
Type: m["Type"].String(),
|
||||
Null: strings.EqualFold(m["Null"].String(), "YES"),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
Extra: m["Extra"].String(),
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||
var result database.Result
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, `SHOW TABLES`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE `date_time_example` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`year` year DEFAULT NULL COMMENT 'year',
|
||||
`date` date DEFAULT NULL COMMENT 'Date',
|
||||
`time` time DEFAULT NULL COMMENT 'time',
|
||||
`datetime` datetime DEFAULT NULL COMMENT 'datetime',
|
||||
`timestamp` timestamp NULL DEFAULT NULL COMMENT 'Timestamp',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
|
||||
|
||||
DROP TABLE IF EXISTS `common_resource`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `common_resource` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`app_id` bigint(20) NOT NULL,
|
||||
`resource_id` varchar(64) NOT NULL,
|
||||
`src_instance_id` varchar(64) DEFAULT NULL,
|
||||
`region` varchar(36) DEFAULT NULL,
|
||||
`zone` varchar(36) DEFAULT NULL,
|
||||
`database_kind` varchar(20) NOT NULL,
|
||||
`source_type` varchar(64) NOT NULL,
|
||||
`ip` varchar(64) DEFAULT NULL,
|
||||
`port` int(10) DEFAULT NULL,
|
||||
`vpc_id` varchar(20) DEFAULT NULL,
|
||||
`subnet_id` varchar(20) DEFAULT NULL,
|
||||
`proxy_ip` varchar(64) DEFAULT NULL,
|
||||
`proxy_port` int(10) DEFAULT NULL,
|
||||
`proxy_id` bigint(20) DEFAULT NULL,
|
||||
`proxy_snat_ip` varchar(64) DEFAULT NULL,
|
||||
`lease_at` timestamp NULL DEFAULT NULL,
|
||||
`uin` varchar(32) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_resource` (`app_id`,`src_instance_id`,`vpc_id`,`subnet_id`,`ip`,`port`),
|
||||
KEY `resource_id` (`resource_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COMMENT='资源公共信息表';
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `common_resource`
|
||||
--
|
||||
|
||||
LOCK TABLES `common_resource` WRITE;
|
||||
/*!40000 ALTER TABLE `common_resource` DISABLE KEYS */;
|
||||
INSERT INTO `common_resource` VALUES (1,1,'2','2','2','3','1','1','1',1,'1','1','1',1,1,'1',NULL,''),(3,2,'3','3','3','3','3','3','3',3,'3','3','3',3,3,'3',NULL,''),(18,1303697168,'dmc-rgnh9qre','vdb-6b6m3u1u','ap-guangzhou','','vdb','cloud','10.0.1.16',80,'vpc-m3dchft7','subnet-9as3a3z2','9.27.72.189',11131,228476,'169.254.128.5, ','2023-11-08 08:13:04',''),(20,1303697168,'dmc-4grzi4jg','tdsqlshard-313spncx','ap-guangzhou','','tdsql','cloud','10.255.0.27',3306,'vpc-407k0e8x','subnet-qhkkk3bo','30.86.239.200',24087,0,'',NULL,'');
|
||||
/*!40000 ALTER TABLE `common_resource` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `managed_resource`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `managed_resource`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `managed_resource` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`instance_id` varchar(64) NOT NULL,
|
||||
`resource_id` varchar(64) NOT NULL,
|
||||
`resource_name` varchar(64) DEFAULT NULL,
|
||||
`status` varchar(36) NOT NULL DEFAULT 'valid',
|
||||
`status_message` varchar(64) DEFAULT NULL,
|
||||
`user` varchar(64) NOT NULL,
|
||||
`password` varchar(1024) NOT NULL,
|
||||
`pay_mode` tinyint(1) DEFAULT '0',
|
||||
`safe_publication` bit(1) DEFAULT b'0',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`expired_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`resource_mark_id` int(11) DEFAULT NULL,
|
||||
`comments` varchar(64) DEFAULT NULL,
|
||||
`rule_template_id` varchar(64) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `resource_id` (`resource_id`),
|
||||
KEY `instance_id` (`instance_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COMMENT='管控实例表';
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `managed_resource`
|
||||
--
|
||||
|
||||
LOCK TABLES `managed_resource` WRITE;
|
||||
/*!40000 ALTER TABLE `managed_resource` DISABLE KEYS */;
|
||||
INSERT INTO `managed_resource` VALUES (1,'2','3','1','1','1','1','1',1,_binary '','2023-11-06 12:14:21','2023-11-06 12:14:21',NULL,1,1,'1',''),(2,'3','2','1','1','1','1','1',1,_binary '\0','2023-11-06 12:15:07','2023-11-06 12:15:07',NULL,1,2,'1',''),(5,'dmcins-jxy0x75m','dmc-rgnh9qre','erichmao-vdb-test','invalid','The Ip field is required','root','2e39af3dd1d447e2b1437b40c62c35995fa22b370c7455ff7815dace3a6e8891ccadcfc893fe1342a4102d742bd7a3e603cd0ac1fcdc072d7c0b5be5836ec87306981b629f9b59aedf0316e9504ab172fa1c95756d5b260114e4feaa0b19223fb61cb268cc4818307ed193dbab830cf556b91cde182686eb70f70ea77f69eff66230dec2ce92bd3352cad31abf47597a5cc6a0d638381dc3bae7aa1b142730790a6d4cefdef1bd460061c966ad5008c2b5fc971b7f4d7dddffa5b1456c45e2917763dd8fffb1fa7fc4783feca95dafc9a9f4edf21b0579f76b0a3154f087e3b9a7fc49af8ff92b12e7b03caa865e72e777dd9d35a11910df0d55ead90e47d5f8',1,_binary '','2023-11-08 08:13:20','2023-11-09 05:31:07',NULL,0,11,NULL,'12345'),(6,'dmcins-erxms6ya','dmc-4grzi4jg','erichmao-vdb-test','invalid','The Ip field is required','leotaowang','641d846cf75bc7944202251d97dca8335f7f149dd4fd911ca5b87c71ef1dc5d0a66c4e5021ef7ad53136cda2fb2567d34e3dd1a7666e3f64ebf532eb2a55d84952aac86b4211f563f7b9da7dd0f88ec288d6680d3513cea0c1b7ad7babb474717f77ebbc9d63bb458adaf982887da9e63df957ffda572c1c3ed187471b99fdc640b45fed76a6d50dc1090eee79b4d94d056c4d43416133481f55bd040759398680104a84d801e6475dcfe919a00859908296747430b728a00c8d54256ae220235a138e0bbf08fe8b6fc8589971436b55bff966154721a91adbdc9c2b6f50ef5849ed77e5b028116abac51584b8d401cd3a88d18df127006358ed33fc3fa6f480',1,_binary '','2023-11-08 22:15:17','2023-11-09 05:31:07',NULL,0,11,NULL,'12345');
|
||||
/*!40000 ALTER TABLE `managed_resource` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `rules_template`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `rules_template`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `rules_template` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`app_id` bigint(20) DEFAULT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`database_kind` varchar(64) DEFAULT NULL,
|
||||
`is_default` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`win_rules` varchar(2048) DEFAULT NULL,
|
||||
`inception_rules` varchar(2048) DEFAULT NULL,
|
||||
`auto_exec_rules` varchar(2048) DEFAULT NULL,
|
||||
`order_check_step` varchar(2048) DEFAULT NULL,
|
||||
`template_id` varchar(64) NOT NULL DEFAULT '',
|
||||
`version` int(11) NOT NULL DEFAULT '1',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_system` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`uin` varchar(64) DEFAULT NULL,
|
||||
`subAccountUin` varchar(64) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_template_id` (`template_id`),
|
||||
UNIQUE KEY `uniq_name` (`name`,`app_id`,`deleted`,`uin`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `rules_template`
|
||||
--
|
||||
|
||||
LOCK TABLES `rules_template` WRITE;
|
||||
/*!40000 ALTER TABLE `rules_template` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `rules_template` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `resource_mark`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `resource_mark`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `resource_mark` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`app_id` bigint(20) NOT NULL,
|
||||
`mark_name` varchar(64) NOT NULL,
|
||||
`color` varchar(11) NOT NULL,
|
||||
`creator` varchar(32) NOT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `app_id_name` (`app_id`,`mark_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='标签信息表';
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `resource_mark`
|
||||
--
|
||||
|
||||
LOCK TABLES `resource_mark` WRITE;
|
||||
/*!40000 ALTER TABLE `resource_mark` DISABLE KEYS */;
|
||||
INSERT INTO `resource_mark` VALUES (10,1,'test','red','1','2023-11-06 02:45:46','2023-11-06 02:45:46');
|
||||
/*!40000 ALTER TABLE `resource_mark` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
SELECT `managed_resource`.`resource_id`,`managed_resource`.`user`,`managed_resource`.`status`,`managed_resource`.`status_message`,`managed_resource`.`safe_publication`,`managed_resource`.`rule_template_id`,`managed_resource`.`created_at`,`managed_resource`.`comments`,`managed_resource`.`expired_at`,`managed_resource`.`resource_mark_id`,`managed_resource`.`instance_id`,`managed_resource`.`resource_name`,`managed_resource`.`pay_mode`,`resource_mark`.`mark_name`,`resource_mark`.`color`,`rules_template`.`name`,`common_resource`.`src_instance_id`,`common_resource`.`database_kind`,`common_resource`.`source_type`,`common_resource`.`ip`,`common_resource`.`port` FROM `managed_resource` LEFT JOIN `common_resource` ON (`managed_resource`.`resource_id`=`common_resource`.`resource_id`) LEFT JOIN `resource_mark` ON (`managed_resource`.`resource_mark_id` = `resource_mark`.`id`) LEFT JOIN `rules_template` ON (`managed_resource`.`rule_template_id` = `rules_template`.`template_id`) ORDER BY `src_instance_id` ASC
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE IF NOT EXISTS `employee`
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
age INT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO employee(name, age) VALUES ('John', 30);
|
||||
INSERT INTO employee(name, age) VALUES ('Mary', 28);
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
CREATE TABLE `jfy_gift` (
|
||||
`id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`gift_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '礼品名称',
|
||||
`at_least_recharge_count` int(0) UNSIGNED NOT NULL DEFAULT 1 COMMENT '最少兑换数量',
|
||||
`comments` json NOT NULL COMMENT '礼品留言',
|
||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '礼品详情',
|
||||
`cost_price` decimal(10, 2) NULL DEFAULT NULL COMMENT '成本价',
|
||||
`cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '封面',
|
||||
`covers` json NOT NULL COMMENT '礼品图片库',
|
||||
`description` varchar(62) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '礼品备注',
|
||||
`express_type` json NOT NULL COMMENT '配送方式',
|
||||
`gift_type` int(0) NOT NULL COMMENT '礼品类型:1:实物;2:虚拟;3:优惠券;4:积分券',
|
||||
`has_props` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否有多个属性',
|
||||
`is_limit_sell` tinyint(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否限购',
|
||||
`limit_customer_tags` json NOT NULL COMMENT '语序购买的会员标签',
|
||||
`limit_sell_custom` tinyint(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启允许购买的会员标签',
|
||||
`limit_sell_cycle` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '限购周期',
|
||||
`limit_sell_cycle_count` int(0) NOT NULL COMMENT '限购期内允许购买的数量',
|
||||
`limit_sell_type` tinyint(0) NOT NULL COMMENT '限购类型',
|
||||
`market_price` decimal(10, 2) NOT NULL COMMENT '市场价',
|
||||
`out_sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内部编码',
|
||||
`props` json NOT NULL COMMENT '规格',
|
||||
`skus` json NOT NULL COMMENT 'SKU',
|
||||
`score_price` decimal(10, 2) NOT NULL COMMENT '兑换所需积分',
|
||||
`stock` int(0) NOT NULL COMMENT '库存',
|
||||
`create_at` datetime(0) NOT NULL COMMENT '创建日期',
|
||||
`store_id` int(0) NOT NULL COMMENT '所属商城',
|
||||
`status` int(0) UNSIGNED NULL DEFAULT 1 COMMENT '1:下架;20:审核中;30:复审中;99:上架',
|
||||
`view_count` int(0) NOT NULL DEFAULT 0 COMMENT '访问量',
|
||||
`sell_count` int(0) NULL DEFAULT 0 COMMENT '销量',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
|
||||
INSERT INTO `jfy_gift` VALUES (17, 'GIFT', 1, '[{\"name\": \"身份证\", \"field\": \"idcard\", \"required\": false}, {\"name\": \"留言2\", \"field\": \"text\", \"required\": false}]', '<p>礼品详情</p>', 0.00, '', '{\"list\": [{\"uid\": \"vc-upload-1629292486099-3\", \"url\": \"https://cdn.taobao.com/sULsYiwaOPjsKGoBXwKtuewPzACpBDfQ.jpg\", \"name\": \"O1CN01OH6PIP1Oc5ot06U17_!!922361725.jpg\", \"status\": \"done\"}, {\"uid\": \"vc-upload-1629292486099-4\", \"url\": \"https://cdn.taobao.com/lqLHDcrFTgNvlWyXfLYZwmsrODzIBtFH.jpg\", \"name\": \"O1CN018hBckI1Oc5ouc8ppl_!!922361725.jpg\", \"status\": \"done\"}, {\"uid\": \"vc-upload-1629292486099-5\", \"url\": \"https://cdn.taobao.com/pvqyutXckICmHhbPBQtrVLHuMlXuGxUg.jpg\", \"name\": \"O1CN0185Ubp91Oc5osQTTcc_!!922361725.jpg\", \"status\": \"done\"}]}', '支持个性定制的父亲节老师长辈的专属礼物', '[\"快递包邮\", \"同城配送\"]', 1, 0, 0, '[]', 0, 'day', 0, 1, 0.00, '259402', '[{\"name\": \"颜色\", \"values\": [\"红色\", \"蓝色\"]}]', '[{\"name\": \"red\", \"stock\": 10, \"gift_id\": 1, \"cost_price\": 80, \"score_price\": 188, \"market_price\": 388}, {\"name\": \"blue\", \"stock\": 100, \"gift_id\": 2, \"cost_price\": 81, \"score_price\": 200, \"market_price\": 288}]', 10.00, 0, '2021-08-18 21:26:13', 100004, 99, 0, 0);
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
-- ----------------------------
|
||||
-- Table structure for parcel_items
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `parcel_items`;
|
||||
CREATE TABLE `parcel_items` (
|
||||
`id` int(11) NOT NULL,
|
||||
`parcel_id` int(11) NULL DEFAULT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of parcel_items
|
||||
-- ----------------------------
|
||||
INSERT INTO `parcel_items` VALUES (1, 1, '新品');
|
||||
INSERT INTO `parcel_items` VALUES (2, 3, '新品2');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for parcels
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `parcels`;
|
||||
CREATE TABLE `parcels` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of parcels
|
||||
-- ----------------------------
|
||||
INSERT INTO `parcels` VALUES (1);
|
||||
INSERT INTO `parcels` VALUES (2);
|
||||
INSERT INTO `parcels` VALUES (3);
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
-- ----------------------------
|
||||
-- Table structure for items
|
||||
-- ----------------------------
|
||||
CREATE TABLE `items` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of items
|
||||
-- ----------------------------
|
||||
INSERT INTO `items` VALUES (1, '金秋产品1');
|
||||
INSERT INTO `items` VALUES (2, '金秋产品2');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for parcels
|
||||
-- ----------------------------
|
||||
CREATE TABLE `parcels` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`item_id` int(11) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of parcels
|
||||
-- ----------------------------
|
||||
INSERT INTO `parcels` VALUES (1, 1);
|
||||
INSERT INTO `parcels` VALUES (2, 2);
|
||||
INSERT INTO `parcels` VALUES (3, 0);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE `issue2105` (
|
||||
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
|
||||
INSERT INTO `issue2105` VALUES ('1', NULL);
|
||||
INSERT INTO `issue2105` VALUES ('2', '[{\"Name\": \"任务类型\", \"Value\": \"高价值\"}, {\"Name\": \"优先级\", \"Value\": \"高\"}, {\"Name\": \"是否亮点功能\", \"Value\": \"是\"}]');
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_role
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_role`;
|
||||
CREATE TABLE `sys_role` (
|
||||
`id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '||s',
|
||||
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色名称||s,r',
|
||||
`code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色 code||s,r',
|
||||
`description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述信息|text',
|
||||
`weight` int(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序||r|min:0#发布状态不能小于 0',
|
||||
`status_id` int(0) UNSIGNED NOT NULL DEFAULT 1 COMMENT '发布状态|hasOne|f:status,fk:id',
|
||||
`created_at` datetime(0) NULL DEFAULT NULL,
|
||||
`updated_at` datetime(0) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `code`(`code`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1091 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色表' ROW_FORMAT = Compact;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sys_role
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_role` VALUES (1, '开发人员', 'developer', '123123', 900, 2, '2022-09-03 21:25:03', '2022-09-09 23:35:23');
|
||||
INSERT INTO `sys_role` VALUES (2, '管理员', 'admin', '', 800, 1, '2022-09-03 21:25:03', '2022-09-09 23:00:17');
|
||||
INSERT INTO `sys_role` VALUES (3, '运营', 'operator', '', 700, 1, '2022-09-03 21:25:03', '2022-09-03 21:25:03');
|
||||
INSERT INTO `sys_role` VALUES (4, '客服', 'service', '', 600, 1, '2022-09-03 21:25:03', '2022-09-03 21:25:03');
|
||||
INSERT INTO `sys_role` VALUES (5, '收银', 'account', '', 500, 1, '2022-09-03 21:25:03', '2022-09-03 21:25:03');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_status
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_status`;
|
||||
CREATE TABLE `sys_status` (
|
||||
`id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`en` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '英文名称',
|
||||
`cn` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '中文名称',
|
||||
`weight` int(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序权重',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '发布状态' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sys_status
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_status` VALUES (1, 'on line', '上线', 900);
|
||||
INSERT INTO `sys_status` VALUES (2, 'undecided', '未决定', 800);
|
||||
INSERT INTO `sys_status` VALUES (3, 'off line', '下线', 700);
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE `a` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (id) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
INSERT INTO `a` (`id`) VALUES ('2');
|
||||
|
||||
CREATE TABLE `b` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL ,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
INSERT INTO `b` (`id`, `name`) VALUES ('2', 'a');
|
||||
INSERT INTO `b` (`id`, `name`) VALUES ('3', 'b');
|
||||
|
||||
CREATE TABLE `c` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
INSERT INTO `c` (`id`) VALUES ('2');
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE `issue2643` (
|
||||
`id` INT(10) NULL DEFAULT NULL,
|
||||
`name` VARCHAR(50) NULL DEFAULT NULL,
|
||||
`value` INT(10) NULL DEFAULT NULL,
|
||||
`dept` VARCHAR(50) NULL DEFAULT NULL
|
||||
)
|
||||
ENGINE=InnoDB
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE `issue3086_user`
|
||||
(
|
||||
`id` int(10) unsigned NOT NULL COMMENT 'User ID',
|
||||
`passport` varchar(45) NOT NULL COMMENT 'User Passport',
|
||||
`password` varchar(45) DEFAULT NULL COMMENT 'User Password',
|
||||
`nickname` varchar(45) DEFAULT NULL COMMENT 'User Nickname',
|
||||
`create_at` datetime DEFAULT NULL COMMENT 'Created Time',
|
||||
`update_at` datetime DEFAULT NULL COMMENT 'Updated Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE `issue3218_sys_config` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '配置名称',
|
||||
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '配置值',
|
||||
`created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `name`(`name`(191)) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sys_config
|
||||
-- ----------------------------
|
||||
INSERT INTO `issue3218_sys_config` VALUES (49, 'site', '{\"banned_ip\":\"22\",\"filings\":\"2222\",\"fixed_page\":\"\",\"site_name\":\"22\",\"version\":\"22\"}', '2023-12-19 14:08:25', '2023-12-19 14:08:25');
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE `issue3626` (
|
||||
id int(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE `issue3754` (
|
||||
id int(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
create_at datetime(0) DEFAULT NULL,
|
||||
update_at datetime(0) DEFAULT NULL,
|
||||
delete_at datetime(0) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE `issue3915` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id',
|
||||
`a` float DEFAULT NULL COMMENT 'user name',
|
||||
`b` float DEFAULT NULL COMMENT 'user status',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
INSERT INTO `issue3915` (`id`,`a`,`b`) VALUES (1,1,2);
|
||||
INSERT INTO `issue3915` (`id`,`a`,`b`) VALUES (2,5,4);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE issue4034 (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
passport VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
nickname VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
DROP TABLE IF EXISTS `issue4086`;
|
||||
CREATE TABLE `issue4086` (
|
||||
`proxy_id` bigint NOT NULL,
|
||||
`recommend_ids` json DEFAULT NULL,
|
||||
`photos` json DEFAULT NULL,
|
||||
PRIMARY KEY (`proxy_id`)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
INSERT INTO `issue4086` (`proxy_id`, `recommend_ids`, `photos`) VALUES (1, '[584, 585]', 'null');
|
||||
INSERT INTO `issue4086` (`proxy_id`, `recommend_ids`, `photos`) VALUES (2, '[]', NULL);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE %s (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`key` varchar(45) DEFAULT NULL,
|
||||
`category_id` int(10) unsigned NOT NULL,
|
||||
`user_id` int(10) unsigned NOT NULL,
|
||||
`title` varchar(255) NOT NULL,
|
||||
`content` mediumtext NOT NULL,
|
||||
`sort` int(10) unsigned DEFAULT '0',
|
||||
`brief` varchar(255) DEFAULT NULL,
|
||||
`thumb` varchar(255) DEFAULT NULL,
|
||||
`tags` varchar(900) DEFAULT NULL,
|
||||
`referer` varchar(255) DEFAULT NULL,
|
||||
`status` smallint(5) unsigned DEFAULT '0',
|
||||
`view_count` int(10) unsigned DEFAULT '0',
|
||||
`zan_count` int(10) unsigned DEFAULT NULL,
|
||||
`cai_count` int(10) unsigned DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE `instance` (
|
||||
`f_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NULL DEFAULT '',
|
||||
PRIMARY KEY (`f_id`) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
INSERT INTO `instance` VALUES (1, 'john');
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
CREATE TABLE `table_a` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`alias` varchar(255) NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
INSERT INTO `table_a` VALUES (1, 'table_a_test1');
|
||||
INSERT INTO `table_a` VALUES (2, 'table_a_test2');
|
||||
|
||||
CREATE TABLE `table_b` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`table_a_id` int(11) NOT NULL,
|
||||
`alias` varchar(255) NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
INSERT INTO `table_b` VALUES (10, 1, 'table_b_test1');
|
||||
INSERT INTO `table_b` VALUES (20, 2, 'table_b_test2');
|
||||
INSERT INTO `table_b` VALUES (30, 1, 'table_b_test3');
|
||||
INSERT INTO `table_b` VALUES (40, 2, 'table_b_test4');
|
||||
|
||||
CREATE TABLE `table_c` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`table_b_id` int(11) NOT NULL,
|
||||
`alias` varchar(255) NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
INSERT INTO `table_c` VALUES (100, 10, 'table_c_test1');
|
||||
INSERT INTO `table_c` VALUES (200, 10, 'table_c_test2');
|
||||
INSERT INTO `table_c` VALUES (300, 20, 'table_c_test3');
|
||||
INSERT INTO `table_c` VALUES (400, 30, 'table_c_test4');
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
|
@ -4,58 +4,43 @@
|
|||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package oracle implements database.Driver for Oracle database.
|
||||
// Package oracle implements database.Driver, which supports operations for database Oracle.
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/sijms/go-ora/v2"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// Driver is the driver for Oracle database.
|
||||
// Driver is the driver for oracle database.
|
||||
type Driver struct {
|
||||
*database.Core
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = `"`
|
||||
rowNumberAliasForSelect = `ROW_NUMBER__`
|
||||
quoteChar = `"`
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := database.Register("oracle", New()); err != nil {
|
||||
if err := database.Register(`oracle`, New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates and returns a driver that implements database.Driver for Oracle.
|
||||
// New create and returns a driver that implements database.Driver, which supports operations for Oracle.
|
||||
func New() database.Driver {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
// New creates and returns a database object for Oracle.
|
||||
// New creates and returns a database object for oracle.
|
||||
// It implements the interface of database.Driver for extra database driver installation.
|
||||
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||
return &Driver{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetChars returns the security char for Oracle.
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for Oracle.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf("oracle://%s:%s@%s:%s/%s",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name)
|
||||
}
|
||||
return sql.Open("oracle", source)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// DoCommit commits current sql and arguments to underlying sql driver.
|
||||
func (d *Driver) DoCommit(ctx context.Context, in database.DoCommitInput) (out database.DoCommitOutput, err error) {
|
||||
out, err = d.Core.DoCommit(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(out.Records) > 0 {
|
||||
// remove auto added field.
|
||||
for i, record := range out.Records {
|
||||
delete(record, rowNumberAliasForSelect)
|
||||
out.Records[i] = record
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
const (
|
||||
returningClause = " RETURNING %s INTO ?"
|
||||
)
|
||||
|
||||
// DoExec commits the sql string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
// It handles INSERT statements specially to support LastInsertId.
|
||||
func (d *Driver) DoExec(
|
||||
ctx context.Context, link database.Link, sql string, args ...interface{},
|
||||
) (result sql.Result, err error) {
|
||||
var (
|
||||
isUseCoreDoExec = true
|
||||
primaryKey string
|
||||
pkField database.TableField
|
||||
)
|
||||
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if tx := database.TXFromCtx(ctx, d.GetGroup()); tx != nil {
|
||||
link = tx
|
||||
} else if link, err = d.MasterLink(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
if tx := database.TXFromCtx(ctx, d.GetGroup()); tx != nil {
|
||||
link = tx
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it is an insert operation with primary key from context.
|
||||
if value := ctx.Value(internalPrimaryKeyInCtx); value != nil {
|
||||
if field, ok := value.(database.TableField); ok {
|
||||
pkField = field
|
||||
isUseCoreDoExec = false
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it is an INSERT statement with primary key.
|
||||
if !isUseCoreDoExec && pkField.Name != "" && strings.Contains(strings.ToUpper(sql), "INSERT INTO") {
|
||||
primaryKey = pkField.Name
|
||||
// Oracle supports RETURNING clause to get the last inserted id
|
||||
sql += fmt.Sprintf(returningClause, d.QuoteWord(primaryKey))
|
||||
} else {
|
||||
// Use default DoExec for non-INSERT or no primary key scenarios
|
||||
return d.Core.DoExec(ctx, link, sql, args...)
|
||||
}
|
||||
|
||||
// Only the insert operation with primary key can execute the following code
|
||||
|
||||
// SQL filtering.
|
||||
sql, args = d.FormatSqlBeforeExecuting(sql, args)
|
||||
sql, args, err = d.DoFilter(ctx, link, sql, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prepare output variable for RETURNING clause
|
||||
var lastInsertId int64
|
||||
// Append the output parameter for the RETURNING clause
|
||||
args = append(args, &lastInsertId)
|
||||
|
||||
// Link execution.
|
||||
_, err = d.DoCommit(ctx, database.DoCommitInput{
|
||||
Link: link,
|
||||
Sql: sql,
|
||||
Args: args,
|
||||
Stmt: nil,
|
||||
Type: database.SqlTypeExecContext,
|
||||
IsTransaction: link.IsTransaction(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return &Result{
|
||||
lastInsertId: 0,
|
||||
rowsAffected: 0,
|
||||
lastInsertIdError: err,
|
||||
}, err
|
||||
}
|
||||
|
||||
// Get rows affected from the result
|
||||
// For single insert with RETURNING clause, affected is always 1
|
||||
var affected int64 = 1
|
||||
|
||||
// Check if the primary key field type supports LastInsertId
|
||||
if !strings.Contains(strings.ToLower(pkField.Type), "int") {
|
||||
return &Result{
|
||||
lastInsertId: 0,
|
||||
rowsAffected: affected,
|
||||
lastInsertIdError: gerror.NewCodef(
|
||||
gcode.CodeNotSupported,
|
||||
"LastInsertId is not supported by primary key type: %s",
|
||||
pkField.Type,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &Result{
|
||||
lastInsertId: lastInsertId,
|
||||
rowsAffected: affected,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
var (
|
||||
newSqlReplacementTmp = `
|
||||
SELECT * FROM (
|
||||
SELECT GFORM.*, ROWNUM ROW_NUMBER__ FROM (%s %s) GFORM WHERE ROWNUM <= %d
|
||||
) WHERE ROW_NUMBER__ > %d
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
newSqlReplacementTmp, err = database.FormatMultiLineSqlToSingle(newSqlReplacementTmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link database.Link, sql string, args []any) (newSql string, newArgs []any, err error) {
|
||||
var index int
|
||||
newArgs = args
|
||||
// Convert placeholder char '?' to string ":vx".
|
||||
newSql, err = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(":v%d", index)
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newSql, err = gregex.ReplaceString("\"", "", newSql)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newSql, err = d.parseSql(newSql)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return d.Core.DoFilter(ctx, link, newSql, newArgs)
|
||||
}
|
||||
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
// for support of oracle server.
|
||||
func (d *Driver) parseSql(toBeCommittedSql string) (string, error) {
|
||||
var (
|
||||
err error
|
||||
operation = gstr.StrTillEx(toBeCommittedSql, " ")
|
||||
keyword = strings.ToUpper(gstr.Trim(operation))
|
||||
)
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
toBeCommittedSql, err = d.handleSelectSqlReplacement(toBeCommittedSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
|
||||
func (d *Driver) handleSelectSqlReplacement(toBeCommittedSql string) (newSql string, err error) {
|
||||
var (
|
||||
match [][]string
|
||||
patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))`
|
||||
)
|
||||
match, err = gregex.MatchAllString(patten, toBeCommittedSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(match) == 0 {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
var index = 1
|
||||
if len(match) < 2 || strings.HasPrefix(match[index][0], "LIMIT") == false {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
// only handle `SELECT ... LIMIT ...` statement.
|
||||
queryExpr, err := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", toBeCommittedSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(queryExpr) == 0 {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
page, limit := 0, 0
|
||||
for i := 1; i < len(match[index]); i++ {
|
||||
if len(strings.TrimSpace(match[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(match[index][i], "LIMIT") {
|
||||
if match[index][i+2] != "" {
|
||||
page, err = strconv.Atoi(match[index][i+1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
limit, err = strconv.Atoi(match[index][i+2])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
limit = (page/limit + 1) * limit
|
||||
page, err = strconv.Atoi(match[index][i+1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
limit, err = strconv.Atoi(match[index][i+1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
var newReplacedSql = fmt.Sprintf(
|
||||
newSqlReplacementTmp,
|
||||
queryExpr[1], queryExpr[2], limit, page,
|
||||
)
|
||||
return newReplacedSql, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
internalPrimaryKeyInCtx gctx.StrKey = "primary_key_field"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
// The list parameter must contain at least one record, which was previously validated.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link database.Link, table string, list database.List, option database.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case database.InsertOptionSave:
|
||||
return d.doSave(ctx, link, table, list, option)
|
||||
|
||||
case database.InsertOptionReplace:
|
||||
// Oracle does not support REPLACE INTO syntax, use SAVE instead.
|
||||
return d.doSave(ctx, link, table, list, option)
|
||||
|
||||
case database.InsertOptionIgnore:
|
||||
// Oracle does not support INSERT IGNORE syntax, use MERGE instead.
|
||||
return d.doInsertIgnore(ctx, link, table, list, option)
|
||||
|
||||
case database.InsertOptionDefault:
|
||||
// For default insert, set primary key field in context to support LastInsertId.
|
||||
// Only set it when the primary key is not provided in the data, for performance reason.
|
||||
tableFields, err := d.GetCore().GetDB().TableFields(ctx, table)
|
||||
if err == nil && len(list) > 0 {
|
||||
for _, field := range tableFields {
|
||||
if strings.EqualFold(field.Key, "pri") {
|
||||
// Check if primary key is provided in the data.
|
||||
pkProvided := false
|
||||
for key := range list[0] {
|
||||
if strings.EqualFold(key, field.Name) {
|
||||
pkProvided = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// Only use RETURNING when primary key is not provided, for performance reason.
|
||||
if !pkProvided {
|
||||
pkField := *field
|
||||
ctx = context.WithValue(ctx, internalPrimaryKeyInCtx, pkField)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []any
|
||||
)
|
||||
// Retrieve the table fields and length.
|
||||
var (
|
||||
listLength = len(list)
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
for k := range list[0] {
|
||||
keys = append(keys, k)
|
||||
valueHolder = append(valueHolder, "?")
|
||||
}
|
||||
var (
|
||||
batchResult = new(database.SqlResult)
|
||||
charL, charR = d.GetChars()
|
||||
keyStr = charL + strings.Join(keys, charL+","+charR) + charR
|
||||
valueHolderStr = strings.Join(valueHolder, ",")
|
||||
)
|
||||
// Format "INSERT...INTO..." statement.
|
||||
// Note: Use standard INSERT INTO syntax instead of INSERT ALL to ensure triggers fire
|
||||
for i := 0; i < listLength; i++ {
|
||||
for _, k := range keys {
|
||||
if s, ok := list[i][k].(database.Raw); ok {
|
||||
params = append(params, gconv.String(s))
|
||||
} else {
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
}
|
||||
values = append(values, valueHolderStr)
|
||||
|
||||
// Execute individual INSERT for each record to trigger row-level triggers
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"INSERT INTO %s(%s) VALUES(%s)",
|
||||
table, keyStr, valueHolderStr,
|
||||
), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.Result = r
|
||||
batchResult.Affected += n
|
||||
}
|
||||
params = params[:0]
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
// doSave support upsert for Oracle
|
||||
func (d *Driver) doSave(ctx context.Context,
|
||||
link database.Link, table string, list database.List, option database.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
return d.doMergeInsert(ctx, link, table, list, option, true)
|
||||
}
|
||||
|
||||
// doInsertIgnore implements INSERT IGNORE operation using MERGE statement for Oracle database.
|
||||
// It only inserts records when there's no conflict on primary/unique keys.
|
||||
func (d *Driver) doInsertIgnore(ctx context.Context,
|
||||
link database.Link, table string, list database.List, option database.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
return d.doMergeInsert(ctx, link, table, list, option, false)
|
||||
}
|
||||
|
||||
// doMergeInsert implements MERGE-based insert operations for Oracle database.
|
||||
// When withUpdate is true, it performs upsert (insert or update).
|
||||
// When withUpdate is false, it performs insert ignore (insert only when no conflict).
|
||||
func (d *Driver) doMergeInsert(
|
||||
ctx context.Context,
|
||||
link database.Link, table string, list database.List, option database.DoInsertOption, withUpdate bool,
|
||||
) (result sql.Result, err error) {
|
||||
// If OnConflict is not specified, automatically get the primary key of the table
|
||||
conflictKeys := option.OnConflict
|
||||
if len(conflictKeys) == 0 {
|
||||
primaryKeys, err := d.Core.GetPrimaryKeys(ctx, table)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(
|
||||
gcode.CodeInternalError,
|
||||
err,
|
||||
`failed to get primary keys for table`,
|
||||
)
|
||||
}
|
||||
foundPrimaryKey := false
|
||||
for _, primaryKey := range primaryKeys {
|
||||
for dataKey := range list[0] {
|
||||
if strings.EqualFold(dataKey, primaryKey) {
|
||||
foundPrimaryKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundPrimaryKey {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundPrimaryKey {
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeMissingParameter,
|
||||
`Replace/Save/InsertIgnore operation requires conflict detection: `+
|
||||
`either specify OnConflict() columns or ensure table '%s' has a primary key in the data`,
|
||||
table,
|
||||
)
|
||||
}
|
||||
// TODO consider composite primary keys.
|
||||
conflictKeys = primaryKeys
|
||||
}
|
||||
|
||||
var (
|
||||
one = list[0]
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
conflictKeySet = gset.NewStrSet(false)
|
||||
|
||||
// queryHolders: Handle data with Holder that need to be upsert
|
||||
// queryValues: Handle data that need to be upsert
|
||||
// insertKeys: Handle valid keys that need to be inserted
|
||||
// insertValues: Handle values that need to be inserted
|
||||
// updateValues: Handle values that need to be updated
|
||||
queryHolders = make([]string, oneLen)
|
||||
queryValues = make([]any, oneLen)
|
||||
insertKeys = make([]string, oneLen)
|
||||
insertValues = make([]string, oneLen)
|
||||
updateValues []string
|
||||
)
|
||||
|
||||
// conflictKeys slice type conv to set type
|
||||
for _, conflictKey := range conflictKeys {
|
||||
conflictKeySet.Add(gstr.ToUpper(conflictKey))
|
||||
}
|
||||
|
||||
index := 0
|
||||
for key, value := range one {
|
||||
keyWithChar := charL + key + charR
|
||||
queryHolders[index] = fmt.Sprintf("? AS %s", keyWithChar)
|
||||
queryValues[index] = value
|
||||
insertKeys[index] = keyWithChar
|
||||
insertValues[index] = fmt.Sprintf("T2.%s", keyWithChar)
|
||||
|
||||
// Build updateValues only when withUpdate is true
|
||||
// Filter conflict keys and soft created fields from updateValues
|
||||
if withUpdate && !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
|
||||
updateValues = append(
|
||||
updateValues,
|
||||
fmt.Sprintf(`T1.%s = T2.%s`, keyWithChar, keyWithChar),
|
||||
)
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
var (
|
||||
batchResult = new(database.SqlResult)
|
||||
sqlStr = parseSqlForMerge(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
|
||||
)
|
||||
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.Result = r
|
||||
batchResult.Affected += n
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
// parseSqlForMerge generates MERGE statement for Oracle database.
|
||||
// When updateValues is empty, it only inserts (INSERT IGNORE behavior).
|
||||
// When updateValues is provided, it performs upsert (INSERT or UPDATE).
|
||||
// Examples:
|
||||
// - INSERT IGNORE: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...)
|
||||
// - UPSERT: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...) WHEN MATCHED THEN UPDATE SET ...
|
||||
func parseSqlForMerge(table string,
|
||||
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
|
||||
) (sqlStr string) {
|
||||
var (
|
||||
queryHolderStr = strings.Join(queryHolders, ",")
|
||||
insertKeyStr = strings.Join(insertKeys, ",")
|
||||
insertValueStr = strings.Join(insertValues, ",")
|
||||
duplicateKeyStr string
|
||||
)
|
||||
|
||||
// Build ON condition
|
||||
for index, keys := range duplicateKey {
|
||||
if index != 0 {
|
||||
duplicateKeyStr += " AND "
|
||||
}
|
||||
duplicateKeyStr += fmt.Sprintf("T1.%s = T2.%s", keys, keys)
|
||||
}
|
||||
|
||||
// Build SQL based on whether UPDATE is needed
|
||||
pattern := gstr.Trim(
|
||||
`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN ` +
|
||||
`NOT MATCHED THEN INSERT(%s) VALUES (%s)`,
|
||||
)
|
||||
if len(updateValues) > 0 {
|
||||
// Upsert: INSERT or UPDATE
|
||||
pattern += gstr.Trim(` WHEN MATCHED THEN UPDATE SET %s`)
|
||||
return fmt.Sprintf(
|
||||
pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr,
|
||||
strings.Join(updateValues, ","),
|
||||
)
|
||||
}
|
||||
// Insert Ignore: INSERT only
|
||||
return fmt.Sprintf(pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr)
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
gora "github.com/sijms/go-ora/v2"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for oracle.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "oracle"
|
||||
)
|
||||
|
||||
options := map[string]string{
|
||||
"CONNECTION TIMEOUT": "60",
|
||||
"PREFETCH_ROWS": "25",
|
||||
}
|
||||
|
||||
if config.Debug {
|
||||
options["TRACE FILE"] = "oracle_trace.log"
|
||||
}
|
||||
// [username:[password]@]host[:port][/service_name][?param1=value1&...¶mN=valueN]
|
||||
if config.Extra != "" {
|
||||
// fix #3226
|
||||
list := strings.Split(config.Extra, "&")
|
||||
for _, v := range list {
|
||||
kv := strings.Split(v, "=")
|
||||
if len(kv) == 2 {
|
||||
options[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
source = gora.BuildUrl(
|
||||
config.Host, gconv.Int(config.Port), config.Name, config.User, config.Pass, options,
|
||||
)
|
||||
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`sql.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
// OrderRandomFunction returns the SQL function for random ordering.
|
||||
func (d *Driver) OrderRandomFunction() string {
|
||||
return "DBMS_RANDOM.VALUE()"
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
// Result implements sql.Result interface for Oracle database.
|
||||
type Result struct {
|
||||
lastInsertId int64
|
||||
rowsAffected int64
|
||||
lastInsertIdError error
|
||||
}
|
||||
|
||||
// LastInsertId returns the last insert id.
|
||||
func (r *Result) LastInsertId() (int64, error) {
|
||||
return r.lastInsertId, r.lastInsertIdError
|
||||
}
|
||||
|
||||
// RowsAffected returns the rows affected.
|
||||
func (r *Result) RowsAffected() (int64, error) {
|
||||
return r.rowsAffected, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
tableFieldsSqlTmp = `
|
||||
SELECT
|
||||
c.COLUMN_NAME AS FIELD,
|
||||
CASE
|
||||
WHEN (c.DATA_TYPE='NUMBER' AND NVL(c.DATA_SCALE,0)=0) THEN 'INT'||'('||c.DATA_PRECISION||','||c.DATA_SCALE||')'
|
||||
WHEN (c.DATA_TYPE='NUMBER' AND NVL(c.DATA_SCALE,0)>0) THEN 'FLOAT'||'('||c.DATA_PRECISION||','||c.DATA_SCALE||')'
|
||||
WHEN c.DATA_TYPE='FLOAT' THEN c.DATA_TYPE||'('||c.DATA_PRECISION||','||c.DATA_SCALE||')'
|
||||
ELSE c.DATA_TYPE||'('||c.DATA_LENGTH||')' END AS TYPE,
|
||||
c.NULLABLE,
|
||||
CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 'PRI' ELSE '' END AS KEY
|
||||
FROM USER_TAB_COLUMNS c
|
||||
LEFT JOIN (
|
||||
SELECT cols.COLUMN_NAME
|
||||
FROM USER_CONSTRAINTS cons
|
||||
JOIN USER_CONS_COLUMNS cols ON cons.CONSTRAINT_NAME = cols.CONSTRAINT_NAME
|
||||
WHERE cons.TABLE_NAME = '%s' AND cons.CONSTRAINT_TYPE = 'P'
|
||||
) pk ON c.COLUMN_NAME = pk.COLUMN_NAME
|
||||
WHERE c.TABLE_NAME = '%s'
|
||||
ORDER BY c.COLUMN_ID
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
tableFieldsSqlTmp, err = database.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*database.TableField, err error) {
|
||||
var (
|
||||
result database.Result
|
||||
link database.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
upperTable = strings.ToUpper(table)
|
||||
structureSql = fmt.Sprintf(tableFieldsSqlTmp, upperTable, upperTable)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, structureSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fields = make(map[string]*database.TableField)
|
||||
for i, m := range result {
|
||||
isNull := false
|
||||
if m["NULLABLE"].String() == "Y" {
|
||||
isNull = true
|
||||
}
|
||||
|
||||
fields[m["FIELD"].String()] = &database.TableField{
|
||||
Index: i,
|
||||
Name: m["FIELD"].String(),
|
||||
Type: m["TYPE"].String(),
|
||||
Null: isNull,
|
||||
Key: m["KEY"].String(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
const (
|
||||
tablesSqlTmp = `SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME`
|
||||
)
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
// Note that it ignores the parameter `schema` in oracle database, as it is not necessary.
|
||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||
var result database.Result
|
||||
// DO NOT use `usedSchema` as parameter for function `SlaveLink`.
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, tablesSqlTmp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -4,39 +4,39 @@
|
|||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package pgsql implements database.Driver for PostgreSQL database.
|
||||
// Package pgsql implements database.Driver, which supports operations for database PostgreSQL.
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
// Driver is the driver for PostgreSQL database.
|
||||
// Driver is the driver for postgresql database.
|
||||
type Driver struct {
|
||||
*database.Core
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = `"`
|
||||
internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
|
||||
defaultSchema string = "public"
|
||||
quoteChar string = `"`
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := database.Register("pgsql", New()); err != nil {
|
||||
if err := database.Register(`pgsql`, New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates and returns a driver that implements database.Driver for PostgreSQL.
|
||||
// New create and returns a driver that implements database.Driver, which supports operations for PostgreSql.
|
||||
func New() database.Driver {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
// New creates and returns a database object for PostgreSQL.
|
||||
// New creates and returns a database object for postgresql.
|
||||
// It implements the interface of database.Driver for extra database driver installation.
|
||||
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||
return &Driver{
|
||||
|
|
@ -44,28 +44,7 @@ func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.D
|
|||
}, nil
|
||||
}
|
||||
|
||||
// GetChars returns the security char for PostgreSQL database.
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for PostgreSQL.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||
var (
|
||||
source string
|
||||
username = config.User
|
||||
password = config.Pass
|
||||
host = config.Host
|
||||
port = config.Port
|
||||
dbName = config.Name
|
||||
)
|
||||
|
||||
source = fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||
host, port, username, password, dbName)
|
||||
|
||||
if config.Extra != "" {
|
||||
source += " " + config.Extra
|
||||
}
|
||||
|
||||
return sql.Open("postgres", source)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,272 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// ConvertValueForField converts value to database acceptable value.
|
||||
func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fieldValue any) (any, error) {
|
||||
if fieldValue == nil {
|
||||
return d.Core.ConvertValueForField(ctx, fieldType, fieldValue)
|
||||
}
|
||||
|
||||
var fieldValueKind = reflect.TypeOf(fieldValue).Kind()
|
||||
|
||||
if fieldValueKind == reflect.Slice {
|
||||
// For bytea type, pass []byte directly without any conversion.
|
||||
if _, ok := fieldValue.([]byte); ok && gstr.Contains(fieldType, "bytea") {
|
||||
return d.Core.ConvertValueForField(ctx, fieldType, fieldValue)
|
||||
}
|
||||
// For pgsql, json or jsonb require '[]'
|
||||
if !gstr.Contains(fieldType, "json") {
|
||||
fieldValue = gstr.ReplaceByMap(gconv.String(fieldValue),
|
||||
map[string]string{
|
||||
"[": "{",
|
||||
"]": "}",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
return d.Core.ConvertValueForField(ctx, fieldType, fieldValue)
|
||||
}
|
||||
|
||||
// CheckLocalTypeForField checks and returns corresponding local golang type for given db type.
|
||||
// The parameter `fieldType` is in lower case, like:
|
||||
// `int2`, `int4`, `int8`, `_int2`, `_int4`, `_int8`, `_float4`, `_float8`, etc.
|
||||
//
|
||||
// PostgreSQL type mapping:
|
||||
//
|
||||
// | PostgreSQL Type | Local Go Type |
|
||||
// |------------------------------|---------------|
|
||||
// | int2, int4 | int |
|
||||
// | int8 | int64 |
|
||||
// | uuid | uuid.UUID |
|
||||
// | _int2, _int4 | []int32 | // Note: pq package does not provide Int16Array; int32 is used for compatibility
|
||||
// | _int8 | []int64 |
|
||||
// | _float4 | []float32 |
|
||||
// | _float8 | []float64 |
|
||||
// | _bool | []bool |
|
||||
// | _varchar, _text | []string |
|
||||
// | _char, _bpchar | []string |
|
||||
// | _numeric, _decimal, _money | []float64 |
|
||||
// | bytea | []byte |
|
||||
// | _bytea | [][]byte |
|
||||
// | _uuid | []uuid.UUID |
|
||||
func (d *Driver) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue any) (database.LocalType, error) {
|
||||
var typeName string
|
||||
match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
|
||||
if len(match) == 3 {
|
||||
typeName = gstr.Trim(match[1])
|
||||
} else {
|
||||
typeName = fieldType
|
||||
}
|
||||
typeName = strings.ToLower(typeName)
|
||||
switch typeName {
|
||||
case "int2", "int4":
|
||||
return database.LocalTypeInt, nil
|
||||
|
||||
case "int8":
|
||||
return database.LocalTypeInt64, nil
|
||||
|
||||
case "uuid":
|
||||
return database.LocalTypeUUID, nil
|
||||
|
||||
case "_int2", "_int4":
|
||||
return database.LocalTypeInt32Slice, nil
|
||||
|
||||
case "_int8":
|
||||
return database.LocalTypeInt64Slice, nil
|
||||
|
||||
case "_float4":
|
||||
return database.LocalTypeFloat32Slice, nil
|
||||
|
||||
case "_float8":
|
||||
return database.LocalTypeFloat64Slice, nil
|
||||
|
||||
case "_bool":
|
||||
return database.LocalTypeBoolSlice, nil
|
||||
|
||||
case "_varchar", "_text", "_char", "_bpchar":
|
||||
return database.LocalTypeStringSlice, nil
|
||||
|
||||
case "_uuid":
|
||||
return database.LocalTypeUUIDSlice, nil
|
||||
|
||||
case "_numeric", "_decimal", "_money":
|
||||
return database.LocalTypeFloat64Slice, nil
|
||||
|
||||
case "bytea":
|
||||
return database.LocalTypeBytes, nil
|
||||
|
||||
case "_bytea":
|
||||
return database.LocalTypeBytesSlice, nil
|
||||
|
||||
default:
|
||||
return d.Core.CheckLocalTypeForField(ctx, fieldType, fieldValue)
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertValueForLocal converts value to local Golang type of value according field type name from database.
|
||||
// The parameter `fieldType` is in lower case, like:
|
||||
// `int2`, `int4`, `int8`, `_int2`, `_int4`, `_int8`, `uuid`, `_uuid`, etc.
|
||||
//
|
||||
// See: https://www.postgresql.org/docs/current/datatype.html
|
||||
//
|
||||
// PostgreSQL type mapping:
|
||||
//
|
||||
// | PostgreSQL Type | SQL Type | pq Type | Go Type |
|
||||
// |-----------------|--------------------------------|-----------------|-------------|
|
||||
// | int2 | int2, smallint | - | int |
|
||||
// | int4 | int4, integer | - | int |
|
||||
// | int8 | int8, bigint, bigserial | - | int64 |
|
||||
// | uuid | uuid | - | uuid.UUID |
|
||||
// | _int2 | int2[], smallint[] | pq.Int32Array | []int32 |
|
||||
// | _int4 | int4[], integer[] | pq.Int32Array | []int32 |
|
||||
// | _int8 | int8[], bigint[] | pq.Int64Array | []int64 |
|
||||
// | _float4 | float4[], real[] | pq.Float32Array | []float32 |
|
||||
// | _float8 | float8[], double precision[] | pq.Float64Array | []float64 |
|
||||
// | _bool | boolean[], bool[] | pq.BoolArray | []bool |
|
||||
// | _varchar | varchar[], character varying[] | pq.StringArray | []string |
|
||||
// | _text | text[] | pq.StringArray | []string |
|
||||
// | _char, _bpchar | char[], character[] | pq.StringArray | []string |
|
||||
// | _numeric | numeric[] | pq.Float64Array | []float64 |
|
||||
// | _decimal | decimal[] | pq.Float64Array | []float64 |
|
||||
// | _money | money[] | pq.Float64Array | []float64 |
|
||||
// | bytea | bytea | - | []byte |
|
||||
// | _bytea | bytea[] | pq.ByteaArray | [][]byte |
|
||||
// | _uuid | uuid[] | pq.StringArray | []uuid.UUID |
|
||||
//
|
||||
// Note: PostgreSQL also supports these array types but they are not yet mapped:
|
||||
// - _date (date[]), _timestamp (timestamp[]), _timestamptz (timestamptz[])
|
||||
// - _jsonb (jsonb[]), _json (json[])
|
||||
func (d *Driver) ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue any) (any, error) {
|
||||
typeName, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||
typeName = strings.ToLower(typeName)
|
||||
|
||||
// Basic types are mostly handled by Core layer; handle array types and special-case bytea here.
|
||||
switch typeName {
|
||||
|
||||
// []byte
|
||||
case "bytea":
|
||||
if v, ok := fieldValue.([]byte); ok {
|
||||
return v, nil
|
||||
}
|
||||
return fieldValue, nil
|
||||
|
||||
// []int32
|
||||
case "_int2", "_int4":
|
||||
var result pq.Int32Array
|
||||
if err := result.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []int32(result), nil
|
||||
|
||||
// []int64
|
||||
case "_int8":
|
||||
var result pq.Int64Array
|
||||
if err := result.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []int64(result), nil
|
||||
|
||||
// []float32
|
||||
case "_float4":
|
||||
var result pq.Float32Array
|
||||
if err := result.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []float32(result), nil
|
||||
|
||||
// []float64
|
||||
case "_float8":
|
||||
var result pq.Float64Array
|
||||
if err := result.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []float64(result), nil
|
||||
|
||||
// []bool
|
||||
case "_bool":
|
||||
var result pq.BoolArray
|
||||
if err := result.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []bool(result), nil
|
||||
|
||||
// []string
|
||||
case "_varchar", "_text", "_char", "_bpchar":
|
||||
var result pq.StringArray
|
||||
if err := result.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []string(result), nil
|
||||
|
||||
// uuid.UUID
|
||||
case "uuid":
|
||||
var uuidStr string
|
||||
switch v := fieldValue.(type) {
|
||||
case []byte:
|
||||
uuidStr = string(v)
|
||||
case string:
|
||||
uuidStr = v
|
||||
default:
|
||||
uuidStr = gconv.String(fieldValue)
|
||||
}
|
||||
result, err := uuid.Parse(uuidStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
|
||||
// []uuid.UUID
|
||||
case "_uuid":
|
||||
var strArray pq.StringArray
|
||||
if err := strArray.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]uuid.UUID, len(strArray))
|
||||
for i, s := range strArray {
|
||||
parsed, err := uuid.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[i] = parsed
|
||||
}
|
||||
return result, nil
|
||||
|
||||
// []float64
|
||||
case "_numeric", "_decimal", "_money":
|
||||
var result pq.Float64Array
|
||||
if err := result.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []float64(result), nil
|
||||
|
||||
// [][]byte
|
||||
case "_bytea":
|
||||
var result pq.ByteaArray
|
||||
if err := result.Scan(fieldValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return [][]byte(result), nil
|
||||
|
||||
default:
|
||||
return d.Core.ConvertValueForLocal(ctx, fieldType, fieldValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// DoExec commits the sql string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (d *Driver) DoExec(ctx context.Context, link database.Link, sql string, args ...any) (result sql.Result, err error) {
|
||||
var (
|
||||
isUseCoreDoExec bool = false // Check whether the default method needs to be used
|
||||
primaryKey string = ""
|
||||
pkField database.TableField
|
||||
)
|
||||
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if tx := database.TXFromCtx(ctx, d.GetGroup()); tx != nil {
|
||||
// Firstly, check and retrieve transaction link from context.
|
||||
link = tx
|
||||
} else if link, err = d.MasterLink(); err != nil {
|
||||
// Or else it creates one from master node.
|
||||
return nil, err
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
// If current link is not transaction link, it checks and retrieves transaction from context.
|
||||
if tx := database.TXFromCtx(ctx, d.GetGroup()); tx != nil {
|
||||
link = tx
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it is an insert operation with primary key.
|
||||
if value := ctx.Value(internalPrimaryKeyInCtx); value != nil {
|
||||
var ok bool
|
||||
pkField, ok = value.(database.TableField)
|
||||
if !ok {
|
||||
isUseCoreDoExec = true
|
||||
}
|
||||
} else {
|
||||
isUseCoreDoExec = true
|
||||
}
|
||||
|
||||
// check if it is an insert operation.
|
||||
if !isUseCoreDoExec && pkField.Name != "" && strings.Contains(sql, "INSERT INTO") {
|
||||
primaryKey = pkField.Name
|
||||
sql += fmt.Sprintf(` RETURNING "%s"`, primaryKey)
|
||||
} else {
|
||||
// use default DoExec
|
||||
return d.Core.DoExec(ctx, link, sql, args...)
|
||||
}
|
||||
|
||||
// Only the insert operation with primary key can execute the following code
|
||||
|
||||
// Sql filtering.
|
||||
sql, args = d.FormatSqlBeforeExecuting(sql, args)
|
||||
sql, args, err = d.DoFilter(ctx, link, sql, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Link execution.
|
||||
var out database.DoCommitOutput
|
||||
out, err = d.DoCommit(ctx, database.DoCommitInput{
|
||||
Link: link,
|
||||
Sql: sql,
|
||||
Args: args,
|
||||
Stmt: nil,
|
||||
Type: database.SqlTypeQueryContext,
|
||||
IsTransaction: link.IsTransaction(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
affected := len(out.Records)
|
||||
if affected > 0 {
|
||||
if !strings.Contains(pkField.Type, "int") {
|
||||
return Result{
|
||||
affected: int64(affected),
|
||||
lastInsertId: 0,
|
||||
lastInsertIdError: gerror.NewCodef(
|
||||
gcode.CodeNotSupported,
|
||||
"LastInsertId is not supported by primary key type: %s", pkField.Type),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if out.Records[affected-1][primaryKey] != nil {
|
||||
lastInsertId := out.Records[affected-1][primaryKey].Int64()
|
||||
return Result{
|
||||
affected: int64(affected),
|
||||
lastInsertId: lastInsertId,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return Result{}, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link database.Link, sql string, args []any,
|
||||
) (newSql string, newArgs []any, err error) {
|
||||
var index int
|
||||
// Convert placeholder char '?' to string "$x".
|
||||
newSql, err = gregex.ReplaceStringFunc(`\?`, sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(`$%d`, index)
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
// Handle pgsql jsonb feature support, which contains place-holder char '?'.
|
||||
// Refer:
|
||||
// https://github.com/gogf/gf/issues/1537
|
||||
// https://www.postgresql.org/docs/12/functions-json.html
|
||||
newSql, err = gregex.ReplaceStringFuncMatch(
|
||||
`(::jsonb([^\w\d]*)\$\d)`,
|
||||
newSql,
|
||||
func(match []string) string {
|
||||
return fmt.Sprintf(`::jsonb%s?`, match[2])
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
newSql, err = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, newSql)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// Add support for pgsql INSERT OR IGNORE.
|
||||
if gstr.HasPrefix(newSql, database.InsertOperationIgnore) {
|
||||
newSql = "INSERT" + newSql[len(database.InsertOperationIgnore):] + " ON CONFLICT DO NOTHING"
|
||||
}
|
||||
|
||||
newArgs = args
|
||||
|
||||
return d.Core.DoFilter(ctx, link, newSql, newArgs)
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
// The list parameter must contain at least one record, which was previously validated.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context,
|
||||
link database.Link, table string, list database.List, option database.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case
|
||||
database.InsertOptionSave,
|
||||
database.InsertOptionReplace:
|
||||
// PostgreSQL does not support REPLACE INTO syntax, use Save (ON CONFLICT ... DO UPDATE) instead.
|
||||
// Automatically detect primary keys if OnConflict is not specified.
|
||||
if len(option.OnConflict) == 0 {
|
||||
primaryKeys, err := d.Core.GetPrimaryKeys(ctx, table)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(
|
||||
gcode.CodeInternalError,
|
||||
err,
|
||||
`failed to get primary keys for Save/Replace operation`,
|
||||
)
|
||||
}
|
||||
foundPrimaryKey := false
|
||||
for _, primaryKey := range primaryKeys {
|
||||
for dataKey := range list[0] {
|
||||
if strings.EqualFold(dataKey, primaryKey) {
|
||||
foundPrimaryKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundPrimaryKey {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundPrimaryKey {
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeMissingParameter,
|
||||
`Replace/Save operation requires conflict detection: `+
|
||||
`either specify OnConflict() columns or ensure table '%s' has a primary key in the data`,
|
||||
table,
|
||||
)
|
||||
}
|
||||
// TODO consider composite primary keys.
|
||||
option.OnConflict = primaryKeys
|
||||
}
|
||||
// Treat Replace as Save operation
|
||||
option.InsertOption = database.InsertOptionSave
|
||||
|
||||
// pgsql support InsertIgnore natively, so no need to set primary key in context.
|
||||
case database.InsertOptionIgnore, database.InsertOptionDefault:
|
||||
// Get table fields to retrieve the primary key TableField object (not just the name)
|
||||
// because DoExec needs the `TableField.Type` to determine if LastInsertId is supported.
|
||||
tableFields, err := d.GetCore().GetDB().TableFields(ctx, table)
|
||||
if err == nil {
|
||||
for _, field := range tableFields {
|
||||
if strings.EqualFold(field.Key, "pri") {
|
||||
pkField := *field
|
||||
ctx = context.WithValue(ctx, internalPrimaryKeyInCtx, pkField)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// FormatUpsert returns SQL clause of type upsert for PgSQL.
|
||||
// For example: ON CONFLICT (id) DO UPDATE SET ...
|
||||
func (d *Driver) FormatUpsert(columns []string, list database.List, option database.DoInsertOption) (string, error) {
|
||||
if len(option.OnConflict) == 0 {
|
||||
return "", gerror.NewCode(
|
||||
gcode.CodeMissingParameter, `Please specify conflict columns`,
|
||||
)
|
||||
}
|
||||
|
||||
var onDuplicateStr string
|
||||
if option.OnDuplicateStr != "" {
|
||||
onDuplicateStr = option.OnDuplicateStr
|
||||
} else if len(option.OnDuplicateMap) > 0 {
|
||||
for k, v := range option.OnDuplicateMap {
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case database.Raw, *database.Raw:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=%s",
|
||||
d.Core.QuoteWord(k),
|
||||
v,
|
||||
)
|
||||
case database.Counter, *database.Counter:
|
||||
var counter database.Counter
|
||||
switch value := v.(type) {
|
||||
case database.Counter:
|
||||
counter = value
|
||||
case *database.Counter:
|
||||
counter = *value
|
||||
}
|
||||
operator, columnVal := "+", counter.Value
|
||||
if columnVal < 0 {
|
||||
operator, columnVal = "-", -columnVal
|
||||
}
|
||||
// Note: In PostgreSQL ON CONFLICT DO UPDATE, we use EXCLUDED to reference
|
||||
// the value that was proposed for insertion. This differs from MySQL's
|
||||
// ON DUPLICATE KEY UPDATE behavior where the column name without prefix
|
||||
// references the current row's value.
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s%s%s",
|
||||
d.QuoteWord(k),
|
||||
d.QuoteWord(counter.Field),
|
||||
operator,
|
||||
gconv.String(columnVal),
|
||||
)
|
||||
default:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s",
|
||||
d.Core.QuoteWord(k),
|
||||
d.Core.QuoteWord(gconv.String(v)),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation, do not automatically update the creating time.
|
||||
if d.Core.IsSoftCreatedFieldName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s",
|
||||
d.Core.QuoteWord(column),
|
||||
d.Core.QuoteWord(column),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
conflictKeys := gstr.Join(option.OnConflict, ",")
|
||||
|
||||
return fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", conflictKeys) + onDuplicateStr, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for pgsql.
|
||||
// https://pkg.go.dev/github.com/lib/pq
|
||||
func (d *Driver) Open(config *database.ConfigNode) (db *sql.DB, err error) {
|
||||
source, err := configNodeToSource(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
underlyingDriverName := "postgres"
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`sql.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func configNodeToSource(config *database.ConfigNode) (string, error) {
|
||||
var source string
|
||||
source = fmt.Sprintf(
|
||||
"user=%s password='%s' host=%s sslmode=disable",
|
||||
config.User, config.Pass, config.Host,
|
||||
)
|
||||
if config.Port != "" {
|
||||
source = fmt.Sprintf("%s port=%s", source, config.Port)
|
||||
}
|
||||
if config.Name != "" {
|
||||
source = fmt.Sprintf("%s dbname=%s", source, config.Name)
|
||||
}
|
||||
if config.Namespace != "" {
|
||||
source = fmt.Sprintf("%s search_path=%s", source, config.Namespace)
|
||||
}
|
||||
if config.Timezone != "" {
|
||||
source = fmt.Sprintf("%s timezone=%s", source, config.Timezone)
|
||||
}
|
||||
if config.Extra != "" {
|
||||
extraMap, err := gstr.Parse(config.Extra)
|
||||
if err != nil {
|
||||
return "", gerror.WrapCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
err,
|
||||
`invalid extra configuration: %s`, config.Extra,
|
||||
)
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
source += fmt.Sprintf(` %s=%s`, k, v)
|
||||
}
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
// OrderRandomFunction returns the SQL function for random ordering.
|
||||
func (d *Driver) OrderRandomFunction() string {
|
||||
return "RANDOM()"
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import "database/sql"
|
||||
|
||||
type Result struct {
|
||||
sql.Result
|
||||
affected int64
|
||||
lastInsertId int64
|
||||
lastInsertIdError error
|
||||
}
|
||||
|
||||
func (pgr Result) RowsAffected() (int64, error) {
|
||||
return pgr.affected, nil
|
||||
}
|
||||
|
||||
func (pgr Result) LastInsertId() (int64, error) {
|
||||
return pgr.lastInsertId, pgr.lastInsertIdError
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
tableFieldsSqlTmp = `
|
||||
SELECT
|
||||
a.attname AS field,
|
||||
t.typname AS type,
|
||||
a.attnotnull AS null,
|
||||
(CASE WHEN d.contype = 'p' THEN 'pri' WHEN d.contype = 'u' THEN 'uni' ELSE '' END) AS key,
|
||||
ic.column_default AS default_value,
|
||||
b.description AS comment,
|
||||
COALESCE(character_maximum_length, numeric_precision, -1) AS length,
|
||||
numeric_scale AS scale
|
||||
FROM pg_attribute a
|
||||
LEFT JOIN pg_class c ON a.attrelid = c.oid
|
||||
LEFT JOIN pg_constraint d ON d.conrelid = c.oid AND a.attnum = d.conkey[1]
|
||||
LEFT JOIN pg_description b ON a.attrelid = b.objoid AND a.attnum = b.objsubid
|
||||
LEFT JOIN pg_type t ON a.atttypid = t.oid
|
||||
LEFT JOIN information_schema.columns ic ON ic.column_name = a.attname AND ic.table_name = c.relname
|
||||
WHERE c.oid = '%s'::regclass
|
||||
AND a.attisdropped IS FALSE
|
||||
AND a.attnum > 0
|
||||
ORDER BY a.attnum`
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
tableFieldsSqlTmp, err = database.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*database.TableField, err error) {
|
||||
var (
|
||||
result database.Result
|
||||
link database.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
// TODO duplicated `id` result?
|
||||
structureSql = fmt.Sprintf(tableFieldsSqlTmp, table)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, structureSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*database.TableField)
|
||||
var (
|
||||
index = 0
|
||||
name string
|
||||
ok bool
|
||||
existingField *database.TableField
|
||||
)
|
||||
for _, m := range result {
|
||||
name = m["field"].String()
|
||||
// Merge duplicated fields, especially for key constraints.
|
||||
// Priority: pri > uni > others
|
||||
if existingField, ok = fields[name]; ok {
|
||||
currentKey := m["key"].String()
|
||||
// Merge key information with priority: pri > uni
|
||||
if currentKey == "pri" || (currentKey == "uni" && existingField.Key != "pri") {
|
||||
existingField.Key = currentKey
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
fieldType string
|
||||
dataType = m["type"].String()
|
||||
dataLength = m["length"].Int()
|
||||
)
|
||||
if dataLength > 0 {
|
||||
fieldType = fmt.Sprintf("%s(%d)", dataType, dataLength)
|
||||
} else {
|
||||
fieldType = dataType
|
||||
}
|
||||
|
||||
fields[name] = &database.TableField{
|
||||
Index: index,
|
||||
Name: name,
|
||||
Type: fieldType,
|
||||
Null: !m["null"].Bool(),
|
||||
Key: m["key"].String(),
|
||||
Default: m["default_value"].Val(),
|
||||
Comment: m["comment"].String(),
|
||||
}
|
||||
index++
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
tablesSqlTmp = `
|
||||
SELECT
|
||||
c.relname
|
||||
FROM
|
||||
pg_class c
|
||||
INNER JOIN pg_namespace n ON
|
||||
c.relnamespace = n.oid
|
||||
WHERE
|
||||
n.nspname = '%s'
|
||||
AND c.relkind IN ('r', 'p')
|
||||
%s
|
||||
ORDER BY
|
||||
c.relname
|
||||
`
|
||||
|
||||
versionRegex = regexp.MustCompile(`PostgreSQL (\d+\.\d+)`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
tablesSqlTmp, err = database.FormatMultiLineSqlToSingle(tablesSqlTmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||
var (
|
||||
result database.Result
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetConfig().Namespace, schema...)
|
||||
)
|
||||
if usedSchema == "" {
|
||||
usedSchema = defaultSchema
|
||||
}
|
||||
// DO NOT use `usedSchema` as parameter for function `SlaveLink`.
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
useRelpartbound := ""
|
||||
if gstr.CompareVersion(d.version(ctx, link), "10") >= 0 {
|
||||
useRelpartbound = "AND c.relpartbound IS NULL"
|
||||
}
|
||||
|
||||
var query = fmt.Sprintf(
|
||||
tablesSqlTmp,
|
||||
usedSchema,
|
||||
useRelpartbound,
|
||||
)
|
||||
|
||||
query, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(query))
|
||||
result, err = d.DoSelect(ctx, link, query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// version checks and returns the database version.
|
||||
func (d *Driver) version(ctx context.Context, link database.Link) string {
|
||||
result, err := d.DoSelect(ctx, link, "SELECT version();")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if v, ok := result[0]["version"]; ok {
|
||||
matches := versionRegex.FindStringSubmatch(v.String())
|
||||
if len(matches) >= 2 {
|
||||
return matches[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE "public"."%s" (
|
||||
"one" int8[] NOT NULL,
|
||||
"two" text[][] NOT NULL
|
||||
);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE "public"."%s" (
|
||||
"text" varchar(255) COLLATE "pg_catalog"."default",
|
||||
"number" int4
|
||||
);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE "public"."%s"
|
||||
(
|
||||
"one" int8[] NOT NULL,
|
||||
"two" text[][] NOT NULL,
|
||||
"three" jsonb,
|
||||
"four" json,
|
||||
"five" jsonb
|
||||
);
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE test_enum (
|
||||
id int8 NOT NULL,
|
||||
status int2 DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT test_enum_pk PRIMARY KEY (id)
|
||||
);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
DROP TABLE IF EXISTS instance;
|
||||
CREATE TABLE instance (
|
||||
f_id SERIAL NOT NULL PRIMARY KEY,
|
||||
name varchar(255) DEFAULT ''
|
||||
);
|
||||
INSERT INTO instance VALUES (1, 'john');
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
CREATE TABLE table_a (
|
||||
id SERIAL PRIMARY KEY,
|
||||
alias varchar(255) DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO table_a VALUES (1, 'table_a_test1');
|
||||
INSERT INTO table_a VALUES (2, 'table_a_test2');
|
||||
|
||||
CREATE TABLE table_b (
|
||||
id SERIAL PRIMARY KEY,
|
||||
table_a_id integer NOT NULL,
|
||||
alias varchar(255) DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO table_b VALUES (10, 1, 'table_b_test1');
|
||||
INSERT INTO table_b VALUES (20, 2, 'table_b_test2');
|
||||
INSERT INTO table_b VALUES (30, 1, 'table_b_test3');
|
||||
INSERT INTO table_b VALUES (40, 2, 'table_b_test4');
|
||||
|
||||
CREATE TABLE table_c (
|
||||
id SERIAL PRIMARY KEY,
|
||||
table_b_id integer NOT NULL,
|
||||
alias varchar(255) DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO table_c VALUES (100, 10, 'table_c_test1');
|
||||
INSERT INTO table_c VALUES (200, 10, 'table_c_test2');
|
||||
INSERT INTO table_c VALUES (300, 20, 'table_c_test3');
|
||||
INSERT INTO table_c VALUES (400, 30, 'table_c_test4');
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name varchar(45) NOT NULL
|
||||
);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE IF NOT EXISTS %s (
|
||||
uid SERIAL PRIMARY KEY,
|
||||
address varchar(45) NOT NULL
|
||||
);
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id SERIAL PRIMARY KEY,
|
||||
uid integer NOT NULL,
|
||||
score integer NOT NULL
|
||||
);
|
||||
|
|
@ -4,18 +4,16 @@
|
|||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package sqlite implements database.Driver for SQLite database.
|
||||
// Package sqlite implements database.Driver, which supports operations for database SQLite.
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
_ "github.com/glebarez/go-sqlite"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
)
|
||||
|
||||
// Driver is the driver for SQLite database.
|
||||
// Driver is the driver for sqlite database.
|
||||
type Driver struct {
|
||||
*database.Core
|
||||
}
|
||||
|
|
@ -25,34 +23,25 @@ const (
|
|||
)
|
||||
|
||||
func init() {
|
||||
if err := database.Register("sqlite", New()); err != nil {
|
||||
if err := database.Register(`sqlite`, New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates and returns a driver that implements database.Driver for SQLite.
|
||||
// New create and returns a driver that implements database.Driver, which supports operations for SQLite.
|
||||
func New() database.Driver {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
// New creates and returns a database object for SQLite.
|
||||
// New creates and returns a database object for sqlite.
|
||||
// It implements the interface of database.Driver for extra database driver installation.
|
||||
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||
return &Driver{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetChars returns the security char for SQLite.
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for SQLite.
|
||||
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||
// For SQLite, use the Name field as the database file path
|
||||
dbName := config.Name
|
||||
if dbName == "" {
|
||||
dbName = ":memory:"
|
||||
}
|
||||
return sql.Open("sqlite", dbName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link database.Link, sql string, args []any,
|
||||
) (newSql string, newArgs []any, err error) {
|
||||
// Special insert/ignore operation for sqlite.
|
||||
switch {
|
||||
case gstr.HasPrefix(sql, database.InsertOperationIgnore):
|
||||
sql = "INSERT OR IGNORE" + sql[len(database.InsertOperationIgnore):]
|
||||
|
||||
case gstr.HasPrefix(sql, database.InsertOperationReplace):
|
||||
sql = "INSERT OR REPLACE" + sql[len(database.InsertOperationReplace):]
|
||||
}
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// FormatUpsert returns SQL clause of type upsert for SQLite.
|
||||
// For example: ON CONFLICT (id) DO UPDATE SET ...
|
||||
func (d *Driver) FormatUpsert(columns []string, list database.List, option database.DoInsertOption) (string, error) {
|
||||
if len(option.OnConflict) == 0 {
|
||||
return "", gerror.NewCode(
|
||||
gcode.CodeMissingParameter, `Please specify conflict columns`,
|
||||
)
|
||||
}
|
||||
|
||||
var onDuplicateStr string
|
||||
if option.OnDuplicateStr != "" {
|
||||
onDuplicateStr = option.OnDuplicateStr
|
||||
} else if len(option.OnDuplicateMap) > 0 {
|
||||
for k, v := range option.OnDuplicateMap {
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case database.Raw, *database.Raw:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=%s",
|
||||
d.Core.QuoteWord(k),
|
||||
v,
|
||||
)
|
||||
case database.Counter, *database.Counter:
|
||||
var counter database.Counter
|
||||
switch value := v.(type) {
|
||||
case database.Counter:
|
||||
counter = value
|
||||
case *database.Counter:
|
||||
counter = *value
|
||||
}
|
||||
operator, columnVal := "+", counter.Value
|
||||
if columnVal < 0 {
|
||||
operator, columnVal = "-", -columnVal
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s%s%s",
|
||||
d.QuoteWord(k),
|
||||
d.QuoteWord(counter.Field),
|
||||
operator,
|
||||
gconv.String(columnVal),
|
||||
)
|
||||
default:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s",
|
||||
d.Core.QuoteWord(k),
|
||||
d.Core.QuoteWord(gconv.String(v)),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation, do not automatically update the creating time.
|
||||
if d.Core.IsSoftCreatedFieldName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=EXCLUDED.%s",
|
||||
d.Core.QuoteWord(column),
|
||||
d.Core.QuoteWord(column),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
conflictKeys := gstr.Join(option.OnConflict, ",")
|
||||
|
||||
return fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", conflictKeys) + onDuplicateStr, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/encoding/gurl"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for sqlite.
|
||||
// https://github.com/glebarez/go-sqlite
|
||||
func (d *Driver) Open(config *database.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "sqlite"
|
||||
)
|
||||
source = config.Name
|
||||
// It searches the source file to locate its absolute path..
|
||||
if absolutePath, _ := gfile.Search(source); absolutePath != "" {
|
||||
source = absolutePath
|
||||
}
|
||||
// Multiple PRAGMAs can be specified, e.g.:
|
||||
// path/to/some.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)
|
||||
if config.Extra != "" {
|
||||
var (
|
||||
options string
|
||||
extraMap map[string]any
|
||||
)
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
if options != "" {
|
||||
options += "&"
|
||||
}
|
||||
options += fmt.Sprintf(`_pragma=%s(%s)`, k, gurl.Encode(gconv.String(v)))
|
||||
}
|
||||
if len(options) > 1 {
|
||||
source += "?" + options
|
||||
}
|
||||
}
|
||||
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`sql.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
// OrderRandomFunction returns the SQL function for random ordering.
|
||||
func (d *Driver) OrderRandomFunction() string {
|
||||
return "RANDOM()"
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gin-base/database"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*database.TableField, err error) {
|
||||
var (
|
||||
result database.Result
|
||||
link database.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, d.QuoteWord(table)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*database.TableField)
|
||||
for i, m := range result {
|
||||
mKey := ""
|
||||
if m["pk"].Bool() {
|
||||
mKey = "pri"
|
||||
}
|
||||
fields[m["name"].String()] = &database.TableField{
|
||||
Index: i,
|
||||
Name: m["name"].String(),
|
||||
Type: m["type"].String(),
|
||||
Key: mKey,
|
||||
Default: m["dflt_value"].Val(),
|
||||
Null: !m["notnull"].Bool(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue