mirror of
https://gitee.com/johng/gf
synced 2026-07-07 06:15:15 +08:00
improve bindArgsToQuery for surpport for pgsql/mssql/oracle for package gdb
This commit is contained in:
@ -440,34 +440,35 @@ func getInsertOperationByOption(option int) string {
|
||||
// sql string, just for debugging.
|
||||
func bindArgsToQuery(query string, args []interface{}) string {
|
||||
index := -1
|
||||
newQuery, _ := gregex.ReplaceStringFunc(`\?`, query, func(s string) string {
|
||||
index++
|
||||
if len(args) > index {
|
||||
if args[index] == nil {
|
||||
return "null"
|
||||
}
|
||||
rv := reflect.ValueOf(args[index])
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
if rv.IsNil() || !rv.IsValid() {
|
||||
newQuery, _ := gregex.ReplaceStringFunc(
|
||||
`(\?|:\d+|\$\d+|@p\d+)`, query, func(s string) string {
|
||||
index++
|
||||
if len(args) > index {
|
||||
if args[index] == nil {
|
||||
return "null"
|
||||
}
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.String, reflect.Map, reflect.Slice, reflect.Array:
|
||||
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
|
||||
case reflect.Struct:
|
||||
if t, ok := args[index].(time.Time); ok {
|
||||
return `'` + gtime.NewFromTime(t).String() + `'`
|
||||
rv := reflect.ValueOf(args[index])
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
if rv.IsNil() || !rv.IsValid() {
|
||||
return "null"
|
||||
}
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
|
||||
switch kind {
|
||||
case reflect.String, reflect.Map, reflect.Slice, reflect.Array:
|
||||
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
|
||||
case reflect.Struct:
|
||||
if t, ok := args[index].(time.Time); ok {
|
||||
return `'` + gtime.NewFromTime(t).String() + `'`
|
||||
}
|
||||
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
|
||||
}
|
||||
return gconv.String(args[index])
|
||||
}
|
||||
return gconv.String(args[index])
|
||||
}
|
||||
return s
|
||||
})
|
||||
return s
|
||||
})
|
||||
return newQuery
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ type dbMssql struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// Open creates and returns a underlying sql.DB object for mssql.
|
||||
func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
source := ""
|
||||
if config.LinkInfo != "" {
|
||||
@ -44,12 +45,15 @@ func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getChars returns the security char for this type of database.
|
||||
func (db *dbMssql) getChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// handleSqlBeforeExec deals with the sql string before commits it to underlying sql driver.
|
||||
func (db *dbMssql) handleSqlBeforeExec(query string) string {
|
||||
index := 0
|
||||
var index int
|
||||
// Convert place holder char '?' to string "@px".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
@ -95,20 +99,26 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
if haveOrder {
|
||||
// 取order by 前面的字符串
|
||||
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 {
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "ORDER BY") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
|
||||
// 取order by表达式的值
|
||||
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 {
|
||||
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 {
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
@ -121,7 +131,8 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
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
|
||||
@ -151,11 +162,13 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
return sql
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// TODO
|
||||
func (db *dbMssql) Tables(schema ...string) (tables []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (db *dbMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
|
||||
_ "github.com/gf-third/mysql"
|
||||
)
|
||||
@ -18,7 +19,7 @@ type dbMysql struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// Open creates and returns a underlying database connection with given configuration.
|
||||
// Open creates and returns a underlying sql.DB object for mysql.
|
||||
func (db *dbMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
@ -37,7 +38,7 @@ func (db *dbMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getChars returns the quote chars for field.
|
||||
// getChars returns the security char for this type of database.
|
||||
func (db *dbMysql) getChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
@ -46,3 +47,74 @@ func (db *dbMysql) getChars() (charLeft string, charRight string) {
|
||||
func (db *dbMysql) handleSqlBeforeExec(sql string) string {
|
||||
return sql
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
func (bs *dbBase) Tables(schema ...string) (tables []string, err error) {
|
||||
var result Result
|
||||
link, err := bs.db.getSlave(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = bs.db.doGetAll(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.
|
||||
//
|
||||
// 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 (bs *dbBase) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := bs.schema.Val()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := bs.cache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
link, err = bs.db.getSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err = bs.doGetAll(
|
||||
link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, bs.db.quoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &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
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ const (
|
||||
tableAlias2 = "GFORM2"
|
||||
)
|
||||
|
||||
// Open creates and returns a underlying sql.DB object for oracle.
|
||||
func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
@ -48,12 +49,15 @@ func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getChars returns the security char for this type of database.
|
||||
func (db *dbOracle) getChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// handleSqlBeforeExec deals with the sql string before commits it to underlying sql driver.
|
||||
func (db *dbOracle) handleSqlBeforeExec(query string) string {
|
||||
index := 0
|
||||
var index int
|
||||
// Convert place holder char '?' to string ":x".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(":%d", index)
|
||||
@ -109,17 +113,22 @@ func (db *dbOracle) parseSql(sql string) string {
|
||||
}
|
||||
}
|
||||
|
||||
//也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
|
||||
// 也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
|
||||
sql = fmt.Sprintf(
|
||||
"SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d",
|
||||
queryExpr[1], queryExpr[2], limit, first,
|
||||
)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// TODO
|
||||
func (db *dbOracle) Tables(schema ...string) (tables []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (db *dbOracle) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
|
||||
@ -25,6 +25,7 @@ type dbPgsql struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// Open creates and returns a underlying sql.DB object for pgsql.
|
||||
func (db *dbPgsql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
@ -43,12 +44,15 @@ func (db *dbPgsql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getChars returns the security char for this type of database.
|
||||
func (db *dbPgsql) getChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// handleSqlBeforeExec deals with the sql string before commits it to underlying sql driver.
|
||||
func (db *dbPgsql) handleSqlBeforeExec(sql string) string {
|
||||
index := 0
|
||||
var index int
|
||||
// Convert place holder char '?' to string "$x".
|
||||
sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("$%d", index)
|
||||
@ -57,11 +61,13 @@ func (db *dbPgsql) handleSqlBeforeExec(sql string) string {
|
||||
return sql
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// TODO
|
||||
func (db *dbPgsql) Tables(schema ...string) (tables []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (db *dbPgsql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
|
||||
@ -20,6 +20,7 @@ type dbSqlite struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// Open creates and returns a underlying sql.DB object for sqlite.
|
||||
func (db *dbSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
@ -35,15 +36,18 @@ func (db *dbSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getChars returns the security char for this type of database.
|
||||
func (db *dbSqlite) getChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// TODO
|
||||
func (db *dbSqlite) Tables(schema ...string) (tables []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
// TODO
|
||||
func (db *dbSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
@ -53,6 +57,7 @@ func (db *dbSqlite) TableFields(table string, schema ...string) (fields map[stri
|
||||
return
|
||||
}
|
||||
|
||||
// handleSqlBeforeExec deals with the sql string before commits it to underlying sql driver.
|
||||
// @todo 需要增加对Save方法的支持,可使用正则来实现替换,
|
||||
// @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
|
||||
func (db *dbSqlite) handleSqlBeforeExec(sql string) string {
|
||||
|
||||
@ -7,8 +7,6 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
@ -120,74 +118,3 @@ func (bs *dbBase) filterFields(schema, table string, data map[string]interface{}
|
||||
}
|
||||
return newDataMap
|
||||
}
|
||||
|
||||
// Tables returns the table name array of current schema.
|
||||
func (bs *dbBase) Tables(schema ...string) (tables []string, err error) {
|
||||
var result Result
|
||||
link, err := bs.db.getSlave(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = bs.db.doGetAll(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 of given table.
|
||||
//
|
||||
// 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 (bs *dbBase) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := bs.schema.Val()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := bs.cache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
link, err = bs.db.getSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err = bs.doGetAll(
|
||||
link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, bs.db.quoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &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
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -11,6 +11,33 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Func_bindArgsToQuery(t *testing.T) {
|
||||
// mysql
|
||||
gtest.Case(t, func() {
|
||||
var s string
|
||||
s = bindArgsToQuery("select * from table where id>=? and sex=?", []interface{}{100, 1})
|
||||
gtest.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// mssql
|
||||
gtest.Case(t, func() {
|
||||
var s string
|
||||
s = bindArgsToQuery("select * from table where id>=@p1 and sex=@p2", []interface{}{100, 1})
|
||||
gtest.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// pgsql
|
||||
gtest.Case(t, func() {
|
||||
var s string
|
||||
s = bindArgsToQuery("select * from table where id>=$1 and sex=$2", []interface{}{100, 1})
|
||||
gtest.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// oracle
|
||||
gtest.Case(t, func() {
|
||||
var s string
|
||||
s = bindArgsToQuery("select * from table where id>=:1 and sex=:2", []interface{}{100, 1})
|
||||
gtest.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Func_doQuoteWord(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
array := map[string]string{
|
||||
|
||||
Reference in New Issue
Block a user