Skip to content

Commit c92adab

Browse files
authored
Merge pull request #50 from kraken-tech/disallow-deferrable-unique-constraint
Disallow deferrable unique constraints
2 parents 6cdde42 + f7314da commit c92adab

File tree

6 files changed

+55
-38
lines changed

6 files changed

+55
-38
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ The following types of tables aren't currently supported:
122122
- `smallserial`
123123
- Tables with triggers.
124124
- Tables with invalid indexes (the user should drop or re-index them first).
125+
- Tables with deferrable unique constraints.
125126
- Referring foreign keys on a different schema than the original table.
126127

127128
## Required user permissions (or privileges)

src/psycopack/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from ._repack import (
99
BasePsycopackError,
1010
CompositePrimaryKey,
11+
DeferrableUniqueConstraint,
1112
InheritedTable,
1213
InvalidIndexes,
1314
InvalidPrimaryKeyTypeForConversion,
@@ -32,6 +33,7 @@
3233
"BackfillBatch",
3334
"BasePsycopackError",
3435
"CompositePrimaryKey",
36+
"DeferrableUniqueConstraint",
3537
"FailureDueToLockTimeout",
3638
"InheritedTable",
3739
"InvalidIndexes",

src/psycopack/_commands.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -295,23 +295,12 @@ def create_unique_constraint_using_idx(
295295
table: str,
296296
constraint: str,
297297
index: str,
298-
is_deferrable: bool,
299-
is_deferred: bool,
300298
) -> None:
301299
add_constraint_sql = dedent("""
302300
ALTER TABLE {schema}.{table}
303301
ADD CONSTRAINT {constraint}
304-
UNIQUE USING INDEX {index}
302+
UNIQUE USING INDEX {index} NOT DEFERRABLE
305303
""")
306-
if is_deferrable:
307-
add_constraint_sql += " DEFERRABLE"
308-
else:
309-
add_constraint_sql += " NOT DEFERRABLE"
310-
311-
if is_deferred:
312-
add_constraint_sql += " INITIALLY DEFERRED"
313-
else:
314-
add_constraint_sql += " INITIALLY IMMEDIATE"
315304

316305
self.cur.execute(
317306
psycopg.sql.SQL(add_constraint_sql)

src/psycopack/_repack.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ class ReferringForeignKeyInDifferentSchema(BasePsycopackError):
5959
pass
6060

6161

62+
class DeferrableUniqueConstraint(BasePsycopackError):
63+
pass
64+
65+
6266
class NoCreateAndUsagePrivilegeOnSchema(BasePsycopackError):
6367
pass
6468

@@ -333,6 +337,21 @@ def pre_validate(self) -> None:
333337
f"schema: {fks_in_different_schema}."
334338
)
335339

340+
deferrable_unique_constraints = [
341+
c.name
342+
for c in self.introspector.get_constraints(
343+
table=self.table, types=["u"]
344+
)
345+
if c.is_deferrable
346+
]
347+
if deferrable_unique_constraints:
348+
raise DeferrableUniqueConstraint(
349+
f"Psycopack does not currently support tables with "
350+
f"deferrable unique constraints. The table {self.table} "
351+
f"has the following deferrable unique constraints: "
352+
f"{deferrable_unique_constraints}."
353+
)
354+
336355
def setup_repacking(self) -> None:
337356
with (
338357
self.tracker.track(_tracker.Stage.SETUP),
@@ -758,8 +777,6 @@ def _create_unique_constraints(self) -> None:
758777
# From previous steps, the index name is the same as the
759778
# constraint, not a typo!
760779
index=constraint_name,
761-
is_deferrable=cons.is_deferrable,
762-
is_deferred=cons.is_deferred,
763780
)
764781

765782
def _create_check_and_fk_constraints(self) -> None:

tests/factories.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ def create_table_for_repacking(
6262
int_with_long_index_name INTEGER,
6363
var_with_unique_idx VARCHAR(10),
6464
var_with_unique_const VARCHAR(10) UNIQUE,
65-
var_with_deferrable_const VARCHAR(10),
66-
var_with_deferred_const VARCHAR(10),
6765
valid_fk INTEGER REFERENCES {schema}.referred_table(id),
6866
not_valid_fk INTEGER,
6967
{table_name} INTEGER,
@@ -115,20 +113,6 @@ def create_table_for_repacking(
115113
CHECK (int_with_not_valid_check >= 0) NOT VALID;
116114
""")
117115
)
118-
cur.execute(
119-
dedent(f"""
120-
ALTER TABLE {schema}.{table_name} ADD CONSTRAINT non_deferrable_const
121-
UNIQUE (var_with_deferrable_const)
122-
DEFERRABLE;
123-
""")
124-
)
125-
cur.execute(
126-
dedent(f"""
127-
ALTER TABLE {schema}.{table_name} ADD CONSTRAINT deferred_const
128-
UNIQUE (var_with_deferred_const)
129-
DEFERRABLE INITIALLY DEFERRED;
130-
""")
131-
)
132116
# Constraint for a column that has the same name as the table.
133117
cur.execute(
134118
dedent(f"""
@@ -156,8 +140,6 @@ def create_table_for_repacking(
156140
int_with_long_index_name,
157141
var_with_unique_idx,
158142
var_with_unique_const,
159-
var_with_deferrable_const,
160-
var_with_deferred_const,
161143
valid_fk,
162144
not_valid_fk,
163145
{table_name},
@@ -173,8 +155,6 @@ def create_table_for_repacking(
173155
(floor(random() * 10) + 1)::int,
174156
substring(md5(random()::text), 1, 10),
175157
substring(md5(random()::text), 1, 10),
176-
substring(md5(random()::text), 1, 10),
177-
substring(md5(random()::text), 1, 10),
178158
(floor(random() * {referred_table_rows}) + 1)::int,
179159
(floor(random() * {referred_table_rows}) + 1)::int,
180160
(floor(random() * 10) + 1)::int,

tests/test_repack.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from psycopack import (
88
BackfillBatch,
99
CompositePrimaryKey,
10+
DeferrableUniqueConstraint,
1011
FailureDueToLockTimeout,
1112
InheritedTable,
1213
InvalidIndexes,
@@ -1356,8 +1357,6 @@ def test_when_table_has_large_value_being_inserted(
13561357
int_with_long_index_name,
13571358
var_with_unique_idx,
13581359
var_with_unique_const,
1359-
var_with_deferrable_const,
1360-
var_with_deferred_const,
13611360
valid_fk,
13621361
not_valid_fk,
13631362
to_repack,
@@ -1372,8 +1371,6 @@ def test_when_table_has_large_value_being_inserted(
13721371
(floor(random() * 10) + 1)::int,
13731372
substring(md5(random()::text), 1, 10),
13741373
substring(md5(random()::text), 1, 10),
1375-
substring(md5(random()::text), 1, 10),
1376-
substring(md5(random()::text), 1, 10),
13771374
(floor(random() * 10) + 1)::int,
13781375
(floor(random() * 10) + 1)::int,
13791376
(floor(random() * 10) + 1)::int,
@@ -1756,6 +1753,37 @@ def test_with_fks_from_another_schema(connection: _psycopg.Connection) -> None:
17561753
repack.full()
17571754

17581755

1756+
def test_with_deferrable_unique_constraint(connection: _psycopg.Connection) -> None:
1757+
with _cur.get_cursor(connection, logged=True) as cur:
1758+
factories.create_table_for_repacking(
1759+
connection=connection,
1760+
cur=cur,
1761+
table_name="to_repack",
1762+
rows=100,
1763+
)
1764+
repack = Psycopack(
1765+
table="to_repack",
1766+
batch_size=1,
1767+
conn=connection,
1768+
cur=cur,
1769+
)
1770+
cur.execute(
1771+
"ALTER TABLE to_repack DROP CONSTRAINT to_repack_var_with_unique_const_key;"
1772+
)
1773+
cur.execute(
1774+
dedent("""
1775+
ALTER TABLE to_repack ADD CONSTRAINT to_repack_var_with_unique_const_key
1776+
UNIQUE (var_with_unique_const)
1777+
DEFERRABLE;
1778+
""")
1779+
)
1780+
1781+
with pytest.raises(
1782+
DeferrableUniqueConstraint, match="to_repack_var_with_unique_const_key"
1783+
):
1784+
repack.full()
1785+
1786+
17591787
def test_without_schema_privileges(connection: _psycopg.Connection) -> None:
17601788
schema = "sweet_schema"
17611789
with _cur.get_cursor(connection, logged=True) as cur:

0 commit comments

Comments
 (0)