-
-
Notifications
You must be signed in to change notification settings - Fork 57
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
Stable column order for generated SQL statements #280
Comments
sounds important and need to be fixed do you have suggestion what to use other than map? maybe sorted slice? 🤔 |
Something like https://github.com/elliotchance/orderedmap can be used instead of map |
At least with this library elements can be looped in order elements were added to map with code: for el := m.Front(); el != nil; el = el.Next() {
fmt.Println(el.Key, el.Value)
} |
although this may increase allocation due to internal linked list, this seems better and easier solution than using sorted slice 🤔 (feel free to work on this if you have time 🙏 ) |
Looked a bit into this but problem is that it affects a lot of public API where maps are used to pass around mutations that are of type |
I could create new |
I think a better way is to make existing // add new field to store first mutation
type Mutation struct {
// don't want to export this, this should not be modified from outside
// should be accessed using First() method
first *Mutate
last *Mutate
// ... other existing definition
}
func (m *Mutation) First() *Mutate {
return m.first
}
// Mutate stores mutation instruction.
type Mutate struct {
// don't want to export this, this should not be modified from outside
// should be accessed using Next() method
next *Mutate
Type ChangeOp
Field string
Value interface{}
} And then we can gradually update existing implementation: for mut := mutation.First(); mut != nil; mut = mut.Next() {
// do something ...
} |
problem is how would you initialize them them when currently everywhere map[string]Mutate is used in API? |
hmmm true, looks like this mostly used in unit testing in this repo, not very sure about use case outside this repo 🤔 how about, for now we can do lazy initialization during first
in the future, I think we can deprecate direct access to the underlying map, and remove it completely in future version |
ah forgot that we pass map of Mutates instead of Mutation to adapter, seems breaking change is un-avoidable 🤦 Lines 15 to 17 in 1cac74f
|
yes that was my proposal to change |
Also why do you avoid using pointers in interfaces? ( |
How will Mutates looks like?
as far as I know, unlike C++, in golang it's not about whether passing reference vs passing by value to avoid copying. it's about whether it'll cause stack allocation vs heap allocation (stack is generally the fastest because heap allocation increase load to GC) and as far as I understand, compiler decide where to allocate using escape analysis, and one of the cause is related to the use of pointer (indirection). so my rule of thumb is basically use plain object as much as possible, use pointer where necessary or it's proven to be better by benchmark. also created a quick benchmark here (shows calling a function wrapped on interface with ptr cause allocation): https://github.com/Fs02/go-pattern-benchmark/tree/master/pass_value_vs_ptr |
I was thinking something like: func NewMutates(mutates ...Mutate) Mutates
type Mutates struct {
...
}
func (m *Mutates) Add(mutate Mutate)
func (m *Mutates) List() []Mutate probably other functions would be needed ( |
any reason why not reuse existing |
Yes |
Only it's that it does duplicate values passed around as fields from |
if not using map, how will you handle access using field name?
sorry, I don't quite understand the case |
I don't seem to be finding anywhere where currently there would be need to access Mutate by field name (only use case currently seems to be only to not allow duplicate mutates for same field). If that's really needed we could have two struct fields: mutates []Mutate
fields map[string]int fields would contain field name as map key and position in mutates array as value. If really needed new function could be added to return Mutate by field name: func (m *Mutatation) Get(field string) Mutate
I mean OnConflict is passed as different argument etc but in such case it could be removed from arguments also so that's fine. |
Insert all query use it for example:
that won't be scalable in the future if we need to have function to delete I prefer to use solution like orderedmap (map that refer to linkedlist items), which I described here: #280 (comment) |
Even if delete is needed I don't think it's much of a problem: i, ok := fields[fieldName]
if !ok {
return
}
delete(fields, fieldName)
mutates = append(mutates[:i], mutates[i+1:]...) |
basically need to shift the whole array? |
Because it can have errors later on. As mostly in library uses non pointer values to pass around you could have copied Mutate value somewhere that would point to different next that in the original map where it could have been changed (like same deleted element). |
hmm, even if we delete the Next value, copied Mutate will still point to a valid pointer, because we can only delete element entry from map (not deallocating the actual element)
this still have but I guess this shouldn't be a problem, better way to delete a |
let's go with the (seems map of linked list item is not a very clean solution, a lot of pointers involved https://go.dev/play/p/tIgI5M2fWq0) |
Currently looks like because of map usage for storing column data each time insert/update/select SQL statements are generated column order is different, that makes grouping of SQL statements for perf stats in logs impossible and also this makes it unusable for prepared statement caching being polluted and unusable that hurts performance.
The text was updated successfully, but these errors were encountered: