Skip to content
This repository has been archived by the owner on Feb 15, 2023. It is now read-only.

go sqlgen: Add support for converting "Zero" value fields to NULL in the db #181

Merged
merged 6 commits into from
Oct 9, 2018

Conversation

willhug
Copy link

@willhug willhug commented Oct 4, 2018

Summary: Adds support to sqlgen to convert "Zero" value model fields to NULL in
the database. This only needs to be applied when inserting a value to the
database because when we read from the db NULL values are noop (leaving the
zero value).

I put some cleanups in the PR as well, would be easy to pull them out if we want.

Additionally, I wrote tests for the "Main" use cases, but I'm very open
to add tests for other edge cases, let me know and I'll add them.

@coveralls
Copy link

coveralls commented Oct 4, 2018

Pull Request Test Coverage Report for Build 1135

  • 46 of 53 (86.79%) changed or added relevant lines in 4 files are covered.
  • 5 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.2%) to 65.761%

Changes Missing Coverage Covered Lines Changed/Added Lines %
internal/fields/reflect.go 20 21 95.24%
sqlgen/reflect.go 2 4 50.0%
internal/fields/sql.go 4 8 50.0%
Files with Coverage Reduction New Missed Lines %
reactive/graph.go 1 94.07%
graphql/schemabuilder/reflect.go 4 71.81%
Totals Coverage Status
Change from base Build 1112: 0.2%
Covered Lines: 3509
Relevant Lines: 5336

💛 - Coveralls

@@ -115,8 +119,7 @@ var _ driver.Valuer = &Valuer{}
// into the type dictated by our descriptor.
type Scanner struct {
*Descriptor
value reflect.Value
isValid bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this not used anywhere? If so, hooray, goodbye allocation!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup!

@@ -77,6 +77,10 @@ func (f Valuer) Value() (driver.Value, error) {
return iface.MarshalJSON()
}
return json.Marshal(i)
case f.Tags.Contains("zeroIsNull"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullable? Trying to think of a better name

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per offline convo: implicitnull?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing to implicitNull

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer all lowercase since that's how go's stdlib does it (omitempty).

@@ -0,0 +1,187 @@
package sqlgen

import (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fine, though it'd be nice to have tests in the fields package given we test tons of other values there 🤷‍♂️. At this point it might be worth consolidating the fields package with sqlgen anyway.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, adding some tests here and seeing edge cases, will update PR

@willhug
Copy link
Author

willhug commented Oct 4, 2018

Adding some tests for filters to make sure they still work properly.

@willhug
Copy link
Author

willhug commented Oct 4, 2018

Updated! Also added a db test thing I've been using locally to get tests to run without an EnvVar every time.

@@ -28,6 +28,8 @@ var DefaultDBConfig = DBConfig{
func init() {
if pw := os.Getenv("DB_PASSWORD"); pw != "" {
DefaultDBConfig.Password = pw
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can change the password to just always be dev and update the docker container on here?

Copy link
Author

@willhug willhug Oct 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once this section is removed and the DefaultDBConfig's password reflects the docker container's password this PR is good to go! Make sure to update the CHANGELOG.md with the changes, too!

Name: "Bob",
Uuid: testfixtures.CustomType{'1', '1', '2', '3', '8', '4', '9', '1', '2', '9', '3'},
Mood: &mood,
ImplicitNull: "test",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm - do we want this in our benchmark?

Copy link
Contributor

@stephen stephen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm excited with this change.

}, rows)
}

func GetCount(db *sql.DB, countQuery string) (int, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't export this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooc: can you do stuff with exported functions in a test?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only from other test files I believe.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -77,6 +77,10 @@ func (f Valuer) Value() (driver.Value, error) {
return iface.MarshalJSON()
}
return json.Marshal(i)
case f.Tags.Contains("implicitnull"):
if IsZero(f.value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm excited by how small this change is that i missed it in my first read.

// Mostly copied from https://github.com/golang/go/issues/7501
// Works for mostly everything except zero initialized arrays (e.g.
// `var ray [5]string` will not be "Zero")
func IsZero(v reflect.Value) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should we make this private?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephen given that it's in an internal package, it's going to be private API one way or another - but I think that's a good point

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

NullByte: []byte("hello there"),
NullTime: timeField,
}
if _, err := db.InsertRow(context.Background(), newRow); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we add a test for UpdateRow as well?

@@ -77,6 +77,10 @@ func (f Valuer) Value() (driver.Value, error) {
return iface.MarshalJSON()
}
return json.Marshal(i)
case f.Tags.Contains("implicitnull"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: what is implicit about this null? i kind of like zeroisnull, but i could be convinced otherwise

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong feelings either way, @berfarah @stephen whichever preference you guys want.

assert.Equal(t, []*ImplicitNull{
newRow,
}, rows)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we also add a test for the reading that a null in the db reads out as the zero value?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@stephen stephen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from @berfarah:
let's verify that we have:

  • integration test for livesql (binlog)
  • integration test for sqlgen CRUD
  • integration test for batching in sqlgen

@willhug
Copy link
Author

willhug commented Oct 5, 2018

Not to self: binlog and Delete still need tests.

@willhug
Copy link
Author

willhug commented Oct 5, 2018

Updated! should be good to review now!

Will Hughes added 6 commits October 9, 2018 12:50
Summary: Changes the default test fixture to use "dev" as the password
for databases.
Summary: Noticed isValid wasn't used outside of the Scan function, so
removed it from the field and just initialized it in the Scan function.
Summary: Adds a function that can take a reflect.Value and return
whether the provided value is the "Zero" value of that type.  We'll use
this to determine whether a provided type should be treated as "null"
for the new "zeroIsNull" tag.
Summary: Adds a new tag `sql:",implicitnull"` we can use on thunder
fields to mark that the "Zero" value of a field should be treated
as NULL in the database.
@willhug willhug merged commit 29e1b32 into master Oct 9, 2018
@willhug willhug deleted the wh/protoStuff/part-1.0 branch October 9, 2018 20:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants