You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Go 1.18 was released on March 2022 and introduced support for the long-awaited generics.
It comes to no-one's surprise that such support was requested soon after for this driver, as you can see here.
Quick Feature overview
Since the initial request, a few generic functions have been added.
The first generic top-level functions appeared in 5.4.0:
ExecuteRead[T any] and ExecuteWrite[T any], generic variants of the session-level API ExecuteRead and ExecuteWrite
GetRecordValue[T RecordValue] to extract a record value in a type-safe way
GetProperty[T PropertyValue] to extract a node or relationship property in a type-safe way
In the 5.5.0 release, the experimental ExecuteQuery[T] also appeared.
While these additions work great and allow to reduce some of usual boilerplate, they also expose some limitations of the current generics support.
These generic APIs are defined at the surface. The core of the driver is still pretty much unsafe and often defaults to the any (Go 1.18 equivalent to interface{}) where a proper type could work better.
Fundamental limitation
The main reason (beyond my own bandwidth) is that Go generics have one very impactful limitation, best illustrated as follows:
Since most the driver has been built with interfaces and struct receivers, you can imagine how this restriction (as technically sound as it is) is very limiting.
Hypothetical Generic Interfaces
Let's say we could retrofit generic type information into the existing interfaces as follows (this is obviously a major breaking change and would not occur before 6.0).
Since I have spent roughly 1 minute on this, maybe there are better solutions than this.
However, there is 1 fundamental limitation that one cannot avoid, in my opinion.
There must be as many DriverWithContext[T] (and SessionWithContext[T]...) as required T, and this is a big no-no.
Imagine you need an int64 for one query and a *User for the other.
The only way you would be able to do it with these generic interfaces would be create one driver instance for each: DriverWithContext[int64], DriverWithContext[*User], ...
This would mean create as many costly connection pools as required result types.
Alternatively, you could define a single type constraint, "union-ing" all the required result types, but that becomes quite cumbersome and inflexible.
I'm happy to be proven wrong here, but I think the only possible solution for the time being is to define new top-level functions.
Needless to say that does not help at all with the current neo4j package cruft 😅
GetPropertyValue limitation
Let's rewind and look at one of the existing top-level generic function and how it's limited by the current lack of generics support internally.
Let's look at GetProperty[T PropertyValue], and zoom in on the PropertyValue definition:
(Note: this is the least ugly definition I could find that makes the compiler happy)
It's not too costly to perform a type assertion of a single any to one of the simple types, and that's what GetProperty does:
value, ok:=rawValue.(T)
if!ok {
zeroValue:=*new(T)
returnzeroValue, fmt.Errorf("expected value to have type %T but found type %T", zeroValue, rawValue)
}
It's not the same story when one gets an []any and needs to get to []T.
The easiest implementation would be to iterate over every element and type-assert them to the desired T and append them to the final []T slice, but it can get pretty costly very quickly.
Supporting better slice types will likely require a rewrite of the internal core message read/write pieces to generic variants and it's not a small undertaking.
Long story short, the current property support is limited to []any.
Note: it gets even more complex with record values, since users can return really any kind of type, so proper type safety is really limited:
We cannot really get beyond []any and map[string]any.
Feedback
Is there any other low-hanging fruit(s) you see when it comes to generic support in the Go driver?
Is the limitation of GetProperty a big blocker for you?
Did I miss better solutions?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Go 1.18 was released on March 2022 and introduced support for the long-awaited generics.
It comes to no-one's surprise that such support was requested soon after for this driver, as you can see here.
Quick Feature overview
Since the initial request, a few generic functions have been added.
The first generic top-level functions appeared in 5.4.0:
ExecuteRead[T any]
andExecuteWrite[T any]
, generic variants of the session-level APIExecuteRead
andExecuteWrite
GetRecordValue[T RecordValue]
to extract a record value in a type-safe wayGetProperty[T PropertyValue]
to extract a node or relationship property in a type-safe wayIn the 5.5.0 release, the experimental
ExecuteQuery[T]
also appeared.While these additions work great and allow to reduce some of usual boilerplate, they also expose some limitations of the current generics support.
These generic APIs are defined at the surface. The core of the driver is still pretty much unsafe and often defaults to the
any
(Go 1.18 equivalent tointerface{}
) where a proper type could work better.Fundamental limitation
The main reason (beyond my own bandwidth) is that Go generics have one very impactful limitation, best illustrated as follows:
Since most the driver has been built with interfaces and struct receivers, you can imagine how this restriction (as technically sound as it is) is very limiting.
Hypothetical Generic Interfaces
Let's say we could retrofit generic type information into the existing interfaces as follows (this is obviously a major breaking change and would not occur before 6.0).
Since I have spent roughly 1 minute on this, maybe there are better solutions than this.
However, there is 1 fundamental limitation that one cannot avoid, in my opinion.
There must be as many
DriverWithContext[T]
(andSessionWithContext[T]
...) as requiredT
, and this is a big no-no.Imagine you need an
int64
for one query and a*User
for the other.The only way you would be able to do it with these generic interfaces would be create one driver instance for each:
DriverWithContext[int64]
,DriverWithContext[*User]
, ...This would mean create as many costly connection pools as required result types.
Alternatively, you could define a single type constraint, "union-ing" all the required result types, but that becomes quite cumbersome and inflexible.
I'm happy to be proven wrong here, but I think the only possible solution for the time being is to define new top-level functions.
Needless to say that does not help at all with the current
neo4j
package cruft 😅GetPropertyValue
limitationLet's rewind and look at one of the existing top-level generic function and how it's limited by the current lack of generics support internally.
Let's look at
GetProperty[T PropertyValue]
, and zoom in on thePropertyValue
definition:PropertyValue
is defined as a type that can be either ofbool
,int64
, ...,[]any
.Since this is used in the context of
GetProperty
, this type is supposed to reflect all the possible types of a node's or relationship's property.Where it falls short is that
[]any
is actually way too broad of a type.A node or relationship can define array properties, but these arrays must be:
A better definition would be:
(Note: this is the least ugly definition I could find that makes the compiler happy)
It's not too costly to perform a type assertion of a single
any
to one of the simple types, and that's whatGetProperty
does:It's not the same story when one gets an
[]any
and needs to get to[]T
.The easiest implementation would be to iterate over every element and type-assert them to the desired
T
and append them to the final[]T
slice, but it can get pretty costly very quickly.Supporting better slice types will likely require a rewrite of the internal core message read/write pieces to generic variants and it's not a small undertaking.
Long story short, the current property support is limited to
[]any
.Note: it gets even more complex with record values, since users can return really any kind of type, so proper type safety is really limited:
We cannot really get beyond
[]any
andmap[string]any
.Feedback
Is there any other low-hanging fruit(s) you see when it comes to generic support in the Go driver?
Is the limitation of
GetProperty
a big blocker for you?Did I miss better solutions?
Please add your voice to the discussion!
Beta Was this translation helpful? Give feedback.
All reactions