@@ -5,10 +5,10 @@ import (
55 "reflect"
66 "slices"
77 "sort"
8- "strings"
98
109 "github.com/databricks/cli/libs/structs/structpath"
1110 "github.com/databricks/cli/libs/structs/structtag"
11+ "github.com/databricks/cli/libs/structs/structtrie"
1212)
1313
1414type Change struct {
@@ -58,23 +58,53 @@ func (c *keyFuncCaller) call(elem any) (string, string) {
5858 return keyField , keyValue
5959}
6060
61- // diffContext holds configuration for the diff operation.
62- type diffContext struct {
63- sliceKeys map [string ]KeyFunc
61+ func advanceTrie (root * structtrie.Node , node * structtrie.Node , pathNode * structpath.PathNode ) * structtrie.Node {
62+ if root == nil {
63+ return nil
64+ }
65+ if node == nil {
66+ node = root
67+ }
68+ return node .Child (pathNode )
69+ }
70+
71+ func keyFuncFor (node * structtrie.Node ) KeyFunc {
72+ if node == nil {
73+ return nil
74+ }
75+ if value := node .Value (); value != nil {
76+ return value .(* keyFuncCaller )
77+ }
78+ return nil
79+ }
80+
81+ // BuildSliceKeyTrie converts a map of slice-key patterns to a PrefixTree used by GetStructDiff.
82+ // Returns nil if sliceKeys is empty.
83+ func BuildSliceKeyTrie (sliceKeys map [string ]KeyFunc ) (* structtrie.Node , error ) {
84+ if len (sliceKeys ) == 0 {
85+ return nil , nil
86+ }
87+
88+ root := structtrie .New ()
89+ for pattern , fn := range sliceKeys {
90+ caller , err := newKeyFuncCaller (fn )
91+ if err != nil {
92+ return nil , err
93+ }
94+ if _ , err := structtrie .InsertString (root , pattern , caller ); err != nil {
95+ return nil , err
96+ }
97+ }
98+ return root , nil
6499}
65100
66101// GetStructDiff compares two Go structs and returns a list of Changes or an error.
67102// Respects ForceSendFields if present.
68103// Types of a and b must match exactly, otherwise returns an error.
69104//
70- // The sliceKeys parameter maps path patterns to functions that extract
71- // key field/value pairs from slice elements. When provided, slices at matching
72- // paths are compared as maps keyed by (keyField, keyValue) instead of by index.
73- // Path patterns use dot notation (e.g., "tasks" or "job.tasks").
74- // The [*] wildcard matches any slice index in the path.
75- // Note, key wildcard is not supported yet ("a.*.c")
76- // Pass nil if no slice key functions are needed.
77- func GetStructDiff (a , b any , sliceKeys map [string ]KeyFunc ) ([]Change , error ) {
105+ // The sliceTrie parameter is produced by BuildSliceKeyTrie and allows comparing slices
106+ // as maps keyed by (keyField, keyValue). Pass nil if no keyed slices are needed.
107+ func GetStructDiff (a , b any , sliceTrie * structtrie.Node ) ([]Change , error ) {
78108 v1 := reflect .ValueOf (a )
79109 v2 := reflect .ValueOf (b )
80110
@@ -93,16 +123,15 @@ func GetStructDiff(a, b any, sliceKeys map[string]KeyFunc) ([]Change, error) {
93123 return nil , fmt .Errorf ("type mismatch: %v vs %v" , v1 .Type (), v2 .Type ())
94124 }
95125
96- ctx := & diffContext {sliceKeys : sliceKeys }
97- if err := diffValues (ctx , nil , v1 , v2 , & changes ); err != nil {
126+ if err := diffValues (sliceTrie , sliceTrie , nil , v1 , v2 , & changes ); err != nil {
98127 return nil , err
99128 }
100129 return changes , nil
101130}
102131
103132// diffValues appends changes between v1 and v2 to the slice. path is the current
104133// JSON-style path (dot + brackets). At the root path is "".
105- func diffValues (ctx * diffContext , path * structpath.PathNode , v1 , v2 reflect.Value , changes * []Change ) error {
134+ func diffValues (trieRoot * structtrie. Node , trieNode * structtrie. Node , path * structpath.PathNode , v1 , v2 reflect.Value , changes * []Change ) error {
106135 if ! v1 .IsValid () {
107136 if ! v2 .IsValid () {
108137 return nil
@@ -145,25 +174,26 @@ func diffValues(ctx *diffContext, path *structpath.PathNode, v1, v2 reflect.Valu
145174
146175 switch kind {
147176 case reflect .Pointer :
148- return diffValues (ctx , path , v1 .Elem (), v2 .Elem (), changes )
177+ return diffValues (trieRoot , trieNode , path , v1 .Elem (), v2 .Elem (), changes )
149178 case reflect .Struct :
150- return diffStruct (ctx , path , v1 , v2 , changes )
179+ return diffStruct (trieRoot , trieNode , path , v1 , v2 , changes )
151180 case reflect .Slice , reflect .Array :
152- if keyFunc := ctx . findKeyFunc ( path ); keyFunc != nil {
153- return diffSliceByKey (ctx , path , v1 , v2 , keyFunc , changes )
181+ if keyFunc := keyFuncFor ( trieNode ); keyFunc != nil {
182+ return diffSliceByKey (trieRoot , trieNode , path , v1 , v2 , keyFunc , changes )
154183 } else if v1 .Len () != v2 .Len () {
155184 * changes = append (* changes , Change {Path : path , Old : v1 .Interface (), New : v2 .Interface ()})
156185 } else {
157186 for i := range v1 .Len () {
158187 node := structpath .NewIndex (path , i )
159- if err := diffValues (ctx , node , v1 .Index (i ), v2 .Index (i ), changes ); err != nil {
188+ nextTrie := advanceTrie (trieRoot , trieNode , node )
189+ if err := diffValues (trieRoot , nextTrie , node , v1 .Index (i ), v2 .Index (i ), changes ); err != nil {
160190 return err
161191 }
162192 }
163193 }
164194 case reflect .Map :
165195 if v1Type .Key ().Kind () == reflect .String {
166- return diffMapStringKey (ctx , path , v1 , v2 , changes )
196+ return diffMapStringKey (trieRoot , trieNode , path , v1 , v2 , changes )
167197 } else {
168198 deepEqualValues (path , v1 , v2 , changes )
169199 }
@@ -179,7 +209,7 @@ func deepEqualValues(path *structpath.PathNode, v1, v2 reflect.Value, changes *[
179209 }
180210}
181211
182- func diffStruct (ctx * diffContext , path * structpath.PathNode , s1 , s2 reflect.Value , changes * []Change ) error {
212+ func diffStruct (trieRoot * structtrie. Node , trieNode * structtrie. Node , path * structpath.PathNode , s1 , s2 reflect.Value , changes * []Change ) error {
183213 t := s1 .Type ()
184214 forced1 := getForceSendFields (s1 )
185215 forced2 := getForceSendFields (s2 )
@@ -192,7 +222,7 @@ func diffStruct(ctx *diffContext, path *structpath.PathNode, s1, s2 reflect.Valu
192222
193223 // Continue traversing embedded structs. Do not add the key to the path though.
194224 if sf .Anonymous {
195- if err := diffValues (ctx , path , s1 .Field (i ), s2 .Field (i ), changes ); err != nil {
225+ if err := diffValues (trieRoot , trieNode , path , s1 .Field (i ), s2 .Field (i ), changes ); err != nil {
196226 return err
197227 }
198228 continue
@@ -228,14 +258,15 @@ func diffStruct(ctx *diffContext, path *structpath.PathNode, s1, s2 reflect.Valu
228258 }
229259 }
230260
231- if err := diffValues (ctx , node , v1Field , v2Field , changes ); err != nil {
261+ nextTrie := advanceTrie (trieRoot , trieNode , node )
262+ if err := diffValues (trieRoot , nextTrie , node , v1Field , v2Field , changes ); err != nil {
232263 return err
233264 }
234265 }
235266 return nil
236267}
237268
238- func diffMapStringKey (ctx * diffContext , path * structpath.PathNode , m1 , m2 reflect.Value , changes * []Change ) error {
269+ func diffMapStringKey (trieRoot * structtrie. Node , trieNode * structtrie. Node , path * structpath.PathNode , m1 , m2 reflect.Value , changes * []Change ) error {
239270 keySet := map [string ]reflect.Value {}
240271 for _ , k := range m1 .MapKeys () {
241272 // Key is always string at this point
@@ -258,7 +289,8 @@ func diffMapStringKey(ctx *diffContext, path *structpath.PathNode, m1, m2 reflec
258289 v1 := m1 .MapIndex (k )
259290 v2 := m2 .MapIndex (k )
260291 node := structpath .NewStringKey (path , ks )
261- if err := diffValues (ctx , node , v1 , v2 , changes ); err != nil {
292+ nextTrie := advanceTrie (trieRoot , trieNode , node )
293+ if err := diffValues (trieRoot , nextTrie , node , v1 , v2 , changes ); err != nil {
262294 return err
263295 }
264296 }
@@ -280,50 +312,6 @@ func getForceSendFields(v reflect.Value) []string {
280312 return nil
281313}
282314
283- // findKeyFunc returns the KeyFunc for the given path, or nil if none matches.
284- // Path patterns support [*] to match any slice index.
285- func (ctx * diffContext ) findKeyFunc (path * structpath.PathNode ) KeyFunc {
286- if ctx .sliceKeys == nil {
287- return nil
288- }
289- pathStr := pathToPattern (path )
290- fmt .Printf ("looking up %q\n " , pathStr )
291- return ctx .sliceKeys [pathStr ]
292- }
293-
294- // pathToPattern converts a PathNode to a pattern string for matching.
295- // Slice indices are converted to [*] wildcard.
296- func pathToPattern (path * structpath.PathNode ) string {
297- if path == nil {
298- return ""
299- }
300-
301- components := path .AsSlice ()
302- var result strings.Builder
303-
304- for i , node := range components {
305- if idx , ok := node .Index (); ok {
306- // Convert numeric index to wildcard
307- _ = idx
308- result .WriteString ("[*]" )
309- } else if key , value , ok := node .KeyValue (); ok {
310- // Key-value syntax
311- result .WriteString ("[" )
312- result .WriteString (key )
313- result .WriteString ("=" )
314- result .WriteString (structpath .EncodeMapKey (value ))
315- result .WriteString ("]" )
316- } else if key , ok := node .StringKey (); ok {
317- if i != 0 {
318- result .WriteString ("." )
319- }
320- result .WriteString (key )
321- }
322- }
323-
324- return result .String ()
325- }
326-
327315// sliceElement holds a slice element with its key information.
328316type sliceElement struct {
329317 keyField string
@@ -347,7 +335,7 @@ func validateKeyFuncElementType(seq reflect.Value, expected reflect.Type) error
347335// diffSliceByKey compares two slices using the provided key function.
348336// Elements are matched by their (keyField, keyValue) pairs instead of by index.
349337// Duplicate keys are allowed and matched in order.
350- func diffSliceByKey (ctx * diffContext , path * structpath.PathNode , v1 , v2 reflect.Value , keyFunc KeyFunc , changes * []Change ) error {
338+ func diffSliceByKey (trieRoot * structtrie. Node , trieNode * structtrie. Node , path * structpath.PathNode , v1 , v2 reflect.Value , keyFunc KeyFunc , changes * []Change ) error {
351339 caller , err := newKeyFuncCaller (keyFunc )
352340 if err != nil {
353341 return err
@@ -405,7 +393,8 @@ func diffSliceByKey(ctx *diffContext, path *structpath.PathNode, v1, v2 reflect.
405393 minLen := min (len (list1 ), len (list2 ))
406394 for i := range minLen {
407395 node := structpath .NewKeyValue (path , keyField , keyValue )
408- if err := diffValues (ctx , node , list1 [i ].value , list2 [i ].value , changes ); err != nil {
396+ nextTrie := advanceTrie (trieRoot , trieNode , node )
397+ if err := diffValues (trieRoot , nextTrie , node , list1 [i ].value , list2 [i ].value , changes ); err != nil {
409398 return err
410399 }
411400 }
0 commit comments