feat(database): 添加 ClickHouse 数据库驱动支持

- 实现了完整的 ClickHouse 驱动,包括连接、查询、插入、更新、删除等基本操作
- 添加了 ClickHouse 特有的数据类型转换和 SQL 过滤功能
- 实现了表结构查询和字段信息获取功能
- 添加了 Ping 检查和错误处理机制
- 增加了对 UPDATE 和 DELETE 语句的语法转换以适配 ClickHouse
- 添加了批量插入操作的支持
- 新增了 14 个测试用例文件用于验证数据库相关功能
This commit is contained in:
maguodong
2026-04-08 15:34:51 +08:00
parent 70ad831041
commit 398e732301
109 changed files with 5275 additions and 545 deletions
+9 -24
View File
@@ -4,58 +4,43 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package oracle implements database.Driver for Oracle database.
// Package oracle implements database.Driver, which supports operations for database Oracle.
package oracle
import (
"database/sql"
"fmt"
_ "github.com/sijms/go-ora/v2"
"git.magicany.cc/black1552/gin-base/database"
)
// Driver is the driver for Oracle database.
// Driver is the driver for oracle database.
type Driver struct {
*database.Core
}
const (
quoteChar = `"`
rowNumberAliasForSelect = `ROW_NUMBER__`
quoteChar = `"`
)
func init() {
if err := database.Register("oracle", New()); err != nil {
if err := database.Register(`oracle`, New()); err != nil {
panic(err)
}
}
// New creates and returns a driver that implements database.Driver for Oracle.
// New create and returns a driver that implements database.Driver, which supports operations for Oracle.
func New() database.Driver {
return &Driver{}
}
// New creates and returns a database object for Oracle.
// New creates and returns a database object for oracle.
// It implements the interface of database.Driver for extra database driver installation.
func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.DB, error) {
return &Driver{
Core: core,
}, nil
}
// GetChars returns the security char for Oracle.
// GetChars returns the security char for this type of database.
func (d *Driver) GetChars() (charLeft string, charRight string) {
return quoteChar, quoteChar
}
// Open creates and returns an underlying sql.DB object for Oracle.
func (d *Driver) Open(config *database.ConfigNode) (*sql.DB, error) {
var source string
if config.Link != "" {
source = config.Link
} else {
source = fmt.Sprintf("oracle://%s:%s@%s:%s/%s",
config.User, config.Pass, config.Host, config.Port, config.Name)
}
return sql.Open("oracle", source)
}
@@ -0,0 +1,29 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
import (
"context"
"git.magicany.cc/black1552/gin-base/database"
)
// DoCommit commits current sql and arguments to underlying sql driver.
func (d *Driver) DoCommit(ctx context.Context, in database.DoCommitInput) (out database.DoCommitOutput, err error) {
out, err = d.Core.DoCommit(ctx, in)
if err != nil {
return
}
if len(out.Records) > 0 {
// remove auto added field.
for i, record := range out.Records {
delete(record, rowNumberAliasForSelect)
out.Records[i] = record
}
}
return
}
+120
View File
@@ -0,0 +1,120 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
import (
"context"
"database/sql"
"fmt"
"strings"
"git.magicany.cc/black1552/gin-base/database"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
)
const (
returningClause = " RETURNING %s INTO ?"
)
// DoExec commits the sql string and its arguments to underlying driver
// through given link object and returns the execution result.
// It handles INSERT statements specially to support LastInsertId.
func (d *Driver) DoExec(
ctx context.Context, link database.Link, sql string, args ...interface{},
) (result sql.Result, err error) {
var (
isUseCoreDoExec = true
primaryKey string
pkField database.TableField
)
// Transaction checks.
if link == nil {
if tx := database.TXFromCtx(ctx, d.GetGroup()); tx != nil {
link = tx
} else if link, err = d.MasterLink(); err != nil {
return nil, err
}
} else if !link.IsTransaction() {
if tx := database.TXFromCtx(ctx, d.GetGroup()); tx != nil {
link = tx
}
}
// Check if it is an insert operation with primary key from context.
if value := ctx.Value(internalPrimaryKeyInCtx); value != nil {
if field, ok := value.(database.TableField); ok {
pkField = field
isUseCoreDoExec = false
}
}
// Check if it is an INSERT statement with primary key.
if !isUseCoreDoExec && pkField.Name != "" && strings.Contains(strings.ToUpper(sql), "INSERT INTO") {
primaryKey = pkField.Name
// Oracle supports RETURNING clause to get the last inserted id
sql += fmt.Sprintf(returningClause, d.QuoteWord(primaryKey))
} else {
// Use default DoExec for non-INSERT or no primary key scenarios
return d.Core.DoExec(ctx, link, sql, args...)
}
// Only the insert operation with primary key can execute the following code
// SQL filtering.
sql, args = d.FormatSqlBeforeExecuting(sql, args)
sql, args, err = d.DoFilter(ctx, link, sql, args)
if err != nil {
return nil, err
}
// Prepare output variable for RETURNING clause
var lastInsertId int64
// Append the output parameter for the RETURNING clause
args = append(args, &lastInsertId)
// Link execution.
_, err = d.DoCommit(ctx, database.DoCommitInput{
Link: link,
Sql: sql,
Args: args,
Stmt: nil,
Type: database.SqlTypeExecContext,
IsTransaction: link.IsTransaction(),
})
if err != nil {
return &Result{
lastInsertId: 0,
rowsAffected: 0,
lastInsertIdError: err,
}, err
}
// Get rows affected from the result
// For single insert with RETURNING clause, affected is always 1
var affected int64 = 1
// Check if the primary key field type supports LastInsertId
if !strings.Contains(strings.ToLower(pkField.Type), "int") {
return &Result{
lastInsertId: 0,
rowsAffected: affected,
lastInsertIdError: gerror.NewCodef(
gcode.CodeNotSupported,
"LastInsertId is not supported by primary key type: %s",
pkField.Type,
),
}, nil
}
return &Result{
lastInsertId: lastInsertId,
rowsAffected: affected,
}, nil
}
+143
View File
@@ -0,0 +1,143 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
import (
"context"
"fmt"
"strconv"
"strings"
"git.magicany.cc/black1552/gin-base/database"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
var (
newSqlReplacementTmp = `
SELECT * FROM (
SELECT GFORM.*, ROWNUM ROW_NUMBER__ FROM (%s %s) GFORM WHERE ROWNUM <= %d
) WHERE ROW_NUMBER__ > %d
`
)
func init() {
var err error
newSqlReplacementTmp, err = database.FormatMultiLineSqlToSingle(newSqlReplacementTmp)
if err != nil {
panic(err)
}
}
// DoFilter deals with the sql string before commits it to underlying sql driver.
func (d *Driver) DoFilter(ctx context.Context, link database.Link, sql string, args []any) (newSql string, newArgs []any, err error) {
var index int
newArgs = args
// Convert placeholder char '?' to string ":vx".
newSql, err = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
index++
return fmt.Sprintf(":v%d", index)
})
if err != nil {
return
}
newSql, err = gregex.ReplaceString("\"", "", newSql)
if err != nil {
return
}
newSql, err = d.parseSql(newSql)
if err != nil {
return
}
return d.Core.DoFilter(ctx, link, newSql, newArgs)
}
// parseSql does some replacement of the sql before commits it to underlying driver,
// for support of oracle server.
func (d *Driver) parseSql(toBeCommittedSql string) (string, error) {
var (
err error
operation = gstr.StrTillEx(toBeCommittedSql, " ")
keyword = strings.ToUpper(gstr.Trim(operation))
)
switch keyword {
case "SELECT":
toBeCommittedSql, err = d.handleSelectSqlReplacement(toBeCommittedSql)
if err != nil {
return "", err
}
}
return toBeCommittedSql, nil
}
func (d *Driver) handleSelectSqlReplacement(toBeCommittedSql string) (newSql string, err error) {
var (
match [][]string
patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))`
)
match, err = gregex.MatchAllString(patten, toBeCommittedSql)
if err != nil {
return "", err
}
if len(match) == 0 {
return toBeCommittedSql, nil
}
var index = 1
if len(match) < 2 || strings.HasPrefix(match[index][0], "LIMIT") == false {
return toBeCommittedSql, nil
}
// only handle `SELECT ... LIMIT ...` statement.
queryExpr, err := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", toBeCommittedSql)
if err != nil {
return "", err
}
if len(queryExpr) == 0 {
return toBeCommittedSql, nil
}
if len(queryExpr) != 4 ||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
return toBeCommittedSql, nil
}
page, limit := 0, 0
for i := 1; i < len(match[index]); i++ {
if len(strings.TrimSpace(match[index][i])) == 0 {
continue
}
if strings.HasPrefix(match[index][i], "LIMIT") {
if match[index][i+2] != "" {
page, err = strconv.Atoi(match[index][i+1])
if err != nil {
return "", err
}
limit, err = strconv.Atoi(match[index][i+2])
if err != nil {
return "", err
}
if page <= 0 {
page = 1
}
limit = (page/limit + 1) * limit
page, err = strconv.Atoi(match[index][i+1])
if err != nil {
return "", err
}
} else {
limit, err = strconv.Atoi(match[index][i+1])
if err != nil {
return "", err
}
}
break
}
}
var newReplacedSql = fmt.Sprintf(
newSqlReplacementTmp,
queryExpr[1], queryExpr[2], limit, page,
)
return newReplacedSql, nil
}
+278
View File
@@ -0,0 +1,278 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
import (
"context"
"database/sql"
"fmt"
"strings"
"git.magicany.cc/black1552/gin-base/database"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
const (
internalPrimaryKeyInCtx gctx.StrKey = "primary_key_field"
)
// DoInsert inserts or updates data for given table.
// The list parameter must contain at least one record, which was previously validated.
func (d *Driver) DoInsert(
ctx context.Context, link database.Link, table string, list database.List, option database.DoInsertOption,
) (result sql.Result, err error) {
switch option.InsertOption {
case database.InsertOptionSave:
return d.doSave(ctx, link, table, list, option)
case database.InsertOptionReplace:
// Oracle does not support REPLACE INTO syntax, use SAVE instead.
return d.doSave(ctx, link, table, list, option)
case database.InsertOptionIgnore:
// Oracle does not support INSERT IGNORE syntax, use MERGE instead.
return d.doInsertIgnore(ctx, link, table, list, option)
case database.InsertOptionDefault:
// For default insert, set primary key field in context to support LastInsertId.
// Only set it when the primary key is not provided in the data, for performance reason.
tableFields, err := d.GetCore().GetDB().TableFields(ctx, table)
if err == nil && len(list) > 0 {
for _, field := range tableFields {
if strings.EqualFold(field.Key, "pri") {
// Check if primary key is provided in the data.
pkProvided := false
for key := range list[0] {
if strings.EqualFold(key, field.Name) {
pkProvided = true
break
}
}
// Only use RETURNING when primary key is not provided, for performance reason.
if !pkProvided {
pkField := *field
ctx = context.WithValue(ctx, internalPrimaryKeyInCtx, pkField)
}
break
}
}
}
default:
}
var (
keys []string
values []string
params []any
)
// Retrieve the table fields and length.
var (
listLength = len(list)
valueHolder = make([]string, 0)
)
for k := range list[0] {
keys = append(keys, k)
valueHolder = append(valueHolder, "?")
}
var (
batchResult = new(database.SqlResult)
charL, charR = d.GetChars()
keyStr = charL + strings.Join(keys, charL+","+charR) + charR
valueHolderStr = strings.Join(valueHolder, ",")
)
// Format "INSERT...INTO..." statement.
// Note: Use standard INSERT INTO syntax instead of INSERT ALL to ensure triggers fire
for i := 0; i < listLength; i++ {
for _, k := range keys {
if s, ok := list[i][k].(database.Raw); ok {
params = append(params, gconv.String(s))
} else {
params = append(params, list[i][k])
}
}
values = append(values, valueHolderStr)
// Execute individual INSERT for each record to trigger row-level triggers
r, err := d.DoExec(ctx, link, fmt.Sprintf(
"INSERT INTO %s(%s) VALUES(%s)",
table, keyStr, valueHolderStr,
), params...)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.Result = r
batchResult.Affected += n
}
params = params[:0]
}
return batchResult, nil
}
// doSave support upsert for Oracle
func (d *Driver) doSave(ctx context.Context,
link database.Link, table string, list database.List, option database.DoInsertOption,
) (result sql.Result, err error) {
return d.doMergeInsert(ctx, link, table, list, option, true)
}
// doInsertIgnore implements INSERT IGNORE operation using MERGE statement for Oracle database.
// It only inserts records when there's no conflict on primary/unique keys.
func (d *Driver) doInsertIgnore(ctx context.Context,
link database.Link, table string, list database.List, option database.DoInsertOption,
) (result sql.Result, err error) {
return d.doMergeInsert(ctx, link, table, list, option, false)
}
// doMergeInsert implements MERGE-based insert operations for Oracle database.
// When withUpdate is true, it performs upsert (insert or update).
// When withUpdate is false, it performs insert ignore (insert only when no conflict).
func (d *Driver) doMergeInsert(
ctx context.Context,
link database.Link, table string, list database.List, option database.DoInsertOption, withUpdate bool,
) (result sql.Result, err error) {
// If OnConflict is not specified, automatically get the primary key of the table
conflictKeys := option.OnConflict
if len(conflictKeys) == 0 {
primaryKeys, err := d.Core.GetPrimaryKeys(ctx, table)
if err != nil {
return nil, gerror.WrapCode(
gcode.CodeInternalError,
err,
`failed to get primary keys for table`,
)
}
foundPrimaryKey := false
for _, primaryKey := range primaryKeys {
for dataKey := range list[0] {
if strings.EqualFold(dataKey, primaryKey) {
foundPrimaryKey = true
break
}
}
if foundPrimaryKey {
break
}
}
if !foundPrimaryKey {
return nil, gerror.NewCodef(
gcode.CodeMissingParameter,
`Replace/Save/InsertIgnore operation requires conflict detection: `+
`either specify OnConflict() columns or ensure table '%s' has a primary key in the data`,
table,
)
}
// TODO consider composite primary keys.
conflictKeys = primaryKeys
}
var (
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
conflictKeySet = gset.NewStrSet(false)
// queryHolders: Handle data with Holder that need to be upsert
// queryValues: Handle data that need to be upsert
// insertKeys: Handle valid keys that need to be inserted
// insertValues: Handle values that need to be inserted
// updateValues: Handle values that need to be updated
queryHolders = make([]string, oneLen)
queryValues = make([]any, oneLen)
insertKeys = make([]string, oneLen)
insertValues = make([]string, oneLen)
updateValues []string
)
// conflictKeys slice type conv to set type
for _, conflictKey := range conflictKeys {
conflictKeySet.Add(gstr.ToUpper(conflictKey))
}
index := 0
for key, value := range one {
keyWithChar := charL + key + charR
queryHolders[index] = fmt.Sprintf("? AS %s", keyWithChar)
queryValues[index] = value
insertKeys[index] = keyWithChar
insertValues[index] = fmt.Sprintf("T2.%s", keyWithChar)
// Build updateValues only when withUpdate is true
// Filter conflict keys and soft created fields from updateValues
if withUpdate && !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
updateValues = append(
updateValues,
fmt.Sprintf(`T1.%s = T2.%s`, keyWithChar, keyWithChar),
)
}
index++
}
var (
batchResult = new(database.SqlResult)
sqlStr = parseSqlForMerge(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
)
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.Result = r
batchResult.Affected += n
}
return batchResult, nil
}
// parseSqlForMerge generates MERGE statement for Oracle database.
// When updateValues is empty, it only inserts (INSERT IGNORE behavior).
// When updateValues is provided, it performs upsert (INSERT or UPDATE).
// Examples:
// - INSERT IGNORE: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...)
// - UPSERT: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...) WHEN MATCHED THEN UPDATE SET ...
func parseSqlForMerge(table string,
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
) (sqlStr string) {
var (
queryHolderStr = strings.Join(queryHolders, ",")
insertKeyStr = strings.Join(insertKeys, ",")
insertValueStr = strings.Join(insertValues, ",")
duplicateKeyStr string
)
// Build ON condition
for index, keys := range duplicateKey {
if index != 0 {
duplicateKeyStr += " AND "
}
duplicateKeyStr += fmt.Sprintf("T1.%s = T2.%s", keys, keys)
}
// Build SQL based on whether UPDATE is needed
pattern := gstr.Trim(
`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN ` +
`NOT MATCHED THEN INSERT(%s) VALUES (%s)`,
)
if len(updateValues) > 0 {
// Upsert: INSERT or UPDATE
pattern += gstr.Trim(` WHEN MATCHED THEN UPDATE SET %s`)
return fmt.Sprintf(
pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr,
strings.Join(updateValues, ","),
)
}
// Insert Ignore: INSERT only
return fmt.Sprintf(pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr)
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
import (
"database/sql"
"strings"
gora "github.com/sijms/go-ora/v2"
"git.magicany.cc/black1552/gin-base/database"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/util/gconv"
)
// Open creates and returns an underlying sql.DB object for oracle.
func (d *Driver) Open(config *database.ConfigNode) (db *sql.DB, err error) {
var (
source string
underlyingDriverName = "oracle"
)
options := map[string]string{
"CONNECTION TIMEOUT": "60",
"PREFETCH_ROWS": "25",
}
if config.Debug {
options["TRACE FILE"] = "oracle_trace.log"
}
// [username:[password]@]host[:port][/service_name][?param1=value1&...&paramN=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
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
// OrderRandomFunction returns the SQL function for random ordering.
func (d *Driver) OrderRandomFunction() string {
return "DBMS_RANDOM.VALUE()"
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
// Result implements sql.Result interface for Oracle database.
type Result struct {
lastInsertId int64
rowsAffected int64
lastInsertIdError error
}
// LastInsertId returns the last insert id.
func (r *Result) LastInsertId() (int64, error) {
return r.lastInsertId, r.lastInsertIdError
}
// RowsAffected returns the rows affected.
func (r *Result) RowsAffected() (int64, error) {
return r.rowsAffected, nil
}
@@ -0,0 +1,84 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
import (
"context"
"fmt"
"strings"
"git.magicany.cc/black1552/gin-base/database"
"github.com/gogf/gf/v2/util/gutil"
)
var (
tableFieldsSqlTmp = `
SELECT
c.COLUMN_NAME AS FIELD,
CASE
WHEN (c.DATA_TYPE='NUMBER' AND NVL(c.DATA_SCALE,0)=0) THEN 'INT'||'('||c.DATA_PRECISION||','||c.DATA_SCALE||')'
WHEN (c.DATA_TYPE='NUMBER' AND NVL(c.DATA_SCALE,0)>0) THEN 'FLOAT'||'('||c.DATA_PRECISION||','||c.DATA_SCALE||')'
WHEN c.DATA_TYPE='FLOAT' THEN c.DATA_TYPE||'('||c.DATA_PRECISION||','||c.DATA_SCALE||')'
ELSE c.DATA_TYPE||'('||c.DATA_LENGTH||')' END AS TYPE,
c.NULLABLE,
CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 'PRI' ELSE '' END AS KEY
FROM USER_TAB_COLUMNS c
LEFT JOIN (
SELECT cols.COLUMN_NAME
FROM USER_CONSTRAINTS cons
JOIN USER_CONS_COLUMNS cols ON cons.CONSTRAINT_NAME = cols.CONSTRAINT_NAME
WHERE cons.TABLE_NAME = '%s' AND cons.CONSTRAINT_TYPE = 'P'
) pk ON c.COLUMN_NAME = pk.COLUMN_NAME
WHERE c.TABLE_NAME = '%s'
ORDER BY c.COLUMN_ID
`
)
func init() {
var err error
tableFieldsSqlTmp, err = database.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
if err != nil {
panic(err)
}
}
// TableFields retrieves and returns the fields' information of specified table of current schema.
//
// Also see DriverMysql.TableFields.
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*database.TableField, err error) {
var (
result database.Result
link database.Link
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
upperTable = strings.ToUpper(table)
structureSql = fmt.Sprintf(tableFieldsSqlTmp, upperTable, upperTable)
)
if link, err = d.SlaveLink(usedSchema); err != nil {
return nil, err
}
result, err = d.DoSelect(ctx, link, structureSql)
if err != nil {
return nil, err
}
fields = make(map[string]*database.TableField)
for i, m := range result {
isNull := false
if m["NULLABLE"].String() == "Y" {
isNull = true
}
fields[m["FIELD"].String()] = &database.TableField{
Index: i,
Name: m["FIELD"].String(),
Type: m["TYPE"].String(),
Null: isNull,
Key: m["KEY"].String(),
}
}
return fields, nil
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package oracle
import (
"context"
"git.magicany.cc/black1552/gin-base/database"
)
const (
tablesSqlTmp = `SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME`
)
// Tables retrieves and returns the tables of current schema.
// It's mainly used in cli tool chain for automatically generating the models.
// Note that it ignores the parameter `schema` in oracle database, as it is not necessary.
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
var result database.Result
// DO NOT use `usedSchema` as parameter for function `SlaveLink`.
link, err := d.SlaveLink(schema...)
if err != nil {
return nil, err
}
result, err = d.DoSelect(ctx, link, tablesSqlTmp)
if err != nil {
return
}
for _, m := range result {
for _, v := range m {
tables = append(tables, v.String())
}
}
return
}