add soft deleting feature, improve chars handling for package gdb

This commit is contained in:
John
2020-04-11 09:09:25 +08:00
parent ef286b0c15
commit 385a503d31
15 changed files with 633 additions and 232 deletions

View File

@ -1,47 +1,23 @@
package main
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/encoding/gjson"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
)
type A struct {
F string
G string
}
type B struct {
A
H string
}
type C struct {
A A
F string
}
type D struct {
I A
F string
}
func SystemJsonEncode(a interface{}) string {
js, err := json.Marshal(a)
if err != nil {
return "{}"
} else {
return fmt.Sprintf("%s", js)
}
}
func main() {
fmt.Println("encoding/json", SystemJsonEncode(B{}))
fmt.Println("gjson", gjson.New(B{}).MustToJsonString())
fmt.Println()
fmt.Println("encoding/json", SystemJsonEncode(C{}))
fmt.Println("gjson", gjson.New(C{}).MustToJsonString())
fmt.Println()
fmt.Println("encoding/json", SystemJsonEncode(D{}))
fmt.Println("gjson", gjson.New(D{}).MustToJsonString())
s := `user u LEFT JOIN user_detail ud ON(ud.id=u.id) LEFT JOIN user_stats us ON(us.id=u.id)`
split := " JOIN "
as := "my-as"
if gstr.Contains(s, split) {
// For join table.
array := gstr.Split(s, split)
array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1])
s = gstr.Join(array, split)
} else {
// For base table.
s = gstr.TrimRight(s) + " AS " + as
}
fmt.Println(s)
}

View File

@ -78,8 +78,8 @@ type DB interface {
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
// Model creation.
Table(tables string) *Model
Model(tables string) *Model
Table(table ...string) *Model
Model(table ...string) *Model
Schema(schema string) *Schema
// Configuration methods.

View File

@ -11,6 +11,7 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/utils"
"reflect"
"regexp"
"strings"
@ -417,7 +418,7 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
for k, _ := range dataMap {
// If it's SAVE operation,
// do not automatically update the creating time.
if k == gSOFT_FIELD_NAME_CREATE {
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
continue
}
if len(updateStr) > 0 {
@ -538,7 +539,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
for _, k := range keys {
// If it's SAVE operation,
// do not automatically update the creating time.
if k == gSOFT_FIELD_NAME_CREATE {
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
continue
}
if len(updateStr) > 0 {

View File

@ -147,8 +147,13 @@ func doQuoteWord(s, charLeft, charRight string) string {
}
// doQuoteString quotes string with quote chars. It handles strings like:
// "user", "user u", "user,user_detail", "user u, user_detail ut",
// "user.user u, user.user_detail ut", "u.id asc".
// "user",
// "user u",
// "user,user_detail",
// "user u, user_detail ut",
// "user.user u, user.user_detail ut",
// "u.id, u.name, u.age",
// "u.id asc".
func doQuoteString(s, charLeft, charRight string) string {
array1 := gstr.SplitAndTrim(s, ",")
for k1, v1 := range array1 {

View File

@ -7,6 +7,8 @@
package gdb
import (
"fmt"
"github.com/gogf/gf/text/gregex"
"time"
"github.com/gogf/gf/text/gstr"
@ -59,14 +61,28 @@ const (
)
// Table creates and returns a new ORM model from given schema.
// The parameter <tables> can be more than one table names, like :
// "user", "user u", "user, user_detail", "user u, user_detail ud"
func (c *Core) Table(table string) *Model {
table = c.DB.QuotePrefixTableName(table)
// The parameter <table> can be more than one table names, and also alias name, like:
// 1. Table names:
// Table("user")
// Table("user u")
// Table("user, user_detail")
// Table("user u, user_detail ud")
// 2. Table name with alias: Table("user", "u")
func (c *Core) Table(table ...string) *Model {
tables := ""
if len(table) > 1 {
tables = fmt.Sprintf(
`%s AS %s`, c.DB.QuotePrefixTableName(table[0]), c.DB.QuoteWord(table[1]),
)
} else if len(table) == 1 {
tables = c.DB.QuotePrefixTableName(table[0])
} else {
panic("table cannot be empty")
}
return &Model{
db: c.DB,
tablesInit: table,
tables: table,
tablesInit: tables,
tables: tables,
fields: "*",
start: -1,
offset: -1,
@ -76,37 +92,39 @@ func (c *Core) Table(table string) *Model {
// Model is alias of Core.Table.
// See Core.Table.
func (c *Core) Model(table string) *Model {
return c.DB.Table(table)
func (c *Core) Model(table ...string) *Model {
return c.DB.Table(table...)
}
// Table acts like Core.Table except it operates on transaction.
// See Core.Table.
func (tx *TX) Table(table string) *Model {
table = tx.db.QuotePrefixTableName(table)
return &Model{
db: tx.db,
tx: tx,
tablesInit: table,
tables: table,
fields: "*",
start: -1,
offset: -1,
option: OPTION_ALLOWEMPTY,
}
func (tx *TX) Table(table ...string) *Model {
model := tx.db.Table(table...)
model.db = tx.db
model.tx = tx
return model
}
// Model is alias of tx.Table.
// See tx.Table.
func (tx *TX) Model(table string) *Model {
return tx.Table(table)
func (tx *TX) Model(table ...string) *Model {
return tx.Table(table...)
}
// As sets an alias name for current table.
func (m *Model) As(as string) *Model {
if m.tables != "" {
model := m.getModel()
model.tables = gstr.TrimRight(model.tables) + " AS " + as
split := " JOIN "
if gstr.Contains(model.tables, split) {
// For join table.
array := gstr.Split(model.tables, split)
array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1])
model.tables = gstr.Join(array, split)
} else {
// For base table.
model.tables = gstr.TrimRight(model.tables) + " AS " + as
}
return model
}
return m

View File

@ -44,7 +44,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
return m.db.DoUpdate(
m.getLink(true),
m.tables,
fmt.Sprintf(`%s='%s'`, m.db.QuoteWord(fieldNameDelete), gtime.Now().String()),
fmt.Sprintf(`%s='%s'`, m.db.QuoteString(fieldNameDelete), gtime.Now().String()),
conditionWhere+conditionExtra,
conditionArgs...,
)

View File

@ -9,22 +9,70 @@ package gdb
import "fmt"
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
func (m *Model) LeftJoin(table string, on string) *Model {
// The parameter <table> can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
func (m *Model) LeftJoin(table ...string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
if len(table) > 2 {
model.tables += fmt.Sprintf(
" LEFT JOIN %s AS %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
)
} else if len(table) == 2 {
model.tables += fmt.Sprintf(
" LEFT JOIN %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), table[1],
)
} else {
panic("invalid join table parameter")
}
return model
}
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
func (m *Model) RightJoin(table string, on string) *Model {
// The parameter <table> can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
func (m *Model) RightJoin(table ...string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
if len(table) > 2 {
model.tables += fmt.Sprintf(
" RIGHT JOIN %s AS %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
)
} else if len(table) == 2 {
model.tables += fmt.Sprintf(
" RIGHT JOIN %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), table[1],
)
} else {
panic("invalid join table parameter")
}
return model
}
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
func (m *Model) InnerJoin(table string, on string) *Model {
// The parameter <table> can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
func (m *Model) InnerJoin(table ...string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
if len(table) > 2 {
model.tables += fmt.Sprintf(
" INNER JOIN %s AS %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
)
} else if len(table) == 2 {
model.tables += fmt.Sprintf(
" INNER JOIN %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), table[1],
)
} else {
panic("invalid join table parameter")
}
return model
}

View File

@ -31,19 +31,24 @@ func (m *Model) All(where ...interface{}) (Result, error) {
return m.Where(where[0], where[1:]...).All()
}
var (
fieldNameDelete = m.getSoftFieldNameDelete()
softDeletingCondition = m.getConditionForSoftDeleting()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
)
if !m.unscoped && fieldNameDelete != "" {
if !m.unscoped && softDeletingCondition != "" {
if conditionWhere == "" {
conditionWhere = " WHERE"
conditionWhere = " WHERE "
} else {
conditionWhere += " AND"
conditionWhere += " AND "
}
conditionWhere += fmt.Sprintf(` %s IS NULL`, m.db.QuoteWord(fieldNameDelete))
conditionWhere += softDeletingCondition
}
return m.getAll(
fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, conditionWhere+conditionExtra),
fmt.Sprintf(
"SELECT %s FROM %s%s",
m.db.QuoteString(m.fields),
m.tables,
conditionWhere+conditionExtra,
),
conditionArgs...,
)
}
@ -244,19 +249,19 @@ func (m *Model) Count(where ...interface{}) (int, error) {
}
countFields := "COUNT(1)"
if m.fields != "" && m.fields != "*" {
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
countFields = fmt.Sprintf(`COUNT(%s)`, m.db.QuoteString(m.fields))
}
var (
fieldNameDelete = m.getSoftFieldNameDelete()
softDeletingCondition = m.getConditionForSoftDeleting()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
)
if !m.unscoped && fieldNameDelete != "" {
if !m.unscoped && softDeletingCondition != "" {
if conditionWhere == "" {
conditionWhere = " WHERE"
conditionWhere = " WHERE "
} else {
conditionWhere += " AND"
conditionWhere += " AND "
}
conditionWhere += fmt.Sprintf(` %s IS NULL`, m.db.QuoteWord(fieldNameDelete))
conditionWhere += softDeletingCondition
}
s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, conditionWhere+conditionExtra)

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright 2020 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,
@ -7,6 +7,10 @@
package gdb
import (
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
)
@ -20,38 +24,121 @@ const (
// getSoftFieldNameCreate checks and returns the field name for record creating time.
// If there's no field name for storing creating time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameCreate() (field string) {
fieldsMap, _ := m.db.TableFields(m.tables)
if len(fieldsMap) > 0 {
field, _ = gutil.MapPossibleItemByKey(
gconv.Map(fieldsMap), gSOFT_FIELD_NAME_CREATE,
)
func (m *Model) getSoftFieldNameCreate(table ...string) string {
name := ""
if len(table) > 0 {
name = table[0]
} else {
name = m.getPrimaryTableName()
}
return
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_CREATE)
}
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
// If there's no field name for storing updating time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameUpdate() (field string) {
fieldsMap, _ := m.db.TableFields(m.tables)
if len(fieldsMap) > 0 {
field, _ = gutil.MapPossibleItemByKey(
gconv.Map(fieldsMap), gSOFT_FIELD_NAME_UPDATE,
)
func (m *Model) getSoftFieldNameUpdate(table ...string) (field string) {
name := ""
if len(table) > 0 {
name = table[0]
} else {
name = m.getPrimaryTableName()
}
return
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_UPDATE)
}
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
// If there's no field name for storing deleting time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameDelete() (field string) {
fieldsMap, _ := m.db.TableFields(m.tables)
func (m *Model) getSoftFieldNameDelete(table ...string) (field string) {
name := ""
if len(table) > 0 {
name = table[0]
} else {
name = m.getPrimaryTableName()
}
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_DELETE)
}
// getSoftFieldName retrieves and returns the field name of the table for possible key.
func (m *Model) getSoftFieldName(table string, key string) (field string) {
fieldsMap, _ := m.db.TableFields(table)
if len(fieldsMap) > 0 {
field, _ = gutil.MapPossibleItemByKey(
gconv.Map(fieldsMap), gSOFT_FIELD_NAME_DELETE,
gconv.Map(fieldsMap), key,
)
}
return
}
// getConditionForSoftDeleting retrieves and returns the condition string for soft deleting.
// It supports multiple tables string like:
// "user u, user_detail ud"
// "user u LEFT JOIN user_detail ud ON(ud.uid=u.uid)"
// "user LEFT JOIN user_detail ON(user_detail.uid=user.uid)"
// "user u LEFT JOIN user_detail ud ON(ud.uid=u.uid) LEFT JOIN user_stats us ON(us.uid=u.uid)"
func (m *Model) getConditionForSoftDeleting() string {
conditionArray := garray.NewStrArray()
if gstr.Contains(m.tables, " JOIN ") {
// Base table.
match, _ := gregex.MatchString(`(.+?) [A-Z]+ JOIN`, m.tables)
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
// Multiple joined tables.
matches, _ := gregex.MatchAllString(`JOIN (.+?) ON`, m.tables)
for _, match := range matches {
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
}
}
if conditionArray.Len() == 0 && gstr.Contains(m.tables, ",") {
// Multiple base tables.
for _, s := range gstr.SplitAndTrim(m.tables, ",") {
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(s))
}
}
conditionArray.FilterEmpty()
if conditionArray.Len() > 0 {
return conditionArray.Join(" AND ")
}
// Only one table.
if fieldName := m.getSoftFieldNameDelete(); fieldName != "" {
return fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(fieldName))
}
return ""
}
// getConditionOfTableStringForSoftDeleting does something as its name describes.
func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
var (
field = ""
table = ""
array1 = gstr.SplitAndTrim(s, " ")
array2 = gstr.SplitAndTrim(array1[0], ".")
)
if len(array2) >= 2 {
table = array2[1]
} else {
table = array2[0]
}
field = m.getSoftFieldNameDelete(table)
if field == "" {
return ""
}
if len(array1) >= 3 {
return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(array1[2]), m.db.QuoteWord(field))
}
if len(array1) >= 2 {
return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(array1[1]), m.db.QuoteWord(field))
}
return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(table), m.db.QuoteWord(field))
}
// getPrimaryTableName parses and returns the primary table name.
func (m *Model) getPrimaryTableName() string {
array1 := gstr.SplitAndTrim(m.tables, ",")
array2 := gstr.SplitAndTrim(array1[0], " ")
array3 := gstr.SplitAndTrim(array2[0], ".")
if len(array3) >= 2 {
return array3[1]
}
return array3[0]
}

View File

@ -74,8 +74,13 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
if len(m.fields) > 0 && m.fields != "*" {
// Keep specified fields.
set := gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ","))
var (
set = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ","))
charL, charR = m.db.GetChars()
chars = charL + charR
)
for k := range data {
k = gstr.Trim(k, chars)
if !set.Contains(k) {
delete(data, k)
}

View File

@ -1,117 +0,0 @@
// 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_FormatSqlWithArgs(t *testing.T) {
// mysql
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=? and sex=?", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
// mssql
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=@p1 and sex=@p2", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
// pgsql
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=$1 and sex=$2", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
// oracle
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=:1 and sex=:2", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
}
func Test_Func_doQuoteWord(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
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 {
t.Assert(doQuoteWord(k, "`", "`"), v)
}
})
}
func Test_Func_doQuoteString(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// "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",
"user.user u, user.user_detail ut": "`user`.`user` u,`user`.`user_detail` ut",
// mssql global schema access with double dots.
"user..user u, user.user_detail ut": "`user`..`user` u,`user`.`user_detail` ut",
}
for k, v := range array {
t.Assert(doQuoteString(k, "`", "`"), v)
}
})
}
func Test_Func_addTablePrefix(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
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`.user_detail": "`user`.`user_detail`",
"`user`.`user_detail`": "`user`.`user_detail`",
"user as u, user_detail as ut": "`user` as u,`user_detail` as ut",
"UserCenter.user as u, UserCenter.user_detail as ut": "`UserCenter`.`user` as u,`UserCenter`.`user_detail` as ut",
// mssql global schema access with double dots.
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`user` as u,`user_detail` as ut",
}
for k, v := range array {
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
}
})
gtest.C(t, func(t *gtest.T) {
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`.user_detail": "`user`.`gf_user_detail`",
"`user`.`user_detail`": "`user`.`gf_user_detail`",
"user as u, user_detail as ut": "`gf_user` as u,`gf_user_detail` as ut",
"UserCenter.user as u, UserCenter.user_detail as ut": "`UserCenter`.`gf_user` as u,`UserCenter`.`gf_user_detail` as ut",
// mssql global schema access with double dots.
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`gf_user` as u,`gf_user_detail` as ut",
}
for k, v := range array {
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
}
})
}

View File

@ -0,0 +1,289 @@
// 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 (
"fmt"
"github.com/gogf/gf/os/gcmd"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/test/gtest"
"testing"
)
const (
SCHEMA = "test_internal"
)
var (
db DB
configNode ConfigNode
)
func init() {
parser, err := gcmd.Parse(map[string]bool{
"name": true,
"type": true,
}, false)
gtest.Assert(err, nil)
configNode = ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pass: "12345678",
Name: parser.GetOpt("name", ""),
Type: parser.GetOpt("type", "mysql"),
Role: "master",
Charset: "utf8",
Weight: 1,
MaxIdleConnCount: 10,
MaxOpenConnCount: 10,
MaxConnLifetime: 600,
}
AddConfigNode(DEFAULT_GROUP_NAME, configNode)
// Default db.
if r, err := New(); err != nil {
gtest.Error(err)
} else {
db = r
}
schemaTemplate := "CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET UTF8"
if _, err := db.Exec(fmt.Sprintf(schemaTemplate, SCHEMA)); err != nil {
gtest.Error(err)
}
db.SetSchema(SCHEMA)
}
func dropTable(table string) {
if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
gtest.Error(err)
}
}
func Test_Func_FormatSqlWithArgs(t *testing.T) {
// mysql
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=? and sex=?", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
// mssql
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=@p1 and sex=@p2", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
// pgsql
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=$1 and sex=$2", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
// oracle
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=:1 and sex=:2", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
}
func Test_Func_doQuoteWord(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
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 {
t.Assert(doQuoteWord(k, "`", "`"), v)
}
})
}
func Test_Func_doQuoteString(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
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, u.name, u.age": "`u`.`id`,`u`.`name`,`u`.`age`",
"u.id asc": "`u`.`id` asc",
"u.id asc, ut.uid desc": "`u`.`id` asc,`ut`.`uid` desc",
"user.user u, user.user_detail ut": "`user`.`user` u,`user`.`user_detail` ut",
// mssql global schema access with double dots.
"user..user u, user.user_detail ut": "`user`..`user` u,`user`.`user_detail` ut",
}
for k, v := range array {
t.Assert(doQuoteString(k, "`", "`"), v)
}
})
}
func Test_Func_addTablePrefix(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
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`.user_detail": "`user`.`user_detail`",
"`user`.`user_detail`": "`user`.`user_detail`",
"user as u, user_detail as ut": "`user` as u,`user_detail` as ut",
"UserCenter.user as u, UserCenter.user_detail as ut": "`UserCenter`.`user` as u,`UserCenter`.`user_detail` as ut",
// mssql global schema access with double dots.
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`user` as u,`user_detail` as ut",
}
for k, v := range array {
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
}
})
gtest.C(t, func(t *gtest.T) {
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`.user_detail": "`user`.`gf_user_detail`",
"`user`.`user_detail`": "`user`.`gf_user_detail`",
"user as u, user_detail as ut": "`gf_user` as u,`gf_user_detail` as ut",
"UserCenter.user as u, UserCenter.user_detail as ut": "`UserCenter`.`gf_user` as u,`UserCenter`.`gf_user_detail` as ut",
// mssql global schema access with double dots.
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`gf_user` as u,`gf_user_detail` as ut",
}
for k, v := range array {
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
}
})
}
func Test_Model_getSoftFieldName(t *testing.T) {
table1 := "soft_deleting_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table1)); err != nil {
gtest.Error(err)
}
defer dropTable(table1)
table2 := "soft_deleting_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
createat datetime DEFAULT NULL,
updateat datetime DEFAULT NULL,
deleteat datetime DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2)); err != nil {
gtest.Error(err)
}
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
model := db.Table(table1)
gtest.Assert(model.getSoftFieldNameCreate(table2), "createat")
gtest.Assert(model.getSoftFieldNameUpdate(table2), "updateat")
gtest.Assert(model.getSoftFieldNameDelete(table2), "deleteat")
})
}
func Test_Model_getConditionForSoftDeleting(t *testing.T) {
table1 := "soft_deleting_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id1 int(11) NOT NULL,
name1 varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
PRIMARY KEY (id1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table1)); err != nil {
gtest.Error(err)
}
defer dropTable(table1)
table2 := "soft_deleting_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id2 int(11) NOT NULL,
name2 varchar(45) DEFAULT NULL,
createat datetime DEFAULT NULL,
updateat datetime DEFAULT NULL,
deleteat datetime DEFAULT NULL,
PRIMARY KEY (id2)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2)); err != nil {
gtest.Error(err)
}
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
model := db.Table(table1)
t.Assert(model.getConditionForSoftDeleting(), "`delete_at` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s as t`, table1))
t.Assert(model.getConditionForSoftDeleting(), "`delete_at` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s, %s`, table1, table2))
t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf(
"`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL",
table1, table2,
))
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s t1, %s as t2`, table1, table2))
t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s as t1, %s as t2`, table1, table2))
t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s as t1`, table1)).LeftJoin(table2+" t2", "t2.id2=t1.id1")
t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s`, table1)).LeftJoin(table2, "t2.id2=t1.id1")
t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf(
"`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL",
table1, table2,
))
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s`, table1)).LeftJoin(table2, "t2.id2=t1.id1").RightJoin(table2, "t2.id2=t1.id1")
t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf(
"`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL AND `%s`.`deleteat` IS NULL",
table1, table2, table2,
))
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(table1+" as t1").LeftJoin(table2+" as t2", "t2.id2=t1.id1").RightJoin(table2+" as t3 ", "t2.id2=t1.id1")
t.Assert(
model.getConditionForSoftDeleting(),
"`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL AND `t3`.`deleteat` IS NULL",
)
})
}

View File

@ -147,3 +147,74 @@ CREATE TABLE %s (
t.Assert(i, 0)
})
}
func Test_SoftDelete_Join(t *testing.T) {
table1 := "time_test_table1"
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table1)); err != nil {
gtest.Error(err)
}
defer dropTable(table1)
table2 := "time_test_table2"
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
createat datetime DEFAULT NULL,
updateat datetime DEFAULT NULL,
deleteat datetime DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2)); err != nil {
gtest.Error(err)
}
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
//db.SetDebug(true)
dataInsert1 := g.Map{
"id": 1,
"name": "name_1",
}
r, err := db.Table(table1).Data(dataInsert1).Insert()
t.Assert(err, nil)
n, _ := r.RowsAffected()
t.Assert(n, 1)
dataInsert2 := g.Map{
"id": 1,
"name": "name_2",
}
r, err = db.Table(table2).Data(dataInsert2).Insert()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
one, err := db.Table(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").FindOne()
t.Assert(err, nil)
t.Assert(one["name"], "name_1")
// Soft deleting.
r, err = db.Table(table1).Delete()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
one, err = db.Table(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").FindOne()
t.Assert(err, nil)
t.Assert(one.IsEmpty(), true)
one, err = db.Table(table2, "t2").LeftJoin(table1, "t1", "t2.id=t1.id").Fields("t2.name").FindOne()
t.Assert(err, nil)
t.Assert(one.IsEmpty(), true)
})
}

View File

@ -6,7 +6,16 @@
package utils
import "strings"
import (
"regexp"
"strings"
)
var (
// replaceCharReg is the regular expression object for replacing chars in key.
// It is used for function EqualFoldWithoutChars.
replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`)
)
// IsLetterUpper checks whether the given byte b is in upper case.
func IsLetterUpper(b byte) bool {
@ -73,3 +82,12 @@ func ReplaceByMap(origin string, replaces map[string]string) string {
}
return origin
}
// EqualFoldWithoutChars checks string <s1> and <s2> equal case-insensitively,
// with/without chars '-'/'_'/'.'/' '.
func EqualFoldWithoutChars(s1, s2 string) bool {
return strings.EqualFold(
replaceCharReg.ReplaceAllString(s1, ""),
replaceCharReg.ReplaceAllString(s2, ""),
)
}

View File

@ -7,8 +7,8 @@
package gutil
import (
"github.com/gogf/gf/internal/utils"
"regexp"
"strings"
)
var (
@ -40,14 +40,9 @@ func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey str
if v, ok := data[key]; ok {
return key, v
}
replacedKey := replaceCharReg.ReplaceAllString(key, "")
if v, ok := data[replacedKey]; ok {
return replacedKey, v
}
// Loop for check.
// Loop checking.
for k, v := range data {
// Remove all special chars and compare with case insensitive.
if strings.EqualFold(replaceCharReg.ReplaceAllString(k, ""), replacedKey) {
if utils.EqualFoldWithoutChars(k, key) {
return k, v
}
}