-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(spanner): assign value to correct index if columns are out of order in SelectAll #13316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -460,6 +460,7 @@ func SelectAll(rows rowIterator, destination interface{}, options ...DecodeOptio | |
|
|
||
| isPrimitive := itemType.Kind() != reflect.Struct | ||
| var pointers []interface{} | ||
| var fieldIndexes []int | ||
| isFirstRow := true | ||
| var err error | ||
| return rows.Do(func(row *Row) error { | ||
|
|
@@ -469,7 +470,7 @@ func SelectAll(rows rowIterator, destination interface{}, options ...DecodeOptio | |
| defer func() { | ||
| isFirstRow = false | ||
| }() | ||
| if pointers, err = structPointers(sliceItem.Elem(), row.fields, s.Lenient); err != nil { | ||
| if pointers, fieldIndexes, err = structPointers(sliceItem.Elem(), row.fields, s.Lenient); err != nil { | ||
| return err | ||
| } | ||
| } | ||
|
|
@@ -502,7 +503,7 @@ func SelectAll(rows rowIterator, destination interface{}, options ...DecodeOptio | |
| if p == nil { | ||
| continue | ||
| } | ||
| e.Field(idx).Set(reflect.ValueOf(p).Elem()) | ||
| e.Field(fieldIndexes[idx]).Set(reflect.ValueOf(p).Elem()) | ||
| idx++ | ||
| } | ||
| } | ||
|
|
@@ -524,40 +525,45 @@ func SelectAll(rows rowIterator, destination interface{}, options ...DecodeOptio | |
| }) | ||
| } | ||
|
|
||
| func structPointers(sliceItem reflect.Value, cols []*sppb.StructType_Field, lenient bool) ([]interface{}, error) { | ||
| pointers := make([]interface{}, 0, len(cols)) | ||
| func structPointers(sliceItem reflect.Value, cols []*sppb.StructType_Field, lenient bool) ([]interface{}, []int, error) { | ||
| pointers := make([]interface{}, len(cols)) | ||
| fieldIndexes := make([]int, len(cols)) | ||
| fieldTag := make(map[string]reflect.Value, len(cols)) | ||
| initFieldTag(sliceItem, &fieldTag) | ||
| fieldIndexMap := make(map[string]int, len(cols)) | ||
| initFieldTag(sliceItem, &fieldTag, &fieldIndexMap) | ||
|
|
||
| for i, colName := range cols { | ||
| if colName.Name == "" { | ||
| return nil, errColNotFound(fmt.Sprintf("column %d", i)) | ||
| return nil, nil, errColNotFound(fmt.Sprintf("column %d", i)) | ||
| } | ||
|
|
||
| var fieldVal reflect.Value | ||
| fieldIdx := i | ||
| if v, ok := fieldTag[strings.ToLower(colName.GetName())]; ok { | ||
| fieldIdx = fieldIndexMap[strings.ToLower(colName.GetName())] | ||
| fieldVal = v | ||
| } else { | ||
| if !lenient { | ||
| return nil, errNoOrDupGoField(sliceItem, colName.GetName()) | ||
| return nil, nil, errNoOrDupGoField(sliceItem, colName.GetName()) | ||
| } | ||
| fieldVal = sliceItem.FieldByName(colName.GetName()) | ||
| } | ||
| if !fieldVal.IsValid() || !fieldVal.CanSet() { | ||
| // have to add if we found a column because Columns() requires | ||
| // len(cols) arguments or it will error. This way we can scan to | ||
| // a useless pointer | ||
| pointers = append(pointers, nil) | ||
| pointers[i] = nil | ||
| continue | ||
| } | ||
|
|
||
| pointers = append(pointers, fieldVal.Addr().Interface()) | ||
| fieldIndexes[i] = fieldIdx | ||
| pointers[i] = fieldVal.Addr().Interface() | ||
| } | ||
| return pointers, nil | ||
| return pointers, fieldIndexes, nil | ||
| } | ||
|
|
||
| // Initialization the tags from struct. | ||
| func initFieldTag(sliceItem reflect.Value, fieldTagMap *map[string]reflect.Value) { | ||
| func initFieldTag(sliceItem reflect.Value, fieldTagMap *map[string]reflect.Value, fieldIndexes *map[string]int) { | ||
| typ := sliceItem.Type() | ||
|
|
||
| for i := 0; i < sliceItem.NumField(); i++ { | ||
|
|
@@ -573,7 +579,7 @@ func initFieldTag(sliceItem reflect.Value, fieldTagMap *map[string]reflect.Value | |
| // found an embedded struct | ||
| if fieldType.Anonymous { | ||
| sliceItemOfAnonymous := sliceItem.Field(i) | ||
| initFieldTag(sliceItemOfAnonymous, fieldTagMap) | ||
| initFieldTag(sliceItemOfAnonymous, fieldTagMap, fieldIndexes) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The recursive call to This will lead to panics or incorrect field assignments when decoding into structs with embedded fields. A correct implementation would need to build an index path (
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Embedded structs are not supported (see the recently added test cases). So this flaw can be ignored, as it is something that was not supported in the first place. |
||
| continue | ||
| } | ||
| } | ||
|
|
@@ -585,5 +591,6 @@ func initFieldTag(sliceItem reflect.Value, fieldTagMap *map[string]reflect.Value | |
| name = fieldType.Name | ||
| } | ||
| (*fieldTagMap)[strings.ToLower(name)] = sliceItem.Field(i) | ||
| (*fieldIndexes)[strings.ToLower(name)] = i | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fallback logic for
lenientmode has a flaw. IfsliceItem.FieldByNamesuccessfully finds a field (which could happen for fields in embedded structs thatinitFieldTagmight not handle correctly), thefieldIdxis not updated. It retains its initial value ofi(the column index), which leads tofieldIndexes[i]being set toi. This bypasses the column-to-field-index mapping for this field, leading to incorrect data assignment if columns are out of order.Given that
initFieldTagis intended to populate the maps for all relevant fields, thiselseblock should ideally handle columns that are not present in the destination struct. Inlenientmode, these should be ignored. The current code does lead to them being ignored becauseFieldByNamewould likely fail and result in an invalidfieldVal. However, ifFieldByNamesucceeds, it introduces a bug.To make this safer, you should either ensure this path also correctly resolves the field index, or rely solely on the pre-computed maps from
initFieldTagand treat any column not in the maps as a field to be ignored in lenient mode.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Embedded structs are not supported (see the recently added test cases). So this flaw can be ignored, as it is something that was not supported in the first place.