avram icon indicating copy to clipboard operation
avram copied to clipboard

Query Criteria need more information on the column

Open jwoertink opened this issue 4 years ago • 0 comments

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.

jwoertink avatar Sep 30 '21 15:09 jwoertink