improve bindArgsToQuery for surpport for pgsql/mssql/oracle for package gdb

This commit is contained in:
John
2020-02-29 19:55:53 +08:00
parent 6e1f8c3cfc
commit b0ef63fc9d
8 changed files with 168 additions and 108 deletions

View File

@ -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
}

View File

@ -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, " ") {

View File

@ -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
}

View File

@ -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, " ") {

View File

@ -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, " ") {

View File

@ -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 {

View File

@ -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
}

View File

@ -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{