Compare commits
No commits in common. "main" and "v1.0.2008" have entirely different histories.
|
|
@ -1,69 +0,0 @@
|
||||||
# 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`
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 consts
|
|
||||||
|
|
||||||
const TemplateGenDaoIndexContent = `
|
|
||||||
// =================================================================================
|
|
||||||
// This file is auto-generated by the Gin-base CLI tool. You may modify it as needed.
|
|
||||||
// =================================================================================
|
|
||||||
|
|
||||||
package {{.TplPackageName}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"{{.TplImportPrefix}}/internal"
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
// {{.TplTableNameCamelLowerCase}}Dao is the data access object for the table {{.TplTableName}}.
|
|
||||||
// You can define custom methods on it to extend its functionality as needed.
|
|
||||||
type {{.TplTableNameCamelLowerCase}}Dao struct {
|
|
||||||
*internal.{{.TplTableNameCamelCase}}Dao
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// {{.TplTableNameCamelCase}} is a globally accessible object for table {{.TplTableName}} operations.
|
|
||||||
{{.TplTableNameCamelCase}} = {{.TplTableNameCamelLowerCase}}Dao{
|
|
||||||
{{- if .TplTableSharding -}}
|
|
||||||
internal.New{{.TplTableNameCamelCase}}Dao({{.TplTableNameCamelLowerCase}}ShardingHandler),
|
|
||||||
{{- else -}}
|
|
||||||
internal.New{{.TplTableNameCamelCase}}Dao(),
|
|
||||||
{{- end -}}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
{{if .TplTableSharding -}}
|
|
||||||
// {{.TplTableNameCamelLowerCase}}ShardingHandler is the handler for sharding operations.
|
|
||||||
// You can fill this sharding handler with your custom implementation.
|
|
||||||
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: database.ShardingSchemaConfig{},
|
|
||||||
})
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
{{- end}}
|
|
||||||
|
|
||||||
// Add your custom methods and functionality below.
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const TemplateGenDaoInternalContent = `
|
|
||||||
// ==========================================================================
|
|
||||||
// Code generated and maintained by Gin-base CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}}
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
// {{.TplTableNameCamelCase}}Dao is the data access object for the table {{.TplTableName}}.
|
|
||||||
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 []database.ModelHandler // handlers for customized model modification.
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.TplTableNameCamelCase}}Columns defines and stores column names for the table {{.TplTableName}}.
|
|
||||||
type {{.TplTableNameCamelCase}}Columns struct {
|
|
||||||
{{.TplColumnDefine}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.TplTableNameCamelLowerCase}}Columns holds the columns for the table {{.TplTableName}}.
|
|
||||||
var {{.TplTableNameCamelLowerCase}}Columns = {{.TplTableNameCamelCase}}Columns{
|
|
||||||
{{.TplColumnNames}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.TplTableNameCamelCase}}Dao creates and returns a new DAO object for table data access.
|
|
||||||
func New{{.TplTableNameCamelCase}}Dao(handlers ...database.ModelHandler) *{{.TplTableNameCamelCase}}Dao {
|
|
||||||
return &{{.TplTableNameCamelCase}}Dao{
|
|
||||||
group: "{{.TplGroupName}}",
|
|
||||||
table: "{{.TplTableName}}",
|
|
||||||
columns: {{.TplTableNameCamelLowerCase}}Columns,
|
|
||||||
handlers: handlers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
|
||||||
func (dao *{{.TplTableNameCamelCase}}Dao) DB() database.DB {
|
|
||||||
return database.Database(dao.group)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table returns the table name of the current DAO.
|
|
||||||
func (dao *{{.TplTableNameCamelCase}}Dao) Table() string {
|
|
||||||
return dao.table
|
|
||||||
}
|
|
||||||
|
|
||||||
// Columns returns all column names of the current DAO.
|
|
||||||
func (dao *{{.TplTableNameCamelCase}}Dao) Columns() {{.TplTableNameCamelCase}}Columns {
|
|
||||||
return dao.columns
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group returns the database configuration group name of the current DAO.
|
|
||||||
func (dao *{{.TplTableNameCamelCase}}Dao) Group() string {
|
|
||||||
return dao.group
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) *database.Model {
|
|
||||||
model := dao.DB().Model(dao.table)
|
|
||||||
for _, handler := range dao.handlers {
|
|
||||||
model = handler(model)
|
|
||||||
}
|
|
||||||
return model.Safe().Ctx(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction wraps the transaction logic using function f.
|
|
||||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
|
||||||
// It commits the transaction and returns nil if function f returns nil.
|
|
||||||
//
|
|
||||||
// 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 database.TX) error) (err error) {
|
|
||||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 consts
|
|
||||||
|
|
||||||
const TemplateGenDaoDoContent = `
|
|
||||||
// =================================================================================
|
|
||||||
// Code generated and maintained by Gin-base CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}}
|
|
||||||
// =================================================================================
|
|
||||||
|
|
||||||
package {{.TplPackageName}}
|
|
||||||
|
|
||||||
{{.TplPackageImports}}
|
|
||||||
|
|
||||||
// {{.TplTableNameCamelCase}} is the golang structure of table {{.TplTableName}} for DAO operations like Where/Data.
|
|
||||||
{{.TplStructDefine}}
|
|
||||||
`
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 consts
|
|
||||||
|
|
||||||
const TemplateGenDaoEntityContent = `
|
|
||||||
// =================================================================================
|
|
||||||
// Code generated and maintained by Gin-base CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}}
|
|
||||||
// =================================================================================
|
|
||||||
|
|
||||||
package {{.TplPackageName}}
|
|
||||||
|
|
||||||
{{.TplPackageImports}}
|
|
||||||
|
|
||||||
// {{.TplTableNameCamelCase}} is the golang structure for table {{.TplTableName}}.
|
|
||||||
{{.TplStructDefine}}
|
|
||||||
`
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 consts
|
|
||||||
|
|
||||||
const TemplateGenTableContent = `
|
|
||||||
// =================================================================================
|
|
||||||
// This file is auto-generated by the Gin-base CLI tool. You may modify it as needed.
|
|
||||||
// =================================================================================
|
|
||||||
|
|
||||||
package {{.TplPackageName}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"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]*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 database.DB, schema ...string) error {
|
|
||||||
return db.GetCore().SetTableFields(ctx, "{{.TplTableName}}", {{.TplTableNameCamelCase}}, schema...)
|
|
||||||
}
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
# 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`
|
|
||||||
|
|
@ -1,491 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
"github.com/olekukonko/tablewriter/renderer"
|
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
|
||||||
"golang.org/x/mod/modfile"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
|
||||||
"github.com/gogf/gf/v2/container/garray"
|
|
||||||
"github.com/gogf/gf/v2/container/gset"
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
|
||||||
"github.com/gogf/gf/v2/os/gproc"
|
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
|
||||||
"github.com/gogf/gf/v2/os/gview"
|
|
||||||
"github.com/gogf/gf/v2/text/gregex"
|
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/mlog"
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
CGenDao struct{}
|
|
||||||
CGenDaoInput struct {
|
|
||||||
g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
|
|
||||||
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
|
|
||||||
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
|
|
||||||
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
|
|
||||||
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
|
|
||||||
ShardingPattern []string `name:"shardingPattern" short:"sp" brief:"{CGenDaoBriefShardingPattern}"`
|
|
||||||
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
|
|
||||||
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
|
|
||||||
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
|
|
||||||
RemoveFieldPrefix string `name:"removeFieldPrefix" short:"rf" brief:"{CGenDaoBriefRemoveFieldPrefix}"`
|
|
||||||
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
|
|
||||||
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
|
|
||||||
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
|
|
||||||
TablePath string `name:"tablePath" short:"tp" brief:"{CGenDaoBriefTablePath}" d:"table"`
|
|
||||||
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
|
|
||||||
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
|
|
||||||
TplDaoTablePath string `name:"tplDaoTablePath" short:"t0" brief:"{CGenDaoBriefTplDaoTablePath}"`
|
|
||||||
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
|
|
||||||
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
|
|
||||||
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
|
|
||||||
TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
|
|
||||||
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
|
|
||||||
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
|
|
||||||
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
|
|
||||||
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
|
|
||||||
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
|
|
||||||
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
|
|
||||||
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
|
|
||||||
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
|
|
||||||
GenTable bool `name:"genTable" short:"gt" brief:"{CGenDaoBriefGenTable}" orphan:"true"`
|
|
||||||
|
|
||||||
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
|
|
||||||
FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenDaoBriefFieldMapping}" orphan:"true"`
|
|
||||||
|
|
||||||
// internal usage purpose.
|
|
||||||
genItems *CGenDaoInternalGenItems
|
|
||||||
}
|
|
||||||
CGenDaoOutput struct{}
|
|
||||||
|
|
||||||
CGenDaoInternalInput struct {
|
|
||||||
CGenDaoInput
|
|
||||||
DB database.DB
|
|
||||||
TableNames []string
|
|
||||||
NewTableNames []string
|
|
||||||
ShardingTableSet *gset.StrSet
|
|
||||||
}
|
|
||||||
DBTableFieldName = string
|
|
||||||
DBFieldTypeName = string
|
|
||||||
CustomAttributeType struct {
|
|
||||||
Type string `brief:"custom attribute type name"`
|
|
||||||
Import string `brief:"custom import for this type"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
createdAt = gtime.Now()
|
|
||||||
tplView = gview.New()
|
|
||||||
defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{
|
|
||||||
"decimal": {
|
|
||||||
Type: "float64",
|
|
||||||
},
|
|
||||||
"money": {
|
|
||||||
Type: "float64",
|
|
||||||
},
|
|
||||||
"numeric": {
|
|
||||||
Type: "float64",
|
|
||||||
},
|
|
||||||
"smallmoney": {
|
|
||||||
Type: "float64",
|
|
||||||
},
|
|
||||||
"uuid": {
|
|
||||||
Type: "uuid.UUID",
|
|
||||||
Import: "github.com/google/uuid",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// tablewriter Options
|
|
||||||
twRenderer = tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
|
|
||||||
Borders: tw.Border{Top: tw.Off, Bottom: tw.Off, Left: tw.Off, Right: tw.Off},
|
|
||||||
Settings: tw.Settings{
|
|
||||||
Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off},
|
|
||||||
},
|
|
||||||
Symbols: tw.NewSymbols(tw.StyleASCII),
|
|
||||||
}))
|
|
||||||
twConfig = tablewriter.WithConfig(tablewriter.Config{
|
|
||||||
Row: tw.CellConfig{
|
|
||||||
Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) {
|
|
||||||
in.genItems = newCGenDaoInternalGenItems()
|
|
||||||
if in.Link != "" {
|
|
||||||
doGenDaoForArray(ctx, -1, in)
|
|
||||||
} else if g.Cfg().Available(ctx) {
|
|
||||||
v := g.Cfg().MustGet(ctx, CGenDaoConfig)
|
|
||||||
if v.IsSlice() {
|
|
||||||
for i := 0; i < len(v.Interfaces()); i++ {
|
|
||||||
doGenDaoForArray(ctx, i, in)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doGenDaoForArray(ctx, -1, in)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doGenDaoForArray(ctx, -1, in)
|
|
||||||
}
|
|
||||||
doClear(in.genItems)
|
|
||||||
mlog.Print("done!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// doGenDaoForArray implements the "gen dao" command for configuration array.
|
|
||||||
func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
db database.DB
|
|
||||||
)
|
|
||||||
if index >= 0 {
|
|
||||||
err = g.Cfg().MustGet(
|
|
||||||
ctx,
|
|
||||||
fmt.Sprintf(`%s.%d`, CGenDaoConfig, index),
|
|
||||||
).Scan(&in)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenDaoConfig, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dirRealPath := gfile.RealPath(in.Path); dirRealPath == "" {
|
|
||||||
mlog.Fatalf(`path "%s" does not exist`, in.Path)
|
|
||||||
}
|
|
||||||
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
|
|
||||||
|
|
||||||
// It uses user passed database configuration.
|
|
||||||
if in.Link != "" {
|
|
||||||
var tempGroup = gtime.TimestampNanoStr()
|
|
||||||
err = database.AddConfigNode(tempGroup, database.ConfigNode{
|
|
||||||
Link: in.Link,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf(`database configuration failed: %+v`, err)
|
|
||||||
}
|
|
||||||
if db, err = database.Instance(tempGroup); err != nil {
|
|
||||||
mlog.Fatalf(`database initialization failed: %+v`, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
db = database.Database(in.Group)
|
|
||||||
}
|
|
||||||
if db == nil {
|
|
||||||
mlog.Fatal(`database initialization failed, may be invalid database configuration`)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tableNames []string
|
|
||||||
if in.Tables != "" {
|
|
||||||
inputTables := gstr.SplitAndTrim(in.Tables, ",")
|
|
||||||
// Check if any table pattern contains wildcard characters.
|
|
||||||
// https://github.com/gogf/gf/issues/4629
|
|
||||||
var hasPattern bool
|
|
||||||
for _, t := range inputTables {
|
|
||||||
if containsWildcard(t) {
|
|
||||||
hasPattern = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasPattern {
|
|
||||||
// Fetch all tables first, then filter by patterns.
|
|
||||||
allTables, err := db.Tables(context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("fetching tables failed: %+v", err)
|
|
||||||
}
|
|
||||||
tableNames = filterTablesByPatterns(allTables, inputTables)
|
|
||||||
} else {
|
|
||||||
// Use exact table names as before.
|
|
||||||
tableNames = inputTables
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tableNames, err = db.Tables(context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("fetching tables failed: %+v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Table excluding.
|
|
||||||
if in.TablesEx != "" {
|
|
||||||
array := garray.NewStrArrayFrom(tableNames)
|
|
||||||
for _, p := range gstr.SplitAndTrim(in.TablesEx, ",") {
|
|
||||||
if containsWildcard(p) {
|
|
||||||
// Use exact match with ^ and $ anchors for consistency with tables pattern.
|
|
||||||
regPattern := "^" + patternToRegex(p) + "$"
|
|
||||||
for _, v := range array.Clone().Slice() {
|
|
||||||
if gregex.IsMatchString(regPattern, v) {
|
|
||||||
array.RemoveValue(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
array.RemoveValue(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tableNames = array.Slice()
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge default typeMapping to input typeMapping.
|
|
||||||
if in.TypeMapping == nil {
|
|
||||||
in.TypeMapping = defaultTypeMapping
|
|
||||||
} else {
|
|
||||||
for key, typeMapping := range defaultTypeMapping {
|
|
||||||
if _, ok := in.TypeMapping[key]; !ok {
|
|
||||||
in.TypeMapping[key] = typeMapping
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generating dao & model go files one by one according to given table name.
|
|
||||||
var (
|
|
||||||
newTableNames = make([]string, len(tableNames))
|
|
||||||
shardingNewTableSet = gset.NewStrSet()
|
|
||||||
)
|
|
||||||
// Sort sharding patterns by length descending, so that longer (more specific) patterns
|
|
||||||
// are matched first. This prevents shorter patterns like "a_?" from incorrectly matching
|
|
||||||
// tables that should match longer patterns like "a_b_?" or "a_c_?".
|
|
||||||
// https://github.com/gogf/gf/issues/4603
|
|
||||||
sortedShardingPatterns := make([]string, len(in.ShardingPattern))
|
|
||||||
copy(sortedShardingPatterns, in.ShardingPattern)
|
|
||||||
sort.Slice(sortedShardingPatterns, func(i, j int) bool {
|
|
||||||
return len(sortedShardingPatterns[i]) > len(sortedShardingPatterns[j])
|
|
||||||
})
|
|
||||||
for i, tableName := range tableNames {
|
|
||||||
newTableName := tableName
|
|
||||||
for _, v := range removePrefixArray {
|
|
||||||
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
|
|
||||||
}
|
|
||||||
if len(sortedShardingPatterns) > 0 {
|
|
||||||
for _, pattern := range sortedShardingPatterns {
|
|
||||||
var (
|
|
||||||
match []string
|
|
||||||
regPattern = gstr.Replace(pattern, "?", `(.+)`)
|
|
||||||
)
|
|
||||||
match, err = gregex.MatchString(regPattern, newTableName)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf(`invalid sharding pattern "%s": %+v`, pattern, err)
|
|
||||||
}
|
|
||||||
if len(match) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newTableName = gstr.Replace(pattern, "?", "")
|
|
||||||
newTableName = gstr.Trim(newTableName, `_.-`)
|
|
||||||
if shardingNewTableSet.Contains(newTableName) {
|
|
||||||
tableNames[i] = ""
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Add prefix to sharding table name, if not, the isSharding check would not match.
|
|
||||||
shardingNewTableSet.Add(in.Prefix + newTableName)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newTableName = in.Prefix + newTableName
|
|
||||||
if tableNames[i] != "" {
|
|
||||||
// If shardingNewTableSet contains newTableName (tableName is empty), it should not be added to tableNames, make it empty and filter later.
|
|
||||||
newTableNames[i] = newTableName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tableNames = garray.NewStrArrayFrom(tableNames).FilterEmpty().Slice()
|
|
||||||
newTableNames = garray.NewStrArrayFrom(newTableNames).FilterEmpty().Slice() // Filter empty table names. make sure that newTableNames and tableNames have the same length.
|
|
||||||
in.genItems.Scale()
|
|
||||||
|
|
||||||
// Dao: index and internal.
|
|
||||||
generateDao(ctx, CGenDaoInternalInput{
|
|
||||||
CGenDaoInput: in,
|
|
||||||
DB: db,
|
|
||||||
TableNames: tableNames,
|
|
||||||
NewTableNames: newTableNames,
|
|
||||||
ShardingTableSet: shardingNewTableSet,
|
|
||||||
})
|
|
||||||
// Table: table fields.
|
|
||||||
generateTable(ctx, CGenDaoInternalInput{
|
|
||||||
CGenDaoInput: in,
|
|
||||||
DB: db,
|
|
||||||
TableNames: tableNames,
|
|
||||||
NewTableNames: newTableNames,
|
|
||||||
ShardingTableSet: shardingNewTableSet,
|
|
||||||
})
|
|
||||||
// Do.
|
|
||||||
generateDo(ctx, CGenDaoInternalInput{
|
|
||||||
CGenDaoInput: in,
|
|
||||||
DB: db,
|
|
||||||
TableNames: tableNames,
|
|
||||||
NewTableNames: newTableNames,
|
|
||||||
})
|
|
||||||
// Entity.
|
|
||||||
generateEntity(ctx, CGenDaoInternalInput{
|
|
||||||
CGenDaoInput: in,
|
|
||||||
DB: db,
|
|
||||||
TableNames: tableNames,
|
|
||||||
NewTableNames: newTableNames,
|
|
||||||
})
|
|
||||||
|
|
||||||
in.genItems.SetClear(in.Clear)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string {
|
|
||||||
var packageImportsArray = garray.NewStrArray()
|
|
||||||
if isDo {
|
|
||||||
packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time package recognition.
|
|
||||||
if strings.Contains(source, "gtime.Time") {
|
|
||||||
packageImportsArray.Append(`"github.com/gogf/gf/v2/os/gtime"`)
|
|
||||||
} else if strings.Contains(source, "time.Time") {
|
|
||||||
packageImportsArray.Append(`"time"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Json type.
|
|
||||||
if strings.Contains(source, "gjson.Json") {
|
|
||||||
packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check and update imports in go.mod
|
|
||||||
if len(appendImports) > 0 {
|
|
||||||
goModPath := utils.GetModPath()
|
|
||||||
if goModPath == "" {
|
|
||||||
mlog.Fatal("go.mod not found in current project")
|
|
||||||
}
|
|
||||||
mod, err := modfile.Parse(goModPath, gfile.GetBytes(goModPath), nil)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("parse go.mod failed: %+v", err)
|
|
||||||
}
|
|
||||||
for _, appendImport := range appendImports {
|
|
||||||
found := false
|
|
||||||
for _, require := range mod.Require {
|
|
||||||
if gstr.Contains(appendImport, require.Mod.Path) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
if err = gproc.ShellRun(ctx, `go get `+appendImport); err != nil {
|
|
||||||
mlog.Fatalf(`%+v`, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packageImportsArray.Append(fmt.Sprintf(`"%s"`, appendImport))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate and write content to golang file.
|
|
||||||
packageImportsStr := ""
|
|
||||||
if packageImportsArray.Len() > 0 {
|
|
||||||
packageImportsStr = fmt.Sprintf("import(\n%s\n)", packageImportsArray.Join("\n"))
|
|
||||||
}
|
|
||||||
return packageImportsStr
|
|
||||||
}
|
|
||||||
|
|
||||||
func assignDefaultVar(view *gview.View, in CGenDaoInternalInput) {
|
|
||||||
var (
|
|
||||||
tplCreatedAtDatetimeStr string
|
|
||||||
tplDatetimeStr = createdAt.String()
|
|
||||||
)
|
|
||||||
if in.WithTime {
|
|
||||||
tplCreatedAtDatetimeStr = fmt.Sprintf(`Created at %s`, tplDatetimeStr)
|
|
||||||
}
|
|
||||||
view.Assigns(g.Map{
|
|
||||||
tplVarDatetimeStr: tplDatetimeStr,
|
|
||||||
tplVarCreatedAtDatetimeStr: tplCreatedAtDatetimeStr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortFieldKeyForDao(fieldMap map[string]*database.TableField) []string {
|
|
||||||
names := make(map[int]string)
|
|
||||||
for _, field := range fieldMap {
|
|
||||||
names[field.Index] = field.Name
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
i = 0
|
|
||||||
j = 0
|
|
||||||
result = make([]string, len(names))
|
|
||||||
)
|
|
||||||
for {
|
|
||||||
if len(names) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if val, ok := names[i]; ok {
|
|
||||||
result[j] = val
|
|
||||||
j++
|
|
||||||
delete(names, i)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTemplateFromPathOrDefault(filePath string, def string) string {
|
|
||||||
if filePath != "" {
|
|
||||||
if contents := gfile.GetContents(filePath); contents != "" {
|
|
||||||
return contents
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
|
|
||||||
// containsWildcard checks if the pattern contains wildcard characters (* or ?).
|
|
||||||
func containsWildcard(pattern string) bool {
|
|
||||||
return gstr.Contains(pattern, "*") || gstr.Contains(pattern, "?")
|
|
||||||
}
|
|
||||||
|
|
||||||
// patternToRegex converts a wildcard pattern to a regex pattern.
|
|
||||||
// Wildcard characters: * matches any characters, ? matches single character.
|
|
||||||
func patternToRegex(pattern string) string {
|
|
||||||
pattern = gstr.ReplaceByMap(pattern, map[string]string{
|
|
||||||
"\r": "",
|
|
||||||
"\n": "",
|
|
||||||
})
|
|
||||||
pattern = gstr.ReplaceByMap(pattern, map[string]string{
|
|
||||||
"*": "\r",
|
|
||||||
"?": "\n",
|
|
||||||
})
|
|
||||||
pattern = gregex.Quote(pattern)
|
|
||||||
pattern = gstr.ReplaceByMap(pattern, map[string]string{
|
|
||||||
"\r": ".*",
|
|
||||||
"\n": ".",
|
|
||||||
})
|
|
||||||
return pattern
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterTablesByPatterns filters tables by given patterns.
|
|
||||||
// Patterns support wildcard characters: * matches any characters, ? matches single character.
|
|
||||||
// https://github.com/gogf/gf/issues/4629
|
|
||||||
func filterTablesByPatterns(allTables []string, patterns []string) []string {
|
|
||||||
var result []string
|
|
||||||
matched := make(map[string]bool)
|
|
||||||
allTablesSet := make(map[string]bool)
|
|
||||||
for _, t := range allTables {
|
|
||||||
allTablesSet[t] = true
|
|
||||||
}
|
|
||||||
for _, p := range patterns {
|
|
||||||
if containsWildcard(p) {
|
|
||||||
regPattern := "^" + patternToRegex(p) + "$"
|
|
||||||
for _, table := range allTables {
|
|
||||||
if !matched[table] && gregex.IsMatchString(regPattern, table) {
|
|
||||||
result = append(result, table)
|
|
||||||
matched[table] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Exact table name, use direct string comparison.
|
|
||||||
if !allTablesSet[p] {
|
|
||||||
mlog.Printf(`table "%s" does not exist, skipped`, p)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !matched[p] {
|
|
||||||
result = append(result, p)
|
|
||||||
matched[p] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/mlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func doClear(items *CGenDaoInternalGenItems) {
|
|
||||||
var allGeneratedFilePaths = make([]string, 0)
|
|
||||||
for _, item := range items.Items {
|
|
||||||
allGeneratedFilePaths = append(allGeneratedFilePaths, item.GeneratedFilePaths...)
|
|
||||||
}
|
|
||||||
for i, v := range allGeneratedFilePaths {
|
|
||||||
allGeneratedFilePaths[i] = gfile.RealPath(v)
|
|
||||||
}
|
|
||||||
for _, item := range items.Items {
|
|
||||||
if !item.Clear {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
doClearItem(item, allGeneratedFilePaths)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func doClearItem(item CGenDaoInternalGenItem, allGeneratedFilePaths []string) {
|
|
||||||
var generatedFilePaths = make([]string, 0)
|
|
||||||
for _, dirPath := range item.StorageDirPaths {
|
|
||||||
filePaths, err := gfile.ScanDirFile(dirPath, "*.go", true)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatal(err)
|
|
||||||
}
|
|
||||||
generatedFilePaths = append(generatedFilePaths, filePaths...)
|
|
||||||
}
|
|
||||||
for _, filePath := range generatedFilePaths {
|
|
||||||
if !gstr.InArray(allGeneratedFilePaths, filePath) {
|
|
||||||
if err := gfile.RemoveFile(filePath); err != nil {
|
|
||||||
mlog.Print(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,260 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/consts"
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
|
||||||
"github.com/gogf/gf/v2/os/gview"
|
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/mlog"
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generateDao(ctx context.Context, in CGenDaoInternalInput) {
|
|
||||||
var (
|
|
||||||
dirPathDao = gfile.Join(in.Path, in.DaoPath)
|
|
||||||
dirPathDaoInternal = gfile.Join(dirPathDao, "internal")
|
|
||||||
)
|
|
||||||
in.genItems.AppendDirPath(dirPathDao)
|
|
||||||
for i := 0; i < len(in.TableNames); i++ {
|
|
||||||
var (
|
|
||||||
realTableName = in.TableNames[i]
|
|
||||||
newTableName = in.NewTableNames[i]
|
|
||||||
)
|
|
||||||
generateDaoSingle(ctx, generateDaoSingleInput{
|
|
||||||
CGenDaoInternalInput: in,
|
|
||||||
TableName: realTableName,
|
|
||||||
NewTableName: newTableName,
|
|
||||||
DirPathDao: dirPathDao,
|
|
||||||
DirPathDaoInternal: dirPathDaoInternal,
|
|
||||||
IsSharding: in.ShardingTableSet.Contains(newTableName),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type generateDaoSingleInput struct {
|
|
||||||
CGenDaoInternalInput
|
|
||||||
// TableName specifies the table name of the table.
|
|
||||||
TableName string
|
|
||||||
// NewTableName specifies the prefix-stripped or custom edited name of the table.
|
|
||||||
NewTableName string
|
|
||||||
DirPathDao string
|
|
||||||
DirPathDaoInternal string
|
|
||||||
IsSharding bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateDaoSingle generates the dao and model content of given table.
|
|
||||||
func generateDaoSingle(ctx context.Context, in generateDaoSingleInput) {
|
|
||||||
// Generating table data preparing.
|
|
||||||
fieldMap, err := in.DB.TableFields(ctx, in.TableName)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
tableNameCamelCase = formatFieldName(in.NewTableName, FieldNameCaseCamel)
|
|
||||||
tableNameCamelLowerCase = formatFieldName(in.NewTableName, FieldNameCaseCamelLower)
|
|
||||||
tableNameSnakeCase = gstr.CaseSnake(in.NewTableName)
|
|
||||||
importPrefix = in.ImportPrefix
|
|
||||||
)
|
|
||||||
if importPrefix == "" {
|
|
||||||
importPrefix = utils.GetImportPath(gfile.Join(in.Path, in.DaoPath))
|
|
||||||
} else {
|
|
||||||
importPrefix = gstr.Join(g.SliceStr{importPrefix, in.DaoPath}, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := gstr.Trim(tableNameSnakeCase, "-_.")
|
|
||||||
if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" {
|
|
||||||
// Add suffix to avoid the table name which contains "_test",
|
|
||||||
// which would make the go file a testing file.
|
|
||||||
fileName += "_table"
|
|
||||||
}
|
|
||||||
|
|
||||||
// dao - index
|
|
||||||
generateDaoIndex(generateDaoIndexInput{
|
|
||||||
generateDaoSingleInput: in,
|
|
||||||
TableNameCamelCase: tableNameCamelCase,
|
|
||||||
TableNameCamelLowerCase: tableNameCamelLowerCase,
|
|
||||||
ImportPrefix: importPrefix,
|
|
||||||
FileName: fileName,
|
|
||||||
})
|
|
||||||
|
|
||||||
// dao - internal
|
|
||||||
generateDaoInternal(generateDaoInternalInput{
|
|
||||||
generateDaoSingleInput: in,
|
|
||||||
TableNameCamelCase: tableNameCamelCase,
|
|
||||||
TableNameCamelLowerCase: tableNameCamelLowerCase,
|
|
||||||
ImportPrefix: importPrefix,
|
|
||||||
FileName: fileName,
|
|
||||||
FieldMap: fieldMap,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type generateDaoIndexInput struct {
|
|
||||||
generateDaoSingleInput
|
|
||||||
TableNameCamelCase string
|
|
||||||
TableNameCamelLowerCase string
|
|
||||||
ImportPrefix string
|
|
||||||
FileName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateDaoIndex(in generateDaoIndexInput) {
|
|
||||||
path := filepath.FromSlash(gfile.Join(in.DirPathDao, in.FileName+".go"))
|
|
||||||
// It should add path to result slice whenever it would generate the path file or not.
|
|
||||||
in.genItems.AppendGeneratedFilePath(path)
|
|
||||||
if in.OverwriteDao || !gfile.Exists(path) {
|
|
||||||
var (
|
|
||||||
ctx = context.Background()
|
|
||||||
tplContent = getTemplateFromPathOrDefault(
|
|
||||||
in.TplDaoIndexPath, consts.TemplateGenDaoIndexContent,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tplView.ClearAssigns()
|
|
||||||
tplView.Assigns(gview.Params{
|
|
||||||
tplVarTableSharding: in.IsSharding,
|
|
||||||
tplVarTableShardingPrefix: in.NewTableName + "_",
|
|
||||||
tplVarImportPrefix: in.ImportPrefix,
|
|
||||||
tplVarTableName: in.TableName,
|
|
||||||
tplVarTableNameCamelCase: in.TableNameCamelCase,
|
|
||||||
tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase,
|
|
||||||
tplVarPackageName: filepath.Base(in.DaoPath),
|
|
||||||
})
|
|
||||||
indexContent, err := tplView.ParseContent(ctx, tplContent)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("parsing template content failed: %v", err)
|
|
||||||
}
|
|
||||||
if err = gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
|
|
||||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
|
||||||
} else {
|
|
||||||
utils.GoFmt(path)
|
|
||||||
mlog.Print("generated:", gfile.RealPath(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type generateDaoInternalInput struct {
|
|
||||||
generateDaoSingleInput
|
|
||||||
TableNameCamelCase string
|
|
||||||
TableNameCamelLowerCase string
|
|
||||||
ImportPrefix string
|
|
||||||
FileName string
|
|
||||||
FieldMap map[string]*database.TableField
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateDaoInternal(in generateDaoInternalInput) {
|
|
||||||
var (
|
|
||||||
ctx = context.Background()
|
|
||||||
removeFieldPrefixArray = gstr.SplitAndTrim(in.RemoveFieldPrefix, ",")
|
|
||||||
tplContent = getTemplateFromPathOrDefault(
|
|
||||||
in.TplDaoInternalPath, consts.TemplateGenDaoInternalContent,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tplView.ClearAssigns()
|
|
||||||
tplView.Assigns(gview.Params{
|
|
||||||
tplVarImportPrefix: in.ImportPrefix,
|
|
||||||
tplVarTableName: in.TableName,
|
|
||||||
tplVarGroupName: in.Group,
|
|
||||||
tplVarTableNameCamelCase: in.TableNameCamelCase,
|
|
||||||
tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase,
|
|
||||||
tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(in.FieldMap, removeFieldPrefixArray)),
|
|
||||||
tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(in.FieldMap, removeFieldPrefixArray)),
|
|
||||||
})
|
|
||||||
assignDefaultVar(tplView, in.CGenDaoInternalInput)
|
|
||||||
modelContent, err := tplView.ParseContent(ctx, tplContent)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("parsing template content failed: %v", err)
|
|
||||||
}
|
|
||||||
path := filepath.FromSlash(gfile.Join(in.DirPathDaoInternal, in.FileName+".go"))
|
|
||||||
in.genItems.AppendGeneratedFilePath(path)
|
|
||||||
if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil {
|
|
||||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
|
||||||
} else {
|
|
||||||
utils.GoFmt(path)
|
|
||||||
mlog.Print("generated:", gfile.RealPath(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateColumnNamesForDao generates and returns the column names assignment content of column struct
|
|
||||||
// for specified table.
|
|
||||||
func generateColumnNamesForDao(fieldMap map[string]*database.TableField, removeFieldPrefixArray []string) string {
|
|
||||||
var (
|
|
||||||
buffer = bytes.NewBuffer(nil)
|
|
||||||
array = make([][]string, len(fieldMap))
|
|
||||||
names = sortFieldKeyForDao(fieldMap)
|
|
||||||
)
|
|
||||||
|
|
||||||
for index, name := range names {
|
|
||||||
field := fieldMap[name]
|
|
||||||
|
|
||||||
newFiledName := field.Name
|
|
||||||
for _, v := range removeFieldPrefixArray {
|
|
||||||
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
array[index] = []string{
|
|
||||||
" #" + formatFieldName(newFiledName, FieldNameCaseCamel) + ":",
|
|
||||||
fmt.Sprintf(` #"%s",`, field.Name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table := tablewriter.NewTable(buffer, twRenderer, twConfig)
|
|
||||||
table.Bulk(array)
|
|
||||||
table.Render()
|
|
||||||
namesContent := buffer.String()
|
|
||||||
// Let's do this hack of table writer for indent!
|
|
||||||
namesContent = gstr.Replace(namesContent, " #", "")
|
|
||||||
buffer.Reset()
|
|
||||||
buffer.WriteString(namesContent)
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateColumnDefinitionForDao generates and returns the column names definition for specified table.
|
|
||||||
func generateColumnDefinitionForDao(fieldMap map[string]*database.TableField, removeFieldPrefixArray []string) string {
|
|
||||||
var (
|
|
||||||
buffer = bytes.NewBuffer(nil)
|
|
||||||
array = make([][]string, len(fieldMap))
|
|
||||||
names = sortFieldKeyForDao(fieldMap)
|
|
||||||
)
|
|
||||||
|
|
||||||
for index, name := range names {
|
|
||||||
var (
|
|
||||||
field = fieldMap[name]
|
|
||||||
comment = gstr.Trim(gstr.ReplaceByArray(field.Comment, g.SliceStr{
|
|
||||||
"\n", " ",
|
|
||||||
"\r", " ",
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
newFiledName := field.Name
|
|
||||||
for _, v := range removeFieldPrefixArray {
|
|
||||||
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
|
|
||||||
}
|
|
||||||
array[index] = []string{
|
|
||||||
" #" + formatFieldName(newFiledName, FieldNameCaseCamel),
|
|
||||||
" # " + "string",
|
|
||||||
" #" + fmt.Sprintf(`// %s`, comment),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table := tablewriter.NewTable(buffer, twRenderer, twConfig)
|
|
||||||
table.Bulk(array)
|
|
||||||
table.Render()
|
|
||||||
defineContent := buffer.String()
|
|
||||||
// Let's do this hack of table writer for indent!
|
|
||||||
defineContent = gstr.Replace(defineContent, " #", "")
|
|
||||||
buffer.Reset()
|
|
||||||
buffer.WriteString(defineContent)
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/consts"
|
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
|
||||||
"github.com/gogf/gf/v2/os/gview"
|
|
||||||
"github.com/gogf/gf/v2/text/gregex"
|
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/mlog"
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generateDo(ctx context.Context, in CGenDaoInternalInput) {
|
|
||||||
var dirPathDo = filepath.FromSlash(gfile.Join(in.Path, in.DoPath))
|
|
||||||
in.genItems.AppendDirPath(dirPathDo)
|
|
||||||
in.NoJsonTag = true
|
|
||||||
in.DescriptionTag = false
|
|
||||||
in.NoModelComment = false
|
|
||||||
// Model content.
|
|
||||||
for i, tableName := range in.TableNames {
|
|
||||||
fieldMap, err := in.DB.TableFields(ctx, tableName)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
newTableName = in.NewTableNames[i]
|
|
||||||
doFilePath = gfile.Join(dirPathDo, gstr.CaseSnake(newTableName)+".go")
|
|
||||||
structDefinition, _ = generateStructDefinition(ctx, generateStructDefinitionInput{
|
|
||||||
CGenDaoInternalInput: in,
|
|
||||||
TableName: tableName,
|
|
||||||
StructName: formatFieldName(newTableName, FieldNameCaseCamel),
|
|
||||||
FieldMap: fieldMap,
|
|
||||||
IsDo: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
// replace all types to any.
|
|
||||||
structDefinition, _ = gregex.ReplaceStringFuncMatch(
|
|
||||||
"([A-Z]\\w*?)\\s+([\\w\\*\\.]+?)\\s+(//)",
|
|
||||||
structDefinition,
|
|
||||||
func(match []string) string {
|
|
||||||
// If the type is already a pointer/slice/map, it does nothing.
|
|
||||||
if !gstr.HasPrefix(match[2], "*") && !gstr.HasPrefix(match[2], "[]") && !gstr.HasPrefix(match[2], "map") {
|
|
||||||
return fmt.Sprintf(`%s any %s`, match[1], match[3])
|
|
||||||
}
|
|
||||||
return match[0]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
modelContent := generateDoContent(
|
|
||||||
ctx,
|
|
||||||
in,
|
|
||||||
tableName,
|
|
||||||
formatFieldName(newTableName, FieldNameCaseCamel),
|
|
||||||
structDefinition,
|
|
||||||
)
|
|
||||||
in.genItems.AppendGeneratedFilePath(doFilePath)
|
|
||||||
err = gfile.PutContents(doFilePath, strings.TrimSpace(modelContent))
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf(`writing content to "%s" failed: %v`, doFilePath, err)
|
|
||||||
} else {
|
|
||||||
utils.GoFmt(doFilePath)
|
|
||||||
mlog.Print("generated:", gfile.RealPath(doFilePath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateDoContent(
|
|
||||||
ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string,
|
|
||||||
) string {
|
|
||||||
var (
|
|
||||||
tplContent = getTemplateFromPathOrDefault(
|
|
||||||
in.TplDaoDoPath, consts.TemplateGenDaoDoContent,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tplView.ClearAssigns()
|
|
||||||
tplView.Assigns(gview.Params{
|
|
||||||
tplVarTableName: tableName,
|
|
||||||
tplVarPackageImports: getImportPartContent(ctx, structDefine, true, nil),
|
|
||||||
tplVarTableNameCamelCase: tableNameCamelCase,
|
|
||||||
tplVarStructDefine: structDefine,
|
|
||||||
tplVarPackageName: filepath.Base(in.DoPath),
|
|
||||||
})
|
|
||||||
assignDefaultVar(tplView, in)
|
|
||||||
doContent, err := tplView.ParseContent(ctx, tplContent)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("parsing template content failed: %v", err)
|
|
||||||
}
|
|
||||||
return doContent
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/consts"
|
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
|
||||||
"github.com/gogf/gf/v2/os/gview"
|
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/mlog"
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
|
|
||||||
var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
|
|
||||||
in.genItems.AppendDirPath(dirPathEntity)
|
|
||||||
// Model content.
|
|
||||||
for i, tableName := range in.TableNames {
|
|
||||||
fieldMap, err := in.DB.TableFields(ctx, tableName)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
newTableName = in.NewTableNames[i]
|
|
||||||
entityFilePath = filepath.FromSlash(gfile.Join(dirPathEntity, gstr.CaseSnake(newTableName)+".go"))
|
|
||||||
structDefinition, appendImports = generateStructDefinition(ctx, generateStructDefinitionInput{
|
|
||||||
CGenDaoInternalInput: in,
|
|
||||||
TableName: tableName,
|
|
||||||
StructName: formatFieldName(newTableName, FieldNameCaseCamel),
|
|
||||||
FieldMap: fieldMap,
|
|
||||||
IsDo: false,
|
|
||||||
})
|
|
||||||
entityContent = generateEntityContent(
|
|
||||||
ctx,
|
|
||||||
in,
|
|
||||||
newTableName,
|
|
||||||
formatFieldName(newTableName, FieldNameCaseCamel),
|
|
||||||
structDefinition,
|
|
||||||
appendImports,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
in.genItems.AppendGeneratedFilePath(entityFilePath)
|
|
||||||
err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent))
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err)
|
|
||||||
} else {
|
|
||||||
utils.GoFmt(entityFilePath)
|
|
||||||
mlog.Print("generated:", gfile.RealPath(entityFilePath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateEntityContent(
|
|
||||||
ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string, appendImports []string,
|
|
||||||
) string {
|
|
||||||
var (
|
|
||||||
tplContent = getTemplateFromPathOrDefault(
|
|
||||||
in.TplDaoEntityPath, consts.TemplateGenDaoEntityContent,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tplView.ClearAssigns()
|
|
||||||
tplView.Assigns(gview.Params{
|
|
||||||
tplVarTableName: tableName,
|
|
||||||
tplVarPackageImports: getImportPartContent(ctx, structDefine, false, appendImports),
|
|
||||||
tplVarTableNameCamelCase: tableNameCamelCase,
|
|
||||||
tplVarStructDefine: structDefine,
|
|
||||||
tplVarPackageName: filepath.Base(in.EntityPath),
|
|
||||||
})
|
|
||||||
assignDefaultVar(tplView, in)
|
|
||||||
entityContent, err := tplView.ParseContent(ctx, tplContent)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("parsing template content failed: %v", err)
|
|
||||||
}
|
|
||||||
return entityContent
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
type (
|
|
||||||
CGenDaoInternalGenItems struct {
|
|
||||||
index int
|
|
||||||
Items []CGenDaoInternalGenItem
|
|
||||||
}
|
|
||||||
CGenDaoInternalGenItem struct {
|
|
||||||
Clear bool
|
|
||||||
StorageDirPaths []string
|
|
||||||
GeneratedFilePaths []string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newCGenDaoInternalGenItems() *CGenDaoInternalGenItems {
|
|
||||||
return &CGenDaoInternalGenItems{
|
|
||||||
index: -1,
|
|
||||||
Items: make([]CGenDaoInternalGenItem, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *CGenDaoInternalGenItems) Scale() {
|
|
||||||
i.Items = append(i.Items, CGenDaoInternalGenItem{
|
|
||||||
StorageDirPaths: make([]string, 0),
|
|
||||||
GeneratedFilePaths: make([]string, 0),
|
|
||||||
Clear: false,
|
|
||||||
})
|
|
||||||
i.index++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *CGenDaoInternalGenItems) SetClear(clear bool) {
|
|
||||||
i.Items[i.index].Clear = clear
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *CGenDaoInternalGenItems) AppendDirPath(storageDirPath string) {
|
|
||||||
i.Items[i.index].StorageDirPaths = append(
|
|
||||||
i.Items[i.index].StorageDirPaths,
|
|
||||||
storageDirPath,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *CGenDaoInternalGenItems) AppendGeneratedFilePath(generatedFilePath string) {
|
|
||||||
i.Items[i.index].GeneratedFilePaths = append(
|
|
||||||
i.Items[i.index].GeneratedFilePaths,
|
|
||||||
generatedFilePath,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
"github.com/gogf/gf/v2/text/gregex"
|
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type generateStructDefinitionInput struct {
|
|
||||||
CGenDaoInternalInput
|
|
||||||
TableName string // Table name.
|
|
||||||
StructName string // Struct name.
|
|
||||||
FieldMap map[string]*database.TableField // Table field map.
|
|
||||||
IsDo bool // Is generating DTO struct.
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) (string, []string) {
|
|
||||||
var appendImports []string
|
|
||||||
buffer := bytes.NewBuffer(nil)
|
|
||||||
array := make([][]string, len(in.FieldMap))
|
|
||||||
names := sortFieldKeyForDao(in.FieldMap)
|
|
||||||
for index, name := range names {
|
|
||||||
var imports string
|
|
||||||
field := in.FieldMap[name]
|
|
||||||
array[index], imports = generateStructFieldDefinition(ctx, field, in)
|
|
||||||
if imports != "" {
|
|
||||||
appendImports = append(appendImports, imports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table := tablewriter.NewTable(buffer, twRenderer, twConfig)
|
|
||||||
table.Bulk(array)
|
|
||||||
table.Render()
|
|
||||||
stContent := buffer.String()
|
|
||||||
// Let's do this hack of table writer for indent!
|
|
||||||
stContent = gstr.Replace(stContent, " #", "")
|
|
||||||
stContent = gstr.Replace(stContent, "` ", "`")
|
|
||||||
stContent = gstr.Replace(stContent, "``", "")
|
|
||||||
buffer.Reset()
|
|
||||||
fmt.Fprintf(buffer, "type %s struct {\n", in.StructName)
|
|
||||||
if in.IsDo {
|
|
||||||
fmt.Fprintf(buffer, "g.Meta `orm:\"table:%s, do:true\"`\n", in.TableName)
|
|
||||||
}
|
|
||||||
buffer.WriteString(stContent)
|
|
||||||
buffer.WriteString("}")
|
|
||||||
return buffer.String(), appendImports
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTypeMappingInfo(
|
|
||||||
ctx context.Context, fieldType string, inTypeMapping map[DBFieldTypeName]CustomAttributeType,
|
|
||||||
) (typeNameStr, importStr string) {
|
|
||||||
if typeMapping, ok := inTypeMapping[strings.ToLower(fieldType)]; ok {
|
|
||||||
typeNameStr = typeMapping.Type
|
|
||||||
importStr = typeMapping.Import
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tryTypeMatch, _ := gregex.MatchString(`(.+?)\(([^\(\)]+)\)([\s\)]*)`, fieldType)
|
|
||||||
var (
|
|
||||||
tryTypeName string
|
|
||||||
moreTry bool
|
|
||||||
)
|
|
||||||
if len(tryTypeMatch) == 4 {
|
|
||||||
tryTypeMatch3, _ := gregex.ReplaceString(`\s+`, "", tryTypeMatch[3])
|
|
||||||
tryTypeName = gstr.Trim(tryTypeMatch[1]) + tryTypeMatch3
|
|
||||||
moreTry = tryTypeMatch3 != ""
|
|
||||||
} else {
|
|
||||||
tryTypeName = gstr.Split(fieldType, " ")[0]
|
|
||||||
}
|
|
||||||
if tryTypeName != "" {
|
|
||||||
if typeMapping, ok := inTypeMapping[strings.ToLower(tryTypeName)]; ok {
|
|
||||||
typeNameStr = typeMapping.Type
|
|
||||||
importStr = typeMapping.Import
|
|
||||||
} else if moreTry {
|
|
||||||
typeNameStr, importStr = getTypeMappingInfo(ctx, tryTypeName, inTypeMapping)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateStructFieldDefinition generates and returns the attribute definition for specified field.
|
|
||||||
func generateStructFieldDefinition(
|
|
||||||
ctx context.Context, field *database.TableField, in generateStructDefinitionInput,
|
|
||||||
) (attrLines []string, appendImport string) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
localTypeName database.LocalType
|
|
||||||
localTypeNameStr string
|
|
||||||
)
|
|
||||||
|
|
||||||
if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
|
|
||||||
localTypeNameStr, appendImport = getTypeMappingInfo(ctx, field.Type, in.TypeMapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
if localTypeNameStr == "" {
|
|
||||||
localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
localTypeNameStr = string(localTypeName)
|
|
||||||
switch localTypeName {
|
|
||||||
case database.LocalTypeDate, database.LocalTypeTime, database.LocalTypeDatetime:
|
|
||||||
if in.StdTime {
|
|
||||||
localTypeNameStr = "time.Time"
|
|
||||||
} else {
|
|
||||||
localTypeNameStr = "*gtime.Time"
|
|
||||||
}
|
|
||||||
|
|
||||||
case database.LocalTypeInt64Bytes:
|
|
||||||
localTypeNameStr = "int64"
|
|
||||||
|
|
||||||
case database.LocalTypeUint64Bytes:
|
|
||||||
localTypeNameStr = "uint64"
|
|
||||||
|
|
||||||
// Special type handle.
|
|
||||||
case database.LocalTypeJson, database.LocalTypeJsonb:
|
|
||||||
if in.GJsonSupport {
|
|
||||||
localTypeNameStr = "*gjson.Json"
|
|
||||||
} else {
|
|
||||||
localTypeNameStr = "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
tagKey = "`"
|
|
||||||
descriptionTag = gstr.Replace(formatComment(field.Comment), `"`, `\"`)
|
|
||||||
)
|
|
||||||
removeFieldPrefixArray := gstr.SplitAndTrim(in.RemoveFieldPrefix, ",")
|
|
||||||
newFiledName := field.Name
|
|
||||||
for _, v := range removeFieldPrefixArray {
|
|
||||||
newFiledName = gstr.TrimLeftStr(newFiledName, v, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.FieldMapping != nil && len(in.FieldMapping) > 0 {
|
|
||||||
if typeMapping, ok := in.FieldMapping[fmt.Sprintf("%s.%s", in.TableName, newFiledName)]; ok {
|
|
||||||
localTypeNameStr = typeMapping.Type
|
|
||||||
appendImport = typeMapping.Import
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attrLines = []string{
|
|
||||||
" #" + formatFieldName(newFiledName, FieldNameCaseCamel),
|
|
||||||
" #" + localTypeNameStr,
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonTag := gstr.CaseConvert(newFiledName, gstr.CaseTypeMatch(in.JsonCase))
|
|
||||||
attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag))
|
|
||||||
// orm tag
|
|
||||||
if !in.IsDo {
|
|
||||||
// entity
|
|
||||||
attrLines = append(attrLines, fmt.Sprintf(` #orm:"%s"`, field.Name))
|
|
||||||
}
|
|
||||||
attrLines = append(attrLines, fmt.Sprintf(` #description:"%s"%s`, descriptionTag, tagKey))
|
|
||||||
attrLines = append(attrLines, fmt.Sprintf(` #// %s`, formatComment(field.Comment)))
|
|
||||||
|
|
||||||
for k, v := range attrLines {
|
|
||||||
if in.NoJsonTag {
|
|
||||||
v, _ = gregex.ReplaceString(`json:".+"`, ``, v)
|
|
||||||
}
|
|
||||||
if !in.DescriptionTag {
|
|
||||||
v, _ = gregex.ReplaceString(`description:".*"`, ``, v)
|
|
||||||
}
|
|
||||||
if in.NoModelComment {
|
|
||||||
v, _ = gregex.ReplaceString(`//.+`, ``, v)
|
|
||||||
}
|
|
||||||
attrLines[k] = v
|
|
||||||
}
|
|
||||||
return attrLines, appendImport
|
|
||||||
}
|
|
||||||
|
|
||||||
type FieldNameCase string
|
|
||||||
|
|
||||||
const (
|
|
||||||
FieldNameCaseCamel FieldNameCase = "CaseCamel"
|
|
||||||
FieldNameCaseCamelLower FieldNameCase = "CaseCamelLower"
|
|
||||||
)
|
|
||||||
|
|
||||||
// formatFieldName formats and returns a new field name that is used for golang codes generating.
|
|
||||||
func formatFieldName(fieldName string, nameCase FieldNameCase) string {
|
|
||||||
// For normal databases like mysql, pgsql, sqlite,
|
|
||||||
// field/table names of that are in normal case.
|
|
||||||
var newFieldName = fieldName
|
|
||||||
if isAllUpper(fieldName) {
|
|
||||||
// For special databases like dm, oracle,
|
|
||||||
// field/table names of that are in upper case.
|
|
||||||
newFieldName = strings.ToLower(fieldName)
|
|
||||||
}
|
|
||||||
switch nameCase {
|
|
||||||
case FieldNameCaseCamel:
|
|
||||||
return gstr.CaseCamel(newFieldName)
|
|
||||||
case FieldNameCaseCamelLower:
|
|
||||||
return gstr.CaseCamelLower(newFieldName)
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAllUpper checks and returns whether given `fieldName` all letters are upper case.
|
|
||||||
func isAllUpper(fieldName string) bool {
|
|
||||||
for _, b := range fieldName {
|
|
||||||
if b >= 'a' && b <= 'z' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatComment formats the comment string to fit the golang code without any lines.
|
|
||||||
func formatComment(comment string) string {
|
|
||||||
comment = gstr.ReplaceByArray(comment, g.SliceStr{
|
|
||||||
"\n", " ",
|
|
||||||
"\r", " ",
|
|
||||||
})
|
|
||||||
comment = gstr.Replace(comment, `\n`, " ")
|
|
||||||
comment = gstr.Trim(comment)
|
|
||||||
return comment
|
|
||||||
}
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/consts"
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
|
||||||
"github.com/gogf/gf/v2/os/gview"
|
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/mlog"
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// generateTable generates dao files for given tables.
|
|
||||||
func generateTable(ctx context.Context, in CGenDaoInternalInput) {
|
|
||||||
dirPathTable := gfile.Join(in.Path, in.TablePath)
|
|
||||||
if !in.GenTable {
|
|
||||||
if gfile.Exists(dirPathTable) {
|
|
||||||
in.genItems.AppendDirPath(dirPathTable)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in.genItems.AppendDirPath(dirPathTable)
|
|
||||||
for i := 0; i < len(in.TableNames); i++ {
|
|
||||||
var (
|
|
||||||
realTableName = in.TableNames[i]
|
|
||||||
newTableName = in.NewTableNames[i]
|
|
||||||
)
|
|
||||||
generateTableSingle(ctx, generateTableSingleInput{
|
|
||||||
CGenDaoInternalInput: in,
|
|
||||||
TableName: realTableName,
|
|
||||||
NewTableName: newTableName,
|
|
||||||
DirPathTable: dirPathTable,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateTableSingleInput is the input parameter for generateTableSingle.
|
|
||||||
type generateTableSingleInput struct {
|
|
||||||
CGenDaoInternalInput
|
|
||||||
// TableName specifies the table name of the table.
|
|
||||||
TableName string
|
|
||||||
// NewTableName specifies the prefix-stripped or custom edited name of the table.
|
|
||||||
NewTableName string
|
|
||||||
DirPathTable string
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateTableSingle generates dao files for a single table.
|
|
||||||
func generateTableSingle(ctx context.Context, in generateTableSingleInput) {
|
|
||||||
// Generating table data preparing.
|
|
||||||
fieldMap, err := in.DB.TableFields(ctx, in.TableName)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tableNameSnakeCase := gstr.CaseSnake(in.NewTableName)
|
|
||||||
fileName := gstr.Trim(tableNameSnakeCase, "-_.")
|
|
||||||
if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" {
|
|
||||||
// Add suffix to avoid the table name which contains "_test",
|
|
||||||
// which would make the go file a testing file.
|
|
||||||
fileName += "_table"
|
|
||||||
}
|
|
||||||
path := filepath.FromSlash(gfile.Join(in.DirPathTable, fileName+".go"))
|
|
||||||
in.genItems.AppendGeneratedFilePath(path)
|
|
||||||
if in.OverwriteDao || !gfile.Exists(path) {
|
|
||||||
var (
|
|
||||||
ctx = context.Background()
|
|
||||||
tplContent = getTemplateFromPathOrDefault(
|
|
||||||
in.TplDaoTablePath, consts.TemplateGenTableContent,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tplView.ClearAssigns()
|
|
||||||
tplView.Assigns(gview.Params{
|
|
||||||
tplVarGroupName: in.Group,
|
|
||||||
tplVarTableName: in.TableName,
|
|
||||||
tplVarTableNameCamelCase: formatFieldName(in.NewTableName, FieldNameCaseCamel),
|
|
||||||
tplVarPackageName: filepath.Base(in.TablePath),
|
|
||||||
tplVarTableFields: generateTableFields(fieldMap),
|
|
||||||
})
|
|
||||||
indexContent, err := tplView.ParseContent(ctx, tplContent)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatalf("parsing template content failed: %v", err)
|
|
||||||
}
|
|
||||||
if err = gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
|
|
||||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
|
||||||
} else {
|
|
||||||
utils.GoFmt(path)
|
|
||||||
mlog.Print("generated:", gfile.RealPath(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateTableFields generates and returns the field definition content for specified table.
|
|
||||||
func generateTableFields(fields map[string]*database.TableField) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fieldNames := make([]string, 0, len(fields))
|
|
||||||
for fieldName := range fields {
|
|
||||||
fieldNames = append(fieldNames, fieldName)
|
|
||||||
}
|
|
||||||
sort.Slice(fieldNames, func(i, j int) bool {
|
|
||||||
return fields[fieldNames[i]].Index < fields[fieldNames[j]].Index // asc
|
|
||||||
})
|
|
||||||
for index, fieldName := range fieldNames {
|
|
||||||
field := fields[fieldName]
|
|
||||||
buf.WriteString(" " + strconv.Quote(field.Name) + ": {\n")
|
|
||||||
buf.WriteString(" Index: " + gconv.String(field.Index) + ",\n")
|
|
||||||
buf.WriteString(" Name: " + strconv.Quote(field.Name) + ",\n")
|
|
||||||
buf.WriteString(" Type: " + strconv.Quote(field.Type) + ",\n")
|
|
||||||
buf.WriteString(" Null: " + gconv.String(field.Null) + ",\n")
|
|
||||||
buf.WriteString(" Key: " + strconv.Quote(field.Key) + ",\n")
|
|
||||||
buf.WriteString(" Default: " + generateDefaultValue(field.Default) + ",\n")
|
|
||||||
buf.WriteString(" Extra: " + strconv.Quote(field.Extra) + ",\n")
|
|
||||||
buf.WriteString(" Comment: " + strconv.Quote(field.Comment) + ",\n")
|
|
||||||
buf.WriteString(" },")
|
|
||||||
if index != len(fieldNames)-1 {
|
|
||||||
buf.WriteString("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateDefaultValue generates and returns the default value definition for specified field.
|
|
||||||
func generateDefaultValue(value interface{}) string {
|
|
||||||
if value == nil {
|
|
||||||
return "nil"
|
|
||||||
}
|
|
||||||
switch v := value.(type) {
|
|
||||||
case string:
|
|
||||||
return strconv.Quote(v)
|
|
||||||
default:
|
|
||||||
return gconv.String(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 gendao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
"github.com/gogf/gf/v2/util/gtag"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
CGenDaoConfig = `gfcli.gen.dao`
|
|
||||||
CGenDaoUsage = `gf gen dao [OPTION]`
|
|
||||||
CGenDaoBrief = `automatically generate go files for dao/do/entity`
|
|
||||||
CGenDaoEg = `
|
|
||||||
gf gen dao
|
|
||||||
gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
|
|
||||||
gf gen dao -p ./model -g user-center -t user,user_detail,user_login
|
|
||||||
gf gen dao -r user_
|
|
||||||
`
|
|
||||||
|
|
||||||
CGenDaoAd = `
|
|
||||||
CONFIGURATION SUPPORT
|
|
||||||
Options are also supported by configuration file.
|
|
||||||
It's suggested using configuration file instead of command line arguments making producing.
|
|
||||||
The configuration node name is "gfcli.gen.dao", which also supports multiple databases, for example(config.yaml):
|
|
||||||
gfcli:
|
|
||||||
gen:
|
|
||||||
dao:
|
|
||||||
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
|
|
||||||
tables: "order,products"
|
|
||||||
jsonCase: "CamelLower"
|
|
||||||
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
|
|
||||||
path: "./my-app"
|
|
||||||
prefix: "primary_"
|
|
||||||
tables: "user, userDetail"
|
|
||||||
typeMapping:
|
|
||||||
decimal:
|
|
||||||
type: decimal.Decimal
|
|
||||||
import: github.com/shopspring/decimal
|
|
||||||
numeric:
|
|
||||||
type: string
|
|
||||||
fieldMapping:
|
|
||||||
table_name.field_name:
|
|
||||||
type: decimal.Decimal
|
|
||||||
import: github.com/shopspring/decimal
|
|
||||||
`
|
|
||||||
CGenDaoBriefPath = `directory path for generated files`
|
|
||||||
CGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
|
|
||||||
CGenDaoBriefTables = `generate models only for given tables, multiple table names separated with ','`
|
|
||||||
CGenDaoBriefTablesEx = `generate models excluding given tables, multiple table names separated with ','`
|
|
||||||
CGenDaoBriefPrefix = `add prefix for all table of specified link/database tables`
|
|
||||||
CGenDaoBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
|
|
||||||
CGenDaoBriefRemoveFieldPrefix = `remove specified prefix of the field, multiple prefix separated with ','`
|
|
||||||
CGenDaoBriefStdTime = `use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables`
|
|
||||||
CGenDaoBriefWithTime = `add created time for auto produced go files`
|
|
||||||
CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables`
|
|
||||||
CGenDaoBriefImportPrefix = `custom import prefix for generated go files`
|
|
||||||
CGenDaoBriefDaoPath = `directory path for storing generated dao files under path`
|
|
||||||
CGenDaoBriefTablePath = `directory path for storing generated table files under path`
|
|
||||||
CGenDaoBriefDoPath = `directory path for storing generated do files under path`
|
|
||||||
CGenDaoBriefEntityPath = `directory path for storing generated entity files under path`
|
|
||||||
CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder`
|
|
||||||
CGenDaoBriefModelFile = `custom file name for storing generated model content`
|
|
||||||
CGenDaoBriefModelFileForDao = `custom file name generating model for DAO operations like Where/Data. It's empty in default`
|
|
||||||
CGenDaoBriefDescriptionTag = `add comment to description tag for each field`
|
|
||||||
CGenDaoBriefNoJsonTag = `no json tag will be added for each field`
|
|
||||||
CGenDaoBriefNoModelComment = `no model comment will be added for each field`
|
|
||||||
CGenDaoBriefClear = `delete all generated go files that do not exist in database`
|
|
||||||
CGenDaoBriefGenTable = `generate table files`
|
|
||||||
CGenDaoBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table`
|
|
||||||
CGenDaoBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table`
|
|
||||||
CGenDaoBriefShardingPattern = `sharding pattern for table name, e.g. "users_?" will be replace tables "users_001,users_002,..." to "users" dao`
|
|
||||||
CGenDaoBriefGroup = `
|
|
||||||
specifying the configuration group name of database for generated ORM instance,
|
|
||||||
it's not necessary and the default value is "default"
|
|
||||||
`
|
|
||||||
CGenDaoBriefJsonCase = `
|
|
||||||
generated json tag case for model struct, cases are as follows:
|
|
||||||
| Case | Example |
|
|
||||||
|---------------- |--------------------|
|
|
||||||
| Camel | AnyKindOfString |
|
|
||||||
| CamelLower | anyKindOfString | default
|
|
||||||
| Snake | any_kind_of_string |
|
|
||||||
| SnakeScreaming | ANY_KIND_OF_STRING |
|
|
||||||
| SnakeFirstUpper | rgb_code_md5 |
|
|
||||||
| Kebab | any-kind-of-string |
|
|
||||||
| KebabScreaming | ANY-KIND-OF-STRING |
|
|
||||||
`
|
|
||||||
CGenDaoBriefTplDaoIndexPath = `template file path for dao index file`
|
|
||||||
CGenDaoBriefTplDaoInternalPath = `template file path for dao internal file`
|
|
||||||
CGenDaoBriefTplDaoDoPathPath = `template file path for dao do file`
|
|
||||||
CGenDaoBriefTplDaoEntityPath = `template file path for dao entity file`
|
|
||||||
|
|
||||||
tplVarTableName = `TplTableName`
|
|
||||||
tplVarTableNameCamelCase = `TplTableNameCamelCase`
|
|
||||||
tplVarTableNameCamelLowerCase = `TplTableNameCamelLowerCase`
|
|
||||||
tplVarTableSharding = `TplTableSharding`
|
|
||||||
tplVarTableShardingPrefix = `TplTableShardingPrefix`
|
|
||||||
tplVarTableFields = `TplTableFields`
|
|
||||||
tplVarPackageImports = `TplPackageImports`
|
|
||||||
tplVarImportPrefix = `TplImportPrefix`
|
|
||||||
tplVarStructDefine = `TplStructDefine`
|
|
||||||
tplVarColumnDefine = `TplColumnDefine`
|
|
||||||
tplVarColumnNames = `TplColumnNames`
|
|
||||||
tplVarGroupName = `TplGroupName`
|
|
||||||
tplVarDatetimeStr = `TplDatetimeStr`
|
|
||||||
tplVarCreatedAtDatetimeStr = `TplCreatedAtDatetimeStr`
|
|
||||||
tplVarPackageName = `TplPackageName`
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
gtag.Sets(g.MapStrStr{
|
|
||||||
`CGenDaoConfig`: CGenDaoConfig,
|
|
||||||
`CGenDaoUsage`: CGenDaoUsage,
|
|
||||||
`CGenDaoBrief`: CGenDaoBrief,
|
|
||||||
`CGenDaoEg`: CGenDaoEg,
|
|
||||||
`CGenDaoAd`: CGenDaoAd,
|
|
||||||
`CGenDaoBriefPath`: CGenDaoBriefPath,
|
|
||||||
`CGenDaoBriefLink`: CGenDaoBriefLink,
|
|
||||||
`CGenDaoBriefTables`: CGenDaoBriefTables,
|
|
||||||
`CGenDaoBriefTablesEx`: CGenDaoBriefTablesEx,
|
|
||||||
`CGenDaoBriefPrefix`: CGenDaoBriefPrefix,
|
|
||||||
`CGenDaoBriefRemovePrefix`: CGenDaoBriefRemovePrefix,
|
|
||||||
`CGenDaoBriefRemoveFieldPrefix`: CGenDaoBriefRemoveFieldPrefix,
|
|
||||||
`CGenDaoBriefStdTime`: CGenDaoBriefStdTime,
|
|
||||||
`CGenDaoBriefWithTime`: CGenDaoBriefWithTime,
|
|
||||||
`CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath,
|
|
||||||
`CGenDaoBriefTablePath`: CGenDaoBriefTablePath,
|
|
||||||
`CGenDaoBriefDoPath`: CGenDaoBriefDoPath,
|
|
||||||
`CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath,
|
|
||||||
`CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport,
|
|
||||||
`CGenDaoBriefImportPrefix`: CGenDaoBriefImportPrefix,
|
|
||||||
`CGenDaoBriefOverwriteDao`: CGenDaoBriefOverwriteDao,
|
|
||||||
`CGenDaoBriefModelFile`: CGenDaoBriefModelFile,
|
|
||||||
`CGenDaoBriefModelFileForDao`: CGenDaoBriefModelFileForDao,
|
|
||||||
`CGenDaoBriefDescriptionTag`: CGenDaoBriefDescriptionTag,
|
|
||||||
`CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
|
|
||||||
`CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
|
|
||||||
`CGenDaoBriefClear`: CGenDaoBriefClear,
|
|
||||||
`CGenDaoBriefGenTable`: CGenDaoBriefGenTable,
|
|
||||||
`CGenDaoBriefTypeMapping`: CGenDaoBriefTypeMapping,
|
|
||||||
`CGenDaoBriefFieldMapping`: CGenDaoBriefFieldMapping,
|
|
||||||
`CGenDaoBriefShardingPattern`: CGenDaoBriefShardingPattern,
|
|
||||||
`CGenDaoBriefGroup`: CGenDaoBriefGroup,
|
|
||||||
`CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase,
|
|
||||||
`CGenDaoBriefTplDaoIndexPath`: CGenDaoBriefTplDaoIndexPath,
|
|
||||||
`CGenDaoBriefTplDaoInternalPath`: CGenDaoBriefTplDaoInternalPath,
|
|
||||||
`CGenDaoBriefTplDaoDoPathPath`: CGenDaoBriefTplDaoDoPathPath,
|
|
||||||
`CGenDaoBriefTplDaoEntityPath`: CGenDaoBriefTplDaoEntityPath,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package consts
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DoNotEditKey is used in generated files,
|
|
||||||
// which marks the files will be overwritten by CLI tool.
|
|
||||||
DoNotEditKey = `DO NOT EDIT`
|
|
||||||
)
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
package mlog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/os/gcmd"
|
|
||||||
"github.com/gogf/gf/v2/os/genv"
|
|
||||||
"github.com/gogf/gf/v2/os/glog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
headerPrintEnvName = "GF_CLI_MLOG_HEADER"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ctx = context.TODO()
|
|
||||||
logger = glog.New()
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if genv.Get(headerPrintEnvName).String() == "1" {
|
|
||||||
logger.SetHeaderPrint(true)
|
|
||||||
} else {
|
|
||||||
logger.SetHeaderPrint(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if gcmd.GetOpt("debug") != nil || gcmd.GetOpt("gf.debug") != nil {
|
|
||||||
logger.SetHeaderPrint(true)
|
|
||||||
logger.SetStackSkip(4)
|
|
||||||
logger.SetFlags(logger.GetFlags() | glog.F_FILE_LONG)
|
|
||||||
logger.SetDebug(true)
|
|
||||||
} else {
|
|
||||||
logger.SetStack(false)
|
|
||||||
logger.SetDebug(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeaderPrint enables/disables header printing to stdout.
|
|
||||||
func SetHeaderPrint(enabled bool) {
|
|
||||||
logger.SetHeaderPrint(enabled)
|
|
||||||
if enabled {
|
|
||||||
_ = genv.Set(headerPrintEnvName, "1")
|
|
||||||
} else {
|
|
||||||
_ = genv.Set(headerPrintEnvName, "0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Print(v ...any) {
|
|
||||||
logger.Print(ctx, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Printf(format string, v ...any) {
|
|
||||||
logger.Printf(ctx, format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Fatal(v ...any) {
|
|
||||||
logger.Fatal(ctx, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Fatalf(format string, v ...any) {
|
|
||||||
logger.Fatalf(ctx, format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debug(v ...any) {
|
|
||||||
logger.Debug(ctx, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debugf(format string, v ...any) {
|
|
||||||
logger.Debugf(ctx, format, v...)
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/tools/imports"
|
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
|
||||||
"github.com/gogf/gf/v2/os/gproc"
|
|
||||||
"github.com/gogf/gf/v2/text/gregex"
|
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/consts"
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/mlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GoFmt formats the source file and adds or removes import statements as necessary.
|
|
||||||
func GoFmt(path string) {
|
|
||||||
replaceFunc := func(path, content string) string {
|
|
||||||
res, err := imports.Process(path, []byte(content), nil)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Printf(`error format "%s" go files: %v`, path, err)
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
return string(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if gfile.IsFile(path) {
|
|
||||||
// File format.
|
|
||||||
if gfile.ExtName(path) != "go" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = gfile.ReplaceFileFunc(replaceFunc, path)
|
|
||||||
} else {
|
|
||||||
// Folder format.
|
|
||||||
err = gfile.ReplaceDirFunc(replaceFunc, path, "*.go", true)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
mlog.Printf(`error format "%s" go files: %v`, path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GoModTidy executes `go mod tidy` at specified directory `dirPath`.
|
|
||||||
func GoModTidy(ctx context.Context, dirPath string) error {
|
|
||||||
command := fmt.Sprintf(`cd %s && go mod tidy`, dirPath)
|
|
||||||
err := gproc.ShellRun(ctx, command)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFileDoNotEdit checks and returns whether file contains `do not edit` key.
|
|
||||||
func IsFileDoNotEdit(filePath string) bool {
|
|
||||||
if !gfile.Exists(filePath) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return gstr.Contains(gfile.GetContents(filePath), consts.DoNotEditKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceGeneratedContentGFV2 replaces generated go content from goframe v1 to v2.
|
|
||||||
func ReplaceGeneratedContentGFV2(folderPath string) (err error) {
|
|
||||||
return gfile.ReplaceDirFunc(func(path, content string) string {
|
|
||||||
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
|
|
||||||
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
|
|
||||||
content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
|
|
||||||
content = gstr.Replace(content, `"github.com/gogf/gf/v2/contrib/`, `"github.com/gogf/gf/contrib/`)
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
return content
|
|
||||||
}, folderPath, "*.go", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetImportPath calculates and returns the golang import path for given `dirPath`.
|
|
||||||
// Note that it needs a `go.mod` in current working directory or parent directories to detect the path.
|
|
||||||
func GetImportPath(dirPath string) string {
|
|
||||||
// If `filePath` does not exist, create it firstly to find the import path.
|
|
||||||
var realPath = gfile.RealPath(dirPath)
|
|
||||||
if realPath == "" {
|
|
||||||
_ = gfile.Mkdir(dirPath)
|
|
||||||
realPath = gfile.RealPath(dirPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
newDir = gfile.Dir(realPath)
|
|
||||||
oldDir string
|
|
||||||
suffix = gfile.Basename(dirPath)
|
|
||||||
goModName = "go.mod"
|
|
||||||
goModPath string
|
|
||||||
importPath string
|
|
||||||
)
|
|
||||||
for {
|
|
||||||
goModPath = gfile.Join(newDir, goModName)
|
|
||||||
if gfile.Exists(goModPath) {
|
|
||||||
match, _ := gregex.MatchString(`^module\s+(.+)\s*`, gfile.GetContents(goModPath))
|
|
||||||
importPath = gstr.Trim(match[1]) + "/" + suffix
|
|
||||||
importPath = gstr.Replace(importPath, `\`, `/`)
|
|
||||||
importPath = gstr.TrimRight(importPath, `/`)
|
|
||||||
return importPath
|
|
||||||
}
|
|
||||||
oldDir = newDir
|
|
||||||
newDir = gfile.Dir(oldDir)
|
|
||||||
if newDir == oldDir {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
suffix = gfile.Basename(oldDir) + "/" + suffix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetModPath retrieves and returns the file path of go.mod for current project.
|
|
||||||
func GetModPath() string {
|
|
||||||
var (
|
|
||||||
oldDir = gfile.Pwd()
|
|
||||||
newDir = oldDir
|
|
||||||
goModName = "go.mod"
|
|
||||||
goModPath string
|
|
||||||
)
|
|
||||||
for {
|
|
||||||
goModPath = gfile.Join(newDir, goModName)
|
|
||||||
if gfile.Exists(goModPath) {
|
|
||||||
return goModPath
|
|
||||||
}
|
|
||||||
newDir = gfile.Dir(oldDir)
|
|
||||||
if newDir == oldDir {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
oldDir = newDir
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/schollz/progressbar/v3"
|
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/mlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTPDownloadFileWithPercent downloads target url file to local path with percent process printing.
|
|
||||||
func HTTPDownloadFileWithPercent(url string, localSaveFilePath string) error {
|
|
||||||
start := time.Now()
|
|
||||||
out, err := os.Create(localSaveFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
headResp, err := http.Head(url)
|
|
||||||
if err != nil {
|
|
||||||
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
|
|
||||||
}
|
|
||||||
defer headResp.Body.Close()
|
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
bar := progressbar.NewOptions(int(resp.ContentLength), progressbar.OptionShowBytes(true), progressbar.OptionShowCount())
|
|
||||||
writer := io.MultiWriter(out, bar)
|
|
||||||
_, err = io.Copy(writer, resp.Body)
|
|
||||||
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
if elapsed > time.Minute {
|
|
||||||
mlog.Printf(`download completed in %.0fm`, float64(elapsed)/float64(time.Minute))
|
|
||||||
} else {
|
|
||||||
mlog.Printf(`download completed in %.0fs`, elapsed.Seconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright GoFrame gf 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 utils_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/test/gtest"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao/internal/utility/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_GetModPath(t *testing.T) {
|
|
||||||
gtest.C(t, func(t *gtest.T) {
|
|
||||||
goModPath := utils.GetModPath()
|
|
||||||
fmt.Println(goModPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
159
cmd/main.go
159
cmd/main.go
|
|
@ -1,159 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/cmd/gendao"
|
|
||||||
"git.magicany.cc/black1552/gin-base/config"
|
|
||||||
_ "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")
|
|
||||||
user := getStringValue(defaultDbConfig, "user", "root")
|
|
||||||
pass := getStringValue(defaultDbConfig, "pass", "")
|
|
||||||
dbType := getStringValue(defaultDbConfig, "type", "mysql")
|
|
||||||
link := getStringValue(defaultDbConfig, "link", "")
|
|
||||||
|
|
||||||
fmt.Println("=== Gin-Base DAO 代码生成工具 ===")
|
|
||||||
fmt.Printf("🔧 类型: %s\n", dbType)
|
|
||||||
|
|
||||||
// 构建数据库连接字符串
|
|
||||||
var connectionInfo string
|
|
||||||
if link == "" {
|
|
||||||
// 如果没有配置 link,则根据数据库类型构建
|
|
||||||
switch dbType {
|
|
||||||
case "sqlite":
|
|
||||||
// SQLite 使用配置文件中的 link 或默认路径
|
|
||||||
connectionInfo = fmt.Sprintf("📁 数据库文件: %s", link)
|
|
||||||
case "mysql", "mariadb":
|
|
||||||
link = fmt.Sprintf("mysql:%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true&loc=Local",
|
|
||||||
user, pass, host, port, name,
|
|
||||||
)
|
|
||||||
connectionInfo = fmt.Sprintf("📊 数据库: %s\n🌐 主机: %s:%s", name, host, port)
|
|
||||||
case "pgsql", "postgresql":
|
|
||||||
link = fmt.Sprintf("pgsql:%s:%s@tcp(%s:%s)/%s?sslmode=disable",
|
|
||||||
user, pass, host, port, name,
|
|
||||||
)
|
|
||||||
connectionInfo = fmt.Sprintf("📊 数据库: %s\n🌐 主机: %s:%s", name, host, port)
|
|
||||||
case "mssql":
|
|
||||||
link = fmt.Sprintf("mssql:%s:%s@tcp(%s:%s)/%s",
|
|
||||||
user, pass, host, port, name,
|
|
||||||
)
|
|
||||||
connectionInfo = fmt.Sprintf("📊 数据库: %s\n🌐 主机: %s:%s", name, host, port)
|
|
||||||
case "oracle":
|
|
||||||
link = fmt.Sprintf("oracle:%s:%s@%s:%s/%s",
|
|
||||||
user, pass, host, port, name,
|
|
||||||
)
|
|
||||||
connectionInfo = fmt.Sprintf("📊 数据库: %s\n🌐 主机: %s:%s", name, host, port)
|
|
||||||
case "clickhouse":
|
|
||||||
link = fmt.Sprintf("clickhouse:%s:%s@tcp(%s:%s)/%s",
|
|
||||||
user, pass, host, port, name,
|
|
||||||
)
|
|
||||||
connectionInfo = fmt.Sprintf("📊 数据库: %s\n🌐 主机: %s:%s", name, host, port)
|
|
||||||
default:
|
|
||||||
fmt.Printf("⚠️ 警告: 未知的数据库类型 %s,尝试使用配置的 link\n", dbType)
|
|
||||||
if link == "" {
|
|
||||||
fmt.Println("❌ 错误: 未配置数据库连接信息")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
connectionInfo = "🔗 使用自定义连接"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 使用配置文件中直接提供的 link
|
|
||||||
connectionInfo = fmt.Sprintf("🔗 连接: %s", link)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(connectionInfo)
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// 准备表名参数
|
|
||||||
tablesArg := ""
|
|
||||||
if len(os.Args) > 1 {
|
|
||||||
tablesArg = joinStrings(os.Args[1:], ",")
|
|
||||||
fmt.Printf("📋 指定生成表: %s\n\n", tablesArg)
|
|
||||||
} else {
|
|
||||||
fmt.Println("💡 提示: 可以通过命令行参数指定要生成的表")
|
|
||||||
fmt.Println(" 例如: gin-dao-gen users orders\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用 gendao 的 Dao 函数生成代码
|
|
||||||
genDao := gendao.CGenDao{}
|
|
||||||
input := gendao.CGenDaoInput{
|
|
||||||
Link: link,
|
|
||||||
Tables: tablesArg,
|
|
||||||
Path: "./",
|
|
||||||
DaoPath: "dao",
|
|
||||||
DoPath: "model/do",
|
|
||||||
EntityPath: "model/entity",
|
|
||||||
TablePath: "model/table",
|
|
||||||
Group: "default",
|
|
||||||
JsonCase: "CamelLower",
|
|
||||||
DescriptionTag: true,
|
|
||||||
GenTable: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("🔨 开始生成代码...")
|
|
||||||
_, err := genDao.Dao(ctx, input)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("\n❌ 代码生成失败: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("\n🎉 代码生成完成!")
|
|
||||||
fmt.Println("📁 生成的文件位于:")
|
|
||||||
fmt.Println(" - ./dao/")
|
|
||||||
fmt.Println(" - ./model/do/")
|
|
||||||
fmt.Println(" - ./model/entity/")
|
|
||||||
fmt.Println(" - ./model/table/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:从 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:连接字符串数组
|
|
||||||
func joinStrings(strs []string, sep string) string {
|
|
||||||
if len(strs) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
result := strs[0]
|
|
||||||
for i := 1; i < len(strs); i++ {
|
|
||||||
result += sep + strs[i]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
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,70 +4,58 @@
|
||||||
// If a copy of the MIT was not distributed with this file,
|
// If a copy of the MIT was not distributed with this file,
|
||||||
// You can obtain one at https://github.com/gogf/gf.
|
// You can obtain one at https://github.com/gogf/gf.
|
||||||
|
|
||||||
// Package clickhouse implements database.Driver, which supports operations for database ClickHouse.
|
// Package clickhouse implements database.Driver for ClickHouse database.
|
||||||
package clickhouse
|
package clickhouse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"database/sql"
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
|
_ "github.com/ClickHouse/clickhouse-go/v2"
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
"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 {
|
type Driver struct {
|
||||||
*database.Core
|
*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 (
|
const (
|
||||||
updateFilterPattern = `(?i)UPDATE[\s]+?(\w+[\.]?\w+)[\s]+?SET`
|
quoteChar = `"`
|
||||||
deleteFilterPattern = `(?i)DELETE[\s]+?FROM[\s]+?(\w+[\.]?\w+)`
|
|
||||||
filterTypePattern = `(?i)^UPDATE|DELETE`
|
|
||||||
needParsedSqlInCtx gctx.StrKey = "NeedParsedSql"
|
|
||||||
driverName = "clickhouse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := database.Register(`clickhouse`, New()); err != nil {
|
if err := database.Register("clickhouse", New()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create and returns a driver that implements database.Driver, which supports operations for clickhouse.
|
// New creates and returns a driver that implements database.Driver for ClickHouse.
|
||||||
func New() database.Driver {
|
func New() database.Driver {
|
||||||
return &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) {
|
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||||
return &Driver{
|
return &Driver{
|
||||||
Core: core,
|
Core: core,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) injectNeedParsedSql(ctx context.Context) context.Context {
|
// GetChars returns the security char for ClickHouse.
|
||||||
if ctx.Value(needParsedSqlInCtx) != nil {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return ctx
|
return quoteChar, quoteChar
|
||||||
}
|
|
||||||
return context.WithValue(ctx, needParsedSqlInCtx, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration returns a Migration instance for ClickHouse database operations.
|
// Open creates and returns an underlying sql.DB object for ClickHouse.
|
||||||
func (d *Driver) Migration() *Migration {
|
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||||
return NewMigration(d)
|
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)
|
||||||
}
|
}
|
||||||
|
return sql.Open("clickhouse", source)
|
||||||
// GetMigration returns a Migration instance implementing the Migration interface.
|
|
||||||
func (d *Driver) GetMigration() database.Migration {
|
|
||||||
return d.Migration()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// 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...)
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// 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...)
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,321 +0,0 @@
|
||||||
// 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Migration implements database migration operations for ClickHouse.
|
|
||||||
type Migration struct {
|
|
||||||
*database.MigrationCore
|
|
||||||
*database.AutoMigrateCore
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMigration creates a new ClickHouse Migration instance.
|
|
||||||
func NewMigration(db database.DB) *Migration {
|
|
||||||
return &Migration{
|
|
||||||
MigrationCore: database.NewMigrationCore(db),
|
|
||||||
AutoMigrateCore: database.NewAutoMigrateCore(db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTable creates a new table with the given name and column definitions.
|
|
||||||
func (m *Migration) CreateTable(ctx context.Context, table string, columns map[string]*database.ColumnDefinition, options ...database.TableOption) error {
|
|
||||||
if len(columns) == 0 {
|
|
||||||
return fmt.Errorf("cannot create table without columns")
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts database.TableOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE TABLE ")
|
|
||||||
if opts.IfNotExists {
|
|
||||||
sql.WriteString("IF NOT EXISTS ")
|
|
||||||
}
|
|
||||||
sql.WriteString(database.QuoteIdentifier(table))
|
|
||||||
sql.WriteString(" (\n")
|
|
||||||
|
|
||||||
// Add columns
|
|
||||||
var colDefs []string
|
|
||||||
for name, def := range columns {
|
|
||||||
colDef := m.buildColumnDefinition(name, def)
|
|
||||||
colDefs = append(colDefs, " "+colDef)
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString(strings.Join(colDefs, ",\n"))
|
|
||||||
sql.WriteString("\n)")
|
|
||||||
|
|
||||||
// Add engine specification (required for ClickHouse)
|
|
||||||
sql.WriteString(" ENGINE = MergeTree()")
|
|
||||||
|
|
||||||
if opts.Comment != "" {
|
|
||||||
sql.WriteString(fmt.Sprintf(" COMMENT '%s'", escapeString(opts.Comment)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildColumnDefinition builds column definition for ClickHouse.
|
|
||||||
func (m *Migration) buildColumnDefinition(name string, def *database.ColumnDefinition) string {
|
|
||||||
var parts []string
|
|
||||||
parts = append(parts, database.QuoteIdentifier(name))
|
|
||||||
|
|
||||||
// Handle ClickHouse-specific types
|
|
||||||
dbType := def.Type
|
|
||||||
parts = append(parts, dbType)
|
|
||||||
|
|
||||||
if !def.Null {
|
|
||||||
// ClickHouse uses Nullable type wrapper
|
|
||||||
if !strings.HasPrefix(dbType, "Nullable(") {
|
|
||||||
// Type is already non-nullable by default in ClickHouse
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Make type nullable if needed
|
|
||||||
if !strings.HasPrefix(dbType, "Nullable(") {
|
|
||||||
parts[len(parts)-1] = fmt.Sprintf("Nullable(%s)", dbType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if def.Default != nil {
|
|
||||||
defaultValue := formatDefaultValue(def.Default)
|
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", defaultValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
if def.Comment != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("COMMENT '%s'", escapeString(def.Comment)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropTable drops an existing table from the database.
|
|
||||||
func (m *Migration) DropTable(ctx context.Context, table string, ifExists ...bool) error {
|
|
||||||
sql := "DROP TABLE "
|
|
||||||
if len(ifExists) > 0 && ifExists[0] {
|
|
||||||
sql += "IF EXISTS "
|
|
||||||
}
|
|
||||||
sql += database.QuoteIdentifier(table)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasTable checks if a table exists in the database.
|
|
||||||
func (m *Migration) HasTable(ctx context.Context, table string) (bool, error) {
|
|
||||||
schema := m.GetDB().GetSchema()
|
|
||||||
if schema == "" {
|
|
||||||
schema = "currentDatabase()"
|
|
||||||
} else {
|
|
||||||
schema = fmt.Sprintf("'%s'", schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM system.tables WHERE database = %s AND name = '%s'",
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameTable renames an existing table from oldName to newName.
|
|
||||||
func (m *Migration) RenameTable(ctx context.Context, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"RENAME TABLE %s TO %s",
|
|
||||||
database.QuoteIdentifier(oldName),
|
|
||||||
database.QuoteIdentifier(newName),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TruncateTable removes all records from a table but keeps the table structure.
|
|
||||||
func (m *Migration) TruncateTable(ctx context.Context, table string) error {
|
|
||||||
sql := fmt.Sprintf("TRUNCATE TABLE %s", database.QuoteIdentifier(table))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddColumn adds a new column to an existing table.
|
|
||||||
func (m *Migration) AddColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.buildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s", database.QuoteIdentifier(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropColumn removes a column from an existing table.
|
|
||||||
func (m *Migration) DropColumn(ctx context.Context, table, column string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP COLUMN %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(column),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameColumn renames a column in an existing table.
|
|
||||||
func (m *Migration) RenameColumn(ctx context.Context, table, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s RENAME COLUMN %s TO %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(oldName),
|
|
||||||
database.QuoteIdentifier(newName),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyColumn modifies an existing column's definition.
|
|
||||||
func (m *Migration) ModifyColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.buildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", database.QuoteIdentifier(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasColumn checks if a column exists in a table.
|
|
||||||
func (m *Migration) HasColumn(ctx context.Context, table, column string) (bool, error) {
|
|
||||||
fields, err := m.GetDB().TableFields(ctx, table)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
_, exists := fields[column]
|
|
||||||
return exists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIndex creates a new index on the specified table and columns.
|
|
||||||
// Note: ClickHouse has limited index support compared to traditional databases.
|
|
||||||
func (m *Migration) CreateIndex(ctx context.Context, table, index string, columns []string, options ...database.IndexOption) error {
|
|
||||||
var opts database.IndexOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClickHouse uses different indexing mechanisms
|
|
||||||
// For now, we'll use ALTER TABLE ADD INDEX syntax
|
|
||||||
colList := m.BuildIndexColumns(columns)
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ADD INDEX %s (%s) TYPE minmax GRANULARITY 1",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(index),
|
|
||||||
colList,
|
|
||||||
)
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropIndex drops an existing index from a table.
|
|
||||||
func (m *Migration) DropIndex(ctx context.Context, table, index string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP INDEX %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(index),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasIndex checks if an index exists on a table.
|
|
||||||
func (m *Migration) HasIndex(ctx context.Context, table, index string) (bool, error) {
|
|
||||||
schema := m.GetDB().GetSchema()
|
|
||||||
if schema == "" {
|
|
||||||
schema = "currentDatabase()"
|
|
||||||
} else {
|
|
||||||
schema = fmt.Sprintf("'%s'", schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM system.data_skipping_indices WHERE database = %s AND table = '%s' AND name = '%s'",
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
index,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateForeignKey is not supported in ClickHouse.
|
|
||||||
func (m *Migration) CreateForeignKey(ctx context.Context, table, constraint string, columns []string, refTable string, refColumns []string, options ...database.ForeignKeyOption) error {
|
|
||||||
// ClickHouse does not support foreign keys
|
|
||||||
return fmt.Errorf("ClickHouse does not support foreign key constraints")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropForeignKey is not supported in ClickHouse.
|
|
||||||
func (m *Migration) DropForeignKey(ctx context.Context, table, constraint string) error {
|
|
||||||
// ClickHouse does not support foreign keys
|
|
||||||
return fmt.Errorf("ClickHouse does not support foreign key constraints")
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasForeignKey always returns false as ClickHouse doesn't support foreign keys.
|
|
||||||
func (m *Migration) HasForeignKey(ctx context.Context, table, constraint string) (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateSchema creates a new database schema.
|
|
||||||
func (m *Migration) CreateSchema(ctx context.Context, schema string) error {
|
|
||||||
sql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", database.QuoteIdentifier(schema))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropSchema drops an existing database schema.
|
|
||||||
func (m *Migration) DropSchema(ctx context.Context, schema string, cascade ...bool) error {
|
|
||||||
sql := fmt.Sprintf("DROP DATABASE IF EXISTS %s", database.QuoteIdentifier(schema))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasSchema checks if a schema exists.
|
|
||||||
func (m *Migration) HasSchema(ctx context.Context, schema string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM system.databases WHERE name = '%s'",
|
|
||||||
schema,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatDefaultValue formats the default value for SQL.
|
|
||||||
func formatDefaultValue(value any) string {
|
|
||||||
switch v := value.(type) {
|
|
||||||
case string:
|
|
||||||
return fmt.Sprintf("'%s'", escapeString(v))
|
|
||||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
|
||||||
return fmt.Sprintf("%d", v)
|
|
||||||
case float32, float64:
|
|
||||||
return fmt.Sprintf("%f", v)
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
return "1"
|
|
||||||
}
|
|
||||||
return "0"
|
|
||||||
case nil:
|
|
||||||
return "NULL"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("'%v'", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// escapeString escapes special characters in strings for SQL.
|
|
||||||
func escapeString(s string) string {
|
|
||||||
s = strings.ReplaceAll(s, "'", "\\'")
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// 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,55 +4,58 @@
|
||||||
// If a copy of the MIT was not distributed with this file,
|
// If a copy of the MIT was not distributed with this file,
|
||||||
// You can obtain one at https://github.com/gogf/gf.
|
// You can obtain one at https://github.com/gogf/gf.
|
||||||
|
|
||||||
// Package mssql implements database.Driver, which supports operations for MSSQL.
|
// Package mssql implements database.Driver for Microsoft SQL Server.
|
||||||
package mssql
|
package mssql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
_ "github.com/microsoft/go-mssqldb"
|
_ "github.com/microsoft/go-mssqldb"
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
"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 {
|
type Driver struct {
|
||||||
*database.Core
|
*database.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
rowNumberAliasForSelect = `ROW_NUMBER__`
|
|
||||||
quoteChar = `"`
|
quoteChar = `"`
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := database.Register(`mssql`, New()); err != nil {
|
if err := database.Register("mssql", New()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create and returns a driver that implements database.Driver, which supports operations for Mssql.
|
// New creates and returns a driver that implements database.Driver for SQL Server.
|
||||||
func New() database.Driver {
|
func New() database.Driver {
|
||||||
return &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) {
|
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||||
return &Driver{
|
return &Driver{
|
||||||
Core: core,
|
Core: core,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for SQL Server.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return quoteChar, quoteChar
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration returns a Migration instance for MSSQL database operations.
|
// Open creates and returns an underlying sql.DB object for SQL Server.
|
||||||
func (d *Driver) Migration() *Migration {
|
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||||
return NewMigration(d)
|
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)
|
||||||
// GetMigration returns a Migration instance implementing the Migration interface.
|
|
||||||
func (d *Driver) GetMigration() database.Migration {
|
|
||||||
return d.Migration()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
// 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(?)
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
// 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)
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,340 +0,0 @@
|
||||||
// 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"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Migration implements database migration operations for SQL Server.
|
|
||||||
type Migration struct {
|
|
||||||
*database.MigrationCore
|
|
||||||
*database.AutoMigrateCore
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMigration creates a new SQL Server Migration instance.
|
|
||||||
func NewMigration(db database.DB) *Migration {
|
|
||||||
return &Migration{
|
|
||||||
MigrationCore: database.NewMigrationCore(db),
|
|
||||||
AutoMigrateCore: database.NewAutoMigrateCore(db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTable creates a new table with the given name and column definitions.
|
|
||||||
func (m *Migration) CreateTable(ctx context.Context, table string, columns map[string]*database.ColumnDefinition, options ...database.TableOption) error {
|
|
||||||
if len(columns) == 0 {
|
|
||||||
return fmt.Errorf("cannot create table without columns")
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts database.TableOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE TABLE ")
|
|
||||||
sql.WriteString(database.QuoteIdentifier(table))
|
|
||||||
sql.WriteString(" (\n")
|
|
||||||
|
|
||||||
// Add columns
|
|
||||||
var colDefs []string
|
|
||||||
var primaryKeys []string
|
|
||||||
for name, def := range columns {
|
|
||||||
colDef := m.buildColumnDefinition(name, def)
|
|
||||||
if def.PrimaryKey {
|
|
||||||
primaryKeys = append(primaryKeys, database.QuoteIdentifier(name))
|
|
||||||
}
|
|
||||||
colDefs = append(colDefs, " "+colDef)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add primary key constraint if needed
|
|
||||||
if len(primaryKeys) > 0 {
|
|
||||||
colDefs = append(colDefs, fmt.Sprintf(" PRIMARY KEY (%s)", strings.Join(primaryKeys, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString(strings.Join(colDefs, ",\n"))
|
|
||||||
sql.WriteString("\n)")
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildColumnDefinition builds column definition for SQL Server.
|
|
||||||
func (m *Migration) buildColumnDefinition(name string, def *database.ColumnDefinition) string {
|
|
||||||
var parts []string
|
|
||||||
parts = append(parts, database.QuoteIdentifier(name))
|
|
||||||
|
|
||||||
// Handle SQL Server-specific types
|
|
||||||
dbType := def.Type
|
|
||||||
if def.AutoIncrement {
|
|
||||||
if dbType == "INT" || dbType == "INTEGER" {
|
|
||||||
dbType = "INT IDENTITY(1,1)"
|
|
||||||
} else if dbType == "BIGINT" {
|
|
||||||
dbType = "BIGINT IDENTITY(1,1)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parts = append(parts, dbType)
|
|
||||||
|
|
||||||
if !def.Null {
|
|
||||||
parts = append(parts, "NOT NULL")
|
|
||||||
} else {
|
|
||||||
parts = append(parts, "NULL")
|
|
||||||
}
|
|
||||||
|
|
||||||
if def.Default != nil {
|
|
||||||
defaultValue := formatDefaultValue(def.Default)
|
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", defaultValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropTable drops an existing table from the database.
|
|
||||||
func (m *Migration) DropTable(ctx context.Context, table string, ifExists ...bool) error {
|
|
||||||
if len(ifExists) > 0 && ifExists[0] {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"IF OBJECT_ID('%s', 'U') IS NOT NULL DROP TABLE %s",
|
|
||||||
table,
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
sql := fmt.Sprintf("DROP TABLE %s", database.QuoteIdentifier(table))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasTable checks if a table exists in the database.
|
|
||||||
func (m *Migration) HasTable(ctx context.Context, table string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '%s'",
|
|
||||||
table,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameTable renames an existing table from oldName to newName.
|
|
||||||
func (m *Migration) RenameTable(ctx context.Context, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"EXEC sp_rename '%s', '%s'",
|
|
||||||
oldName,
|
|
||||||
newName,
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TruncateTable removes all records from a table but keeps the table structure.
|
|
||||||
func (m *Migration) TruncateTable(ctx context.Context, table string) error {
|
|
||||||
sql := fmt.Sprintf("TRUNCATE TABLE %s", database.QuoteIdentifier(table))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddColumn adds a new column to an existing table.
|
|
||||||
func (m *Migration) AddColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.buildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s ADD %s", database.QuoteIdentifier(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropColumn removes a column from an existing table.
|
|
||||||
func (m *Migration) DropColumn(ctx context.Context, table, column string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP COLUMN %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(column),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameColumn renames a column in an existing table.
|
|
||||||
func (m *Migration) RenameColumn(ctx context.Context, table, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"EXEC sp_rename '%s.%s', '%s', 'COLUMN'",
|
|
||||||
table,
|
|
||||||
oldName,
|
|
||||||
newName,
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyColumn modifies an existing column's definition.
|
|
||||||
func (m *Migration) ModifyColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.buildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", database.QuoteIdentifier(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasColumn checks if a column exists in a table.
|
|
||||||
func (m *Migration) HasColumn(ctx context.Context, table, column string) (bool, error) {
|
|
||||||
fields, err := m.GetDB().TableFields(ctx, table)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
_, exists := fields[column]
|
|
||||||
return exists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIndex creates a new index on the specified table and columns.
|
|
||||||
func (m *Migration) CreateIndex(ctx context.Context, table, index string, columns []string, options ...database.IndexOption) error {
|
|
||||||
var opts database.IndexOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE ")
|
|
||||||
|
|
||||||
if opts.Unique {
|
|
||||||
sql.WriteString("UNIQUE ")
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString("INDEX ")
|
|
||||||
sql.WriteString(database.QuoteIdentifier(index))
|
|
||||||
sql.WriteString(" ON ")
|
|
||||||
sql.WriteString(database.QuoteIdentifier(table))
|
|
||||||
|
|
||||||
colList := m.BuildIndexColumns(columns)
|
|
||||||
sql.WriteString(fmt.Sprintf(" (%s)", colList))
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropIndex drops an existing index from a table.
|
|
||||||
func (m *Migration) DropIndex(ctx context.Context, table, index string) error {
|
|
||||||
sql := fmt.Sprintf("DROP INDEX %s ON %s", database.QuoteIdentifier(index), database.QuoteIdentifier(table))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasIndex checks if an index exists on a table.
|
|
||||||
func (m *Migration) HasIndex(ctx context.Context, table, index string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM sys.indexes WHERE name = '%s' AND object_id = OBJECT_ID('%s')",
|
|
||||||
index,
|
|
||||||
table,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateForeignKey creates a foreign key constraint.
|
|
||||||
func (m *Migration) CreateForeignKey(ctx context.Context, table, constraint string, columns []string, refTable string, refColumns []string, options ...database.ForeignKeyOption) error {
|
|
||||||
var opts database.ForeignKeyOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY %s REFERENCES %s %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(constraint),
|
|
||||||
m.BuildForeignKeyColumns(columns),
|
|
||||||
database.QuoteIdentifier(refTable),
|
|
||||||
m.BuildForeignKeyColumns(refColumns),
|
|
||||||
)
|
|
||||||
|
|
||||||
if opts.OnDelete != "" {
|
|
||||||
sql += fmt.Sprintf(" ON DELETE %s", opts.OnDelete)
|
|
||||||
}
|
|
||||||
if opts.OnUpdate != "" {
|
|
||||||
sql += fmt.Sprintf(" ON UPDATE %s", opts.OnUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropForeignKey drops a foreign key constraint.
|
|
||||||
func (m *Migration) DropForeignKey(ctx context.Context, table, constraint string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP CONSTRAINT %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(constraint),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasForeignKey checks if a foreign key constraint exists.
|
|
||||||
func (m *Migration) HasForeignKey(ctx context.Context, table, constraint string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '%s' AND TABLE_NAME = '%s' AND CONSTRAINT_TYPE = 'FOREIGN KEY'",
|
|
||||||
constraint,
|
|
||||||
table,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateSchema creates a new database schema.
|
|
||||||
func (m *Migration) CreateSchema(ctx context.Context, schema string) error {
|
|
||||||
sql := fmt.Sprintf("CREATE SCHEMA %s", database.QuoteIdentifier(schema))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropSchema drops an existing database schema.
|
|
||||||
func (m *Migration) DropSchema(ctx context.Context, schema string, cascade ...bool) error {
|
|
||||||
sql := fmt.Sprintf("DROP SCHEMA %s", database.QuoteIdentifier(schema))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasSchema checks if a schema exists.
|
|
||||||
func (m *Migration) HasSchema(ctx context.Context, schema string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '%s'",
|
|
||||||
schema,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatDefaultValue formats the default value for SQL.
|
|
||||||
func formatDefaultValue(value any) string {
|
|
||||||
switch v := value.(type) {
|
|
||||||
case string:
|
|
||||||
return fmt.Sprintf("'%s'", escapeString(v))
|
|
||||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
|
||||||
return fmt.Sprintf("%d", v)
|
|
||||||
case float32, float64:
|
|
||||||
return fmt.Sprintf("%f", v)
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
return "1"
|
|
||||||
}
|
|
||||||
return "0"
|
|
||||||
case nil:
|
|
||||||
return "NULL"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("'%v'", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// escapeString escapes special characters in strings for SQL.
|
|
||||||
func escapeString(s string) string {
|
|
||||||
s = strings.ReplaceAll(s, "'", "''")
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
// 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,16 +4,20 @@
|
||||||
// If a copy of the MIT was not distributed with this file,
|
// If a copy of the MIT was not distributed with this file,
|
||||||
// You can obtain one at https://github.com/gogf/gf.
|
// You can obtain one at https://github.com/gogf/gf.
|
||||||
|
|
||||||
// Package mysql implements database.Driver, which supports operations for database MySQL.
|
// Package mysql implements database.Driver for MySQL database.
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
"database/sql"
|
||||||
"git.magicany.cc/black1552/gin-base/database/consts"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
|
||||||
|
"git.magicany.cc/black1552/gin-base/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Driver is the driver for mysql database.
|
// Driver is the driver for MySQL database.
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
*database.Core
|
*database.Core
|
||||||
}
|
}
|
||||||
|
|
@ -26,21 +30,22 @@ func init() {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
driverObj = New()
|
driverObj = New()
|
||||||
driverNames = consts.SliceStr{"mysql", "mariadb", "tidb"} // TODO remove mariadb and tidb in future versions.
|
|
||||||
)
|
)
|
||||||
for _, driverName := range driverNames {
|
// Register for both mysql and mariadb (compatible protocol)
|
||||||
if err = database.Register(driverName, driverObj); err != nil {
|
if err = database.Register("mysql", driverObj); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err = database.Register("mariadb", driverObj); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// New create and returns a driver that implements database.Driver, which supports operations for MySQL.
|
// New creates and returns a driver that implements database.Driver for MySQL.
|
||||||
func New() database.Driver {
|
func New() database.Driver {
|
||||||
return &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.
|
// 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) {
|
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||||
return &Driver{
|
return &Driver{
|
||||||
|
|
@ -48,17 +53,31 @@ func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.D
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for MySQL database.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return quoteChar, quoteChar
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration returns a Migration instance for MySQL database operations.
|
// Open creates and returns an underlying sql.DB object for MySQL.
|
||||||
func (d *Driver) Migration() *Migration {
|
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||||
return NewMigration(d)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMigration returns a Migration instance implementing the Migration interface.
|
// Default params
|
||||||
func (d *Driver) GetMigration() database.Migration {
|
params = append(params, "loc=Local", "parseTime=true")
|
||||||
return d.Migration()
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,365 +0,0 @@
|
||||||
// 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Migration implements database migration operations for MySQL.
|
|
||||||
type Migration struct {
|
|
||||||
*database.MigrationCore
|
|
||||||
*database.AutoMigrateCore
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMigration creates a new MySQL Migration instance.
|
|
||||||
func NewMigration(db database.DB) *Migration {
|
|
||||||
return &Migration{
|
|
||||||
MigrationCore: database.NewMigrationCore(db),
|
|
||||||
AutoMigrateCore: database.NewAutoMigrateCore(db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTable creates a new table with the given name and column definitions.
|
|
||||||
func (m *Migration) CreateTable(ctx context.Context, table string, columns map[string]*database.ColumnDefinition, options ...database.TableOption) error {
|
|
||||||
if len(columns) == 0 {
|
|
||||||
return fmt.Errorf("cannot create table without columns")
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts database.TableOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE TABLE ")
|
|
||||||
if opts.IfNotExists {
|
|
||||||
sql.WriteString("IF NOT EXISTS ")
|
|
||||||
}
|
|
||||||
sql.WriteString(database.QuoteIdentifier(table))
|
|
||||||
sql.WriteString(" (\n")
|
|
||||||
|
|
||||||
// Add columns
|
|
||||||
var colDefs []string
|
|
||||||
var primaryKeys []string
|
|
||||||
for name, def := range columns {
|
|
||||||
colDef := m.BuildColumnDefinition(name, def)
|
|
||||||
if def.PrimaryKey {
|
|
||||||
primaryKeys = append(primaryKeys, database.QuoteIdentifier(name))
|
|
||||||
}
|
|
||||||
colDefs = append(colDefs, " "+colDef)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add primary key constraint if needed
|
|
||||||
if len(primaryKeys) > 0 {
|
|
||||||
colDefs = append(colDefs, fmt.Sprintf(" PRIMARY KEY (%s)", strings.Join(primaryKeys, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString(strings.Join(colDefs, ",\n"))
|
|
||||||
sql.WriteString("\n)")
|
|
||||||
|
|
||||||
// Add table options
|
|
||||||
if opts.Engine != "" {
|
|
||||||
sql.WriteString(fmt.Sprintf(" ENGINE=%s", opts.Engine))
|
|
||||||
}
|
|
||||||
if opts.Charset != "" {
|
|
||||||
sql.WriteString(fmt.Sprintf(" DEFAULT CHARSET=%s", opts.Charset))
|
|
||||||
}
|
|
||||||
if opts.Collation != "" {
|
|
||||||
sql.WriteString(fmt.Sprintf(" COLLATE=%s", opts.Collation))
|
|
||||||
}
|
|
||||||
if opts.Comment != "" {
|
|
||||||
sql.WriteString(fmt.Sprintf(" COMMENT='%s'", escapeString(opts.Comment)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropTable drops an existing table from the database.
|
|
||||||
func (m *Migration) DropTable(ctx context.Context, table string, ifExists ...bool) error {
|
|
||||||
sql := "DROP TABLE "
|
|
||||||
if len(ifExists) > 0 && ifExists[0] {
|
|
||||||
sql += "IF EXISTS "
|
|
||||||
}
|
|
||||||
sql += database.QuoteIdentifier(table)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasTable checks if a table exists in the database.
|
|
||||||
func (m *Migration) HasTable(ctx context.Context, table string) (bool, error) {
|
|
||||||
schema := m.GetDB().GetSchema()
|
|
||||||
if schema == "" {
|
|
||||||
schema = "DATABASE()"
|
|
||||||
} else {
|
|
||||||
schema = fmt.Sprintf("'%s'", schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = %s AND table_name = '%s'",
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameTable renames an existing table from oldName to newName.
|
|
||||||
func (m *Migration) RenameTable(ctx context.Context, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"RENAME TABLE %s TO %s",
|
|
||||||
database.QuoteIdentifier(oldName),
|
|
||||||
database.QuoteIdentifier(newName),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TruncateTable removes all records from a table but keeps the table structure.
|
|
||||||
func (m *Migration) TruncateTable(ctx context.Context, table string) error {
|
|
||||||
sql := fmt.Sprintf("TRUNCATE TABLE %s", database.QuoteIdentifier(table))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddColumn adds a new column to an existing table.
|
|
||||||
func (m *Migration) AddColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.BuildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s", database.QuoteIdentifier(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropColumn removes a column from an existing table.
|
|
||||||
func (m *Migration) DropColumn(ctx context.Context, table, column string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP COLUMN %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(column),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameColumn renames a column in an existing table.
|
|
||||||
func (m *Migration) RenameColumn(ctx context.Context, table, oldName, newName string) error {
|
|
||||||
// MySQL requires the full column definition when renaming
|
|
||||||
fields, err := m.GetDB().TableFields(ctx, table)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
field, ok := fields[oldName]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("column %s does not exist in table %s", oldName, table)
|
|
||||||
}
|
|
||||||
|
|
||||||
def := &database.ColumnDefinition{
|
|
||||||
Type: field.Type,
|
|
||||||
Null: field.Null,
|
|
||||||
Default: field.Default,
|
|
||||||
Comment: field.Comment,
|
|
||||||
AutoIncrement: strings.Contains(field.Extra, "auto_increment"),
|
|
||||||
}
|
|
||||||
|
|
||||||
colDef := m.BuildColumnDefinition(newName, def)
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s CHANGE COLUMN %s %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(oldName),
|
|
||||||
colDef,
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyColumn modifies an existing column's definition.
|
|
||||||
func (m *Migration) ModifyColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.BuildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", database.QuoteIdentifier(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasColumn checks if a column exists in a table.
|
|
||||||
func (m *Migration) HasColumn(ctx context.Context, table, column string) (bool, error) {
|
|
||||||
fields, err := m.GetDB().TableFields(ctx, table)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
_, exists := fields[column]
|
|
||||||
return exists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIndex creates a new index on the specified table and columns.
|
|
||||||
func (m *Migration) CreateIndex(ctx context.Context, table, index string, columns []string, options ...database.IndexOption) error {
|
|
||||||
var opts database.IndexOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE ")
|
|
||||||
|
|
||||||
if opts.Unique {
|
|
||||||
sql.WriteString("UNIQUE ")
|
|
||||||
}
|
|
||||||
if opts.FullText {
|
|
||||||
sql.WriteString("FULLTEXT ")
|
|
||||||
}
|
|
||||||
if opts.Spatial {
|
|
||||||
sql.WriteString("SPATIAL ")
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString("INDEX ")
|
|
||||||
sql.WriteString(database.QuoteIdentifier(index))
|
|
||||||
sql.WriteString(" ON ")
|
|
||||||
sql.WriteString(database.QuoteIdentifier(table))
|
|
||||||
|
|
||||||
colList := m.BuildIndexColumns(columns)
|
|
||||||
sql.WriteString(fmt.Sprintf(" (%s)", colList))
|
|
||||||
|
|
||||||
if opts.Using != "" {
|
|
||||||
sql.WriteString(fmt.Sprintf(" USING %s", opts.Using))
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropIndex drops an existing index from a table.
|
|
||||||
func (m *Migration) DropIndex(ctx context.Context, table, index string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"DROP INDEX %s ON %s",
|
|
||||||
database.QuoteIdentifier(index),
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasIndex checks if an index exists on a table.
|
|
||||||
func (m *Migration) HasIndex(ctx context.Context, table, index string) (bool, error) {
|
|
||||||
schema := m.GetDB().GetSchema()
|
|
||||||
if schema == "" {
|
|
||||||
schema = "DATABASE()"
|
|
||||||
} else {
|
|
||||||
schema = fmt.Sprintf("'%s'", schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = %s AND table_name = '%s' AND index_name = '%s'",
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
index,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateForeignKey creates a foreign key constraint.
|
|
||||||
func (m *Migration) CreateForeignKey(ctx context.Context, table, constraint string, columns []string, refTable string, refColumns []string, options ...database.ForeignKeyOption) error {
|
|
||||||
var opts database.ForeignKeyOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY %s REFERENCES %s %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(constraint),
|
|
||||||
m.BuildForeignKeyColumns(columns),
|
|
||||||
database.QuoteIdentifier(refTable),
|
|
||||||
m.BuildForeignKeyColumns(refColumns),
|
|
||||||
)
|
|
||||||
|
|
||||||
if opts.OnDelete != "" {
|
|
||||||
sql += fmt.Sprintf(" ON DELETE %s", opts.OnDelete)
|
|
||||||
}
|
|
||||||
if opts.OnUpdate != "" {
|
|
||||||
sql += fmt.Sprintf(" ON UPDATE %s", opts.OnUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropForeignKey drops a foreign key constraint.
|
|
||||||
func (m *Migration) DropForeignKey(ctx context.Context, table, constraint string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP FOREIGN KEY %s",
|
|
||||||
database.QuoteIdentifier(table),
|
|
||||||
database.QuoteIdentifier(constraint),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasForeignKey checks if a foreign key constraint exists.
|
|
||||||
func (m *Migration) HasForeignKey(ctx context.Context, table, constraint string) (bool, error) {
|
|
||||||
schema := m.GetDB().GetSchema()
|
|
||||||
if schema == "" {
|
|
||||||
schema = "DATABASE()"
|
|
||||||
} else {
|
|
||||||
schema = fmt.Sprintf("'%s'", schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_schema = %s AND table_name = '%s' AND constraint_name = '%s' AND constraint_type = 'FOREIGN KEY'",
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
constraint,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateSchema creates a new database schema.
|
|
||||||
func (m *Migration) CreateSchema(ctx context.Context, schema string) error {
|
|
||||||
sql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", database.QuoteIdentifier(schema))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropSchema drops an existing database schema.
|
|
||||||
func (m *Migration) DropSchema(ctx context.Context, schema string, cascade ...bool) error {
|
|
||||||
sql := "DROP DATABASE "
|
|
||||||
if len(cascade) > 0 && cascade[0] {
|
|
||||||
sql += "IF EXISTS "
|
|
||||||
}
|
|
||||||
sql += database.QuoteIdentifier(schema)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasSchema checks if a schema exists.
|
|
||||||
func (m *Migration) HasSchema(ctx context.Context, schema string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '%s'",
|
|
||||||
schema,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// escapeString escapes special characters in strings for SQL.
|
|
||||||
func escapeString(s string) string {
|
|
||||||
s = strings.ReplaceAll(s, "'", "''")
|
|
||||||
s = strings.ReplaceAll(s, "\\", "\\\\")
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
-- ----------------------------
|
|
||||||
-- 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);
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
-- ----------------------------
|
|
||||||
-- 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);
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
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\": \"是\"}]');
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
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');
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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');
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
CREATE TABLE `issue3626` (
|
|
||||||
id int(11) NOT NULL,
|
|
||||||
name varchar(45) DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
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');
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
|
|
||||||
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');
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
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,53 +4,58 @@
|
||||||
// If a copy of the MIT was not distributed with this file,
|
// If a copy of the MIT was not distributed with this file,
|
||||||
// You can obtain one at https://github.com/gogf/gf.
|
// You can obtain one at https://github.com/gogf/gf.
|
||||||
|
|
||||||
// Package oracle implements database.Driver, which supports operations for database Oracle.
|
// Package oracle implements database.Driver for Oracle database.
|
||||||
package oracle
|
package oracle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
_ "github.com/sijms/go-ora/v2"
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
"git.magicany.cc/black1552/gin-base/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Driver is the driver for oracle database.
|
// Driver is the driver for Oracle database.
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
*database.Core
|
*database.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
rowNumberAliasForSelect = `ROW_NUMBER__`
|
|
||||||
quoteChar = `"`
|
quoteChar = `"`
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := database.Register(`oracle`, New()); err != nil {
|
if err := database.Register("oracle", New()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create and returns a driver that implements database.Driver, which supports operations for Oracle.
|
// New creates and returns a driver that implements database.Driver for Oracle.
|
||||||
func New() database.Driver {
|
func New() database.Driver {
|
||||||
return &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) {
|
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||||
return &Driver{
|
return &Driver{
|
||||||
Core: core,
|
Core: core,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for Oracle.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return quoteChar, quoteChar
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration returns a Migration instance for Oracle database operations.
|
// Open creates and returns an underlying sql.DB object for Oracle.
|
||||||
func (d *Driver) Migration() *Migration {
|
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||||
return NewMigration(d)
|
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)
|
||||||
// GetMigration returns a Migration instance implementing the Migration interface.
|
|
||||||
func (d *Driver) GetMigration() database.Migration {
|
|
||||||
return d.Migration()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,278 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,351 +0,0 @@
|
||||||
// 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Migration implements database migration operations for Oracle.
|
|
||||||
type Migration struct {
|
|
||||||
*database.MigrationCore
|
|
||||||
*database.AutoMigrateCore
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMigration creates a new Oracle Migration instance.
|
|
||||||
func NewMigration(db database.DB) *Migration {
|
|
||||||
return &Migration{
|
|
||||||
MigrationCore: database.NewMigrationCore(db),
|
|
||||||
AutoMigrateCore: database.NewAutoMigrateCore(db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTable creates a new table with the given name and column definitions.
|
|
||||||
func (m *Migration) CreateTable(ctx context.Context, table string, columns map[string]*database.ColumnDefinition, options ...database.TableOption) error {
|
|
||||||
if len(columns) == 0 {
|
|
||||||
return fmt.Errorf("cannot create table without columns")
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts database.TableOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE TABLE ")
|
|
||||||
sql.WriteString(database.QuoteIdentifierDouble(table))
|
|
||||||
sql.WriteString(" (\n")
|
|
||||||
|
|
||||||
// Add columns
|
|
||||||
var colDefs []string
|
|
||||||
var primaryKeys []string
|
|
||||||
for name, def := range columns {
|
|
||||||
colDef := m.buildColumnDefinition(name, def)
|
|
||||||
if def.PrimaryKey {
|
|
||||||
primaryKeys = append(primaryKeys, database.QuoteIdentifierDouble(name))
|
|
||||||
}
|
|
||||||
colDefs = append(colDefs, " "+colDef)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add primary key constraint if needed
|
|
||||||
if len(primaryKeys) > 0 {
|
|
||||||
colDefs = append(colDefs, fmt.Sprintf(" PRIMARY KEY (%s)", strings.Join(primaryKeys, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString(strings.Join(colDefs, ",\n"))
|
|
||||||
sql.WriteString("\n)")
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildColumnDefinition builds column definition for Oracle.
|
|
||||||
func (m *Migration) buildColumnDefinition(name string, def *database.ColumnDefinition) string {
|
|
||||||
var parts []string
|
|
||||||
parts = append(parts, database.QuoteIdentifierDouble(name))
|
|
||||||
|
|
||||||
// Handle Oracle-specific types
|
|
||||||
dbType := def.Type
|
|
||||||
parts = append(parts, dbType)
|
|
||||||
|
|
||||||
if !def.Null {
|
|
||||||
parts = append(parts, "NOT NULL")
|
|
||||||
}
|
|
||||||
|
|
||||||
if def.Default != nil {
|
|
||||||
defaultValue := formatDefaultValue(def.Default)
|
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", defaultValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropTable drops an existing table from the database.
|
|
||||||
func (m *Migration) DropTable(ctx context.Context, table string, ifExists ...bool) error {
|
|
||||||
if len(ifExists) > 0 && ifExists[0] {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"BEGIN EXECUTE IMMEDIATE 'DROP TABLE %s'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
sql := fmt.Sprintf("DROP TABLE %s", database.QuoteIdentifierDouble(table))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasTable checks if a table exists in the database.
|
|
||||||
func (m *Migration) HasTable(ctx context.Context, table string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM USER_TABLES WHERE TABLE_NAME = '%s'",
|
|
||||||
strings.ToUpper(table),
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameTable renames an existing table from oldName to newName.
|
|
||||||
func (m *Migration) RenameTable(ctx context.Context, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s RENAME TO %s",
|
|
||||||
database.QuoteIdentifierDouble(oldName),
|
|
||||||
database.QuoteIdentifierDouble(newName),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TruncateTable removes all records from a table but keeps the table structure.
|
|
||||||
func (m *Migration) TruncateTable(ctx context.Context, table string) error {
|
|
||||||
sql := fmt.Sprintf("TRUNCATE TABLE %s", database.QuoteIdentifierDouble(table))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddColumn adds a new column to an existing table.
|
|
||||||
func (m *Migration) AddColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.buildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s ADD %s", database.QuoteIdentifierDouble(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropColumn removes a column from an existing table.
|
|
||||||
func (m *Migration) DropColumn(ctx context.Context, table, column string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP COLUMN %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(column),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameColumn renames a column in an existing table.
|
|
||||||
func (m *Migration) RenameColumn(ctx context.Context, table, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s RENAME COLUMN %s TO %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(oldName),
|
|
||||||
database.QuoteIdentifierDouble(newName),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyColumn modifies an existing column's definition.
|
|
||||||
func (m *Migration) ModifyColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.buildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s MODIFY %s", database.QuoteIdentifierDouble(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasColumn checks if a column exists in a table.
|
|
||||||
func (m *Migration) HasColumn(ctx context.Context, table, column string) (bool, error) {
|
|
||||||
fields, err := m.GetDB().TableFields(ctx, table)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
_, exists := fields[column]
|
|
||||||
return exists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIndex creates a new index on the specified table and columns.
|
|
||||||
func (m *Migration) CreateIndex(ctx context.Context, table, index string, columns []string, options ...database.IndexOption) error {
|
|
||||||
var opts database.IndexOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE ")
|
|
||||||
|
|
||||||
if opts.Unique {
|
|
||||||
sql.WriteString("UNIQUE ")
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString("INDEX ")
|
|
||||||
sql.WriteString(database.QuoteIdentifierDouble(index))
|
|
||||||
sql.WriteString(" ON ")
|
|
||||||
sql.WriteString(database.QuoteIdentifierDouble(table))
|
|
||||||
|
|
||||||
colList := m.BuildIndexColumnsWithDouble(columns)
|
|
||||||
sql.WriteString(fmt.Sprintf(" (%s)", colList))
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildIndexColumnsWithDouble builds the column list for index creation using double quotes.
|
|
||||||
func (m *Migration) BuildIndexColumnsWithDouble(columns []string) string {
|
|
||||||
quoted := make([]string, len(columns))
|
|
||||||
for i, col := range columns {
|
|
||||||
quoted[i] = database.QuoteIdentifierDouble(col)
|
|
||||||
}
|
|
||||||
return strings.Join(quoted, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropIndex drops an existing index from a table.
|
|
||||||
func (m *Migration) DropIndex(ctx context.Context, table, index string) error {
|
|
||||||
sql := fmt.Sprintf("DROP INDEX %s", database.QuoteIdentifierDouble(index))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasIndex checks if an index exists on a table.
|
|
||||||
func (m *Migration) HasIndex(ctx context.Context, table, index string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM USER_INDEXES WHERE INDEX_NAME = '%s' AND TABLE_NAME = '%s'",
|
|
||||||
strings.ToUpper(index),
|
|
||||||
strings.ToUpper(table),
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateForeignKey creates a foreign key constraint.
|
|
||||||
func (m *Migration) CreateForeignKey(ctx context.Context, table, constraint string, columns []string, refTable string, refColumns []string, options ...database.ForeignKeyOption) error {
|
|
||||||
var opts database.ForeignKeyOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY %s REFERENCES %s %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(constraint),
|
|
||||||
m.BuildForeignKeyColumnsWithDouble(columns),
|
|
||||||
database.QuoteIdentifierDouble(refTable),
|
|
||||||
m.BuildForeignKeyColumnsWithDouble(refColumns),
|
|
||||||
)
|
|
||||||
|
|
||||||
if opts.OnDelete != "" {
|
|
||||||
sql += fmt.Sprintf(" ON DELETE %s", opts.OnDelete)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildForeignKeyColumnsWithDouble builds the column list for foreign key using double quotes.
|
|
||||||
func (m *Migration) BuildForeignKeyColumnsWithDouble(columns []string) string {
|
|
||||||
quoted := make([]string, len(columns))
|
|
||||||
for i, col := range columns {
|
|
||||||
quoted[i] = database.QuoteIdentifierDouble(col)
|
|
||||||
}
|
|
||||||
return "(" + strings.Join(quoted, ", ") + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropForeignKey drops a foreign key constraint.
|
|
||||||
func (m *Migration) DropForeignKey(ctx context.Context, table, constraint string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP CONSTRAINT %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(constraint),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasForeignKey checks if a foreign key constraint exists.
|
|
||||||
func (m *Migration) HasForeignKey(ctx context.Context, table, constraint string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM USER_CONSTRAINTS WHERE CONSTRAINT_NAME = '%s' AND TABLE_NAME = '%s' AND CONSTRAINT_TYPE = 'R'",
|
|
||||||
strings.ToUpper(constraint),
|
|
||||||
strings.ToUpper(table),
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateSchema creates a new database schema (user in Oracle).
|
|
||||||
func (m *Migration) CreateSchema(ctx context.Context, schema string) error {
|
|
||||||
// In Oracle, schema is equivalent to user
|
|
||||||
sql := fmt.Sprintf("CREATE USER %s IDENTIFIED BY password", database.QuoteIdentifierDouble(schema))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropSchema drops an existing database schema (user in Oracle).
|
|
||||||
func (m *Migration) DropSchema(ctx context.Context, schema string, cascade ...bool) error {
|
|
||||||
sql := "DROP USER "
|
|
||||||
if len(cascade) > 0 && cascade[0] {
|
|
||||||
sql += database.QuoteIdentifierDouble(schema) + " CASCADE"
|
|
||||||
} else {
|
|
||||||
sql += database.QuoteIdentifierDouble(schema)
|
|
||||||
}
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasSchema checks if a schema exists.
|
|
||||||
func (m *Migration) HasSchema(ctx context.Context, schema string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM ALL_USERS WHERE USERNAME = '%s'",
|
|
||||||
strings.ToUpper(schema),
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatDefaultValue formats the default value for SQL.
|
|
||||||
func formatDefaultValue(value any) string {
|
|
||||||
switch v := value.(type) {
|
|
||||||
case string:
|
|
||||||
return fmt.Sprintf("'%s'", escapeString(v))
|
|
||||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
|
||||||
return fmt.Sprintf("%d", v)
|
|
||||||
case float32, float64:
|
|
||||||
return fmt.Sprintf("%f", v)
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
return "1"
|
|
||||||
}
|
|
||||||
return "0"
|
|
||||||
case nil:
|
|
||||||
return "NULL"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("'%v'", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// escapeString escapes special characters in strings for SQL.
|
|
||||||
func escapeString(s string) string {
|
|
||||||
s = strings.ReplaceAll(s, "'", "''")
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
// 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()"
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
// 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,
|
// If a copy of the MIT was not distributed with this file,
|
||||||
// You can obtain one at https://github.com/gogf/gf.
|
// You can obtain one at https://github.com/gogf/gf.
|
||||||
|
|
||||||
// Package pgsql implements database.Driver, which supports operations for database PostgreSQL.
|
// Package pgsql implements database.Driver for PostgreSQL database.
|
||||||
package pgsql
|
package pgsql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
"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 {
|
type Driver struct {
|
||||||
*database.Core
|
*database.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
|
quoteChar = `"`
|
||||||
defaultSchema string = "public"
|
|
||||||
quoteChar string = `"`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := database.Register(`pgsql`, New()); err != nil {
|
if err := database.Register("pgsql", New()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create and returns a driver that implements database.Driver, which supports operations for PostgreSql.
|
// New creates and returns a driver that implements database.Driver for PostgreSQL.
|
||||||
func New() database.Driver {
|
func New() database.Driver {
|
||||||
return &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.
|
// 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) {
|
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
|
||||||
return &Driver{
|
return &Driver{
|
||||||
|
|
@ -44,17 +44,28 @@ func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.D
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for PostgreSQL database.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return quoteChar, quoteChar
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration returns a Migration instance for PostgreSQL database operations.
|
// Open creates and returns an underlying sql.DB object for PostgreSQL.
|
||||||
func (d *Driver) Migration() *Migration {
|
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
|
||||||
return NewMigration(d)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMigration returns a Migration instance implementing the Migration interface.
|
return sql.Open("postgres", source)
|
||||||
func (d *Driver) GetMigration() database.Migration {
|
|
||||||
return d.Migration()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,272 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,481 +0,0 @@
|
||||||
// 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"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gin-base/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Migration implements database migration operations for PostgreSQL.
|
|
||||||
type Migration struct {
|
|
||||||
*database.MigrationCore
|
|
||||||
*database.AutoMigrateCore
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMigration creates a new PostgreSQL Migration instance.
|
|
||||||
func NewMigration(db database.DB) *Migration {
|
|
||||||
return &Migration{
|
|
||||||
MigrationCore: database.NewMigrationCore(db),
|
|
||||||
AutoMigrateCore: database.NewAutoMigrateCore(db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTable creates a new table with the given name and column definitions.
|
|
||||||
func (m *Migration) CreateTable(ctx context.Context, table string, columns map[string]*database.ColumnDefinition, options ...database.TableOption) error {
|
|
||||||
if len(columns) == 0 {
|
|
||||||
return fmt.Errorf("cannot create table without columns")
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts database.TableOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE TABLE ")
|
|
||||||
if opts.IfNotExists {
|
|
||||||
sql.WriteString("IF NOT EXISTS ")
|
|
||||||
}
|
|
||||||
sql.WriteString(database.QuoteIdentifierDouble(table))
|
|
||||||
sql.WriteString(" (\n")
|
|
||||||
|
|
||||||
// Add columns
|
|
||||||
var colDefs []string
|
|
||||||
var primaryKeys []string
|
|
||||||
for name, def := range columns {
|
|
||||||
colDef := m.buildColumnDefinition(name, def)
|
|
||||||
if def.PrimaryKey {
|
|
||||||
primaryKeys = append(primaryKeys, database.QuoteIdentifierDouble(name))
|
|
||||||
}
|
|
||||||
colDefs = append(colDefs, " "+colDef)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add primary key constraint if needed
|
|
||||||
if len(primaryKeys) > 0 {
|
|
||||||
colDefs = append(colDefs, fmt.Sprintf(" PRIMARY KEY (%s)", strings.Join(primaryKeys, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString(strings.Join(colDefs, ",\n"))
|
|
||||||
sql.WriteString("\n)")
|
|
||||||
|
|
||||||
// Add table comment if provided
|
|
||||||
if opts.Comment != "" {
|
|
||||||
commentSQL := fmt.Sprintf(
|
|
||||||
"COMMENT ON TABLE %s IS '%s'",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
escapeString(opts.Comment),
|
|
||||||
)
|
|
||||||
if err := m.ExecuteSQL(ctx, commentSQL); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildColumnDefinition builds column definition for PostgreSQL.
|
|
||||||
func (m *Migration) buildColumnDefinition(name string, def *database.ColumnDefinition) string {
|
|
||||||
var parts []string
|
|
||||||
parts = append(parts, database.QuoteIdentifierDouble(name))
|
|
||||||
|
|
||||||
// Handle PostgreSQL-specific types
|
|
||||||
dbType := def.Type
|
|
||||||
if def.AutoIncrement {
|
|
||||||
if dbType == "INT" || dbType == "INTEGER" {
|
|
||||||
dbType = "SERIAL"
|
|
||||||
} else if dbType == "BIGINT" {
|
|
||||||
dbType = "BIGSERIAL"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parts = append(parts, dbType)
|
|
||||||
|
|
||||||
if def.PrimaryKey {
|
|
||||||
// Primary key is handled separately
|
|
||||||
} else {
|
|
||||||
if !def.Null {
|
|
||||||
parts = append(parts, "NOT NULL")
|
|
||||||
}
|
|
||||||
|
|
||||||
if def.Unique {
|
|
||||||
parts = append(parts, "UNIQUE")
|
|
||||||
}
|
|
||||||
|
|
||||||
if def.Default != nil {
|
|
||||||
defaultValue := formatDefaultValue(def.Default)
|
|
||||||
parts = append(parts, fmt.Sprintf("DEFAULT %s", defaultValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropTable drops an existing table from the database.
|
|
||||||
func (m *Migration) DropTable(ctx context.Context, table string, ifExists ...bool) error {
|
|
||||||
sql := "DROP TABLE "
|
|
||||||
if len(ifExists) > 0 && ifExists[0] {
|
|
||||||
sql += "IF EXISTS "
|
|
||||||
}
|
|
||||||
sql += database.QuoteIdentifierDouble(table)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasTable checks if a table exists in the database.
|
|
||||||
func (m *Migration) HasTable(ctx context.Context, table string) (bool, error) {
|
|
||||||
schema := m.GetDB().GetSchema()
|
|
||||||
if schema == "" {
|
|
||||||
schema = "current_schema()"
|
|
||||||
} else {
|
|
||||||
schema = fmt.Sprintf("'%s'", schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = %s AND table_name = '%s'",
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameTable renames an existing table from oldName to newName.
|
|
||||||
func (m *Migration) RenameTable(ctx context.Context, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s RENAME TO %s",
|
|
||||||
database.QuoteIdentifierDouble(oldName),
|
|
||||||
database.QuoteIdentifierDouble(newName),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TruncateTable removes all records from a table but keeps the table structure.
|
|
||||||
func (m *Migration) TruncateTable(ctx context.Context, table string) error {
|
|
||||||
sql := fmt.Sprintf("TRUNCATE TABLE %s", database.QuoteIdentifierDouble(table))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddColumn adds a new column to an existing table.
|
|
||||||
func (m *Migration) AddColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
colDef := m.buildColumnDefinition(column, definition)
|
|
||||||
sql := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s", database.QuoteIdentifierDouble(table), colDef)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropColumn removes a column from an existing table.
|
|
||||||
func (m *Migration) DropColumn(ctx context.Context, table, column string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP COLUMN %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(column),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameColumn renames a column in an existing table.
|
|
||||||
func (m *Migration) RenameColumn(ctx context.Context, table, oldName, newName string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s RENAME COLUMN %s TO %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(oldName),
|
|
||||||
database.QuoteIdentifierDouble(newName),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyColumn modifies an existing column's definition.
|
|
||||||
func (m *Migration) ModifyColumn(ctx context.Context, table, column string, definition *database.ColumnDefinition) error {
|
|
||||||
// PostgreSQL requires multiple ALTER statements for different modifications
|
|
||||||
var statements []string
|
|
||||||
|
|
||||||
if definition.Type != "" {
|
|
||||||
statements = append(statements, fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ALTER COLUMN %s TYPE %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(column),
|
|
||||||
definition.Type,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if definition.Default != nil {
|
|
||||||
defaultValue := formatDefaultValue(definition.Default)
|
|
||||||
statements = append(statements, fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(column),
|
|
||||||
defaultValue,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
statements = append(statements, fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(column),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !definition.Null {
|
|
||||||
statements = append(statements, fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ALTER COLUMN %s SET NOT NULL",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(column),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
statements = append(statements, fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(column),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute all statements
|
|
||||||
for _, stmt := range statements {
|
|
||||||
if err := m.ExecuteSQL(ctx, stmt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasColumn checks if a column exists in a table.
|
|
||||||
func (m *Migration) HasColumn(ctx context.Context, table, column string) (bool, error) {
|
|
||||||
fields, err := m.GetDB().TableFields(ctx, table)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
_, exists := fields[column]
|
|
||||||
return exists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIndex creates a new index on the specified table and columns.
|
|
||||||
func (m *Migration) CreateIndex(ctx context.Context, table, index string, columns []string, options ...database.IndexOption) error {
|
|
||||||
var opts database.IndexOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql strings.Builder
|
|
||||||
sql.WriteString("CREATE ")
|
|
||||||
|
|
||||||
if opts.Unique {
|
|
||||||
sql.WriteString("UNIQUE ")
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.WriteString("INDEX ")
|
|
||||||
sql.WriteString(database.QuoteIdentifierDouble(index))
|
|
||||||
sql.WriteString(" ON ")
|
|
||||||
sql.WriteString(database.QuoteIdentifierDouble(table))
|
|
||||||
|
|
||||||
colList := m.BuildIndexColumnsWithDouble(columns)
|
|
||||||
sql.WriteString(fmt.Sprintf(" (%s)", colList))
|
|
||||||
|
|
||||||
if opts.Using != "" {
|
|
||||||
sql.WriteString(fmt.Sprintf(" USING %s", opts.Using))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Comment != "" {
|
|
||||||
// Comment will be added after index creation
|
|
||||||
}
|
|
||||||
|
|
||||||
err := m.ExecuteSQL(ctx, sql.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add comment if provided
|
|
||||||
if opts.Comment != "" {
|
|
||||||
commentSQL := fmt.Sprintf(
|
|
||||||
"COMMENT ON INDEX %s IS '%s'",
|
|
||||||
database.QuoteIdentifierDouble(index),
|
|
||||||
escapeString(opts.Comment),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, commentSQL)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildIndexColumnsWithDouble builds the column list for index creation using double quotes.
|
|
||||||
func (m *Migration) BuildIndexColumnsWithDouble(columns []string) string {
|
|
||||||
quoted := make([]string, len(columns))
|
|
||||||
for i, col := range columns {
|
|
||||||
quoted[i] = database.QuoteIdentifierDouble(col)
|
|
||||||
}
|
|
||||||
return strings.Join(quoted, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropIndex drops an existing index from a table.
|
|
||||||
func (m *Migration) DropIndex(ctx context.Context, table, index string) error {
|
|
||||||
sql := fmt.Sprintf("DROP INDEX %s", database.QuoteIdentifierDouble(index))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasIndex checks if an index exists on a table.
|
|
||||||
func (m *Migration) HasIndex(ctx context.Context, table, index string) (bool, error) {
|
|
||||||
schema := m.GetDB().GetSchema()
|
|
||||||
if schema == "" {
|
|
||||||
schema = "current_schema()"
|
|
||||||
} else {
|
|
||||||
schema = fmt.Sprintf("'%s'", schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM pg_indexes WHERE schemaname = %s AND tablename = '%s' AND indexname = '%s'",
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
index,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateForeignKey creates a foreign key constraint.
|
|
||||||
func (m *Migration) CreateForeignKey(ctx context.Context, table, constraint string, columns []string, refTable string, refColumns []string, options ...database.ForeignKeyOption) error {
|
|
||||||
var opts database.ForeignKeyOptions
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY %s REFERENCES %s %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(constraint),
|
|
||||||
m.BuildForeignKeyColumnsWithDouble(columns),
|
|
||||||
database.QuoteIdentifierDouble(refTable),
|
|
||||||
m.BuildForeignKeyColumnsWithDouble(refColumns),
|
|
||||||
)
|
|
||||||
|
|
||||||
if opts.OnDelete != "" {
|
|
||||||
sql += fmt.Sprintf(" ON DELETE %s", opts.OnDelete)
|
|
||||||
}
|
|
||||||
if opts.OnUpdate != "" {
|
|
||||||
sql += fmt.Sprintf(" ON UPDATE %s", opts.OnUpdate)
|
|
||||||
}
|
|
||||||
if opts.Deferrable {
|
|
||||||
sql += " DEFERRABLE"
|
|
||||||
if opts.InitiallyDeferred {
|
|
||||||
sql += " INITIALLY DEFERRED"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildForeignKeyColumnsWithDouble builds the column list for foreign key using double quotes.
|
|
||||||
func (m *Migration) BuildForeignKeyColumnsWithDouble(columns []string) string {
|
|
||||||
quoted := make([]string, len(columns))
|
|
||||||
for i, col := range columns {
|
|
||||||
quoted[i] = database.QuoteIdentifierDouble(col)
|
|
||||||
}
|
|
||||||
return "(" + strings.Join(quoted, ", ") + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropForeignKey drops a foreign key constraint.
|
|
||||||
func (m *Migration) DropForeignKey(ctx context.Context, table, constraint string) error {
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"ALTER TABLE %s DROP CONSTRAINT %s",
|
|
||||||
database.QuoteIdentifierDouble(table),
|
|
||||||
database.QuoteIdentifierDouble(constraint),
|
|
||||||
)
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasForeignKey checks if a foreign key constraint exists.
|
|
||||||
func (m *Migration) HasForeignKey(ctx context.Context, table, constraint string) (bool, error) {
|
|
||||||
schema := m.GetDB().GetSchema()
|
|
||||||
if schema == "" {
|
|
||||||
schema = "current_schema()"
|
|
||||||
} else {
|
|
||||||
schema = fmt.Sprintf("'%s'", schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_schema = %s AND table_name = '%s' AND constraint_name = '%s' AND constraint_type = 'FOREIGN KEY'",
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
constraint,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateSchema creates a new database schema.
|
|
||||||
func (m *Migration) CreateSchema(ctx context.Context, schema string) error {
|
|
||||||
sql := fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", database.QuoteIdentifierDouble(schema))
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DropSchema drops an existing database schema.
|
|
||||||
func (m *Migration) DropSchema(ctx context.Context, schema string, cascade ...bool) error {
|
|
||||||
sql := "DROP SCHEMA "
|
|
||||||
if len(cascade) > 0 && cascade[0] {
|
|
||||||
sql += "IF EXISTS "
|
|
||||||
sql += database.QuoteIdentifierDouble(schema) + " CASCADE"
|
|
||||||
} else {
|
|
||||||
sql += "IF EXISTS " + database.QuoteIdentifierDouble(schema)
|
|
||||||
}
|
|
||||||
return m.ExecuteSQL(ctx, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasSchema checks if a schema exists.
|
|
||||||
func (m *Migration) HasSchema(ctx context.Context, schema string) (bool, error) {
|
|
||||||
query := fmt.Sprintf(
|
|
||||||
"SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '%s'",
|
|
||||||
schema,
|
|
||||||
)
|
|
||||||
|
|
||||||
value, err := m.GetDB().GetValue(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Int() > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatDefaultValue formats the default value for SQL.
|
|
||||||
func formatDefaultValue(value any) string {
|
|
||||||
switch v := value.(type) {
|
|
||||||
case string:
|
|
||||||
return fmt.Sprintf("'%s'", escapeString(v))
|
|
||||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
|
||||||
return fmt.Sprintf("%d", v)
|
|
||||||
case float32, float64:
|
|
||||||
return fmt.Sprintf("%f", v)
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
return "TRUE"
|
|
||||||
}
|
|
||||||
return "FALSE"
|
|
||||||
case nil:
|
|
||||||
return "NULL"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("'%v'", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// escapeString escapes special characters in strings for SQL.
|
|
||||||
func escapeString(s string) string {
|
|
||||||
s = strings.ReplaceAll(s, "'", "''")
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
// 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()"
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
// 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 ""
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue