So I’m writing a query (with Postgres) that uses a row value/composite value. For a little background see https://use-the-index-luke.com/blog/2013-07/pagination-done-the-postgresql-way slide 22.
My query looks roughly like this:
SELECT first_name, role, inserted_at
FROM users
WHERE (ARRAY_POSITION(array['admin', 'member', 'previous_member'], role::text), inserted_at) >= (1, '2018-07-04 09:01:25.320614')
ORDER BY ARRAY_POSITION(array['admin', 'member', 'previous_member'], role::text) DESC, inserted_at ASC;
The array_position
call isn’t too important here but it allows me to define a custom order for a postgres enum: https://stackoverflow.com/questions/4088532/custom-order-by-explanation
The important part of the query above would look like this with basic sorting, the where clause: WHERE (role, inserted_at) >= (1, '2018-07-04 09:01:25.320614')
, and the order by: ORDER BY role DESC, inserted_at ASC
Since as far as I can tell Ecto doesn’t have a built-in macro to generate these row values (basically just comma-separated expressions) I want to create a macro to generate my own. However this is complicated by the fact that I want to create a macro that would work with a row value of any size, so rather than just 2 parts as above, it should support 3 as well.
A basic fragment for a 2-part row value looks like:
fragment("(?, ?) >= (?, ?), arg1, arg2, arg3, arg4)
and for a 3-part row value it would look like:
fragment("(?, ?, ?) >= (?, ?, ?)", arg1, arg2, arg3, arg4, arg5, arg6)
But also you’d want to pass in the operator instead of hard-coding it to ">="
so you need one more argument. And of course Elixir doesn’t have variadic functions so you have to pass in a list. And rather than pass in all the arguments in the same order that they appear in the query I think it would make sense to pass them in with their associated counterpart, so a list of tuples:
If the SQL looked like (colA, colB) >= (valA, valB)
then the list would look like [{colA, valA}, {colB, valB}]
So with that out of the way this is how I would like to call the macro in a where
query:
where(query, [user], row_value_comparison(">=", [{user.role, ^role}, {user.inserted_at, ^inserted_at}]))
But I’m having trouble defining the row_value_comparison
macro, specifically I’m running into trouble trying to call fragment
with a variable amount of arguments, would Kernel.apply/3
work here?