Skip to content

Commit be90677

Browse files
committed
the hacky ReassignBackupIdAction
1 parent 1b4f681 commit be90677

4 files changed

Lines changed: 83 additions & 2 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import dataclasses
2+
from typing import Optional
3+
4+
from typing_extensions import override
5+
6+
from prime_backup.action import Action
7+
from prime_backup.db.access import DbAccess
8+
from prime_backup.types.backup_filter import BackupSortOrder
9+
10+
11+
@dataclasses.dataclass(frozen=True)
12+
class ReassignBackupIdResult:
13+
max_id: Optional[int]
14+
15+
16+
class ReassignBackupIdAction(Action[ReassignBackupIdResult]):
17+
def __init__(self, order: BackupSortOrder = BackupSortOrder.id):
18+
super().__init__()
19+
self.order = order
20+
21+
@override
22+
def run(self) -> ReassignBackupIdResult:
23+
with DbAccess.open_session() as session:
24+
max_id = session.reassign_backup_id(self.order)
25+
self.logger.info('Backup ID reassigned, current max Backup.id: {}'.format(max_id))
26+
return ReassignBackupIdResult(max_id)

prime_backup/db/session.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Optional, Sequence, Dict, Iterator, Callable, Set, Generator, Iterable, Tuple, Any
88
from typing import TypeVar, List
99

10-
from sqlalchemy import select, delete, desc, func, Select, JSON, text, or_, not_, and_, exists, Row
10+
from sqlalchemy import select, delete, desc, func, Select, JSON, text, or_, not_, and_, exists, Row, update
1111
from sqlalchemy.orm import Session
1212
from typing_extensions import overload, Union, TypedDict, Unpack, NotRequired
1313

@@ -74,6 +74,11 @@ def __supports_json_query(cls) -> bool:
7474
def __supports_vacuum_into(cls) -> bool:
7575
return cls.__check_support(db_utils.check_sqlite_vacuum_into_support, 'SQLite backend does not support VACUUM INTO statement. Insecure manual file copy is used as the fallback')
7676

77+
@classmethod
78+
@functools.lru_cache
79+
def __supports_row_number(cls) -> bool:
80+
return cls.__check_support(db_utils.check_sqlite_row_number, 'SQLite backend does not support ROW_NUMBER() statement, ID reassignment is not available')
81+
7782
@classmethod
7883
def __validate_int_fields_range(cls, obj: schema.Base):
7984
from sqlalchemy import Integer, BigInteger
@@ -1588,3 +1593,44 @@ def iterate_backup_batch(self, *, batch_size: int = 1000) -> Iterator[List[schem
15881593

15891594
def delete_backup(self, backup: schema.Backup):
15901595
self.session.delete(backup)
1596+
1597+
def reassign_backup_id(self, order: BackupSortOrder) -> Optional[int]:
1598+
if not self.__supports_row_number():
1599+
raise RuntimeError('Current SQLite version {} does not support ROW_NUMBER() function'.format(db_utils.get_sqlite_version()))
1600+
1601+
order_by: list
1602+
if order == BackupSortOrder.time:
1603+
order_by = [schema.Backup.timestamp, schema.Backup.timestamp_ns_part, schema.Backup.id]
1604+
elif order == BackupSortOrder.time_r:
1605+
order_by = [desc(schema.Backup.timestamp), desc(schema.Backup.timestamp_ns_part), desc(schema.Backup.id)]
1606+
elif order == BackupSortOrder.id:
1607+
order_by = [schema.Backup.id]
1608+
elif order == BackupSortOrder.id_r:
1609+
order_by = [desc(schema.Backup.id)]
1610+
else:
1611+
raise ValueError(order)
1612+
1613+
numbered_cte = select(
1614+
schema.Backup.id,
1615+
func.row_number().over(order_by=order_by).label('new_id')
1616+
).cte('numbered')
1617+
numbered_alias = numbered_cte.alias()
1618+
self.session.execute(
1619+
update(schema.Backup).
1620+
values(id=numbered_alias.c.new_id).
1621+
where(schema.Backup.id == numbered_alias.c.id)
1622+
)
1623+
1624+
max_id: Optional[int] = self.session.execute(select(func.max(schema.Backup.id))).scalar()
1625+
table_name = schema.Backup.__tablename__
1626+
if max_id is not None:
1627+
self.session.execute(
1628+
text(f'''UPDATE sqlite_sequence SET seq = :seq WHERE name = :name''').
1629+
bindparams(seq=max_id, name=table_name)
1630+
)
1631+
else:
1632+
self.session.execute(
1633+
text(f'''DELETE FROM sqlite_sequence WHERE name = :name''').
1634+
bindparams(name=table_name)
1635+
)
1636+
return max_id

prime_backup/utils/db_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import sqlite3
22

33

4+
def get_sqlite_version() -> str:
5+
return sqlite3.sqlite_version
6+
7+
48
def check_sqlite_json_query_support() -> bool:
59
try:
610
with sqlite3.connect(':memory:') as conn:
@@ -18,7 +22,12 @@ def check_sqlite_vacuum_into_support() -> bool:
1822
return sqlite3.sqlite_version_info >= (3, 27, 0)
1923

2024

25+
def check_sqlite_row_number() -> bool:
26+
return sqlite3.sqlite_version_info >= (3, 25, 0)
27+
28+
2129
if __name__ == '__main__':
2230
print('version:', sqlite3.sqlite_version)
2331
print('json query:', check_sqlite_json_query_support())
2432
print('vacuum into:', check_sqlite_vacuum_into_support())
33+
print('row number:', check_sqlite_row_number())

prime_backup/utils/file_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def __get_copier():
9292

9393

9494
def copy_file_obj_fast(src: SupportsReadBytes, dst: SupportsWriteBytes, *, estimate_read_size: int = 0):
95-
if estimate_read_size > 1048576 and False: # TODO
95+
if estimate_read_size > 1048576 and True: # TODO
9696
__get_copier().copy(src, dst)
9797
else:
9898
shutil.copyfileobj(src, dst)

0 commit comments

Comments
 (0)