Compare commits

..

237 Commits

Author SHA1 Message Date
9a6aa01115 constant name changes; version update 2020-12-15 20:16:17 +08:00
4a88e0255d improve shutdown feature for ghttp.Server 2020-12-15 19:24:38 +08:00
edc56949b7 improve package glog for rotation feature 2020-12-15 13:03:12 +08:00
63bc06d0fe improve package gview 2020-12-15 00:09:55 +08:00
9b58b66172 fix issue of HAVING statement before ORDER BY 2020-12-14 23:34:23 +08:00
3517295e96 improve package gcdm 2020-12-14 23:00:22 +08:00
c685876e6f improve package gparser 2020-12-14 21:22:04 +08:00
cb2c9c43a8 rename constant names 2020-12-14 19:34:02 +08:00
751a567e84 add Is* functions for package gvar 2020-12-14 18:54:14 +08:00
0a99bb9a7d improve gconv.Interfaces 2020-12-14 16:56:41 +08:00
a2e7aec37f change constant variable names 2020-12-14 13:26:48 +08:00
102e2d07d9 improve package gcfg for automatic configuration file type checks; change const variables for package gcfg/glog/gtimer/gmode/gins/gi18n 2020-12-14 13:02:08 +08:00
bb39ed136f Merge branch 'fix/json' 2020-12-12 01:42:56 +08:00
363f6eba44 remove automatic stack content printing for error that has stack infomation 2020-12-11 01:12:53 +08:00
0ca305a1bf improve package gerror 2020-12-11 01:08:15 +08:00
1d1e64b834 improve package gerror 2020-12-11 00:45:15 +08:00
688e327f15 improve unit testing case for package ghttp 2020-12-10 23:38:59 +08:00
8c7ec0e7d9 Merge branch 'master' of https://github.com/gogf/gf 2020-12-10 23:33:43 +08:00
84fef8dea3 add error code feature for package gerror 2020-12-10 23:33:24 +08:00
c1d2ad68b3 Merge pull request #1051 from dozysun/master
fix InsertIgnore didn't work
2020-12-10 20:17:43 +08:00
a577605726 improve package gtimer 2020-12-10 20:10:07 +08:00
71444736ae fix InsertIgnore didn't work
fix InsertIgnore didn't work
2020-12-10 14:40:58 +08:00
b7e41ec32c improve package gvalid 2020-12-09 21:00:30 +08:00
7fa09596b0 improve package gsession 2020-12-09 16:22:03 +08:00
7316e6648f change errors to gerror; update copyright in comment 2020-12-09 16:04:29 +08:00
e9d346ce4f improve package glog 2020-12-09 13:54:27 +08:00
4b91e709f7 Merge branch 'master' of https://github.com/gogf/gf into develop 2020-12-09 13:48:50 +08:00
7de89286da Merge pull request #1045 from prcseraph/master 2020-12-09 13:48:33 +08:00
569a953b43 inprove package gconv for empty string converting to number array 2020-12-09 13:39:09 +08:00
1e7f795c69 remove package github.com/json-iterator/go 2020-12-09 01:38:56 +08:00
d2ae383b83 improve unit testing case for package gdb 2020-12-09 01:33:02 +08:00
8978112433 add gdb.Raw for raw sql instead of prepare argument feature 2020-12-09 01:29:23 +08:00
117eaea720 add support for cert and key file reading from resource manager for ghttp.Server 2020-12-09 00:06:56 +08:00
278e85357d * 日志根据大小切片时,文件重命名读写冲突。 2020-12-08 18:54:42 +08:00
60ec59fa4a improve package gsession 2020-12-07 20:39:52 +08:00
0aa82ad020 improve package gpool 2020-12-07 18:57:40 +08:00
7bd319ddc7 Merge branch 'master' of https://github.com/gogf/gf into develop 2020-12-07 13:19:42 +08:00
7ceb667486 Merge pull request #1040 from Wlvs530/master
Not Callback ExpireFunc In Pool.Get()
2020-12-07 13:18:48 +08:00
80c4786afd Not Callback ExpireFunc In Pool.Get() 2020-12-05 13:21:18 +08:00
2f741d3b24 add genv.SetMap 2020-12-05 00:06:03 +08:00
d2b65f0f91 add gcmd.GetWithEnv/genv.GetWithCmd; remove package cmdenv; 2020-12-04 23:44:54 +08:00
c226782f23 improve package gcmd,internal/cmdenv 2020-12-04 23:29:20 +08:00
57a82ebcc0 improve gconv.Struct 2020-12-04 18:17:11 +08:00
416885a726 gdb driver example updates 2020-12-04 15:42:35 +08:00
9b4d2d9172 fix issue in unit testing case for package gjson/gparser 2020-12-04 15:41:26 +08:00
d4b2bf20bb gdb driver example updates 2020-12-04 15:37:14 +08:00
ce9a0555c5 improve gconv.Interfaces by adding support for type map as its parameter 2020-12-04 14:33:47 +08:00
5171250a9d improve gutil.Keys,gdb.Model.Fields,internal/structs 2020-12-04 14:22:50 +08:00
80b629916a fix issue in function Clone for package gmap/garray/gtree 2020-12-02 21:38:29 +08:00
ecaf0da228 improve package glog; add more unit testing case for package gmlock 2020-12-02 21:33:07 +08:00
8c0a905a9f improve package gconv for custom type converting 2020-12-01 15:57:06 +08:00
5a0326f666 fix issue in unit testing case for package gconv 2020-11-30 09:07:44 +08:00
790a651ac1 fix typo 2020-11-29 23:55:32 +08:00
6f93bd17f2 add context feature for package gdb 2020-11-29 23:54:38 +08:00
3419d66c4b add context feature for package gdb 2020-11-29 23:50:16 +08:00
cabf684ec9 add context feature for package gdb 2020-11-29 23:47:57 +08:00
2b6e6ce28e improve update counter feature for package gdb 2020-11-29 22:26:16 +08:00
32101189a2 Merge branch 'master' of https://github.com/gogf/gf into develop 2020-11-29 21:46:36 +08:00
600c081801 Merge pull request #1028 from arieslee/gdb-counter
add update counter method for package gdb.
2020-11-29 21:46:17 +08:00
0899a9d49a Merge pull request #1027 from arieslee/gvalid-phone-loose
Add 172-segment verification rules, add loose mobile number verificat…
2020-11-29 21:45:28 +08:00
c56f4eabca inprove gconv.Interfaces for struct type 2020-11-29 21:34:28 +08:00
bfe89e0b12 add build-in fuction json for package gview 2020-11-28 22:48:01 +08:00
6cb38cfa92 add update counter method for package gdb. 2020-11-28 14:56:21 +08:00
09bb0c9397 更新gdb.Update方法 2020-11-28 13:52:17 +08:00
fa47b0306d Add 172-segment verification rules, add loose mobile number verification rules. 2020-11-28 12:23:18 +08:00
55429ad589 fix data race issue in unit testing case of package gdb 2020-11-28 00:11:22 +08:00
9592fb099f Change time string argument wrapping with TO_DATE function for DriverOracle in package gdb 2020-11-27 23:11:55 +08:00
18ec6116ad improve time argument timezone handling for package gdb 2020-11-27 22:51:34 +08:00
9d0f306684 improve time argument timezone handling for package gdb 2020-11-27 21:03:22 +08:00
3eba8d690f add TimeMaintainDisabled configuration for automatic time maintaining feature 2020-11-27 13:28:18 +08:00
c0b59007ce improve configuration for package gdb 2020-11-27 13:24:05 +08:00
3485ba2a5d improve package gtime 2020-11-26 22:40:55 +08:00
43ecfc7484 Merge branch 'develop' of https://github.com/gogf/gf into develop 2020-11-26 22:12:48 +08:00
5ba53e56c9 improve join feature for sub-query for gdb.Model 2020-11-26 22:11:58 +08:00
af1d14ace6 Merge pull request #1021 from tiansin/develop
fix: 🐛 fix model chunk page
2020-11-26 21:40:45 +08:00
c02bf715c5 improve gutil.ListItemValues/ListItemValuesUnique supporting retrieving values from slice attributes 2020-11-26 21:13:44 +08:00
0c0e902b07 if CreatedAt/UpdatedAt/DeletedAt field name configured, just use it, ignore the default field names 2020-11-26 20:17:24 +08:00
750b53d7aa improve debug infor for argument of time.Time for package gdb 2020-11-26 19:53:44 +08:00
1d807c095a Merge branch 'master' into develop 2020-11-26 19:28:25 +08:00
69b5873bf9 fix: 🐛 fix model chunk page 2020-11-26 14:26:32 +08:00
60fc9b6417 add gerror.ApiCurrent impements for gerror.Error 2020-11-25 19:06:29 +08:00
0bc8944a08 change ghttp.NewClient to g.Client 2020-11-25 16:40:45 +08:00
33292f54e0 improve status handler by supporting multiple status handler for package ghttp 2020-11-25 16:37:41 +08:00
fc215ef0b2 add function ghttp.RouterGroup.ALLMap 2020-11-24 21:19:01 +08:00
65c67427d4 compatible with Case of https://github.com/pkg/errors 2020-11-23 16:32:57 +08:00
bc8142974f version update 2020-11-21 14:08:29 +08:00
fadb7a8f8f fix issue 1002 2020-11-21 13:20:32 +08:00
e1bfe90833 improve package gdb for structire retrieving in transaction operations 2020-11-21 12:24:32 +08:00
042dc0b33f improve package gdb for structire retrieving in transaction operations 2020-11-21 11:42:50 +08:00
8ca535dbf0 fix issue in session cookie feature 2020-11-21 11:18:21 +08:00
e20183e7a1 improve gutil.Keys/Values for embedded struct 2020-11-20 00:53:12 +08:00
df86ffb61e improve package glog for nil file pointer 2020-11-19 20:43:44 +08:00
bfcf133c91 improve sqlite.Open 2020-11-19 20:37:27 +08:00
24a377d3a8 fix issue in custom mapping for gconv.Struct 2020-11-19 14:52:19 +08:00
baf51bc68f merge develop 2020-11-18 10:57:14 +08:00
5f4b585164 version updates 2020-11-18 10:52:53 +08:00
17a11187b0 fix issue in gdb.Model.Fields 2020-11-18 00:32:09 +08:00
7caf7976cf add gdb.Model.Args 2020-11-18 00:16:34 +08:00
1a31792c14 add SpecialCharsMapOrStruct for package ghtml 2020-11-15 16:49:44 +08:00
d56eb49e41 improve performance for gconv.Struct/Structs using directly reflect set 2020-11-15 15:13:40 +08:00
a1236b5e16 donator updates 2020-11-15 11:08:26 +08:00
8f278be0dc improve build-in varables for view of ghttp 2020-11-14 10:24:06 +08:00
f8ab71e7f0 Merge branch 'master' of https://github.com/gogf/gf 2020-11-12 20:52:23 +08:00
9cca3a3ec1 version updates 2020-11-12 20:51:04 +08:00
97bcf2a438 Merge pull request #980 from lutherlau/patch-4
Update garray_sorted_str.go
2020-11-12 20:44:48 +08:00
85dd2e9f4f Merge pull request #978 from lutherlau/patch-1
Update gtimer_timer.go
2020-11-12 20:39:57 +08:00
23d62da87f Merge pull request #981 from lutherlau/patch-3
Update garray_sorted_int.go
2020-11-12 20:39:44 +08:00
b4d947fecd Merge pull request #979 from lutherlau/patch-2
Update garray_sorted_any.go
2020-11-12 20:39:17 +08:00
650c95af31 Merge pull request #982 from lutherlau/patch-5
Update gtree_btree.go
2020-11-12 20:38:51 +08:00
943116d495 appaned parameters to url if http method is GET for ghttp.Client 2020-11-12 20:09:05 +08:00
644df7c16e Merge branch 'master' of https://github.com/gogf/gf 2020-11-12 18:58:05 +08:00
638773b216 improve client dump for package ghttp 2020-11-12 18:57:18 +08:00
889e7914e2 Merge pull request #983 from coolhihi/master
Update gjson_api_new_load.go to make gjson work when sometimes the xml end with space or `\n`
2020-11-12 18:41:36 +08:00
68cc85f2b2 improve file extension handling for package glog 2020-11-12 18:38:58 +08:00
c273ce576b add default configuration for MaxOpenConnCount for package gdb 2020-11-10 21:19:15 +08:00
f8d57096a8 add slice parameter support for gdb.Model.Where 2020-11-10 13:43:12 +08:00
d7542e87ae improve gdb.Model.Count 2020-11-10 10:37:42 +08:00
b178210a31 fix issue in gconv.Map for embedded struct attributes converting 2020-11-10 09:53:12 +08:00
3e6a23b0e1 add more unit testing case for package gview 2020-11-08 18:01:09 +08:00
ee8d2afe58 add build-in function map/maps for package gview 2020-11-08 17:11:04 +08:00
11e102e137 fix issue in internal/structs.MapField 2020-11-08 16:21:09 +08:00
e06b62ecf2 add default value from struct tag for ghttp.Request 2020-11-08 15:44:04 +08:00
d178102f82 remove third party package 'structs'; improve performance for package internal/structs 2020-11-08 14:25:17 +08:00
e1dd5cce7d improve performance for gconv.Struct 2020-11-08 00:06:05 +08:00
1edc1f35fb add individual cache for package gdb/gfile 2020-11-07 20:00:35 +08:00
4df47be521 Make it work when the xml content end with \s* 2020-11-07 18:32:50 +08:00
9cb88bca5a improve fields quoting for gdb.Model 2020-11-06 21:32:10 +08:00
fa8cc1d3f4 improve gutil.Keys 2020-11-06 21:21:09 +08:00
9ae8a7ca33 improve Fields/FieldsEx by adding support for map/struct parameters for gdb.Model 2020-11-06 20:52:16 +08:00
f4da179140 there should be WHERE statement in Update/Delete operations 2020-11-06 00:00:41 +08:00
13330658cb add function Keys/Values for package gutil 2020-11-05 23:32:56 +08:00
9f04e46166 mark gconv.StructDeep/StructsDeep deprecated 2020-11-05 23:02:29 +08:00
1072ea3fb0 Update gtree_btree.go
bugfix: avoid overflow
2020-11-05 22:44:09 +08:00
ea9e8055a4 Update garray_sorted_str.go
bugfix: avoid overflow
2020-11-05 22:42:15 +08:00
784abf2a30 Update garray_sorted_int.go
bugfix: avoid overflow
2020-11-05 22:41:43 +08:00
ed4a70deff Update garray_sorted_any.go
bugfix: avoid overflow
2020-11-05 22:37:36 +08:00
fef20d10a2 Update gtimer_timer.go
bugfix: avoid overflow
2020-11-05 22:33:33 +08:00
176dcdc7cc fix issue in gconv.Struct for json string parameter 2020-11-05 22:19:34 +08:00
12ed05f846 improva package internal/empty by adding more common types assertion 2020-11-05 20:40:34 +08:00
5999f22f76 fix issue in embeded struct validation for package gvalid 2020-11-04 20:04:54 +08:00
c056fd2a06 improve word quoting for function FieldsEx 2020-11-04 19:53:50 +08:00
cb422f043e improve slice converting for package gconv 2020-10-30 22:26:26 +08:00
4ae89dc9f6 improve package internal/structs 2020-10-30 22:04:34 +08:00
4f6f07db1d temporaryly remove guid.N 2020-10-30 14:31:31 +08:00
cd981c7294 improve snowflake number generating for package guid 2020-10-30 12:47:39 +08:00
557d2967fa improve snowflake number generating for package guid 2020-10-30 12:40:35 +08:00
a7a70636dd improve snowflake number generating for package guid 2020-10-30 12:33:56 +08:00
2215661f89 comment update for package guid 2020-10-30 12:24:18 +08:00
a22b590b43 comment update for package guid 2020-10-30 12:22:09 +08:00
1b0b209662 add unique number generating for package guid 2020-10-30 12:11:21 +08:00
2a2761c54f donator updates 2020-10-29 21:11:52 +08:00
1c83d72f39 donator updates 2020-10-28 23:54:40 +08:00
fcea774b59 donator updates 2020-10-28 23:34:19 +08:00
da2ec21571 fix issue 2020-10-27 10:41:18 +08:00
ebb8d8a2f7 fix issue in 2020-10-27 10:40:47 +08:00
9706a9c768 version updates 2020-10-26 21:20:34 +08:00
cee67a8d4e improve urlencoding handling for parameters posted along with file uploading 2020-10-26 20:21:09 +08:00
87650557fd remove debugging codes from package gtime 2020-10-26 19:06:27 +08:00
d3bf52f12f fix issue in unit testing case for package gi18n 2020-10-26 19:00:11 +08:00
6b13a4849b improve package gi18n 2020-10-25 17:33:14 +08:00
8e380c0d9d improve package gtime/gconv for map converting 2020-10-25 11:33:30 +08:00
0caf4bfcec improve gconv.StructDeep 2020-10-25 10:47:47 +08:00
9c3b978b50 improve package ghttp and internal/structs 2020-10-22 15:16:31 +08:00
ab689a7792 improve gutil.Dump 2020-10-22 09:24:57 +08:00
846646d92d improve json validation rule for package gvalid 2020-10-22 09:11:38 +08:00
2eb2b89432 improve gconv.Struct* by doing the converting using json.Unmarshal if given params is json string/bytes 2020-10-21 14:09:16 +08:00
43441a8218 allow custom validation rule validate empty or nil values 2020-10-21 00:08:36 +08:00
561a541fa1 add custom CreatedAt/UpdatedAt/DeletedAt filed name configuration for package gdb 2020-10-20 21:01:39 +08:00
ffe9ecc141 improve package internal/empty 2020-10-20 14:07:01 +08:00
77f7884604 add function gutil.Try/g.Try;improve error string for gconv.Struct 2020-10-20 13:36:43 +08:00
0a203d1e22 fix issue in struct converting for ghttp.Request 2020-10-19 11:29:41 +08:00
f4f4550483 improve package gerror 2020-10-18 20:18:55 +08:00
e87226a092 improve package gerror 2020-10-18 11:29:09 +08:00
391a3ec9bd version update 2020-10-18 11:26:19 +08:00
dd5cd31ef5 add automatic data key to field name mapping feature for package gdb 2020-10-17 18:16:13 +08:00
de92e804fe Merge branch 'master' of https://github.com/gogf/gf 2020-10-17 17:17:27 +08:00
7725d9aaaf add Current/Next function for package gerror 2020-10-17 17:17:10 +08:00
bd3e25adea Merge pull request #946 from yuancjun/patch-1
Update ghttp_server_config.go
2020-10-15 20:41:07 +08:00
0b1d49af4b upgrade fsnotify to v1.4.9 2020-10-15 20:14:35 +08:00
7efa9e351e remove default initialization function and add lazy initialization feature for package gfsnotify 2020-10-15 20:07:21 +08:00
0b0141954b Update ghttp_server_config.go 2020-10-15 18:04:32 +08:00
82b531fbfb add domain support for domain of ghttp.Server 2020-10-14 21:18:11 +08:00
67fb626339 add configuration SessionCookieOutput for ghttp.Server 2020-10-14 20:52:26 +08:00
9044d5f05d fix issue in custom session id lost for ghttp.Request 2020-10-13 20:34:31 +08:00
47663aa1f1 fix issue of EOF in ghttp.Request when no data posted using form-data 2020-10-13 20:12:54 +08:00
63c0aab19c improve package gtime 2020-10-12 23:32:32 +08:00
261216f5e4 improve structure sql for package gdb 2020-10-12 23:22:56 +08:00
f88b799d67 add more unit testing case for package gdb 2020-10-11 22:21:03 +08:00
9c48dd172c add auto-json support for slice/struct attribute for data inserting of package gdb 2020-10-10 17:29:38 +08:00
09ce105eee improve gdb.Model.Fields/FieldsEx for package gdb 2020-10-10 14:00:10 +08:00
651aa895f8 improve function addWordBoundariesToNumbers for package gstr 2020-10-10 13:38:28 +08:00
0509e41198 recover number for unit testing case of package gconv 2020-10-10 00:04:55 +08:00
1b40d6a53a do tx.Rollback if there's panic in gdb.Transaction 2020-10-09 23:42:33 +08:00
f9189d48d1 remove type ValueFunc from package gcache 2020-10-09 23:36:39 +08:00
849874a247 improve adapter definition for package gcache 2020-10-09 20:59:49 +08:00
3f6510bae7 fix issue in gconv.Struct 2020-09-29 23:47:37 +08:00
a585a26c39 improve custom rule function type for package gvalid 2020-09-29 23:25:20 +08:00
9943966a86 improva function formatSql for package gdb 2020-09-29 22:53:44 +08:00
3617e51c01 improve gdb.Model.ScanList 2020-09-28 23:52:02 +08:00
c931032f08 comment update for gdb.Model.Unscoped 2020-09-27 23:37:40 +08:00
37b286eaa4 improve performance for gconv.Maps;comment update for package gcache 2020-09-27 22:35:02 +08:00
619287c273 improve cache feature for package gdb 2020-09-27 00:15:11 +08:00
aeb9b68298 improve adapter feature for package gcache 2020-09-26 22:44:07 +08:00
bae8f6315b comment updat 2020-09-26 21:13:09 +08:00
a6f70f8935 comment updat 2020-09-26 21:00:28 +08:00
acca6f4009 add adapter feature for package gcache 2020-09-26 20:47:29 +08:00
727fdd2391 improve unit testing case for association feature for package gdb 2020-09-25 08:33:22 +08:00
1d174e00c0 improve package internal/intlog 2020-09-24 23:46:19 +08:00
a5e3e2f5ba change g.SetDebug function to control the debugging information for framework 2020-09-24 23:40:44 +08:00
da43c2d52f improve example for package gview 2020-09-22 20:12:34 +08:00
262f27748c improve custom validation rule feature for package gvalid 2020-09-22 08:45:22 +08:00
a29aef7e1c improve package gvalid for custom rule 2020-09-21 23:51:30 +08:00
1671391195 comment updates 2020-09-21 22:56:48 +08:00
28b0d59c61 improve package gcfg/gjson for data loading 2020-09-21 21:30:58 +08:00
86433cef25 improve gtime.New, gconv.String 2020-09-18 23:59:49 +08:00
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
334 changed files with 9649 additions and 3996 deletions

View File

@ -9,7 +9,6 @@ package driver
import (
"database/sql"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/gtime"
)
@ -51,14 +50,13 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
tsMilli := gtime.TimestampMilli()
rows, err = d.DriverMysql.DoQuery(link, sql, args...)
if _, err := d.DriverMysql.InsertIgnore("monitor", g.Map{
"sql": gdb.FormatSqlWithArgs(sql, args),
"cost": gtime.TimestampMilli() - tsMilli,
"time": gtime.Now(),
"error": err.Error(),
}); err != nil {
panic(err)
}
link.Exec(
"INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
gdb.FormatSqlWithArgs(sql, args),
gtime.TimestampMilli()-tsMilli,
gtime.Now(),
err,
)
return
}
@ -67,13 +65,12 @@ func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows
func (d *MyDriver) DoExec(link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
tsMilli := gtime.TimestampMilli()
result, err = d.DriverMysql.DoExec(link, sql, args...)
if _, err := d.DriverMysql.InsertIgnore("monitor", g.Map{
"sql": gdb.FormatSqlWithArgs(sql, args),
"cost": gtime.TimestampMilli() - tsMilli,
"time": gtime.Now(),
"error": err.Error(),
}); err != nil {
panic(err)
}
link.Exec(
"INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
gdb.FormatSqlWithArgs(sql, args),
gtime.TimestampMilli()-tsMilli,
gtime.Now(),
err,
)
return
}

View File

@ -1,10 +1,15 @@
# MySQL数据库配置
# MySQL.
[database]
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true"
MaxOpen = 100
# Redis.
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
#[database]
# [[database.default]]
# type = "mysql"

View File

@ -3,6 +3,7 @@ package main
import (
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/util/gutil"
"time"
)
func main() {
@ -10,7 +11,7 @@ func main() {
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pass: "123456",
Pass: "12345678",
Name: "test",
Type: "mysql",
Role: "master",
@ -20,19 +21,20 @@ func main() {
if err != nil {
panic(err)
}
//db.GetCache().SetAdapter(adapter.NewRedis(g.Redis()))
// 开启调试模式以便于记录所有执行的SQL
db.SetDebug(true)
// 执行2次查询并将查询结果缓存3秒并可执行缓存名称(可选)
for i := 0; i < 2; i++ {
r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One()
gutil.Dump(r.ToMap())
for i := 0; i < 3; i++ {
r, _ := db.Table("user").Cache(3000*time.Second).Where("id=?", 1).One()
gutil.Dump(r.Map())
}
// 执行更新操作,并清理指定名称的查询缓存
db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("uid=?", 1).Update()
//db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("id=?", 1).Update()
// 再次执行查询,启用查询缓存特性
r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One()
gutil.Dump(r.ToMap())
//r, _ := db.Table("user").Cache(300000*time.Second, "vip-user").Where("id=?", 1).One()
//gutil.Dump(r.Map())
}

View File

@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/gogf/gf/database/gdb"
"time"
"github.com/gogf/gf/frame/g"
)
func main() {
@ -18,10 +18,9 @@ func main() {
db.SetDebug(true)
type User struct {
CreateTime time.Time `orm:"create_time"`
}
r, e := db.Table("user").Data(User{CreateTime: time.Now()}).Insert()
r, e := db.Table("user").Data(g.Map{
"create_at": "now()",
}).Unscoped().Insert()
if e != nil {
panic(e)
}

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

@ -1,12 +1,15 @@
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
db := g.DB()
db.SetDebug(true)
db.Table("user").Data("num=num+1").Where("id", 8).Update()
one, err := g.DB().Table("carlist c").
LeftJoin("cardetail d", "c.postid=d.carid").
Where("c.postid", "142039140032006").
Fields("c.*,d.*").One()
fmt.Println(err)
g.Dump(one)
}

View File

@ -2,25 +2,14 @@ package main
import (
"fmt"
"github.com/gogf/gf/i18n/gi18n"
"github.com/gogf/gf/frame/g"
)
func main() {
t := gi18n.New()
t.SetPath("/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/i18n/gi18n/i18n")
t.SetLanguage("en")
fmt.Println(t.Translate(`hello`))
fmt.Println(t.Translate(`{#hello}{#world}!`))
t.SetLanguage("ja")
fmt.Println(t.Translate(`hello`))
fmt.Println(t.Translate(`{#hello}{#world}!`))
t.SetLanguage("ru")
fmt.Println(t.Translate(`hello`))
fmt.Println(t.Translate(`{#hello}{#world}!`))
fmt.Println(t.Translate(`hello`, "zh-CN"))
fmt.Println(t.Translate(`{#hello}{#world}!`, "zh-CN"))
var (
orderId = 865271654
orderAmount = 99.8
)
fmt.Println(g.I18n().Tfl(`en`, `{#OrderPaid}`, orderId, orderAmount))
fmt.Println(g.I18n().Tfl(`zh-CN`, `{#OrderPaid}`, orderId, orderAmount))
}

View File

@ -1,3 +1 @@
hello = "Hello"
world = "World"
OrderPaid = "You have successfully complete order #%d payment, paid amount: ¥%0.2f."

View File

@ -1,2 +1 @@
hello = "你好"
world = "世界"
OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。"

View File

@ -15,10 +15,10 @@ func main() {
r.Response.Writeln("end")
})
s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) {
ghttp.HookBeforeServe: func(r *ghttp.Request) {
glog.To(r.Response.Writer).Println("BeforeServe")
},
ghttp.HOOK_AFTER_SERVE: func(r *ghttp.Request) {
ghttp.HookAfterServe: func(r *ghttp.Request) {
glog.To(r.Response.Writer).Println("AfterServe")
},
})

View File

@ -3,15 +3,21 @@ package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/glog"
)
type GetById struct {
Id *g.Var `p:"id" v:"required|integer#id不能为空|id必须为整数"`
}
func main() {
s := g.Server()
s.SetIndexFolder(true)
s.BindHandler("/", func(r *ghttp.Request) {
glog.Println(r.Header)
r.Response.Write("hello world")
var idInfo *GetById
if err := r.Parse(&idInfo); err != nil {
r.Response.Write(err)
}
r.Response.Write("ok")
})
s.SetPort(8999)
s.Run()

View File

@ -12,7 +12,7 @@ func Order(r *ghttp.Request) {
func main() {
s := g.Server()
s.Group("/api.v1", func(group *ghttp.RouterGroup) {
group.Hook("/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
group.Hook("/*any", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.CORSDefault()
})
g.GET("/order", Order)

View File

@ -11,10 +11,10 @@ func main() {
p := "/:name/info/{uid}"
s := g.Server()
s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) { glog.Println(ghttp.HOOK_BEFORE_SERVE) },
ghttp.HOOK_AFTER_SERVE: func(r *ghttp.Request) { glog.Println(ghttp.HOOK_AFTER_SERVE) },
ghttp.HOOK_BEFORE_OUTPUT: func(r *ghttp.Request) { glog.Println(ghttp.HOOK_BEFORE_OUTPUT) },
ghttp.HOOK_AFTER_OUTPUT: func(r *ghttp.Request) { glog.Println(ghttp.HOOK_AFTER_OUTPUT) },
ghttp.HookBeforeServe: func(r *ghttp.Request) { glog.Println(ghttp.HookBeforeServe) },
ghttp.HookAfterServe: func(r *ghttp.Request) { glog.Println(ghttp.HookAfterServe) },
ghttp.HookBeforeOutput: func(r *ghttp.Request) { glog.Println(ghttp.HookBeforeOutput) },
ghttp.HookAfterOutput: func(r *ghttp.Request) { glog.Println(ghttp.HookAfterOutput) },
})
s.BindHandler(p, func(r *ghttp.Request) {
r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid"))

View File

@ -11,7 +11,7 @@ func main() {
// 多事件回调示例事件1
pattern1 := "/:name/info"
s.BindHookHandlerByMap(pattern1, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) {
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.SetParam("uid", 1000)
},
})
@ -22,7 +22,7 @@ func main() {
// 多事件回调示例事件2
pattern2 := "/{object}/list/{page}.java"
s.BindHookHandlerByMap(pattern2, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_OUTPUT: func(r *ghttp.Request) {
ghttp.HookBeforeOutput: func(r *ghttp.Request) {
r.Response.SetBuffer([]byte(
fmt.Sprintf("通过事件修改输出内容, object:%s, page:%s", r.Get("object"), r.GetRouterString("page"))),
)

View File

@ -7,10 +7,10 @@ import (
func main() {
s := g.Server()
s.BindHookHandler("/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
s.BindHookHandler("/*any", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Writeln("/*any")
})
s.BindHookHandler("/v1/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
s.BindHookHandler("/v1/*", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Writeln("/v1/*")
r.ExitHook()
})

View File

@ -11,7 +11,7 @@ func main() {
r.Response.Writeln(r.Get("name"))
})
s.BindHookHandlerByMap("/", map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) {
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.SetParam("name", "john")
},
})

View File

@ -12,17 +12,17 @@ func main() {
})
s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) {
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.Response.Writeln("/priority/:name")
},
})
s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) {
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.Response.Writeln("/priority/*any")
},
})
s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) {
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.Response.Writeln("/priority/show")
},
})

View File

@ -27,10 +27,10 @@ func main() {
})
})
group.Group("/hook", func(group *ghttp.RouterGroup) {
group.Hook("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
group.Hook("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook any")
})
group.Hook("/:name", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
group.Hook("/:name", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook name")
})
})

View File

@ -1,12 +1,13 @@
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := ghttp.GetServer()
s.EnablePProf()
s := g.Server()
s.Domain("localhost").EnablePProf()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Writeln("哈喽世界!")
})

View File

@ -18,7 +18,7 @@ func main() {
s := g.Server()
s.SetIndexFolder(true)
s.SetServerRoot("root")
s.BindHookHandler("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
s.BindHookHandler("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) {
fmt.Println(r.URL.Path, r.IsFileRequest())
})
s.BindHandler("/template", func(r *ghttp.Request) {

View File

@ -27,7 +27,7 @@ func main() {
s := g.Server()
obj := new(Object)
s.Group("/api").Bind([]ghttp.GroupItem{
{"ALL", "*", HookHandler, ghttp.HOOK_BEFORE_SERVE},
{"ALL", "*", HookHandler, ghttp.HookBeforeServe},
{"ALL", "/handler", Handler},
{"ALL", "/obj", obj},
{"GET", "/obj/show", obj, "Show"},

View File

@ -54,10 +54,10 @@ func main() {
})
})
group.Group("/hook", func(group *ghttp.RouterGroup) {
group.Hook("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
group.Hook("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook any")
})
group.Hook("/:name", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
group.Hook("/:name", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook name")
})
})

View File

@ -9,7 +9,7 @@ import (
func main() {
s := g.Server()
s.BindHookHandler("/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
s.BindHookHandler("/*any", ghttp.HookBeforeServe, func(r *ghttp.Request) {
fmt.Println(r.Router)
fmt.Println(r.Get("customer_id"))
})

View File

@ -13,7 +13,7 @@ func main() {
key := "key"
// 第一次锁带时间
gmlock.Lock(key, 1000)
gmlock.Lock(key)
glog.Println("lock1")
// 这个时候上一次的计时解锁已失效
gmlock.Unlock(key)

View File

@ -2,19 +2,18 @@ package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/frame/gmvc"
"github.com/gogf/gf/net/ghttp"
)
type Controller struct {
gmvc.Controller
}
func (c *Controller) Test() {
c.View.Display("layout.html")
}
func main() {
s := g.Server()
s.BindControllerMethod("/", new(Controller), "Test")
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.WriteTpl("layout.html", g.Map{
"header": "This is header",
"container": "This is container",
"footer": "This is footer",
})
})
s.SetPort(8199)
s.Run()
}

View File

@ -1,3 +1,3 @@
{{define "container"}}
<h1>CONTAINER</h1>
<h1>{{.container}}</h1>
{{end}}

View File

@ -1,3 +1,3 @@
{{define "footer"}}
<h1>FOOTER</h1>
<h1>{{.footer}}</h1>
{{end}}

View File

@ -1,3 +1,3 @@
{{define "header"}}
<h1>HEADER</h1>
<h1>{{.header}}</h1>
{{end}}

View File

@ -2,14 +2,14 @@
<html>
<head>
<title>GoFrame Layout</title>
{{template "header"}}
{{template "header" .}}
</head>
<body>
<div class="container">
{{template "container"}}
{{template "container" .}}
</div>
<div class="footer">
{{template "footer"}}
{{template "footer" .}}
</div>
</body>
</html>

View File

@ -9,11 +9,13 @@ func main() {
s := g.Server()
s.BindHandler("/main1", func(r *ghttp.Request) {
r.Response.WriteTpl("layout.html", g.Map{
"name": "smith",
"mainTpl": "main/main1.html",
})
})
s.BindHandler("/main2", func(r *ghttp.Request) {
r.Response.WriteTpl("layout.html", g.Map{
"name": "john",
"mainTpl": "main/main2.html",
})
})

View File

@ -11,7 +11,7 @@ func main() {
fmt.Println(1)
gutil.Throw("error")
fmt.Println(2)
}, func(err interface{}) {
}, func(err error) {
fmt.Println(err)
})
}

View File

@ -115,6 +115,32 @@ 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|
|阿康|wechat|¥100.00|
|Tzp|wechat|¥10.00|
|[hkxiaoyu118](https://github.com/hkxiaoyu118)|wechat|¥10.00|
|辰|wechat|¥50.00|
|LSJ|wechat|¥66.66|我想我是海祝gf越来越好统治后端
|yu|wechat|¥100.00|感谢开源加油我是QQ群里的lah
|雁字回时月满楼|wechat|¥20.00|感谢gf
|Panda|wechat|¥20.00|支持一下gf很棒👍
|[Thunur](https://gitee.com/thunur)|wechat|¥100.00|
|[Mr.奇淼](https://www.gin-vue-admin.com/)|wechat|¥18.88|强哥无敌,奇淼爱你
|[SliverHorn](hhttps://github.com/sliverhorn)|wechat|¥17.77|强哥无敌SliverHorn爱你
|[fly的狐狸](https://github.com/zcool321)|wechat|¥50.00|
|北漂生活|wechat|¥66.66|gf大展鸿图
|YJ|wechat|¥10.00|YangJ-Eric祝愿越来越好
|秋葵|wechat|¥20.00|之前强哥
|陈诚|wechat|¥100.00|Loocor恭喜郭总发版🎉
|**栋|alipay|¥100.00|
|**浩|alipay|¥100.00|
|RAGGA-TIME|alipay|¥50.00|
|[ChArmy](https://gitee.com/charmy)|alipay|¥50.00|
|[sanfenzui](https://gitee.com/sanfenzui)|alipay|¥88.00|
|刘宇|wechat|¥30.00|请你喝咖啡

View File

@ -125,6 +125,7 @@ The concurrency starts from `100` to `10000`.
- [LeYouJia](https://www.leyoujia.com/)
- [IGG](https://igg.com)
- [XiMaLaYa](https://www.ximalaya.com)
- [ZYBang](https://www.zybang.com/)
> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://github.com/gogf/gf/issues/168).

View File

@ -145,6 +145,7 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
- [乐有家](https://www.leyoujia.com/)
- [IGG](https://igg.com)
- [喜马拉雅](https://www.ximalaya.com)
- [作业帮](https://www.zybang.com/)
> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://github.com/gogf/gf/issues/168) 留言。

View File

@ -1,3 +1,177 @@
# `v1.14.2` (2020-10-27)
# GoFrame
`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链提供了常用的基础开发模块缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件`Router`、`Cookie`、`Session`、`Middleware`、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、`TLS/HTTPS`、`Rewrite`等特性。
## 特点
* 模块化、松耦合设计;
* 模块丰富、开箱即用;
* 简便易用、易于维护;
* 高代码质量、高单元测试覆盖率;
* 社区活跃,大牛谦逊低调脾气好;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 设计为团队及企业使用;
## 支持我们
OSC最佳开源项目评选开始了如果您喜欢`GoFrame`,欢迎为`GoFrame`投上您宝贵的一票🙏 https://www.oschina.net/p/goframe
# Change Log
由于`GoFrame`是模块化设计,因此每个版本的更新记录都会以模块的形式进行介绍。
重要更新:
1. 将框架内所有的`json`操作从标准库替换为`json-iterator/go`,提高操作效率。
1. 缓存模块重构底层设计,增加适配器设计模式,并增加内存及`Redis`适配器支持。其中内存适配器默认核心模块提供,`Redis`适配器由社区模块提供https://goframe.org/os/gcache/adapter
1. 增加可自定义的校验规则注册特性https://goframe.org/util/gvalid/customrule
1. `Web Server`增加所有配置项示例https://goframe.org/net/ghttp/config/example
1. `ORM`新增基于`Redis`的`SQL`缓存适配器https://goframe.org/database/gdb/model/cache
1. `ORM`新增模型关联实验特性https://goframe.org/database/gdb/model/association
1. `ORM`改进时间自动更新特性增加自定义时间字段https://goframe.org/database/gdb/model/auto-time
1. 错误处理模块新增`Current`及`Next`方法https://goframe.org/errors/gerror/index
## `net`
1. `ghttp`
- `Client`
- 增加`GetVar/PutVar/PostVar`等`*Var`请求方法,用于发起`HTTP`请求获取内容之后直接返回泛型对象,方便类型转换,特别是针对于返回`XML/JSON`的结果处理将会更加简便https://goframe.org/net/ghttp/client/demo/index
- 增加`SetProxy/Proxy`方法,用于设置客户端代理,支持`HTTP/Socket5`代理类型https://goframe.org/net/ghttp/client/demo/proxy
- 增加`SetRedirectLimit/RedirectLimit`方法,用于设置页面跳转数量限制。
- `Request`
- 增加`ParseQuery`, `ParseForm`方法,用于解析指定类型的参数,并绑定到给定的对象。
- 增加`GetHeader`方法,用于获取指定`Header`参数。
- 增加`GetRemoteIp`方法用于获取请求客户端IP。在IP白名单限制时应当使用`GetRemoteIp`而不是`GetClientIp`进行判断,后者可以通过`Header`伪造。
- 增加`ReloadParam`方法,往往用在中间件处理中,当中间件修改了请求参数,需要通过调用该方法重新解析一下请求参数。
- 增加`GetRouterMap`方法,用于获得所有的路由参数返回为`map`。
- `Response`
- 将`Output`方法名称改为`Flush`,用于将缓冲区的数据写入到客户端数据流中。
- `Server`
- `Server`增加所有配置项示例https://goframe.org/net/ghttp/config/example
- 增加`SessionCookieOutput`配置,用于控制是否输出`SessionId`到`Cookie`中,默认为开启。
- 改进路由解析,增加对`URI`带有重复的`/`符号的支持。
- `Pprof`功能路由支持`Domain`绑定。
- 其他一些细节改进。
- `Cookie`
- 增加`SetHttpCookie`方法,用于根据标准库`http.Cookie`对象设置`Cookie`。
- 其他一些功能改进
## `database`
1. `gdb`
- 新增模型关联实验特性https://goframe.org/database/gdb/model/association
- 改进时间自动更新特性增加自定义时间字段https://goframe.org/database/gdb/model/auto-time
- 新增基于`Redis`的`SQL`缓存适配器https://goframe.org/database/gdb/model/cache
- 新增对输入参数的键名-字段名自动识别映射特性https://goframe.org/database/gdb/senior
- 新增`DB.HasTable`方法,用于判断是否当前数据库存在指定数据表。
- 新增`Model.HasField`方法,用于判断是否当前数据表存在指定字段。
- 新增`Model.ScanList`方法,用于智能地将当前`struct`/`slice`绑定到指定的`list`对应属性上。
- 新增`Result.MapKeyValue`方法,用于将当前`Result`转换为`map[string]Value`类型。
- 新增`Result.IsEmpty/Len/Size/ScanList`方法。
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
- `SQL`日志内容增加分组名称打印。
- 改进`DataToMapDeep`方法。
- 其他一些细节改进工作。
1. `gredis`
- 新增`TLS`特性支持并支持配置文件配置https://goframe.org/database/gredis/config
## `container`
1. `gvar`
- 增加`Scan`及`ScanDeep`方法,用于`struct`/`slice`自动识别转换。
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
- 增加`MapStrAny`接口实现方法。
## `os`
1. `gcache`
- 增加`GetVar`方法,用于获取缓存数据并返回为泛型对象。
- 增加`Update`方法,用于仅修改缓存数值,不修改缓存过期时间。
- 增加`UpdateExpire`方法,用于仅修改缓存过期时间,不修改缓存数值。
- 重构底层设计,增加适配器设计模式,并增加内存及`Redis`适配器支持。其中内存适配器默认核心模块提供,`Redis`适配器由社区模块提供https://goframe.org/os/gcache/adapter
- 注意,本次模块的修改会有部分方法不兼容,部分方法增加了`error`参数返回,升级时请注意查看。编译时将不会通过。
- 其他一些功能改进。
1. `gfile`
- 增加`ScanDirFileFunc`方法,用于自定义函数处理的递归目录文件遍历。
- 改进`Scan*`方法,增加递归层级限制,默认层级限制为`100000`.
1. `gfsnotify`
- 去掉模块初始化时的`Watcher`对象创建,调整为运行时按需创建,并且增加了并发安全控制。
1. `grpool`
- 增加`AddWithRecover`方法,用于添加异步任务时给定一个`recover`处理方法,当任务`panic`时交由该`recover`方法处理,防止异步任务`panic`引起整个进程崩溃。
> 这里解决的痛点是`recover`只能捕获到当前`goroutine`的`panic`,因此只能在创建异步任务的时候指定`recover`处理方法。
1. `gtime`
- 增加`ParseDuration`方法,增加了对时间单位`d`的支持,表示天。
- 改进`New`方法,支持通过字符串、时间戳、`time.Time`对象创建`gtime.Time`对象https://goframe.org/os/gtime/time
- 改进`Add/AddStr/ToLocation/ToZone/UTCLocal/AddDate/Truncate/Round`方法,这些方法调用时,不再修改当前对象本身,而是创建并返回一个新的`gtine.Time`对象,以便保证和标准库`time.Time`的逻辑一致,防止混淆。
- 其他一些细节改进。
1. `gtimer`
- 增加`Reset`方法,用于重置定时任务的计时。
1. `gfcache`
- 去掉了该模块,该模块的功能作用不是特别大。
## `debug`
1. `gdebug`
- 新增`GoroutineId`方法,用于获取当前执行的`goroutine id`,仅作调试使用。
## `encoding`
1. `gjson`
- 新增`GetScan/GetScanDeep`方法。
- 新增`ToScan/ToScanDeep`方法。
- 新增`LoadContentType`方法,用于根据指定类型的内容创建`Json`操作对象。
- 新增`IsValidDataType`方法,用于判断给定的数据类型是否支持解析。
- 其他一些改进。
- 单元测试完善。
1. `gcompress`
- 新增`GzipFile/UnGzipFile`基于`gzip`压缩算法的文件压缩/解压。
## `i18n`
1. `gi18n`
- 新增`TranslateFormat/TranslateFormatLang/Tf/Tfl`方法: https://goframe.org/i18n/gi18n/index
## `text`
1. `gstr`
- 增加`SnakeFirstUpperCase`方法,用于在字母大写前增加连接符,并不会处理数字,例如:`SnakeFirstUpperCase("RGBCodeMd5")`将会返回`rgb_code_md5`。
## `util`
1. `gconv`
- 增加对指针基本类型的转换支持。
- 增加`Scan/ScanDeep`方法,用于自动识别转换`Struct/[]Struct`。
- 改进`MapDeep`方法的层级递归处理。
- 其他一些细节改进,性能改进。
1. `gutil`
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
- 增加`ItemValue`方法,用于获取指定`map/*map/struct/*struct`类型的键值/属性值。
- 增加`MapOmitEmpty`方法,用于过滤`map`中的空值。
- 增加`SliceDelete`方法,用于数组项删除。
- 增加`Try`方法,通过闭包执行给定的方法,如果方法产生`panic`则该方法返回`error`,否则返回`nil`。
- 改进`TryCatch(try func(), catch ...func(exception interface{}))`为`TryCatch(try func(), catch ...func(exception error))`
1. `gvalid`
- 增加自定义规则特性开发者可注册自定义的校验规则https://goframe.org/util/gvalid/customrule
- 其他一些功能改进。
## `error`
1. `gerror`
- 新增`Current`方法,用于获取当前错误层级的`error`接口对象。
- 新增`Next`方法,用于获取层级错误的下一级错误`error`接口对象。当下一层级不存在时,返回`nil`。
- 文档更新https://goframe.org/errors/gerror/index
## Bug Fix
1. 修复`garray`模块的`Unique`方法问题。
1. 修复`glog`中定时器懒初始化时的`goroutine`泄露问题。
1. 修复`gstr`中名称`Case`转换相关方法在名称中带有数字+特殊字符时的名称转换问题。
1. 修复`ghttp`模块中的`CORS`跨域设置`Header`细节问题。
1. 其他BUG修复https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug+is%3Aclosed
# `v1.13.1` (2020-06-10)
# GoFrame

View File

@ -453,7 +453,7 @@ func (a *Array) Clone() (newArray *Array) {
array := make([]interface{}, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewArrayFrom(array, !a.mu.IsSafe())
return NewArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.

View File

@ -469,7 +469,7 @@ func (a *IntArray) Clone() (newArray *IntArray) {
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewIntArrayFrom(array, !a.mu.IsSafe())
return NewIntArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.

View File

@ -457,7 +457,7 @@ func (a *StrArray) Clone() (newArray *StrArray) {
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewStrArrayFrom(array, !a.mu.IsSafe())
return NewStrArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.

View File

@ -446,7 +446,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result
mid := 0
cmp := -2
for min <= max {
mid = (min + max) / 2
mid = min + int((max-min)/2)
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
@ -499,7 +499,7 @@ func (a *SortedArray) Clone() (newArray *SortedArray) {
array := make([]interface{}, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedArrayFrom(array, a.comparator, !a.mu.IsSafe())
return NewSortedArrayFrom(array, a.comparator, a.mu.IsSafe())
}
// Clear deletes all items of current array.

View File

@ -443,7 +443,7 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
mid := 0
cmp := -2
for min <= max {
mid = (min + max) / 2
mid = min + int((max-min)/2)
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
@ -496,7 +496,7 @@ func (a *SortedIntArray) Clone() (newArray *SortedIntArray) {
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedIntArrayFrom(array, !a.mu.IsSafe())
return NewSortedIntArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.

View File

@ -445,7 +445,7 @@ func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result i
mid := 0
cmp := -2
for min <= max {
mid = (min + max) / 2
mid = min + int((max-min)/2)
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
@ -498,7 +498,7 @@ func (a *SortedStrArray) Clone() (newArray *SortedStrArray) {
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedStrArrayFrom(array, !a.mu.IsSafe())
return NewSortedStrArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.

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

@ -56,7 +56,7 @@ func (m *IntAnyMap) Iterator(f func(k int, v interface{}) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *IntAnyMap) Clone() *IntAnyMap {
return NewIntAnyMapFrom(m.MapCopy(), !m.mu.IsSafe())
return NewIntAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@ -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

@ -54,7 +54,7 @@ func (m *IntIntMap) Iterator(f func(k int, v int) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *IntIntMap) Clone() *IntIntMap {
return NewIntIntMapFrom(m.MapCopy(), !m.mu.IsSafe())
return NewIntIntMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@ -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

@ -54,7 +54,7 @@ func (m *IntStrMap) Iterator(f func(k int, v string) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *IntStrMap) Clone() *IntStrMap {
return NewIntStrMapFrom(m.MapCopy(), !m.mu.IsSafe())
return NewIntStrMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@ -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

@ -56,7 +56,7 @@ func (m *StrAnyMap) Iterator(f func(k string, v interface{}) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *StrAnyMap) Clone() *StrAnyMap {
return NewStrAnyMapFrom(m.MapCopy(), !m.mu.IsSafe())
return NewStrAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@ -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

@ -54,7 +54,7 @@ func (m *StrIntMap) Iterator(f func(k string, v int) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *StrIntMap) Clone() *StrIntMap {
return NewStrIntMapFrom(m.MapCopy(), !m.mu.IsSafe())
return NewStrIntMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@ -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

@ -55,7 +55,7 @@ func (m *StrStrMap) Iterator(f func(k string, v string) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *StrStrMap) Clone() *StrStrMap {
return NewStrStrMapFrom(m.MapCopy(), !m.mu.IsSafe())
return NewStrStrMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@ -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

@ -1,4 +1,4 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -19,18 +19,10 @@ import (
// Pool is an Object-Reusable Pool.
type Pool struct {
// Available/idle items list.
list *glist.List
// Whether the pool is closed.
closed *gtype.Bool
// Time To Live for pool items.
TTL time.Duration
// Callback function to create pool item.
NewFunc func() (interface{}, error)
list *glist.List // Available/idle items list.
closed *gtype.Bool // Whether the pool is closed.
TTL time.Duration // Time To Live for pool items.
NewFunc func() (interface{}, error) // Callback function to create pool item.
// ExpireFunc is the for expired items destruction.
// This function needs to be defined when the pool items
// need to perform additional destruction operations.
@ -40,8 +32,8 @@ type Pool struct {
// Pool item.
type poolItem struct {
expire int64 // Expire timestamp in milliseconds.
value interface{} // Item value.
value interface{} // Item value.
expireAt int64 // Expire timestamp in milliseconds.
}
// Creation function for object.
@ -80,11 +72,11 @@ func (p *Pool) Put(value interface{}) error {
value: value,
}
if p.TTL == 0 {
item.expire = 0
item.expireAt = 0
} else {
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
// So we need calculate the milliseconds using its nanoseconds value.
item.expire = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
}
p.list.PushBack(item)
return nil
@ -112,8 +104,11 @@ func (p *Pool) Get() (interface{}, error) {
for !p.closed.Val() {
if r := p.list.PopFront(); r != nil {
f := r.(*poolItem)
if f.expire == 0 || f.expire > gtime.TimestampMilli() {
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
return f.value, nil
} else if p.ExpireFunc != nil {
// TODO: move expire function calling asynchronously from `Get` operation.
p.ExpireFunc(f.value)
}
} else {
break
@ -169,9 +164,9 @@ func (p *Pool) checkExpireItems() {
}
if r := p.list.PopFront(); r != nil {
item := r.(*poolItem)
latestExpire = item.expire
latestExpire = item.expireAt
// TODO improve the auto-expiration mechanism of the pool.
if item.expire > timestampMilli {
if item.expireAt > timestampMilli {
p.list.PushFront(item)
break
}

View File

@ -56,7 +56,7 @@ func NewAVLTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{
// Clone returns a new tree with a copy of current tree.
func (tree *AVLTree) Clone() *AVLTree {
newTree := NewAVLTree(tree.comparator, !tree.mu.IsSafe())
newTree := NewAVLTree(tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
@ -441,9 +441,9 @@ func (tree *AVLTree) MapStrAny() map[string]interface{} {
func (tree *AVLTree) Flip(comparator ...func(v1, v2 interface{}) int) {
t := (*AVLTree)(nil)
if len(comparator) > 0 {
t = NewAVLTree(comparator[0], !tree.mu.IsSafe())
t = NewAVLTree(comparator[0], tree.mu.IsSafe())
} else {
t = NewAVLTree(tree.comparator, !tree.mu.IsSafe())
t = NewAVLTree(tree.comparator, tree.mu.IsSafe())
}
tree.IteratorAsc(func(key, value interface{}) bool {
t.put(value, key, nil, &t.root)

View File

@ -68,7 +68,7 @@ func NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[inter
// Clone returns a new tree with a copy of current tree.
func (tree *BTree) Clone() *BTree {
newTree := NewBTree(tree.m, tree.comparator, !tree.mu.IsSafe())
newTree := NewBTree(tree.m, tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
@ -620,7 +620,7 @@ func (tree *BTree) middle() int {
func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bool) {
low, mid, high := 0, 0, len(node.Entries)-1
for low <= high {
mid = (high + low) / 2
mid = low + int((high-low)/2)
compare := tree.getComparator()(key, node.Entries[mid].Key)
switch {
case compare > 0:

View File

@ -83,7 +83,7 @@ func (tree *RedBlackTree) SetComparator(comparator func(a, b interface{}) int) {
// Clone returns a new tree with a copy of current tree.
func (tree *RedBlackTree) Clone() *RedBlackTree {
newTree := NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
newTree := NewRedBlackTree(tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
@ -656,9 +656,9 @@ func (tree *RedBlackTree) Search(key interface{}) (value interface{}, found bool
func (tree *RedBlackTree) Flip(comparator ...func(v1, v2 interface{}) int) {
t := (*RedBlackTree)(nil)
if len(comparator) > 0 {
t = NewRedBlackTree(comparator[0], !tree.mu.IsSafe())
t = NewRedBlackTree(comparator[0], tree.mu.IsSafe())
} else {
t = NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
t = NewRedBlackTree(tree.comparator, tree.mu.IsSafe())
}
tree.IteratorAsc(func(key, value interface{}) bool {
t.doSet(value, key)

View File

@ -1,4 +1,4 @@
// Copyright 2018-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -11,8 +11,6 @@ import (
"github.com/gogf/gf/internal/json"
"time"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gconv"
@ -88,16 +86,6 @@ func (v *Var) Interface() interface{} {
return v.Val()
}
// IsNil checks whether <v> is nil.
func (v *Var) IsNil() bool {
return v.Val() == nil
}
// IsEmpty checks whether <v> is empty.
func (v *Var) IsEmpty() bool {
return empty.IsEmpty(v.Val())
}
// Bytes converts and returns <v> as []byte.
func (v *Var) Bytes() []byte {
return gconv.Bytes(v.Val())

98
container/gvar/gvar_is.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright GoFrame 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 gvar
import (
"github.com/gogf/gf/internal/empty"
"reflect"
)
// IsNil checks whether <v> is nil.
func (v *Var) IsNil() bool {
return v.Val() == nil
}
// IsEmpty checks whether <v> is empty.
func (v *Var) IsEmpty() bool {
return empty.IsEmpty(v.Val())
}
// IsInt checks whether <v> is type of int.
func (v *Var) IsInt() bool {
switch v.Val().(type) {
case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64:
return true
}
return false
}
// IsUint checks whether <v> is type of uint.
func (v *Var) IsUint() bool {
switch v.Val().(type) {
case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64:
return true
}
return false
}
// IsFloat checks whether <v> is type of float.
func (v *Var) IsFloat() bool {
switch v.Val().(type) {
case float32, *float32, float64, *float64:
return true
}
return false
}
// IsSlice checks whether <v> is type of slice.
func (v *Var) IsSlice() bool {
var (
reflectValue = reflect.ValueOf(v.Val())
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
switch reflectKind {
case reflect.Slice, reflect.Array:
return true
}
return false
}
// IsMap checks whether <v> is type of map.
func (v *Var) IsMap() bool {
var (
reflectValue = reflect.ValueOf(v.Val())
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
switch reflectKind {
case reflect.Map:
return true
}
return false
}
// IsStruct checks whether <v> is type of struct.
func (v *Var) IsStruct() bool {
var (
reflectValue = reflect.ValueOf(v.Val())
reflectKind = reflectValue.Kind()
)
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
switch reflectKind {
case reflect.Struct:
return true
}
return false
}

View File

@ -20,6 +20,7 @@ func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
// Struct maps value of <v> to <pointer> recursively.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
// Deprecated, use Struct instead.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
@ -30,6 +31,7 @@ func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error {
}
// StructsDeep converts and returns <v> as given struct slice recursively.
// Deprecated, use Struct instead.
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}

View File

@ -0,0 +1,170 @@
// Copyright GoFrame 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 gvar_test
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func TestVar_IsNil(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(0).IsNil(), false)
t.Assert(g.NewVar(nil).IsNil(), true)
t.Assert(g.NewVar(g.Map{}).IsNil(), false)
t.Assert(g.NewVar(g.Slice{}).IsNil(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(1).IsNil(), false)
t.Assert(g.NewVar(0.1).IsNil(), false)
t.Assert(g.NewVar(g.Map{"k": "v"}).IsNil(), false)
t.Assert(g.NewVar(g.Slice{0}).IsNil(), false)
})
}
func TestVar_IsEmpty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(0).IsEmpty(), true)
t.Assert(g.NewVar(nil).IsEmpty(), true)
t.Assert(g.NewVar(g.Map{}).IsEmpty(), true)
t.Assert(g.NewVar(g.Slice{}).IsEmpty(), true)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(1).IsEmpty(), false)
t.Assert(g.NewVar(0.1).IsEmpty(), false)
t.Assert(g.NewVar(g.Map{"k": "v"}).IsEmpty(), false)
t.Assert(g.NewVar(g.Slice{0}).IsEmpty(), false)
})
}
func TestVar_IsInt(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(0).IsInt(), true)
t.Assert(g.NewVar(nil).IsInt(), false)
t.Assert(g.NewVar(g.Map{}).IsInt(), false)
t.Assert(g.NewVar(g.Slice{}).IsInt(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(1).IsInt(), true)
t.Assert(g.NewVar(-1).IsInt(), true)
t.Assert(g.NewVar(0.1).IsInt(), false)
t.Assert(g.NewVar(g.Map{"k": "v"}).IsInt(), false)
t.Assert(g.NewVar(g.Slice{0}).IsInt(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(int8(1)).IsInt(), true)
t.Assert(g.NewVar(uint8(1)).IsInt(), false)
})
}
func TestVar_IsUint(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(0).IsUint(), false)
t.Assert(g.NewVar(nil).IsUint(), false)
t.Assert(g.NewVar(g.Map{}).IsUint(), false)
t.Assert(g.NewVar(g.Slice{}).IsUint(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(1).IsUint(), false)
t.Assert(g.NewVar(-1).IsUint(), false)
t.Assert(g.NewVar(0.1).IsUint(), false)
t.Assert(g.NewVar(g.Map{"k": "v"}).IsUint(), false)
t.Assert(g.NewVar(g.Slice{0}).IsUint(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(int8(1)).IsUint(), false)
t.Assert(g.NewVar(uint8(1)).IsUint(), true)
})
}
func TestVar_IsFloat(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(0).IsFloat(), false)
t.Assert(g.NewVar(nil).IsFloat(), false)
t.Assert(g.NewVar(g.Map{}).IsFloat(), false)
t.Assert(g.NewVar(g.Slice{}).IsFloat(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(1).IsFloat(), false)
t.Assert(g.NewVar(-1).IsFloat(), false)
t.Assert(g.NewVar(0.1).IsFloat(), true)
t.Assert(g.NewVar(float64(1)).IsFloat(), true)
t.Assert(g.NewVar(g.Map{"k": "v"}).IsFloat(), false)
t.Assert(g.NewVar(g.Slice{0}).IsFloat(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(int8(1)).IsFloat(), false)
t.Assert(g.NewVar(uint8(1)).IsFloat(), false)
})
}
func TestVar_IsSlice(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(0).IsSlice(), false)
t.Assert(g.NewVar(nil).IsSlice(), false)
t.Assert(g.NewVar(g.Map{}).IsSlice(), false)
t.Assert(g.NewVar(g.Slice{}).IsSlice(), true)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(1).IsSlice(), false)
t.Assert(g.NewVar(-1).IsSlice(), false)
t.Assert(g.NewVar(0.1).IsSlice(), false)
t.Assert(g.NewVar(float64(1)).IsSlice(), false)
t.Assert(g.NewVar(g.Map{"k": "v"}).IsSlice(), false)
t.Assert(g.NewVar(g.Slice{0}).IsSlice(), true)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(int8(1)).IsSlice(), false)
t.Assert(g.NewVar(uint8(1)).IsSlice(), false)
})
}
func TestVar_IsMap(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(0).IsMap(), false)
t.Assert(g.NewVar(nil).IsMap(), false)
t.Assert(g.NewVar(g.Map{}).IsMap(), true)
t.Assert(g.NewVar(g.Slice{}).IsMap(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(1).IsMap(), false)
t.Assert(g.NewVar(-1).IsMap(), false)
t.Assert(g.NewVar(0.1).IsMap(), false)
t.Assert(g.NewVar(float64(1)).IsMap(), false)
t.Assert(g.NewVar(g.Map{"k": "v"}).IsMap(), true)
t.Assert(g.NewVar(g.Slice{0}).IsMap(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(int8(1)).IsMap(), false)
t.Assert(g.NewVar(uint8(1)).IsMap(), false)
})
}
func TestVar_IsStruct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(0).IsStruct(), false)
t.Assert(g.NewVar(nil).IsStruct(), false)
t.Assert(g.NewVar(g.Map{}).IsStruct(), false)
t.Assert(g.NewVar(g.Slice{}).IsStruct(), false)
})
gtest.C(t, func(t *gtest.T) {
t.Assert(g.NewVar(1).IsStruct(), false)
t.Assert(g.NewVar(-1).IsStruct(), false)
t.Assert(g.NewVar(0.1).IsStruct(), false)
t.Assert(g.NewVar(float64(1)).IsStruct(), false)
t.Assert(g.NewVar(g.Map{"k": "v"}).IsStruct(), false)
t.Assert(g.NewVar(g.Slice{0}).IsStruct(), false)
})
gtest.C(t, func(t *gtest.T) {
a := &struct {
}{}
t.Assert(g.NewVar(a).IsStruct(), true)
t.Assert(g.NewVar(*a).IsStruct(), true)
t.Assert(g.NewVar(&a).IsStruct(), true)
})
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,10 +8,11 @@
package gdb
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/cmdenv"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/os/gcmd"
"time"
"github.com/gogf/gf/container/gvar"
@ -42,6 +43,12 @@ type DB interface {
// Note that it is not recommended using the this function manually.
Open(config *ConfigNode) (*sql.DB, error)
// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy
// of current DB object and with given context in it.
// Note that this returned DB object can be used only once, so do not assign it to
// a global or package variable for long using.
Ctx(ctx context.Context) DB
// ===========================================================================
// Query APIs.
// ===========================================================================
@ -128,6 +135,7 @@ type DB interface {
GetDryRun() bool
SetLogger(logger *glog.Logger)
GetLogger() *glog.Logger
GetConfig() *ConfigNode
SetMaxIdleConnCount(n int)
SetMaxOpenConnCount(n int)
SetMaxConnLifetime(d time.Duration)
@ -136,6 +144,7 @@ type DB interface {
// Utility methods.
// ===========================================================================
GetCtx() context.Context
GetChars() (charLeft string, charRight string)
GetMaster(schema ...string) (*sql.DB, error)
GetSlave(schema ...string) (*sql.DB, error)
@ -153,27 +162,24 @@ type DB interface {
HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{})
// ===========================================================================
// Internal methods.
// Internal methods, for internal usage purpose, you do not need consider it.
// ===========================================================================
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
convertValue(fieldValue interface{}, fieldType string) interface{}
rowsToResult(rows *sql.Rows) (Result, error)
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error)
convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{}
convertRowsToResult(rows *sql.Rows) (Result, error)
}
// Core is the base struct for database management.
type Core struct {
DB DB // DB interface object.
group string // Configuration group name.
debug *gtype.Bool // Enable debug mode for the database.
cache *gcache.Cache // Cache manager.
schema *gtype.String // Custom schema for this object.
dryrun *gtype.Bool // Dry run.
prefix string // Table prefix.
logger *glog.Logger // Logger.
maxIdleConnCount int // Max idle connection count.
maxOpenConnCount int // Max open connection count.
maxConnLifetime time.Duration // Max TTL for a connection.
DB DB // DB interface object.
group string // Configuration group name.
debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime.
cache *gcache.Cache // Cache manager, SQL result cache only.
schema *gtype.String // Custom schema for this object.
logger *glog.Logger // Logger.
config *ConfigNode // Current config node.
ctx context.Context // Context for chaining operation only.
}
// Driver is the interface for integrating sql drivers into package gdb.
@ -212,32 +218,30 @@ type Link interface {
Prepare(sql string) (*sql.Stmt, error)
}
// Counter is the type for update count.
type Counter struct {
Field string
Value float64
}
type (
// Value is the field value type.
Value = *gvar.Var
// Record is the row record of the table.
Record map[string]Value
// Result is the row record array.
Result []Record
// Map is alias of map[string]interface{},
// which is the most common usage map type.
Map = map[string]interface{}
// List is type of map array.
List = []Map
Raw string // Raw is a raw sql that will not be treated as argument but as a direct sql part.
Value = *gvar.Var // Value is the field value type.
Record map[string]Value // Record is the row record of the table.
Result []Record // Result is the row record array.
Map = map[string]interface{} // Map is alias of map[string]interface{}, which is the most common usage map type.
List = []Map // List is type of map array.
)
const (
gINSERT_OPTION_DEFAULT = 0
gINSERT_OPTION_REPLACE = 1
gINSERT_OPTION_SAVE = 2
gINSERT_OPTION_IGNORE = 3
gDEFAULT_BATCH_NUM = 10 // Per count for batch insert/replace/save
gDEFAULT_CONN_MAX_IDLE_COUNT = 10 // Max idle connection count in pool.
gDEFAULT_CONN_MAX_LIFE_TIME = 30 // Max life time for per connection in pool in seconds.
insertOptionDefault = 0
insertOptionReplace = 1
insertOptionSave = 2
insertOptionIgnore = 3
defaultBatchNumber = 10 // Per count for batch insert/replace/save.
defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
defaultMaxOpenConnCount = 100 // Max open connection count in pool.
defaultMaxConnLifeTime = 30 * time.Second // Max life time for per connection in pool in seconds.
)
var (
@ -262,7 +266,10 @@ var (
// regularFieldNameRegPattern is the regular expression pattern for a string
// which is a regular field name of table.
regularFieldNameRegPattern = `^[\w\.\-]+$`
regularFieldNameRegPattern = `^[\w\.\-\_]+$`
// internalCache is the memory cache for internal usage.
internalCache = gcache.New()
// allDryRun sets dry-run feature for all database connections.
// It is commonly used for command options for convenience.
@ -271,7 +278,7 @@ var (
func init() {
// allDryRun is initialized from environment or command options.
allDryRun = cmdenv.Get("gf.gdb.dryrun", false).Bool()
allDryRun = gcmd.GetWithEnv("gf.gdb.dryrun", false).Bool()
}
// Register registers custom database driver to gdb.
@ -282,30 +289,27 @@ 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]
// which is DefaultGroupName in default.
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()
if len(configs.config) < 1 {
return nil, errors.New("empty database configuration")
return nil, gerror.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,
debug: gtype.NewBool(),
cache: gcache.New(),
schema: gtype.NewString(),
dryrun: gtype.NewBool(),
logger: glog.New(),
prefix: node.Prefix,
maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT,
maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure.
group: groupName,
debug: gtype.NewBool(),
cache: gcache.New(),
schema: gtype.NewString(),
logger: glog.New(),
config: node,
}
if v, ok := driverMap[node.Type]; ok {
c.DB, err = v.New(c, node)
@ -314,19 +318,19 @@ func New(name ...string) (db DB, err error) {
}
return c.DB, nil
} else {
return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
return nil, gerror.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
}
} else {
return nil, err
}
} else {
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, group))
return nil, gerror.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName))
}
}
// Instance returns an instance for DB operations.
// The parameter <name> specifies the configuration group name,
// which is DEFAULT_GROUP_NAME in default.
// which is DefaultGroupName in default.
func Instance(name ...string) (db DB, err error) {
group := configs.group
if len(name) > 0 && name[0] != "" {
@ -360,7 +364,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
}
}
if len(masterList) < 1 {
return nil, errors.New("at least one master node configuration's need to make sense")
return nil, gerror.New("at least one master node configuration's need to make sense")
}
if len(slaveList) < 1 {
slaveList = masterList
@ -371,7 +375,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
return getConfigNodeByWeight(slaveList), nil
}
} else {
return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
return nil, gerror.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
}
}
@ -438,30 +442,34 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
node = &n
}
// Cache the underlying connection pool object by node.
v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
sqlDb, err = c.DB.Open(node)
if err != nil {
intlog.Printf("DB open failed: %v, %+v", err, node)
return nil
return nil, err
}
if c.maxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(c.maxIdleConnCount)
} else if node.MaxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
if c.config.MaxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount)
} else {
sqlDb.SetMaxIdleConns(defaultMaxIdleConnCount)
}
if c.maxOpenConnCount > 0 {
sqlDb.SetMaxOpenConns(c.maxOpenConnCount)
} else if node.MaxOpenConnCount > 0 {
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
if c.config.MaxOpenConnCount > 0 {
sqlDb.SetMaxOpenConns(c.config.MaxOpenConnCount)
} else {
sqlDb.SetMaxOpenConns(defaultMaxOpenConnCount)
}
if c.maxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(c.maxConnLifetime * time.Second)
} else if node.MaxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second)
if c.config.MaxConnLifetime > 0 {
// Automatically checks whether MaxConnLifetime is configured using string like: "30s", "60s", etc.
// Or else it is configured just using number, which means value in seconds.
if c.config.MaxConnLifetime > time.Second {
sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime)
} else {
sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime * time.Second)
}
} else {
sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime)
}
return sqlDb
return sqlDb, nil
}, 0)
if v != nil && sqlDb == nil {
sqlDb = v.(*sql.DB)

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,9 +8,11 @@
package gdb
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/text/gstr"
"reflect"
"strings"
@ -22,6 +24,35 @@ import (
"github.com/gogf/gf/util/gconv"
)
// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy
// of current DB object and with given context in it.
// Note that this returned DB object can be used only once, so do not assign it to
// a global or package variable for long using.
func (c *Core) Ctx(ctx context.Context) DB {
if ctx == nil {
return c.DB
}
var (
err error
newCore = &Core{}
configNode = c.DB.GetConfig()
)
*newCore = *c
newCore.ctx = ctx
newCore.DB, err = driverMap[configNode.Type].New(newCore, configNode)
// Seldom error, just log it.
if err != nil {
c.DB.GetLogger().Ctx(ctx).Error(err)
}
return newCore.DB
}
// GetCtx returns the context for current DB.
// Note that it might be nil.
func (c *Core) GetCtx() context.Context {
return c.ctx
}
// Master creates and returns a connection from master node if master-slave configured.
// It returns the default connection if master-slave not configured.
func (c *Core) Master() (*sql.DB, error) {
@ -163,7 +194,7 @@ func (c *Core) DoGetAll(link Link, sql string, args ...interface{}) (result Resu
return nil, err
}
defer rows.Close()
return c.DB.rowsToResult(rows)
return c.DB.convertRowsToResult(rows)
}
// GetOne queries and returns one record from database.
@ -310,6 +341,11 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
return err
}
defer func() {
if err == nil {
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}
if err != nil {
if e := tx.Rollback(); e != nil {
err = e
@ -334,7 +370,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 +386,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 +405,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 +423,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,13 +458,19 @@ 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:
dataMap = DataToMapDeep(data)
case reflect.Struct:
if _, ok := data.(apiInterfaces); ok {
return c.DB.DoBatchInsert(link, table, data, option, batch...)
} else {
dataMap = ConvertDataForTableRecord(data)
}
case reflect.Map:
dataMap = ConvertDataForTableRecord(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
return result, gerror.New(fmt.Sprint("unsupported data type:", reflectKind))
}
if len(dataMap) == 0 {
return nil, errors.New("data cannot be empty")
return nil, gerror.New("data cannot be empty")
}
var (
charL, charR = c.DB.GetChars()
@ -425,14 +479,18 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
)
for k, v := range dataMap {
fields = append(fields, charL+k+charR)
values = append(values, "?")
params = append(params, v)
if s, ok := v.(Raw); ok {
values = append(values, gconv.String(s))
} else {
values = append(values, "?")
params = append(params, v)
}
}
if option == gINSERT_OPTION_SAVE {
if option == insertOptionSave {
for k, _ := range dataMap {
// If it's SAVE operation,
// do not automatically update the creating time.
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
if c.isSoftCreatedFiledName(k) {
continue
}
if len(updateStr) > 0 {
@ -465,35 +523,48 @@ 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 (
keys []string
values []string
params []interface{}
listMap List
keys []string // Field names.
values []string // Value holder string array, like: (?,?,?)
params []interface{} // Values that will be committed to underlying database driver.
listMap List // The data list that passed from caller.
)
switch value := list.(type) {
case Result:
@ -518,10 +589,10 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
case reflect.Slice, reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
case reflect.Map:
listMap = List{DataToMapDeep(value)}
listMap = List{ConvertDataForTableRecord(value)}
case reflect.Struct:
if v, ok := value.(apiInterfaces); ok {
var (
@ -529,18 +600,18 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
list = make(List, len(array))
)
for i := 0; i < len(array); i++ {
list[i] = DataToMapDeep(array[i])
list[i] = ConvertDataForTableRecord(array[i])
}
listMap = list
} else {
listMap = List{DataToMapDeep(value)}
listMap = List{ConvertDataForTableRecord(value)}
}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
}
}
if len(listMap) < 1 {
return result, errors.New("data list cannot be empty")
return result, gerror.New("data list cannot be empty")
}
if link == nil {
if link, err = c.DB.Master(); err != nil {
@ -548,25 +619,22 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
}
}
// Handle the field names and place holders.
holders := []string(nil)
for k, _ := range listMap[0] {
keys = append(keys, k)
holders = append(holders, "?")
}
// Prepare the batch result pointer.
var (
charL, charR = c.DB.GetChars()
batchResult = new(SqlResult)
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
valueHolderStr = "(" + strings.Join(holders, ",") + ")"
operation = GetInsertOperationByOption(option)
updateStr = ""
charL, charR = c.DB.GetChars()
batchResult = new(SqlResult)
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
operation = GetInsertOperationByOption(option)
updateStr = ""
)
if option == gINSERT_OPTION_SAVE {
if option == insertOptionSave {
for _, k := range keys {
// If it's SAVE operation,
// do not automatically update the creating time.
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
if c.isSoftCreatedFiledName(k) {
continue
}
if len(updateStr) > 0 {
@ -580,27 +648,34 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
}
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
}
batchNum := gDEFAULT_BATCH_NUM
batchNum := defaultBatchNumber
if len(batch) > 0 && batch[0] > 0 {
batchNum = batch[0]
}
listMapLen := len(listMap)
var (
listMapLen = len(listMap)
valueHolder = make([]string, 0)
)
for i := 0; i < listMapLen; i++ {
values = values[:0]
// Note that the map type is unordered,
// so it should use slice+key to retrieve the value.
for _, k := range keys {
params = append(params, listMap[i][k])
if s, ok := listMap[i][k].(Raw); ok {
values = append(values, gconv.String(s))
} else {
values = append(values, "?")
params = append(params, listMap[i][k])
}
}
values = append(values, valueHolderStr)
valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
if len(values) == batchNum || (i == listMapLen-1 && len(values) > 0) {
r, err := c.DB.DoExec(
link,
fmt.Sprintf(
"%s INTO %s(%s) VALUES%s %s",
operation,
table,
keysStr,
strings.Join(values, ","),
operation, table, keysStr,
gstr.Join(valueHolder, ","),
updateStr,
),
params...,
@ -615,7 +690,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
batchResult.affected += n
}
params = params[:0]
values = values[:0]
valueHolder = valueHolder[:0]
}
}
return batchResult, nil
@ -636,15 +711,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 (
@ -663,18 +734,38 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
case reflect.Map, reflect.Struct:
var (
fields []string
dataMap = DataToMapDeep(data)
dataMap = ConvertDataForTableRecord(data)
)
for k, v := range dataMap {
fields = append(fields, c.DB.QuoteWord(k)+"=?")
params = append(params, v)
switch value := v.(type) {
case *Counter:
if value.Value != 0 {
column := c.DB.QuoteWord(value.Field)
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
case Counter:
if value.Value != 0 {
column := c.DB.QuoteWord(value.Field)
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
default:
if s, ok := v.(Raw); ok {
fields = append(fields, c.DB.QuoteWord(k)+"="+gconv.String(s))
} else {
fields = append(fields, c.DB.QuoteWord(k)+"=?")
params = append(params, v)
}
}
}
updates = strings.Join(fields, ",")
default:
updates = gconv.String(data)
}
if len(updates) == 0 {
return nil, errors.New("data cannot be empty")
return nil, gerror.New("data cannot be empty")
}
if len(params) > 0 {
args = append(params, args...)
@ -704,15 +795,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 {
@ -723,8 +810,8 @@ func (c *Core) DoDelete(link Link, table string, condition string, args ...inter
return c.DB.DoExec(link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
}
// rowsToResult converts underlying data record type sql.Rows to Result type.
func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
// convertRowsToResult converts underlying data record type sql.Rows to Result type.
func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
if !rows.Next() {
return nil, nil
}
@ -756,7 +843,7 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
if value == nil {
row[columnNames[i]] = gvar.New(nil)
} else {
row[columnNames[i]] = gvar.New(c.DB.convertValue(value, columnTypes[i]))
row[columnNames[i]] = gvar.New(c.DB.convertFieldValueToLocalValue(value, columnTypes[i]))
}
}
records = append(records, row)
@ -777,14 +864,14 @@ func (c *Core) MarshalJSON() ([]byte, error) {
}
// writeSqlToLogger outputs the sql object to logger.
// It is enabled when configuration "debug" is true.
// It is enabled only if configuration "debug" is true.
func (c *Core) writeSqlToLogger(v *Sql) {
s := fmt.Sprintf("[%3d ms] [%s] %s", v.End-v.Start, v.Group, v.Format)
if v.Error != nil {
s += "\nError: " + v.Error.Error()
c.logger.Error(s)
c.logger.Ctx(c.DB.GetCtx()).Error(s)
} else {
c.logger.Debug(s)
c.logger.Ctx(c.DB.GetCtx()).Debug(s)
}
}
@ -801,3 +888,22 @@ func (c *Core) HasTable(name string) (bool, error) {
}
return false, nil
}
// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time.
func (c *Core) isSoftCreatedFiledName(fieldName string) bool {
if fieldName == "" {
return false
}
if config := c.DB.GetConfig(); config.CreatedAt != "" {
if utils.EqualFoldWithoutChars(fieldName, config.CreatedAt) {
return true
}
return gstr.InArray(append([]string{config.CreatedAt}, createdFiledNames...), fieldName)
}
for _, v := range createdFiledNames {
if utils.EqualFoldWithoutChars(fieldName, v) {
return true
}
}
return false
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -16,7 +16,8 @@ import (
)
const (
DEFAULT_GROUP_NAME = "default" // Default group name.
DEFAULT_GROUP_NAME = "default" // Deprecated, use DefaultGroupName instead.
DefaultGroupName = "default" // Default group name.
)
// Config is the configuration management object.
@ -27,22 +28,26 @@ type ConfigGroup []ConfigNode
// ConfigNode is configuration for one node.
type ConfigNode struct {
Host string // Host of server, ip or domain like: 127.0.0.1, localhost
Port string // Port, it's commonly 3306.
User string // Authentication username.
Pass string // Authentication password.
Name string // Default used database name.
Type string // Database type: mysql, sqlite, mssql, pgsql, oracle.
Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
Debug bool // (Optional) Debug mode enables debug information logging and output.
Prefix string // (Optional) Table prefix.
DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool.
MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool.
MaxConnLifetime time.Duration `json:"maxlifetime"` // (Optional) Max connection TTL configuration for underlying connection pool.
Host string // Host of server, ip or domain like: 127.0.0.1, localhost
Port string // Port, it's commonly 3306.
User string // Authentication username.
Pass string // Authentication password.
Name string // Default used database name.
Type string // Database type: mysql, sqlite, mssql, pgsql, oracle.
Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
Debug bool // (Optional) Debug mode enables debug information logging and output.
Prefix string // (Optional) Table prefix.
DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool.
MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool.
MaxConnLifetime time.Duration `json:"maxlifetime"` // (Optional) Max connection TTL configuration for underlying connection pool.
CreatedAt string // (Optional) The filed name of table for automatic-filled created datetime.
UpdatedAt string // (Optional) The filed name of table for automatic-filled updated datetime.
DeletedAt string // (Optional) The filed name of table for automatic-filled updated datetime.
TimeMaintainDisabled bool // (Optional) Disable the automatic time maintaining feature.
}
// configs is internal used configuration object.
@ -54,7 +59,7 @@ var configs struct {
func init() {
configs.config = make(Config)
configs.group = DEFAULT_GROUP_NAME
configs.group = DefaultGroupName
}
// SetConfig sets the global configuration for package.
@ -84,12 +89,12 @@ func AddConfigNode(group string, node ConfigNode) {
// AddDefaultConfigNode adds one node configuration to configuration of default group.
func AddDefaultConfigNode(node ConfigNode) {
AddConfigNode(DEFAULT_GROUP_NAME, node)
AddConfigNode(DefaultGroupName, node)
}
// AddDefaultConfigGroup adds multiple node configurations to configuration of default group.
func AddDefaultConfigGroup(nodes ConfigGroup) {
SetConfigGroup(DEFAULT_GROUP_NAME, nodes)
SetConfigGroup(DefaultGroupName, nodes)
}
// GetConfig retrieves and returns the configuration of given group.
@ -115,6 +120,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
@ -127,18 +140,18 @@ func (c *Core) GetLogger() *glog.Logger {
// SetMaxIdleConnCount sets the max idle connection count for underlying connection pool.
func (c *Core) SetMaxIdleConnCount(n int) {
c.maxIdleConnCount = n
c.config.MaxIdleConnCount = n
}
// SetMaxOpenConnCount sets the max open connection count for underlying connection pool.
func (c *Core) SetMaxOpenConnCount(n int) {
c.maxOpenConnCount = n
c.config.MaxOpenConnCount = n
}
// SetMaxConnLifetime sets the connection TTL for underlying connection pool.
// If parameter <d> <= 0, it means the connection never expires.
func (c *Core) SetMaxConnLifetime(d time.Duration) {
c.maxConnLifetime = d
c.config.MaxConnLifetime = d
}
// String returns the node as string.
@ -154,6 +167,11 @@ func (node *ConfigNode) String() string {
)
}
// GetConfig returns the current used node configuration.
func (c *Core) GetConfig() *ConfigNode {
return c.config
}
// SetDebug enables/disables the debug mode.
func (c *Core) SetDebug(debug bool) {
c.debug.Set(debug)
@ -169,28 +187,27 @@ func (c *Core) GetCache() *gcache.Cache {
return c.cache
}
// GetPrefix returns the table prefix string configured.
func (c *Core) GetPrefix() string {
return c.prefix
}
// GetGroup returns the group string configured.
func (c *Core) GetGroup() string {
return c.group
}
// SetDryRun enables/disables the DryRun feature.
func (c *Core) SetDryRun(dryrun bool) {
c.dryrun.Set(dryrun)
// Deprecated, use GetConfig instead.
func (c *Core) SetDryRun(enabled bool) {
c.config.DryRun = enabled
}
// GetDryRun returns the DryRun value.
// Deprecated, use GetConfig instead.
func (c *Core) GetDryRun() bool {
if allDryRun {
// Globally set.
return true
}
return c.dryrun.Val()
return c.config.DryRun
}
// GetPrefix returns the table prefix string configured.
// Deprecated, use GetConfig instead.
func (c *Core) GetPrefix() string {
return c.config.Prefix
}
// SetSchema changes the schema for this database connection object.

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,8 @@
package gdb
import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/util/gutil"
"strings"
"time"
@ -20,9 +22,9 @@ import (
"github.com/gogf/gf/util/gconv"
)
// convertValue automatically checks and converts field value from database type
// convertFieldValueToLocalValue automatically checks and converts field value from database type
// to golang variable type.
func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{} {
func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} {
// If there's no type retrieved, it returns the <fieldValue> directly
// to use its original data type, as <fieldValue> is type of interface{}.
if fieldType == "" {
@ -144,16 +146,52 @@ func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{
}
}
// filterFields removes all key-value pairs which are not the field of given table.
func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
// It must use data copy here to avoid its changing the origin data map.
newDataMap := make(map[string]interface{}, len(data))
if fields, err := c.DB.TableFields(table, schema); err == nil {
for k, v := range data {
if _, ok := fields[k]; ok {
newDataMap[k] = v
// mappingAndFilterData automatically mappings the map key to table field and removes
// all key-value pairs that are not the field of given table.
func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
if fieldsMap, err := c.DB.TableFields(table, schema); err == nil {
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
for k, _ := range fieldsMap {
fieldsKeyMap[k] = nil
}
// Automatic data key to table field name mapping.
var foundKey string
for dataKey, dataValue := range data {
if _, ok := fieldsKeyMap[dataKey]; !ok {
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
if foundKey != "" {
data[foundKey] = dataValue
delete(data, dataKey)
} else if !filter {
if schema != "" {
return nil, gerror.Newf(`no column of name "%s" found for table "%s" in schema "%s"`, dataKey, table, schema)
}
return nil, gerror.Newf(`no column of name "%s" found for table "%s"`, dataKey, table)
}
}
}
// Data filtering.
if filter {
for dataKey, _ := range data {
if _, ok := fieldsMap[dataKey]; !ok {
delete(data, dataKey)
}
}
}
}
return newDataMap
return data, nil
}
//// filterFields removes all key-value pairs which are not the field of given table.
//func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
// // It must use data copy here to avoid its changing the origin data map.
// newDataMap := make(map[string]interface{}, len(data))
// if fields, err := c.DB.TableFields(table, schema); err == nil {
// for k, v := range data {
// if _, ok := fields[k]; ok {
// newDataMap[k] = v
// }
// }
// }
// return newDataMap
//}

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -13,8 +13,8 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"strconv"
"strings"
@ -192,53 +192,64 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, errors.New("function TableFields supports only single table operations")
return nil, gerror.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v := d.DB.GetCache().GetOrSetFunc(
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
var result Result
var link *sql.DB
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`mssql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
var (
result Result
link *sql.DB
)
link, err = d.DB.GetSlave(checkSchema)
if err != nil {
return nil
return nil, err
}
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
SELECT a.name Field,
structureSql := fmt.Sprintf(`
SELECT
a.name Field,
CASE b.name
WHEN 'datetime' THEN 'datetime'
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20),a.xprec) + ',' + convert(varchar(20),a.xscale) + ')'
WHEN 'char' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END as TYPE,
case when a.isnullable=1 then 'YES'else 'NO' end as [Null],
case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in (
SELECT name FROM sysindexes WHERE indid in(
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
))) then 'PRI' else '' end AS [Key],
case when COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 then 'auto_increment'else '' end Extra,
isnull(e.text,'') as [Default],
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
CASE WHEN exists (
SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
SELECT name FROM sysindexes WHERE indid IN (
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
)
)
) THEN 'PRI' ELSE '' END AS [Key],
CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
isnull(e.text,'') AS [Default],
isnull(g.[value],'') AS [Comment]
FROM syscolumns a
left join systypes b on a.xtype=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
left join sys.extended_properties f on d.id=f.major_id and f.minor_id =0
where d.name='%s'
order by a.id,a.colorder`, strings.ToUpper(table)))
FROM syscolumns a
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
LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
WHERE d.name='%s'
ORDER BY a.id,a.colorder`,
strings.ToUpper(table),
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.DB.DoGetAll(link, structureSql)
if err != nil {
return nil
return nil, err
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[strings.ToLower(m["FIELD"].String())] = &TableField{
fields[strings.ToLower(m["Field"].String())] = &TableField{
Index: i,
Name: strings.ToLower(m["FIELD"].String()),
Type: strings.ToLower(m["TYPE"].String()),
Name: strings.ToLower(m["Field"].String()),
Type: strings.ToLower(m["Type"].String()),
Null: m["Null"].Bool(),
Key: m["Key"].String(),
Default: m["Default"].Val(),
@ -246,7 +257,7 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
Comment: m["Comment"].String(),
}
}
return fields
return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,8 +8,8 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
@ -31,6 +31,7 @@ func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) {
}
// Open creates and returns a underlying sql.DB object for mysql.
// Note that it converts time.Time argument to local timezone in default.
func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if config.LinkInfo != "" {
@ -41,7 +42,7 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
}
} else {
source = fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true",
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
)
}
@ -96,27 +97,29 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, errors.New("function TableFields supports only single table operations")
return nil, gerror.New("function TableFields supports only single table operations")
}
checkSchema := d.schema.Val()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v := d.cache.GetOrSetFunc(
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
func() interface{} {
var result Result
var link *sql.DB
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`mysql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
var (
result Result
link *sql.DB
)
link, err = d.DB.GetSlave(checkSchema)
if err != nil {
return nil
return nil, err
}
result, err = d.DB.DoGetAll(
link,
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)),
)
if err != nil {
return nil
return nil, err
}
fields = make(map[string]*TableField)
for i, m := range result {
@ -131,7 +134,7 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
Comment: m["Comment"].String(),
}
}
return fields
return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -13,10 +13,11 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"reflect"
"strconv"
"strings"
@ -72,50 +73,60 @@ func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []inter
return fmt.Sprintf(":v%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
// Change time string argument wrapping with TO_DATE function.
for i, v := range args {
if reflect.TypeOf(v).Kind() == reflect.String {
valueStr := gconv.String(v)
if gregex.IsMatchString(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`, valueStr) {
args[i] = fmt.Sprintf(`TO_DATE('%s','yyyy-MM-dd HH:MI:SS')`, valueStr)
}
}
}
return d.parseSql(str), args
}
// 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
}
}
@ -151,24 +162,30 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, errors.New("function TableFields supports only single table operations")
return nil, gerror.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v := d.DB.GetCache().GetOrSetFunc(
fmt.Sprintf(`oracle_table_fields_%s_%s`, table, checkSchema),
func() interface{} {
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`oracle_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
result := (Result)(nil)
result, err = d.DB.GetAll(fmt.Sprintf(`
SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table)))
structureSql := fmt.Sprintf(`
SELECT
COLUMN_NAME AS FIELD,
CASE DATA_TYPE
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
strings.ToUpper(table),
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.DB.GetAll(structureSql)
if err != nil {
return nil
return nil, err
}
fields = make(map[string]*TableField)
for i, m := range result {
@ -178,7 +195,7 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
Type: strings.ToLower(m["TYPE"].String()),
}
}
return fields
return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)
@ -188,24 +205,26 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
table = strings.ToUpper(table)
v := d.DB.GetCache().GetOrSetFunc("table_unique_index_"+table, func() interface{} {
res := (Result)(nil)
res, err = d.DB.GetAll(fmt.Sprintf(`
v, _ := internalCache.GetOrSetFunc(
"table_unique_index_"+table,
func() (interface{}, error) {
res := (Result)(nil)
res, err = d.DB.GetAll(fmt.Sprintf(`
SELECT INDEX_NAME,COLUMN_NAME,CHAR_LENGTH FROM USER_IND_COLUMNS
WHERE TABLE_NAME = '%s'
AND INDEX_NAME IN(SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME='%s' AND UNIQUENESS='UNIQUE')
ORDER BY INDEX_NAME,COLUMN_POSITION`, table, table))
if err != nil {
return nil
}
fields := make(map[string]map[string]string)
for _, v := range res {
mm := make(map[string]string)
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
fields[v["INDEX_NAME"].String()] = mm
}
return fields
}, 0)
if err != nil {
return nil, err
}
fields := make(map[string]map[string]string)
for _, v := range res {
mm := make(map[string]string)
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
fields[v["INDEX_NAME"].String()] = mm
}
return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]map[string]string)
}
@ -231,15 +250,15 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
case reflect.Map:
fallthrough
case reflect.Struct:
dataMap = DataToMapDeep(data)
dataMap = ConvertDataForTableRecord(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
return result, gerror.New(fmt.Sprint("unsupported data type:", kind))
}
indexs := make([]string, 0)
indexMap := make(map[string]string)
indexExists := false
if option != gINSERT_OPTION_DEFAULT {
if option != insertOptionDefault {
index, err := d.getTableUniqueIndex(table)
if err != nil {
return nil, err
@ -267,7 +286,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
k = strings.ToUpper(k)
// 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge否则使用insert
if (option == gINSERT_OPTION_REPLACE || option == gINSERT_OPTION_SAVE) && indexExists {
if (option == insertOptionReplace || option == insertOptionSave) && indexExists {
fields = append(fields, tableAlias1+"."+charL+k+charR)
values = append(values, tableAlias2+"."+charL+k+charR)
params = append(params, v)
@ -293,18 +312,18 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
}
}
if indexExists && option != gINSERT_OPTION_DEFAULT {
if indexExists && option != insertOptionDefault {
switch option {
case gINSERT_OPTION_REPLACE:
case insertOptionReplace:
fallthrough
case gINSERT_OPTION_SAVE:
case insertOptionSave:
tmp := fmt.Sprintf(
"MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)",
table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2,
strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","),
)
return d.DB.DoExec(link, tmp, params...)
case gINSERT_OPTION_IGNORE:
case insertOptionIgnore:
return d.DB.DoExec(link,
fmt.Sprintf(
"INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)",
@ -351,19 +370,19 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
case reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
case reflect.Map:
fallthrough
case reflect.Struct:
listMap = List{Map(DataToMapDeep(list))}
listMap = List{Map(ConvertDataForTableRecord(list))}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
}
}
// 判断长度
if len(listMap) < 1 {
return result, errors.New("empty data list")
return result, gerror.New("empty data list")
}
if link == nil {
if link, err = d.DB.Master(); err != nil {
@ -382,7 +401,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
valueHolderStr := strings.Join(holders, ",")
// 当操作类型非insert时调用单笔的insert功能
if option != gINSERT_OPTION_DEFAULT {
if option != insertOptionDefault {
for _, v := range listMap {
r, err := d.DB.DoInsert(link, table, v, option, 1)
if err != nil {
@ -400,7 +419,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
}
// 构造批量写入数据格式(注意map的遍历是无序的)
batchNum := gDEFAULT_BATCH_NUM
batchNum := defaultBatchNumber
if len(batch) > 0 {
batchNum = batch[0]
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -13,8 +13,8 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
"strings"
@ -100,28 +100,35 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, errors.New("function TableFields supports only single table operations")
return nil, gerror.New("function TableFields supports only single table operations")
}
table, _ = gregex.ReplaceString("\"", "", table)
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v := d.DB.GetCache().GetOrSetFunc(
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
var result Result
var link *sql.DB
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`pgsql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
var (
result Result
link *sql.DB
)
link, err = d.DB.GetSlave(checkSchema)
if err != nil {
return nil
return nil, err
}
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
ORDER BY a.attnum`, strings.ToLower(table)))
structureSql := fmt.Sprintf(`
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
ORDER BY a.attnum`,
strings.ToLower(table),
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.DB.DoGetAll(link, structureSql)
if err != nil {
return nil
return nil, err
}
fields = make(map[string]*TableField)
@ -132,7 +139,7 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
Type: m["type"].String(),
}
}
return fields
return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -12,8 +12,8 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/text/gstr"
@ -36,15 +36,14 @@ func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
// Open creates and returns a underlying sql.DB object for sqlite.
func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
var source string
var err error
if config.LinkInfo != "" {
source = config.LinkInfo
} else {
source = config.Name
}
source, err = gfile.Search(source)
if err != nil {
return nil, err
// It searches the source file to locate its absolute path..
if absolutePath, _ := gfile.Search(source); absolutePath != "" {
source = absolutePath
}
intlog.Printf("Open: %s", source)
if db, err := sql.Open("sqlite3", source); err == nil {
@ -92,23 +91,26 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
return nil, errors.New("function TableFields supports only single table operations")
return nil, gerror.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v := d.DB.GetCache().GetOrSetFunc(
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema), func() interface{} {
var result Result
var link *sql.DB
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`sqlite_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
var (
result Result
link *sql.DB
)
link, err = d.DB.GetSlave(checkSchema)
if err != nil {
return nil
return nil, err
}
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
if err != nil {
return nil
return nil, err
}
fields = make(map[string]*TableField)
for i, m := range result {
@ -118,7 +120,7 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
Type: strings.ToLower(m["type"].String()),
}
}
return fields
return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)

View File

@ -1,4 +1,4 @@
// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,9 +8,10 @@ package gdb
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gutil"
@ -87,9 +88,9 @@ func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) [
func GetInsertOperationByOption(option int) string {
var operator string
switch option {
case gINSERT_OPTION_REPLACE:
case insertOptionReplace:
operator = "REPLACE"
case gINSERT_OPTION_IGNORE:
case insertOptionIgnore:
operator = "INSERT IGNORE"
default:
operator = "INSERT"
@ -97,14 +98,58 @@ func GetInsertOperationByOption(option int) string {
return operator
}
// DataToMapDeep converts struct object to map type recursively.
// ConvertDataForTableRecord is a very important function, which does converting for any data that
// will be inserted into table as a record.
//
// The parameter <obj> should be type of *map/map/*struct/struct.
// It supports inherit struct definition for struct.
func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
var (
rvValue reflect.Value
rvKind reflect.Kind
data = DataToMapDeep(value)
)
for k, v := range data {
rvValue = reflect.ValueOf(v)
rvKind = rvValue.Kind()
for rvKind == reflect.Ptr {
rvValue = rvValue.Elem()
rvKind = rvValue.Kind()
}
switch rvKind {
case reflect.Slice, reflect.Array, reflect.Map:
// It should ignore the bytes type.
if _, ok := v.([]byte); !ok {
// Convert the value to JSON.
data[k], _ = json.Marshal(v)
}
case reflect.Struct:
switch v.(type) {
case time.Time, *time.Time, gtime.Time, *gtime.Time:
continue
case Counter, *Counter:
continue
default:
// Use string conversion in default.
if s, ok := v.(apiString); ok {
data[k] = s.String()
} else {
// Convert the value to JSON.
data[k], _ = json.Marshal(v)
}
}
}
}
return data
}
// DataToMapDeep converts <value> to map type recursively.
// The parameter <value> should be type of *map/map/*struct/struct.
// It supports inherit struct definition for struct.
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 +227,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.
@ -269,32 +314,40 @@ func doQuoteString(s, charLeft, charRight string) string {
// GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer.
// This function automatically retrieves primary or unique field and its attribute value as condition.
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) {
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}, err error) {
tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT})
if err != nil {
return "", nil, err
}
array := ([]string)(nil)
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
array = strings.Split(field.Tag, ",")
for _, field := range tagField {
array = strings.Split(field.TagValue, ",")
if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) {
return array[0], []interface{}{field.Value()}
return array[0], []interface{}{field.Value()}, nil
}
if len(where) > 0 {
where += " "
}
where += field.Tag + "=?"
where += field.TagValue + "=?"
args = append(args, field.Value())
}
return
}
// GetPrimaryKey retrieves and returns primary key field name from given struct.
func GetPrimaryKey(pointer interface{}) string {
func GetPrimaryKey(pointer interface{}) (string, error) {
tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT})
if err != nil {
return "", err
}
array := ([]string)(nil)
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
array = strings.Split(field.Tag, ",")
for _, field := range tagField {
array = strings.Split(field.TagValue, ",")
if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY {
return array[0]
return array[0], nil
}
}
return ""
return "", nil
}
// GetPrimaryKeyCondition returns a new where condition by primary field name.
@ -317,8 +370,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()
@ -341,14 +396,14 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
// The internal handleArguments function might be called twice during the SQL procedure,
// but do not worry about it, it's safe and efficient.
func formatSql(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
sql = gstr.Trim(sql)
sql = gstr.Replace(sql, "\n", " ")
sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
// DO NOT do this as there may be multiple lines and comments in the sql.
// sql = gstr.Trim(sql)
// sql = gstr.Replace(sql, "\n", " ")
// sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
return handleArguments(sql, args)
}
// formatWhere formats where statement and its arguments.
// TODO []interface{} type support for parameter <where> does not completed yet.
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) {
var (
buffer = bytes.NewBuffer(nil)
@ -430,20 +485,26 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
}
// formatWhereInterfaces formats <where> as []interface{}.
// TODO supporting for parameter <where> with []interface{} type is not completed yet.
func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} {
if len(where) == 0 {
return newArgs
}
if len(where)%2 != 0 {
buffer.WriteString(gstr.Join(gconv.Strings(where), ""))
return newArgs
}
var str string
var array []interface{}
var holderCount int
for i := 0; i < len(where); {
if holderCount > 0 {
array = gconv.Interfaces(where[i])
newArgs = append(newArgs, array...)
holderCount -= len(array)
for i := 0; i < len(where); i += 2 {
str = gconv.String(where[i])
if buffer.Len() > 0 {
buffer.WriteString(" AND " + db.QuoteWord(str) + "=?")
} else {
str = gconv.String(where[i])
holderCount = gstr.Count(str, "?")
buffer.WriteString(str)
buffer.WriteString(db.QuoteWord(str) + "=?")
}
if s, ok := where[i+1].(Raw); ok {
buffer.WriteString(gconv.String(s))
} else {
newArgs = append(newArgs, where[i+1])
}
}
return newArgs
@ -512,14 +573,18 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
} else {
buffer.WriteString(quotedKey)
}
newArgs = append(newArgs, value)
if s, ok := value.(Raw); ok {
buffer.WriteString(gconv.String(s))
} else {
newArgs = append(newArgs, value)
}
}
}
return newArgs
}
// handleArguments is a nice function which handles the query and its arguments before committing to
// underlying driver.
// handleArguments is an important function, which handles the sql and all its arguments
// before committing them to underlying driver.
func handleArguments(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
newSql = sql
// insertHolderCount is used to calculate the inserting position for the '?' holder.
@ -528,14 +593,14 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
if len(args) > 0 {
for index, arg := range args {
var (
rv = reflect.ValueOf(arg)
kind = rv.Kind()
reflectValue = reflect.ValueOf(arg)
reflectKind = reflectValue.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
switch kind {
switch reflectKind {
case reflect.Slice, reflect.Array:
// It does not split the type of []byte.
// Eg: table.Where("name = ?", []byte("john"))
@ -544,7 +609,7 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
continue
}
if rv.Len() == 0 {
if reflectValue.Len() == 0 {
// Empty slice argument, it converts the sql to a false sql.
// Eg:
// Query("select * from xxx where id in(?)", g.Slice{}) -> select * from xxx where 0=1
@ -558,15 +623,15 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
}
}
} else {
for i := 0; i < rv.Len(); i++ {
newArgs = append(newArgs, rv.Index(i).Interface())
for i := 0; i < reflectValue.Len(); i++ {
newArgs = append(newArgs, reflectValue.Index(i).Interface())
}
}
// If the '?' holder count equals the length of the slice,
// it does not implement the arguments splitting logic.
// Eg: db.Query("SELECT ?+?", g.Slice{1, 2})
if len(args) == 1 && gstr.Count(newSql, "?") == rv.Len() {
if len(args) == 1 && gstr.Count(newSql, "?") == reflectValue.Len() {
break
}
// counter is used to finding the inserting position for the '?' holder.
@ -581,36 +646,37 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
counter++
if counter == index+insertHolderCount+1 {
replaced = true
insertHolderCount += rv.Len() - 1
return "?" + strings.Repeat(",?", rv.Len()-1)
insertHolderCount += reflectValue.Len() - 1
return "?" + strings.Repeat(",?", reflectValue.Len()-1)
}
return s
})
// Special struct handling.
case reflect.Struct:
// The underlying driver supports time.Time/*time.Time types.
if _, ok := arg.(time.Time); ok {
newArgs = append(newArgs, arg)
continue
}
if _, ok := arg.(*time.Time); ok {
newArgs = append(newArgs, arg)
continue
}
switch v := arg.(type) {
// The underlying driver supports time.Time/*time.Time types.
case time.Time, *time.Time:
newArgs = append(newArgs, arg)
continue
// Special handling for gtime.Time.
// Special handling for gtime.Time/*gtime.Time.
//
// DO NOT use its underlying gtime.Time.Time as its argument,
// because the std time.Time will be converted to certain timezone
// according to underlying driver. And the underlying driver also
// converts the time.Time to string automatically as the following does.
case gtime.Time:
newArgs = append(newArgs, v.String())
continue
case *gtime.Time:
newArgs = append(newArgs, v.String())
continue
default:
// It converts the struct to string in default
// if it implements the String interface.
// if it has implemented the String interface.
if v, ok := arg.(apiString); ok {
newArgs = append(newArgs, v.String())
continue
@ -629,7 +695,7 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
// formatError customizes and returns the SQL error.
func formatError(err error, sql string, args ...interface{}) error {
if err != nil && err != ErrNoRows {
return errors.New(fmt.Sprintf("%s, %s\n", err.Error(), FormatSqlWithArgs(sql, args)))
return gerror.New(fmt.Sprintf("%s, %s\n", err.Error(), FormatSqlWithArgs(sql, args)))
}
return err
}
@ -661,7 +727,7 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
case reflect.Struct:
if t, ok := args[index].(time.Time); ok {
return `'` + gtime.NewFromTime(t).String() + `'`
return `'` + t.Format(`2006-01-02 15:04:05`) + `'`
}
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
}
@ -672,13 +738,17 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
return newQuery
}
// mapToStruct maps the <data> to given struct.
// convertMapToStruct maps the <data> to given struct.
// Note that the given parameter <pointer> should be a pointer to s struct.
func mapToStruct(data map[string]interface{}, pointer interface{}) error {
func convertMapToStruct(data map[string]interface{}, pointer interface{}) error {
tagNameMap, err := structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT})
if err != nil {
return err
}
// It retrieves and returns the mapping between orm tag and the struct attribute name.
mapping := make(map[string]string)
for tag, attr := range structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
for tag, attr := range tagNameMap {
mapping[strings.Split(tag, ",")[0]] = attr
}
return gconv.StructDeep(data, pointer, mapping)
return gconv.Struct(data, pointer, mapping)
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,7 @@
package gdb
import (
"context"
"fmt"
"github.com/gogf/gf/text/gregex"
"time"
@ -52,13 +53,14 @@ type whereHolder struct {
}
const (
gLINK_TYPE_MASTER = 1
gLINK_TYPE_SLAVE = 2
gWHERE_HOLDER_WHERE = 1
gWHERE_HOLDER_AND = 2
gWHERE_HOLDER_OR = 3
OPTION_OMITEMPTY = 1 << iota
OPTION_ALLOWEMPTY
OPTION_OMITEMPTY = 1
OPTION_ALLOWEMPTY = 2
linkTypeMaster = 1
linkTypeSlave = 2
whereHolderWhere = 1
whereHolderAnd = 2
whereHolderOr = 3
)
// Table creates and returns a new ORM model from given schema.
@ -112,6 +114,16 @@ func (tx *TX) Model(table ...string) *Model {
return tx.Table(table...)
}
// Ctx sets the context for current operation.
func (m *Model) Ctx(ctx context.Context) *Model {
if ctx == nil {
return m
}
model := m.getModel()
model.db = model.db.Ctx(ctx)
return model
}
// As sets an alias name for current table.
func (m *Model) As(as string) *Model {
if m.tables != "" {
@ -141,6 +153,7 @@ func (m *Model) DB(db DB) *Model {
// TX sets/changes the transaction for current operation.
func (m *Model) TX(tx *TX) *Model {
model := m.getModel()
model.db = tx.db
model.tx = tx
return model
}
@ -177,7 +190,7 @@ func (m *Model) Clone() *Model {
// Master marks the following operation on master node.
func (m *Model) Master() *Model {
model := m.getModel()
model.linkType = gLINK_TYPE_MASTER
model.linkType = linkTypeMaster
return model
}
@ -185,7 +198,7 @@ func (m *Model) Master() *Model {
// Note that it makes sense only if there's any slave node configured.
func (m *Model) Slave() *Model {
model := m.getModel()
model.linkType = gLINK_TYPE_SLAVE
model.linkType = linkTypeSlave
return model
}
@ -199,3 +212,10 @@ func (m *Model) Safe(safe ...bool) *Model {
}
return m
}
// Args sets custom arguments for model operation.
func (m *Model) Args(args ...interface{}) *Model {
model := m.getModel()
model.extraArgs = append(model.extraArgs, args)
return model
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -27,7 +27,7 @@ func (m *Model) Where(where interface{}, args ...interface{}) *Model {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_WHERE,
operator: whereHolderWhere,
where: where,
args: args,
})
@ -65,7 +65,7 @@ func (m *Model) And(where interface{}, args ...interface{}) *Model {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_AND,
operator: whereHolderAnd,
where: where,
args: args,
})
@ -79,7 +79,7 @@ func (m *Model) Or(where interface{}, args ...interface{}) *Model {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: gWHERE_HOLDER_OR,
operator: whereHolderOr,
where: where,
args: args,
})

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -9,16 +9,11 @@ package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gstr"
)
// Unscoped disables the soft deleting feature.
func (m *Model) Unscoped() *Model {
model := m.getModel()
model.unscoped = true
return model
}
// Delete does "DELETE FROM ... " statement for the model.
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
@ -32,8 +27,8 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
}
}()
var (
fieldNameDelete = m.getSoftFieldNameDelete()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
fieldNameDelete = m.getSoftFieldNameDeleted()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
)
// Soft deleting.
if !m.unscoped && fieldNameDelete != "" {
@ -45,5 +40,9 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
append([]interface{}{gtime.Now().String()}, conditionArgs...),
)
}
return m.db.DoDelete(m.getLink(true), m.tables, conditionWhere+conditionExtra, conditionArgs...)
conditionStr := conditionWhere + conditionExtra
if !gstr.ContainsI(conditionStr, " WHERE ") {
return nil, gerror.New("there should be WHERE condition statement for DELETE operation")
}
return m.db.DoDelete(m.getLink(true), m.tables, conditionStr, conditionArgs...)
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -10,6 +10,8 @@ import (
"fmt"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
)
// Filter marks filtering the fields which does not exist in the fields of the operated table.
@ -24,49 +26,70 @@ func (m *Model) Filter() *Model {
}
// Fields sets the operation fields of the model, multiple fields joined using char ','.
func (m *Model) Fields(fields string) *Model {
model := m.getModel()
model.fields = fields
return model
// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
length := len(fieldNamesOrMapStruct)
if length == 0 {
return m
}
switch {
// String slice.
case length >= 2:
model := m.getModel()
model.fields = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",")
return model
// It need type asserting.
case length == 1:
model := m.getModel()
switch r := fieldNamesOrMapStruct[0].(type) {
case string:
model.fields = gstr.Join(m.mappingAndFilterToTableFields([]string{r}), ",")
case []string:
model.fields = gstr.Join(m.mappingAndFilterToTableFields(r), ",")
default:
model.fields = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r)), ",")
}
return model
}
return m
}
// 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))
// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
length := len(fieldNamesOrMapStruct)
if length == 0 {
return m
}
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
switch {
case length >= 2:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",")
return model
case length == 1:
switch r := fieldNamesOrMapStruct[0].(type) {
case string:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields([]string{r}), ",")
case []string:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(r), ",")
default:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r)), ",")
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
return model
}
model.fields = model.db.QuoteString(model.fields)
return model
return m
}
// 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 +116,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

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,7 +8,7 @@ package gdb
import (
"database/sql"
"errors"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
@ -56,8 +56,10 @@ func (m *Model) Data(data ...interface{}) *Model {
case Map:
model.data = params
default:
rv := reflect.ValueOf(params)
kind := rv.Kind()
var (
rv = reflect.ValueOf(params)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
@ -66,11 +68,11 @@ func (m *Model) Data(data ...interface{}) *Model {
case reflect.Slice, reflect.Array:
list := make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
list[i] = DataToMapDeep(rv.Index(i).Interface())
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
model.data = list
case reflect.Map:
model.data = DataToMapDeep(data[0])
model.data = ConvertDataForTableRecord(data[0])
case reflect.Struct:
if v, ok := data[0].(apiInterfaces); ok {
var (
@ -78,11 +80,11 @@ func (m *Model) Data(data ...interface{}) *Model {
list = make(List, len(array))
)
for i := 0; i < len(array); i++ {
list[i] = DataToMapDeep(array[i])
list[i] = ConvertDataForTableRecord(array[i])
}
model.data = list
} else {
model.data = DataToMapDeep(data[0])
model.data = ConvertDataForTableRecord(data[0])
}
default:
model.data = data[0]
@ -99,7 +101,7 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Insert()
}
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
return m.doInsertWithOption(insertOptionDefault, data...)
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
@ -107,9 +109,9 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
// see Model.Data.
func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Insert()
return m.Data(data...).InsertIgnore()
}
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
return m.doInsertWithOption(insertOptionIgnore, data...)
}
// Replace does "REPLACE INTO ..." statement for the model.
@ -119,7 +121,7 @@ func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Replace()
}
return m.doInsertWithOption(gINSERT_OPTION_REPLACE, data...)
return m.doInsertWithOption(insertOptionReplace, data...)
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
@ -132,7 +134,7 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Save()
}
return m.doInsertWithOption(gINSERT_OPTION_SAVE, data...)
return m.doInsertWithOption(insertOptionSave, data...)
}
// doInsertWithOption inserts data with option parameter.
@ -143,17 +145,17 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
}
}()
if m.data == nil {
return nil, errors.New("inserting into table with empty data")
return nil, gerror.New("inserting into table with empty data")
}
var (
nowString = gtime.Now().String()
fieldNameCreate = m.getSoftFieldNameCreate()
fieldNameUpdate = m.getSoftFieldNameUpdate()
fieldNameDelete = m.getSoftFieldNameDelete()
fieldNameCreate = m.getSoftFieldNameCreated()
fieldNameUpdate = m.getSoftFieldNameUpdated()
fieldNameDelete = m.getSoftFieldNameDeleted()
)
// Batch operation.
if list, ok := m.data.(List); ok {
batch := gDEFAULT_BATCH_NUM
batch := defaultBatchNumber
if m.batch > 0 {
batch = m.batch
}
@ -170,10 +172,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
list[k] = v
}
}
newData, err := m.filterDataForInsertOrUpdate(list)
if err != nil {
return nil, err
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
newData,
option,
batch,
)
@ -190,12 +196,16 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
data[fieldNameUpdate] = nowString
}
}
newData, err := m.filterDataForInsertOrUpdate(data)
if err != nil {
return nil, err
}
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
newData,
option,
)
}
return nil, errors.New("inserting into table with invalid data type")
return nil, gerror.New("inserting into table with invalid data type")
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -6,29 +6,30 @@
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,
// 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")
// Table("user", "u").LeftJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
func (m *Model) LeftJoin(table ...string) *Model {
model := m.getModel()
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
return m.doJoin("LEFT", table...)
}
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
@ -36,22 +37,9 @@ func (m *Model) LeftJoin(table ...string) *Model {
// 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")
// Table("user", "u").RightJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
func (m *Model) RightJoin(table ...string) *Model {
model := m.getModel()
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
return m.doJoin("RIGHT", table...)
}
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
@ -59,20 +47,48 @@ func (m *Model) RightJoin(table ...string) *Model {
// 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")
// Table("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
func (m *Model) InnerJoin(table ...string) *Model {
model := m.getModel()
return m.doJoin("INNER", table...)
}
// doJoin does "LEFT/RIGHT/INNER JOIN ... ON ..." statement on the 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")
// Table("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
// Related issues:
// https://github.com/gogf/gf/issues/1024
func (m *Model) doJoin(operator string, table ...string) *Model {
var (
model = m.getModel()
joinStr = ""
)
if len(table) > 0 {
if isSubQuery(table[0]) {
joinStr = gstr.Trim(table[0])
if joinStr[0] != '(' {
joinStr = "(" + joinStr + ")"
}
} 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],
" %s JOIN %s AS %s ON (%s)",
operator, 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],
" %s JOIN %s ON (%s)",
operator, joinStr, table[1],
)
} else if len(table) == 1 {
model.tables += fmt.Sprintf(
" %s JOIN %s", operator, joinStr,
)
} else {
panic("invalid join table parameter")
}
return model
}

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,7 +8,10 @@ package gdb
import (
"fmt"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"reflect"
)
@ -43,7 +46,7 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
}
var (
softDeletingCondition = m.getConditionForSoftDeleting()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(limit1)
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(limit1, false)
)
if !m.unscoped && softDeletingCondition != "" {
if conditionWhere == "" {
@ -53,12 +56,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,10 +70,60 @@ 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.
if !gstr.Contains(m.fields, ".") && !gstr.Contains(m.fields, " ") {
return m.db.QuoteString(m.fields)
}
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 += m.db.QuoteWord(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
if page == 0 {
if page <= 0 {
page = 1
}
model := m
@ -290,7 +344,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
}
var (
softDeletingCondition = m.getConditionForSoftDeleting()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, true)
)
if !m.unscoped && softDeletingCondition != "" {
if conditionWhere == "" {
@ -381,23 +435,35 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
// doGetAllBySql does the select statement on the database.
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
cacheKey := ""
cacheObj := m.db.GetCache()
// Retrieve from cache.
if m.cacheEnabled && m.tx == nil {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
cacheKey = sql + ", @PARAMS:" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
if v, _ := cacheObj.GetVar(cacheKey); !v.IsNil() {
if result, ok := v.Val().(Result); ok {
// In-memory cache.
return result, nil
} else {
// Other cache, it needs conversion.
var result Result
if err = json.Unmarshal(v.Bytes(), &result); err != nil {
return nil, err
} else {
return result, nil
}
}
}
}
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
// Cache the result.
if cacheKey != "" && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
cacheObj.Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
cacheObj.Set(cacheKey, result, m.cacheDuration)
}
}
return result, err

View File

@ -1,4 +1,4 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -15,58 +15,94 @@ import (
"github.com/gogf/gf/util/gutil"
)
const (
gSOFT_FIELD_NAME_CREATE = "create_at"
gSOFT_FIELD_NAME_UPDATE = "update_at"
gSOFT_FIELD_NAME_DELETE = "delete_at"
var (
createdFiledNames = []string{"created_at", "create_at"} // Default filed names of table for automatic-filled created datetime.
updatedFiledNames = []string{"updated_at", "update_at"} // Default filed names of table for automatic-filled updated datetime.
deletedFiledNames = []string{"deleted_at", "delete_at"} // Default filed names of table for automatic-filled deleted datetime.
)
// Unscoped disables the auto-update time feature for insert, update and delete options.
func (m *Model) Unscoped() *Model {
model := m.getModel()
model.unscoped = true
return model
}
// 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(table ...string) string {
func (m *Model) getSoftFieldNameCreated(table ...string) string {
// It checks whether this feature disabled.
if m.db.GetConfig().TimeMaintainDisabled {
return ""
}
tableName := ""
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_CREATE)
config := m.db.GetConfig()
if config.CreatedAt != "" {
return m.getSoftFieldName(tableName, []string{config.CreatedAt})
}
return m.getSoftFieldName(tableName, createdFiledNames)
}
// 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(table ...string) (field string) {
func (m *Model) getSoftFieldNameUpdated(table ...string) (field string) {
// It checks whether this feature disabled.
if m.db.GetConfig().TimeMaintainDisabled {
return ""
}
tableName := ""
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_UPDATE)
config := m.db.GetConfig()
if config.UpdatedAt != "" {
return m.getSoftFieldName(tableName, []string{config.UpdatedAt})
}
return m.getSoftFieldName(tableName, updatedFiledNames)
}
// 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(table ...string) (field string) {
func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) {
// It checks whether this feature disabled.
if m.db.GetConfig().TimeMaintainDisabled {
return ""
}
tableName := ""
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_DELETE)
config := m.db.GetConfig()
if config.UpdatedAt != "" {
return m.getSoftFieldName(tableName, []string{config.DeletedAt})
}
return m.getSoftFieldName(tableName, deletedFiledNames)
}
// getSoftFieldName retrieves and returns the field name of the table for possible key.
func (m *Model) getSoftFieldName(table string, key string) (field string) {
func (m *Model) getSoftFieldName(table string, keys []string) (field string) {
fieldsMap, _ := m.db.TableFields(table)
if len(fieldsMap) > 0 {
field, _ = gutil.MapPossibleItemByKey(
gconv.Map(fieldsMap), key,
)
for _, key := range keys {
field, _ = gutil.MapPossibleItemByKey(
gconv.Map(fieldsMap), key,
)
if field != "" {
return
}
}
}
return
}
@ -86,8 +122,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]))
}
@ -103,7 +139,7 @@ func (m *Model) getConditionForSoftDeleting() string {
return conditionArray.Join(" AND ")
}
// Only one table.
if fieldName := m.getSoftFieldNameDelete(); fieldName != "" {
if fieldName := m.getSoftFieldNameDeleted(); fieldName != "" {
return fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(fieldName))
}
return ""
@ -122,7 +158,7 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
} else {
table = array2[0]
}
field = m.getSoftFieldNameDelete(table)
field = m.getSoftFieldNameDeleted(table)
if field == "" {
return ""
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,8 +8,8 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
@ -38,14 +38,14 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
}
}()
if m.data == nil {
return nil, errors.New("updating table with empty data")
return nil, gerror.New("updating table with empty data")
}
var (
updateData = m.data
fieldNameCreate = m.getSoftFieldNameCreate()
fieldNameUpdate = m.getSoftFieldNameUpdate()
fieldNameDelete = m.getSoftFieldNameDelete()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
fieldNameCreate = m.getSoftFieldNameCreated()
fieldNameUpdate = m.getSoftFieldNameUpdated()
fieldNameDelete = m.getSoftFieldNameDeleted()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
)
// Automatically update the record updating time.
if !m.unscoped && fieldNameUpdate != "" {
@ -59,7 +59,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
}
switch refKind {
case reflect.Map, reflect.Struct:
dataMap := DataToMapDeep(m.data)
dataMap := ConvertDataForTableRecord(m.data)
gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameUpdate != "" {
dataMap[fieldNameUpdate] = gtime.Now().String()
@ -73,11 +73,19 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
updateData = updates
}
}
newData, err := m.filterDataForInsertOrUpdate(updateData)
if err != nil {
return nil, err
}
conditionStr := conditionWhere + conditionExtra
if !gstr.ContainsI(conditionStr, " WHERE ") {
return nil, gerror.New("there should be WHERE condition statement for UPDATE operation")
}
return m.db.DoUpdate(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(updateData),
conditionWhere+conditionExtra,
newData,
conditionStr,
m.mergeArguments(conditionArgs)...,
)
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -11,8 +11,10 @@ import (
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
"time"
)
@ -26,29 +28,66 @@ func (m *Model) getModel() *Model {
}
}
// mappingAndFilterToTableFields mappings and changes given field name to really table field name.
func (m *Model) mappingAndFilterToTableFields(fields []string) []string {
fieldsMap, err := m.db.TableFields(m.tables)
if err != nil || len(fieldsMap) == 0 {
return fields
}
var (
inputFieldsArray = gstr.SplitAndTrim(gstr.Join(fields, ","), ",")
outputFieldsArray = make([]string, 0, len(inputFieldsArray))
)
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
for k, _ := range fieldsMap {
fieldsKeyMap[k] = nil
}
for _, field := range inputFieldsArray {
if _, ok := fieldsKeyMap[field]; !ok {
if !gregex.IsMatchString(regularFieldNameRegPattern, field) {
outputFieldsArray = append(outputFieldsArray, field)
continue
} else {
if foundKey, _ := gutil.MapPossibleItemByKey(fieldsKeyMap, field); foundKey != "" {
outputFieldsArray = append(outputFieldsArray, foundKey)
}
}
} else {
outputFieldsArray = append(outputFieldsArray, field)
}
}
return outputFieldsArray
}
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, error) {
var err error
switch value := data.(type) {
case List:
for k, item := range value {
value[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
value[k], err = m.doMappingAndFilterForInsertOrUpdateDataMap(item, false)
if err != nil {
return nil, err
}
}
return value
return value, nil
case Map:
return m.doFilterDataMapForInsertOrUpdate(value, true)
return m.doMappingAndFilterForInsertOrUpdateDataMap(value, true)
default:
return data
return data, nil
}
}
// doFilterDataMapForInsertOrUpdate does the filter features for map.
// doMappingAndFilterForInsertOrUpdateDataMap does the filter features for map.
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map {
if m.filter {
data = m.db.filterFields(m.schema, m.tables, data)
func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) {
var err error
data, err = m.db.mappingAndFilterData(m.schema, m.tables, data, m.filter)
if err != nil {
return nil, err
}
// Remove key-value pairs of which the value is empty.
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
@ -103,7 +142,7 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
delete(data, v)
}
}
return data
return data, nil
}
// getLink returns the underlying database link object with configured <linkType> attribute.
@ -115,19 +154,19 @@ func (m *Model) getLink(master bool) Link {
linkType := m.linkType
if linkType == 0 {
if master {
linkType = gLINK_TYPE_MASTER
linkType = linkTypeMaster
} else {
linkType = gLINK_TYPE_SLAVE
linkType = linkTypeSlave
}
}
switch linkType {
case gLINK_TYPE_MASTER:
case linkTypeMaster:
link, err := m.db.GetMaster(m.schema)
if err != nil {
panic(err)
}
return link
case gLINK_TYPE_SLAVE:
case linkTypeSlave:
link, err := m.db.GetSlave(m.schema)
if err != nil {
panic(err)
@ -158,11 +197,11 @@ func (m *Model) getPrimaryKey() string {
// Note that this function does not change any attribute value of the <m>.
//
// The parameter <limit1> specifies whether limits querying only one record if m.limit is not set.
func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
if len(m.whereHolder) > 0 {
for _, v := range m.whereHolder {
switch v.operator {
case gWHERE_HOLDER_WHERE:
case whereHolderWhere:
if conditionWhere == "" {
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
@ -175,7 +214,7 @@ func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionEx
}
fallthrough
case gWHERE_HOLDER_AND:
case whereHolderAnd:
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
)
@ -190,7 +229,7 @@ func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionEx
conditionArgs = append(conditionArgs, newArgs...)
}
case gWHERE_HOLDER_OR:
case whereHolderOr:
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
)
@ -213,9 +252,6 @@ func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionEx
if m.groupBy != "" {
conditionExtra += " GROUP BY " + m.groupBy
}
if m.orderBy != "" {
conditionExtra += " ORDER BY " + m.orderBy
}
if len(m.having) > 0 {
havingStr, havingArgs := formatWhere(
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OPTION_OMITEMPTY > 0,
@ -225,18 +261,25 @@ func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionEx
conditionArgs = append(conditionArgs, havingArgs...)
}
}
if m.limit != 0 {
if m.start >= 0 {
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
} else {
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
if m.orderBy != "" {
conditionExtra += " ORDER BY " + m.orderBy
}
if !isCountStatement {
if m.limit != 0 {
if m.start >= 0 {
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
} else {
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
}
} else if limit1 {
conditionExtra += " LIMIT 1"
}
if m.offset >= 0 {
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
}
} else if limit1 {
conditionExtra += " LIMIT 1"
}
if m.offset >= 0 {
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
}
if m.lockInfo != "" {
conditionExtra += " " + m.lockInfo
}

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -59,7 +59,7 @@ func (tx *TX) GetAll(sql string, args ...interface{}) (Result, error) {
return nil, err
}
defer rows.Close()
return tx.db.rowsToResult(rows)
return tx.db.convertRowsToResult(rows)
}
// GetOne queries and returns one record from database.
@ -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

@ -1,4 +1,4 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,9 +8,9 @@ package gdb
import (
"database/sql"
"errors"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/util/gconv"
"reflect"
)
@ -52,24 +52,24 @@ func (r Record) Struct(pointer interface{}) error {
}
// Special handling for parameter type: reflect.Value
if _, ok := pointer.(reflect.Value); ok {
return mapToStruct(r.Map(), pointer)
return convertMapToStruct(r.Map(), pointer)
}
var (
reflectValue = reflect.ValueOf(pointer)
reflectKind = reflectValue.Kind()
)
if reflectKind != reflect.Ptr {
return errors.New("parameter should be type of *struct/**struct")
return gerror.New("parameter should be type of *struct/**struct")
}
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind == reflect.Invalid {
return errors.New("parameter is an invalid pointer, maybe nil")
return gerror.New("parameter is an invalid pointer, maybe nil")
}
if reflectKind != reflect.Ptr && reflectKind != reflect.Struct {
return errors.New("parameter should be type of *struct/**struct")
return gerror.New("parameter should be type of *struct/**struct")
}
return mapToStruct(r.Map(), pointer)
return convertMapToStruct(r.Map(), pointer)
}
// IsEmpty checks and returns whether <r> is empty.

View File

@ -1,4 +1,4 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -224,19 +224,16 @@ func (r Result) Structs(pointer interface{}) (err error) {
itemKind = itemType.Kind()
)
for i := 0; i < length; i++ {
var elem reflect.Value
if itemKind == reflect.Ptr {
e := reflect.New(itemType.Elem()).Elem()
if err = r[i].Struct(e); err != nil {
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
}
array.Index(i).Set(e.Addr())
elem = reflect.New(itemType.Elem())
} else {
e := reflect.New(itemType).Elem()
if err = r[i].Struct(e); err != nil {
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
}
array.Index(i).Set(e)
elem = reflect.New(itemType).Elem()
}
if err = r[i].Struct(elem); err != nil {
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
}
array.Index(i).Set(elem)
}
reflect.ValueOf(pointer).Elem().Set(array)
return nil

View File

@ -1,4 +1,4 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -8,8 +8,8 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
@ -45,14 +45,14 @@ import (
func (r Result) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) {
// Necessary checks for parameters.
if attributeName == "" {
return errors.New(`attributeName should not be empty`)
return gerror.New(`attributeName should not be empty`)
}
if len(relation) > 0 {
if len(relation) < 2 {
return errors.New(`relation name and key should are both necessary`)
return gerror.New(`relation name and key should are both necessary`)
}
if relation[0] == "" || relation[1] == "" {
return errors.New(`relation name and key should not be empty`)
return gerror.New(`relation name and key should not be empty`)
}
}
@ -181,7 +181,7 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
}
}
if len(relationDataMap) > 0 && !relationValue.IsValid() {
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
}
switch attrKind {
case reflect.Array, reflect.Slice:
@ -196,7 +196,7 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
}
} else {
// May be the attribute does not exist yet.
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
}
} else {
return fmt.Errorf(`relationKey should not be empty as field "%s" is slice`, attributeName)
@ -207,15 +207,25 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
if len(relationDataMap) > 0 {
relationField = relationValue.FieldByName(relationAttrName)
if relationField.IsValid() {
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
v := relationDataMap[gconv.String(relationField.Interface())]
if v == nil {
// There's no relational data.
continue
}
if err = gconv.Struct(v, e); err != nil {
return err
}
} else {
// May be the attribute does not exist yet.
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
}
} else {
if err = gconv.Struct(r[i], e); err != nil {
v := r[i]
if v == nil {
// There's no relational data.
continue
}
if err = gconv.Struct(v, e); err != nil {
return err
}
}
@ -226,15 +236,25 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
if len(relationDataMap) > 0 {
relationField = relationValue.FieldByName(relationAttrName)
if relationField.IsValid() {
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
v := relationDataMap[gconv.String(relationField.Interface())]
if v == nil {
// There's no relational data.
continue
}
if err = gconv.Struct(v, e); err != nil {
return err
}
} else {
// May be the attribute does not exist yet.
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
}
} else {
if err = gconv.Struct(r[i], e); err != nil {
v := r[i]
if v == nil {
// There's no relational data.
continue
}
if err = gconv.Struct(v, e); err != nil {
return err
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -57,7 +57,7 @@ func init() {
nodePrefix.Prefix = PREFIX1
gdb.AddConfigNode("test", configNode)
gdb.AddConfigNode("prefix", nodePrefix)
gdb.AddConfigNode(gdb.DEFAULT_GROUP_NAME, configNode)
gdb.AddConfigNode(gdb.DefaultGroupName, configNode)
// Default db.
if r, err := gdb.New(); err != nil {
gtest.Error(err)

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -17,7 +17,152 @@ import (
"github.com/gogf/gf/test/gtest"
)
func Test_Table_Relation(t *testing.T) {
func Test_Table_Relation_One(t *testing.T) {
var (
tableUser = "user_" + gtime.TimestampMicroStr()
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
tableUserScores = "user_scores_" + gtime.TimestampMicroStr()
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
course varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type EntityUser struct {
Uid int `orm:"uid"`
Name string `orm:"name"`
}
type EntityUserDetail struct {
Uid int `orm:"uid"`
Address string `orm:"address"`
}
type EntityUserScores struct {
Id int `orm:"id"`
Uid int `orm:"uid"`
Score int `orm:"score"`
Course string `orm:"course"`
}
type Entity struct {
User *EntityUser
UserDetail *EntityUserDetail
UserScores []*EntityUserScores
}
// Initialize the data.
var err error
gtest.C(t, func(t *gtest.T) {
err = db.Transaction(func(tx *gdb.TX) error {
r, err := tx.Table(tableUser).Save(EntityUser{
Name: "john",
})
if err != nil {
return err
}
uid, err := r.LastInsertId()
if err != nil {
return err
}
_, err = tx.Table(tableUserDetail).Save(EntityUserDetail{
Uid: int(uid),
Address: "Beijing DongZhiMen #66",
})
if err != nil {
return err
}
_, err = tx.Table(tableUserScores).Save(g.Slice{
EntityUserScores{Uid: int(uid), Score: 100, Course: "math"},
EntityUserScores{Uid: int(uid), Score: 99, Course: "physics"},
})
return err
})
t.Assert(err, nil)
})
// Data check.
gtest.C(t, func(t *gtest.T) {
r, err := db.Table(tableUser).All()
t.Assert(err, nil)
t.Assert(r.Len(), 1)
t.Assert(r[0]["uid"].Int(), 1)
t.Assert(r[0]["name"].String(), "john")
r, err = db.Table(tableUserDetail).Where("uid", r[0]["uid"].Int()).All()
t.Assert(err, nil)
t.Assert(r.Len(), 1)
t.Assert(r[0]["uid"].Int(), 1)
t.Assert(r[0]["address"].String(), `Beijing DongZhiMen #66`)
r, err = db.Table(tableUserScores).Where("uid", r[0]["uid"].Int()).All()
t.Assert(err, nil)
t.Assert(r.Len(), 2)
t.Assert(r[0]["uid"].Int(), 1)
t.Assert(r[1]["uid"].Int(), 1)
t.Assert(r[0]["course"].String(), `math`)
t.Assert(r[1]["course"].String(), `physics`)
})
// Entity query.
gtest.C(t, func(t *gtest.T) {
var user Entity
// SELECT * FROM `user` WHERE `name`='john'
err := db.Table(tableUser).Scan(&user.User, "name", "john")
t.Assert(err, nil)
// SELECT * FROM `user_detail` WHERE `uid`=1
err = db.Table(tableUserDetail).Scan(&user.UserDetail, "uid", user.User.Uid)
t.Assert(err, nil)
// SELECT * FROM `user_scores` WHERE `uid`=1
err = db.Table(tableUserScores).Scan(&user.UserScores, "uid", user.User.Uid)
t.Assert(err, nil)
t.Assert(user.User, EntityUser{
Uid: 1,
Name: "john",
})
t.Assert(user.UserDetail, EntityUserDetail{
Uid: 1,
Address: "Beijing DongZhiMen #66",
})
t.Assert(user.UserScores, []EntityUserScores{
{Id: 1, Uid: 1, Course: "math", Score: 100},
{Id: 2, Uid: 1, Course: "physics", Score: 99},
})
})
}
func Test_Table_Relation_Many(t *testing.T) {
var (
tableUser = "user_" + gtime.TimestampMicroStr()
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

View File

@ -0,0 +1,64 @@
// Copyright GoFrame 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_test
import (
"context"
"testing"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/test/gtest"
)
func Test_Ctx(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
db, err := gdb.Instance()
t.Assert(err, nil)
err1 := db.PingMaster()
err2 := db.PingSlave()
t.Assert(err1, nil)
t.Assert(err2, nil)
newDb := db.Ctx(context.Background())
t.AssertNE(newDb, nil)
})
}
func Test_Ctx_Query(t *testing.T) {
db.GetLogger().SetCtxKeys("SpanId", "TraceId")
gtest.C(t, func(t *gtest.T) {
db.SetDebug(true)
defer db.SetDebug(false)
ctx := context.WithValue(context.Background(), "TraceId", "12345678")
ctx = context.WithValue(ctx, "SpanId", "0.1")
db.Ctx(ctx).Query("select 1")
})
gtest.C(t, func(t *gtest.T) {
db.SetDebug(true)
defer db.SetDebug(false)
db.Query("select 2")
})
}
func Test_Ctx_Model(t *testing.T) {
table := createInitTable()
defer dropTable(table)
db.GetLogger().SetCtxKeys("SpanId", "TraceId")
gtest.C(t, func(t *gtest.T) {
db.SetDebug(true)
defer db.SetDebug(false)
ctx := context.WithValue(context.Background(), "TraceId", "12345678")
ctx = context.WithValue(ctx, "SpanId", "0.1")
db.Model(table).Ctx(ctx).All()
})
gtest.C(t, func(t *gtest.T) {
db.SetDebug(true)
defer db.SetDebug(false)
db.Model(table).All()
})
}

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -9,6 +9,7 @@ package gdb
import (
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/os/gcmd"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/test/gtest"
@ -46,7 +47,7 @@ func init() {
MaxOpenConnCount: 10,
MaxConnLifetime: 600,
}
AddConfigNode(DEFAULT_GROUP_NAME, configNode)
AddConfigNode(DefaultGroupName, configNode)
// Default db.
if r, err := New(); err != nil {
gtest.Error(err)
@ -204,9 +205,9 @@ CREATE TABLE %s (
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")
gtest.Assert(model.getSoftFieldNameCreated(table2), "createat")
gtest.Assert(model.getSoftFieldNameUpdated(table2), "updateat")
gtest.Assert(model.getSoftFieldNameDeleted(table2), "deleteat")
})
}
@ -292,14 +293,46 @@ CREATE TABLE %s (
}
// Fix issue: https://github.com/gogf/gf/issues/819
func Test_Func_DataToMapDeep(t *testing.T) {
func Test_Func_ConvertDataForTableRecord(t *testing.T) {
type Test struct {
ResetPasswordTokenAt mysql.NullTime `orm:"reset_password_token_at"`
}
gtest.C(t, func(t *gtest.T) {
m := DataToMapDeep(new(Test))
m := ConvertDataForTableRecord(new(Test))
t.Assert(len(m), 1)
t.AssertNE(m["reset_password_token_at"], nil)
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)
})
}
func TestResult_Structs1(t *testing.T) {
type A struct {
Id int `orm:"id"`
}
type B struct {
*A
Name string
}
gtest.C(t, func(t *gtest.T) {
r := Result{
Record{"id": gvar.New(nil), "name": gvar.New("john")},
Record{"id": gvar.New(nil), "name": gvar.New("smith")},
}
array := make([]*B, 2)
err := r.Structs(&array)
t.Assert(err, nil)
t.Assert(array[0].Id, 0)
t.Assert(array[1].Id, 0)
t.Assert(array[0].Name, "john")
t.Assert(array[1].Name, "smith")
})
}

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -9,6 +9,7 @@ package gdb_test
import (
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/encoding/gparser"
"testing"
"time"
@ -183,6 +184,119 @@ func Test_DB_Insert(t *testing.T) {
})
}
// Fix issue: https://github.com/gogf/gf/issues/819
func Test_DB_Insert_WithStructAndSliceAttribute(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type Password struct {
Salt string `json:"salt"`
Pass string `json:"pass"`
}
data := g.Map{
"id": 1,
"passport": "t1",
"password": &Password{"123", "456"},
"nickname": []string{"A", "B", "C"},
"create_time": gtime.Now().String(),
}
_, err := db.Insert(table, data)
t.Assert(err, nil)
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
t.Assert(err, nil)
t.Assert(one["passport"], data["passport"])
t.Assert(one["create_time"], data["create_time"])
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
})
}
func Test_DB_Insert_KeyFieldNameMapping(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
Nickname string
CreateTime string
}
data := User{
Id: 1,
Passport: "user_1",
Password: "pass_1",
Nickname: "name_1",
CreateTime: "2020-10-10 12:00:01",
}
_, err := db.Insert(table, data)
t.Assert(err, nil)
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
t.Assert(err, nil)
t.Assert(one["passport"], data.Passport)
t.Assert(one["create_time"], data.CreateTime)
t.Assert(one["nickname"], data.Nickname)
})
}
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
Nickname string
CreateTime string
}
data := User{
Id: 1,
Passport: "user_10",
Password: "pass_10",
Nickname: "name_10",
CreateTime: "2020-10-10 12:00:01",
}
_, err := db.Update(table, data, "id=1")
t.Assert(err, nil)
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
t.Assert(err, nil)
t.Assert(one["passport"], data.Passport)
t.Assert(one["create_time"], data.CreateTime)
t.Assert(one["nickname"], data.Nickname)
})
}
func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
Nickname string
CreateTime string
NoneExistField string
}
data := User{
Id: 1,
Passport: "user_1",
Password: "pass_1",
Nickname: "name_1",
CreateTime: "2020-10-10 12:00:01",
}
_, err := db.Insert(table, data)
t.AssertNE(err, nil)
})
}
func Test_DB_InsertIgnore(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -605,7 +719,7 @@ func Test_DB_Delete(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Delete(table, nil)
result, err := db.Delete(table, 1)
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, SIZE)
@ -654,7 +768,7 @@ func Test_DB_Time(t *testing.T) {
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Delete(table, nil)
result, err := db.Delete(table, 1)
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 2)
@ -1291,3 +1405,67 @@ func Test_Empty_Slice_Argument(t *testing.T) {
t.Assert(len(result), 0)
})
}
// update counter test
func Test_DB_UpdateCounter(t *testing.T) {
tableName := "gf_update_counter_test_" + gtime.TimestampNanoStr()
_, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL,
views int(8) unsigned DEFAULT '0' NOT NULL ,
updated_time int(10) unsigned DEFAULT '0' NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableName))
if err != nil {
gtest.Fatal(err)
}
defer dropTable(tableName)
gtest.C(t, func(t *gtest.T) {
insertData := g.Map{
"id": 1,
"views": 0,
"updated_time": 0,
}
_, err = db.Insert(tableName, insertData)
t.Assert(err, nil)
})
gtest.C(t, func(t *gtest.T) {
gdbCounter := &gdb.Counter{
Field: "views",
Value: 1,
}
updateData := g.Map{
"views": gdbCounter,
"updated_time": gtime.Now().Unix(),
}
result, err := db.Update(tableName, updateData, "id", 1)
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 1)
one, err := db.Table(tableName).Where("id", 1).One()
t.Assert(err, nil)
t.Assert(one["id"].Int(), 1)
t.Assert(one["views"].Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
gdbCounter := &gdb.Counter{
Field: "views",
Value: -1,
}
updateData := g.Map{
"views": gdbCounter,
"updated_time": gtime.Now().Unix(),
}
result, err := db.Update(tableName, updateData, "id", 1)
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 1)
one, err := db.Table(tableName).Where("id", 1).One()
t.Assert(err, nil)
t.Assert(one["id"].Int(), 1)
t.Assert(one["views"].Int(), 0)
})
}

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,
@ -11,6 +11,9 @@ import (
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/debug/gdebug"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/util/gutil"
"testing"
"time"
@ -52,20 +55,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 +83,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 +97,164 @@ func Test_Model_Insert(t *testing.T) {
n, _ = result.RowsAffected()
t.Assert(n, 3)
})
}
// Fix issue: https://github.com/gogf/gf/issues/819
func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type Password struct {
Salt string `json:"salt"`
Pass string `json:"pass"`
}
data := g.Map{
"id": 1,
"passport": "t1",
"password": &Password{"123", "456"},
"nickname": []string{"A", "B", "C"},
"create_time": gtime.Now().String(),
}
_, err := db.Table(table).Data(data).Insert()
t.Assert(err, nil)
one, err := db.Table(table).One("id", 1)
t.Assert(err, nil)
t.Assert(one["passport"], data["passport"])
t.Assert(one["create_time"], data["create_time"])
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
})
}
func Test_Model_Insert_KeyFieldNameMapping(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
Nickname string
CreateTime string
}
data := User{
Id: 1,
Passport: "user_1",
Password: "pass_1",
Nickname: "name_1",
CreateTime: "2020-10-10 12:00:01",
}
_, err := db.Model(table).Data(data).Insert()
t.Assert(err, nil)
one, err := db.Model(table).FindOne(1)
t.Assert(err, nil)
t.Assert(one["passport"], data.Passport)
t.Assert(one["create_time"], data.CreateTime)
t.Assert(one["nickname"], data.Nickname)
})
}
func Test_Model_Update_KeyFieldNameMapping(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
Nickname string
CreateTime string
}
data := User{
Id: 1,
Passport: "user_10",
Password: "pass_10",
Nickname: "name_10",
CreateTime: "2020-10-10 12:00:01",
}
_, err := db.Model(table).Data(data).WherePri(1).Update()
t.Assert(err, nil)
one, err := db.Model(table).FindOne(1)
t.Assert(err, nil)
t.Assert(one["passport"], data.Passport)
t.Assert(one["create_time"], data.CreateTime)
t.Assert(one["nickname"], data.Nickname)
})
}
func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
Nickname string
CreateTime string
NoneExistFiled string
}
data := User{
Id: 1,
Passport: "user_1",
Password: "pass_1",
Nickname: "name_1",
CreateTime: "2020-10-10 12:00:01",
}
_, err := db.Model(table).Data(data).Insert()
t.AssertNE(err, nil)
})
}
func Test_Model_Insert_Time(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "t1",
"password": "p1",
"nickname": "n1",
"create_time": "2020-10-10 20:09:18.334",
}
_, err := db.Table(table).Data(data).Insert()
t.Assert(err, nil)
one, err := db.Table(table).One("id", 1)
t.Assert(err, nil)
t.Assert(one["passport"], data["passport"])
t.Assert(one["create_time"], "2020-10-10 20:09:18")
t.Assert(one["nickname"], data["nickname"])
})
}
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) {
@ -229,7 +389,7 @@ func Test_Model_Update(t *testing.T) {
defer dropTable(table)
// UPDATE...LIMIT
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Data("nickname", "T100").Order("id desc").Limit(2).Update()
result, err := db.Table(table).Data("nickname", "T100").Where(1).Order("id desc").Limit(2).Update()
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 2)
@ -605,6 +765,32 @@ func Test_Model_Count(t *testing.T) {
t.Assert(err, nil)
t.Assert(count, SIZE)
})
gtest.C(t, func(t *gtest.T) {
count, err := db.Table(table).FieldsEx("id").Where("id>8").Count()
t.Assert(err, nil)
t.Assert(count, 2)
})
gtest.C(t, func(t *gtest.T) {
count, err := db.Table(table).Fields("distinct id,nickname").Where("id>8").Count()
t.Assert(err, nil)
t.Assert(count, 2)
})
// COUNT...LIMIT...
gtest.C(t, func(t *gtest.T) {
count, err := db.Table(table).Page(1, 2).Count()
t.Assert(err, nil)
t.Assert(count, SIZE)
})
//gtest.C(t, func(t *gtest.T) {
// count, err := db.Table(table).Fields("id myid").Where("id>8").Count()
// t.Assert(err, nil)
// t.Assert(count, 2)
//})
//gtest.C(t, func(t *gtest.T) {
// count, err := db.Table(table).As("u1").LeftJoin(table, "u2", "u2.id=u1.id").Fields("u2.id u2id").Where("u1.id>8").Count()
// t.Assert(err, nil)
// t.Assert(count, 2)
//})
}
func Test_Model_FindCount(t *testing.T) {
@ -1024,6 +1210,21 @@ func Test_Model_Where(t *testing.T) {
t.AssertGT(len(result), 0)
t.Assert(result["id"].Int(), 3)
})
// slice
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Where(g.Slice{"id", 3}).One()
t.Assert(err, nil)
t.AssertGT(len(result), 0)
t.Assert(result["id"].Int(), 3)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Where(g.Slice{"id", 3, "nickname", "name_3"}).One()
t.Assert(err, nil)
t.AssertGT(len(result), 0)
t.Assert(result["id"].Int(), 3)
})
// slice parameter
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One()
@ -1589,14 +1790,14 @@ func Test_Model_Delete(t *testing.T) {
// DELETE...LIMIT
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Limit(2).Delete()
result, err := db.Table(table).Where(1).Limit(2).Delete()
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 2)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Delete()
result, err := db.Table(table).Where(1).Delete()
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, SIZE-2)
@ -1856,7 +2057,7 @@ func Test_Model_Option_Where(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createInitTable()
defer dropTable(table)
r, err := db.Table(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).Update()
r, err := db.Table(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).And(1).Update()
t.Assert(err, nil)
n, _ := r.RowsAffected()
t.Assert(n, SIZE)
@ -1935,6 +2136,19 @@ func Test_Model_FieldsEx(t *testing.T) {
})
}
func Test_Model_FieldsEx_WithReservedWords(t *testing.T) {
table := "fieldsex_test_table"
sqlTpcPath := gdebug.TestDataPath("reservedwords_table_tpl.sql")
if _, err := db.Exec(fmt.Sprintf(gfile.GetContents(sqlTpcPath), table)); err != nil {
gtest.Error(err)
}
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := db.Table(table).FieldsEx("content").One()
t.Assert(err, nil)
})
}
func Test_Model_FieldsStr(t *testing.T) {
table := createTable()
defer dropTable(table)
@ -2243,6 +2457,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)
@ -2395,6 +2622,144 @@ func Test_Model_Min_Max(t *testing.T) {
})
}
func Test_Model_Fields_AutoMapping(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
value, err := db.Table(table).Fields("ID").Where("id", 2).Value()
t.Assert(err, nil)
t.Assert(value.Int(), 2)
})
gtest.C(t, func(t *gtest.T) {
value, err := db.Table(table).Fields("NICK_NAME").Where("id", 2).Value()
t.Assert(err, nil)
t.Assert(value.String(), "name_2")
})
// Map
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Fields(g.Map{
"ID": 1,
"NICK_NAME": 1,
}).Where("id", 2).One()
t.Assert(err, nil)
t.Assert(len(one), 2)
t.Assert(one["id"], 2)
t.Assert(one["nickname"], "name_2")
})
// Struct
gtest.C(t, func(t *gtest.T) {
type T struct {
ID int
NICKNAME int
}
one, err := db.Table(table).Fields(&T{
ID: 0,
NICKNAME: 0,
}).Where("id", 2).One()
t.Assert(err, nil)
t.Assert(len(one), 2)
t.Assert(one["id"], 2)
t.Assert(one["nickname"], "name_2")
})
}
func Test_Model_FieldsEx_AutoMapping(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// "id": i,
// "passport": fmt.Sprintf(`user_%d`, i),
// "password": fmt.Sprintf(`pass_%d`, i),
// "nickname": fmt.Sprintf(`name_%d`, i),
// "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(),
gtest.C(t, func(t *gtest.T) {
value, err := db.Table(table).FieldsEx("Passport, Password, NickName, CreateTime").Where("id", 2).Value()
t.Assert(err, nil)
t.Assert(value.Int(), 2)
})
gtest.C(t, func(t *gtest.T) {
value, err := db.Table(table).FieldsEx("ID, Passport, Password, CreateTime").Where("id", 2).Value()
t.Assert(err, nil)
t.Assert(value.String(), "name_2")
})
// Map
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).FieldsEx(g.Map{
"Passport": 1,
"Password": 1,
"CreateTime": 1,
}).Where("id", 2).One()
t.Assert(err, nil)
t.Assert(len(one), 2)
t.Assert(one["id"], 2)
t.Assert(one["nickname"], "name_2")
})
// Struct
gtest.C(t, func(t *gtest.T) {
type T struct {
Passport int
Password int
CreateTime int
}
one, err := db.Table(table).FieldsEx(&T{
Passport: 0,
Password: 0,
CreateTime: 0,
}).Where("id", 2).One()
t.Assert(err, nil)
t.Assert(len(one), 2)
t.Assert(one["id"], 2)
t.Assert(one["nickname"], "name_2")
})
}
func Test_Model_Fields_Struct(t *testing.T) {
table := createInitTable()
defer dropTable(table)
type A struct {
Passport string
Password string
}
type B struct {
A
NickName string
}
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Fields(A{}).Where("id", 2).One()
t.Assert(err, nil)
t.Assert(len(one), 2)
t.Assert(one["passport"], "user_2")
t.Assert(one["password"], "pass_2")
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Fields(&A{}).Where("id", 2).One()
t.Assert(err, nil)
t.Assert(len(one), 2)
t.Assert(one["passport"], "user_2")
t.Assert(one["password"], "pass_2")
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Fields(B{}).Where("id", 2).One()
t.Assert(err, nil)
t.Assert(len(one), 3)
t.Assert(one["passport"], "user_2")
t.Assert(one["password"], "pass_2")
t.Assert(one["nickname"], "name_2")
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Fields(&B{}).Where("id", 2).One()
t.Assert(err, nil)
t.Assert(len(one), 3)
t.Assert(one["passport"], "user_2")
t.Assert(one["password"], "pass_2")
t.Assert(one["nickname"], "name_2")
})
}
func Test_Model_NullField(t *testing.T) {
table := createTable()
defer dropTable(table)
@ -2471,3 +2836,204 @@ func Test_Model_HasField(t *testing.T) {
t.Assert(err, nil)
})
}
// Issue: https://github.com/gogf/gf/issues/1002
func Test_Model_Issue1002(t *testing.T) {
table := createTable()
defer dropTable(table)
result, err := db.Table(table).Data(g.Map{
"id": 1,
"passport": "port_1",
"password": "pass_1",
"nickname": "name_2",
"create_time": "2020-10-27 19:03:33",
}).Insert()
gtest.Assert(err, nil)
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
// where + string.
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").Value()
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").FindValue()
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").FindValue("id")
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
// where + string arguments.
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").Value()
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").FindValue()
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").FindValue("id")
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
// where + gtime.Time arguments.
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).Value()
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).FindValue()
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).FindValue("id")
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
// where + time.Time arguments, UTC.
t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:32")
t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:34")
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).Value()
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).FindValue()
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
gtest.C(t, func(t *gtest.T) {
v, err := db.Table(table).Where("create_time>? and create_time<?", t1, t2).FindValue("id")
t.Assert(err, nil)
t.Assert(v.Int(), 1)
})
}
func createTableForTimeZoneTest() string {
tableName := "user_" + gtime.Now().TimestampNanoStr()
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
passport varchar(45) NULL,
password char(32) NULL,
nickname varchar(45) NULL,
created_at timestamp NULL,
updated_at timestamp NULL,
deleted_at timestamp NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableName,
)); err != nil {
gtest.Fatal(err)
}
return tableName
}
// https://github.com/gogf/gf/issues/1012
func Test_TimeZoneInsert(t *testing.T) {
tableName := createTableForTimeZoneTest()
defer dropTable(tableName)
asiaLocal, err := time.LoadLocation("Asia/Shanghai")
gtest.Assert(err, nil)
CreateTime := "2020-11-22 12:23:45"
UpdateTime := "2020-11-22 13:23:45"
DeleteTime := "2020-11-22 14:23:45"
type User struct {
Id int `json:"id"`
CreatedAt *gtime.Time `json:"created_at"`
UpdatedAt gtime.Time `json:"updated_at"`
DeletedAt time.Time `json:"deleted_at"`
}
t1, _ := time.ParseInLocation("2006-01-02 15:04:05", CreateTime, asiaLocal)
t2, _ := time.ParseInLocation("2006-01-02 15:04:05", UpdateTime, asiaLocal)
t3, _ := time.ParseInLocation("2006-01-02 15:04:05", DeleteTime, asiaLocal)
u := &User{
Id: 1,
CreatedAt: gtime.New(t1.UTC()),
UpdatedAt: *gtime.New(t2.UTC()),
DeletedAt: t3.UTC(),
}
gtest.C(t, func(t *gtest.T) {
_, _ = db.Table(tableName).Unscoped().Insert(u)
userEntity := &User{}
err := db.Table(tableName).Where("id", 1).Unscoped().Struct(&userEntity)
t.Assert(err, nil)
t.Assert(userEntity.CreatedAt.String(), "2020-11-22 04:23:45")
t.Assert(userEntity.UpdatedAt.String(), "2020-11-22 05:23:45")
t.Assert(gtime.NewFromTime(userEntity.DeletedAt).String(), "2020-11-22 06:23:45")
})
}
func Test_Model_Fields_Map_Struct(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// map
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Fields(g.Map{
"ID": 1,
"PASSPORT": 1,
"NONE_EXIST": 1,
}).Where("id", 1).One()
t.Assert(err, nil)
t.Assert(len(result), 2)
t.Assert(result["id"], 1)
t.Assert(result["passport"], "user_1")
})
// struct
gtest.C(t, func(t *gtest.T) {
type A struct {
ID int
PASSPORT string
XXX_TYPE int
}
var a = A{}
err := db.Table(table).Fields(a).Where("id", 1).Struct(&a)
t.Assert(err, nil)
t.Assert(a.ID, 1)
t.Assert(a.PASSPORT, "user_1")
t.Assert(a.XXX_TYPE, 0)
})
// *struct
gtest.C(t, func(t *gtest.T) {
type A struct {
ID int
PASSPORT string
XXX_TYPE int
}
var a *A
err := db.Table(table).Fields(a).Where("id", 1).Struct(&a)
t.Assert(err, nil)
t.Assert(a.ID, 1)
t.Assert(a.PASSPORT, "user_1")
t.Assert(a.XXX_TYPE, 0)
})
// **struct
gtest.C(t, func(t *gtest.T) {
type A struct {
ID int
PASSPORT string
XXX_TYPE int
}
var a *A
err := db.Table(table).Fields(&a).Where("id", 1).Struct(&a)
t.Assert(err, nil)
t.Assert(a.ID, 1)
t.Assert(a.PASSPORT, "user_1")
t.Assert(a.XXX_TYPE, 0)
})
}

View File

@ -0,0 +1,86 @@
// Copyright GoFrame 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_test
import (
"github.com/gogf/gf/frame/g"
"testing"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/test/gtest"
)
func Test_Insert_Raw(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
user := db.Table(table)
result, err := user.Filter().Data(g.Map{
"id": gdb.Raw("id+2"),
"passport": "port_1",
"password": "pass_1",
"nickname": "name_1",
"create_time": gdb.Raw("now()"),
}).Insert()
t.Assert(err, nil)
n, _ := result.LastInsertId()
t.Assert(n, 2)
})
}
func Test_BatchInsert_Raw(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
user := db.Table(table)
result, err := user.Filter().Data(
g.List{
g.Map{
"id": gdb.Raw("id+2"),
"passport": "port_2",
"password": "pass_2",
"nickname": "name_2",
"create_time": gdb.Raw("now()"),
},
g.Map{
"id": gdb.Raw("id+4"),
"passport": "port_4",
"password": "pass_4",
"nickname": "name_4",
"create_time": gdb.Raw("now()"),
},
},
).Insert()
t.Assert(err, nil)
n, _ := result.LastInsertId()
t.Assert(n, 4)
})
}
func Test_Update_Raw(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
user := db.Table(table)
result, err := user.Data(g.Map{
"id": gdb.Raw("id+100"),
"create_time": gdb.Raw("now()"),
}).Where("id", 1).Update()
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 1)
})
gtest.C(t, func(t *gtest.T) {
user := db.Table(table)
n, err := user.Where("id", 101).Count()
t.Assert(err, nil)
t.Assert(n, 1)
})
}

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame 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,

Some files were not shown because too many files have changed in this diff Show More