@@ -19,6 +19,7 @@ package node
1919import (
2020 "fmt"
2121 "reflect"
22+ "strings"
2223
2324 "github.com/go-juicedev/juice/driver"
2425 "github.com/go-juicedev/juice/eval"
@@ -77,22 +78,65 @@ type ForeachNode struct {
7778
7879// Accept accepts parameters and returns query and arguments.
7980func (f ForeachNode ) Accept (translator driver.Translator , p eval.Parameter ) (query string , args []any , err error ) {
81+ builder := getStringBuilder ()
82+ defer putStringBuilder (builder )
83+
84+ // Estimation logic for pre-allocation
85+ var totalPlaceholders int
86+ for _ , node := range f .Nodes {
87+ if t , ok := node .(* TextNode ); ok {
88+ for _ , token := range t .tokens {
89+ if ! token .isFormat {
90+ totalPlaceholders ++
91+ }
92+ }
93+ } else {
94+ totalPlaceholders += 2
95+ }
96+ }
97+
98+ // We don't know the exact length yet, but we can guess from p
99+ coll , exists := p .Get (f .Collection )
100+ var length int
101+ if exists {
102+ c := coll
103+ for c .Kind () == reflect .Interface || c .Kind () == reflect .Ptr {
104+ if c .IsNil () {
105+ break
106+ }
107+ c = c .Elem ()
108+ }
109+ if c .Kind () == reflect .Slice || c .Kind () == reflect .Array || c .Kind () == reflect .Map {
110+ length = c .Len ()
111+ }
112+ }
113+
114+ argsList := make ([]any , 0 , length * totalPlaceholders )
115+
116+ if err = f .AcceptTo (translator , p , builder , & argsList ); err != nil {
117+ return "" , nil , err
118+ }
119+
120+ return builder .String (), argsList , nil
121+ }
122+
123+ func (f ForeachNode ) AcceptTo (translator driver.Translator , p eval.Parameter , builder * strings.Builder , args * []any ) error {
80124 p = f .BindNodes .ConvertParameter (p )
81125
82126 // if item already exists
83127 if _ , exists := p .Get (f .Item ); exists {
84- return "" , nil , fmt .Errorf ("item %s already exists" , f .Item )
128+ return fmt .Errorf ("item %s already exists" , f .Item )
85129 }
86130
87131 // one collection from parameter
88132 value , exists := p .Get (f .Collection )
89133 if ! exists {
90- return "" , nil , fmt .Errorf ("collection %s not found" , f .Collection )
134+ return fmt .Errorf ("collection %s not found" , f .Collection )
91135 }
92136
93137 // if valueItem can not be iterated
94138 if ! value .CanInterface () {
95- return "" , nil , fmt .Errorf ("collection %s can not be iterated" , f .Collection )
139+ return fmt .Errorf ("collection %s can not be iterated" , f .Collection )
96140 }
97141
98142 // if valueItem is not a slice
@@ -102,36 +146,21 @@ func (f ForeachNode) Accept(translator driver.Translator, p eval.Parameter) (que
102146
103147 switch value .Kind () {
104148 case reflect .Array , reflect .Slice :
105- return f .acceptSlice (value , translator , p )
149+ return f .acceptSliceTo (value , translator , p , builder , args )
106150 case reflect .Map :
107- return f .acceptMap (value , translator , p )
151+ return f .acceptMapTo (value , translator , p , builder , args )
108152 default :
109- return "" , nil , fmt .Errorf ("collection %s is not a slice or map" , f .Collection )
153+ return fmt .Errorf ("collection %s is not a slice or map" , f .Collection )
110154 }
111155}
112156
113- func (f ForeachNode ) acceptSlice (value reflect.Value , translator driver.Translator , p eval.Parameter ) ( query string , args []any , err error ) {
157+ func (f ForeachNode ) acceptSliceTo (value reflect.Value , translator driver.Translator , p eval.Parameter , builder * strings. Builder , args * []any ) error {
114158 sliceLength := value .Len ()
115159
116160 if sliceLength == 0 {
117- return "" , nil , nil
161+ return nil
118162 }
119163
120- // Pre-allocate args slice capacity to avoid multiple growths
121- // Estimate: number of slice elements * number of Nodes
122- estimatedArgsLen := sliceLength * len (f .Nodes )
123-
124- args = make ([]any , 0 , estimatedArgsLen )
125-
126- // Pre-allocate string builder capacity to minimize buffer reallocations
127- // Capacity = open + items + separators + close
128- estimatedBuilderCap := len (f .Open ) + (2 * sliceLength ) + (len (f .Separator ) * (sliceLength - 1 )) + len (f .Close )
129-
130- var builder = getStringBuilder ()
131- defer putStringBuilder (builder )
132-
133- builder .Grow (estimatedBuilderCap )
134-
135164 builder .WriteString (f .Open )
136165
137166 end := sliceLength - 1
@@ -147,15 +176,21 @@ func (f ForeachNode) acceptSlice(value reflect.Value, translator driver.Translat
147176 }
148177
149178 for _ , node := range f .Nodes {
150- q , a , err := node .Accept (translator , fp )
151- if err != nil {
152- return "" , nil , err
153- }
154- if len (q ) > 0 {
155- builder .WriteString (q )
156- }
157- if len (a ) > 0 {
158- args = append (args , a ... )
179+ if nw , ok := node .(NodeWriter ); ok {
180+ if err := nw .AcceptTo (translator , fp , builder , args ); err != nil {
181+ return err
182+ }
183+ } else {
184+ q , a , err := node .Accept (translator , fp )
185+ if err != nil {
186+ return err
187+ }
188+ if len (q ) > 0 {
189+ builder .WriteString (q )
190+ }
191+ if len (a ) > 0 {
192+ * args = append (* args , a ... )
193+ }
159194 }
160195 }
161196
@@ -168,31 +203,16 @@ func (f ForeachNode) acceptSlice(value reflect.Value, translator driver.Translat
168203 // if sliceLength is not zero, add close
169204 builder .WriteString (f .Close )
170205
171- return builder . String (), args , nil
206+ return nil
172207}
173208
174- func (f ForeachNode ) acceptMap (value reflect.Value , translator driver.Translator , p eval.Parameter ) ( query string , args []any , err error ) {
209+ func (f ForeachNode ) acceptMapTo (value reflect.Value , translator driver.Translator , p eval.Parameter , builder * strings. Builder , args * []any ) error {
175210 mapLength := value .Len ()
176211
177212 if mapLength == 0 {
178- return "" , nil , nil
213+ return nil
179214 }
180215
181- // Pre-allocate args slice capacity to avoid multiple growths
182- // Estimate: number of elements * number of Nodes
183- estimatedArgsLen := mapLength * len (f .Nodes )
184-
185- args = make ([]any , 0 , estimatedArgsLen )
186-
187- // Pre-allocate string builder capacity to minimize buffer reallocations
188- // Capacity = open + items + separators + close
189- estimatedBuilderCap := len (f .Open ) + (2 * mapLength ) + (len (f .Separator ) * (mapLength - 1 )) + len (f .Close )
190-
191- var builder = getStringBuilder ()
192- defer putStringBuilder (builder )
193-
194- builder .Grow (estimatedBuilderCap )
195-
196216 builder .WriteString (f .Open )
197217
198218 end := mapLength - 1
@@ -210,15 +230,21 @@ func (f ForeachNode) acceptMap(value reflect.Value, translator driver.Translator
210230 fp .IndexValue = iter .Key ()
211231
212232 for _ , node := range f .Nodes {
213- q , a , err := node .Accept (translator , fp )
214- if err != nil {
215- return "" , nil , err
216- }
217- if len (q ) > 0 {
218- builder .WriteString (q )
219- }
220- if len (a ) > 0 {
221- args = append (args , a ... )
233+ if nw , ok := node .(NodeWriter ); ok {
234+ if err := nw .AcceptTo (translator , fp , builder , args ); err != nil {
235+ return err
236+ }
237+ } else {
238+ q , a , err := node .Accept (translator , fp )
239+ if err != nil {
240+ return err
241+ }
242+ if len (q ) > 0 {
243+ builder .WriteString (q )
244+ }
245+ if len (a ) > 0 {
246+ * args = append (* args , a ... )
247+ }
222248 }
223249 }
224250
@@ -233,7 +259,7 @@ func (f ForeachNode) acceptMap(value reflect.Value, translator driver.Translator
233259
234260 builder .WriteString (f .Close )
235261
236- return builder . String (), args , nil
262+ return nil
237263}
238264
239- var _ Node = (* ForeachNode )(nil )
265+ var _ NodeWriter = (* ForeachNode )(nil )
0 commit comments