Hey :) I want to make the proposal for named params in fragments.
Current SituationCurrently, fragments are written like `fragment("? + ?", 40, 2)` which is great and simple for small fragments with few distinct params. However, there are some problems:
- Big fragments have readability problems - one has to switch between reading the fragment string and the params list. This adds accidental cognitive load.
- If there are many fragment params, one has to manually keep the params in the correct order.
- If a param is used multiple times within the query, one has to list if just as often in the params list.
This is nicely demonstrated by a fragment I created for my $day_job recently:
fragment(
"? < ? OR (? = ? AND ? <= ?)",
service.invoice_year,
^max_year,
service.invoice_year,
^max_year,
service.invoice_month,
^max_month
)
Let's ignore the fact, that this could be solved in other ways (like creating dates and comparing those instead).
Just looking at the fragment string (
"? < ? OR (? = ? AND ? <= ?)") is confusing and one has to go back and forth between it and the params to verify it works. Some params (e.g.
^max_year) are listed more than once in the params list and the params need to be correctly ordered to work with the 6 question marks in that query string. I don't look forward changing/debugging that little fragment when I have to touch it next :) The situation gets worse for actually big fragments.
ProposalI want to make the proposal for named fragment query-strings.
fragment(
":service_year < :year OR (:service_year = :year AND :service_month <= :month)",
service_year: service.invoice_year,
service_month: service.invoice_month,
year: ^max_year,
month: ^max_month
)
- the query string gets longer, but way easier to read
- we can order our params however we want
- params don't need to be duplicated
My argument for named params is mostly around readability and maintainability of code - which, in my experience, is a pretty high priority in complex systems (since they tend to produce complex ecto queries). One could also make an argument around efficiency by arguing that duplicated params only need to be passed-in once.
Other DiscussionsImplementation
Now, I'm no expert in Ecto internals, but I'm confident there is a (efficient) way to implement this proposal.
And I'm sure there is plenty opportunity to bike-shed around the syntax :) I chose a symbol-like syntax (:param_name) in my example to make its use in the fragment query similar looking to the keyword param. There is other possible ways to interpolate params (e.g. ?{param_name}) which might cause less collisions with SQL.
I went ahead and implemented a named_fragment() macro which implements a fragment with named params and expands into regular a fragment call. This might help us to play with the idea and validate it:
https://github.com/tessi/ecto_named_fragmentMy implementation might solve readability and maintainability problems but can not be more efficient than
fragment(), because after macro-expansion params are duplicated again.
cheers,
tessi