Description

I have a custom data type and its Scanner method that will check for any data input

// TimeString type
type TimeString string

func (t *TimeString) Scan(value interface{}) error {
        fmt.Println("Scanner called!") //This never get call!!!!
    switch v := value.(type) {
    case time.Time:
        result := v.UTC().Format("2006-01-02T15:04:05.000Z")
        *t = TimeString(result)
        return nil
    case string:
        if _, err := time.Parse(time.RFC3339Nano, v); err != nil || len(v) != len("2006-01-02T15:04:05.000Z") {
            return fmt.Errorf("wrong time format, expecting an ISO 8601 string format with nanoseconds, got: %v", v)
        } else {
            *t = TimeString(v)
            return nil
        }
    default:
        return fmt.Errorf("value '%v' of incompatible type '%T' found", value, v)
    }
}
func (t TimeString) Value() (driver.Value, error) {
    return string(t), nil
}
func (TimeString) GormDBDataType(db *gorm.DB, field *schema.Field) string {
    switch db.Dialector.Name() {
    case "sqlite":
        return "TEXT"
    case "mysql", "postgres":
        return "VARCHAR(255)"
    }
    return "VARCHAR"
}

When I create a new record:

type ProdOrder struct {
    //Main columns
    OrderId           uint       `gorm:"autoIncrement;primaryKey;not null;unique" json:"orderId"`
    ReleasedDate      TimeString `gorm:"default:NULL" json:"releasedDate"`
}

Sample json to POST into database

{
    "releasedDate": "2023-03-02T15:23:25.972Z"
}
//....
orderData := make(map[string]interface{})
//....
ServerDB.Model(&db.ProdOrder{}).Create(orderData)

No matter if I call Create and passing map or struct. The Scanner method never called Any idea why this is happening?

Comment From: github-actions[bot]

The issue has been automatically marked as stale as it missing playground pull request link, which is important to help others understand your issue effectively and make sure the issue hasn't been fixed on latest master, checkout https://github.com/go-gorm/playground for details. it will be closed in 30 days if no further activity occurs. if you are asking question, please use the Question template, most likely your question already answered https://github.com/go-gorm/gorm/issues or described in the document https://gorm.io ✨ Search Before Asking

Comment From: a631807682

When create, only valuer will be called, unless the returning statement is supported.

Comment From: KiddoV

@a631807682 so the Scanner only called on Update, I assumed? What do you mean about "returning statement is supported"? Didn't my scanner have return already?

Comment From: a631807682

@a631807682 so the Scanner only called on Update, I assumed? What do you mean about "returning statement is supported"? Didn't my scanner have return already?

The function of the scanner is to scan the data read from the database into the structure, means that it will not trigger as long as there is no need to read data. Some databases (postgres, sqlite) support returning the value of the database through the return statement when inserting.

https://www.postgresql.org/docs/current/dml-returning.html

Comment From: KiddoV

Hmmm... I got it know. I understand the Scanner the other way around. Is there any way to check for a valid custom data when user submit to database? For example, I want user to submit the time in ISO format string only, if not then throw an error says wrong format.

I know I can use BeforeSave but it only for 1 model (...and I have to create this function for every model if needed), but I want to check for every model that is using my custom data with only 1 function.

Comment From: a631807682

You need to check in valuer.

func (t TimeString) Value() (driver.Value, error) {
    // check here
}

Comment From: KiddoV

When fmt.Println(). I only see the Value() called only 1 time when database is initialize, not every time I submit the record though.

Comment From: a631807682

We have a lot of unit tests, if you find something wrong, you can create a new issue to explain it. https://github.com/go-gorm/gorm/blob/master/tests/scanner_valuer_test.go

Comment From: KiddoV

Never mind, I got it! Thanks!