Does gorm interpret the content of a struct with a logical OR?

Issue

New to SQL, I am writing as an exercise an API middleware that checks if the information contained in some headers match a database entry ("token-based authentication"). Database access is based on GORM.

To this, I have defined my ORM as follows:

type User struct {
    ID       uint
    UserName string
    Token string
}

In my middleware I retrieve the content of relevant headers and end up with the variables userHeader and tokenHeader. They are supposed to be matched to the database in order to do the authentication.

The user table has one single entry:

select * from users 
// 1,admin,admintoken

The authentication code is

    var auth User
    res := db.Where(&User{UserName: userHeader, Token: tokenHeader}).Find(&auth)
    if res.RowsAffected == 1 {
        // authentication succeeded
    }

When testing this, I end up with the following two incorrect results (other combinations are correct):

  • with only one header set to a correct value (and the other one not present) the authentication is successful (adding the other header with an incorrect value is OK (=auth fails))
  • no headers set → authentication goes though

I expected my query to mean (in the context of the incorrect results above)

 select * from users where users.user_name = 'admin' and users.token = ''
 select * from users where users.user_name = '' and users.token = ''

and this query is correct on the console, i.e. produces zero results (ran against the database).

The ORM one, however, seems to discard non-existing headers and assume they are fine (this is at least my understanding)

I also tried to chain the Where clauses via

db.Where(&User{UserName: userHeader}).Where(&User{Token: tokenHeader}).Find(&auth) 

but the result is the same.

What should be the correct query?

Solution

The gorm.io documentation says the following on the use of structs in Where conditionals:

When querying with struct, GORM will only query with non-zero fields,
that means if your field’s value is 0, '', false or other zero
values, it won’t be used to build query conditions …

The suggested solution to this is:

To include zero values in the query conditions, you can use a map,
which will include all key-values as query conditions …


So, when the token header or both headers are empty, but you still want to include them in the WHERE clause of the generated query, you need to use a map instead of the struct as the argument to the Where method.

db.Where(map[string]interface{}{"user_name": userHeader, "token": tokenHeader}).Find(&auth)

You can use Debug() to check for the generated SQL (it gets printed into stderr); use it if you are unsure what SQL your code generates

Answered By – mkopriva

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.