Add support for unique constraints (PostgreSQL-only).#46192
Add support for unique constraints (PostgreSQL-only).#46192yahonda merged 1 commit intorails:mainfrom
Conversation
a4178d8 to
335e84a
Compare
|
This is something I could definitely use. Ran into this exact issue (needing a deferred unique constraint) implementing a drag-and-drop feature with https://github.com/brendon/acts_as_list. Thank you! |
|
If the Rails team doesn't want to add the new methods, another possible syntax we could use is: add_index :items, [:position], unique: trueadd_index :items, [:position], unique: { deferrable: true }add_index :items, [:position], unique: { deferrable: :deferred } |
335e84a to
c306c83
Compare
|
Thanks for the information. Let me confirm that brendon/acts_as_list#378 is the issue you are referring to? |
|
The question above is for @sergiopantoja |
@sergiopantoja The syntax of The |
|
Let me leave some comments.
How about
As far as I understand, PostgreSQL allows users to create a unique constraints on top of the unique index In this case, even if users creates deferrable unique constraint, the existing unique index does not allow users |
|
@yahonda Thank you for reviewing.
Okay, I'm going to rename those methods.
I think the
👍 |
24cf203 to
7cfe0e8
Compare
|
@yahonda I fixed some based on your reviewing 😄
|
7cfe0e8 to
9b53af4
Compare
9b53af4 to
0f33ec4
Compare
0f33ec4 to
de8fbf0
Compare
|
I have one question.
Let me confirm this is correct. Adding
add_unique_key :users, [:name], deferrable: true
ALTER TABLE "users" ADD CONSTRAINT uniq_rails_4a6f7b0ccc UNIQUE ("name") DEFERRABLEHere is my example using
CREATE TABLE example(
row integer NOT NULL,
col integer NOT NULL,
UNIQUE (row, col)
);
begin;
INSERT INTO example (row, col) VALUES (1,1),(2,2),(3,3);
commit;
SELECT * FROM example;
begin;
UPDATE example SET row = row + 1, col = col + 1;
# This statement raises `ERROR: duplicate key value violates unique constraint "example_row_col_key"
# DETAIL: Key ("row", col)=(2, 2) already exists.` error
commit;
SELECT * FROM example;
CREATE TABLE
BEGIN
INSERT 0 3
COMMIT
row | col
-----+-----
1 | 1
2 | 2
3 | 3
(3 rows)
BEGIN
ERROR: duplicate key value violates unique constraint "example_row_col_key"
DETAIL: Key ("row", col)=(2, 2) already exists.
ROLLBACK
row | col
-----+-----
1 | 1
2 | 2
3 | 3
(3 rows)
test46192=#
CREATE TABLE example(
row integer NOT NULL,
col integer NOT NULL,
UNIQUE (row, col) DEFERRABLE
);
begin;
INSERT INTO example (row, col) VALUES (1,1),(2,2),(3,3);
commit;
SELECT * FROM example;
begin;
UPDATE example SET row = row + 1, col = col + 1; # This statement should update the rows
commit;
SELECT * FROM example;
CREATE TABLE
BEGIN
INSERT 0 3
COMMIT
row | col
-----+-----
1 | 1
2 | 2
3 | 3
(3 rows)
BEGIN
UPDATE 3
COMMIT
row | col
-----+-----
2 | 2
3 | 3
4 | 4
(3 rows)
test46192=# |
|
My previous question is more like confirming PostgreSQL behavior, so let me answer the question.
Now I understand this API design comes from |
I didn't know there was a no conflict with bulk update without specifying
I like the current Hmmm... |
de8fbf0 to
e16ba22
Compare
If just setting Reading PostgreSQL document however, I'm not clear what is an expected behavior only when
|
Investigated how
Here are my test details. https://gist.github.com/yahonda/1adcf1a776a385b8ec47c9cdf5dcf08b |
|
Based on how PostgreSQL works, investigated at #46192 (comment) I'd like to propose that
If |
bd90559 to
12a2721
Compare
|
@yahonda Thank you for your detailed investigation 🙏 I have updated some fixes and rebased 😄 The changes are as follows
|
12a2721 to
4293b88
Compare
`deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2. Because `deferrable: true` and `deferrable: :deferred` are hard to understand. Both true and :deferred are truthy values. This behavior is the same as the deferrable option of the add_unique_key method, added in rails#46192. *Hiroyuki Ishii*
`deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2. Because `deferrable: true` and `deferrable: :deferred` are hard to understand. Both true and :deferred are truthy values. This behavior is the same as the deferrable option of the add_unique_key method, added in rails#46192. *Hiroyuki Ishii*
`deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2. Because `deferrable: true` and `deferrable: :deferred` are hard to understand. Both true and :deferred are truthy values. This behavior is the same as the deferrable option of the add_unique_key method, added in rails#46192. *Hiroyuki Ishii*
`deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2. Because `deferrable: true` and `deferrable: :deferred` are hard to understand. Both true and :deferred are truthy values. This behavior is the same as the deferrable option of the add_unique_key method, added in rails#46192. *Hiroyuki Ishii*
`deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2. Because `deferrable: true` and `deferrable: :deferred` are hard to understand. Both true and :deferred are truthy values. This behavior is the same as the deferrable option of the add_unique_key method, added in rails#46192. *Hiroyuki Ishii*
follow-up rails#46192 Fixed a bug where `unique_keys` returned the old column name after the column specified in add_unique_key was renamed. Since `pg_attribute.attname` may return the old column name after renaming a column, match `attrelid, attnum` in the process of getting the list of column names.
|
Update to anyone landing here from Google or this post, this was renamed back to add_unique_constraint ( |
|
@searls FYI : here is the changelog link locked with SHA by pressing "Y" on the page. This link will work "forever" |
Motivation / Background
Add support for unique constraints (PostgreSQL-only).
See PostgreSQL's Unique Constraints documentation for more on unique constraints.
By default, unique constraints in PostgreSQL are checked after each statement.
This works for most use cases, but becomes a major limitation when replacing
records with unique column by using multiple statements.
An example of swapping unique columns between records.
Using the default behavior, the transaction would fail when executing the
first
UPDATEstatement.By passing the
:deferrableoption to theadd_unique_keystatement inmigrations, it's possible to defer this check.
Passing
deferrable: :immediatedoes not change the behaviour of the previous example,but allows manually deferring the check using
SET CONSTRAINTS ALL DEFERREDwithin a transaction.This will cause the unique constraints to be checked after the transaction.
It's also possible to adjust the default behavior from an immediate
check (after the statement), to a deferred check (after the transaction):
PostgreSQL allows users to create a unique constraints on top of the unique
index that cannot be deferred. In this case, even if users creates deferrable
unique constraint, the existing unique index does not allow users to violate uniqueness
within the transaction. If you want to change existing unique index to deferrable,
you need execute
remove_indexbefore creating deferrable unique constraints.Detail
Similar to #40224, this extends Active Record's migration/schema dumping to support Unique Constraints.
Additional information
The unique constraint is supported by most DB, but its function is almost equivalent to unique index.
Perhaps only PostgreSQL and Oracle Database with deferrable features would benefit from supporting unique constraint syntax.
Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]main(if not - rebase it).