4
4
import logging
5
5
import MySQLdb
6
6
7
+
7
8
class TableBuilder (object ):
8
9
9
10
connection = None
10
11
cursor = None
11
12
12
- def template (self , host , user , password = None , db = None , tables = None ):
13
+ host = None
14
+ port = None
15
+ user = None
16
+ password = None
17
+ dbs = None
18
+ tables = None
19
+
20
+ def __init__ (self , host , port , user , password = None , dbs = None , tables = None ):
21
+ self .host = host
22
+ self .port = port
23
+ self .user = user
24
+ self .password = password
25
+ self .dbs = dbs
26
+ self .tables = tables
27
+
28
+ def templates (self ):
29
+ """
30
+ Create templates for specified MySQL tables. In case no tables specified all tables from specified db are templated
31
+
32
+ :param host: string MySQL host
33
+ :param user: string MySQL user
34
+ :param password: string MySQL password
35
+ :param dbs: list of string MySQL datatabse/ May be omitted, in this case tables has to contain full table names, Ex.: db.table1
36
+ :param tables: list of string list of table names. May be short (in case db specified) or full (in the form db.table, in case no db specified)
37
+ :return: dict of CREATE TABLE () templates
38
+ """
39
+ res = {}
40
+
41
+ db = None
42
+
43
+ try :
44
+ db = self .dbs [0 ]
45
+ except :
46
+ pass
47
+
13
48
# sanity check
14
- if db is None and tables is None :
15
- return None
49
+ if db is None and self . tables is None :
50
+ return res
16
51
17
52
# MySQL connections
18
53
self .connection = MySQLdb .connect (
19
- host = host ,
20
- user = user ,
21
- passwd = password ,
54
+ host = self . host ,
55
+ user = self . user ,
56
+ passwd = self . password ,
22
57
db = db ,
23
58
)
24
59
self .cursor = self .connection .cursor ()
25
60
26
61
# in case to tables specified - list all tables of the DB specified
27
- if db is not None and tables is None :
62
+ if db is not None and self . tables is None :
28
63
self .cursor .execute ("USE " + db )
29
- tables = []
64
+ self . tables = []
30
65
self .cursor .execute ("SHOW TABLES" ) # execute 'SHOW TABLES' (but data is not returned)
31
66
for (table_name ,) in self .cursor :
32
- tables .append (table_name )
33
-
34
- # tables can be something like 'db1, db2, db3'
35
- # make [db1, db2, db3]
36
- if isinstance (tables , str ):
37
- tables = [table .strip () for table in tables .split (',' )]
38
-
39
- for table in tables :
40
- print (self .table (table , db ))
41
-
42
- def table (self , table_name , db = None ):
67
+ self .tables .append (table_name )
68
+
69
+ # create dict of table templates
70
+ for table in self .tables :
71
+ if not db in res :
72
+ res [db ] = {}
73
+ res [db ][table ] = self .create_table_template (table , db )
74
+
75
+ # {
76
+ # 'db': {
77
+ # 'table1': 'CREATE TABLE(...)...',
78
+ # 'table2': 'CREATE TABLE(...)...',
79
+ # }
80
+ # }
81
+ return res
82
+
83
+ def create_table_template (self , table_name , db = None ):
84
+ """
85
+ Produce template for CH's
86
+ CREATE TABLE(
87
+ ...
88
+ columns specification
89
+ ...
90
+ ) ENGINE = MergeTree(_SPECIFY_DateField_HERE, (SPECIFY_INDEX_FIELD1, SPECIFY_INDEX_FIELD2, ...etc...), 8192)
91
+ for specified MySQL's table
92
+ :param table_name: string - name of the table in MySQL which will be used as a base for CH's CREATE TABLE template
93
+ :param db: string - name of the DB in MySQL
94
+ :return: string - almost-ready-to-use CREATE TABLE statement
95
+ """
43
96
44
97
# `db`.`table` or just `table`
45
98
name = '`{0}`.`{1}`' .format (db , table_name ) if db else '`{0}`' .format (table_name )
@@ -52,10 +105,10 @@ def table(self, table_name, db=None):
52
105
for (_field , _type , _null , _key , _default , _extra ,) in self .cursor :
53
106
# Field | Type | Null | Key | Default | Extra
54
107
55
- # build ready-to-sql column specification
108
+ # build ready-to-sql column specification Ex.:
56
109
# `integer_1` Nullable(Int32)
57
110
# `u_integer_1` Nullable(UInt32)
58
- ch_columns .append ('`{0}` {1}' .format (_field , self .map (mysql_type = _type , null = _null ,)))
111
+ ch_columns .append ('`{0}` {1}' .format (_field , self .map_type (mysql_type = _type , nullable = _null , )))
59
112
60
113
sql = """
61
114
CREATE TABLE {0} (
@@ -67,7 +120,17 @@ def table(self, table_name, db=None):
67
120
)
68
121
return sql
69
122
70
- def map (self , mysql_type , null = False ):
123
+ def map_type (self , mysql_type , nullable = False ):
124
+ """
125
+ Map MySQL type (as a string from DESC table statement) to CH type (as string)
126
+ :param mysql_type: string MySQL type (from DESC statement). Ex.: 'INT(10) UNSIGNED', 'BOOLEAN'
127
+ :param nullable: bool|string True|'yes' is this field nullable
128
+ :return: string CH's type specification directly usable in CREATE TABLE statement. Ex.:
129
+ Nullable(Int32)
130
+ Nullable(UInt32)
131
+ """
132
+
133
+ # deal with UPPER CASE strings for simplicity
71
134
mysql_type = mysql_type .upper ()
72
135
73
136
# Numeric Types
@@ -146,22 +209,26 @@ def map(self, mysql_type, null=False):
146
209
ch_type = 'UNKNOWN'
147
210
148
211
# Deal with NULLs
149
- if isinstance (null , bool ):
150
- if null :
212
+ if isinstance (nullable , bool ):
213
+ # for bool - simple statement
214
+ if nullable :
151
215
ch_type = 'Nullable(' + ch_type + ')'
152
- elif isinstance (null , str ):
153
- if null .upper () == "YES" :
216
+ elif isinstance (nullable , str ):
217
+ # also accept case-insencitive string 'yes'
218
+ if nullable .upper () == "YES" :
154
219
ch_type = 'Nullable(' + ch_type + ')'
155
220
156
221
return ch_type
157
222
158
223
if __name__ == '__main__' :
159
224
tb = TableBuilder ()
160
- tb .template (
225
+ templates = tb .templates (
161
226
host = '127.0.0.1' ,
162
227
user = 'reader' ,
163
228
password = 'qwerty' ,
164
229
db = 'db' ,
165
230
# tables='datatypes, enum_datatypes, json_datatypes',
166
231
tables = ['datatypes' , 'enum_datatypes' , 'json_datatypes' ],
167
232
)
233
+ for table in templates :
234
+ print (table , '=' , templates [table ])
0 commit comments