Additional scan performance improvements

Move typeStack to ScanContext, so it is shared between rows.Scan calls.
Use string.Builder for string concatenations.
Simplify value assign logic.
Move convert value to the last assign step (needs for type conversions are rare).
This commit is contained in:
go-jet 2022-02-09 12:34:10 +01:00
parent c10244aeab
commit c86903fd1d
8 changed files with 428 additions and 174 deletions

View file

@ -16,6 +16,8 @@ type ScanContext struct {
commonIdentToColumnIndex map[string]int
groupKeyInfoCache map[string]groupKeyInfo
typeInfoMap map[string]typeInfo
typesVisited typeStack // to prevent circular dependency scan
}
// NewScanContext creates new ScanContext from rows
@ -39,7 +41,7 @@ func NewScanContext(rows *sql.Rows) (*ScanContext, error) {
commonIdentifier := toCommonIdentifier(names[0])
if len(names) > 1 {
commonIdentifier += "." + toCommonIdentifier(names[1])
commonIdentifier = concat(commonIdentifier, ".", toCommonIdentifier(names[1]))
}
commonIdentToColumnIndex[commonIdentifier] = i
@ -53,15 +55,17 @@ func NewScanContext(rows *sql.Rows) (*ScanContext, error) {
commonIdentToColumnIndex: commonIdentToColumnIndex,
typeInfoMap: make(map[string]typeInfo),
typesVisited: newTypeStack(),
}, nil
}
func createScanSlice(columnCount int) []interface{} {
scanSlice := make([]interface{}, columnCount)
scanPtrSlice := make([]interface{}, columnCount)
for i := range scanPtrSlice {
scanPtrSlice[i] = &scanSlice[i] // if destination is pointer to interface sql.Scan will just forward driver value
var a interface{}
scanPtrSlice[i] = &a // if destination is pointer to interface sql.Scan will just forward driver value
}
return scanPtrSlice
@ -72,8 +76,8 @@ type typeInfo struct {
}
type fieldMapping struct {
complexType bool // slice or struct
columnIndex int
complexType bool // slice and struct are complex types
rowIndex int // index in ScanContext.row
implementsScanner bool
}
@ -82,7 +86,7 @@ func (s *ScanContext) getTypeInfo(structType reflect.Type, parentField *reflect.
typeMapKey := structType.String()
if parentField != nil {
typeMapKey += string(parentField.Tag)
typeMapKey = concat(typeMapKey, string(parentField.Tag))
}
if typeInfo, ok := s.typeInfoMap[typeMapKey]; ok {
@ -100,7 +104,7 @@ func (s *ScanContext) getTypeInfo(structType reflect.Type, parentField *reflect.
columnIndex := s.typeToColumnIndex(newTypeName, fieldName)
fieldMap := fieldMapping{
columnIndex: columnIndex,
rowIndex: columnIndex,
}
if implementsScannerType(field.Type) {
@ -128,14 +132,15 @@ func (s *ScanContext) getGroupKey(structType reflect.Type, structField *reflect.
mapKey := structType.Name()
if structField != nil {
mapKey += structField.Type.String()
mapKey = concat(mapKey, structField.Type.String())
}
if groupKeyInfo, ok := s.groupKeyInfoCache[mapKey]; ok {
return s.constructGroupKey(groupKeyInfo)
}
groupKeyInfo := s.getGroupKeyInfo(structType, structField, newTypeStack())
tempTypeStack := newTypeStack()
groupKeyInfo := s.getGroupKeyInfo(structType, structField, &tempTypeStack)
s.groupKeyInfoCache[mapKey] = groupKeyInfo
@ -150,10 +155,7 @@ func (s *ScanContext) constructGroupKey(groupKeyInfo groupKeyInfo) string {
var groupKeys []string
for _, index := range groupKeyInfo.indexes {
cellValue := s.rowElem(index)
subKey := valueToString(reflect.ValueOf(cellValue))
groupKeys = append(groupKeys, subKey)
groupKeys = append(groupKeys, s.rowElemToString(index))
}
var subTypesGroupKeys []string
@ -161,7 +163,7 @@ func (s *ScanContext) constructGroupKey(groupKeyInfo groupKeyInfo) string {
subTypesGroupKeys = append(subTypesGroupKeys, s.constructGroupKey(subType))
}
return groupKeyInfo.typeName + "(" + strings.Join(groupKeys, ",") + strings.Join(subTypesGroupKeys, ",") + ")"
return concat(groupKeyInfo.typeName, "(", strings.Join(groupKeys, ","), strings.Join(subTypesGroupKeys, ","), ")")
}
func (s *ScanContext) getGroupKeyInfo(
@ -231,32 +233,36 @@ func (s *ScanContext) typeToColumnIndex(typeName, fieldName string) int {
return index
}
func (s *ScanContext) rowElem(index int) interface{} {
cellValue := reflect.ValueOf(s.row[index])
if cellValue.IsValid() && !cellValue.IsNil() {
return cellValue.Elem().Interface()
}
return nil
// rowElemValue always returns non-ptr value,
// invalid value is nil
func (s *ScanContext) rowElemValue(index int) reflect.Value {
scannedValue := reflect.ValueOf(s.row[index])
return scannedValue.Elem().Elem() // no need to check validity of Elem, because s.row[index] always contains interface in interface
}
func (s *ScanContext) rowElemValuePtr(index int) reflect.Value {
rowElem := s.rowElem(index)
rowElemValue := reflect.ValueOf(rowElem)
func (s *ScanContext) rowElemToString(index int) string {
value := s.rowElemValue(index)
if !value.IsValid() {
return "nil"
}
valueInterface := value.Interface()
if t, ok := valueInterface.(fmt.Stringer); ok {
return t.String()
}
return fmt.Sprintf("%#v", valueInterface)
}
func (s *ScanContext) rowElemValueClonePtr(index int) reflect.Value {
rowElemValue := s.rowElemValue(index)
if !rowElemValue.IsValid() {
return reflect.Value{}
}
if rowElemValue.Kind() == reflect.Ptr {
return rowElemValue
}
if rowElemValue.CanAddr() {
return rowElemValue.Addr()
}
newElem := reflect.New(rowElemValue.Type())
newElem.Elem().Set(rowElemValue)
return newElem