Skip to content

Commit c86903f

Browse files
committed
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).
1 parent c10244a commit c86903f

File tree

8 files changed

+423
-169
lines changed

8 files changed

+423
-169
lines changed

qrm/qrm.go

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,15 @@ func ScanOneRowToDest(scanContext *ScanContext, rows *sql.Rows, destPtr interfac
7474
err := rows.Scan(scanContext.row...)
7575

7676
if err != nil {
77-
return fmt.Errorf("rows scan error, %w", err)
77+
return fmt.Errorf("jet: rows scan error, %w", err)
7878
}
7979

80-
destValue := reflect.ValueOf(destPtr)
80+
destValuePtr := reflect.ValueOf(destPtr)
8181

82-
_, err = mapRowToStruct(scanContext, "", newTypeStack(), destValue, nil)
82+
_, err = mapRowToStruct(scanContext, "", destValuePtr, nil)
8383

8484
if err != nil {
85-
return fmt.Errorf("failed to map a row, %w", err)
85+
return fmt.Errorf("jet: failed to scan a row into destination, %w", err)
8686
}
8787

8888
return nil
@@ -121,7 +121,7 @@ func queryToSlice(ctx context.Context, db DB, query string, args []interface{},
121121

122122
scanContext.rowNum++
123123

124-
_, err = mapRowToSlice(scanContext, "", newTypeStack(), slicePtrValue, nil)
124+
_, err = mapRowToSlice(scanContext, "", slicePtrValue, nil)
125125

126126
if err != nil {
127127
return scanContext.rowNum, err
@@ -139,7 +139,6 @@ func queryToSlice(ctx context.Context, db DB, query string, args []interface{},
139139
func mapRowToSlice(
140140
scanContext *ScanContext,
141141
groupKey string,
142-
typesVisited *typeStack,
143142
slicePtrValue reflect.Value,
144143
field *reflect.StructField) (updated bool, err error) {
145144

@@ -154,19 +153,19 @@ func mapRowToSlice(
154153

155154
structGroupKey := scanContext.getGroupKey(sliceElemType, field)
156155

157-
groupKey = groupKey + "," + structGroupKey
156+
groupKey = concat(groupKey, ",", structGroupKey)
158157

159158
index, ok := scanContext.uniqueDestObjectsMap[groupKey]
160159

161160
if ok {
162161
structPtrValue := getSliceElemPtrAt(slicePtrValue, index)
163162

164-
return mapRowToStruct(scanContext, groupKey, typesVisited, structPtrValue, field, true)
163+
return mapRowToStruct(scanContext, groupKey, structPtrValue, field, true)
165164
}
166165

167166
destinationStructPtr := newElemPtrValueForSlice(slicePtrValue)
168167

169-
updated, err = mapRowToStruct(scanContext, groupKey, typesVisited, destinationStructPtr, field)
168+
updated, err = mapRowToStruct(scanContext, groupKey, destinationStructPtr, field)
170169

171170
if err != nil {
172171
return
@@ -192,7 +191,7 @@ func mapRowToBaseTypeSlice(scanContext *ScanContext, slicePtrValue reflect.Value
192191
return
193192
}
194193
}
195-
rowElemPtr := scanContext.rowElemValuePtr(index)
194+
rowElemPtr := scanContext.rowElemValueClonePtr(index)
196195

197196
if rowElemPtr.IsValid() && !rowElemPtr.IsNil() {
198197
updated = true
@@ -208,7 +207,6 @@ func mapRowToBaseTypeSlice(scanContext *ScanContext, slicePtrValue reflect.Value
208207
func mapRowToStruct(
209208
scanContext *ScanContext,
210209
groupKey string,
211-
typesVisited *typeStack, // to prevent circular dependency scan
212210
structPtrValue reflect.Value,
213211
parentField *reflect.StructField,
214212
onlySlices ...bool, // small optimization, not to assign to already assigned struct fields
@@ -217,12 +215,12 @@ func mapRowToStruct(
217215
mapOnlySlices := len(onlySlices) > 0
218216
structType := structPtrValue.Type().Elem()
219217

220-
if typesVisited.contains(&structType) {
218+
if scanContext.typesVisited.contains(&structType) {
221219
return false, nil
222220
}
223221

224-
typesVisited.push(&structType)
225-
defer typesVisited.pop()
222+
scanContext.typesVisited.push(&structType)
223+
defer scanContext.typesVisited.pop()
226224

227225
typeInf := scanContext.getTypeInfo(structType, parentField)
228226

@@ -240,7 +238,7 @@ func mapRowToStruct(
240238

241239
if fieldMap.complexType {
242240
var changed bool
243-
changed, err = mapRowToDestinationValue(scanContext, groupKey, typesVisited, fieldValue, &field)
241+
changed, err = mapRowToDestinationValue(scanContext, groupKey, fieldValue, &field)
244242

245243
if err != nil {
246244
return
@@ -251,34 +249,36 @@ func mapRowToStruct(
251249
}
252250

253251
} else {
254-
if mapOnlySlices || fieldMap.columnIndex == -1 {
252+
if mapOnlySlices || fieldMap.rowIndex == -1 {
255253
continue
256254
}
257255

258-
cellValue := scanContext.rowElem(fieldMap.columnIndex)
256+
scannedValue := scanContext.rowElemValue(fieldMap.rowIndex)
259257

260-
if cellValue == nil {
258+
if !scannedValue.IsValid() {
259+
setZeroValue(fieldValue) // scannedValue is nil, destination should be set to zero value
261260
continue
262261
}
263262

264-
initializeValueIfNilPtr(fieldValue)
265263
updated = true
266264

267265
if fieldMap.implementsScanner {
268-
scanner := getScanner(fieldValue)
266+
initializeValueIfNilPtr(fieldValue)
267+
fieldScanner := getScanner(fieldValue)
269268

270-
err = scanner.Scan(cellValue)
269+
value := scannedValue.Interface()
270+
271+
err := fieldScanner.Scan(value)
271272

272273
if err != nil {
273-
err = fmt.Errorf(`can't scan %T(%q) to '%s %s': %w`, cellValue, cellValue, field.Name, field.Type.String(), err)
274-
return
274+
return updated, fmt.Errorf(`can't scan %T(%q) to '%s %s': %w`, value, value, field.Name, field.Type.String(), err)
275275
}
276276
} else {
277-
err = setReflectValue(reflect.ValueOf(cellValue), fieldValue)
277+
err := assign(scannedValue, fieldValue)
278278

279279
if err != nil {
280-
err = fmt.Errorf(`can't assign %T(%q) to '%s %s': %w`, cellValue, cellValue, field.Name, field.Type.String(), err)
281-
return
280+
return updated, fmt.Errorf(`can't assign %T(%q) to '%s %s': %w`, scannedValue.Interface(), scannedValue.Interface(),
281+
field.Name, field.Type.String(), err)
282282
}
283283
}
284284
}
@@ -290,7 +290,6 @@ func mapRowToStruct(
290290
func mapRowToDestinationValue(
291291
scanContext *ScanContext,
292292
groupKey string,
293-
typesVisited *typeStack,
294293
dest reflect.Value,
295294
structField *reflect.StructField) (updated bool, err error) {
296295

@@ -306,7 +305,7 @@ func mapRowToDestinationValue(
306305
}
307306
}
308307

309-
updated, err = mapRowToDestinationPtr(scanContext, groupKey, typesVisited, destPtrValue, structField)
308+
updated, err = mapRowToDestinationPtr(scanContext, groupKey, destPtrValue, structField)
310309

311310
if err != nil {
312311
return
@@ -322,7 +321,6 @@ func mapRowToDestinationValue(
322321
func mapRowToDestinationPtr(
323322
scanContext *ScanContext,
324323
groupKey string,
325-
typesVisited *typeStack,
326324
destPtrValue reflect.Value,
327325
structField *reflect.StructField) (updated bool, err error) {
328326

@@ -331,9 +329,9 @@ func mapRowToDestinationPtr(
331329
destValueKind := destPtrValue.Elem().Kind()
332330

333331
if destValueKind == reflect.Struct {
334-
return mapRowToStruct(scanContext, groupKey, typesVisited, destPtrValue, structField)
332+
return mapRowToStruct(scanContext, groupKey, destPtrValue, structField)
335333
} else if destValueKind == reflect.Slice {
336-
return mapRowToSlice(scanContext, groupKey, typesVisited, destPtrValue, structField)
334+
return mapRowToSlice(scanContext, groupKey, destPtrValue, structField)
337335
} else {
338336
panic("jet: unsupported dest type: " + structField.Name + " " + structField.Type.String())
339337
}

qrm/scan_context.go

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ type ScanContext struct {
1616
commonIdentToColumnIndex map[string]int
1717
groupKeyInfoCache map[string]groupKeyInfo
1818
typeInfoMap map[string]typeInfo
19+
20+
typesVisited typeStack // to prevent circular dependency scan
1921
}
2022

2123
// NewScanContext creates new ScanContext from rows
@@ -39,7 +41,7 @@ func NewScanContext(rows *sql.Rows) (*ScanContext, error) {
3941
commonIdentifier := toCommonIdentifier(names[0])
4042

4143
if len(names) > 1 {
42-
commonIdentifier += "." + toCommonIdentifier(names[1])
44+
commonIdentifier = concat(commonIdentifier, ".", toCommonIdentifier(names[1]))
4345
}
4446

4547
commonIdentToColumnIndex[commonIdentifier] = i
@@ -53,15 +55,17 @@ func NewScanContext(rows *sql.Rows) (*ScanContext, error) {
5355
commonIdentToColumnIndex: commonIdentToColumnIndex,
5456

5557
typeInfoMap: make(map[string]typeInfo),
58+
59+
typesVisited: newTypeStack(),
5660
}, nil
5761
}
5862

5963
func createScanSlice(columnCount int) []interface{} {
60-
scanSlice := make([]interface{}, columnCount)
6164
scanPtrSlice := make([]interface{}, columnCount)
6265

6366
for i := range scanPtrSlice {
64-
scanPtrSlice[i] = &scanSlice[i] // if destination is pointer to interface sql.Scan will just forward driver value
67+
var a interface{}
68+
scanPtrSlice[i] = &a // if destination is pointer to interface sql.Scan will just forward driver value
6569
}
6670

6771
return scanPtrSlice
@@ -72,8 +76,8 @@ type typeInfo struct {
7276
}
7377

7478
type fieldMapping struct {
75-
complexType bool // slice or struct
76-
columnIndex int
79+
complexType bool // slice and struct are complex types
80+
rowIndex int // index in ScanContext.row
7781
implementsScanner bool
7882
}
7983

@@ -82,7 +86,7 @@ func (s *ScanContext) getTypeInfo(structType reflect.Type, parentField *reflect.
8286
typeMapKey := structType.String()
8387

8488
if parentField != nil {
85-
typeMapKey += string(parentField.Tag)
89+
typeMapKey = concat(typeMapKey, string(parentField.Tag))
8690
}
8791

8892
if typeInfo, ok := s.typeInfoMap[typeMapKey]; ok {
@@ -100,7 +104,7 @@ func (s *ScanContext) getTypeInfo(structType reflect.Type, parentField *reflect.
100104
columnIndex := s.typeToColumnIndex(newTypeName, fieldName)
101105

102106
fieldMap := fieldMapping{
103-
columnIndex: columnIndex,
107+
rowIndex: columnIndex,
104108
}
105109

106110
if implementsScannerType(field.Type) {
@@ -128,14 +132,15 @@ func (s *ScanContext) getGroupKey(structType reflect.Type, structField *reflect.
128132
mapKey := structType.Name()
129133

130134
if structField != nil {
131-
mapKey += structField.Type.String()
135+
mapKey = concat(mapKey, structField.Type.String())
132136
}
133137

134138
if groupKeyInfo, ok := s.groupKeyInfoCache[mapKey]; ok {
135139
return s.constructGroupKey(groupKeyInfo)
136140
}
137141

138-
groupKeyInfo := s.getGroupKeyInfo(structType, structField, newTypeStack())
142+
tempTypeStack := newTypeStack()
143+
groupKeyInfo := s.getGroupKeyInfo(structType, structField, &tempTypeStack)
139144

140145
s.groupKeyInfoCache[mapKey] = groupKeyInfo
141146

@@ -150,18 +155,15 @@ func (s *ScanContext) constructGroupKey(groupKeyInfo groupKeyInfo) string {
150155
var groupKeys []string
151156

152157
for _, index := range groupKeyInfo.indexes {
153-
cellValue := s.rowElem(index)
154-
subKey := valueToString(reflect.ValueOf(cellValue))
155-
156-
groupKeys = append(groupKeys, subKey)
158+
groupKeys = append(groupKeys, s.rowElemToString(index))
157159
}
158160

159161
var subTypesGroupKeys []string
160162
for _, subType := range groupKeyInfo.subTypes {
161163
subTypesGroupKeys = append(subTypesGroupKeys, s.constructGroupKey(subType))
162164
}
163165

164-
return groupKeyInfo.typeName + "(" + strings.Join(groupKeys, ",") + strings.Join(subTypesGroupKeys, ",") + ")"
166+
return concat(groupKeyInfo.typeName, "(", strings.Join(groupKeys, ","), strings.Join(subTypesGroupKeys, ","), ")")
165167
}
166168

167169
func (s *ScanContext) getGroupKeyInfo(
@@ -231,32 +233,36 @@ func (s *ScanContext) typeToColumnIndex(typeName, fieldName string) int {
231233
return index
232234
}
233235

234-
func (s *ScanContext) rowElem(index int) interface{} {
235-
cellValue := reflect.ValueOf(s.row[index])
236+
// rowElemValue always returns non-ptr value,
237+
// invalid value is nil
238+
func (s *ScanContext) rowElemValue(index int) reflect.Value {
239+
scannedValue := reflect.ValueOf(s.row[index])
240+
return scannedValue.Elem().Elem() // no need to check validity of Elem, because s.row[index] always contains interface in interface
241+
}
242+
243+
func (s *ScanContext) rowElemToString(index int) string {
244+
value := s.rowElemValue(index)
245+
246+
if !value.IsValid() {
247+
return "nil"
248+
}
249+
250+
valueInterface := value.Interface()
236251

237-
if cellValue.IsValid() && !cellValue.IsNil() {
238-
return cellValue.Elem().Interface()
252+
if t, ok := valueInterface.(fmt.Stringer); ok {
253+
return t.String()
239254
}
240255

241-
return nil
256+
return fmt.Sprintf("%#v", valueInterface)
242257
}
243258

244-
func (s *ScanContext) rowElemValuePtr(index int) reflect.Value {
245-
rowElem := s.rowElem(index)
246-
rowElemValue := reflect.ValueOf(rowElem)
259+
func (s *ScanContext) rowElemValueClonePtr(index int) reflect.Value {
260+
rowElemValue := s.rowElemValue(index)
247261

248262
if !rowElemValue.IsValid() {
249263
return reflect.Value{}
250264
}
251265

252-
if rowElemValue.Kind() == reflect.Ptr {
253-
return rowElemValue
254-
}
255-
256-
if rowElemValue.CanAddr() {
257-
return rowElemValue.Addr()
258-
}
259-
260266
newElem := reflect.New(rowElemValue.Type())
261267
newElem.Elem().Set(rowElemValue)
262268
return newElem

qrm/type_stack.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import "reflect"
44

55
type typeStack []*reflect.Type
66

7-
func newTypeStack() *typeStack {
7+
func newTypeStack() typeStack {
88
stack := make(typeStack, 0, 20)
9-
return &stack
9+
return stack
1010
}
1111

1212
func (s *typeStack) isEmpty() bool {

0 commit comments

Comments
 (0)