Compare commits

...

22 Commits

Author SHA1 Message Date
e96ccd5f71 v1.13.6 2020-09-17 23:49:03 +08:00
50a087bb3d Merge branch 'master' of https://github.com/gogf/gf 2020-09-17 23:41:23 +08:00
1ec049c52f improve gf server for routes check while starting 2020-09-17 23:27:10 +08:00
ec38805542 Merge pull request #908 from cai1111/patch-2
Update gdb_driver_mssql.go
2020-09-17 19:23:25 +08:00
4c8517d075 Update gdb_driver_mssql.go 2020-09-17 11:06:24 +08:00
edf06da6ea add struct which implements Interfaces function parameter support for gdb.Model.Insert/Save 2020-09-13 11:21:10 +08:00
eb43a2040e add ghttp.Client.RedirectLimit and gfile.ScanDirFileFunc 2020-09-09 22:26:46 +08:00
9d0ecc7d3e add more unit testing case for package gdb 2020-09-07 20:25:59 +08:00
ad943c5e02 improve gjson.New by using gconv.MapDeep for map/struct 2020-09-07 19:44:11 +08:00
bdb4fd0d25 donator updates 2020-09-07 19:10:04 +08:00
2440e05457 fix issue in limit operations for database driver oracle 2020-09-03 22:38:07 +08:00
1337c6c0d1 add sub query sql support for join functions 2020-09-03 21:57:58 +08:00
957689e07c up 2020-09-02 23:22:16 +08:00
3952d74f87 up 2020-09-02 21:36:21 +08:00
6dc4b81693 add max recursive depth for directory scanning for function gfile.Scan* 2020-09-02 21:25:02 +08:00
9cd953b7be improve function FieldsEx by filtering fields from custom fields specified by function Fields for package gdb 2020-09-02 20:37:02 +08:00
631810dda2 add function String for package gmap 2020-09-02 19:53:58 +08:00
8c12bc5506 change panic to internal logging for package glog 2020-09-01 21:22:19 +08:00
d4091a4826 improve some transaction operations by directly calling model operations, making their implements logic the same 2020-08-31 15:57:04 +08:00
a7c269886b improve support for dynamic database configurations in codes 2020-08-31 15:39:27 +08:00
f54593037b improve CURD functions for package gdb 2020-08-31 00:59:42 +08:00
0415cf6a08 fix issue in nil gtime attribute for model entity for package gdb 2020-08-31 00:39:12 +08:00
37 changed files with 600 additions and 192 deletions

View File

@ -8,9 +8,6 @@ import (
func main() {
db := g.DB()
db.SetMaxIdleConnCount(10)
db.SetMaxOpenConnCount(10)
db.SetMaxConnLifetime(time.Minute)
// 开启调试模式以便于记录所有执行的SQL
db.SetDebug(true)
@ -19,7 +16,7 @@ func main() {
for i := 0; i < 10; i++ {
go db.Table("user").All()
}
time.Sleep(time.Second)
time.Sleep(time.Millisecond * 100)
}
}

View File

@ -115,6 +115,10 @@ please note your github/gitee account in your payment bill. All the donations wi
|[hyuant](https://github.com/hyuant)|alipay|¥66.66|
|*庆|alipay|¥9.99| 支持一下gf越来越好
|**君|alipay|¥10.00| 加油
|向回走的闹钟|wechat|¥20.00| 越来越好
|金毛|wechat|¥100.00|
|莫失莫忘|wechat|¥100.00|
|**航|alipay|¥20.00|

View File

@ -457,6 +457,12 @@ func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
}
}
// String returns the map as a string.
func (m *AnyAnyMap) String() string {
b, _ := m.MarshalJSON()
return gconv.UnsafeBytesToStr(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *AnyAnyMap) MarshalJSON() ([]byte, error) {
return json.Marshal(gconv.Map(m.Map()))

View File

@ -455,6 +455,12 @@ func (m *IntAnyMap) Merge(other *IntAnyMap) {
}
}
// String returns the map as a string.
func (m *IntAnyMap) String() string {
b, _ := m.MarshalJSON()
return gconv.UnsafeBytesToStr(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *IntAnyMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()

View File

@ -426,6 +426,12 @@ func (m *IntIntMap) Merge(other *IntIntMap) {
}
}
// String returns the map as a string.
func (m *IntIntMap) String() string {
b, _ := m.MarshalJSON()
return gconv.UnsafeBytesToStr(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *IntIntMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()

View File

@ -426,6 +426,12 @@ func (m *IntStrMap) Merge(other *IntStrMap) {
}
}
// String returns the map as a string.
func (m *IntStrMap) String() string {
b, _ := m.MarshalJSON()
return gconv.UnsafeBytesToStr(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *IntStrMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()

View File

@ -451,6 +451,12 @@ func (m *StrAnyMap) Merge(other *StrAnyMap) {
}
}
// String returns the map as a string.
func (m *StrAnyMap) String() string {
b, _ := m.MarshalJSON()
return gconv.UnsafeBytesToStr(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *StrAnyMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()

View File

@ -429,6 +429,12 @@ func (m *StrIntMap) Merge(other *StrIntMap) {
}
}
// String returns the map as a string.
func (m *StrIntMap) String() string {
b, _ := m.MarshalJSON()
return gconv.UnsafeBytesToStr(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *StrIntMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()

View File

@ -429,6 +429,12 @@ func (m *StrStrMap) Merge(other *StrStrMap) {
}
}
// String returns the map as a string.
func (m *StrStrMap) String() string {
b, _ := m.MarshalJSON()
return gconv.UnsafeBytesToStr(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *StrStrMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()

View File

@ -510,6 +510,12 @@ func (m *ListMap) Merge(other *ListMap) {
})
}
// String returns the map as a string.
func (m *ListMap) String() string {
b, _ := m.MarshalJSON()
return gconv.UnsafeBytesToStr(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *ListMap) MarshalJSON() ([]byte, error) {
return json.Marshal(gconv.Map(m.Map()))

View File

@ -283,10 +283,10 @@ func Register(name string, driver Driver) error {
// New creates and returns an ORM object with global configurations.
// The parameter <name> specifies the configuration group name,
// which is DEFAULT_GROUP_NAME in default.
func New(name ...string) (db DB, err error) {
group := configs.group
if len(name) > 0 && name[0] != "" {
group = name[0]
func New(group ...string) (db DB, err error) {
groupName := configs.group
if len(group) > 0 && group[0] != "" {
groupName = group[0]
}
configs.RLock()
defer configs.RUnlock()
@ -294,10 +294,10 @@ func New(name ...string) (db DB, err error) {
if len(configs.config) < 1 {
return nil, errors.New("empty database configuration")
}
if _, ok := configs.config[group]; ok {
if node, err := getConfigNodeByGroup(group, true); err == nil {
if _, ok := configs.config[groupName]; ok {
if node, err := getConfigNodeByGroup(groupName, true); err == nil {
c := &Core{
group: group,
group: groupName,
debug: gtype.NewBool(),
cache: gcache.New(),
schema: gtype.NewString(),
@ -320,7 +320,7 @@ func New(name ...string) (db DB, err error) {
return nil, err
}
} else {
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, group))
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName))
}
}

View File

@ -334,7 +334,10 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
//
// The parameter <batch> specifies the batch operation count when given data is slice.
func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
if len(batch) > 0 {
return c.Model(table).Data(data).Batch(batch[0]).Insert()
}
return c.Model(table).Data(data).Insert()
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
@ -347,7 +350,10 @@ func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result,
//
// The parameter <batch> specifies the batch operation count when given data is slice.
func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
if len(batch) > 0 {
return c.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
}
return c.Model(table).Data(data).InsertIgnore()
}
// Replace does "REPLACE INTO ..." statement for the table.
@ -363,7 +369,10 @@ func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.R
// If given data is type of slice, it then does batch replacing, and the optional parameter
// <batch> specifies the batch operation count.
func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
if len(batch) > 0 {
return c.Model(table).Data(data).Batch(batch[0]).Replace()
}
return c.Model(table).Data(data).Replace()
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
@ -378,11 +387,14 @@ func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result
// If given data is type of slice, it then does batch saving, and the optional parameter
// <batch> specifies the batch operation count.
func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
if len(batch) > 0 {
return c.Model(table).Data(data).Batch(batch[0]).Save()
}
return c.Model(table).Data(data).Save()
}
// doInsert inserts or updates data for given table.
//
// This function is usually used for custom interface definition, you do not need call it manually.
// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
// Eg:
// Data(g.Map{"uid": 10000, "name":"john"})
@ -410,7 +422,13 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
switch reflectKind {
case reflect.Slice, reflect.Array:
return c.DB.DoBatchInsert(link, table, data, option, batch...)
case reflect.Map, reflect.Struct:
case reflect.Struct:
if _, ok := data.(apiInterfaces); ok {
return c.DB.DoBatchInsert(link, table, data, option, batch...)
} else {
dataMap = DataToMapDeep(data)
}
case reflect.Map:
dataMap = DataToMapDeep(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
@ -465,28 +483,41 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
// BatchInsert batch inserts data.
// The parameter <list> must be type of slice of map or struct.
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_DEFAULT, batch...)
if len(batch) > 0 {
return c.Model(table).Data(list).Batch(batch[0]).Insert()
}
return c.Model(table).Data(list).Insert()
}
// BatchInsert batch inserts data with ignore option.
// BatchInsertIgnore batch inserts data with ignore option.
// The parameter <list> must be type of slice of map or struct.
func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_IGNORE, batch...)
if len(batch) > 0 {
return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
}
return c.Model(table).Data(list).InsertIgnore()
}
// BatchReplace batch replaces data.
// The parameter <list> must be type of slice of map or struct.
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_REPLACE, batch...)
if len(batch) > 0 {
return c.Model(table).Data(list).Batch(batch[0]).Replace()
}
return c.Model(table).Data(list).Replace()
}
// BatchSave batch replaces data.
// The parameter <list> must be type of slice of map or struct.
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
if len(batch) > 0 {
return c.Model(table).Data(list).Batch(batch[0]).Save()
}
return c.Model(table).Data(list).Save()
}
// DoBatchInsert batch inserts/replaces/saves data.
// This function is usually used for custom interface definition, you do not need call it manually.
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
table = c.DB.QuotePrefixTableName(table)
var (
@ -636,15 +667,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
// "age IN(?,?)", 18, 50
// User{ Id : 1, UserName : "john"}
func (c *Core) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
if newWhere != "" {
newWhere = " WHERE " + newWhere
}
return c.DB.DoUpdate(nil, table, data, newWhere, newArgs...)
return c.Model(table).Data(data).Where(condition, args...).Update()
}
// doUpdate does "UPDATE ... " statement for the table.
// Also see Update.
// This function is usually used for custom interface definition, you do not need call it manually.
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
table = c.DB.QuotePrefixTableName(table)
var (
@ -704,15 +731,11 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
// "age IN(?,?)", 18, 50
// User{ Id : 1, UserName : "john"}
func (c *Core) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
if newWhere != "" {
newWhere = " WHERE " + newWhere
}
return c.DB.DoDelete(nil, table, newWhere, newArgs...)
return c.Model(table).Where(condition, args...).Delete()
}
// DoDelete does "DELETE FROM ... " statement for the table.
// Also see Delete.
// This function is usually used for custom interface definition, you do not need call it manually.
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
if link == nil {
if link, err = c.DB.Master(); err != nil {

View File

@ -115,6 +115,14 @@ func GetDefaultGroup() string {
return configs.group
}
// IsConfigured checks and returns whether the database configured.
// It returns true if any configuration exists.
func IsConfigured() bool {
configs.RLock()
defer configs.RUnlock()
return len(configs.config) > 0
}
// SetLogger sets the logger for orm.
func (c *Core) SetLogger(logger *glog.Logger) {
c.logger = logger

View File

@ -223,7 +223,7 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
isnull(e.text,'') as [Default],
isnull(g.[value],'') AS [Comment]
FROM syscolumns a
left join systypes b on a.xtype=b.xusertype
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

View File

@ -78,44 +78,45 @@ func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []inter
// parseSql does some replacement of the sql before commits it to underlying driver,
// for support of oracle server.
func (d *DriverOracle) parseSql(sql string) string {
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
if gregex.IsMatchString(patten, sql) == false {
var (
patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))`
allMatch, _ = gregex.MatchAllString(patten, sql)
)
if len(allMatch) == 0 {
return sql
}
res, err := gregex.MatchAllString(patten, sql)
if err != nil {
return ""
}
var (
index = 0
keyword = strings.ToUpper(strings.TrimSpace(res[index][0]))
keyword = strings.ToUpper(strings.TrimSpace(allMatch[index][0]))
)
index++
switch keyword {
case "SELECT":
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false &&
strings.HasPrefix(res[index][0], "limit") == false) {
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 ||
if len(queryExpr) != 4 ||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
break
}
first, limit := 0, 0
for i := 1; i < len(res[index]); i++ {
if len(strings.TrimSpace(res[index][i])) == 0 {
for i := 1; i < len(allMatch[index]); i++ {
if len(strings.TrimSpace(allMatch[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])
if strings.HasPrefix(allMatch[index][i], "LIMIT") {
if allMatch[index][i+2] != "" {
first, _ = strconv.Atoi(allMatch[index][i+1])
limit, _ = strconv.Atoi(allMatch[index][i+2])
} else {
limit, _ = strconv.Atoi(allMatch[index][i+1])
}
break
}
}

View File

@ -104,7 +104,6 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
if v, ok := value.(apiMapStrAny); ok {
return v.MapStrAny()
}
var (
rvValue reflect.Value
rvField reflect.Value
@ -182,7 +181,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
// The underlying driver supports time.Time/*time.Time types.
fieldValue := rvField.Interface()
switch fieldValue.(type) {
case time.Time, *time.Time:
case time.Time, *time.Time, gtime.Time, *gtime.Time:
data[name] = fieldValue
default:
// Use string conversion in default.
@ -317,8 +316,10 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
return where
}
if len(where) == 1 {
rv := reflect.ValueOf(where[0])
kind := rv.Kind()
var (
rv = reflect.ValueOf(where[0])
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()

View File

@ -33,40 +33,20 @@ func (m *Model) Fields(fields string) *Model {
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
// Note that this function supports only single table operations.
func (m *Model) FieldsEx(fields string) *Model {
if gstr.Contains(m.tables, " ") {
panic("function FieldsEx supports only single table operations")
}
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
model := m.getModel()
model.fieldsEx = fields
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
model.fields = ""
for _, k := range fieldsArray {
if fieldsExSet.Contains(k) {
continue
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
model.fields = model.db.QuoteString(model.fields)
return model
}
// Deprecated, use GetFieldsStr instead.
// This function name confuses the user that it was a chaining function.
func (m *Model) FieldsStr(prefix ...string) string {
return m.GetFieldsStr(prefix...)
}
// FieldsStr retrieves and returns all fields from the table, joined with char ','.
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsStr("u.").
func (m *Model) FieldsStr(prefix ...string) string {
func (m *Model) GetFieldsStr(prefix ...string) string {
prefixStr := ""
if len(prefix) > 0 {
prefixStr = prefix[0]
@ -93,11 +73,17 @@ func (m *Model) FieldsStr(prefix ...string) string {
return newFields
}
// Deprecated, use GetFieldsExStr instead.
// This function name confuses the user that it was a chaining function.
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
return m.GetFieldsExStr(fields, prefix...)
}
// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table,
// joined with char ','.
// The parameter <fields> specifies the fields that are excluded.
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsExStr("id", "u.").
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
prefixStr := ""
if len(prefix) > 0 {
prefixStr = prefix[0]

View File

@ -6,7 +6,21 @@
package gdb
import "fmt"
import (
"fmt"
"github.com/gogf/gf/text/gstr"
)
// isSubQuery checks and returns whether given string a sub-query sql string.
func isSubQuery(s string) bool {
s = gstr.TrimLeft(s)
if p := gstr.Pos(s, " "); p != -1 {
if gstr.Equal(s[:p], "select") {
return true
}
}
return false
}
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
// The parameter <table> can be joined table and its joined condition,
@ -14,19 +28,32 @@ import "fmt"
// 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()
var (
model = m.getModel()
joinStr = ""
)
if len(table) > 0 {
if isSubQuery(table[0]) {
joinStr = "(" + table[0] + ")"
} else {
joinStr = m.db.QuotePrefixTableName(table[0])
}
}
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],
joinStr, 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],
joinStr, table[1],
)
} else if len(table) == 1 {
model.tables += fmt.Sprintf(
" LEFT JOIN %s",
joinStr,
)
} else {
panic("invalid join table parameter")
}
return model
}
@ -37,19 +64,32 @@ func (m *Model) LeftJoin(table ...string) *Model {
// 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()
var (
model = m.getModel()
joinStr = ""
)
if len(table) > 0 {
if isSubQuery(table[0]) {
joinStr = "(" + table[0] + ")"
} else {
joinStr = m.db.QuotePrefixTableName(table[0])
}
}
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],
joinStr, 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],
joinStr, table[1],
)
} else if len(table) == 1 {
model.tables += fmt.Sprintf(
" RIGHT JOIN %s",
joinStr,
)
} else {
panic("invalid join table parameter")
}
return model
}
@ -60,19 +100,32 @@ func (m *Model) RightJoin(table ...string) *Model {
// 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()
var (
model = m.getModel()
joinStr = ""
)
if len(table) > 0 {
if isSubQuery(table[0]) {
joinStr = "(" + table[0] + ")"
} else {
joinStr = m.db.QuotePrefixTableName(table[0])
}
}
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],
joinStr, 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],
joinStr, table[1],
)
} else if len(table) == 1 {
model.tables += fmt.Sprintf(
" INNER JOIN %s",
joinStr,
)
} else {
panic("invalid join table parameter")
}
return model
}

View File

@ -8,7 +8,9 @@ package gdb
import (
"fmt"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"reflect"
)
@ -53,12 +55,13 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
}
conditionWhere += softDeletingCondition
}
// DO NOT quote the m.fields where, in case of fields like:
// DISTINCT t.user_id uid
return m.doGetAllBySql(
fmt.Sprintf(
"SELECT %s FROM %s%s",
m.fields,
m.getFieldsFiltered(),
m.tables,
conditionWhere+conditionExtra,
),
@ -66,6 +69,53 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
)
}
// getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will
// really be committed to underlying database driver.
func (m *Model) getFieldsFiltered() string {
if m.fieldsEx == "" {
// No filtering.
return m.fields
}
var (
fieldsArray []string
fieldsExSet = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fieldsEx, ","))
)
if m.fields != "*" {
// Filter custom fields with fieldEx.
fieldsArray = make([]string, 0, 8)
for _, v := range gstr.SplitAndTrim(m.fields, ",") {
fieldsArray = append(fieldsArray, v[gstr.PosR(v, "-")+1:])
}
} else {
if gstr.Contains(m.tables, " ") {
panic("function FieldsEx supports only single table operations")
}
// Filter table fields with fieldEx.
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
fieldsArray = make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
}
newFields := ""
for _, k := range fieldsArray {
if fieldsExSet.Contains(k) {
continue
}
if len(newFields) > 0 {
newFields += ","
}
newFields += k
}
return newFields
}
// Chunk iterates the query result with given size and callback function.
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
page := m.start

View File

@ -86,8 +86,8 @@ func (m *Model) getConditionForSoftDeleting() string {
// 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)
// Multiple joined tables, exclude the sub query sql which contains char '(' and ')'.
matches, _ := gregex.MatchAllString(`JOIN ([^()]+?) ON`, m.tables)
for _, match := range matches {
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
}

View File

@ -115,7 +115,6 @@ func (tx *TX) GetScan(objPointer interface{}, sql string, args ...interface{}) e
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
}
// GetValue queries and returns the field value from database.
@ -154,7 +153,10 @@ func (tx *TX) GetCount(sql string, args ...interface{}) (int, error) {
//
// The parameter <batch> specifies the batch operation count when given data is slice.
func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_DEFAULT, batch...)
if len(batch) > 0 {
return tx.Model(table).Data(data).Batch(batch[0]).Insert()
}
return tx.Model(table).Data(data).Insert()
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
@ -167,7 +169,10 @@ func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result,
//
// The parameter <batch> specifies the batch operation count when given data is slice.
func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_IGNORE, batch...)
if len(batch) > 0 {
return tx.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
}
return tx.Model(table).Data(data).InsertIgnore()
}
// Replace does "REPLACE INTO ..." statement for the table.
@ -183,7 +188,10 @@ func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Re
// If given data is type of slice, it then does batch replacing, and the optional parameter
// <batch> specifies the batch operation count.
func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_REPLACE, batch...)
if len(batch) > 0 {
return tx.Model(table).Data(data).Batch(batch[0]).Replace()
}
return tx.Model(table).Data(data).Replace()
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
@ -198,31 +206,46 @@ func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result,
// If given data is type of slice, it then does batch saving, and the optional parameter
// <batch> specifies the batch operation count.
func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_SAVE, batch...)
if len(batch) > 0 {
return tx.Model(table).Data(data).Batch(batch[0]).Save()
}
return tx.Model(table).Data(data).Save()
}
// BatchInsert batch inserts data.
// The parameter <list> must be type of slice of map or struct.
func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_DEFAULT, batch...)
if len(batch) > 0 {
return tx.Model(table).Data(list).Batch(batch[0]).Insert()
}
return tx.Model(table).Data(list).Insert()
}
// BatchInsert batch inserts data with ignore option.
// The parameter <list> must be type of slice of map or struct.
func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_IGNORE, batch...)
if len(batch) > 0 {
return tx.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
}
return tx.Model(table).Data(list).InsertIgnore()
}
// BatchReplace batch replaces data.
// The parameter <list> must be type of slice of map or struct.
func (tx *TX) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_REPLACE, batch...)
if len(batch) > 0 {
return tx.Model(table).Data(list).Batch(batch[0]).Replace()
}
return tx.Model(table).Data(list).Replace()
}
// BatchSave batch replaces data.
// The parameter <list> must be type of slice of map or struct.
func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_SAVE, batch...)
if len(batch) > 0 {
return tx.Model(table).Data(list).Batch(batch[0]).Save()
}
return tx.Model(table).Data(list).Save()
}
// Update does "UPDATE ... " statement for the table.
@ -240,11 +263,7 @@ func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Resul
// "age IN(?,?)", 18, 50
// User{ Id : 1, UserName : "john"}
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
if newWhere != "" {
newWhere = " WHERE " + newWhere
}
return tx.db.DoUpdate(tx.tx, table, data, newWhere, newArgs...)
return tx.Model(table).Data(data).Where(condition, args...).Update()
}
// Delete does "DELETE FROM ... " statement for the table.
@ -259,9 +278,5 @@ func (tx *TX) Update(table string, data interface{}, condition interface{}, args
// "age IN(?,?)", 18, 50
// User{ Id : 1, UserName : "john"}
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
if newWhere != "" {
newWhere = " WHERE " + newWhere
}
return tx.db.DoDelete(tx.tx, table, newWhere, newArgs...)
return tx.Model(table).Where(condition, args...).Delete()
}

View File

@ -303,3 +303,12 @@ func Test_Func_DataToMapDeep(t *testing.T) {
t.Assert(m["reset_password_token_at"], new(mysql.NullTime))
})
}
func Test_isSubQuery(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(isSubQuery("user"), false)
t.Assert(isSubQuery("user.uid"), false)
t.Assert(isSubQuery("u, user.uid"), false)
t.Assert(isSubQuery("select 1"), true)
})
}

View File

@ -52,20 +52,20 @@ func Test_Model_Insert(t *testing.T) {
t.Assert(n, 1)
type User struct {
Id int `gconv:"id"`
Uid int `gconv:"uid"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
Id int `gconv:"id"`
Uid int `gconv:"uid"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime *gtime.Time `json:"create_time"`
}
// Model inserting.
result, err = db.Table(table).Filter().Data(User{
Id: 3,
Uid: 3,
Passport: "t3",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "name_3",
CreateTime: gtime.Now().String(),
Id: 3,
Uid: 3,
Passport: "t3",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "name_3",
}).Insert()
t.Assert(err, nil)
n, _ = result.RowsAffected()
@ -80,7 +80,7 @@ func Test_Model_Insert(t *testing.T) {
Passport: "t4",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "T4",
CreateTime: gtime.Now().String(),
CreateTime: gtime.Now(),
}).Insert()
t.Assert(err, nil)
n, _ = result.RowsAffected()
@ -94,7 +94,30 @@ func Test_Model_Insert(t *testing.T) {
n, _ = result.RowsAffected()
t.Assert(n, 3)
})
}
func Test_Model_BatchInsertWithArrayStruct(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
user := db.Table(table)
array := garray.New()
for i := 1; i <= SIZE; i++ {
array.Append(g.Map{
"id": i,
"uid": i,
"passport": fmt.Sprintf("t%d", i),
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": fmt.Sprintf("name_%d", i),
"create_time": gtime.Now().String(),
})
}
result, err := user.Filter().Data(array).Insert()
t.Assert(err, nil)
n, _ := result.LastInsertId()
t.Assert(n, SIZE)
})
}
func Test_Model_InsertIgnore(t *testing.T) {
@ -2243,6 +2266,19 @@ func Test_Model_DryRun(t *testing.T) {
})
}
func Test_Model_Join_SubQuery(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
subQuery := fmt.Sprintf("select * from `%s`", table)
r, err := db.Table(table, "t1").Fields("t2.id").LeftJoin(subQuery, "t2", "t2.id=t1.id").Array()
t.Assert(err, nil)
t.Assert(len(r), SIZE)
t.Assert(r[0], "1")
t.Assert(r[SIZE-1], SIZE)
})
}
func Test_Model_Cache(t *testing.T) {
table := createInitTable()
defer dropTable(table)

View File

@ -0,0 +1,49 @@
// 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_Oracle_parseSql(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
o := new(DriverOracle)
sql := `UPDATE user SET name='john'`
newSql := o.parseSql(sql)
t.Assert(newSql, sql)
})
gtest.C(t, func(t *gtest.T) {
o := new(DriverOracle)
sql := `SELECT * FROM user`
newSql := o.parseSql(sql)
t.Assert(newSql, sql)
})
gtest.C(t, func(t *gtest.T) {
o := new(DriverOracle)
sql := `SELECT * FROM user LIMIT 0, 10`
newSql := o.parseSql(sql)
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT * FROM user ) GFORM WHERE ROWNUM <= 10) WHERE ROWNUM_ >= 0`)
})
gtest.C(t, func(t *gtest.T) {
o := new(DriverOracle)
sql := `SELECT * FROM user LIMIT 1`
newSql := o.parseSql(sql)
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT * FROM user ) GFORM WHERE ROWNUM <= 1) WHERE ROWNUM_ >= 0`)
})
gtest.C(t, func(t *gtest.T) {
o := new(DriverOracle)
sql := `SELECT ENAME FROM USER_INFO WHERE ID=2 LIMIT 1`
newSql := o.parseSql(sql)
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT ENAME FROM USER_INFO WHERE ID=2 ) GFORM WHERE ROWNUM <= 1) WHERE ROWNUM_ >= 0`)
})
}

View File

@ -55,8 +55,10 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
}
}
default:
rv := reflect.ValueOf(data)
kind := rv.Kind()
var (
rv = reflect.ValueOf(data)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
@ -72,9 +74,7 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
}
case reflect.Map, reflect.Struct:
i := interface{}(nil)
// Note that it uses Map function implementing the converting.
// Note that it here should not use MapDeep function if you really know what it means.
i = gconv.Map(data, tags)
i = gconv.MapDeep(data, tags)
j = &Json{
p: &i,
c: byte(gDEFAULT_SPLIT_CHAR),

View File

@ -47,3 +47,25 @@ func Test_Load_NewWithTag(t *testing.T) {
t.Assert(j.Get("addr-json"), nil)
})
}
func Test_Load_New_CustomStruct(t *testing.T) {
type Base struct {
Id int
}
type User struct {
Base
Name string
}
user := new(User)
user.Id = 1
user.Name = "john"
gtest.C(t, func(t *gtest.T) {
j := gjson.New(user)
t.AssertNE(j, nil)
s, err := j.ToJsonString()
t.Assert(err, nil)
t.Assert(s == `{"Id":1,"Name":"john"}` || s == `{"Name":"john","Id":1}`, true)
})
}

View File

@ -10,7 +10,7 @@ import (
"github.com/gogf/gf/encoding/gjson"
)
// New creates a Parser object with any variable type of <data>, but <data> should be a map or
// New creates a Parser object with any variable type of <data>, but <data> should be a map, struct or
// slice for data access reason, or it will make no sense.
//
// The parameter <safe> specifies whether using this Json object in concurrent-safe context, which

View File

@ -31,26 +31,26 @@ func Database(name ...string) gdb.DB {
}
instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group)
db := instances.GetOrSetFuncLock(instanceKey, func() interface{} {
// If configuration file does not exist but the gdb configurations are already set.
if !Config().Available() && gdb.GetConfig(group) != nil {
db, err := gdb.Instance(group)
if err != nil {
panic(err)
}
return db
}
// Check the configuration file.
var m map[string]interface{}
var (
configMap map[string]interface{}
configNodeKey string
)
// It firstly searches the configuration of the instance name.
nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
if nodeKey == "" {
nodeKey = gDATABASE_NODE_NAME
if Config().Available() {
configNodeKey, _ = gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
if configNodeKey == "" {
configNodeKey = gDATABASE_NODE_NAME
}
configMap = Config().GetMap(configNodeKey)
}
if m = Config().GetMap(nodeKey); len(m) == 0 {
if len(configMap) == 0 && !gdb.IsConfigured() {
panic(fmt.Sprintf(`database init failed: "%s" node not found, is config file or configuration missing?`, gDATABASE_NODE_NAME))
}
if len(configMap) == 0 {
configMap = make(map[string]interface{})
}
// Parse <m> as map-slice and adds it to gdb's global configurations.
for group, groupConfig := range m {
for g, groupConfig := range configMap {
cg := gdb.ConfigGroup{}
switch value := groupConfig.(type) {
case []interface{}:
@ -65,37 +65,51 @@ func Database(name ...string) gdb.DB {
}
}
if len(cg) > 0 {
intlog.Printf("%s, %#v", group, cg)
gdb.SetConfigGroup(group, cg)
if gdb.GetConfig(group) == nil {
intlog.Printf("add configuration for group: %s, %#v", g, cg)
gdb.SetConfigGroup(g, cg)
} else {
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", g, cg)
intlog.Printf("%s, %#v", g, cg)
}
}
}
// Parse <m> as a single node configuration,
// which is the default group configuration.
if node := parseDBConfigNode(m); node != nil {
if node := parseDBConfigNode(configMap); node != nil {
cg := gdb.ConfigGroup{}
if node.LinkInfo != "" || node.Host != "" {
cg = append(cg, *node)
}
if len(cg) > 0 {
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
if gdb.GetConfig(group) == nil {
intlog.Printf("add configuration for group: %s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
} else {
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
}
}
}
// Create a new ORM object with given configurations.
if db, err := gdb.New(name...); err == nil {
// Initialize logger for ORM.
var m map[string]interface{}
m = Config().GetMap(fmt.Sprintf("%s.%s", nodeKey, gLOGGER_NODE_NAME))
if len(m) == 0 {
m = Config().GetMap(nodeKey)
}
if len(m) > 0 {
if err := db.GetLogger().SetConfigWithMap(m); err != nil {
panic(err)
if Config().Available() {
// Initialize logger for ORM.
var loggerConfigMap map[string]interface{}
loggerConfigMap = Config().GetMap(fmt.Sprintf("%s.%s", configNodeKey, gLOGGER_NODE_NAME))
if len(loggerConfigMap) == 0 {
loggerConfigMap = Config().GetMap(configNodeKey)
}
if len(loggerConfigMap) > 0 {
if err := db.GetLogger().SetConfigWithMap(loggerConfigMap); err != nil {
panic(err)
}
}
}
return db
} else {
// It panics often because it dose not find its configuration for given group.
panic(err)
}
return nil

View File

@ -149,3 +149,14 @@ func (c *Client) Proxy(proxyURL string) *Client {
newClient.SetProxy(proxyURL)
return c
}
// RedirectLimit is a chaining function,
// which sets the redirect limit the number of jumps for the request.
func (c *Client) RedirectLimit(redirectLimit int) *Client {
newClient := c
if c.parent == nil {
newClient = c.Clone()
}
newClient.SetRedirectLimit(redirectLimit)
return c
}

View File

@ -267,11 +267,6 @@ func (s *Server) Start() error {
return errors.New("[ghttp] server is already running")
}
// If there's no route registered and no static service enabled,
// it then returns an error of invalid usage of server.
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
return errors.New(`[ghttp] there's no route set or static feature enabled, did you forget import the router?`)
}
// Logging path setting check.
if s.config.LogPath != "" {
if err := s.config.Logger.SetPath(s.config.LogPath); err != nil {
@ -316,6 +311,12 @@ func (s *Server) Start() error {
// Check the group routes again.
s.handlePreBindItems()
// If there's no route registered and no static service enabled,
// it then returns an error of invalid usage of server.
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
return errors.New(`[ghttp] there's no route set or static feature enabled, did you forget import the router?`)
}
// Start the HTTP server.
reloaded := false
fdMapStr := genv.Get(gADMIN_ACTION_RELOAD_ENVKEY)

View File

@ -7,12 +7,18 @@
package gfile
import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/text/gstr"
"os"
"path/filepath"
"sort"
)
const (
// Max recursive depth for directory scanning.
gMAX_SCAN_DEPTH = 100000
)
// ScanDir returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
@ -23,7 +29,7 @@ func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
if len(recursive) > 0 {
isRecursive = recursive[0]
}
list, err := doScanDir(path, pattern, isRecursive, nil)
list, err := doScanDir(0, path, pattern, isRecursive, nil)
if err != nil {
return nil, err
}
@ -47,7 +53,7 @@ func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
func ScanDirFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
list, err := doScanDir(path, pattern, recursive, handler)
list, err := doScanDir(0, path, pattern, recursive, handler)
if err != nil {
return nil, err
}
@ -69,7 +75,7 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
if len(recursive) > 0 {
isRecursive = recursive[0]
}
list, err := doScanDir(path, pattern, isRecursive, func(path string) string {
list, err := doScanDir(0, path, pattern, isRecursive, func(path string) string {
if IsDir(path) {
return ""
}
@ -84,6 +90,38 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
return list, nil
}
// ScanDirFileFunc returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
// symbol to separate multiple patterns.
//
// The parameter <recursive> specifies whether scanning the <path> recursively, which
// means it scans its sub-files and appends the files path to result array if the sub-file
// is also a folder. It is false in default.
//
// The parameter <handler> specifies the callback function handling each sub-file path of
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
//
// Note that the parameter <path> for <handler> is not a directory but a file.
// It returns only files, exclusive of directories.
func ScanDirFileFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
list, err := doScanDir(0, path, pattern, recursive, func(path string) string {
if IsDir(path) {
return ""
}
return handler(path)
})
if err != nil {
return nil, err
}
if len(list) > 0 {
sort.Strings(list)
}
return list, nil
}
// doScanDir is an internal method which scans directory and returns the absolute path
// list of files that are not sorted.
//
@ -97,7 +135,10 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
// The parameter <handler> specifies the callback function handling each sub-file path of
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
func doScanDir(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
func doScanDir(depth int, path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
if depth >= gMAX_SCAN_DEPTH {
return nil, gerror.Newf("directory scanning exceeds max recursive depth: %d", gMAX_SCAN_DEPTH)
}
list := ([]string)(nil)
file, err := os.Open(path)
if err != nil {
@ -115,7 +156,7 @@ func doScanDir(path string, pattern string, recursive bool, handler func(path st
for _, name := range names {
filePath = path + Separator + name
if IsDir(filePath) && recursive {
array, _ := doScanDir(filePath, pattern, true, handler)
array, _ := doScanDir(depth+1, filePath, pattern, true, handler)
if len(array) > 0 {
list = append(list, array...)
}

View File

@ -7,6 +7,7 @@
package gfile_test
import (
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/debug/gdebug"
"testing"
@ -65,3 +66,30 @@ func Test_ScanDirFile(t *testing.T) {
t.AssertIN(teatPath+gfile.Separator+"dir2"+gfile.Separator+"file2", files)
})
}
func Test_ScanDirFileFunc(t *testing.T) {
teatPath := gdebug.TestDataPath()
gtest.C(t, func(t *gtest.T) {
array := garray.New()
files, err := gfile.ScanDirFileFunc(teatPath, "*", false, func(path string) string {
array.Append(1)
return path
})
t.Assert(err, nil)
t.Assert(len(files), 0)
t.Assert(array.Len(), 0)
})
gtest.C(t, func(t *gtest.T) {
array := garray.New()
files, err := gfile.ScanDirFileFunc(teatPath, "*", true, func(path string) string {
array.Append(1)
if gfile.Basename(path) == "file1" {
return path
}
return ""
})
t.Assert(err, nil)
t.Assert(len(files), 1)
t.Assert(array.Len(), 3)
})
}

View File

@ -230,7 +230,8 @@ func (l *Logger) printToWriter(now time.Time, std io.Writer, buffer *bytes.Buffe
}
} else {
if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
panic(err)
// panic(err)
intlog.Error(err)
}
}
}
@ -249,7 +250,9 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
stat, err := file.Stat()
if err != nil {
file.Close()
panic(err)
// panic(err)
intlog.Error(err)
return
}
if stat.Size() > l.config.RotateSize {
l.rotateFileBySize(now)
@ -258,7 +261,9 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
}
if _, err := file.Write(buffer.Bytes()); err != nil {
file.Close()
panic(err)
// panic(err)
intlog.Error(err)
return
}
file.Close()
}
@ -272,7 +277,8 @@ func (l *Logger) getFilePointer(path string) *gfpool.File {
gDEFAULT_FILE_EXPIRE,
)
if err != nil {
panic(err)
// panic(err)
intlog.Error(err)
}
return file
}

View File

@ -8,6 +8,7 @@ package glog
import (
"context"
"github.com/gogf/gf/internal/intlog"
"io"
"github.com/gogf/gf/os/gfile"
@ -55,7 +56,8 @@ func (l *Logger) Path(path string) *Logger {
}
if path != "" {
if err := logger.SetPath(path); err != nil {
panic(err)
// panic(err)
intlog.Error(err)
}
}
return logger
@ -73,7 +75,8 @@ func (l *Logger) Cat(category string) *Logger {
}
if logger.config.Path != "" {
if err := logger.SetPath(gfile.Join(logger.config.Path, category)); err != nil {
panic(err)
// panic(err)
intlog.Error(err)
}
}
return logger
@ -115,7 +118,8 @@ func (l *Logger) LevelStr(levelStr string) *Logger {
logger = l
}
if err := logger.SetLevelStr(levelStr); err != nil {
panic(err)
// panic(err)
intlog.Error(err)
}
return logger
}

View File

@ -28,7 +28,8 @@ func (l *Logger) rotateFileBySize(now time.Time) {
l.rmu.Lock()
defer l.rmu.Unlock()
if err := l.doRotateFile(l.getFilePath(now)); err != nil {
panic(err)
// panic(err)
intlog.Error(err)
}
}

View File

@ -37,7 +37,7 @@ func MapDeep(value interface{}, tags ...string) map[string]interface{} {
// doMapConvert implements the map converting.
// It automatically checks and converts json string to map if <value> is string/[]byte.
//
// TODO completely implement the recursive converting for all types.
// TODO completely implement the recursive converting for all types, especially the map.
func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]interface{} {
if value == nil {
return nil

View File

@ -1,4 +1,4 @@
package gf
const VERSION = "v1.13.4"
const VERSION = "v1.13.6"
const AUTHORS = "john<john@goframe.org>"