hasura / graphql-engine

Blazing fast, instant realtime GraphQL APIs on your DB with fine grained access control, also trigger webhooks on database events.
https://hasura.io
Apache License 2.0
31.1k stars 2.77k forks source link

Parsing of literal composite type containing array #2084

Open cortopy opened 5 years ago

cortopy commented 5 years ago

As per https://github.com/hasura/graphql-engine/issues/1041, I'm using the literal type escape hatch in order to insert a record into a table which contains a composite type.

This is a simplified sql mockup of my data:

CREATE TYPE public.value_qualifier AS
 ENUM ('qualifier1','qualifier2','qualifier3','qualifier4');

CREATE TYPE public.value_qualified AS
(
  value text,
  qualifier public.value_qualifier[]
);

CREATE TABLE public.test (
    id serial NOT NULL,
    value public.value_qualified,
    CONSTRAINT test_pkey PRIMARY KEY (id)
);

The issue arises when trying to insert data into the value column that contains more than one element in the array.

That's a bit of a mouthful, so as a set of examples, this works using the HTTP interface that graphiQL uses (not tested on graphQL):

{
  value: "('some-text',{})"
}

This also works

{
  value: "('some-text',{qualifier1})"
}

However, this doesn't:

{
  value: "('some-text',{qualifier1,qualifier2})"
}

The error I'm getting is something like Warning: Missing translation for key: "{"path":"$.args","error":"malformed array literal: \"{qualifier1\"","code":"data-exception"}"

Not having looked at the source code I've been scratching my head as to why this happens. It looks to me as if the parsing of the composite type does something like split(",", x) in a way that divides the string within the parentheses into x number of values. However, if this is the case, the parsing is breaking the array up.

It's as if hasura is sending {qualifier1 as a value, hence the malformed array error

GavinRay97 commented 4 years ago

+1

sanderhahn commented 4 years ago

Was looking into related issue regarding escaping and found that this seems to work:

mutation {
  insert_test(objects:[{
    value: "('x',{qualifier1\\,qualifier2})"
  }]) {
    affected_rows
  }
}
hasura=> select to_json(value) from test;
                         to_json                         
--------------------------------------------------------- 
 {"value":"'x'","qualifier":["qualifier1","qualifier2"]}

Parsing can potentially be handled using:

hasura=> select * from json_populate_record(null::test, '{"value":{"value":"x","qualifier":["qualifier1","qualifier2"]}}');
 id |             value             
----+-------------------------------
    | (x,"{qualifier1,qualifier2}")
(1 row)
ozanmakes commented 4 years ago

I'm trying to get Hasura to execute this query:

INSERT INTO foo VALUES ( (1,ARRAY[ row(275,303,313,0)::bar ]) );

I've tried the following GraphQL mutation with various escapings as suggested by @sanderhahn, but I'm always getting either "malformed array literal" or "malformed record literal" back:

mutation MyMutation {
  insert_foo(object: {item: "(1,ARRAY[ row(275,303,313,0)::bar ])"}) {
    ...
  }
}

Is there a way to extend the workaround to this, or is it unsupported altogether?

sanderhahn commented 4 years ago

Hey Ozan! How are you doing? :)

Not sure if this is similar:

create type bar as (
    a int,
    b int,
    c int,
    d int
);

create table foo (
    id int,
    bars bar[]
);

-- dropped one pair of parenthesis
insert into foo values (1, array[ row(275,303,313,0)::bar ]);

-- external text representation of array literals
insert into foo values (2, '{"(275,303,313,0)"}');

-- possible json notation of composite type
select to_json(array[ row(275,303,313,0)::bar ]);
-- [{"a":275,"b":303,"c":313,"d":0}]

Update: found out later that the slashes are not necessary here... somehow they are removed before the value reaches PostgreSQL if you enable sql logs.

mutation {
  insert_foo_one(object: {
    id: 3,
    bars: "{\"(275\\,303\\,313\\,0)\"}"
  }) {
    id
    bars
  }
}

More convenient syntax would be allowing json as input for the composite type:

mutation InsertFoo($id: Int!, $bars: _bar) {
  insert_foo_one(object: {
    id: $id,
    bars: $bars
  }) {
    id
    bars
  }
}
{
  "id": 4,
  "bars": [{"a":275,"b":303,"c":313,"d":0}]
}

Error: A string is expected for type: _bar

ozanmakes commented 4 years ago

Hey Sander, awesome to run into you again! Shoot me an email (it's in my profile), I'd love to catch up!

And thanks for the tip, that did the trick. For my schema I needed to escape the commas further. Not pretty, but works:

 insert_foo_one(object: {
    foo: "(1,{\"(224\\\\,4\\\\,481\\\\,0)\"})"
  }) {
    id
    bars
  }
eads commented 3 years ago

Bump! My colleague @ilica and I just ran into this, and it would be pretty nice to be able to pass straight JSON into columns typed as ARRAYs.