mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.
**Type alias migration to `any`:**
* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)
**Generated code and templates:**
* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
**Container and utility updates:**
* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)
These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
526 lines
15 KiB
Go
526 lines
15 KiB
Go
// 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 gdb
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"github.com/gogf/gf/v2"
|
|
"github.com/gogf/gf/v2/container/gvar"
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/internal/intlog"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
"github.com/gogf/gf/v2/util/guid"
|
|
)
|
|
|
|
// Query commits one query SQL to underlying driver and returns the execution result.
|
|
// It is most commonly used for data querying.
|
|
func (c *Core) Query(ctx context.Context, sql string, args ...any) (result Result, err error) {
|
|
return c.db.DoQuery(ctx, nil, sql, args...)
|
|
}
|
|
|
|
// DoQuery commits the sql string and its arguments to underlying driver
|
|
// through given link object and returns the execution result.
|
|
func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...any) (result Result, err error) {
|
|
// Transaction checks.
|
|
if link == nil {
|
|
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
|
// Firstly, check and retrieve transaction link from context.
|
|
link = &txLink{tx.GetSqlTX()}
|
|
} else if link, err = c.SlaveLink(); 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 := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
|
link = &txLink{tx.GetSqlTX()}
|
|
}
|
|
}
|
|
|
|
// Sql filtering.
|
|
sql, args = c.FormatSqlBeforeExecuting(sql, args)
|
|
sql, args, err = c.db.DoFilter(ctx, link, sql, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// SQL format and retrieve.
|
|
if v := ctx.Value(ctxKeyCatchSQL); v != nil {
|
|
var (
|
|
manager = v.(*CatchSQLManager)
|
|
formattedSql = FormatSqlWithArgs(sql, args)
|
|
)
|
|
manager.SQLArray.Append(formattedSql)
|
|
if !manager.DoCommit && ctx.Value(ctxKeyInternalProducedSQL) == nil {
|
|
return nil, nil
|
|
}
|
|
}
|
|
// Link execution.
|
|
var out DoCommitOutput
|
|
out, err = c.db.DoCommit(ctx, DoCommitInput{
|
|
Link: link,
|
|
Sql: sql,
|
|
Args: args,
|
|
Stmt: nil,
|
|
Type: SqlTypeQueryContext,
|
|
IsTransaction: link.IsTransaction(),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return out.Records, err
|
|
}
|
|
|
|
// Exec commits one query SQL to underlying driver and returns the execution result.
|
|
// It is most commonly used for data inserting and updating.
|
|
func (c *Core) Exec(ctx context.Context, sql string, args ...any) (result sql.Result, err error) {
|
|
return c.db.DoExec(ctx, nil, sql, args...)
|
|
}
|
|
|
|
// DoExec commits the sql string and its arguments to underlying driver
|
|
// through given link object and returns the execution result.
|
|
func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...any) (result sql.Result, err error) {
|
|
// Transaction checks.
|
|
if link == nil {
|
|
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
|
// Firstly, check and retrieve transaction link from context.
|
|
link = &txLink{tx.GetSqlTX()}
|
|
} else if link, err = c.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 tries retrieving transaction object from context.
|
|
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
|
link = &txLink{tx.GetSqlTX()}
|
|
}
|
|
}
|
|
|
|
// SQL filtering.
|
|
sql, args = c.FormatSqlBeforeExecuting(sql, args)
|
|
sql, args, err = c.db.DoFilter(ctx, link, sql, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// SQL format and retrieve.
|
|
if v := ctx.Value(ctxKeyCatchSQL); v != nil {
|
|
var (
|
|
manager = v.(*CatchSQLManager)
|
|
formattedSql = FormatSqlWithArgs(sql, args)
|
|
)
|
|
manager.SQLArray.Append(formattedSql)
|
|
if !manager.DoCommit && ctx.Value(ctxKeyInternalProducedSQL) == nil {
|
|
return new(SqlResult), nil
|
|
}
|
|
}
|
|
// Link execution.
|
|
var out DoCommitOutput
|
|
out, err = c.db.DoCommit(ctx, DoCommitInput{
|
|
Link: link,
|
|
Sql: sql,
|
|
Args: args,
|
|
Stmt: nil,
|
|
Type: SqlTypeExecContext,
|
|
IsTransaction: link.IsTransaction(),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return out.Result, err
|
|
}
|
|
|
|
// DoFilter is a hook function, which filters the sql and its arguments before it's committed to underlying driver.
|
|
// The parameter `link` specifies the current database connection operation object. You can modify the sql
|
|
// string `sql` and its arguments `args` as you wish before they're committed to driver.
|
|
func (c *Core) DoFilter(
|
|
ctx context.Context, link Link, sql string, args []any,
|
|
) (newSql string, newArgs []any, err error) {
|
|
return sql, args, nil
|
|
}
|
|
|
|
// DoCommit commits current sql and arguments to underlying sql driver.
|
|
func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) {
|
|
var (
|
|
sqlTx *sql.Tx
|
|
sqlStmt *sql.Stmt
|
|
sqlRows *sql.Rows
|
|
sqlResult sql.Result
|
|
stmtSqlRows *sql.Rows
|
|
stmtSqlRow *sql.Row
|
|
rowsAffected int64
|
|
cancelFuncForTimeout context.CancelFunc
|
|
formattedSql = FormatSqlWithArgs(in.Sql, in.Args)
|
|
timestampMilli1 = gtime.TimestampMilli()
|
|
)
|
|
|
|
// Trace span start.
|
|
tr := otel.GetTracerProvider().Tracer(traceInstrumentName, trace.WithInstrumentationVersion(gf.VERSION))
|
|
ctx, span := tr.Start(ctx, string(in.Type), trace.WithSpanKind(trace.SpanKindClient))
|
|
defer span.End()
|
|
|
|
// Execution by type.
|
|
switch in.Type {
|
|
case SqlTypeBegin:
|
|
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeTrans)
|
|
formattedSql = fmt.Sprintf(
|
|
`%s (IosolationLevel: %s, ReadOnly: %t)`,
|
|
formattedSql, in.TxOptions.Isolation.String(), in.TxOptions.ReadOnly,
|
|
)
|
|
if sqlTx, err = in.Db.BeginTx(ctx, &in.TxOptions); err == nil {
|
|
tx := &TXCore{
|
|
db: c.db,
|
|
tx: sqlTx,
|
|
ctx: ctx,
|
|
master: in.Db,
|
|
transactionId: guid.S(),
|
|
cancelFunc: cancelFuncForTimeout,
|
|
}
|
|
tx.ctx = context.WithValue(ctx, transactionKeyForContext(tx.db.GetGroup()), tx)
|
|
tx.ctx = context.WithValue(tx.ctx, transactionIdForLoggerCtx, transactionIdGenerator.Add(1))
|
|
out.Tx = tx
|
|
ctx = out.Tx.GetCtx()
|
|
}
|
|
out.RawResult = sqlTx
|
|
|
|
case SqlTypeTXCommit:
|
|
if in.TxCancelFunc != nil {
|
|
defer in.TxCancelFunc()
|
|
}
|
|
err = in.Tx.Commit()
|
|
|
|
case SqlTypeTXRollback:
|
|
if in.TxCancelFunc != nil {
|
|
defer in.TxCancelFunc()
|
|
}
|
|
err = in.Tx.Rollback()
|
|
|
|
case SqlTypeExecContext:
|
|
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeExec)
|
|
defer cancelFuncForTimeout()
|
|
if c.db.GetDryRun() {
|
|
sqlResult = new(SqlResult)
|
|
} else {
|
|
sqlResult, err = in.Link.ExecContext(ctx, in.Sql, in.Args...)
|
|
}
|
|
out.RawResult = sqlResult
|
|
|
|
case SqlTypeQueryContext:
|
|
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
|
|
defer cancelFuncForTimeout()
|
|
sqlRows, err = in.Link.QueryContext(ctx, in.Sql, in.Args...)
|
|
out.RawResult = sqlRows
|
|
|
|
case SqlTypePrepareContext:
|
|
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypePrepare)
|
|
defer cancelFuncForTimeout()
|
|
sqlStmt, err = in.Link.PrepareContext(ctx, in.Sql)
|
|
out.RawResult = sqlStmt
|
|
|
|
case SqlTypeStmtExecContext:
|
|
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeExec)
|
|
defer cancelFuncForTimeout()
|
|
if c.db.GetDryRun() {
|
|
sqlResult = new(SqlResult)
|
|
} else {
|
|
sqlResult, err = in.Stmt.ExecContext(ctx, in.Args...)
|
|
}
|
|
out.RawResult = sqlResult
|
|
|
|
case SqlTypeStmtQueryContext:
|
|
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
|
|
defer cancelFuncForTimeout()
|
|
stmtSqlRows, err = in.Stmt.QueryContext(ctx, in.Args...)
|
|
out.RawResult = stmtSqlRows
|
|
|
|
case SqlTypeStmtQueryRowContext:
|
|
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
|
|
defer cancelFuncForTimeout()
|
|
stmtSqlRow = in.Stmt.QueryRowContext(ctx, in.Args...)
|
|
out.RawResult = stmtSqlRow
|
|
|
|
default:
|
|
panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid SqlType "%s"`, in.Type))
|
|
}
|
|
// Result handling.
|
|
switch {
|
|
case sqlResult != nil && !c.GetIgnoreResultFromCtx(ctx):
|
|
rowsAffected, err = sqlResult.RowsAffected()
|
|
out.Result = sqlResult
|
|
|
|
case sqlRows != nil:
|
|
out.Records, err = c.RowsToResult(ctx, sqlRows)
|
|
rowsAffected = int64(len(out.Records))
|
|
|
|
case sqlStmt != nil:
|
|
out.Stmt = &Stmt{
|
|
Stmt: sqlStmt,
|
|
core: c,
|
|
link: in.Link,
|
|
sql: in.Sql,
|
|
}
|
|
}
|
|
var (
|
|
timestampMilli2 = gtime.TimestampMilli()
|
|
sqlObj = &Sql{
|
|
Sql: in.Sql,
|
|
Type: in.Type,
|
|
Args: in.Args,
|
|
Format: formattedSql,
|
|
Error: err,
|
|
Start: timestampMilli1,
|
|
End: timestampMilli2,
|
|
Group: c.db.GetGroup(),
|
|
Schema: c.db.GetSchema(),
|
|
RowsAffected: rowsAffected,
|
|
IsTransaction: in.IsTransaction,
|
|
}
|
|
)
|
|
|
|
// Tracing.
|
|
c.traceSpanEnd(ctx, span, sqlObj)
|
|
|
|
// Logging.
|
|
if c.db.GetDebug() {
|
|
c.writeSqlToLogger(ctx, sqlObj)
|
|
}
|
|
if err != nil && err != sql.ErrNoRows {
|
|
err = gerror.WrapCode(
|
|
gcode.CodeDbOperationError,
|
|
err,
|
|
FormatSqlWithArgs(in.Sql, in.Args),
|
|
)
|
|
}
|
|
return out, err
|
|
}
|
|
|
|
// Prepare creates a prepared statement for later queries or executions.
|
|
// Multiple queries or executions may be run concurrently from the
|
|
// returned statement.
|
|
// The caller must call the statement's Close method
|
|
// when the statement is no longer needed.
|
|
//
|
|
// The parameter `execOnMaster` specifies whether executing the sql on master node,
|
|
// or else it executes the sql on slave node if master-slave configured.
|
|
func (c *Core) Prepare(ctx context.Context, sql string, execOnMaster ...bool) (*Stmt, error) {
|
|
var (
|
|
err error
|
|
link Link
|
|
)
|
|
if len(execOnMaster) > 0 && execOnMaster[0] {
|
|
if link, err = c.MasterLink(); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
if link, err = c.SlaveLink(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return c.db.DoPrepare(ctx, link, sql)
|
|
}
|
|
|
|
// DoPrepare calls prepare function on given link object and returns the statement object.
|
|
func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (stmt *Stmt, err error) {
|
|
// Transaction checks.
|
|
if link == nil {
|
|
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
|
// Firstly, check and retrieve transaction link from context.
|
|
link = &txLink{tx.GetSqlTX()}
|
|
} else {
|
|
// Or else it creates one from master node.
|
|
if link, err = c.MasterLink(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
} else if !link.IsTransaction() {
|
|
// If current link is not transaction link, it checks and retrieves transaction from context.
|
|
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
|
link = &txLink{tx.GetSqlTX()}
|
|
}
|
|
}
|
|
|
|
if c.db.GetConfig().PrepareTimeout > 0 {
|
|
// DO NOT USE cancel function in prepare statement.
|
|
var cancelFunc context.CancelFunc
|
|
ctx, cancelFunc = context.WithTimeout(ctx, c.db.GetConfig().PrepareTimeout)
|
|
defer cancelFunc()
|
|
}
|
|
|
|
// Link execution.
|
|
var out DoCommitOutput
|
|
out, err = c.db.DoCommit(ctx, DoCommitInput{
|
|
Link: link,
|
|
Sql: sql,
|
|
Type: SqlTypePrepareContext,
|
|
IsTransaction: link.IsTransaction(),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return out.Stmt, err
|
|
}
|
|
|
|
// FormatUpsert formats and returns SQL clause part for upsert statement.
|
|
// In default implements, this function performs upsert statement for MySQL like:
|
|
// `INSERT INTO ... ON DUPLICATE KEY UPDATE x=VALUES(z),m=VALUES(y)...`
|
|
func (c *Core) FormatUpsert(columns []string, list List, option DoInsertOption) (string, error) {
|
|
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 Raw, *Raw:
|
|
onDuplicateStr += fmt.Sprintf(
|
|
"%s=%s",
|
|
c.QuoteWord(k),
|
|
v,
|
|
)
|
|
case Counter, *Counter:
|
|
var counter Counter
|
|
switch value := v.(type) {
|
|
case Counter:
|
|
counter = value
|
|
case *Counter:
|
|
counter = *value
|
|
}
|
|
operator, columnVal := c.getCounterAlter(counter)
|
|
onDuplicateStr += fmt.Sprintf(
|
|
"%s=%s%s%s",
|
|
c.QuoteWord(k),
|
|
c.QuoteWord(counter.Field),
|
|
operator,
|
|
gconv.String(columnVal),
|
|
)
|
|
default:
|
|
onDuplicateStr += fmt.Sprintf(
|
|
"%s=VALUES(%s)",
|
|
c.QuoteWord(k),
|
|
c.QuoteWord(gconv.String(v)),
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
for _, column := range columns {
|
|
// If it's `SAVE` operation, do not automatically update the creating time.
|
|
if c.IsSoftCreatedFieldName(column) {
|
|
continue
|
|
}
|
|
if len(onDuplicateStr) > 0 {
|
|
onDuplicateStr += ","
|
|
}
|
|
onDuplicateStr += fmt.Sprintf(
|
|
"%s=VALUES(%s)",
|
|
c.QuoteWord(column),
|
|
c.QuoteWord(column),
|
|
)
|
|
}
|
|
}
|
|
|
|
return InsertOnDuplicateKeyUpdate + " " + onDuplicateStr, nil
|
|
}
|
|
|
|
// RowsToResult converts underlying data record type sql.Rows to Result type.
|
|
func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error) {
|
|
if rows == nil {
|
|
return nil, nil
|
|
}
|
|
defer func() {
|
|
if err := rows.Close(); err != nil {
|
|
intlog.Errorf(ctx, `%+v`, err)
|
|
}
|
|
}()
|
|
if !rows.Next() {
|
|
return nil, nil
|
|
}
|
|
// Column names and types.
|
|
columnTypes, err := rows.ColumnTypes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(columnTypes) > 0 {
|
|
if internalData := c.getInternalColumnFromCtx(ctx); internalData != nil {
|
|
internalData.FirstResultColumn = columnTypes[0].Name()
|
|
}
|
|
}
|
|
var (
|
|
values = make([]any, len(columnTypes))
|
|
result = make(Result, 0)
|
|
scanArgs = make([]any, len(values))
|
|
)
|
|
for i := range values {
|
|
scanArgs[i] = &values[i]
|
|
}
|
|
for {
|
|
if err = rows.Scan(scanArgs...); err != nil {
|
|
return result, err
|
|
}
|
|
record := Record{}
|
|
for i, value := range values {
|
|
if value == nil {
|
|
// DO NOT use `gvar.New(nil)` here as it creates an initialized object
|
|
// which will cause struct converting issue.
|
|
record[columnTypes[i].Name()] = nil
|
|
} else {
|
|
var (
|
|
convertedValue any
|
|
columnType = columnTypes[i]
|
|
)
|
|
if convertedValue, err = c.columnValueToLocalValue(ctx, value, columnType); err != nil {
|
|
return nil, err
|
|
}
|
|
record[columnTypes[i].Name()] = gvar.New(convertedValue)
|
|
}
|
|
}
|
|
result = append(result, record)
|
|
if !rows.Next() {
|
|
break
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// OrderRandomFunction returns the SQL function for random ordering.
|
|
func (c *Core) OrderRandomFunction() string {
|
|
return "RAND()"
|
|
}
|
|
|
|
func (c *Core) columnValueToLocalValue(
|
|
ctx context.Context, value any, columnType *sql.ColumnType,
|
|
) (any, error) {
|
|
var scanType = columnType.ScanType()
|
|
if scanType != nil {
|
|
// Common basic builtin types.
|
|
switch scanType.Kind() {
|
|
case
|
|
reflect.Bool,
|
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
|
reflect.Float32, reflect.Float64:
|
|
return gconv.Convert(
|
|
gconv.String(value),
|
|
columnType.ScanType().String(),
|
|
), nil
|
|
default:
|
|
}
|
|
}
|
|
// Other complex types, especially custom types.
|
|
return c.db.ConvertValueForLocal(ctx, columnType.DatabaseTypeName(), value)
|
|
}
|