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

Add a story for upsert #196

Closed
sgrif opened this Issue Feb 6, 2016 · 34 comments

Comments

Projects
None yet
6 participants
@sgrif
Member

sgrif commented Feb 6, 2016

This is currently a gap in our API. Both SQLite and PG support it, though I'm not sure we can do a backend agnostic abstraction. I'm fine with providing two backend specific abstractions for this case.

@sgrif

This comment has been minimized.

Member

sgrif commented Apr 22, 2016

Bumping to the 0.7 milestone. SQLite has extremely simple semantics, and we should at least be able to implement it for at least that backend. PG might get bumped to 0.8.

I don't think we can abstract over this in a database agnostic way. Instead we will provide backend specific APIs. SQLite will probably be insert(&new_record).into(table).on_conflict(replace)

@sgrif

This comment has been minimized.

Member

sgrif commented Apr 22, 2016

SQLite provides 5 options for alternate behaviors. REPLACE, ROLLBACK, ABORT, FAIL, and IGNORE. We won't provide support for INSERT OR ROLLBACK, as our handling of transactions is based on lexical scope and we can't properly handle this case.

sgrif added a commit that referenced this issue Apr 23, 2016

Add support for `INSERT OR REPLACE` on SQLite
First piece of #196. SQLite has extremely simple semantics here. `INSERT
OR REPLACE` will literally take what the new row would have been had the
insert succeeded, and will replace the existing row with it. For this to
be most useful, we'll probably want to make it easier to use non-literal
expressions in insert at some point in the future. We'll need to see
what other usage looks like.

This also lays the groundwork for supporting the other modifiers on
SQLite. I had originally planned on doing an API that looks more like
what PG will look like. However, since the semantics are so different,
we'll probably want to keep the APIs from stepping on each others toes.

Another alternative I had leaned towards was `insert.or(replace)`.
However, as I started thinking about the compile fail tests that'd be
required there, and saw the implementation, I shied away from it. Since
there's a fixed set of 4 modifiers that we will support, I think one
function each is fine.

@sgrif sgrif modified the milestones: 1.0, 0.7 Jun 27, 2016

@Eijebong

This comment has been minimized.

Member

Eijebong commented Aug 1, 2016

Any ETA for postgres support ? :)

@sgrif

This comment has been minimized.

Member

sgrif commented Aug 1, 2016

Is this a blocker for your application? I can definitely get it done for 0.8

@Eijebong

This comment has been minimized.

Member

Eijebong commented Aug 1, 2016

Yes, it's a blocker.

@sgrif sgrif modified the milestones: 0.8, 1.0 Aug 1, 2016

@sgrif

This comment has been minimized.

Member

sgrif commented Aug 1, 2016

Tentative release date for 0.8 is the 28th (if enough little things like this get completed I might release sooner though)

@Eijebong

This comment has been minimized.

Member

Eijebong commented Aug 1, 2016

Cool, thank you !

@drusellers

This comment has been minimized.

drusellers commented Oct 11, 2016

👍

@norcalli

This comment has been minimized.

norcalli commented Nov 28, 2016

This doesn't seem to have made it into the 0.8 release for Postgres, is it possible to prioritize a backend specific method for the next release? If I have the time, I may be able to send a PR (over the next few weeks)

@sgrif

This comment has been minimized.

Member

sgrif commented Nov 28, 2016

It will be in the next release. 0.8 was featureless as we needed to get macros 1.1 support out.

@sgrif

This comment has been minimized.

Member

sgrif commented Dec 8, 2016

I'm going to release 0.9 today. Unfortunately, I wasn't able to complete this in time despite my best efforts. PG's upsert is an extremely complex feature, and it's just going to take some more time for me to implement proper support with an API that is up to our standards. I can't push back the 0.9 release any longer, as I am going on vacation on Tuesday and I want to release with enough time to fix any issues that may occur. So I will move this to the 0.10 release, which is slated for the end of January.

@sgrif sgrif modified the milestones: 0.10, 0.9 Dec 8, 2016

@norcalli

This comment has been minimized.

norcalli commented Dec 8, 2016

I understand.

@norcalli

This comment has been minimized.

norcalli commented Dec 8, 2016

I didn't mean to submit that comment, dang mobile. I understand, as I've had some dealings with trying to implement it myself in Scala/Slick, and I appreciate waiting to implement it properly.

Have a good vacation!

@seamusabshere

This comment has been minimized.

seamusabshere commented Feb 7, 2017

hey @sgrif do you have a WIP branch for this?

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 7, 2017

@seamusabshere No, just a few spikes which I'm going to throw the code away from.

@emk

This comment has been minimized.

Contributor

emk commented Feb 8, 2017

I would be interested on working on this (as soon as I finish the uuid upgrade and integration tests). Do you have any ideas about what the API should look like?

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 8, 2017

That's the main thing I'm trying to decide on. I need to spike on it a bit more to decide for sure. I think it'll probably be something like the examples below. Feel free to take a swing at it if you'd like, but I suspect that implementing this properly will require a pretty thorough knowledge of our internals. I'm skipping all of the modifiers on the conflict target for now. I'm also unsure if I want to handle multiple on conflict clauses to start, but I've included it below.

// In addition to the examples that I say must fail to compile, all of this must fail to compile if used with a backend other than PG.

// single record ON CONFLICT DO NOTHING
insert(&new_user.on_conflict_do_nothing()).into(users)
// multi-record ON CONFLICT DO NOTHING
insert(&vec![user1, user2].on_conflict_do_nothing()).into(users)
// Need to ensure that this does not compile
insert(&vec![user1.on_conflict_do_nothing()]).into(users)

// everything below should also work with vecs, should also fail to compile if the conflict handler is on a record inside the vec
// single record single handler by constraint name
insert(&new_user.on_conflict(on_constraint("users_pkey"), do_update().set(any_valid_as_changeset)).into(users)
// should fail to compile
insert(&new_user.on_conflict(on_constraint("users_pkey"), do_update().set(posts::title.eq("foo"))).into(users)
// single record single handler with where clause
insert(&new_user.on_conflict(on_constraint("users_pkey"), do_update().set(any_valid_as_changeset).filter(any_valid_predicate)).into(users)
// should fail to compile
insert(&new_user.on_conflict(on_constraint("users_pkey"), do_update().set(any_valid_as_changeset).filter(non_bool_expr)).into(users)
// should fail to compile
insert(&new_user.on_conflict(on_constraint("users_pkey"), do_update().set(any_valid_as_changeset).filter(posts::title.eq("hi"))).into(users)
// single record single handler by single column
insert(&new_user.on_conflict(id, do_update().set(any_valid_as_changeset)).into(users)
// single record single handler by multiple columns
insert(&new_user.on_conflict((project_id, email), do_update().set(any_valid_as_changeset)).into(users)
// single record single handler by arbitrary expr
insert(&new_user.on_conflict(lower(email), do_update().set(any_valid_as_changeset)).into(users)
// should fail to compile
insert(&new_user.on_conflict(posts::id, do_update().set(any_valid_as_changeset)).into(users)
// single record multiple handlers
insert(&new_user.on_conflict(any_valid_args).on_conflict(any_valid_args)).into(users)
// ON CONFLICT DO NOTHING with conflict_target
insert(&new_user.on_conflict(id, do_nothing()))

I think that's everything, though I might have forgotten a few cases.

@emk

This comment has been minimized.

Contributor

emk commented Feb 8, 2017

Hmm, yeah, that's complicated enough that it would take me a long time to get up to speed.

Are there any reasonably good ways to work around this missing feature? It turns out that we need it fairly badly in our Rust production app. I can obviously fall back to postgres and send raw SQL if all else fails. The feature we really need is ON CONFLICT DO NOTHING on a vector of records (specifically the second line of your example), which might make this slightly easier to work around.

Any thoughts on the best way to tackle this?

(I'm currently working on integration tests that prove we can mix serde and uuid and diesel without any problem, for the other issue I opened.)

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 8, 2017

Depending on how important actual atomicity is, the easiest workaround would just be to do a SELECT query on the fields that could conflict, filter the vector, and then an update. You could also write a stored procedure and use sql_function!. I am going to do on_conflict_do_nothing before the rest though, I might be able to finish just that piece of it soon.

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 8, 2017

Yes, I agree. The hard part is mostly just making sure that incorrect uses fail to compile.

@seamusabshere

This comment has been minimized.

seamusabshere commented Feb 13, 2017

hey @sgrif please do push this out to us if you have other priorities

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 13, 2017

The plan is to have ON CONFLICT DO NOTHING as part of 0.11.

sgrif added a commit that referenced this issue Feb 15, 2017

Add support for PG `ON CONFLICT DO NOTHING`
This is the first piece of #196, and somewhat lays the groundwork for
how the rest will be implemented. Getting the examples to compile and
pass was actually quite easy. The hard part was figuring out how to get
the nested cases to fail to compile.

Rather than completely upend our `InsertStatement` structure, I've
instead opted to add a marker trait which we implement in
`#[derive(Insertable)]`. It's a bit of a kludge, but it gets the job
done.

sgrif added a commit that referenced this issue Feb 15, 2017

Add support for PG `ON CONFLICT DO NOTHING`
This is the first piece of #196, and somewhat lays the groundwork for
how the rest will be implemented. Getting the examples to compile and
pass was actually quite easy. The hard part was figuring out how to get
the nested cases to fail to compile.

Rather than completely upend our `InsertStatement` structure, I've
instead opted to add a marker trait which we implement in
`#[derive(Insertable)]`. It's a bit of a kludge, but it gets the job
done.
@sgrif

This comment has been minimized.

Member

sgrif commented Feb 15, 2017

Removing from the milestone since ON CONFLICT DO NOTHING is all we are planning on doing for upsert in 0.11. The rest will probably be in 0.12. ON CONFLICT DO NOTHING is implemented by #712

@sgrif sgrif removed this from the 0.11 milestone Feb 15, 2017

sgrif added a commit that referenced this issue Feb 16, 2017

Add support for PG `ON CONFLICT DO NOTHING`
This is the first piece of #196, and somewhat lays the groundwork for
how the rest will be implemented. Getting the examples to compile and
pass was actually quite easy. The hard part was figuring out how to get
the nested cases to fail to compile.

Rather than completely upend our `InsertStatement` structure, I've
instead opted to add a marker trait which we implement in
`#[derive(Insertable)]`. It's a bit of a kludge, but it gets the job
done.
@emk

This comment has been minimized.

Contributor

emk commented Feb 17, 2017

It's great to have this!

I accidentally omitted use diesel::pg::upsert::OnConflictExtension; from my file, and Rust printed:

error: no method named `on_conflict_do_nothing` found for type `models::score::Score` in the current scope
   --> src/scoring.rs:303:43
    |
303 |                     diesel::insert(&score.on_conflict_do_nothing())
    |                                           ^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: items from traits can only be used if the trait is in scope; the following trait is implemented but not in scope, perhaps add a `use` for it:
    = help: candidate #1: `use diesel::pg::upsert::OnConflictExtension;`

But this was then followed by dozens of lines of errors like the following:

    = note: required because of the requirements on the impl of `diesel::query_builder::insert_statement::UndecoratedInsertRecord<models::score::scores::table>` for `&std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<std::vec::Vec<_>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`

I'm not sure what's going on with that error message, but in addition to spamming my error output console, it looks a bit suspicious. We do have huge-tables enabled, but I can't figure out why that would result in very deeply nested vectors. So this might really be a separate issue, but I don't understand enough diesel internals to diagnose it.

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 17, 2017

It's a Rust bug, not a Diesel one. It sometimes does weird recursive trait lookups when the type at the bottom of it is a type error.

@emk

This comment has been minimized.

Contributor

emk commented Feb 17, 2017

Ah, thank you! Good to know. Is there an upstream bug for that which I can watch?

I've fixed that, but now I'm hitting more weird errors. The following code compiles fine:

                for score in new_scores {
                    diesel::insert(&score.on_conflict_do_nothing())
                        .into(scores::table)
                        .execute(&*dconn)?;
                }

But if I change it to:

                for score_chunk in new_scores.chunks(5000) {
                    diesel::insert(&score_chunk.on_conflict_do_nothing())
                        .into(scores::table)
                        .execute(&*dconn)?;
                }

...I get:

error: no method named `execute` found for type `diesel::query_builder::insert_statement::InsertStatement<models::score::scores::table, &diesel::pg::upsert::on_conflict_clause::OnConflictDoNothing<&&[models::score::Score]>>` in the current scope
   --> src/scoring.rs:306:26
    |
306 |                         .execute(&*dconn)?;
    |                          ^^^^^^^
    |
    = note: the method `execute` exists but the following trait bounds were not satisfied: `diesel::query_builder::insert_statement::InsertStatement<models::score::scores::table, &diesel::pg::upsert::on_conflict_clause::OnConflictDoNothing<&&[models::score::Score]>> : diesel::query_builder::QueryFragment<_>`, `&diesel::query_builder::insert_statement::InsertStatement<models::score::scores::table, &diesel::pg::upsert::on_conflict_clause::OnConflictDoNothing<&&[models::score::Score]>> : diesel::query_builder::QueryFragment<_>`, `&mut diesel::query_builder::insert_statement::InsertStatement<models::score::scores::table, &diesel::pg::upsert::on_conflict_clause::OnConflictDoNothing<&&[models::score::Score]>> : diesel::query_builder::QueryFragment<_>`, `&mut diesel::query_builder::insert_statement::InsertStatement<models::score::scores::table, &diesel::pg::upsert::on_conflict_clause::OnConflictDoNothing<&&[models::score::Score]>> : diesel::query_builder::QueryId`

I've tried several variations of the basic syntax, but it generally results in errors before reaching execute.

(I'm inserting rows in batches of 5,000 to avoid generating SQL INSERT INTO statements with more than 65,535 ? placeholders. This was necessary in earlier versions of diesel but I haven't checked the latest.)

@emk

This comment has been minimized.

Contributor

emk commented Feb 17, 2017

Here's what happens:

error[E0308]: mismatched types
   --> src/scoring.rs:304:36
    |
304 |                     diesel::insert(score_chunk.on_conflict_do_nothing())
    |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected reference, found struct `diesel::pg::upsert::on_conflict_clause::OnConflictDoNothing`
    |
    = note: expected type `&_`
    = note:    found type `diesel::pg::upsert::on_conflict_clause::OnConflictDoNothing<&&[models::score::Score]>`

The &&[..] is coming from on_conflict_do_nothing, maybe?

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 17, 2017

Yeah, maybe give &(*score_chunk).on_conflict_do_nothing() a try or even &OnConflictExtension::on_conflict_do_nothing(score_chunk). I'll add an explicit test for when the type is &[T] and not Vec<T>

@emk

This comment has been minimized.

Contributor

emk commented Feb 17, 2017

&(*score_chunk).on_conflict_do_nothing() produces:

error: no method named `on_conflict_do_nothing` found for type `[models::score::Score]` in the current scope
   --> src/scoring.rs:304:52
    |
304 |                     diesel::insert(&(*score_chunk).on_conflict_do_nothing())
    |                                                    ^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: the method `on_conflict_do_nothing` exists but the following trait bounds were not satisfied: `[models::score::Score] : std::marker::Sized`

The second produces:

error[E0277]: the trait bound `[models::score::Score]: std::marker::Sized` is not satisfied
   --> src/scoring.rs:305:37
    |
305 |                     diesel::insert(&OnConflictExtension::on_conflict_do_nothing(score_chunk))
    |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `[models::score::Score]`
    |
    = note: `[models::score::Score]` does not have a constant size known at compile-time
    = note: required because of the requirements on the impl of `diesel::pg::upsert::OnConflictExtension` for `[models::score::Score]`
    = note: required by `diesel::pg::upsert::OnConflictExtension::on_conflict_do_nothing`

There's probably a magic permutation which will work, but I haven't found it yet.

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 17, 2017

Nope, I need to add a : ?Sized constraint on the trait I think.

@emk

This comment has been minimized.

Contributor

emk commented Feb 17, 2017

OK, cool. :-) Thank you for your help figuring this out!

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 17, 2017

I'll release a 0.11.1 with #723 once it's green.

@sgrif sgrif added this to the 0.12 milestone Feb 17, 2017

sgrif added a commit that referenced this issue Feb 23, 2017

Add support for `ON CONFLICT conflict_target DO NOTHING`
This adds support for the next third of #196, adding the ability to
specify the conflict target. This does not yet add support for `DO
UPDATE`, that will come in a later PR. This does not support all
possible conflict targets. They're described in the pg syntax as

```
one of:
    ( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ]
    ON CONSTRAINT constraint_name
```

The grammar supported by this PR is:

```
one of:
    ( index_column_name [, ...] )
    ON CONSTRAINT constraint_name
```

Supporting expression indexes is a bit more work, since column names
have to appear without the table name here. We'd either need to add a
new trait which is implemented for everything in the query builder, or
modify `QueryFragment#to_sql` to have an additional parameter of whether
to include the table name or not.

It's something I want to support eventually, but it's low priority (and
may even have to wait until 2.0). I also need to verify whether bind
parameters can appear in expression position here or not. If the answer
is no, we'll just need to provide a raw SQL API for expression indexes
no matter what.

sgrif added a commit that referenced this issue Feb 25, 2017

Add support for `ON CONFLICT conflict_target DO NOTHING`
This adds support for the next third of #196, adding the ability to
specify the conflict target. This does not yet add support for `DO
UPDATE`, that will come in a later PR. This does not support all
possible conflict targets. They're described in the pg syntax as

```
one of:
    ( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ]
    ON CONSTRAINT constraint_name
```

The grammar supported by this PR is:

```
one of:
    ( index_column_name [, ...] )
    ON CONSTRAINT constraint_name
```

Supporting expression indexes is a bit more work, since column names
have to appear without the table name here. We'd either need to add a
new trait which is implemented for everything in the query builder, or
modify `QueryFragment#to_sql` to have an additional parameter of whether
to include the table name or not.

It's something I want to support eventually, but it's low priority (and
may even have to wait until 2.0). I also need to verify whether bind
parameters can appear in expression position here or not. If the answer
is no, we'll just need to provide a raw SQL API for expression indexes
no matter what.

sgrif added a commit that referenced this issue Mar 11, 2017

Add support for `ON CONFLICT DO UPDATE`
With this commit, we support the majority of PG's upsert. The things
that are still missing are things I want to support at some point, but
they're lower priority. The things that are known to be missing are:

- Support for expression indexes
    - We need a way for the entire ast to know not to qualify column
      names in that one position
- Where clauses on the `DO UPDATE` clause
    - Should actually be easy to do, just not high priority
- Support for using `excluded.column` in arbitrary expressions
    - This probably already works, just needs tests. Additional work is
      needed to make things like the `+` operator work though.

`ON CONFLICT (target) DO UPDATE SET changes` is done as:
`insert(&record.on_conflict(target, do_update().set(changes)))`. Changes
can be anything that works with normal update statements. You can
reference the value that was proposed for insertion as
`excluded(column)` (this is important for multi-row insertions).

There's one notable type safety hole which is that `excluded` can be
passed to normal update statements and compile successfully. We should
fix this at some point, but it's not worth blocking this feature for,
as you'd only ever really run into it by explicitly trying to break
things.

Fixes #196.

sgrif added a commit that referenced this issue Mar 11, 2017

Add support for `ON CONFLICT DO UPDATE`
With this commit, we support the majority of PG's upsert. The things
that are still missing are things I want to support at some point, but
they're lower priority. The things that are known to be missing are:

- Support for expression indexes
    - We need a way for the entire ast to know not to qualify column
      names in that one position
- Where clauses on the `DO UPDATE` clause
    - Should actually be easy to do, just not high priority
- Support for using `excluded.column` in arbitrary expressions
    - This probably already works, just needs tests. Additional work is
      needed to make things like the `+` operator work though.

`ON CONFLICT (target) DO UPDATE SET changes` is done as:
`insert(&record.on_conflict(target, do_update().set(changes)))`. Changes
can be anything that works with normal update statements. You can
reference the value that was proposed for insertion as
`excluded(column)` (this is important for multi-row insertions).

There's one notable type safety hole which is that `excluded` can be
passed to normal update statements and compile successfully. We should
fix this at some point, but it's not worth blocking this feature for,
as you'd only ever really run into it by explicitly trying to break
things.

Fixes #196.

sgrif added a commit that referenced this issue Mar 11, 2017

Add support for `ON CONFLICT DO UPDATE`
With this commit, we support the majority of PG's upsert. The things
that are still missing are things I want to support at some point, but
they're lower priority. The things that are known to be missing are:

- Support for expression indexes
    - We need a way for the entire ast to know not to qualify column
      names in that one position
- Where clauses on the `DO UPDATE` clause
    - Should actually be easy to do, just not high priority
- Support for using `excluded.column` in arbitrary expressions
    - This probably already works, just needs tests. Additional work is
      needed to make things like the `+` operator work though.

`ON CONFLICT (target) DO UPDATE SET changes` is done as:
`insert(&record.on_conflict(target, do_update().set(changes)))`. Changes
can be anything that works with normal update statements. You can
reference the value that was proposed for insertion as
`excluded(column)` (this is important for multi-row insertions).

There's one notable type safety hole which is that `excluded` can be
passed to normal update statements and compile successfully. We should
fix this at some point, but it's not worth blocking this feature for,
as you'd only ever really run into it by explicitly trying to break
things.

Fixes #196.
@sgrif

This comment has been minimized.

Member

sgrif commented Mar 11, 2017

#793 implements this with two main exceptions:

  • ON CONFLICT DO UPDATE with expression indices
  • ON CONFLICT DO UPDATE with a where clause on the update

These are both things that we will eventually support, but at the moment they are not prioritized. If you need one of those two features, please let me know.

sgrif added a commit that referenced this issue Mar 12, 2017

Add support for `ON CONFLICT DO UPDATE`
With this commit, we support the majority of PG's upsert. The things
that are still missing are things I want to support at some point, but
they're lower priority. The things that are known to be missing are:

- Support for expression indexes
    - We need a way for the entire ast to know not to qualify column
      names in that one position
- Where clauses on the `DO UPDATE` clause
    - Should actually be easy to do, just not high priority
- Support for using `excluded.column` in arbitrary expressions
    - This probably already works, just needs tests. Additional work is
      needed to make things like the `+` operator work though.

`ON CONFLICT (target) DO UPDATE SET changes` is done as:
`insert(&record.on_conflict(target, do_update().set(changes)))`. Changes
can be anything that works with normal update statements. You can
reference the value that was proposed for insertion as
`excluded(column)` (this is important for multi-row insertions).

There's one notable type safety hole which is that `excluded` can be
passed to normal update statements and compile successfully. We should
fix this at some point, but it's not worth blocking this feature for,
as you'd only ever really run into it by explicitly trying to break
things.

Fixes #196.

@sgrif sgrif closed this in #793 Mar 14, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment