avram
avram copied to clipboard
Query Criteria need more information on the column
Related: https://github.com/luckyframework/avram/pull/733 Also sort of related: https://github.com/luckyframework/lucky/discussions/1583#discussioncomment-1382134
When we run a query, each column method on the query object is actually an instance of Criteria. In this Criteria object, we know the name of the column, and the query object class, as well as the value to query for. The thing we don't know is what type that column is.
For example:
PostQuery.new.posted(true)
In this example, posted() is basically
def posted
Bool.adapter.criteria(self, "posts.posted")
end
def posted(value)
posted.eq(value)
end
The criteria method here is basically
def criteria
Criteria(PostQuery, Bool).new(post_query, "posts.posted")
end
We can infer that posted is Bool within the Criteria by checking that in Criteria(T, V), V is Bool. However, if we use an Array...
PostQuery.new.tags(["crystal", "lucky"])
def tags
Array(String).adapter.criteria(self, "posts.tags")
end
def criteria
Criteria(PostQuery, String).new(post_query, "posts.tags")
end
It ends up looking like this. We run everything through the String adapter for Array(String), so now we don't really know that tags is an Array. We also have Enum, and JSON::Any which are a bit complex. If we want to start having special query methods that can only be ran on these complex types, then the Criteria needs to understand what they are, but at compile-time.
One option I tried, was to pass in "optional" generics, but it gets pretty messy.
class Criteria(T, V, *R)
def special_array_method
check_array(R[0]?)
end
def check_array(is_array : Array.class)
end
def check_array(is_anything_else : Nil)
{% raise "compile-time error" %}
end
end
# `R` is a Tuple so R[0] is `nil`
Criteria(PostQuery, String)
# `R[0]` here is `Array(String)`
Critera(PostQuery, String, Array(String))
The issue I ran in to here was within macro land, things got quite messy, and I was fighting the compiler the entire time when some classes said they needed the methods when they didn't.
I'm not sure if this is the right path, or if there's a better one, but if we want to start really flexing what postgres can do in queries, we will need to have a bit more control.