-
Notifications
You must be signed in to change notification settings - Fork 289
Add outbound Postgres bindings to Go SDK #1227
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
Conversation
5465a9c
to
16b616b
Compare
Hmm. I must have done something funky to get those merge conflicts. I'll try to resolve and re-push my changes. |
Signed-off-by: Justin Pflueger <justin.pflueger@fermyon.com>
Signed-off-by: Justin Pflueger <justin.pflueger@fermyon.com>
Signed-off-by: Justin Pflueger <justin.pflueger@fermyon.com>
Signed-off-by: Justin Pflueger <justin.pflueger@fermyon.com>
Signed-off-by: Justin Pflueger <justin.pflueger@fermyon.com>
Signed-off-by: Justin Pflueger <justin.pflueger@fermyon.com>
Signed-off-by: Justin Pflueger <justin.pflueger@fermyon.com>
16b616b
to
7088ec0
Compare
Weirdness caused by me not signing commits so I had to rebase with a merge conflict. Fixed locally and force-pushed my branch to update, sorry for the churn. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great overall -- just a couple of requests.
GENERATED_OUTBOUND_REDIS = redis/outbound-redis.c redis/outbound-redis.h | ||
GENERATED_SPIN_REDIS = redis/spin-redis.c redis/spin-redis.h | ||
GENERATED_KEY_VALUE = key_value/key-value.c key_value/key-value.h | ||
GENERATED_OUTBOUND_PG = postgres/outbound-pg.c postgres/outbound-pg.h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you please add postgres/sdk-version-go.c
to SDK_VERSION_DEST_FILES
below? That helps ensure the SDK version information is embedded in components built with the SDK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do, I need to rebase onto v1.0 branch first and then I'll add it to avoid any merge conflicts.
@@ -0,0 +1,18 @@ | |||
# Spin component with outbound postgres in TinyGo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind adding instructions for using wit-bindgen
0.3.0 to re-generate the bindings if necessary (e.g. because we add variant cases, new functions, etc. to outbound-pg.wit)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll need to change a decent amount of the code. I broke things out into different files in order to better understand how things were working but that would be tedious to someone who is just trying to update the bindings quickly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dicej I added instructions to the readme in the sdk/go
folder. Let me know if that is too hacky to include :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a Go ignoramus so please ignore me if I'm completely off beam
sdk/go/postgres/db_value.go
Outdated
|
||
func (n DbValue) GetBoolean() bool { | ||
if g, w := n.Kind(), DbValueKindBoolean; g != w { | ||
panic(fmt.Sprintf("Attr kind is %v, not %v", g.String(), w.String())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know Go database idioms but panicking on the wrong type seems unforgiving. Would it make sense to have a Get and TryGet function for each type, e.g. GetBoolean() bool
and TryGetBoolean() bool, bool
for the if val, ok := v.TryGetBoolean(); ok { ... }
idiom?
Are there existing Go database API patterns we could try to emulate, or is our WIT just too far away from them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is just how the tinygo binding generator in wit-bindgen works. The idea is that the correct way to get the value is ask what the type is first and then extract the value using the appropriate getter. It's like poor-man's pattern matching. So if the app developer skips the first step and just assumes the type, it's a programmer error, hence the panic. By that reasoning, making the developer check for errors in the second step would be redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed with what Joel said here. With the lack of generics in Go usually this method is accompanied by a check like this:
if dbValue.Kind() == DbValueKindBoolean {
dbBool := dbValue.GetBoolean()
}
My understand of the go idioms is that a tuple is essentially used like a Result<T>
in rust where a function like func x() (bool, Error)
is equivalent to fn x() -> Result<bool, Error>
in the Rust world.
All that being said, this is generated code so our request will need to go to wit-bindgen.
sdk/go/postgres/db_value.go
Outdated
n.kind = DbValueKindBinary | ||
} | ||
|
||
func DbValueDbNull() DbValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have something like an IsNull
function? What sort of experience should we expect for loading a value from a NULLable column, especially if the developer wants graceful failure rather than panic?
(Again this is me being ignorant of How Things Are in Go.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically in Go, null == nil so our language is a little off but we could add a function like this in an additional layer of our SDK. I did find that check necessary, especially on nullable columns where the column type is actually string but there was no value so the kind in our sdk was DbValueKindNull
instead of string with a nil
value.
func (v *DbValue) IsNull() bool {
return v.Kind() == DbValueKindBoolean
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me clarify what I'm driving at. Consider a People table with two (interesting) columns, Age (integer) and MiddleName (string). Both are NULLable in the database (to reflect unknown age and people who don't have a middle name).
I want to query for the set of People and process that list, for example rendering to HTML (printing unknown
for NULL ages and -
for no middle name), or marshalling to JSON (where I guess I'd omit the NULL fields), but for generality going via some intermediate Go structure.
How (generally) do I do this?
If memory serves, for the intermediate structure to allow nil
, its values would have to be pointers e.g. { Age *int, MiddleName *string }
. Is that right? If so, what is the outline for populating them, something like:
var age
if v.Kind() == DbNull {
age = nil
} else {
age = &v.GetInt()
}
person := Person { Age: age, ... }
Is that right (modulo Go syntax!), and if so, does it seem like what a Go programmer would expect to right?
To be clear, I am not suggesting it's not! I'm not familiar with Go idioms - for all I know this is exactly what a Go programmer would find familiar. I'm just confirming that we are happy with whatever the pattern for nullable columns is.
Hope this makes more sense now - sorry for the somewhat muddled question first time around!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If memory serves, for the intermediate structure to allow nil, its values would have to be pointers e.g. { Age *int, MiddleName *string }. Is that right?
I do believe that is right, yes. When I created a struct without pointers, the struct is initialized with default values (i.e. 0 for int, "" for string, etc.)
sdk/go/postgres/pg_types.go
Outdated
|
||
func (n PgError) GetConnectionFailed() string { | ||
if g, w := n.Kind(), PgErrorKindConnectionFailed; g != w { | ||
panic(fmt.Sprintf("Attr kind is %v, not %v", g.String(), w.String())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again panics seem quite harsh, especially given that the developer can't expect errors the way they can data types... is the expectation that the app would call Kind()
before calling the relevant Get function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least the way that I've used it and the way the generated code works, I'm always forced to check the kind before calling this function.
sdk/go/postgres/pg_types.go
Outdated
return n.val.(string) | ||
} | ||
|
||
func (n *PgError) SetConnectionFailed(v string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite sure where an app would use these functions - could they be private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The set functions could be private yes but this is just how wit-bindgen works as it stands today. I'm unclear on how we could customize this behavior. We could wrap the generated code better but that would incur a maintenance cost.
Hey I am glad that you were able to use the |
Hey, @Mossaka — I think this is still using the C bindings generator. |
@radu-matei actually this is using both. I used our pinned version of @Mossaka it was pretty easy to use once I got our WIT into the right format. I do have some small feedback for the generated code and I'll file issues in the wit-bindgen repo so you can keep track of them there. |
Signed-off-by: Justin Pflueger <justin.pflueger@fermyon.com>
@jpflueger Outbound Postgres for the Go SDK landed in #1922. Shall we close this? |
I noticed that there were no bindings for outbound Postgres in the go sdk. I was able to generate bindings using wit-bindgen 0.3.0 by wrapping
outbound-pg.wit
into a temporary wit file using an interface for the rdbms-types.wit and a world for the query and execute functions. Most of the binding code was generated but I did make some modifications for better error handling and split out the functions for readability. I wanted to submit these changes as a starting point. Happy to rework whatever you suggest as this is my first time generating bindings and I'm not super familiar with C or Go.