-
Notifications
You must be signed in to change notification settings - Fork 21.6k
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 support for unique constraints (PostgreSQL-only). #46192
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: true add_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") DEFERRABLE Here 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 https://www.postgresql.org/docs/15/sql-set-constraints.html
|
Investigated how https://www.postgresql.org/docs/9.3/sql-createtable.html
https://www.postgresql.org/docs/15/sql-createtable.html
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
UPDATE
statement.By passing the
:deferrable
option to theadd_unique_key
statement inmigrations, it's possible to defer this check.
Passing
deferrable: :immediate
does not change the behaviour of the previous example,but allows manually deferring the check using
SET CONSTRAINTS ALL DEFERRED
within 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_index
before 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).