mirror of
https://gitee.com/johng/gf.git
synced 2024-12-01 19:57:40 +08:00
parent
c4a51f4c2f
commit
8266a16b4e
31
.github/workflows/ci-main.yml
vendored
31
.github/workflows/ci-main.yml
vendored
@ -67,9 +67,13 @@ jobs:
|
||||
- 3306:3306
|
||||
|
||||
# PostgreSQL backend server.
|
||||
# docker run -d --name postgres -p 5432:5432 \
|
||||
# -e POSTGRES_PASSWORD=12345678 -e POSTGRES_USER=postgres -e POSTGRES_DB=test \
|
||||
# -v postgres:/Users/john/Temp/postgresql/data loads/postgres:13
|
||||
# docker run -d --name postgres \
|
||||
# -p 5432:5432 \
|
||||
# -e POSTGRES_PASSWORD=12345678 \
|
||||
# -e POSTGRES_USER=postgres \
|
||||
# -e POSTGRES_DB=test \
|
||||
# -v postgres:/Users/john/Temp/postgresql/data \
|
||||
# loads/postgres:13
|
||||
postgres:
|
||||
image: loads/postgres:13
|
||||
env:
|
||||
@ -87,7 +91,8 @@ jobs:
|
||||
--health-retries 5
|
||||
|
||||
# MSSQL backend server.
|
||||
# docker run -d --name mssql -p 1433:1433 \
|
||||
# docker run \
|
||||
# -p 1433:1433 \
|
||||
# -e ACCEPT_EULA=Y \
|
||||
# -e SA_PASSWORD=LoremIpsum86 \
|
||||
# -e MSSQL_DB=test \
|
||||
@ -123,8 +128,13 @@ jobs:
|
||||
- 9001:9001
|
||||
|
||||
# Polaris backend server.
|
||||
# docker run -d --name polaris -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 loads/polaris-server-standalone:1.11.2
|
||||
# docker run -d --name polaris -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 loads/polaris-standalone:v1.16.3
|
||||
# docker run -d --name polaris \
|
||||
# -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \
|
||||
# loads/polaris-server-standalone:1.11.2
|
||||
#
|
||||
# docker run -d --name polaris \
|
||||
# -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \
|
||||
# loads/polaris-standalone:v1.16.3
|
||||
polaris:
|
||||
image: loads/polaris-standalone:v1.17.2
|
||||
ports:
|
||||
@ -134,7 +144,14 @@ jobs:
|
||||
- 9090:9090
|
||||
- 9091:9091
|
||||
|
||||
# Oracle 11g server
|
||||
# Oracle 11g server.
|
||||
# docker run \
|
||||
# -e ORACLE_ALLOW_REMOTE=true \
|
||||
# -e ORACLE_SID=XE \
|
||||
# -e ORACLE_DB_USER_NAME=system \
|
||||
# -e ORACLE_DB_PASSWORD=oracle \
|
||||
# -p 1521:1521 \
|
||||
# loads/oracle-xe-11g-r2:11.2.0
|
||||
oracle-server:
|
||||
image: loads/oracle-xe-11g-r2:11.2.0
|
||||
env:
|
||||
|
@ -9,25 +9,10 @@ package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// Driver is the driver for postgresql database.
|
||||
@ -71,386 +56,9 @@ func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for clickhouse.
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
source := config.Link
|
||||
// clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(replaceSchemaPattern, "@$1/"+config.Name, config.Link)
|
||||
} else {
|
||||
// If no schema, the link is matched for replacement
|
||||
dbName, _ := gregex.MatchString(replaceSchemaPattern, config.Link)
|
||||
if len(dbName) > 0 {
|
||||
config.Name = dbName[len(dbName)-1]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 gdb.Result
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := fmt.Sprintf("select name from `system`.tables where database = '%s'", d.GetConfig().Name)
|
||||
result, err = d.DoSelect(ctx, link, query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
tables = append(tables, m["name"].String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
columns = "name,position,default_expression,comment,type,is_in_partition_key,is_in_sorting_key,is_in_primary_key,is_in_sampling_key"
|
||||
getColumnsSql = fmt.Sprintf(
|
||||
"select %s from `system`.columns c where `table` = '%s'",
|
||||
columns, table,
|
||||
)
|
||||
)
|
||||
result, err = d.DoSelect(ctx, link, getColumnsSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.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()] = &gdb.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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// DoFilter handles the sql before posts it to database.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link gdb.Link, originSql string, args []interface{},
|
||||
) (newSql string, newArgs []interface{}, 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
|
||||
|
||||
}
|
||||
return originSql, args, nil
|
||||
}
|
||||
|
||||
// DoCommit commits current sql and arguments to underlying sql driver.
|
||||
func (d *Driver) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.DoCommitOutput, err error) {
|
||||
ctx = d.InjectIgnoreResult(ctx)
|
||||
return d.Core.DoCommit(ctx, in)
|
||||
}
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
var (
|
||||
keys []string // Field names.
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
// 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 gdb.TX
|
||||
stmt *gdb.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
|
||||
}
|
||||
for i := 0; i < len(list); i++ {
|
||||
// Values that will be committed to underlying database driver.
|
||||
params := make([]interface{}, 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
|
||||
}
|
||||
|
||||
// ConvertValueForField converts value to the type of the record field.
|
||||
func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, 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
|
||||
}
|
||||
|
||||
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:
|
||||
// for gtime type, needs to get time.Time
|
||||
if itemValue != nil {
|
||||
return itemValue.Time, nil
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return fieldValue, nil
|
||||
}
|
||||
|
||||
// DoDelete does "DELETE FROM ... " statement for the table.
|
||||
func (d *Driver) DoDelete(ctx context.Context, link gdb.Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
ctx = d.injectNeedParsedSql(ctx)
|
||||
return d.Core.DoDelete(ctx, link, table, condition, args...)
|
||||
}
|
||||
|
||||
// DoUpdate does "UPDATE ... " statement for the table.
|
||||
func (d *Driver) DoUpdate(ctx context.Context, link gdb.Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
ctx = d.injectNeedParsedSql(ctx)
|
||||
return d.Core.DoUpdate(ctx, link, table, data, condition, args...)
|
||||
}
|
||||
|
||||
// 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 interface{}, 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 interface{}, 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 interface{}, batch ...int) (sql.Result, error) {
|
||||
return nil, errUnsupportedReplace
|
||||
}
|
||||
|
||||
// Begin starts and returns the transaction object.
|
||||
func (d *Driver) Begin(ctx context.Context) (tx gdb.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 gdb.TX) error) error {
|
||||
return errUnsupportedTransaction
|
||||
}
|
||||
|
||||
func (d *Driver) injectNeedParsedSql(ctx context.Context) context.Context {
|
||||
if ctx.Value(needParsedSqlInCtx) != nil {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, needParsedSqlInCtx, true)
|
||||
}
|
||||
|
||||
func (d *Driver) getNeedParsedSqlFromCtx(ctx context.Context) bool {
|
||||
if ctx.Value(needParsedSqlInCtx) != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
85
contrib/drivers/clickhouse/clickhouse_convert.go
Normal file
85
contrib/drivers/clickhouse/clickhouse_convert.go
Normal file
@ -0,0 +1,85 @@
|
||||
// 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 interface{}) (interface{}, 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
|
||||
}
|
||||
|
||||
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:
|
||||
// for gtime type, needs to get time.Time
|
||||
if itemValue != nil {
|
||||
return itemValue.Time, nil
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return fieldValue, nil
|
||||
}
|
19
contrib/drivers/clickhouse/clickhouse_do_commit.go
Normal file
19
contrib/drivers/clickhouse/clickhouse_do_commit.go
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// DoCommit commits current sql and arguments to underlying sql driver.
|
||||
func (d *Driver) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.DoCommitOutput, err error) {
|
||||
ctx = d.InjectIgnoreResult(ctx)
|
||||
return d.Core.DoCommit(ctx, in)
|
||||
}
|
20
contrib/drivers/clickhouse/clickhouse_do_delete.go
Normal file
20
contrib/drivers/clickhouse/clickhouse_do_delete.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// DoDelete does "DELETE FROM ... " statement for the table.
|
||||
func (d *Driver) DoDelete(ctx context.Context, link gdb.Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
ctx = d.injectNeedParsedSql(ctx)
|
||||
return d.Core.DoDelete(ctx, link, table, condition, args...)
|
||||
}
|
85
contrib/drivers/clickhouse/clickhouse_do_filter.go
Normal file
85
contrib/drivers/clickhouse/clickhouse_do_filter.go
Normal file
@ -0,0 +1,85 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
// DoFilter handles the sql before posts it to database.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link gdb.Link, originSql string, args []interface{},
|
||||
) (newSql string, newArgs []interface{}, 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
|
||||
|
||||
}
|
||||
return originSql, args, nil
|
||||
}
|
||||
|
||||
func (d *Driver) getNeedParsedSqlFromCtx(ctx context.Context) bool {
|
||||
if ctx.Value(needParsedSqlInCtx) != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
72
contrib/drivers/clickhouse/clickhouse_do_insert.go
Normal file
72
contrib/drivers/clickhouse/clickhouse_do_insert.go
Normal file
@ -0,0 +1,72 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
var (
|
||||
keys []string // Field names.
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
// 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 gdb.TX
|
||||
stmt *gdb.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
|
||||
}
|
||||
for i := 0; i < len(list); i++ {
|
||||
// Values that will be committed to underlying database driver.
|
||||
params := make([]interface{}, 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
|
||||
}
|
20
contrib/drivers/clickhouse/clickhouse_do_update.go
Normal file
20
contrib/drivers/clickhouse/clickhouse_do_update.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// DoUpdate does "UPDATE ... " statement for the table.
|
||||
func (d *Driver) DoUpdate(ctx context.Context, link gdb.Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
ctx = d.injectNeedParsedSql(ctx)
|
||||
return d.Core.DoUpdate(ctx, link, table, data, condition, args...)
|
||||
}
|
27
contrib/drivers/clickhouse/clickhouse_insert.go
Normal file
27
contrib/drivers/clickhouse/clickhouse_insert.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// InsertIgnore Other queries for modifying data parts are not supported: REPLACE, MERGE, UPSERT, INSERT UPDATE.
|
||||
func (d *Driver) InsertIgnore(ctx context.Context, table string, data interface{}, 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 interface{}, 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 interface{}, batch ...int) (sql.Result, error) {
|
||||
return nil, errUnsupportedReplace
|
||||
}
|
63
contrib/drivers/clickhouse/clickhouse_open.go
Normal file
63
contrib/drivers/clickhouse/clickhouse_open.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for clickhouse.
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
source := config.Link
|
||||
// clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(replaceSchemaPattern, "@$1/"+config.Name, config.Link)
|
||||
} else {
|
||||
// If no schema, the link is matched for replacement
|
||||
dbName, _ := gregex.MatchString(replaceSchemaPattern, config.Link)
|
||||
if len(dbName) > 0 {
|
||||
config.Name = dbName[len(dbName)-1]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
41
contrib/drivers/clickhouse/clickhouse_ping.go
Normal file
41
contrib/drivers/clickhouse/clickhouse_ping.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
)
|
||||
|
||||
// PingMaster pings the master node to check authentication or keeps the connection alive.
|
||||
func (d *Driver) PingMaster() error {
|
||||
conn, err := d.Master()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.ping(conn)
|
||||
}
|
||||
|
||||
// PingSlave pings the slave node to check authentication or keeps the connection alive.
|
||||
func (d *Driver) PingSlave() error {
|
||||
conn, err := d.Slave()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.ping(conn)
|
||||
}
|
||||
|
||||
// ping Returns the Clickhouse specific error.
|
||||
func (d *Driver) ping(conn *sql.DB) error {
|
||||
err := conn.Ping()
|
||||
if exception, ok := err.(*clickhouse.Exception); ok {
|
||||
return fmt.Errorf("[%d]%s", exception.Code, exception.Message)
|
||||
}
|
||||
return err
|
||||
}
|
70
contrib/drivers/clickhouse/clickhouse_table_fields.go
Normal file
70
contrib/drivers/clickhouse/clickhouse_table_fields.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.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]*gdb.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()] = &gdb.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
|
||||
}
|
36
contrib/drivers/clickhouse/clickhouse_tables.go
Normal file
36
contrib/drivers/clickhouse/clickhouse_tables.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
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 gdb.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
|
||||
}
|
23
contrib/drivers/clickhouse/clickhouse_transaction.go
Normal file
23
contrib/drivers/clickhouse/clickhouse_transaction.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// Begin starts and returns the transaction object.
|
||||
func (d *Driver) Begin(ctx context.Context) (tx gdb.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 gdb.TX) error) error {
|
||||
return errUnsupportedTransaction
|
||||
}
|
@ -8,24 +8,10 @@
|
||||
package dm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "gitee.com/chunanyong/dm"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
@ -61,390 +47,7 @@ func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for pgsql.
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "dm"
|
||||
)
|
||||
if config.Name == "" {
|
||||
return nil, fmt.Errorf(
|
||||
`dm.Open failed for driver "%s" without DB Name`, underlyingDriverName,
|
||||
)
|
||||
}
|
||||
// Data Source Name of DM8:
|
||||
// dm://userName:password@ip:port/dbname
|
||||
// dm://userName:password@DW/dbname?DW=(192.168.1.1:5236,192.168.1.2:5236)
|
||||
var domain string
|
||||
if config.Port != "" {
|
||||
domain = fmt.Sprintf("%s:%s", config.Host, config.Port)
|
||||
} else {
|
||||
domain = config.Host
|
||||
}
|
||||
source = fmt.Sprintf(
|
||||
"dm://%s:%s@%s/%s?charset=%s&schema=%s",
|
||||
config.User, config.Pass, domain, config.Name, config.Charset, config.Name,
|
||||
)
|
||||
// Demo of timezone setting:
|
||||
// &loc=Asia/Shanghai
|
||||
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)
|
||||
}
|
||||
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`dm.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// 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 gdb.Result
|
||||
// When schema is empty, return the default link
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The link has been distinguished and no longer needs to judge the owner
|
||||
result, err = d.DoSelect(
|
||||
ctx, link, `SELECT * FROM ALL_TABLES`,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
if v, ok := m["IOT_NAME"]; ok {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
// When no schema is specified, the configuration item is returned by default
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
// When usedSchema is empty, return the default link
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The link has been distinguished and no longer needs to judge the owner
|
||||
result, err = d.DoSelect(
|
||||
ctx, link,
|
||||
fmt.Sprintf(
|
||||
`SELECT * FROM ALL_TAB_COLUMNS WHERE Table_Name= '%s' AND OWNER = '%s'`,
|
||||
strings.ToUpper(table),
|
||||
strings.ToUpper(d.GetSchema()),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
// m[NULLABLE] returns "N" "Y"
|
||||
// "N" means not null
|
||||
// "Y" means could be null
|
||||
var nullable bool
|
||||
if m["NULLABLE"].String() != "N" {
|
||||
nullable = true
|
||||
}
|
||||
fields[m["COLUMN_NAME"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["COLUMN_NAME"].String(),
|
||||
Type: m["DATA_TYPE"].String(),
|
||||
Null: nullable,
|
||||
Default: m["DATA_DEFAULT"].Val(),
|
||||
// Key: m["Key"].String(),
|
||||
// Extra: m["Extra"].String(),
|
||||
// Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// ConvertValueForField converts value to the type of the record field.
|
||||
func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
||||
switch itemValue := fieldValue.(type) {
|
||||
// dm does not support time.Time, it so here converts it to time string that it supports.
|
||||
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 gtime.New(itemValue).String(), nil
|
||||
|
||||
// dm does not support time.Time, it so here converts it to time string that it supports.
|
||||
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 gtime.New(itemValue).String(), nil
|
||||
}
|
||||
|
||||
return fieldValue, nil
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
// There should be no need to capitalize, because it has been done from field processing before
|
||||
newSql, _ = gregex.ReplaceString(`["\n\t]`, "", sql)
|
||||
newSql = gstr.ReplaceI(gstr.ReplaceI(newSql, "GROUP_CONCAT", "LISTAGG"), "SEPARATOR", ",")
|
||||
|
||||
// TODO The current approach is too rough. We should deal with the GROUP_CONCAT function and the parsing of the index field from within the select from match.
|
||||
// (GROUP_CONCAT DM does not approve; index cannot be used as a query column name, and security characters need to be added, such as "index")
|
||||
l, r := d.GetChars()
|
||||
if strings.Contains(newSql, "INDEX") || strings.Contains(newSql, "index") {
|
||||
if !(strings.Contains(newSql, "_INDEX") || strings.Contains(newSql, "_index")) {
|
||||
newSql = gstr.ReplaceI(newSql, "INDEX", l+"INDEX"+r)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO i tried to do but it never work:
|
||||
// array, err := gregex.MatchAllString(`SELECT (.*INDEX.*) FROM .*`, newSql)
|
||||
// g.Dump("err:", err)
|
||||
// g.Dump("array:", array)
|
||||
// g.Dump("array:", array[0][1])
|
||||
|
||||
// newSql, err = gregex.ReplaceString(`SELECT (.*INDEX.*) FROM .*`, l+"INDEX"+r, newSql)
|
||||
// g.Dump("err:", err)
|
||||
// g.Dump("newSql:", newSql)
|
||||
|
||||
// re, err := regexp.Compile(`.*SELECT (.*INDEX.*) FROM .*`)
|
||||
// newSql = re.ReplaceAllStringFunc(newSql, func(data string) string {
|
||||
// fmt.Println("data:", data)
|
||||
// return data
|
||||
// })
|
||||
|
||||
return d.Core.DoFilter(
|
||||
ctx,
|
||||
link,
|
||||
newSql,
|
||||
args,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO I originally wanted to only convert keywords in select
|
||||
// 但是我发现 DoQuery 中会对 sql 会对 " " 达梦的安全字符 进行 / 转义,最后还是导致达梦无法正常解析
|
||||
// However, I found that DoQuery() will perform / escape on sql with " " Dameng's safe characters, which ultimately caused Dameng to be unable to parse normally.
|
||||
// But processing in DoFilter() is OK
|
||||
// func (d *Driver) DoQuery(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (gdb.Result, error) {
|
||||
// l, r := d.GetChars()
|
||||
// new := gstr.ReplaceI(sql, "INDEX", l+"INDEX"+r)
|
||||
// g.Dump("new:", new)
|
||||
// return d.Core.DoQuery(
|
||||
// ctx,
|
||||
// link,
|
||||
// new,
|
||||
// args,
|
||||
// )
|
||||
// }
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionReplace:
|
||||
// TODO:: Should be Supported
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported, `Replace operation is not supported by dm driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionSave:
|
||||
// This syntax currently only supports design tables whose primary key is ID.
|
||||
listLength := len(list)
|
||||
if listLength == 0 {
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeInvalidRequest, `Save operation list is empty by dm driver`,
|
||||
)
|
||||
}
|
||||
var (
|
||||
keysSort []string
|
||||
charL, charR = d.GetChars()
|
||||
)
|
||||
// Column names need to be aligned in the syntax
|
||||
for k := range list[0] {
|
||||
keysSort = append(keysSort, k)
|
||||
}
|
||||
var char = struct {
|
||||
charL string
|
||||
charR string
|
||||
valueCharL string
|
||||
valueCharR string
|
||||
duplicateKey string
|
||||
keys []string
|
||||
}{
|
||||
charL: charL,
|
||||
charR: charR,
|
||||
valueCharL: "'",
|
||||
valueCharR: "'",
|
||||
// TODO:: Need to dynamically set the primary key of the table
|
||||
duplicateKey: "ID",
|
||||
keys: keysSort,
|
||||
}
|
||||
|
||||
// insertKeys: Handle valid keys that need to be inserted and updated
|
||||
// insertValues: Handle values that need to be inserted
|
||||
// updateValues: Handle values that need to be updated
|
||||
// queryValues: Handle only one insert with column name
|
||||
insertKeys, insertValues, updateValues, queryValues := parseValue(list[0], char)
|
||||
// unionValues: Handling values that need to be inserted and updated
|
||||
unionValues := parseUnion(list[1:], char)
|
||||
|
||||
batchResult := new(gdb.SqlResult)
|
||||
// parseSql():
|
||||
// MERGE INTO {{table}} T1
|
||||
// USING ( SELECT {{queryValues}} FROM DUAL
|
||||
// {{unionValues}} ) T2
|
||||
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}})
|
||||
// WHEN NOT MATCHED THEN
|
||||
// INSERT {{insertKeys}} VALUES {{insertValues}}
|
||||
// WHEN MATCHED THEN
|
||||
// UPDATE SET {{updateValues}}
|
||||
sqlStr := parseSql(
|
||||
insertKeys, insertValues, updateValues, queryValues, unionValues, table, char.duplicateKey,
|
||||
)
|
||||
r, err := d.DoExec(ctx, link, sqlStr)
|
||||
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
|
||||
}
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
|
||||
func parseValue(listOne gdb.Map, char struct {
|
||||
charL string
|
||||
charR string
|
||||
valueCharL string
|
||||
valueCharR string
|
||||
duplicateKey string
|
||||
keys []string
|
||||
}) (insertKeys []string, insertValues []string, updateValues []string, queryValues []string) {
|
||||
for _, column := range char.keys {
|
||||
if listOne[column] == nil {
|
||||
// remove unassigned struct object
|
||||
continue
|
||||
}
|
||||
insertKeys = append(insertKeys, char.charL+column+char.charR)
|
||||
insertValues = append(insertValues, "T2."+char.charL+column+char.charR)
|
||||
if column != char.duplicateKey {
|
||||
updateValues = append(
|
||||
updateValues,
|
||||
fmt.Sprintf(`T1.%s = T2.%s`, char.charL+column+char.charR, char.charL+column+char.charR),
|
||||
)
|
||||
}
|
||||
|
||||
saveValue := gconv.String(listOne[column])
|
||||
queryValues = append(
|
||||
queryValues,
|
||||
fmt.Sprintf(
|
||||
char.valueCharL+"%s"+char.valueCharR+" AS "+char.charL+"%s"+char.charR,
|
||||
saveValue, column,
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseUnion(list gdb.List, char struct {
|
||||
charL string
|
||||
charR string
|
||||
valueCharL string
|
||||
valueCharR string
|
||||
duplicateKey string
|
||||
keys []string
|
||||
}) (unionValues []string) {
|
||||
for _, mapper := range list {
|
||||
var saveValue []string
|
||||
for _, column := range char.keys {
|
||||
if mapper[column] == nil {
|
||||
continue
|
||||
}
|
||||
// va := reflect.ValueOf(mapper[column])
|
||||
// ty := reflect.TypeOf(mapper[column])
|
||||
// switch ty.Kind() {
|
||||
// case reflect.String:
|
||||
// saveValue = append(saveValue, char.valueCharL+va.String()+char.valueCharR)
|
||||
|
||||
// case reflect.Int:
|
||||
// saveValue = append(saveValue, strconv.FormatInt(va.Int(), 10))
|
||||
|
||||
// case reflect.Int64:
|
||||
// saveValue = append(saveValue, strconv.FormatInt(va.Int(), 10))
|
||||
|
||||
// default:
|
||||
// // The fish has no chance getting here.
|
||||
// // Nothing to do.
|
||||
// }
|
||||
saveValue = append(saveValue,
|
||||
fmt.Sprintf(
|
||||
char.valueCharL+"%s"+char.valueCharR,
|
||||
gconv.String(mapper[column]),
|
||||
))
|
||||
}
|
||||
unionValues = append(
|
||||
unionValues,
|
||||
fmt.Sprintf(`UNION ALL SELECT %s FROM DUAL`, strings.Join(saveValue, ",")),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseSql(
|
||||
insertKeys, insertValues, updateValues, queryValues, unionValues []string, table, duplicateKey string,
|
||||
) (sqlStr string) {
|
||||
var (
|
||||
queryValueStr = strings.Join(queryValues, ",")
|
||||
unionValueStr = strings.Join(unionValues, " ")
|
||||
insertKeyStr = strings.Join(insertKeys, ",")
|
||||
insertValueStr = strings.Join(insertValues, ",")
|
||||
updateValueStr = strings.Join(updateValues, ",")
|
||||
pattern = gstr.Trim(`
|
||||
MERGE INTO %s T1 USING (SELECT %s FROM DUAL %s) T2 ON %s
|
||||
WHEN NOT MATCHED
|
||||
THEN
|
||||
INSERT(%s) VALUES (%s)
|
||||
WHEN MATCHED
|
||||
THEN
|
||||
UPDATE SET %s;
|
||||
COMMIT;
|
||||
`)
|
||||
)
|
||||
return fmt.Sprintf(
|
||||
pattern,
|
||||
table, queryValueStr, unionValueStr,
|
||||
fmt.Sprintf("(T1.%s = T2.%s)", duplicateKey, duplicateKey),
|
||||
insertKeyStr, insertValueStr, updateValueStr,
|
||||
)
|
||||
}
|
||||
|
40
contrib/drivers/dm/dm_convert.go
Normal file
40
contrib/drivers/dm/dm_convert.go
Normal file
@ -0,0 +1,40 @@
|
||||
// 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 dm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"time"
|
||||
|
||||
"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 interface{}) (interface{}, error) {
|
||||
switch itemValue := fieldValue.(type) {
|
||||
// dm does not support time.Time, it so here converts it to time string that it supports.
|
||||
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 gtime.New(itemValue).String(), nil
|
||||
|
||||
// dm does not support time.Time, it so here converts it to time string that it supports.
|
||||
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 gtime.New(itemValue).String(), nil
|
||||
}
|
||||
|
||||
return fieldValue, nil
|
||||
}
|
59
contrib/drivers/dm/dm_do_filter.go
Normal file
59
contrib/drivers/dm/dm_do_filter.go
Normal 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 dm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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 gdb.Link, sql string, args []interface{},
|
||||
) (newSql string, newArgs []interface{}, err error) {
|
||||
// There should be no need to capitalize, because it has been done from field processing before
|
||||
newSql, _ = gregex.ReplaceString(`["\n\t]`, "", sql)
|
||||
newSql = gstr.ReplaceI(gstr.ReplaceI(newSql, "GROUP_CONCAT", "LISTAGG"), "SEPARATOR", ",")
|
||||
|
||||
// TODO The current approach is too rough. We should deal with the GROUP_CONCAT function and the
|
||||
// parsing of the index field from within the select from match.
|
||||
// (GROUP_CONCAT DM does not approve; index cannot be used as a query column name, and security characters need to be added, such as "index")
|
||||
l, r := d.GetChars()
|
||||
if strings.Contains(newSql, "INDEX") || strings.Contains(newSql, "index") {
|
||||
if !(strings.Contains(newSql, "_INDEX") || strings.Contains(newSql, "_index")) {
|
||||
newSql = gstr.ReplaceI(newSql, "INDEX", l+"INDEX"+r)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO i tried to do but it never work:
|
||||
// array, err := gregex.MatchAllString(`SELECT (.*INDEX.*) FROM .*`, newSql)
|
||||
// g.Dump("err:", err)
|
||||
// g.Dump("array:", array)
|
||||
// g.Dump("array:", array[0][1])
|
||||
|
||||
// newSql, err = gregex.ReplaceString(`SELECT (.*INDEX.*) FROM .*`, l+"INDEX"+r, newSql)
|
||||
// g.Dump("err:", err)
|
||||
// g.Dump("newSql:", newSql)
|
||||
|
||||
// re, err := regexp.Compile(`.*SELECT (.*INDEX.*) FROM .*`)
|
||||
// newSql = re.ReplaceAllStringFunc(newSql, func(data string) string {
|
||||
// fmt.Println("data:", data)
|
||||
// return data
|
||||
// })
|
||||
|
||||
return d.Core.DoFilter(
|
||||
ctx,
|
||||
link,
|
||||
newSql,
|
||||
args,
|
||||
)
|
||||
}
|
207
contrib/drivers/dm/dm_do_insert.go
Normal file
207
contrib/drivers/dm/dm_do_insert.go
Normal file
@ -0,0 +1,207 @@
|
||||
// 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 dm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionReplace:
|
||||
// TODO:: Should be Supported
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported, `Replace operation is not supported by dm driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionSave:
|
||||
// This syntax currently only supports design tables whose primary key is ID.
|
||||
listLength := len(list)
|
||||
if listLength == 0 {
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeInvalidRequest, `Save operation list is empty by dm driver`,
|
||||
)
|
||||
}
|
||||
var (
|
||||
keysSort []string
|
||||
charL, charR = d.GetChars()
|
||||
)
|
||||
// Column names need to be aligned in the syntax
|
||||
for k := range list[0] {
|
||||
keysSort = append(keysSort, k)
|
||||
}
|
||||
var char = struct {
|
||||
charL string
|
||||
charR string
|
||||
valueCharL string
|
||||
valueCharR string
|
||||
duplicateKey string
|
||||
keys []string
|
||||
}{
|
||||
charL: charL,
|
||||
charR: charR,
|
||||
valueCharL: "'",
|
||||
valueCharR: "'",
|
||||
// TODO:: Need to dynamically set the primary key of the table
|
||||
duplicateKey: "ID",
|
||||
keys: keysSort,
|
||||
}
|
||||
|
||||
// insertKeys: Handle valid keys that need to be inserted and updated
|
||||
// insertValues: Handle values that need to be inserted
|
||||
// updateValues: Handle values that need to be updated
|
||||
// queryValues: Handle only one insert with column name
|
||||
insertKeys, insertValues, updateValues, queryValues := parseValue(list[0], char)
|
||||
// unionValues: Handling values that need to be inserted and updated
|
||||
unionValues := parseUnion(list[1:], char)
|
||||
|
||||
batchResult := new(gdb.SqlResult)
|
||||
// parseSql():
|
||||
// MERGE INTO {{table}} T1
|
||||
// USING ( SELECT {{queryValues}} FROM DUAL
|
||||
// {{unionValues}} ) T2
|
||||
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}})
|
||||
// WHEN NOT MATCHED THEN
|
||||
// INSERT {{insertKeys}} VALUES {{insertValues}}
|
||||
// WHEN MATCHED THEN
|
||||
// UPDATE SET {{updateValues}}
|
||||
sqlStr := parseSql(
|
||||
insertKeys, insertValues, updateValues, queryValues, unionValues, table, char.duplicateKey,
|
||||
)
|
||||
r, err := d.DoExec(ctx, link, sqlStr)
|
||||
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
|
||||
}
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
|
||||
func parseValue(listOne gdb.Map, char struct {
|
||||
charL string
|
||||
charR string
|
||||
valueCharL string
|
||||
valueCharR string
|
||||
duplicateKey string
|
||||
keys []string
|
||||
}) (insertKeys []string, insertValues []string, updateValues []string, queryValues []string) {
|
||||
for _, column := range char.keys {
|
||||
if listOne[column] == nil {
|
||||
// remove unassigned struct object
|
||||
continue
|
||||
}
|
||||
insertKeys = append(insertKeys, char.charL+column+char.charR)
|
||||
insertValues = append(insertValues, "T2."+char.charL+column+char.charR)
|
||||
if column != char.duplicateKey {
|
||||
updateValues = append(
|
||||
updateValues,
|
||||
fmt.Sprintf(`T1.%s = T2.%s`, char.charL+column+char.charR, char.charL+column+char.charR),
|
||||
)
|
||||
}
|
||||
|
||||
saveValue := gconv.String(listOne[column])
|
||||
queryValues = append(
|
||||
queryValues,
|
||||
fmt.Sprintf(
|
||||
char.valueCharL+"%s"+char.valueCharR+" AS "+char.charL+"%s"+char.charR,
|
||||
saveValue, column,
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseUnion(list gdb.List, char struct {
|
||||
charL string
|
||||
charR string
|
||||
valueCharL string
|
||||
valueCharR string
|
||||
duplicateKey string
|
||||
keys []string
|
||||
}) (unionValues []string) {
|
||||
for _, mapper := range list {
|
||||
var saveValue []string
|
||||
for _, column := range char.keys {
|
||||
if mapper[column] == nil {
|
||||
continue
|
||||
}
|
||||
// va := reflect.ValueOf(mapper[column])
|
||||
// ty := reflect.TypeOf(mapper[column])
|
||||
// switch ty.Kind() {
|
||||
// case reflect.String:
|
||||
// saveValue = append(saveValue, char.valueCharL+va.String()+char.valueCharR)
|
||||
|
||||
// case reflect.Int:
|
||||
// saveValue = append(saveValue, strconv.FormatInt(va.Int(), 10))
|
||||
|
||||
// case reflect.Int64:
|
||||
// saveValue = append(saveValue, strconv.FormatInt(va.Int(), 10))
|
||||
|
||||
// default:
|
||||
// // The fish has no chance getting here.
|
||||
// // Nothing to do.
|
||||
// }
|
||||
saveValue = append(saveValue,
|
||||
fmt.Sprintf(
|
||||
char.valueCharL+"%s"+char.valueCharR,
|
||||
gconv.String(mapper[column]),
|
||||
))
|
||||
}
|
||||
unionValues = append(
|
||||
unionValues,
|
||||
fmt.Sprintf(`UNION ALL SELECT %s FROM DUAL`, strings.Join(saveValue, ",")),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseSql(
|
||||
insertKeys, insertValues, updateValues, queryValues, unionValues []string, table, duplicateKey string,
|
||||
) (sqlStr string) {
|
||||
var (
|
||||
queryValueStr = strings.Join(queryValues, ",")
|
||||
unionValueStr = strings.Join(unionValues, " ")
|
||||
insertKeyStr = strings.Join(insertKeys, ",")
|
||||
insertValueStr = strings.Join(insertValues, ",")
|
||||
updateValueStr = strings.Join(updateValues, ",")
|
||||
pattern = gstr.Trim(`
|
||||
MERGE INTO %s T1 USING (SELECT %s FROM DUAL %s) T2 ON %s
|
||||
WHEN NOT MATCHED
|
||||
THEN
|
||||
INSERT(%s) VALUES (%s)
|
||||
WHEN MATCHED
|
||||
THEN
|
||||
UPDATE SET %s;
|
||||
COMMIT;
|
||||
`)
|
||||
)
|
||||
return fmt.Sprintf(
|
||||
pattern,
|
||||
table, queryValueStr, unionValueStr,
|
||||
fmt.Sprintf("(T1.%s = T2.%s)", duplicateKey, duplicateKey),
|
||||
insertKeyStr, insertValueStr, updateValueStr,
|
||||
)
|
||||
}
|
23
contrib/drivers/dm/dm_do_query.go
Normal file
23
contrib/drivers/dm/dm_do_query.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package dm
|
||||
|
||||
// TODO I originally wanted to only convert keywords in select
|
||||
// 但是我发现 DoQuery 中会对 sql 会对 " " 达梦的安全字符 进行 / 转义,最后还是导致达梦无法正常解析
|
||||
// However, I found that DoQuery() will perform / escape on sql with " " Dameng's safe characters, which ultimately caused Dameng to be unable to parse normally.
|
||||
// But processing in DoFilter() is OK
|
||||
// func (d *Driver) DoQuery(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (gdb.Result, error) {
|
||||
// l, r := d.GetChars()
|
||||
// new := gstr.ReplaceI(sql, "INDEX", l+"INDEX"+r)
|
||||
// g.Dump("new:", new)
|
||||
// return d.Core.DoQuery(
|
||||
// ctx,
|
||||
// link,
|
||||
// new,
|
||||
// args,
|
||||
// )
|
||||
// }
|
65
contrib/drivers/dm/dm_open.go
Normal file
65
contrib/drivers/dm/dm_open.go
Normal file
@ -0,0 +1,65 @@
|
||||
// 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 dm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for pgsql.
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "dm"
|
||||
)
|
||||
if config.Name == "" {
|
||||
return nil, fmt.Errorf(
|
||||
`dm.Open failed for driver "%s" without DB Name`, underlyingDriverName,
|
||||
)
|
||||
}
|
||||
// Data Source Name of DM8:
|
||||
// dm://userName:password@ip:port/dbname
|
||||
// dm://userName:password@DW/dbname?DW=(192.168.1.1:5236,192.168.1.2:5236)
|
||||
var domain string
|
||||
if config.Port != "" {
|
||||
domain = fmt.Sprintf("%s:%s", config.Host, config.Port)
|
||||
} else {
|
||||
domain = config.Host
|
||||
}
|
||||
source = fmt.Sprintf(
|
||||
"dm://%s:%s@%s/%s?charset=%s&schema=%s",
|
||||
config.User, config.Pass, domain, config.Name, config.Charset, config.Name,
|
||||
)
|
||||
// Demo of timezone setting:
|
||||
// &loc=Asia/Shanghai
|
||||
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)
|
||||
}
|
||||
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`dm.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
70
contrib/drivers/dm/dm_table_fields.go
Normal file
70
contrib/drivers/dm/dm_table_fields.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package dm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
tableFieldsSqlTmp = `SELECT * FROM ALL_TAB_COLUMNS WHERE Table_Name= '%s' AND OWNER = '%s'`
|
||||
)
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
// When no schema is specified, the configuration item is returned by default
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
// When usedSchema is empty, return the default link
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The link has been distinguished and no longer needs to judge the owner
|
||||
result, err = d.DoSelect(
|
||||
ctx, link,
|
||||
fmt.Sprintf(
|
||||
tableFieldsSqlTmp,
|
||||
strings.ToUpper(table),
|
||||
strings.ToUpper(d.GetSchema()),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
// m[NULLABLE] returns "N" "Y"
|
||||
// "N" means not null
|
||||
// "Y" means could be null
|
||||
var nullable bool
|
||||
if m["NULLABLE"].String() != "N" {
|
||||
nullable = true
|
||||
}
|
||||
fields[m["COLUMN_NAME"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["COLUMN_NAME"].String(),
|
||||
Type: m["DATA_TYPE"].String(),
|
||||
Null: nullable,
|
||||
Default: m["DATA_DEFAULT"].Val(),
|
||||
// Key: m["Key"].String(),
|
||||
// Extra: m["Extra"].String(),
|
||||
// Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
39
contrib/drivers/dm/dm_tables.go
Normal file
39
contrib/drivers/dm/dm_tables.go
Normal 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 dm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
const (
|
||||
tablesSqlTmp = `SELECT * FROM ALL_TABLES`
|
||||
)
|
||||
|
||||
// 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 gdb.Result
|
||||
// When schema is empty, return the default link
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The link has been distinguished and no longer needs to judge the owner
|
||||
result, err = d.DoSelect(ctx, link, tablesSqlTmp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
if v, ok := m["IOT_NAME"]; ok {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -12,7 +12,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "gitee.com/chunanyong/dm"
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
@ -12,20 +12,11 @@
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// Driver is the driver for SQL server database.
|
||||
@ -43,6 +34,21 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// formatSqlTmp formats sql template string into one line.
|
||||
func formatSqlTmp(sqlTmp string) string {
|
||||
var err error
|
||||
// format sql template string.
|
||||
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sqlTmp
|
||||
}
|
||||
|
||||
// New create and returns a driver that implements gdb.Driver, which supports operations for Mssql.
|
||||
func New() gdb.Driver {
|
||||
return &Driver{}
|
||||
@ -56,262 +62,7 @@ func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for mssql.
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "sqlserver"
|
||||
)
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`database=([\w\.\-]+)+`, "database="+config.Name, source)
|
||||
}
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name,
|
||||
)
|
||||
if config.Extra != "" {
|
||||
var extraMap map[string]interface{}
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
source += fmt.Sprintf(`;%s=%s`, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
var index int
|
||||
// Convert placeholder char '?' to string "@px".
|
||||
newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
})
|
||||
newSql, _ = gregex.ReplaceString("\"", "", newSql)
|
||||
return d.Core.DoFilter(ctx, link, d.parseSql(newSql), args)
|
||||
}
|
||||
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
// for support of microsoft sql server.
|
||||
func (d *Driver) parseSql(sql string) string {
|
||||
// SELECT * FROM USER WHERE ID=1 LIMIT 1
|
||||
if m, _ := gregex.MatchString(`^SELECT(.+)LIMIT 1$`, sql); len(m) > 1 {
|
||||
return fmt.Sprintf(`SELECT TOP 1 %s`, m[1])
|
||||
}
|
||||
// SELECT * FROM USER WHERE AGE>18 ORDER BY ID DESC LIMIT 100, 200
|
||||
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
return sql
|
||||
}
|
||||
res, err := gregex.MatchAllString(patten, sql)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
keyword = strings.TrimSpace(res[index][0])
|
||||
)
|
||||
index++
|
||||
switch strings.ToUpper(keyword) {
|
||||
case "SELECT":
|
||||
// LIMIT statement checks.
|
||||
if len(res) < 2 ||
|
||||
(strings.HasPrefix(res[index][0], "LIMIT") == false &&
|
||||
strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
break
|
||||
}
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
// ORDER BY statement checks.
|
||||
var (
|
||||
selectStr = ""
|
||||
orderStr = ""
|
||||
haveOrder = gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
)
|
||||
if haveOrder {
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "ORDER BY") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
orderExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
|
||||
if len(orderExpr) != 4 ||
|
||||
strings.EqualFold(orderExpr[1], "ORDER BY") == false ||
|
||||
strings.EqualFold(orderExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
orderStr = orderExpr[2]
|
||||
} else {
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
}
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") ||
|
||||
strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
break
|
||||
}
|
||||
}
|
||||
if haveOrder {
|
||||
sql = fmt.Sprintf(
|
||||
"SELECT * FROM "+
|
||||
"(SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ "+
|
||||
"WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d",
|
||||
orderStr, selectStr, first, first+limit,
|
||||
)
|
||||
} else {
|
||||
if first == 0 {
|
||||
first = limit
|
||||
}
|
||||
sql = fmt.Sprintf(
|
||||
"SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ",
|
||||
limit, first+limit, selectStr,
|
||||
)
|
||||
}
|
||||
default:
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
// 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 gdb.Result
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = d.DoSelect(
|
||||
ctx, link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT
|
||||
a.name Field,
|
||||
CASE b.name
|
||||
WHEN 'datetime' THEN 'datetime'
|
||||
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
|
||||
WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
|
||||
CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
|
||||
CASE WHEN exists (
|
||||
SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
|
||||
SELECT name FROM sysindexes WHERE indid IN (
|
||||
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
|
||||
)
|
||||
)
|
||||
) THEN 'PRI' ELSE '' END AS [Key],
|
||||
CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
|
||||
isnull(e.text,'') AS [Default],
|
||||
isnull(g.[value],'') AS [Comment]
|
||||
FROM syscolumns a
|
||||
LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype
|
||||
INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties'
|
||||
LEFT JOIN syscomments e ON a.cdefault=e.id
|
||||
LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id
|
||||
LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
|
||||
WHERE d.name='%s'
|
||||
ORDER BY a.id,a.colorder`,
|
||||
table,
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DoSelect(ctx, link, structureSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &gdb.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
|
||||
}
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by mssql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Replace operation is not supported by mssql driver`,
|
||||
)
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
||||
|
163
contrib/drivers/mssql/mssql_do_filter.go
Normal file
163
contrib/drivers/mssql/mssql_do_filter.go
Normal file
@ -0,0 +1,163 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
var (
|
||||
selectSqlTmp = `SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ `
|
||||
selectWithOrderSqlTmp = `
|
||||
SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_
|
||||
WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
selectWithOrderSqlTmp = formatSqlTmp(selectWithOrderSqlTmp)
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link gdb.Link, sql string, args []interface{},
|
||||
) (newSql string, newArgs []interface{}, 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 1$`, toBeCommittedSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(match) > 1 {
|
||||
return fmt.Sprintf(`SELECT TOP 1 %s`, match[1]), nil
|
||||
}
|
||||
|
||||
// SELECT * FROM USER WHERE AGE>18 ORDER BY ID DESC LIMIT 100, 200
|
||||
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, toBeCommittedSql) == false {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
allMatch, err := gregex.MatchAllString(patten, toBeCommittedSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var index = 1
|
||||
// LIMIT statement checks.
|
||||
if len(allMatch) < 2 ||
|
||||
(strings.HasPrefix(allMatch[index][0], "LIMIT") == false &&
|
||||
strings.HasPrefix(allMatch[index][0], "limit") == false) {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", toBeCommittedSql) == false {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
// ORDER BY statement checks.
|
||||
var (
|
||||
selectStr = ""
|
||||
orderStr = ""
|
||||
haveOrder = gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", toBeCommittedSql)
|
||||
)
|
||||
if haveOrder {
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", toBeCommittedSql)
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "ORDER BY") == false {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
orderExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", toBeCommittedSql)
|
||||
if len(orderExpr) != 4 ||
|
||||
strings.EqualFold(orderExpr[1], "ORDER BY") == false ||
|
||||
strings.EqualFold(orderExpr[3], "LIMIT") == false {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
orderStr = orderExpr[2]
|
||||
} else {
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", toBeCommittedSql)
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
}
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(allMatch[index]); i++ {
|
||||
if len(strings.TrimSpace(allMatch[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(allMatch[index][i], "LIMIT") ||
|
||||
strings.HasPrefix(allMatch[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+2])
|
||||
break
|
||||
}
|
||||
}
|
||||
if haveOrder {
|
||||
toBeCommittedSql = fmt.Sprintf(
|
||||
selectWithOrderSqlTmp,
|
||||
orderStr, selectStr, first, first+limit,
|
||||
)
|
||||
return toBeCommittedSql, nil
|
||||
}
|
||||
|
||||
if first == 0 {
|
||||
first = limit
|
||||
}
|
||||
toBeCommittedSql = fmt.Sprintf(
|
||||
selectSqlTmp,
|
||||
limit, first+limit, selectStr,
|
||||
)
|
||||
return toBeCommittedSql, nil
|
||||
}
|
36
contrib/drivers/mssql/mssql_do_insert.go
Normal file
36
contrib/drivers/mssql/mssql_do_insert.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by mssql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Replace operation is not supported by mssql driver`,
|
||||
)
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
59
contrib/drivers/mssql/mssql_open.go
Normal file
59
contrib/drivers/mssql/mssql_open.go
Normal 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 mssql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for mssql.
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "sqlserver"
|
||||
)
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`database=([\w\.\-]+)+`, "database="+config.Name, source)
|
||||
}
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name,
|
||||
)
|
||||
if config.Extra != "" {
|
||||
var extraMap map[string]interface{}
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
source += fmt.Sprintf(`;%s=%s`, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
84
contrib/drivers/mssql/mssql_table_fields.go
Normal file
84
contrib/drivers/mssql/mssql_table_fields.go
Normal file
@ -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 mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
tableFieldsSqlTmp = `
|
||||
SELECT
|
||||
a.name Field,
|
||||
CASE b.name
|
||||
WHEN 'datetime' THEN 'datetime'
|
||||
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
|
||||
WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
|
||||
CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
|
||||
CASE WHEN exists (
|
||||
SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
|
||||
SELECT name FROM sysindexes WHERE indid IN (
|
||||
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
|
||||
)
|
||||
)
|
||||
) THEN 'PRI' ELSE '' END AS [Key],
|
||||
CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
|
||||
isnull(e.text,'') AS [Default],
|
||||
isnull(g.[value],'') AS [Comment]
|
||||
FROM syscolumns a
|
||||
LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype
|
||||
INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties'
|
||||
LEFT JOIN syscomments e ON a.cdefault=e.id
|
||||
LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id
|
||||
LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
|
||||
WHERE d.name='%s'
|
||||
ORDER BY a.id,a.colorder
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.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]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &gdb.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
|
||||
}
|
38
contrib/drivers/mssql/mssql_tables.go
Normal file
38
contrib/drivers/mssql/mssql_tables.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
const (
|
||||
tablesSqlTmp = `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 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 gdb.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
|
||||
}
|
@ -10,7 +10,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
@ -8,20 +8,10 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// Driver is the driver for mysql database.
|
||||
@ -59,119 +49,7 @@ func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "mysql"
|
||||
)
|
||||
// [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source)
|
||||
}
|
||||
} else {
|
||||
// TODO: Do not set charset when charset is not specified (in v2.5.0)
|
||||
source = fmt.Sprintf(
|
||||
"%s:%s@%s(%s:%s)/%s?charset=%s",
|
||||
config.User, config.Pass, config.Protocol, config.Host, config.Port, 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)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter handles the sql before posts it to database.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
// 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 gdb.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
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(
|
||||
ctx, link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &gdb.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
|
||||
}
|
||||
|
18
contrib/drivers/mysql/mysql_do_filter.go
Normal file
18
contrib/drivers/mysql/mysql_do_filter.go
Normal file
@ -0,0 +1,18 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// DoFilter handles the sql before posts it to database.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
63
contrib/drivers/mysql/mysql_open.go
Normal file
63
contrib/drivers/mysql/mysql_open.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
// 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 *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "mysql"
|
||||
)
|
||||
// [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source)
|
||||
}
|
||||
} else {
|
||||
// TODO: Do not set charset when charset is not specified (in v2.5.0)
|
||||
source = fmt.Sprintf(
|
||||
"%s:%s@%s(%s:%s)/%s?charset=%s",
|
||||
config.User, config.Pass, config.Protocol, config.Host, config.Port, 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)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
59
contrib/drivers/mysql/mysql_table_fields.go
Normal file
59
contrib/drivers/mysql/mysql_table_fields.go
Normal 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 mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(
|
||||
ctx, link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &gdb.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
|
||||
}
|
33
contrib/drivers/mysql/mysql_tables.go
Normal file
33
contrib/drivers/mysql/mysql_tables.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// 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 gdb.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
|
||||
}
|
@ -12,21 +12,9 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
gora "github.com/sijms/go-ora/v2"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// Driver is the driver for oracle database.
|
||||
@ -44,6 +32,21 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// formatSqlTmp formats sql template string into one line.
|
||||
func formatSqlTmp(sqlTmp string) string {
|
||||
var err error
|
||||
// format sql template string.
|
||||
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sqlTmp
|
||||
}
|
||||
|
||||
// New create and returns a driver that implements gdb.Driver, which supports operations for Oracle.
|
||||
func New() gdb.Driver {
|
||||
return &Driver{}
|
||||
@ -57,255 +60,7 @@ func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for oracle.
|
||||
func (d *Driver) Open(config *gdb.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.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`@(.+?)/([\w\.\-]+)+`, "@$1/"+config.Name, source)
|
||||
}
|
||||
} else {
|
||||
if config.Extra != "" {
|
||||
var extraMap map[string]interface{}
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
options[k] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
var index int
|
||||
// Convert placeholder char '?' to string ":vx".
|
||||
newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(":v%d", index)
|
||||
})
|
||||
newSql, _ = gregex.ReplaceString("\"", "", newSql)
|
||||
return d.Core.DoFilter(ctx, link, d.parseSql(newSql), args)
|
||||
}
|
||||
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
// for support of oracle server.
|
||||
func (d *Driver) parseSql(sql string) string {
|
||||
var (
|
||||
patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))`
|
||||
allMatch, _ = gregex.MatchAllString(patten, sql)
|
||||
)
|
||||
if len(allMatch) == 0 {
|
||||
return sql
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
keyword = strings.ToUpper(strings.TrimSpace(allMatch[index][0]))
|
||||
)
|
||||
index++
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
if len(allMatch) < 2 || strings.HasPrefix(allMatch[index][0], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
page, limit := 0, 0
|
||||
for i := 1; i < len(allMatch[index]); i++ {
|
||||
if len(strings.TrimSpace(allMatch[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(allMatch[index][i], "LIMIT") {
|
||||
if allMatch[index][i+2] != "" {
|
||||
page, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+2])
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
limit = (page/limit + 1) * limit
|
||||
|
||||
page, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
} else {
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
sql = fmt.Sprintf(
|
||||
"SELECT * FROM "+
|
||||
"(SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d)"+
|
||||
" WHERE ROWNUM_ > %d",
|
||||
queryExpr[1], queryExpr[2], limit, page,
|
||||
)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
// 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 gdb.Result
|
||||
result, err = d.DoSelect(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
structureSql = fmt.Sprintf(`
|
||||
SELECT
|
||||
COLUMN_NAME AS FIELD,
|
||||
CASE
|
||||
WHEN (DATA_TYPE='NUMBER' AND NVL(DATA_SCALE,0)=0) THEN 'INT'||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN (DATA_TYPE='NUMBER' AND NVL(DATA_SCALE,0)>0) THEN 'FLOAT'||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN DATA_TYPE='FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE,NULLABLE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
||||
strings.ToUpper(table),
|
||||
)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DoSelect(ctx, link, structureSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
isNull := false
|
||||
if m["NULLABLE"].String() == "Y" {
|
||||
isNull = true
|
||||
}
|
||||
|
||||
fields[m["FIELD"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["FIELD"].String(),
|
||||
Type: m["TYPE"].String(),
|
||||
Null: isNull,
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by oracle driver`)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by oracle driver`)
|
||||
}
|
||||
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []interface{}
|
||||
)
|
||||
// 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(gdb.SqlResult)
|
||||
charL, charR = d.GetChars()
|
||||
keyStr = charL + strings.Join(keys, charL+","+charR) + charR
|
||||
valueHolderStr = strings.Join(valueHolder, ",")
|
||||
)
|
||||
// Format "INSERT...INTO..." statement.
|
||||
intoStr := make([]string, 0)
|
||||
for i := 0; i < len(list); i++ {
|
||||
for _, k := range keys {
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
values = append(values, valueHolderStr)
|
||||
intoStr = append(intoStr, fmt.Sprintf("INTO %s(%s) VALUES(%s)", table, keyStr, valueHolderStr))
|
||||
if len(intoStr) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"INSERT ALL %s SELECT * FROM DUAL",
|
||||
strings.Join(intoStr, " "),
|
||||
), 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]
|
||||
intoStr = intoStr[:0]
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
||||
|
140
contrib/drivers/oracle/oracle_do_filter.go
Normal file
140
contrib/drivers/oracle/oracle_do_filter.go
Normal file
@ -0,0 +1,140 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
var (
|
||||
newSqlReplacementTmp = `
|
||||
SELECT * FROM (
|
||||
SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d
|
||||
)
|
||||
WHERE ROWNUM_ > %d
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
newSqlReplacementTmp = formatSqlTmp(newSqlReplacementTmp)
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, 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
|
||||
}
|
95
contrib/drivers/oracle/oracle_do_insert.go
Normal file
95
contrib/drivers/oracle/oracle_do_insert.go
Normal file
@ -0,0 +1,95 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by oracle driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Replace operation is not supported by oracle driver`,
|
||||
)
|
||||
}
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []interface{}
|
||||
)
|
||||
// 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(gdb.SqlResult)
|
||||
charL, charR = d.GetChars()
|
||||
keyStr = charL + strings.Join(keys, charL+","+charR) + charR
|
||||
valueHolderStr = strings.Join(valueHolder, ",")
|
||||
)
|
||||
// Format "INSERT...INTO..." statement.
|
||||
intoStrArray := make([]string, 0)
|
||||
for i := 0; i < len(list); i++ {
|
||||
for _, k := range keys {
|
||||
if s, ok := list[i][k].(gdb.Raw); ok {
|
||||
params = append(params, gconv.String(s))
|
||||
} else {
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
}
|
||||
values = append(values, valueHolderStr)
|
||||
intoStrArray = append(
|
||||
intoStrArray,
|
||||
fmt.Sprintf(
|
||||
"INTO %s(%s) VALUES(%s)",
|
||||
table, keyStr, valueHolderStr,
|
||||
),
|
||||
)
|
||||
if len(intoStrArray) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"INSERT ALL %s SELECT * FROM DUAL",
|
||||
strings.Join(intoStrArray, " "),
|
||||
), 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]
|
||||
intoStrArray = intoStrArray[:0]
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
69
contrib/drivers/oracle/oracle_open.go
Normal file
69
contrib/drivers/oracle/oracle_open.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
gora "github.com/sijms/go-ora/v2"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for oracle.
|
||||
func (d *Driver) Open(config *gdb.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.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`@(.+?)/([\w\.\-]+)+`, "@$1/"+config.Name, source)
|
||||
}
|
||||
} else {
|
||||
if config.Extra != "" {
|
||||
var extraMap map[string]interface{}
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
options[k] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
67
contrib/drivers/oracle/oracle_table_fields.go
Normal file
67
contrib/drivers/oracle/oracle_table_fields.go
Normal file
@ -0,0 +1,67 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var (
|
||||
tableFieldsSqlTmp = `
|
||||
SELECT
|
||||
COLUMN_NAME AS FIELD,
|
||||
CASE
|
||||
WHEN (DATA_TYPE='NUMBER' AND NVL(DATA_SCALE,0)=0) THEN 'INT'||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN (DATA_TYPE='NUMBER' AND NVL(DATA_SCALE,0)>0) THEN 'FLOAT'||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN DATA_TYPE='FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE,NULLABLE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
structureSql = fmt.Sprintf(tableFieldsSqlTmp, strings.ToUpper(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]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
isNull := false
|
||||
if m["NULLABLE"].String() == "Y" {
|
||||
isNull = true
|
||||
}
|
||||
|
||||
fields[m["FIELD"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["FIELD"].String(),
|
||||
Type: m["TYPE"].String(),
|
||||
Null: isNull,
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
39
contrib/drivers/oracle/oracle_tables.go
Normal file
39
contrib/drivers/oracle/oracle_tables.go
Normal 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
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 gdb.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
|
||||
}
|
@ -8,10 +8,11 @@ package oracle_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
@ -11,12 +11,13 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
_ "github.com/sijms/go-ora/v2"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
_ "github.com/sijms/go-ora/v2"
|
||||
)
|
||||
|
||||
var (
|
@ -131,7 +131,6 @@ func Test_Model_RightJoin(t *testing.T) {
|
||||
func TestPage(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
db.SetDebug(true)
|
||||
result, err := db.Model(table).Page(1, 2).Order("ID").All()
|
||||
gtest.Assert(err, nil)
|
||||
fmt.Println("page:1--------", result)
|
||||
@ -235,6 +234,28 @@ func Test_Model_Insert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/3286
|
||||
func Test_Model_Insert_Raw(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(g.Map{
|
||||
"ID": 1,
|
||||
"UID": 1,
|
||||
"PASSPORT": "t1",
|
||||
"PASSWORD": "25d55ad283aa400af464c76d713c07ad",
|
||||
"NICKNAME": gdb.Raw("name_1"),
|
||||
"SALARY": 2675.11,
|
||||
"CREATE_TIME": gtime.Now().String(),
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
value, err := db.Model(table).Fields("PASSPORT").Where("id=1").Value()
|
||||
t.AssertNil(err)
|
||||
t.Assert(value.String(), "t1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_Time(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
@ -12,22 +12,12 @@
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// Driver is the driver for postgresql database.
|
||||
@ -47,6 +37,21 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// formatSqlTmp formats sql template string into one line.
|
||||
func formatSqlTmp(sqlTmp string) string {
|
||||
var err error
|
||||
// format sql template string.
|
||||
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sqlTmp
|
||||
}
|
||||
|
||||
// New create and returns a driver that implements gdb.Driver, which supports operations for PostgreSql.
|
||||
func New() gdb.Driver {
|
||||
return &Driver{}
|
||||
@ -60,425 +65,7 @@ func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for pgsql.
|
||||
// https://pkg.go.dev/github.com/lib/pq
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "postgres"
|
||||
)
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`dbname=([\w\.\-]+)+`, "dbname="+config.Name, source)
|
||||
}
|
||||
} else {
|
||||
if config.Name != "" {
|
||||
source = fmt.Sprintf(
|
||||
"user=%s password=%s host=%s port=%s dbname=%s sslmode=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name,
|
||||
)
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"user=%s password=%s host=%s port=%s sslmode=disable",
|
||||
config.User, config.Pass, config.Host, config.Port,
|
||||
)
|
||||
}
|
||||
|
||||
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 != "" {
|
||||
var extraMap map[string]interface{}
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
source += fmt.Sprintf(` %s=%s`, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// CheckLocalTypeForField checks and returns corresponding local golang type for given db type.
|
||||
func (d *Driver) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (gdb.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
|
||||
// For pgsql, int2 = smallint.
|
||||
"int2",
|
||||
// For pgsql, int4 = integer
|
||||
"int4":
|
||||
return gdb.LocalTypeInt, nil
|
||||
|
||||
case
|
||||
// For pgsql, int8 = bigint
|
||||
"int8":
|
||||
return gdb.LocalTypeInt64, nil
|
||||
|
||||
case
|
||||
"_int2",
|
||||
"_int4":
|
||||
return gdb.LocalTypeIntSlice, nil
|
||||
|
||||
case
|
||||
"_int8":
|
||||
return gdb.LocalTypeInt64Slice, 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:
|
||||
// `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
|
||||
func (d *Driver) ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
||||
typeName, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||
typeName = strings.ToLower(typeName)
|
||||
switch typeName {
|
||||
// For pgsql, int2 = smallint and int4 = integer.
|
||||
case "int2", "int4":
|
||||
return gconv.Int(gconv.String(fieldValue)), nil
|
||||
|
||||
// For pgsql, int8 = bigint.
|
||||
case "int8":
|
||||
return gconv.Int64(gconv.String(fieldValue)), nil
|
||||
|
||||
// Int32 slice.
|
||||
case
|
||||
"_int2", "_int4":
|
||||
return gconv.Ints(
|
||||
gstr.ReplaceByMap(gconv.String(fieldValue),
|
||||
map[string]string{
|
||||
"{": "[",
|
||||
"}": "]",
|
||||
},
|
||||
),
|
||||
), nil
|
||||
|
||||
// Int64 slice.
|
||||
case
|
||||
"_int8":
|
||||
return gconv.Int64s(
|
||||
gstr.ReplaceByMap(gconv.String(fieldValue),
|
||||
map[string]string{
|
||||
"{": "[",
|
||||
"}": "]",
|
||||
},
|
||||
),
|
||||
), nil
|
||||
|
||||
default:
|
||||
return d.Core.ConvertValueForLocal(ctx, fieldType, fieldValue)
|
||||
}
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
var index int
|
||||
// Convert placeholder char '?' to string "$x".
|
||||
newSql, _ = gregex.ReplaceStringFunc(`\?`, sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(`$%d`, index)
|
||||
})
|
||||
// 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, _ = gregex.ReplaceStringFuncMatch(`(::jsonb([^\w\d]*)\$\d)`, newSql, func(match []string) string {
|
||||
return fmt.Sprintf(`::jsonb%s?`, match[2])
|
||||
})
|
||||
newSql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, newSql)
|
||||
return d.Core.DoFilter(ctx, link, newSql, args)
|
||||
}
|
||||
|
||||
// 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 gdb.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(`
|
||||
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`,
|
||||
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 gdb.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 := regexp.MustCompile(`PostgreSQL (\d+\.\d+)`).FindStringSubmatch(v.String())
|
||||
if len(matches) >= 2 {
|
||||
return matches[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
// TODO duplicated `id` result?
|
||||
structureSql = fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null,
|
||||
(case when d.contype is not null then 'pri' 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.relname = '%s' and a.attisdropped is false and a.attnum > 0
|
||||
ORDER BY a.attnum`,
|
||||
table,
|
||||
)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DoSelect(ctx, link, structureSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
var (
|
||||
index = 0
|
||||
name string
|
||||
ok bool
|
||||
)
|
||||
for _, m := range result {
|
||||
name = m["field"].String()
|
||||
// Filter duplicated fields.
|
||||
if _, ok = fields[name]; ok {
|
||||
continue
|
||||
}
|
||||
fields[name] = &gdb.TableField{
|
||||
Index: index,
|
||||
Name: name,
|
||||
Type: m["type"].String(),
|
||||
Null: !m["null"].Bool(),
|
||||
Key: m["key"].String(),
|
||||
Default: m["default_value"].Val(),
|
||||
Comment: m["comment"].String(),
|
||||
}
|
||||
index++
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by pgsql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Replace operation is not supported by pgsql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionIgnore:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Insert ignore operation is not supported by pgsql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionDefault:
|
||||
tableFields, err := d.GetCore().GetDB().TableFields(ctx, table)
|
||||
if err == nil {
|
||||
for _, field := range tableFields {
|
||||
if field.Key == "pri" {
|
||||
pkField := *field
|
||||
ctx = context.WithValue(ctx, internalPrimaryKeyInCtx, pkField)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
|
||||
// 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 gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
var (
|
||||
isUseCoreDoExec bool = false // Check whether the default method needs to be used
|
||||
primaryKey string = ""
|
||||
pkField gdb.TableField
|
||||
)
|
||||
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if tx := gdb.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 := gdb.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.(gdb.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 += " RETURNING " + 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
|
||||
|
||||
if d.GetConfig().ExecTimeout > 0 {
|
||||
var cancelFunc context.CancelFunc
|
||||
ctx, cancelFunc = context.WithTimeout(ctx, d.GetConfig().ExecTimeout)
|
||||
defer cancelFunc()
|
||||
}
|
||||
|
||||
// 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 gdb.DoCommitOutput
|
||||
out, err = d.DoCommit(ctx, gdb.DoCommitInput{
|
||||
Link: link,
|
||||
Sql: sql,
|
||||
Args: args,
|
||||
Stmt: nil,
|
||||
Type: gdb.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
|
||||
}
|
||||
|
98
contrib/drivers/pgsql/pgsql_convert.go
Normal file
98
contrib/drivers/pgsql/pgsql_convert.go
Normal file
@ -0,0 +1,98 @@
|
||||
// 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"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// CheckLocalTypeForField checks and returns corresponding local golang type for given db type.
|
||||
func (d *Driver) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (gdb.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
|
||||
// For pgsql, int2 = smallint.
|
||||
"int2",
|
||||
// For pgsql, int4 = integer
|
||||
"int4":
|
||||
return gdb.LocalTypeInt, nil
|
||||
|
||||
case
|
||||
// For pgsql, int8 = bigint
|
||||
"int8":
|
||||
return gdb.LocalTypeInt64, nil
|
||||
|
||||
case
|
||||
"_int2",
|
||||
"_int4":
|
||||
return gdb.LocalTypeIntSlice, nil
|
||||
|
||||
case
|
||||
"_int8":
|
||||
return gdb.LocalTypeInt64Slice, 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:
|
||||
// `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
|
||||
func (d *Driver) ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
||||
typeName, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||
typeName = strings.ToLower(typeName)
|
||||
switch typeName {
|
||||
// For pgsql, int2 = smallint and int4 = integer.
|
||||
case "int2", "int4":
|
||||
return gconv.Int(gconv.String(fieldValue)), nil
|
||||
|
||||
// For pgsql, int8 = bigint.
|
||||
case "int8":
|
||||
return gconv.Int64(gconv.String(fieldValue)), nil
|
||||
|
||||
// Int32 slice.
|
||||
case
|
||||
"_int2", "_int4":
|
||||
return gconv.Ints(
|
||||
gstr.ReplaceByMap(gconv.String(fieldValue),
|
||||
map[string]string{
|
||||
"{": "[",
|
||||
"}": "]",
|
||||
},
|
||||
),
|
||||
), nil
|
||||
|
||||
// Int64 slice.
|
||||
case
|
||||
"_int8":
|
||||
return gconv.Int64s(
|
||||
gstr.ReplaceByMap(gconv.String(fieldValue),
|
||||
map[string]string{
|
||||
"{": "[",
|
||||
"}": "]",
|
||||
},
|
||||
),
|
||||
), nil
|
||||
|
||||
default:
|
||||
return d.Core.ConvertValueForLocal(ctx, fieldType, fieldValue)
|
||||
}
|
||||
}
|
115
contrib/drivers/pgsql/pgsql_do_exec.go
Normal file
115
contrib/drivers/pgsql/pgsql_do_exec.go
Normal file
@ -0,0 +1,115 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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 gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
var (
|
||||
isUseCoreDoExec bool = false // Check whether the default method needs to be used
|
||||
primaryKey string = ""
|
||||
pkField gdb.TableField
|
||||
)
|
||||
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if tx := gdb.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 := gdb.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.(gdb.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 += " RETURNING " + 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
|
||||
|
||||
if d.GetConfig().ExecTimeout > 0 {
|
||||
var cancelFunc context.CancelFunc
|
||||
ctx, cancelFunc = context.WithTimeout(ctx, d.GetConfig().ExecTimeout)
|
||||
defer cancelFunc()
|
||||
}
|
||||
|
||||
// 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 gdb.DoCommitOutput
|
||||
out, err = d.DoCommit(ctx, gdb.DoCommitInput{
|
||||
Link: link,
|
||||
Sql: sql,
|
||||
Args: args,
|
||||
Stmt: nil,
|
||||
Type: gdb.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
|
||||
}
|
46
contrib/drivers/pgsql/pgsql_do_filter.go
Normal file
46
contrib/drivers/pgsql/pgsql_do_filter.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(
|
||||
ctx context.Context, link gdb.Link, sql string, args []interface{},
|
||||
) (newSql string, newArgs []interface{}, 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
|
||||
}
|
||||
newArgs = args
|
||||
return d.Core.DoFilter(ctx, link, newSql, newArgs)
|
||||
}
|
52
contrib/drivers/pgsql/pgsql_do_insert.go
Normal file
52
contrib/drivers/pgsql/pgsql_do_insert.go
Normal file
@ -0,0 +1,52 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data forF given table.
|
||||
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case gdb.InsertOptionSave:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by pgsql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Replace operation is not supported by pgsql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionIgnore:
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Insert ignore operation is not supported by pgsql driver`,
|
||||
)
|
||||
|
||||
case gdb.InsertOptionDefault:
|
||||
tableFields, err := d.GetCore().GetDB().TableFields(ctx, table)
|
||||
if err == nil {
|
||||
for _, field := range tableFields {
|
||||
if field.Key == "pri" {
|
||||
pkField := *field
|
||||
ctx = context.WithValue(ctx, internalPrimaryKeyInCtx, pkField)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
76
contrib/drivers/pgsql/pgsql_open.go
Normal file
76
contrib/drivers/pgsql/pgsql_open.go
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"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 *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "postgres"
|
||||
)
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`dbname=([\w\.\-]+)+`, "dbname="+config.Name, source)
|
||||
}
|
||||
} else {
|
||||
if config.Name != "" {
|
||||
source = fmt.Sprintf(
|
||||
"user=%s password=%s host=%s port=%s dbname=%s sslmode=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name,
|
||||
)
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"user=%s password=%s host=%s port=%s sslmode=disable",
|
||||
config.User, config.Pass, config.Host, config.Port,
|
||||
)
|
||||
}
|
||||
|
||||
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 != "" {
|
||||
var extraMap map[string]interface{}
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
source += fmt.Sprintf(` %s=%s`, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
78
contrib/drivers/pgsql/pgsql_table_fields.go
Normal file
78
contrib/drivers/pgsql/pgsql_table_fields.go
Normal file
@ -0,0 +1,78 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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 is not null then 'pri' 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.relname = '%s' and a.attisdropped is false and a.attnum > 0
|
||||
ORDER BY a.attnum`
|
||||
)
|
||||
|
||||
func init() {
|
||||
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.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]*gdb.TableField)
|
||||
var (
|
||||
index = 0
|
||||
name string
|
||||
ok bool
|
||||
)
|
||||
for _, m := range result {
|
||||
name = m["field"].String()
|
||||
// Filter duplicated fields.
|
||||
if _, ok = fields[name]; ok {
|
||||
continue
|
||||
}
|
||||
fields[name] = &gdb.TableField{
|
||||
Index: index,
|
||||
Name: name,
|
||||
Type: m["type"].String(),
|
||||
Null: !m["null"].Bool(),
|
||||
Key: m["key"].String(),
|
||||
Default: m["default_value"].Val(),
|
||||
Comment: m["comment"].String(),
|
||||
}
|
||||
index++
|
||||
}
|
||||
return fields, nil
|
||||
}
|
96
contrib/drivers/pgsql/pgsql_tables.go
Normal file
96
contrib/drivers/pgsql/pgsql_tables.go
Normal file
@ -0,0 +1,96 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
tablesSqlTmp = formatSqlTmp(tablesSqlTmp)
|
||||
}
|
||||
|
||||
// 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 gdb.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 gdb.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 := regexp.MustCompile(`PostgreSQL (\d+\.\d+)`).FindStringSubmatch(v.String())
|
||||
if len(matches) >= 2 {
|
||||
return matches[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
@ -11,20 +11,9 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/glebarez/go-sqlite"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gurl"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// Driver is the driver for sqlite database.
|
||||
@ -55,138 +44,7 @@ func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for sqlite.
|
||||
// https://github.com/glebarez/go-sqlite
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "sqlite"
|
||||
)
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
} else {
|
||||
source = config.Name
|
||||
}
|
||||
// It searches the source file to locate its absolute path..
|
||||
if absolutePath, _ := gfile.Search(source); absolutePath != "" {
|
||||
source = absolutePath
|
||||
}
|
||||
|
||||
// Multiple PRAGMAs can be specified, e.g.:
|
||||
// path/to/some.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)
|
||||
if config.Extra != "" {
|
||||
var (
|
||||
options string
|
||||
extraMap map[string]interface{}
|
||||
)
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
if options != "" {
|
||||
options += "&"
|
||||
}
|
||||
options += fmt.Sprintf(`_pragma=%s(%s)`, k, gurl.Encode(gconv.String(v)))
|
||||
}
|
||||
if len(options) > 1 {
|
||||
source += "?" + options
|
||||
}
|
||||
}
|
||||
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`sql.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
// Special insert/ignore operation for sqlite.
|
||||
switch {
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationIgnore):
|
||||
sql = "INSERT OR IGNORE" + sql[len(gdb.InsertOperationIgnore):]
|
||||
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationReplace):
|
||||
sql = "INSERT OR REPLACE" + sql[len(gdb.InsertOperationReplace):]
|
||||
|
||||
default:
|
||||
if gstr.Contains(sql, gdb.InsertOnDuplicateKeyUpdate) {
|
||||
return sql, args, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by sqlite driver`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
// 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 gdb.Result
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = d.DoSelect(
|
||||
ctx,
|
||||
link,
|
||||
`SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, d.QuoteWord(table)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
mKey := ""
|
||||
if m["pk"].Bool() {
|
||||
mKey = "pri"
|
||||
}
|
||||
fields[m["name"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["name"].String(),
|
||||
Type: m["type"].String(),
|
||||
Key: mKey,
|
||||
Default: m["dflt_value"].Val(),
|
||||
Null: !m["notnull"].Bool(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
37
contrib/drivers/sqlite/sqlite_do_filter.go
Normal file
37
contrib/drivers/sqlite/sqlite_do_filter.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"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 gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
// Special insert/ignore operation for sqlite.
|
||||
switch {
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationIgnore):
|
||||
sql = "INSERT OR IGNORE" + sql[len(gdb.InsertOperationIgnore):]
|
||||
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationReplace):
|
||||
sql = "INSERT OR REPLACE" + sql[len(gdb.InsertOperationReplace):]
|
||||
|
||||
default:
|
||||
if gstr.Contains(sql, gdb.InsertOnDuplicateKeyUpdate) {
|
||||
return sql, args, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by sqlite driver`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
71
contrib/drivers/sqlite/sqlite_open.go
Normal file
71
contrib/drivers/sqlite/sqlite_open.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gurl"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Open creates and returns an underlying sql.DB object for sqlite.
|
||||
// https://github.com/glebarez/go-sqlite
|
||||
func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
var (
|
||||
source string
|
||||
underlyingDriverName = "sqlite"
|
||||
)
|
||||
if config.Link != "" {
|
||||
// ============================================================================
|
||||
// Deprecated from v2.2.0.
|
||||
// ============================================================================
|
||||
source = config.Link
|
||||
} else {
|
||||
source = config.Name
|
||||
}
|
||||
// It searches the source file to locate its absolute path..
|
||||
if absolutePath, _ := gfile.Search(source); absolutePath != "" {
|
||||
source = absolutePath
|
||||
}
|
||||
|
||||
// Multiple PRAGMAs can be specified, e.g.:
|
||||
// path/to/some.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)
|
||||
if config.Extra != "" {
|
||||
var (
|
||||
options string
|
||||
extraMap map[string]interface{}
|
||||
)
|
||||
if extraMap, err = gstr.Parse(config.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extraMap {
|
||||
if options != "" {
|
||||
options += "&"
|
||||
}
|
||||
options += fmt.Sprintf(`_pragma=%s(%s)`, k, gurl.Encode(gconv.String(v)))
|
||||
}
|
||||
if len(options) > 1 {
|
||||
source += "?" + options
|
||||
}
|
||||
}
|
||||
|
||||
if db, err = sql.Open(underlyingDriverName, source); err != nil {
|
||||
err = gerror.WrapCodef(
|
||||
gcode.CodeDbOperationError, err,
|
||||
`sql.Open failed for driver "%s" by source "%s"`, underlyingDriverName, source,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
49
contrib/drivers/sqlite/sqlite_table_fields.go
Normal file
49
contrib/drivers/sqlite/sqlite_table_fields.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, d.QuoteWord(table)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
mKey := ""
|
||||
if m["pk"].Bool() {
|
||||
mKey = "pri"
|
||||
}
|
||||
fields[m["name"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["name"].String(),
|
||||
Type: m["type"].String(),
|
||||
Key: mKey,
|
||||
Default: m["dflt_value"].Val(),
|
||||
Null: !m["notnull"].Bool(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
38
contrib/drivers/sqlite/sqlite_tables.go
Normal file
38
contrib/drivers/sqlite/sqlite_tables.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
const (
|
||||
tablesSqlTmp = `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' 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 gdb.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
|
||||
}
|
@ -3,6 +3,7 @@ module github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.6.2
|
||||
github.com/gogf/gf/v2 v2.6.2
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
)
|
||||
@ -10,10 +11,13 @@ require (
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
@ -21,6 +25,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
||||
@ -29,6 +34,13 @@ require (
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/gogf/gf/v2 => ../../../
|
||||
replace (
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 => ../sqlite/
|
||||
github.com/gogf/gf/v2 => ../../../
|
||||
)
|
||||
|
@ -3,16 +3,23 @@ github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
|
||||
@ -32,6 +39,9 @@ github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@ -54,3 +64,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
|
@ -13,12 +13,12 @@
|
||||
package sqlitecgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gurl"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
@ -26,16 +26,15 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// Driver is the driver for sqlite database.
|
||||
type Driver struct {
|
||||
*gdb.Core
|
||||
gdb.DB
|
||||
}
|
||||
|
||||
const (
|
||||
quoteChar = "`"
|
||||
var (
|
||||
sqliteDriver = sqlite.New()
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -52,8 +51,12 @@ func New() gdb.Driver {
|
||||
// New creates and returns a database object for sqlite.
|
||||
// It implements the interface of gdb.Driver for extra database driver installation.
|
||||
func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
db, err := sqliteDriver.New(core, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Driver{
|
||||
Core: core,
|
||||
DB: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -107,88 +110,3 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||
return quoteChar, quoteChar
|
||||
}
|
||||
|
||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
// Special insert/ignore operation for sqlite.
|
||||
switch {
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationIgnore):
|
||||
sql = "INSERT OR IGNORE" + sql[len(gdb.InsertOperationIgnore):]
|
||||
|
||||
case gstr.HasPrefix(sql, gdb.InsertOperationReplace):
|
||||
sql = "INSERT OR REPLACE" + sql[len(gdb.InsertOperationReplace):]
|
||||
|
||||
default:
|
||||
if gstr.Contains(sql, gdb.InsertOnDuplicateKeyUpdate) {
|
||||
return sql, args, gerror.NewCode(
|
||||
gcode.CodeNotSupported,
|
||||
`Save operation is not supported by sqlite driver`,
|
||||
)
|
||||
}
|
||||
}
|
||||
return d.Core.DoFilter(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
// 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 gdb.Result
|
||||
link, err := d.SlaveLink(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = d.DoSelect(
|
||||
ctx,
|
||||
link,
|
||||
`SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, d.QuoteWord(table)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
mKey := ""
|
||||
if m["pk"].Bool() {
|
||||
mKey = "pri"
|
||||
}
|
||||
fields[m["name"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["name"].String(),
|
||||
Type: m["type"].String(),
|
||||
Key: mKey,
|
||||
Default: m["dflt_value"].Val(),
|
||||
Null: !m["notnull"].Bool(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
@ -487,12 +487,13 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
operation = GetInsertOperationByOption(option.InsertOption)
|
||||
)
|
||||
// `ON DUPLICATED...` statement only takes effect on Save operation.
|
||||
if option.InsertOption == InsertOptionSave {
|
||||
onDuplicateStr = c.formatOnDuplicate(keys, option)
|
||||
}
|
||||
var (
|
||||
listLength = len(list)
|
||||
valueHolder = make([]string, 0)
|
||||
listLength = len(list)
|
||||
valueHolders = make([]string, 0)
|
||||
)
|
||||
for i := 0; i < listLength; i++ {
|
||||
values = values[:0]
|
||||
@ -506,9 +507,9 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
}
|
||||
valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
|
||||
valueHolders = append(valueHolders, "("+gstr.Join(values, ",")+")")
|
||||
// Batch package checks: It meets the batch number, or it is the last element.
|
||||
if len(valueHolder) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
if len(valueHolders) == option.BatchCount || (i == listLength-1 && len(valueHolders) > 0) {
|
||||
var (
|
||||
stdSqlResult sql.Result
|
||||
affectedRows int64
|
||||
@ -516,7 +517,7 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
stdSqlResult, err = c.db.DoExec(ctx, link, fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES%s %s",
|
||||
operation, c.QuotePrefixTableName(table), keysStr,
|
||||
gstr.Join(valueHolder, ","),
|
||||
gstr.Join(valueHolders, ","),
|
||||
onDuplicateStr,
|
||||
), params...)
|
||||
if err != nil {
|
||||
@ -530,7 +531,7 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
batchResult.Affected += affectedRows
|
||||
}
|
||||
params = params[:0]
|
||||
valueHolder = valueHolder[:0]
|
||||
valueHolders = valueHolders[:0]
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
|
Loading…
Reference in New Issue
Block a user