userver: en/testsuite/databases/clickhouse/control.py Source File
Loading...
Searching...
No Matches
control.py
1import dataclasses
2import logging
3import pathlib
4
5import clickhouse_driver
6
7from . import classes
8
9logger = logging.getLogger(__name__)
10
11
12@dataclasses.dataclass(frozen=True)
14 body: str
15 source: str
16 path: str | None
17
18
20 def __init__(self, conn_info: classes.ConnectionInfo):
21 self._conn_info = conn_info
22 self._cache: dict[str, clickhouse_driver.Client] = {}
23 self._master_connection = None
24
25 def get_master_connection(self):
26 return self.get_connection('default')
27
28 def get_connection(self, dbname: str) -> clickhouse_driver.Client:
29 if dbname not in self._cache:
30 self._cache[dbname] = self._create_connection(dbname)
31 return self._cache[dbname]
32
33 def get_connection_info(self, dbname: str) -> classes.ConnectionInfo:
34 return self._conn_info.replace(dbname=dbname)
35
36 def _create_connection(self, dbname: str):
37 return self._connect(self.get_connection_info(dbname))
38
39 def _connect(self, conn_info: classes.ConnectionInfo):
40 return clickhouse_driver.Client(
41 host=conn_info.host,
42 port=conn_info.tcp_port,
43 database=conn_info.dbname,
44 )
45
46
48 _migrations_run: set[tuple[str, pathlib.Path]]
49 _initialized: set[str]
50
51 def __init__(self, connections: ConnectionCache, verbose: bool = False):
52 self._connections = connections
53 self._verbose = verbose
54 self._migrations_run = set()
55 self._initialized = set()
56
57 def get_connection(
58 self,
59 dbname: str,
60 create_db: bool = True,
61 ) -> clickhouse_driver.Client:
62 if dbname not in self._initialized:
63 if create_db:
64 self._init_db(dbname)
65 self._initialized.add(dbname)
66 return self._connections.get_connection(dbname)
67
68 def run_migration(self, dbname: str, path: pathlib.Path):
69 key = dbname, path
70 if key in self._migrations_run:
71 return
72 logger.debug(
73 'Running clickhouse-sql script %s against database %s',
74 path,
75 dbname,
76 )
77 conn = self.get_connection(dbname)
78 conn.execute(path.read_text())
79 self._migrations_run.add(key)
80
81 def _init_db(self, dbname: str):
82 conn = self._connections.get_master_connection()
83 conn.execute(f'DROP DATABASE IF EXISTS `{dbname}`')
84 conn.execute(f'CREATE DATABASE `{dbname}`')
85
86
87class Control:
88 def __init__(
89 self,
90 databases: classes.DatabasesDict,
91 state: DatabasesState,
92 ):
93 self._databases = databases
94 self._state = state
95
96 def get_connections(self):
97 return {
98 alias: self._state.get_connection(dbconfig.dbname)
99 for alias, dbconfig in self._databases.items()
100 }
101
102 def run_migrations(self):
103 for dbconfig in self._databases.values():
104 self._run_database_migrations(dbconfig)
105
106 def _run_database_migrations(self, dbconfig: classes.DatabaseConfig):
107 for path in dbconfig.migrations:
108 self._state.run_migration(dbconfig.dbname, path)
109
110
111def _get_db_tables_list(connection: clickhouse_driver.Client):
112 return connection.execute('SHOW TABLES')
113
114
115def apply_queries(
116 connection: clickhouse_driver.Client,
117 queries: list[ClickhouseQuery],
118):
119 tables = _get_db_tables_list(connection)
120 if tables:
121 for (table,) in tables:
122 connection.execute(f'TRUNCATE TABLE `{table}`')
123
124 for query in queries:
125 try:
126 connection.execute(query.body)
127 except Exception as exc:
128 error_message = (
129 f'ClickHouse apply query error\nQuery from {query.source}\n'
130 )
131 if query.path:
132 error_message += f'File path: {query.path}\n'
133 error_message += '\n' + str(exc)
134 raise RuntimeError(error_message)