Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to encode array of composites #65

Closed
bgamari opened this issue Dec 18, 2016 · 7 comments
Closed

Unable to encode array of composites #65

bgamari opened this issue Dec 18, 2016 · 7 comments

Comments

@bgamari
Copy link

bgamari commented Dec 18, 2016

The encoders supplied in Hasql.Encoders are not expressive enough to represent an array of composite values. Specifically, arrayValue expects a Value, which isn't a semigroup.

@nikita-volkov
Copy link
Owner

nikita-volkov commented Dec 18, 2016

There's intentionally no encoders for composite types, because Postgres doesn't provide enough support for them (on the OID) level. Decoders are not a problem, but Encoders are.

@bgamari
Copy link
Author

bgamari commented Dec 18, 2016 via email

@nikita-volkov
Copy link
Owner

There is no predefined OID for a composite type

@tekul
Copy link

tekul commented Mar 29, 2017

Is there some way of working around this? I have a table which has a column containing an array of text, integer pairs and I'm not sure how to update or insert to that with Hasql. Would I have to build the explicit SQL for the entire command, including the array content?

@nikita-volkov
Copy link
Owner

Would I have to build the explicit SQL for the entire command, including the array content?

That's an option you always have.

As for the alternative, I recommend exploring something in the spirit of a solution to multiple values, based on unzipping of arrays. (Please, post back if you find a solution).

BTW, some form of support for composite encoding is likely to come in the next major release.

@tekul
Copy link

tekul commented Mar 29, 2017

Thanks a lot for your super fast response! With the help of that link I managed to get it to work, though I'm pushing my boundaries somewhat, and there may be a better way :). Since my composite type only has two fields it's not overly complex. As suggested I split it into two separate arrays of type text and int2 and then recombine them with a cast in the SQL.

The relevant part of my schema is:

CREATE TYPE dict_entry AS (word text, index smallint);

CREATE TABLE story
    ( story_id text PRIMARY KEY
    , title text NOT NULL
    , img_url text NOT NULL
    , level smallint NOT NULL CHECK (level >= 0 AND level < 10)
    , curriculum text NOT NULL CHECK (length(curriculum) > 0)
    , tags text[] NOT NULL
    , content text NOT NULL CHECK (length(content) > 0)
    , words dict_entry[] NOT NULL
    , created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
    );

and my corresponding Hasql code is

evText :: E.Params Text
evText = E.value E.text

insertStory :: Query Story ()
insertStory = Q.statement sql storyEncoder D.unit True
  where
    sql = "INSERT INTO story (story_id, title, img_url, level, curriculum, tags, content, words) \
                 \VALUES ($1, $2, $3, $4, $5, $6, $7, (array(select word::dict_entry from unnest ($8, $9) as word)))"

storyEncoder :: E.Params Story
storyEncoder =
    contramap (fromJust . storyId) evText <>
    contramap title evText <>
    contramap img evText <>
    contramap (fromIntegral . storyLevel) (E.value E.int4) <>
    contramap (head . tags) evText <>
    contramap (tail . tags) (array E.text) <>
    contramap content evText <>
    contramap (map word . words) (array E.text) <>
    contramap (map (fromIntegral . index) . words) (array E.int2)
  where
    array v = E.value (E.array (E.arrayDimension foldl' (E.arrayValue v)))
    storyId = id :: Story -> Maybe Text
    storyLevel = level :: Story -> Int

The Haskell data types can be found here.

tekul added a commit to ThreeMinuteLearning/my3ml that referenced this issue Mar 29, 2017
Works for storing and retrieving all the story data.

Dealing with the composite list of dictionary entries is a bit tricky when
doing an insert/update and requires a workaround where the entry is unzipped
into arrays and then recombined in the SQL and cast to the dict_entry type.
See nikita-volkov/hasql#65 for more information.
@nikita-volkov
Copy link
Owner

Yes. Seems like a good solution. Thanks for posting back!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants