22import os
33import subprocess
44import unittest
5-
6- import platform
75from typing import List
86
9- from parameterized import parameterized
107from clickhouse_driver import Client
8+ from ripley import from_clickhouse
9+ from parameterized import parameterized
1110
1211
13- os . environ [ 'CLICKHOUSE_USER' ] = 'thedus_tests'
14- os . environ [ 'CLICKHOUSE_PASSWORD' ] = 'thedus_tests'
12+ _CLICKHOUSE_DB = 'thedus_tests'
13+ _THEDUS_DIR = os . path . join ( os . path . dirname ( __file__ ), 'migrations' )
1514
16-
17- def init_clickhouse (database : str = 'default' ) -> Client :
18- return Client (
19- host = 'localhost' ,
20- port = 9000 ,
21- user = os .environ ['CLICKHOUSE_USER' ],
22- password = os .environ ['CLICKHOUSE_PASSWORD' ],
23- database = database ,
24- )
15+ os .environ ['THEDUS_DIR' ] = _THEDUS_DIR
16+ os .environ ['CLICKHOUSE_DB' ] = _CLICKHOUSE_DB
17+ os .environ ['CLICKHOUSE_PASSWORD' ] = _CLICKHOUSE_DB
18+ os .environ ['CLICKHOUSE_USER' ] = _CLICKHOUSE_DB
2519
2620
2721class BaseCliTest (unittest .TestCase ):
2822 maxDiff = 10000
29- clickhouse = init_clickhouse ()
23+ clickhouse = Client (
24+ host = 'localhost' ,
25+ port = 9000 ,
26+ user = _CLICKHOUSE_DB ,
27+ password = _CLICKHOUSE_DB ,
28+ database = _CLICKHOUSE_DB ,
29+ )
3030
3131 @property
3232 def db_name (self ):
33- minor , major , _ = platform .python_version ().split ('.' )
34- return 'thedus_' + '_' .join ([minor , major ])
33+ return _CLICKHOUSE_DB
3534
3635 @property
3736 def thedus_dir (self ) -> str :
38- return os . environ [ 'THEDUS_DIR' ]
37+ return _THEDUS_DIR
3938
4039 @property
4140 def test_tables (self ) -> List [str ]:
@@ -46,21 +45,17 @@ def test_tables(self) -> List[str]:
4645 ]
4746
4847 def setUp (self ):
49- thedus_dir = os .path .join (os .path .dirname (__file__ ), 'migrations' )
5048 os .environ ['THEDUS_ENV' ] = ''
51- os .environ ['THEDUS_DIR' ] = thedus_dir
52- os .environ ['CLICKHOUSE_DB' ] = self .db_name
5349
54- with os .scandir (thedus_dir ) as entries :
50+ with os .scandir (self . thedus_dir ) as entries :
5551 for entry in entries :
5652 if entry .name == '.gitignore' :
5753 continue
5854 os .remove (entry .path )
5955
60- self .clickhouse = init_clickhouse ()
61- self .clickhouse .execute (f'DROP DATABASE IF EXISTS { self .db_name } ' )
62- self .clickhouse .execute (f'CREATE DATABASE IF NOT EXISTS { self .db_name } ' )
63- self .clickhouse = init_clickhouse (self .db_name )
56+ clickhouse = from_clickhouse (self .clickhouse )
57+ for table in clickhouse .get_tables_by_db (_CLICKHOUSE_DB ):
58+ self .clickhouse .execute (f"DROP TABLE { table .full_name } " )
6459
6560 for file_name , up , down , skip_env in (
6661 (
@@ -269,3 +264,83 @@ def test_downgrade_to_revision(self):
269264 self .check_thedus_output (result , ['rollback 20250101000000_0_create_tbl_metrics' , 'done' ])
270265 result = subprocess .getoutput ('thedus downgrade' )
271266 self .check_thedus_output (result , ['done' ])
267+
268+
269+ class TestSaveDbStructure (BaseCliTest ):
270+ def test_save_db_structure (self ):
271+ self .clickhouse .execute ("""
272+ CREATE TABLE votes
273+ (
274+ `Id` UInt32,
275+ `PostId` Int32,
276+ `VoteTypeId` UInt8,
277+ `CreationDate` DateTime64(3, 'UTC'),
278+ `UserId` Int32,
279+ `BountyAmount` UInt8
280+ )
281+ ENGINE = MergeTree
282+ ORDER BY (VoteTypeId, CreationDate, PostId)
283+ """ )
284+
285+ self .clickhouse .execute ("""
286+ CREATE TABLE up_down_votes_per_day
287+ (
288+ `Day` Date,
289+ `UpVotes` UInt32,
290+ `DownVotes` UInt32
291+ )
292+ ENGINE = SummingMergeTree
293+ ORDER BY Day
294+ """ )
295+
296+ self .clickhouse .execute ("""
297+ CREATE MATERIALIZED VIEW up_down_votes_per_day_mv TO up_down_votes_per_day AS
298+ SELECT toStartOfDay(CreationDate)::Date AS Day,
299+ countIf(VoteTypeId = 2) AS UpVotes,
300+ countIf(VoteTypeId = 3) AS DownVotes
301+ FROM votes
302+ GROUP BY Day
303+ """ )
304+
305+ self .clickhouse .execute ("""
306+ CREATE VIEW upvotes_per_user AS
307+ SELECT toDate(CreationDate) AS Day,
308+ UserId,
309+ count() AS user_votes
310+ FROM votes
311+ GROUP BY Day, UserId
312+ """ )
313+
314+ result = subprocess .run (
315+ 'thedus save-db-structure' ,
316+ shell = True ,
317+ cwd = self .thedus_dir ,
318+ stdout = subprocess .PIPE ,
319+ stderr = subprocess .STDOUT ,
320+ text = True ,
321+ env = os .environ ,
322+ )
323+
324+ filename = f'{ _CLICKHOUSE_DB } .sql'
325+ self .assertEqual (result .stdout [32 :], f'./{ filename } created\n ' )
326+
327+ with codecs .open (os .path .join (self .thedus_dir , filename )) as file :
328+ self .assertEqual (
329+ (
330+ f"CREATE TABLE { _CLICKHOUSE_DB } .up_down_votes_per_day "
331+ "(`Day` Date, `UpVotes` UInt32, `DownVotes` UInt32) "
332+ "ENGINE = SummingMergeTree ORDER BY Day SETTINGS index_granularity = 8192;\n "
333+ f"CREATE TABLE { _CLICKHOUSE_DB } .votes (`Id` UInt32, `PostId` Int32, `VoteTypeId` UInt8, "
334+ "`CreationDate` DateTime64(3, 'UTC'), `UserId` Int32, `BountyAmount` UInt8) ENGINE = MergeTree "
335+ "ORDER BY (VoteTypeId, CreationDate, PostId) SETTINGS index_granularity = 8192;\n "
336+ "CREATE MATERIALIZED VIEW "
337+ f"{ _CLICKHOUSE_DB } .up_down_votes_per_day_mv TO { _CLICKHOUSE_DB } .up_down_votes_per_day "
338+ "(`Day` Date, `UpVotes` UInt64, `DownVotes` UInt64) AS "
339+ "SELECT CAST(toStartOfDay(CreationDate), 'Date') AS Day, countIf(VoteTypeId = 2) AS UpVotes, "
340+ f"countIf(VoteTypeId = 3) AS DownVotes FROM { _CLICKHOUSE_DB } .votes GROUP BY Day;\n "
341+ f"CREATE VIEW { _CLICKHOUSE_DB } .upvotes_per_user (`Day` Date, `UserId` Int32, `user_votes` UInt64) "
342+ f"AS SELECT toDate(CreationDate) AS Day, UserId, count() AS user_votes FROM { _CLICKHOUSE_DB } .votes "
343+ "GROUP BY Day, UserId"
344+ ),
345+ file .read (),
346+ )
0 commit comments