@@ -163,29 +163,22 @@ func CompareResource(
163163 memberShapeRef := specField .ShapeRef
164164 memberShape := memberShapeRef .Shape
165165
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 (
170169 compareConfig ,
171170 memberShape ,
172171 deltaVarName ,
172+ fieldPath ,
173173 firstResAdaptedVarName ,
174174 secondResAdaptedVarName ,
175- fieldPath ,
176175 indentLevel ,
177176 )
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
187178
188179 switch memberShape .Type {
180+ case "blob" :
181+ // We already handled the case of blobs above, so we can skip it here.
189182 case "structure" :
190183 // Recurse through all the struct's fields and subfields, building
191184 // nested conditionals and calls to `delta.Add()`...
@@ -197,7 +190,7 @@ func CompareResource(
197190 firstResAdaptedVarName ,
198191 secondResAdaptedVarName ,
199192 fieldPath ,
200- indentLevel ,
193+ indentLevel + 1 ,
201194 )
202195 case "list" :
203196 // Returns Go code that compares all the elements of the slice fields...
@@ -209,7 +202,7 @@ func CompareResource(
209202 firstResAdaptedVarName ,
210203 secondResAdaptedVarName ,
211204 fieldPath ,
212- indentLevel ,
205+ indentLevel + 1 ,
213206 )
214207 case "map" :
215208 // Returns Go code that compares all the elements of the map fields...
@@ -221,7 +214,7 @@ func CompareResource(
221214 firstResAdaptedVarName ,
222215 secondResAdaptedVarName ,
223216 fieldPath ,
224- indentLevel ,
217+ indentLevel + 1 ,
225218 )
226219 default :
227220 // if *a.ko.Spec.Name != *b.ko.Spec.Name) {
@@ -234,15 +227,14 @@ func CompareResource(
234227 firstResAdaptedVarName ,
235228 secondResAdaptedVarName ,
236229 fieldPath ,
237- indentLevel ,
230+ indentLevel + 1 ,
238231 )
239232 }
240- if nilCode != "" {
233+ if needToCloseBlock {
241234 // }
242235 out += fmt .Sprintf (
243236 "%s}\n " , indent ,
244237 )
245- indentLevel --
246238 }
247239 }
248240 return out
@@ -285,12 +277,8 @@ func compareNil(
285277 indent := strings .Repeat ("\t " , indentLevel )
286278
287279 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 ""
292280 case "boolean" , "string" , "character" , "byte" , "short" , "integer" , "long" ,
293- "float" , "double" , "timestamp" , "structure" , "map" , " jsonvalue" :
281+ "float" , "double" , "timestamp" , "structure" , "jsonvalue" :
294282 // if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name) {
295283 out += fmt .Sprintf (
296284 "%sif ackcompare.HasNilDifference(%s, %s) {\n " ,
@@ -355,11 +343,6 @@ func compareScalar(
355343 "%sif *%s != *%s {\n " ,
356344 indent , firstResVarName , secondResVarName ,
357345 )
358- case "blob" :
359- out += fmt .Sprintf (
360- "%sif !bytes.Equal(%s, %s) {\n " ,
361- indent , firstResVarName , secondResVarName ,
362- )
363346 case "timestamp" :
364347 // if !a.ko.Spec.CreatedAt.Equal(b.ko.Spec.CreatedAt) {
365348 out += fmt .Sprintf (
@@ -650,29 +633,20 @@ func CompareStruct(
650633 continue
651634 }
652635
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 (
657637 compareConfig ,
658638 memberShape ,
659639 deltaVarName ,
640+ memberFieldPath ,
660641 firstResAdaptedVarName ,
661642 secondResAdaptedVarName ,
662- memberFieldPath ,
663643 indentLevel ,
664644 )
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
674646
675647 switch memberShape .Type {
648+ case "blob" :
649+ // We already handled the case of blobs above, so we can skip it here.
676650 case "structure" :
677651 // Recurse through all the struct's fields and subfields, building
678652 // nested conditionals and calls to `delta.Add()`...
@@ -684,7 +658,7 @@ func CompareStruct(
684658 firstResAdaptedVarName ,
685659 secondResAdaptedVarName ,
686660 memberFieldPath ,
687- indentLevel ,
661+ indentLevel + 1 ,
688662 )
689663 case "list" :
690664 // Returns Go code that compares all the elements of the slice fields...
@@ -696,7 +670,7 @@ func CompareStruct(
696670 firstResAdaptedVarName ,
697671 secondResAdaptedVarName ,
698672 memberFieldPath ,
699- indentLevel ,
673+ indentLevel + 1 ,
700674 )
701675 case "map" :
702676 // Returns Go code that compares all the elements of the map fields...
@@ -708,7 +682,7 @@ func CompareStruct(
708682 firstResAdaptedVarName ,
709683 secondResAdaptedVarName ,
710684 memberFieldPath ,
711- indentLevel ,
685+ indentLevel + 1 ,
712686 )
713687 default :
714688 // if *a.ko.Spec.Name != *b.ko.Spec.Name {
@@ -721,16 +695,169 @@ func CompareStruct(
721695 firstResAdaptedVarName ,
722696 secondResAdaptedVarName ,
723697 memberFieldPath ,
724- indentLevel ,
698+ indentLevel + 1 ,
725699 )
726700 }
727- if nilCode != "" {
701+ if needToCloseBlock {
728702 // }
729703 out += fmt .Sprintf (
730704 "%s}\n " , indent ,
731705 )
732- indentLevel --
733706 }
734707 }
735708 return out
736709}
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