|
1 | 1 | import dataclasses |
2 | 2 | from textwrap import dedent |
| 3 | +from typing import Tuple, Union |
3 | 4 | from unittest import mock |
4 | 5 |
|
5 | 6 | import pytest |
@@ -137,7 +138,7 @@ def _assert_repack( |
137 | 138 | if table_before.pk_seq_val is None or table_before.pk_seq_val > 0: |
138 | 139 | assert table_before.pk_seq_val == table_after.pk_seq_val |
139 | 140 | else: |
140 | | - assert table_after.pk_seq_val == 2**31 |
| 141 | + assert table_after.pk_seq_val is None or table_after.pk_seq_val >= 2**31 |
141 | 142 |
|
142 | 143 | # All functions and triggers are removed. |
143 | 144 | trigger_info = _get_trigger_info(repack, cur) |
@@ -166,6 +167,78 @@ def _assert_reset(repack: Psycopack, cur: _cur.Cursor) -> None: |
166 | 167 | assert repack.introspector.get_table_oid(table=repack.tracker.tracker_table) is None |
167 | 168 |
|
168 | 169 |
|
| 170 | +def _do_writes( |
| 171 | + table: str, |
| 172 | + cur: _cur.Cursor, |
| 173 | + schema: str = "public", |
| 174 | + check_table: str | None = None, |
| 175 | +) -> None: |
| 176 | + """ |
| 177 | + Do some writes (insert, update, delete) to check that the copy function works. |
| 178 | + """ |
| 179 | + cur.execute( |
| 180 | + dedent(f""" |
| 181 | + INSERT INTO {schema}.{table} ( |
| 182 | + var_with_btree, |
| 183 | + var_with_pattern_ops, |
| 184 | + int_with_check, |
| 185 | + int_with_not_valid_check, |
| 186 | + int_with_long_index_name, |
| 187 | + var_with_unique_idx, |
| 188 | + var_with_unique_const, |
| 189 | + valid_fk, |
| 190 | + not_valid_fk, |
| 191 | + {table}, |
| 192 | + var_maybe_with_exclusion, |
| 193 | + var_with_multiple_idx |
| 194 | + ) |
| 195 | + VALUES ( |
| 196 | + substring(md5(random()::text), 1, 10), |
| 197 | + substring(md5(random()::text), 1, 10), |
| 198 | + (floor(random() * 10) + 1)::int, |
| 199 | + (floor(random() * 10) + 1)::int, |
| 200 | + (floor(random() * 10) + 1)::int, |
| 201 | + substring(md5(random()::text), 1, 10), |
| 202 | + substring(md5(random()::text), 1, 10), |
| 203 | + (floor(random() * 10) + 1)::int, |
| 204 | + (floor(random() * 10) + 1)::int, |
| 205 | + (floor(random() * 10) + 1)::int, |
| 206 | + substring(md5(random()::text), 1, 10), |
| 207 | + substring(md5(random()::text), 1, 10) |
| 208 | + ) |
| 209 | + RETURNING id; |
| 210 | + """) |
| 211 | + ) |
| 212 | + result = cur.fetchone() |
| 213 | + assert result is not None |
| 214 | + id_ = result[0] |
| 215 | + if check_table is not None: |
| 216 | + assert _query_row(table=table, id_=id_, cur=cur, schema=schema) == _query_row( |
| 217 | + table=check_table, id_=id_, cur=cur, schema=schema |
| 218 | + ) |
| 219 | + |
| 220 | + cur.execute(f"UPDATE {schema}.{table} SET var_with_btree = 'foo' WHERE id = {id_};") |
| 221 | + if check_table is not None: |
| 222 | + assert _query_row(table=table, id_=id_, cur=cur, schema=schema) == _query_row( |
| 223 | + table=check_table, id_=id_, cur=cur, schema=schema |
| 224 | + ) |
| 225 | + |
| 226 | + cur.execute(f"DELETE FROM {schema}.{table} WHERE id = {id_};") |
| 227 | + assert _query_row(table=table, id_=id_, cur=cur, schema=schema) is None |
| 228 | + if check_table is not None: |
| 229 | + assert _query_row(table=check_table, id_=id_, cur=cur, schema=schema) is None |
| 230 | + |
| 231 | + |
| 232 | +def _query_row( |
| 233 | + table: str, |
| 234 | + id_: int, |
| 235 | + cur: _cur.Cursor, |
| 236 | + schema: str = "public", |
| 237 | +) -> Tuple[Union[int, str], ...] | None: |
| 238 | + cur.execute(f"SELECT * FROM {schema}.{table} WHERE id = {id_};") |
| 239 | + return cur.fetchone() |
| 240 | + |
| 241 | + |
169 | 242 | @pytest.mark.parametrize( |
170 | 243 | "pk_type", |
171 | 244 | ("bigint", "bigserial", "integer", "serial", "smallint", "smallserial"), |
@@ -1324,6 +1397,56 @@ def test_when_table_has_negative_pk_values( |
1324 | 1397 | ) |
1325 | 1398 |
|
1326 | 1399 |
|
| 1400 | +@pytest.mark.parametrize( |
| 1401 | + "initial_pk_type", |
| 1402 | + ( |
| 1403 | + "integer", |
| 1404 | + "serial", |
| 1405 | + "smallint", |
| 1406 | + "smallserial", |
| 1407 | + ), |
| 1408 | +) |
| 1409 | +def test_with_writes_when_table_has_negative_pk_values( |
| 1410 | + connection: _psycopg.Connection, initial_pk_type: str |
| 1411 | +) -> None: |
| 1412 | + with _cur.get_cursor(connection, logged=True) as cur: |
| 1413 | + factories.create_table_for_repacking( |
| 1414 | + connection=connection, |
| 1415 | + cur=cur, |
| 1416 | + table_name="to_repack", |
| 1417 | + rows=100, |
| 1418 | + pk_type=initial_pk_type, |
| 1419 | + pk_start=-200, |
| 1420 | + ) |
| 1421 | + table_before = _collect_table_info(table="to_repack", connection=connection) |
| 1422 | + |
| 1423 | + repack = Psycopack( |
| 1424 | + table="to_repack", |
| 1425 | + batch_size=1, |
| 1426 | + conn=connection, |
| 1427 | + cur=cur, |
| 1428 | + convert_pk_to_bigint=True, |
| 1429 | + ) |
| 1430 | + repack.pre_validate() |
| 1431 | + repack.setup_repacking() |
| 1432 | + repack.backfill() |
| 1433 | + _do_writes(table="to_repack", cur=cur, check_table=repack.copy_table) |
| 1434 | + repack.sync_schemas() |
| 1435 | + _do_writes(table="to_repack", cur=cur, check_table=repack.copy_table) |
| 1436 | + repack.swap() |
| 1437 | + _do_writes(table="to_repack", cur=cur, check_table=repack.repacked_name) |
| 1438 | + repack.clean_up() |
| 1439 | + _do_writes(table="to_repack", cur=cur) |
| 1440 | + |
| 1441 | + table_after = _collect_table_info(table="to_repack", connection=connection) |
| 1442 | + _assert_repack( |
| 1443 | + table_before=table_before, |
| 1444 | + table_after=table_after, |
| 1445 | + repack=repack, |
| 1446 | + cur=cur, |
| 1447 | + ) |
| 1448 | + |
| 1449 | + |
1327 | 1450 | def test_when_table_has_large_value_being_inserted( |
1328 | 1451 | connection: _psycopg.Connection, |
1329 | 1452 | ) -> None: |
|
0 commit comments