@@ -163,29 +163,22 @@ func CompareResource(
163
163
memberShapeRef := specField .ShapeRef
164
164
memberShape := memberShapeRef .Shape
165
165
166
- // if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name) {
167
- // delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
168
- // }
169
- nilCode := compareNil (
166
+ // Use len, bytes.Equal and HasNilDifference to fast compare types, and
167
+ // try to avoid deep comparison as much as possible.
168
+ fastComparisonOutput , needToCloseBlock := fastCompareTypes (
170
169
compareConfig ,
171
170
memberShape ,
172
171
deltaVarName ,
172
+ fieldPath ,
173
173
firstResAdaptedVarName ,
174
174
secondResAdaptedVarName ,
175
- fieldPath ,
176
175
indentLevel ,
177
176
)
178
-
179
- if nilCode != "" {
180
- // else if a.ko.Spec.Name != nil && b.ko.Spec.Name != nil {
181
- out += fmt .Sprintf (
182
- "%s else if %s != nil && %s != nil {\n " ,
183
- nilCode , firstResAdaptedVarName , secondResAdaptedVarName ,
184
- )
185
- indentLevel ++
186
- }
177
+ out += fastComparisonOutput
187
178
188
179
switch memberShape .Type {
180
+ case "blob" :
181
+ // We already handled the case of blobs above, so we can skip it here.
189
182
case "structure" :
190
183
// Recurse through all the struct's fields and subfields, building
191
184
// nested conditionals and calls to `delta.Add()`...
@@ -197,7 +190,7 @@ func CompareResource(
197
190
firstResAdaptedVarName ,
198
191
secondResAdaptedVarName ,
199
192
fieldPath ,
200
- indentLevel ,
193
+ indentLevel + 1 ,
201
194
)
202
195
case "list" :
203
196
// Returns Go code that compares all the elements of the slice fields...
@@ -209,7 +202,7 @@ func CompareResource(
209
202
firstResAdaptedVarName ,
210
203
secondResAdaptedVarName ,
211
204
fieldPath ,
212
- indentLevel ,
205
+ indentLevel + 1 ,
213
206
)
214
207
case "map" :
215
208
// Returns Go code that compares all the elements of the map fields...
@@ -221,7 +214,7 @@ func CompareResource(
221
214
firstResAdaptedVarName ,
222
215
secondResAdaptedVarName ,
223
216
fieldPath ,
224
- indentLevel ,
217
+ indentLevel + 1 ,
225
218
)
226
219
default :
227
220
// if *a.ko.Spec.Name != *b.ko.Spec.Name) {
@@ -234,15 +227,14 @@ func CompareResource(
234
227
firstResAdaptedVarName ,
235
228
secondResAdaptedVarName ,
236
229
fieldPath ,
237
- indentLevel ,
230
+ indentLevel + 1 ,
238
231
)
239
232
}
240
- if nilCode != "" {
233
+ if needToCloseBlock {
241
234
// }
242
235
out += fmt .Sprintf (
243
236
"%s}\n " , indent ,
244
237
)
245
- indentLevel --
246
238
}
247
239
}
248
240
return out
@@ -285,12 +277,8 @@ func compareNil(
285
277
indent := strings .Repeat ("\t " , indentLevel )
286
278
287
279
switch shape .Type {
288
- case "list" , "blob" :
289
- // for slice types, there is no nilability test. Instead, the normal
290
- // value test checks length of slices.
291
- return ""
292
280
case "boolean" , "string" , "character" , "byte" , "short" , "integer" , "long" ,
293
- "float" , "double" , "timestamp" , "structure" , "map" , " jsonvalue" :
281
+ "float" , "double" , "timestamp" , "structure" , "jsonvalue" :
294
282
// if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name) {
295
283
out += fmt .Sprintf (
296
284
"%sif ackcompare.HasNilDifference(%s, %s) {\n " ,
@@ -355,11 +343,6 @@ func compareScalar(
355
343
"%sif *%s != *%s {\n " ,
356
344
indent , firstResVarName , secondResVarName ,
357
345
)
358
- case "blob" :
359
- out += fmt .Sprintf (
360
- "%sif !bytes.Equal(%s, %s) {\n " ,
361
- indent , firstResVarName , secondResVarName ,
362
- )
363
346
case "timestamp" :
364
347
// if !a.ko.Spec.CreatedAt.Equal(b.ko.Spec.CreatedAt) {
365
348
out += fmt .Sprintf (
@@ -650,29 +633,20 @@ func CompareStruct(
650
633
continue
651
634
}
652
635
653
- // if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name == nil) {
654
- // delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
655
- // }
656
- nilCode := compareNil (
636
+ fastComparisonOutput , needToCloseBlock := fastCompareTypes (
657
637
compareConfig ,
658
638
memberShape ,
659
639
deltaVarName ,
640
+ memberFieldPath ,
660
641
firstResAdaptedVarName ,
661
642
secondResAdaptedVarName ,
662
- memberFieldPath ,
663
643
indentLevel ,
664
644
)
665
-
666
- if nilCode != "" {
667
- // else if a.ko.Spec.Name != nil && b.ko.Spec.Name != nil {
668
- out += fmt .Sprintf (
669
- "%s else if %s != nil && %s != nil {\n " ,
670
- nilCode , firstResAdaptedVarName , secondResAdaptedVarName ,
671
- )
672
- indentLevel ++
673
- }
645
+ out += fastComparisonOutput
674
646
675
647
switch memberShape .Type {
648
+ case "blob" :
649
+ // We already handled the case of blobs above, so we can skip it here.
676
650
case "structure" :
677
651
// Recurse through all the struct's fields and subfields, building
678
652
// nested conditionals and calls to `delta.Add()`...
@@ -684,7 +658,7 @@ func CompareStruct(
684
658
firstResAdaptedVarName ,
685
659
secondResAdaptedVarName ,
686
660
memberFieldPath ,
687
- indentLevel ,
661
+ indentLevel + 1 ,
688
662
)
689
663
case "list" :
690
664
// Returns Go code that compares all the elements of the slice fields...
@@ -696,7 +670,7 @@ func CompareStruct(
696
670
firstResAdaptedVarName ,
697
671
secondResAdaptedVarName ,
698
672
memberFieldPath ,
699
- indentLevel ,
673
+ indentLevel + 1 ,
700
674
)
701
675
case "map" :
702
676
// Returns Go code that compares all the elements of the map fields...
@@ -708,7 +682,7 @@ func CompareStruct(
708
682
firstResAdaptedVarName ,
709
683
secondResAdaptedVarName ,
710
684
memberFieldPath ,
711
- indentLevel ,
685
+ indentLevel + 1 ,
712
686
)
713
687
default :
714
688
// if *a.ko.Spec.Name != *b.ko.Spec.Name {
@@ -721,16 +695,169 @@ func CompareStruct(
721
695
firstResAdaptedVarName ,
722
696
secondResAdaptedVarName ,
723
697
memberFieldPath ,
724
- indentLevel ,
698
+ indentLevel + 1 ,
725
699
)
726
700
}
727
- if nilCode != "" {
701
+ if needToCloseBlock {
728
702
// }
729
703
out += fmt .Sprintf (
730
704
"%s}\n " , indent ,
731
705
)
732
- indentLevel --
733
706
}
734
707
}
735
708
return out
736
709
}
710
+
711
+ // fastCompareTypes outputs Go code that fast-compares two objects of the same
712
+ // type, by leveraging nil check comparison and length checks. This is used
713
+ // when we want to quickly determine if two objects are different, but don't
714
+ // need to know the specific differences. For example, when determining if a
715
+ // that an array of structs has changed, we don't need to know which structs
716
+ // have changed, if the size of the array has changed.
717
+ //
718
+ // Generally, we can distinguish between the following cases:
719
+ // 1. Both objects are structures or scalars type pointers.
720
+ // 2. Both objects are collections (slices or maps).
721
+ // 3. Both objects are blobs.
722
+ //
723
+ // In the case of 1, we can use the HasNilDifference function to
724
+ // eliminate early on the case where one of the objects is nil and other
725
+ // is not.
726
+ //
727
+ // In the case of 2, we can use the built-in len function to eliminate
728
+ // early on the case where the collections have different lengths.
729
+ // The trick here is that len(nil) is 0, so we don't need to check for
730
+ // nils explicitly.
731
+ //
732
+ // In the case of 3, we can use the bytes.Equal function to compare the
733
+ // byte arrays. bytes.Equal works well with nil arrays too.
734
+ // Output code will look something like this:
735
+ //
736
+ // if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name) {
737
+ // delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
738
+ // } else if a.ko.Spec.Name != nil && b.ko.Spec.Name != nil {
739
+ // if *a.ko.Spec.Name != *b.ko.Spec.Name) {
740
+ // delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
741
+ // }
742
+ // }
743
+ //
744
+ // if len(a.ko.Spec.Tags) != len(b.ko.Spec.Tags) {
745
+ // delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
746
+ // } else if len(a.ko.Spec.Tags) > 0 {
747
+ // if !ackcompare.SliceStringPEqual(a.ko.Spec.Tags, b.ko.Spec.Tags) {
748
+ // delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
749
+ // }
750
+ // }
751
+ func fastCompareTypes (
752
+ // struct informing code generator how to compare the field values.
753
+ compareConfig * ackgenconfig.CompareFieldConfig ,
754
+ // AWSSDK Shape describing the type of the field being compared.
755
+ memberShape * awssdkmodel.Shape ,
756
+ // String representing the name of the variable that is of type
757
+ // `*ackcompare.Delta`. We will generate Go code that calls the `Add()`
758
+ // method of this variable when differences between fields are detected.
759
+ deltaVarName string ,
760
+ // String representing the json path of the field being compared, e.g.
761
+ // "Spec.Name".
762
+ fieldPath string ,
763
+ // String representing the name of the variable that represents the first
764
+ // CR under comparison. This will typically be something like "a.ko". See
765
+ // `templates/pkg/resource/delta.go.tpl`.
766
+ firstResVarName string ,
767
+ // String representing the name of the variable that represents the second
768
+ // CR under comparison. This will typically be something like "b.ko". See
769
+ // `templates/pkg/resource/delta.go.tpl`.
770
+ secondResVarName string ,
771
+ // Number of levels of indentation to use
772
+ indentLevel int ,
773
+ ) (string , bool ) {
774
+ out := ""
775
+ needToCloseBlock := false
776
+ indent := strings .Repeat ("\t " , indentLevel )
777
+
778
+ switch memberShape .Type {
779
+ case "list" , "map" :
780
+ // Note once we eliminate the case of non equal sizes, we can
781
+ // safely assume that the objects have the same length and we can
782
+ // we can only iterate over them if they have more than 0 elements.
783
+ //
784
+ // if len(a.ko.Spec.Tags) != len(b.ko.Spec.Tags) {
785
+ // delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
786
+ // } else len(a.ko.Spec.Tags) > 0 {
787
+ // ...
788
+ // }
789
+ out += fmt .Sprintf (
790
+ "%sif len(%s) != len(%s) {\n " ,
791
+ indent ,
792
+ firstResVarName ,
793
+ secondResVarName ,
794
+ )
795
+ out += fmt .Sprintf (
796
+ "%s\t %s.Add(\" %s\" , %s, %s)\n " ,
797
+ indent ,
798
+ deltaVarName ,
799
+ fieldPath ,
800
+ firstResVarName ,
801
+ secondResVarName ,
802
+ )
803
+ // For sure we are inside the else block of the nil/size check, so we need
804
+ // to increase the indentation level.
805
+ out += fmt .Sprintf (
806
+ "%s} else if len(%s) > 0 {\n " ,
807
+ indent ,
808
+ firstResVarName ,
809
+ )
810
+ // For sure we are inside the else block of the nil/size check, so we need
811
+ // to increase the indentation level and ask the caller to close the block.
812
+ needToCloseBlock = true
813
+ case "blob" :
814
+ // Blob is a special case because we need to compare the byte arrays
815
+ // using the bytes.Equal function.
816
+ //
817
+ // if !bytes.Equal(a.ko.Spec.Certificate, b.ko.Spec.Certificate) {
818
+ // delta.Add("Spec.Certificate", a.ko.Spec.Certificate, b.ko.Spec.Certificate)
819
+ // }
820
+ out += fmt .Sprintf (
821
+ "%sif !bytes.Equal(%s, %s) {\n " ,
822
+ indent ,
823
+ firstResVarName ,
824
+ secondResVarName ,
825
+ )
826
+ out += fmt .Sprintf (
827
+ "%s\t %s.Add(\" %s\" , %s, %s)\n " ,
828
+ indent ,
829
+ deltaVarName ,
830
+ fieldPath ,
831
+ firstResVarName ,
832
+ secondResVarName ,
833
+ )
834
+ out += fmt .Sprintf (
835
+ "%s}\n " , indent ,
836
+ )
837
+ default :
838
+ // For any other type, we can use the HasNilDifference function to
839
+ // eliminate early on the case where one of the objects is nil and
840
+ // other is not.
841
+ out += compareNil (
842
+ compareConfig ,
843
+ memberShape ,
844
+ deltaVarName ,
845
+ firstResVarName ,
846
+ secondResVarName ,
847
+ fieldPath ,
848
+ indentLevel ,
849
+ )
850
+
851
+ // if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name) {
852
+ // delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
853
+ // } else if a.ko.Spec.Name != nil && b.ko.Spec.Name != nil {
854
+ out += fmt .Sprintf (
855
+ " else if %s != nil && %s != nil {\n " ,
856
+ firstResVarName , secondResVarName ,
857
+ )
858
+ // For sure we are inside the else block of the nil/size check, so we need
859
+ // to increase the indentation level and ask the caller to close the block.
860
+ needToCloseBlock = true
861
+ }
862
+ return out , needToCloseBlock
863
+ }
0 commit comments