From 27cf47bcd3e47424d010caed363ce8f946c3bf10 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 13 Dec 2019 15:25:49 +0800 Subject: [PATCH] improve table prefix and quote feature for gdb --- database/gdb/gdb.go | 1 + database/gdb/gdb_base.go | 22 ++++---- database/gdb/gdb_func.go | 52 ++++++++++++++++++- database/gdb/gdb_model.go | 44 +++++----------- database/gdb/gdb_unit_z_func_test.go | 77 ++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 43 deletions(-) create mode 100644 database/gdb/gdb_unit_z_func_test.go diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index c3dabeffe..1f7734993 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -97,6 +97,7 @@ type DB interface { getDebug() bool getPrefix() string quoteWord(s string) string + quoteString(s string) string doSetSchema(sqlDb *sql.DB, schema string) error filterFields(table string, data map[string]interface{}) map[string]interface{} convertValue(fieldValue []byte, fieldType string) interface{} diff --git a/database/gdb/gdb_base.go b/database/gdb/gdb_base.go index af7167ab5..e3413ca5e 100644 --- a/database/gdb/gdb_base.go +++ b/database/gdb/gdb_base.go @@ -15,8 +15,6 @@ import ( "regexp" "strings" - "github.com/gogf/gf/text/gstr" - "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gcache" "github.com/gogf/gf/os/gtime" @@ -29,8 +27,8 @@ const ( ) var ( - wordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) // Regular expression object for a word. - lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`) // Regular expression object for a string which has operator at its tail. + // lastOperatorReg is the regular expression object for a string which has operator at its tail. + lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`) ) // 打印SQL对象(仅在debug=true时有效) @@ -607,14 +605,18 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { return records, nil } -// 使用关键字操作符转义给定字符串。 -// 如果给定的字符串不为单词,那么不转义,直接返回该字符串。 +// quoteWord checks given string a word, if true quotes it with security chars of the database +// and returns the quoted string; or else return without any change. func (bs *dbBase) quoteWord(s string) string { charLeft, charRight := bs.db.getChars() - if wordReg.MatchString(s) && !gstr.ContainsAny(s, charLeft+charRight) { - return charLeft + s + charRight - } - return s + return doQuoteWord(s, charLeft, charRight) +} + +// quoteString quotes string with quote chars. Strings like: +// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc". +func (bs *dbBase) quoteString(s string) string { + charLeft, charRight := bs.db.getChars() + return doQuoteString(s, charLeft, charRight) } // 动态切换数据库 diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 79ee47cfe..77efaca8b 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/os/gtime" "reflect" + "regexp" "strings" "time" @@ -45,6 +46,55 @@ const ( ORM_TAG_FOR_PRIMARY = "primary" ) +var ( + // quoteWordReg is the regular expression object for a word check. + quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) +) + +// doQuoteWord checks given string a word, if true quotes it with and +// and returns the quoted string; or else return without any change. +func doQuoteWord(s, charLeft, charRight string) string { + if quoteWordReg.MatchString(s) && !gstr.ContainsAny(s, charLeft+charRight) { + return charLeft + s + charRight + } + return s +} + +// doQuoteString quotes string with quote chars. It handles strings like: +// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc". +func doQuoteString(s, charLeft, charRight string) string { + array1 := gstr.SplitAndTrim(s, ",") + for k1, v1 := range array1 { + array2 := gstr.SplitAndTrim(v1, " ") + array3 := gstr.SplitAndTrim(array2[0], ".") + if len(array3) == 1 { + array3[0] = doQuoteWord(array3[0], charLeft, charRight) + } else if len(array3) == 2 { + array3[1] = doQuoteWord(array3[1], charLeft, charRight) + } + array2[0] = gstr.Join(array3, ".") + array1[k1] = gstr.Join(array2, " ") + } + return gstr.Join(array1, ",") +} + +// addTablePrefix adds prefix string to the table. It handles strings like: +// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut". +// +// Note that, this should be used before any quoting function calls. +func addTablePrefix(table, prefix string) string { + if prefix == "" { + return table + } + array1 := gstr.SplitAndTrim(table, ",") + for k1, v1 := range array1 { + array2 := gstr.SplitAndTrim(v1, " ") + array2[0] = prefix + array2[0] + array1[k1] = gstr.Join(array2, " ") + } + return gstr.Join(array1, ",") +} + // 获得struct对象对应的where查询条件 func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) { array := ([]string)(nil) @@ -135,7 +185,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) ( if gstr.Pos(newWhere, "?") == -1 { if lastOperatorReg.MatchString(newWhere) { newWhere += "?" - } else if wordReg.MatchString(newWhere) { + } else if quoteWordReg.MatchString(newWhere) { newWhere += "=?" } } diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 7e73053e2..0c5101140 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -11,7 +11,6 @@ import ( "errors" "fmt" "reflect" - "strings" "time" "github.com/gogf/gf/container/gset" @@ -69,11 +68,8 @@ const ( // The parameter can be more than one table names, like : // "user", "user u", "user, user_detail", "user u, user_detail ud" func (bs *dbBase) Table(table string) *Model { - if !gstr.Contains(table, ",") { - array := gstr.SplitAndTrim(table, " ") - array[0] = bs.db.quoteWord(bs.db.getPrefix() + array[0]) - table = gstr.Join(array, " ") - } + table = addTablePrefix(table, bs.db.getPrefix()) + table = bs.db.quoteString(table) return &Model{ db: bs.db, tablesInit: table, @@ -95,11 +91,8 @@ func (bs *dbBase) From(tables string) *Model { // Table acts like dbBase.Table except it operates on transaction. // See dbBase.Table. func (tx *TX) Table(table string) *Model { - if !gstr.Contains(table, ",") { - array := gstr.SplitAndTrim(table, " ") - array[0] = tx.db.quoteWord(tx.db.getPrefix() + array[0]) - table = gstr.Join(array, " ") - } + table = addTablePrefix(table, tx.db.getPrefix()) + table = tx.db.quoteString(table) return &Model{ db: tx.db, tx: tx, @@ -187,9 +180,8 @@ func (m *Model) getModel() *Model { // LeftJoin does "LEFT JOIN ... ON ..." statement on the model. func (m *Model) LeftJoin(table string, on string) *Model { model := m.getModel() - array := gstr.SplitAndTrim(table, " ") - array[0] = m.db.quoteWord(m.db.getPrefix() + array[0]) - table = gstr.Join(array, " ") + table = addTablePrefix(table, m.db.getPrefix()) + table = m.db.quoteString(table) model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", table, on) return model } @@ -197,9 +189,8 @@ func (m *Model) LeftJoin(table string, on string) *Model { // RightJoin does "RIGHT JOIN ... ON ..." statement on the model. func (m *Model) RightJoin(table string, on string) *Model { model := m.getModel() - array := gstr.SplitAndTrim(table, " ") - array[0] = m.db.quoteWord(m.db.getPrefix() + array[0]) - table = gstr.Join(array, " ") + table = addTablePrefix(table, m.db.getPrefix()) + table = m.db.quoteString(table) model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", table, on) return model } @@ -207,9 +198,8 @@ func (m *Model) RightJoin(table string, on string) *Model { // InnerJoin does "INNER JOIN ... ON ..." statement on the model. func (m *Model) InnerJoin(table string, on string) *Model { model := m.getModel() - array := gstr.SplitAndTrim(table, " ") - array[0] = m.db.quoteWord(m.db.getPrefix() + array[0]) - table = gstr.Join(array, " ") + table = addTablePrefix(table, m.db.getPrefix()) + table = m.db.quoteString(table) model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", table, on) return model } @@ -323,24 +313,14 @@ func (m *Model) Or(where interface{}, args ...interface{}) *Model { // GroupBy sets the "GROUP BY" statement for the model. func (m *Model) GroupBy(groupBy string) *Model { model := m.getModel() - if !gstr.Contains(groupBy, ",") { - array := strings.Split(groupBy, " ") - array[0] = m.db.quoteWord(array[0]) - groupBy = strings.Join(array, " ") - } - model.groupBy = groupBy + model.groupBy = m.db.quoteString(groupBy) return model } // OrderBy sets the "ORDER BY" statement for the model. func (m *Model) OrderBy(orderBy string) *Model { model := m.getModel() - if !gstr.Contains(orderBy, ",") { - array := strings.Split(orderBy, " ") - array[0] = m.db.quoteWord(array[0]) - orderBy = strings.Join(array, " ") - } - model.orderBy = orderBy + model.orderBy = m.db.quoteString(orderBy) return model } diff --git a/database/gdb/gdb_unit_z_func_test.go b/database/gdb/gdb_unit_z_func_test.go new file mode 100644 index 000000000..c4af09232 --- /dev/null +++ b/database/gdb/gdb_unit_z_func_test.go @@ -0,0 +1,77 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "github.com/gogf/gf/test/gtest" + "testing" +) + +func Test_Func_doQuoteWord(t *testing.T) { + gtest.Case(t, func() { + array := map[string]string{ + "user": "`user`", + "user u": "user u", + "user_detail": "`user_detail`", + "user,user_detail": "user,user_detail", + "user u, user_detail ut": "user u, user_detail ut", + "u.id asc": "u.id asc", + "u.id asc, ut.uid desc": "u.id asc, ut.uid desc", + } + for k, v := range array { + gtest.Assert(doQuoteWord(k, "`", "`"), v) + } + }) +} + +func Test_Func_doQuoteString(t *testing.T) { + gtest.Case(t, func() { + // "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc". + array := map[string]string{ + "user": "`user`", + "user u": "`user` u", + "user,user_detail": "`user`,`user_detail`", + "user u, user_detail ut": "`user` u,`user_detail` ut", + "u.id asc": "u.`id` asc", + "u.id asc, ut.uid desc": "u.`id` asc,ut.`uid` desc", + } + for k, v := range array { + gtest.Assert(doQuoteString(k, "`", "`"), v) + } + }) +} + +func Test_Func_addTablePrefix(t *testing.T) { + gtest.Case(t, func() { + prefix := "" + array := map[string]string{ + "user": "user", + "user u": "user u", + "user as u": "user as u", + "user,user_detail": "user,user_detail", + "user u, user_detail ut": "user u, user_detail ut", + "user as u, user_detail as ut": "user as u, user_detail as ut", + } + for k, v := range array { + gtest.Assert(addTablePrefix(k, prefix), v) + } + }) + gtest.Case(t, func() { + prefix := "gf_" + array := map[string]string{ + "user": "gf_user", + "user u": "gf_user u", + "user as u": "gf_user as u", + "user,user_detail": "gf_user,gf_user_detail", + "user u, user_detail ut": "gf_user u,gf_user_detail ut", + "user as u, user_detail as ut": "gf_user as u,gf_user_detail as ut", + } + for k, v := range array { + gtest.Assert(addTablePrefix(k, prefix), v) + } + }) +}