lykahb / groundhog

This library maps datatypes to a relational model, in a way similar to what ORM libraries do in OOP. See the tutorial https://www.schoolofhaskell.com/user/lykahb/groundhog for introduction
http://hackage.haskell.org/package/groundhog
176 stars 39 forks source link

Add PersistList to PersistVal data type #83

Closed goolord closed 6 years ago

goolord commented 6 years ago

I needed this for a project I work on, this is useful to me for querying arrays in raw queries. I also added a cabal.project file in order to make working on it easier for me, as well as adding some entries to the .gitignore

lykahb commented 6 years ago

Thank you for the PR. The description of the core datatypes may be a bit lacking as it doesn't tell the best practices to work with the expressions or new datatypes.

The PersistValue data type is meant for the types that be stored into a column and that any backend can support. For the complex types that are likely backend-specific there is PersistCustom constructor, that can be seen as a catch-all one making extensions of the datatype unnecessary.

There may be several things in the project that are similar to your use case. The Postgresql backend has Array, Geometry and HStore. Postgresql Array can be used in raw queries and expressions. The Functions module has in_ and notIn_ that accept lists of expressions. If none of those are what you are looking for, they can serve as examples of making more complex expressions or datatypes.

goolord commented 6 years ago

Consider the type of

queryRaw :: (PersistBackend m, Conn m ~ conn) => 
             Bool           -- ^ keep in cache
          -> String         -- ^ query
          -> [PersistValue] -- ^ positional parameters
          -> m (RowStream [PersistValue])

I don't know if I'm just being obtuse, but I can't see a way to use Arrays in queries without expanding the PersistValue data type here. PersistCustom doesn't cut it for most backends here because the relevant instances call error. (for the postgresql backend):

toField (P (PersistCustom _ _))       = error "toField: unexpected PersistCustom"

How would I go about querying an array like this?

None of the libraries warn of incomplete pattern matches, so I am assuming that I covered each backend here.

If the advice here to is to rewrite the query without using queryRaw, I will just close this PR, I'm not that attached to it.

lykahb commented 6 years ago

None of the backends accepts PersistCustom directly. It is rendered by the Sql module before getting to the backend. PersistCustom was added ad-hoc to allow values that affect the query. break the existing API. The types could be improved to avoid the incomplete patterns. When PersistValue gets to parameter substitution it is too late to update the query with PersistCustom. I think that Groundhog should accommodate any use case without extending PersistValue.

The raw queries are different from the others as they aren't going through the rendering pipeline. If they were, you could define, say, a newtype InlineList and convert it into PersistCustom. A lot of Groundhog flexibility isn't readily available for the raw queries.

As far as I understand, you have something this where the list length is unknown.

let list = map toPrimitivePersistValue ([1, 2, 3] :: [Int])
queryRaw "SELECT a FROM tbl WHERE b IN [?, ?, ?]" list

One way to get this work is to make a helper function that generates a part of the query. This is generic and keeps the core datatypes generic as well.

queryRaw ("SELECT a FROM tbl WHERE b IN " ++ listQuery list) list
goolord commented 6 years ago

Ok, as I thought. Thanks for taking the time to explain.