|
4 | 4 | import sqlite3 |
5 | 5 | import string |
6 | 6 | from pathlib import Path |
7 | | -from typing import Optional, Sequence, Dict, Iterator, Callable, Set, Generator, Iterable, Tuple, Any, Type, TYPE_CHECKING |
| 7 | +from typing import Optional, Sequence, Dict, Iterator, Set, Generator, Iterable, Tuple, Any, Type, TYPE_CHECKING |
8 | 8 | from typing import TypeVar, List |
9 | 9 |
|
10 | 10 | from sqlalchemy import select, delete, desc, func, Select, JSON, text, or_, not_, and_, exists, Row, update, inspect, ColumnElement, insert |
11 | 11 | from sqlalchemy.orm import Session, Mapper, InstrumentedAttribute |
12 | 12 | from typing_extensions import overload, Union, TypedDict, Unpack, NotRequired |
13 | 13 |
|
14 | 14 | from prime_backup.db import schema, db_constants |
| 15 | +from prime_backup.db.db_features import DbFeatures |
15 | 16 | from prime_backup.db.values import FileRole, BackupTagDict, OffsetChunk, OffsetChunkGroup, BlobStorageMethod, ChunkGroupChunkBindingIdentifier, BlobChunkGroupBindingIdentifier, FileIdentifier |
16 | 17 | from prime_backup.exceptions import BackupNotFound, BackupFileNotFound, BlobHashNotFound, PrimeBackupError, FilesetNotFound, FilesetFileNotFound, BlobIdNotFound, ChunkHashNotFound, ChunkIdNotFound, ChunkGroupChunkBindingNotFound, BlobChunkGroupBindingNotFound, ChunkGroupIdNotFound, ChunkGroupHashNotFound, PackIdNotFound |
17 | 18 | from prime_backup.types.backup_filter import BackupFilter, BackupTagFilter, BackupSortOrder |
@@ -67,34 +68,6 @@ def __init__(self, session: Session, db_path: Optional[Path] = None): |
67 | 68 | # the limit in old sqlite (https://www.sqlite.org/limits.html#max_variable_number) |
68 | 69 | self.__safe_var_limit = 999 - 20 |
69 | 70 |
|
70 | | - @classmethod |
71 | | - def __check_support(cls, check_func: Callable[[], bool], msg: str): |
72 | | - if not (is_supported := check_func()): |
73 | | - from prime_backup import logger |
74 | | - import sqlite3 |
75 | | - logger.get().warning(f'WARN: {msg}. SQLite version: {sqlite3.sqlite_version}') |
76 | | - return is_supported |
77 | | - |
78 | | - @classmethod |
79 | | - @functools.lru_cache |
80 | | - def __supports_json_query(cls) -> bool: |
81 | | - return cls.__check_support(db_utils.check_sqlite_json_query_support, 'SQLite backend does not support json query. Inefficient manual query is used as the fallback') |
82 | | - |
83 | | - @classmethod |
84 | | - @functools.lru_cache |
85 | | - def __supports_vacuum_into(cls) -> bool: |
86 | | - 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') |
87 | | - |
88 | | - @classmethod |
89 | | - @functools.lru_cache |
90 | | - def __supports_row_number(cls) -> bool: |
91 | | - return cls.__check_support(db_utils.check_sqlite_row_number, 'SQLite backend does not support ROW_NUMBER() statement, ID reassignment is not available') |
92 | | - |
93 | | - @classmethod |
94 | | - @functools.lru_cache |
95 | | - def __supports_returning(cls) -> bool: |
96 | | - return db_utils.check_sqlite_returning_support() |
97 | | - |
98 | 71 | @classmethod |
99 | 72 | @functools.lru_cache(None) |
100 | 73 | def __get_schema_column_fields(cls, typ: Type[schema.Base]) -> List[Tuple[str, 'TypeEngine']]: |
@@ -169,7 +142,7 @@ def no_auto_flush(self) -> Generator[None, None, None]: |
169 | 142 | def vacuum(self, into_file: Optional[str] = None, allow_vacuum_into_fallback: bool = True): |
170 | 143 | # https://www.sqlite.org/lang_vacuum.html |
171 | 144 | if into_file is not None: |
172 | | - if self.__supports_vacuum_into(): |
| 145 | + if DbFeatures.supports_vacuum_into(): |
173 | 146 | self.session.execute(text('VACUUM INTO :into_file').bindparams(into_file=str(into_file))) |
174 | 147 | elif allow_vacuum_into_fallback: |
175 | 148 | if self.db_path is None: |
@@ -548,7 +521,7 @@ def insert_chunks(self, rows: List[CreateChunkKwargs]) -> Dict[str, int]: |
548 | 521 |
|
549 | 522 | hashes: List[str] = [row['hash'] for row in rows] |
550 | 523 | hash_to_id: Dict[str, int] = {} |
551 | | - if self.__supports_returning(): |
| 524 | + if DbFeatures.supports_returning(): |
552 | 525 | for row_page in collection_utils.slicing_iterate(rows, self.__safe_var_limit // self.__CHUNK_INSERT_FIELD_COUNT): |
553 | 526 | for chunk_hash, chunk_id in self.session.execute( |
554 | 527 | insert(schema.Chunk). |
@@ -1584,7 +1557,7 @@ def __needs_manual_backup_tag_filter(cls, backup_filter: Optional[BackupFilter]) |
1584 | 1557 | """ |
1585 | 1558 | SQLite does not support json query, and the backup filter contains tag filter |
1586 | 1559 | """ |
1587 | | - return backup_filter is not None and len(backup_filter.tag_filters) > 0 and not cls.__supports_json_query() |
| 1560 | + return backup_filter is not None and len(backup_filter.tag_filters) > 0 and not DbFeatures.supports_json_query() |
1588 | 1561 |
|
1589 | 1562 | @classmethod |
1590 | 1563 | def __manual_backup_tag_filter(cls, backup: schema.Backup, backup_filter: BackupFilter) -> bool: |
@@ -1669,7 +1642,7 @@ def __apply_backup_filter(cls, s: Select[_TP], backup_filter: BackupFilter) -> S |
1669 | 1642 | schema.Backup.timestamp < ts_sec, |
1670 | 1643 | and_(schema.Backup.timestamp == ts_sec, schema.Backup.timestamp_ns_part <= ts_ns_part) |
1671 | 1644 | )) |
1672 | | - if cls.__supports_json_query(): |
| 1645 | + if DbFeatures.supports_json_query(): |
1673 | 1646 | s = cls.__sql_backup_tag_filter(s, backup_filter) |
1674 | 1647 |
|
1675 | 1648 | sort_order = backup_filter.sort_order or BackupSortOrder.time_r |
@@ -1897,7 +1870,7 @@ def delete_backup(self, backup: schema.Backup): |
1897 | 1870 | self.session.delete(backup) |
1898 | 1871 |
|
1899 | 1872 | def reassign_backup_id(self, order: BackupSortOrder) -> Optional[int]: |
1900 | | - if not self.__supports_row_number(): |
| 1873 | + if not DbFeatures.supports_row_number(): |
1901 | 1874 | raise RuntimeError('Current SQLite version {} does not support ROW_NUMBER() function'.format(db_utils.get_sqlite_version())) |
1902 | 1875 |
|
1903 | 1876 | order_by: list |
|
0 commit comments