georgysavva / scany

Library for scanning data from a database into Go structs and more
MIT License
1.31k stars 68 forks source link

Scanning into struct with non-primitive (custom) field #111

Closed ahmadmusair closed 1 year ago

ahmadmusair commented 1 year ago

Hi, I tried to scan into struct with non-primitive field, but I got this error scanning all: scanning: scanning: doing scan: starting: scany: to scan into a primitive type, columns number must be exactly 1, got: 15. I have tried the solution from #92, by setting db struct tag to empty string "" but couldn't solve the problem. (note: I'm using pgxpool)

My Struct

type Role struct {
    ID                        int    `db:"id"`
    CompanyID                 int    `db:"company_id"`
    BusinessID                int    `db:"business_id"`
    Name                      string `db:"name"`
    IsDefault                 bool   `db:"is_default"`
    CashAndBankAccess         Access `db:""`
    SummaryAccess             Access `db:""`
    ...
}

type Access struct {
    Create string
    Read   string
    Update string
    Delete string
}

My Querying Logic

func (pa Adapter) LoadRolesByBusinessID(businessID int) ([]*domain.Role, error) {
    var roles []*domain.Role

    query := `SELECT * FROM roles WHERE business_id = $1`

    err := pgxscan.Select(context.Background(), pa.Pool, &roles, query, businessID)

    if err != nil {
        if pgxscan.NotFound(err) {
            return nil, domain.ErrNotFound
        }
        return roles, err
    }

    return roles, nil
}

Am I missing something? I have similar logic, but the struct I'm scanning into has only primitive field and it works perfectly

georgysavva commented 1 year ago

Hey. Thank you for opening this issue. I tried to replicate the error, but it works on my end.

In any case, let me explain it to you. That error has nothing to do with the fields in your struct. It doesn't matter whether they are primitive or complex. The only way to get this error is to pass to pgxscan.Select() something other than a list of maps/structs as the scanning destination — the third parameter. The library tries to detect a list of structs or maps, and if there is none, it falls back to the primitive type case, like scanning into an integer variable where it's required to have only one column in the result rows. Another case of the library handling your destination as a primitive type is when you type implements the sql.Scanner interface. So watch out for that as well.

I hope this helps. If not, provide the full struct definition, the destination variable initialization, the library call, and the query with all columns defined explicitly (no * sign). That way, I can replicate it completely.

ahmadmusair commented 1 year ago

Hey, thanks a lot for your response and explanation. Your insights helped me identify the issue. Turns out, the problem was with the sql.Scanner implementation for Role struct in my code. I made the necessary changes based on your advice (deleting the sql.Scanner implementation), and now the error is gone.

I really appreciate your quick and informative help. Thanks again!

georgysavva commented 1 year ago

Sure! I am glad you resolved the issue.