Skip to content

Commit fbdb365

Browse files
committed
增加foreach动态sql支持
1 parent 3c2cbe9 commit fbdb365

File tree

6 files changed

+130
-7
lines changed

6 files changed

+130
-7
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ where| where 元素只会在至少有一个子元素的条件返回 SQL 子句
1717
set | set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号。
1818
include | 使用sql标签定义的语句替换。
1919
choose<br>when<br>otherwise | 有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,gobatis 提供了 choose 元素,它有点像switch 语句。
20+
foreach | foreach 允许指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。
2021

2122
## 待完成项
2223

23-
* 继续完善动态sql支持(foreach、trim)
24+
* 继续完善动态sql支持(trim)
2425
* ~~性能优化:增加动态sql缓存~~
2526
(已经实现,但测试发现性能提升很小,目前该功能被关闭)
2627

parsing/dynamics.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ func (m *DynamicData) ReplaceWithMap(objParams map[string]interface{}) string {
4141

4242
getFunc := func(s string) string {
4343
if o, ok := objParams[s]; ok {
44+
if str, ok := o.(string); ok {
45+
return str
46+
}
47+
4448
//zero time convert to empty string (for <if> </if> element)
4549
if ti, ok := o.(time.Time); ok {
4650
if ti.IsZero() {
@@ -49,6 +53,7 @@ func (m *DynamicData) ReplaceWithMap(objParams map[string]interface{}) string {
4953
return ti.String()
5054
}
5155
}
56+
5257
var str string
5358
reflection.SafeSetValue(reflect.ValueOf(&str), o)
5459
return str

parsing/xml/dynamics.go

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ package xml
1010

1111
import (
1212
"encoding/xml"
13+
"fmt"
1314
"github.com/xfali/gobatis/logging"
1415
"github.com/xfali/gobatis/parsing"
16+
"github.com/xfali/gobatis/reflection"
17+
"strconv"
1518
"strings"
1619
"unicode"
1720
)
@@ -23,6 +26,7 @@ type Foreach struct {
2326
Index string `xml:"index,attr"`
2427
Open string `xml:"open,attr"`
2528
Close string `xml:"close,attr"`
29+
Data string `xml:",chardata"`
2630
}
2731

2832
type Sql struct {
@@ -43,7 +47,7 @@ type Include struct {
4347

4448
type If struct {
4549
Test string `xml:"test,attr"`
46-
Date string `xml:",chardata"`
50+
Data string `xml:",chardata"`
4751
}
4852

4953
type Where struct {
@@ -60,7 +64,7 @@ type When struct {
6064
}
6165

6266
type Otherwise struct {
63-
Date string `xml:",chardata"`
67+
Data string `xml:",chardata"`
6468
}
6569

6670
type Choose struct {
@@ -91,15 +95,15 @@ func (de *If) Format(getFunc func(key string) string) string {
9195
return ""
9296
}
9397
}
94-
return strings.TrimSpace(de.Date)
98+
return strings.TrimSpace(de.Data)
9599
}
96100

97101
ret := false
98102
if len(orStrs) != 0 {
99103
for _, v := range orStrs {
100104
ret = Compare(v, getFunc)
101105
if ret == true {
102-
return strings.TrimSpace(de.Date)
106+
return strings.TrimSpace(de.Data)
103107
}
104108
}
105109
if ret == false {
@@ -242,11 +246,42 @@ func (de *Choose) Format(getFunc func(key string) string) string {
242246
}
243247
retStr := ret.String()
244248
if retStr == "" {
245-
retStr = strings.TrimSpace(de.Otherwise.Date)
249+
retStr = strings.TrimSpace(de.Otherwise.Data)
246250
}
247251
return retStr
248252
}
249253

254+
func (de *Foreach) Format(getFunc func(key string) string) string {
255+
if de.Collection == "" {
256+
return ""
257+
}
258+
259+
listStr := getValueFromFunc(de.Collection, getFunc)
260+
if listStr == "" || listStr == "nil" {
261+
return ""
262+
}
263+
264+
elems := reflection.ParseSliceParamString(listStr)
265+
ret := strings.Builder{}
266+
ret.WriteString(de.Open)
267+
itemStr := fmt.Sprintf("#{%s}", de.Item)
268+
indexStr := fmt.Sprintf("#{%s}", de.Index)
269+
length := len(elems)
270+
originData := strings.TrimSpace(de.Data)
271+
realStr := ""
272+
for i, v := range elems {
273+
realStr = strings.Replace(originData, itemStr, v, -1)
274+
realStr = strings.Replace(realStr, indexStr, strconv.Itoa(i), -1)
275+
ret.WriteString(realStr)
276+
if i < length-1 {
277+
ret.WriteString(de.Separator)
278+
}
279+
}
280+
ret.WriteString(de.Close)
281+
282+
return ret.String()
283+
}
284+
250285
func escape(src string) string {
251286
src = strings.Replace(src, "&lt;", "<", -1)
252287
src = strings.Replace(src, "&gt;", ">", -1)
@@ -266,13 +301,15 @@ type WhereProcessor string
266301
type SetProcessor string
267302
type IncludeProcessor string
268303
type ChooseProcessor string
304+
type ForeachProcessor string
269305

270306
var gProcessorMap = map[string]typeProcessor{
271307
"if": IfProcessor("if"),
272308
"where": WhereProcessor("where"),
273309
"set": SetProcessor("set"),
274310
"include": IncludeProcessor("include"),
275311
"choose": ChooseProcessor("choose"),
312+
"foreach": ForeachProcessor("foreach"),
276313
}
277314

278315
func (d IfProcessor) EndStr() string {
@@ -335,6 +372,18 @@ func (d ChooseProcessor) Parse(src string) parsing.DynamicElement {
335372
return &v
336373
}
337374

375+
func (d ForeachProcessor) EndStr() string {
376+
return "</" + string(d) + ">"
377+
}
378+
379+
func (d ForeachProcessor) Parse(src string) parsing.DynamicElement {
380+
v := Foreach{}
381+
if xml.Unmarshal([]byte(src), &v) != nil {
382+
logging.Warn("parse if element failed")
383+
}
384+
return &v
385+
}
386+
338387
func ParseDynamic(src string, sqls []Sql) (*parsing.DynamicData, error) {
339388
src = escape(src)
340389

reflection/parseparam.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@
99
package reflection
1010

1111
import (
12+
"github.com/xormplus/builder"
1213
"reflect"
1314
"strconv"
15+
"strings"
16+
)
17+
18+
const(
19+
slice_param_separator = "_&eLEm_"
1420
)
1521

1622
type paramParser struct {
@@ -60,7 +66,29 @@ func (parser *paramParser)parseOne(v interface{}) {
6066
// }
6167
// parser.parseOne(elemV.Interface())
6268
//}
63-
parser.ret[strconv.Itoa(parser.index)] = v
69+
l := rv.Len()
70+
builder := builder.StringBuilder{}
71+
for i := 0; i < l; i++ {
72+
elemV := rv.Index(i)
73+
if !elemV.CanInterface() {
74+
elemV = reflect.Indirect(elemV)
75+
}
76+
if elemV.Kind() == reflect.String {
77+
builder.WriteString(elemV.String())
78+
} else {
79+
var str string
80+
if SafeSetValue(reflect.ValueOf(&str), elemV.Interface()) {
81+
builder.WriteString(str)
82+
} else {
83+
//log
84+
}
85+
}
86+
87+
if i < l - 1 {
88+
builder.WriteString(slice_param_separator)
89+
}
90+
}
91+
parser.ret[strconv.Itoa(parser.index)] = builder.String()
6492
parser.index++
6593
} else if rt.Kind() == reflect.Map {
6694
keys := rv.MapKeys()
@@ -79,6 +107,10 @@ func (parser *paramParser)parseOne(v interface{}) {
79107
}
80108
}
81109

110+
func ParseSliceParamString(src string) []string {
111+
return strings.Split(src, slice_param_separator)
112+
}
113+
82114
func structKey(oi *StructInfo, field string) string {
83115
return oi.Name + "." + field
84116
}

test/reflection_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,20 @@ func TestReflectionParseComplex(t *testing.T) {
162162
}
163163
}
164164

165+
func TestReflectionParseSlice(t *testing.T) {
166+
ret := reflection.ParseParams([]int{1,2,3,4})
167+
if len(ret) == 0 {
168+
t.Fail()
169+
}
170+
for k, v := range ret {
171+
t.Logf("complex key : %s value : %v", k, v)
172+
elems := reflection.ParseSliceParamString(v.(string))
173+
for _, e := range elems {
174+
t.Logf("slice elem %v\n", e)
175+
}
176+
}
177+
}
178+
165179
func TestSimpleTypeTime(t *testing.T) {
166180
ret := reflection.IsSimpleObject(time.Time{})
167181
if !ret {

test/xmlparse_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,25 @@ func TestXmlDynamicWhere(t *testing.T) {
369369
}
370370
})
371371
}
372+
373+
func TestXmlDynamicForeach(t *testing.T) {
374+
src := `SELECT * FROM PERSON WHERE IN
375+
<foreach item="item" index="index" collection="{0}"
376+
open="(" separator="," close=")">
377+
#{item}
378+
</foreach>
379+
`
380+
logging.SetLevel(logging.DEBUG)
381+
m, err := xml.ParseDynamic(src, nil)
382+
if err != nil {
383+
t.Fatal(err)
384+
}
385+
t.Run("foreach first", func(t *testing.T) {
386+
params := []string{
387+
"first", "second",
388+
}
389+
ret := m.Replace(params)
390+
t.Logf("arg first : %s\n", ret)
391+
392+
})
393+
}

0 commit comments

Comments
 (0)