hasql-postgres icon indicating copy to clipboard operation
hasql-postgres copied to clipboard

Export more

Open mikeplus64 opened this issue 9 years ago • 8 comments

At the moment you can't write instances for CxValue or CxRow for anything, since neither the StmtParam nor the ResultValue constructors are exported. (I need CxValue instances for non-homogenous data, which I can get by using the JSON Value instance, but that is quite a roundabout method. Tuple instances for CxValue would also work I guess, but still in a kind of roundabout way -- I'd be immediately repackaging them anyway)

mikeplus64 avatar Apr 08 '15 16:04 mikeplus64

I'm gonna need some motivating example, because I don't understand the use case so far.

I suspect that this is closely related to https://github.com/nikita-volkov/hasql-postgres/issues/4. I've provided an extensive explanation of why such these classes are not exported in that discussion.

nikita-volkov avatar Apr 08 '15 17:04 nikita-volkov

I want StmtParam and ResultValue exported, so we can write our own CxRow and CxValue instances. I suppose you'd also need more of Mapping exported for that to be possible (and convenient).

It's extremely cumbersome to work with many-element tuples, so I (and I expect everybody else...) repackage results from queries into custom types. The overhead is unnecessary, and the tuple to record code is very precarious. It's easier to be reason about clear code.

hasql-postgres doesn't (and can't) support any queries that use array_agg on "heterogeneous" (... homogenously heterogenous? :) I mean things that don't have the same type for all fields - ie, pretty well any table you'd actually use) data.

CREATE TABLE tag(id INT PRIMARY KEY, name TEXT);
CREATE TABLE post (id INT PRIMARY KEY, ... lots of stuff ...);
CREATE TABLE post_to_tag(post INT REFERENCES post(id), tag INT REFERENCES tag(id));

Here's a query that we can't do with these tables, with hasql-postgres:

SELECT post.*, array_agg(tag.*) AS tags 
FROM post 
  JOIN post_to_tag ON post_to_tag.post = post.id 
  JOIN tag ON post_to_tag.tag = tag.id 
GROUP BY post.id; 

It would be possible with tuple instances for CxValue (I don't know whether you'd want to use a Mapping instance behind the scenes), or with a custom instance for the Haskell-side Tag type.

mikeplus64 avatar Apr 09 '15 02:04 mikeplus64

Looking into it, what it (tuple instances for Mapping (=> CxValue) -- not the argument for whether users should be able to write their own CxRow/CxValue instances) seems to boil down to is supporting composite types, which I think Postgres creates out of rows you give to array_agg. A much simpler but maybe less compelling example is just

CREATE TABLE thing(foo int, bar text);
SELECT array_agg(thing.*) FROM thing;

Might be worth opening a new issue for that?

mikeplus64 avatar Apr 09 '15 08:04 mikeplus64

The way I understand it, your issue can be separated in three:

  1. Request for support of custom mappings to rows
  2. Request for support of custom mappings to Postgresql's types
  3. Request for support of Postgresql's "row" types. As I understand array_arg revolves around them.

Concerning the support for "row" types, I confirm, this should be implemented. However it will require some work in the "postgresql-binary" package. Unfortunately I'm not gonna have time to do this any time soon. Contribution is welcome though. The way I see it, "row" types should be mapped to tuples as well.

As for the custom mappings, the discussion in https://github.com/nikita-volkov/hasql-postgres/issues/4 is indeed closely related. Long story short, I find that types of result rows only make sense in the context of the executed query. They pertain to the query, not to a Postgres table or any custom model in Haskell. In one query you may be getting only the dates and titles of your posts, in another one - all the fields you have in that table, in a third one you'll be joining it with another table - are you gonna create a custom type for each such case? This is one of the reasons why I believe that tuples are the most consistent way to access the results of queries, and I intend them to be used as intermediate results, which the user than converts to some normalised model. Other reasons I explain in the aforementioned discussion.

nikita-volkov avatar Apr 09 '15 09:04 nikita-volkov

My grumble is that tuple mangling code is just very cumbersome, as well as feeling unnecessary. For my application, I actually am making custom types for pretty much every query I'm using in outside-the-backend code. Dealing with these custom types is nicer than 10+ element tuples. I just compose these types together for large joins since I don't really have any cases where I want only some columns from whichever tables, but not others.

As is probably apparent from the content of my posts here, I don't know head nor tail of postgres internals... Maybe a look at hpqtypes, which seems to support composite types (I'm not 100% on this, since at least in postgresql-simple, they refer to "composite types" as to mean the concatenation of row columns), will be enlightening.

mikeplus64 avatar Apr 09 '15 15:04 mikeplus64

My grumble is that tuple mangling code is just very cumbersome, as well as feeling unnecessary. For my application, I actually am making custom types for pretty much every query I'm using in outside-the-backend code. Dealing with these custom types is nicer than 10+ element tuples. I just compose these types together for large joins since I don't really have any cases where I want only some columns from whichever tables, but not others.

Can you provide a few examples? It's just that I haven't met any such problems in my experience, but I don't want to judge subjectively.

I don't know head nor tail of postgres internals

Well, neither did I before I implemented that library ) Basically it's about translation of the "libpqtypes" library. Here is a post, which in part provides an introduction to how it can be approach.

nikita-volkov avatar Apr 09 '15 20:04 nikita-volkov

I think as per #16, Generic instances for CxRow - ie: :*:, :.:, M1 and K1 - is a good compromise. Users can't shoot themselves in the foot so long as the Generic instance is correct, but they also avoid any tuple-related foot incidents.

mikeplus64 avatar Apr 14 '15 13:04 mikeplus64

Thank you for your contribution, Mike. I'll check out the code on the weekend.

nikita-volkov avatar Apr 14 '15 16:04 nikita-volkov