graphql-engine icon indicating copy to clipboard operation
graphql-engine copied to clipboard

On-conflict support for partial unique indexes

Open Mike96Angelo opened this issue 4 years ago • 24 comments

I was trying to add Hasura to an existing project and noticed that my Postgresql Unique Indices weren't showing up in the Hasura Console.

These Postgresql Unique Indices are also missing in the on_conflict constraint enum in the GraphQL

NOTE: I am not using Hasura to manage my db migrations.

Docker Image: hasura/graphql-engine:v1.0.0.cli-migrations

GraphQL query:

mutation {
  insert_MyTable(
    on_conflict: {
      constraint: MyTable_column1_column2_key, 
      update_columns: [ column3, column4 ]
    }, 
    objects: {
      column1: "value1",
      column2: "value2",
      column3: "value3",
      column4: "value4"
    }
  ) { 
    returning {
      id
    }
  }
}

Responds with the following error:

{
  "errors": [
    {
      "extensions": {
        "path": "$.selectionSet.insert_MyTable.args.on_conflict.constraint",
        "code": "validation-failed"
      },
      "message": "unexpected value \"MyTable_column1_column2_key\" for enum: 'MyTable_constraint'"
    }
  ]
}

Mike96Angelo avatar Jan 08 '20 17:01 Mike96Angelo

Postgres's ON CONFLICT clause requires you to specify the name of the unique constraint, you cannot use the name of the unique index. You can create a unique constraint as follows:

ALTER TABLE <table_name> ADD CONSTRAINT <constraint_name> UNIQUE (<column1>, <column2> );

This will automatically create a unique b-tree index on the specified group of columns.

cc @marionschleifer We'll need to add this to the docs where we talk about upserts.

0x777 avatar Jan 09 '20 06:01 0x777

@0x777 Perfect that worked Thanks!

Mike96Angelo avatar Jan 09 '20 15:01 Mike96Angelo

OK one case where I've found no solution is where I need to add a partial index. https://stackoverflow.com/questions/8289100/create-unique-constraint-with-null-columns

In my case I have composite index of three columns:

{
  "routes": {
    "connection_airport_id": "1",
    "connection_airport_id": null,
    "destination_airport_id": "1"
  }
}

The connection_airport_id may or may not be null. A composite unique constraint allows multiple rows as per the above example. If a value is present for connection_airport_id, the constraint works fine. I found the solution to be a partial index as described here: https://www.enterprisedb.com/postgres-tutorials/postgresql-unique-constraint-null-allowing-only-one-null

However, this doesn't allow me to use on conflict.

Is there any workaround here?

toddheslin avatar Jun 11 '20 04:06 toddheslin

Another use case is creating unique indexes using expressions like

CREATE UNIQUE INDEX example_index ON example_table ((some_json_field->>'field1'), (some_json_field->>'field2'));

Unfortunately not possible with a constraint.

remiremi avatar Jul 03 '20 17:07 remiremi

Related to #2219

tirumaraiselvan avatar Jul 15 '20 06:07 tirumaraiselvan

Bumping this - working on a feature where we want to upsert with on_conflict being on a partial unique index on a table; currently this isn't possible. I'm getting an error:

{
  "errors": [
    {
      "extensions": {
        "path": "$.selectionSet.insert_<tablename>.args.on_conflict.constraint",
        "code": "validation-failed"
      },
      "message": "unexpected value \"unique_idx_name\" for enum: '<tablename>_constraint'"
    }
  ]
}

Any chance you'll add support for this? It's possible in Postgres; I've successfully upsert-ed on a unique index before.

cakemountain avatar Sep 24 '20 21:09 cakemountain

I've experienced this as well. Even from the GraphQL explorer, the UI suggests the constraint name, but it errors when you execute.

jmoseley avatar Oct 16 '20 18:10 jmoseley

On-conflict support for partial unique indexes (or exclusion constraints) would be great.

MikeBosw avatar Oct 17 '20 13:10 MikeBosw

For anyone that runs into this in the future: I was also having the problem where it wouldn't pick up uniqueness constraints, even ones that were added via hasura migrations, that existed in the DB. It turns out I needed to restart the hasura application server for it to recognize the constraints.

AdamFerguson avatar Feb 19 '21 23:02 AdamFerguson

Just ran into this as well. This could be implemented by adding an extra field to the on_conflict parameter, named columns which could be a set of exposed columns for the given schema. Then you could use this to generate the corresponding SQL with the already existing where parameter: {columns: [a, b], where: {b: {_is_null: true}}} =>

insert into ...
on conflict (a, b) where b is null
do update ...

I am interested in contributing this feature.

thenonameguy avatar May 14 '21 08:05 thenonameguy

I started actually looking into this, it seems the internal Postgres DML types already support mapping columns:

data SQLConflictTarget
  = SQLColumn ![PGCol]
  | SQLConstraint !ConstraintName
  deriving (Show, Eq)

instance ToSQL SQLConflictTarget where
  toSQL (SQLColumn cols)      = "("
                                <~> ("," <+> cols)
                                <~> ")"

  toSQL (SQLConstraint cons) = "ON CONSTRAINT" <~> toSQL cons

data SQLConflict
  = DoNothing !(Maybe SQLConflictTarget)
  | Update !SQLConflictTarget !SetExp !(Maybe WhereFrag)
  deriving (Show, Eq)

instance ToSQL SQLConflict where
  toSQL (DoNothing Nothing)   = "ON CONFLICT DO NOTHING"
  toSQL (DoNothing (Just ct)) = "ON CONFLICT"
                                <~> toSQL ct
                                <~> "DO NOTHING"
  toSQL (Update ct set whr)   = "ON CONFLICT"
                                <~> toSQL ct <~> "DO UPDATE"
                                <~> toSQL set <~> toSQL whr

Seems to be more of a feature exposition issue through GQL.

thenonameguy avatar May 17 '21 21:05 thenonameguy

Any updates on this? To work around this i would need to add an extra column referencing a foreign key so that I can create a unique constraint on 2 columns. Ideally the table does not need to know about the entity its referencing but this would be necessary to create a unique constraint on 2 columns that basically does what a unique partial index would do.

ShehabSN avatar Jun 26 '21 21:06 ShehabSN

I just stumbled on this - would be really nice to have!!

carlpaten avatar Jul 15 '21 21:07 carlpaten

@tirumaraiselvan or someone else from Hasura: would you welcome a PR implementing on_conflict support for partial unique indexes?

carlpaten avatar Jul 20 '21 15:07 carlpaten

This would be a great add-on. Anyone found a workaround for preventing multiple Null values on a column with a unique constraint that doesn't involve partial unique indexes?

carlosbaraza avatar Aug 02 '21 09:08 carlosbaraza

I just stumbled on this too - would be really nice to have @0x777 !!

nestarz avatar Aug 23 '21 12:08 nestarz

Bump. This would be useful for my team as well.

cakemountain avatar Oct 27 '21 02:10 cakemountain

I think this ticket was unintentionally closed. The referred commit deals with mssql…

dvekeman avatar Mar 10 '22 19:03 dvekeman

Would love to add a check constraint against a partial unique index as well

cdussud avatar Mar 24 '22 14:03 cdussud

watching. hasura suggests using rails migrations and yet it doesn't support the strategy of uniqueness of rails migrations.

sakirtemel avatar Mar 26 '22 17:03 sakirtemel

@tirumaraiselvan Bumping this as closed incorrectly - on-conflict support for partial unique indexes still very much desired.

crieggalder avatar Sep 05 '22 21:09 crieggalder

@tirumaraiselvan is this on the roadmap? This has come up several times in the past year with my team :)

cakemountain avatar Sep 08 '22 20:09 cakemountain

my team also needs this heavily

yogeshwar607 avatar Sep 09 '22 10:09 yogeshwar607

@tirumaraiselvan any traction for this one yet?

thomaslogangraves avatar Oct 13 '22 16:10 thomaslogangraves