Skip to content

What is the purpose of using reflection on fields with custom Scanner #4266

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

Closed
Robbie-Perry opened this issue Apr 8, 2021 · 1 comment
Closed
Assignees
Labels

Comments

@Robbie-Perry
Copy link

Your Question

I have a type (Geometry4326) that implements the Scanner and Valuer interfaces, and wraps an underlying C type that performs geometric operations:

// Geom is an example GORM Model
type Geom struct{
	ID  int
	AOI *Geometry4326 `gorm:"type:geometry(POINT,4326)"`
}
// Geometry4326 wraps the geos Geometry struct and implements Scanner and Valuer
type Geometry4326 struct {
	*geos.Geometry
	valid bool
}
/* From geom library: */
type Geometry struct {
	g *C.GEOSGeometry
}

Due to an issue described here, reflect panics in go versions after 1.15.4 when attempting to call Value.Interface() on new reflect.Values created with a reflect.Type. Unfortunately, this is what happens in GORM schema/field.go, when it recursively enters the struct and uses reflection to create new instances of the (unexported) fields. This happens when trying to do a form of Get (such as db.First()), causing a panic later on line 234 (gorm v1.21.6) when it tries to do:
if _, ok := fieldValue.Interface().(*time.Time); ok {...}

Snippet from field.go:

fieldValue := reflect.New(field.IndirectFieldType)
// if field is valuer, used its value or first fields as data type
valuer, isValuer := fieldValue.Interface().(driver.Valuer)
if isValuer {
	if _, ok := fieldValue.Interface().(GormDataTypeInterface); !ok {
		if v, err := valuer.Value(); reflect.ValueOf(v).IsValid() && err == nil {
			fieldValue = reflect.ValueOf(v)
		}

		var getRealFieldValue func(reflect.Value)
		getRealFieldValue = func(v reflect.Value) {
			rv := reflect.Indirect(v)
			if rv.Kind() == reflect.Struct && !rv.Type().ConvertibleTo(TimeReflectType) {
				for i := 0; i < rv.Type().NumField(); i++ {
					newFieldType := rv.Type().Field(i).Type
					for newFieldType.Kind() == reflect.Ptr {
						newFieldType = newFieldType.Elem()
					}

					fieldValue = reflect.New(newFieldType)

					if rv.Type() != reflect.Indirect(fieldValue).Type() {
						getRealFieldValue(fieldValue)
					}

					if fieldValue.IsValid() {
						return
					}

					for key, value := range ParseTagSetting(field.IndirectFieldType.Field(i).Tag.Get("gorm"), ";") {
						if _, ok := field.TagSettings[key]; !ok {
							field.TagSettings[key] = value
						}
					}
				}
			}
		}

		getRealFieldValue(fieldValue)
	}
}

The document you expected this should be explained

N/A

Expected answer

This begs the question, why does GORM care what is inside the underlying struct if it implements its own Scanner and Valuer? Why is it trying to create a new instance of an unexported struct field of an underlying library? All it needs to care about is the column and the type, so that it can perform a query, allowing Scan() to actually fill the retrieved data into the struct.

@Robbie-Perry Robbie-Perry added the type:question general questions label Apr 8, 2021
@github-actions
Copy link

github-actions bot commented Jun 8, 2021

This issue has been automatically marked as stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants