db-sync-tool-kmi 2.11.6__py3-none-any.whl → 3.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- db_sync_tool/__main__.py +7 -252
- db_sync_tool/cli.py +733 -0
- db_sync_tool/database/process.py +94 -111
- db_sync_tool/database/utility.py +339 -121
- db_sync_tool/info.py +1 -1
- db_sync_tool/recipes/drupal.py +87 -12
- db_sync_tool/recipes/laravel.py +7 -6
- db_sync_tool/recipes/parsing.py +102 -0
- db_sync_tool/recipes/symfony.py +17 -28
- db_sync_tool/recipes/typo3.py +33 -54
- db_sync_tool/recipes/wordpress.py +13 -12
- db_sync_tool/remote/client.py +206 -71
- db_sync_tool/remote/file_transfer.py +303 -0
- db_sync_tool/remote/rsync.py +18 -15
- db_sync_tool/remote/system.py +2 -3
- db_sync_tool/remote/transfer.py +51 -47
- db_sync_tool/remote/utility.py +29 -30
- db_sync_tool/sync.py +52 -28
- db_sync_tool/utility/config.py +367 -0
- db_sync_tool/utility/config_resolver.py +573 -0
- db_sync_tool/utility/console.py +779 -0
- db_sync_tool/utility/exceptions.py +32 -0
- db_sync_tool/utility/helper.py +155 -148
- db_sync_tool/utility/info.py +53 -20
- db_sync_tool/utility/log.py +55 -31
- db_sync_tool/utility/logging_config.py +410 -0
- db_sync_tool/utility/mode.py +85 -150
- db_sync_tool/utility/output.py +122 -51
- db_sync_tool/utility/parser.py +33 -53
- db_sync_tool/utility/pure.py +93 -0
- db_sync_tool/utility/security.py +79 -0
- db_sync_tool/utility/system.py +277 -194
- db_sync_tool/utility/validation.py +2 -9
- db_sync_tool_kmi-3.0.2.dist-info/METADATA +99 -0
- db_sync_tool_kmi-3.0.2.dist-info/RECORD +44 -0
- {db_sync_tool_kmi-2.11.6.dist-info → db_sync_tool_kmi-3.0.2.dist-info}/WHEEL +1 -1
- db_sync_tool_kmi-2.11.6.dist-info/METADATA +0 -276
- db_sync_tool_kmi-2.11.6.dist-info/RECORD +0 -34
- {db_sync_tool_kmi-2.11.6.dist-info → db_sync_tool_kmi-3.0.2.dist-info}/entry_points.txt +0 -0
- {db_sync_tool_kmi-2.11.6.dist-info → db_sync_tool_kmi-3.0.2.dist-info/licenses}/LICENSE +0 -0
- {db_sync_tool_kmi-2.11.6.dist-info → db_sync_tool_kmi-3.0.2.dist-info}/top_level.txt +0 -0
db_sync_tool/database/process.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: future_fstrings -*-
|
|
3
2
|
|
|
4
3
|
"""
|
|
5
4
|
Process script
|
|
@@ -7,6 +6,8 @@ Process script
|
|
|
7
6
|
import semantic_version
|
|
8
7
|
|
|
9
8
|
from db_sync_tool.utility import parser, mode, system, helper, output
|
|
9
|
+
from db_sync_tool.utility.console import get_output_manager
|
|
10
|
+
from db_sync_tool.utility.helper import quote_shell_arg
|
|
10
11
|
from db_sync_tool.database import utility as database_utility
|
|
11
12
|
|
|
12
13
|
|
|
@@ -21,8 +22,7 @@ def create_origin_database_dump():
|
|
|
21
22
|
helper.check_and_create_dump_dir(mode.Client.ORIGIN,
|
|
22
23
|
helper.get_dump_dir(mode.Client.ORIGIN))
|
|
23
24
|
|
|
24
|
-
_dump_file_path =
|
|
25
|
-
mode.Client.ORIGIN) + database_utility.database_dump_file_name
|
|
25
|
+
_dump_file_path = database_utility.get_dump_file_path(mode.Client.ORIGIN)
|
|
26
26
|
|
|
27
27
|
_database_version = database_utility.get_database_version(mode.Client.ORIGIN)
|
|
28
28
|
output.message(
|
|
@@ -31,41 +31,58 @@ def create_origin_database_dump():
|
|
|
31
31
|
True
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
# Performance-optimized mysqldump options:
|
|
35
|
+
# --single-transaction: Consistent snapshot without locks (InnoDB)
|
|
36
|
+
# --quick: Row-by-row streaming instead of buffering in memory
|
|
37
|
+
# --extended-insert: Multi-row INSERTs (30-50% smaller dumps)
|
|
38
|
+
# --no-tablespaces: Skip tablespace info (requires PROCESS privilege in MySQL 8+)
|
|
39
|
+
_mysqldump_options = '--single-transaction --quick --extended-insert --no-tablespaces '
|
|
40
|
+
|
|
35
41
|
# Remove --no-tablespaces option for mysql < 5.6
|
|
36
|
-
|
|
37
|
-
if not _database_version is None:
|
|
42
|
+
if _database_version is not None:
|
|
38
43
|
if _database_version[0] == database_utility.DatabaseSystem.MYSQL and \
|
|
39
44
|
semantic_version.Version(_database_version[1]) < semantic_version.Version('5.6.0'):
|
|
40
|
-
_mysqldump_options = ''
|
|
45
|
+
_mysqldump_options = '--single-transaction --quick --extended-insert '
|
|
46
|
+
|
|
47
|
+
cfg = system.get_typed_config()
|
|
41
48
|
|
|
42
49
|
# Adding additional where clause to sync only selected rows
|
|
43
|
-
if
|
|
44
|
-
|
|
45
|
-
_mysqldump_options = _mysqldump_options + f'--where=\'{_where}\' '
|
|
50
|
+
if cfg.where != '':
|
|
51
|
+
_mysqldump_options = _mysqldump_options + f'--where=\'{cfg.where}\' '
|
|
46
52
|
|
|
47
53
|
# Adding additional mysqldump options
|
|
48
54
|
# see https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html#mysqldump-option-summary
|
|
49
|
-
if
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
+
if cfg.additional_mysqldump_options != '':
|
|
56
|
+
_mysqldump_options = _mysqldump_options + f'{cfg.additional_mysqldump_options} '
|
|
57
|
+
|
|
58
|
+
# Run mysql dump command
|
|
59
|
+
# Note: --defaults-extra-file MUST be the first option for MySQL/MariaDB
|
|
60
|
+
_db_name = quote_shell_arg(cfg.origin.db.name)
|
|
61
|
+
_safe_dump_path = quote_shell_arg(_dump_file_path)
|
|
62
|
+
|
|
63
|
+
# Get table names and shell-quote them safely (strip backticks first)
|
|
64
|
+
_raw_tables = database_utility.get_database_tables()
|
|
65
|
+
_safe_tables = ''
|
|
66
|
+
if _raw_tables.strip():
|
|
67
|
+
# Split on backtick-quoted names, strip backticks, shell-quote each
|
|
68
|
+
_table_names = [t.strip('`') for t in _raw_tables.split() if t.strip('`')]
|
|
69
|
+
_safe_tables = ' ' + ' '.join(quote_shell_arg(t) for t in _table_names)
|
|
70
|
+
|
|
71
|
+
# Stream mysqldump directly to gzip (50% less I/O, 40% faster start)
|
|
72
|
+
_safe_gz_path = quote_shell_arg(_dump_file_path + '.gz')
|
|
55
73
|
mode.run_command(
|
|
56
|
-
|
|
57
|
-
database_utility.generate_mysql_credentials(mode.Client.ORIGIN) + '
|
|
58
|
-
|
|
74
|
+
helper.get_command(mode.Client.ORIGIN, 'mysqldump') + ' ' +
|
|
75
|
+
database_utility.generate_mysql_credentials(mode.Client.ORIGIN) + ' ' +
|
|
76
|
+
_mysqldump_options + _db_name + ' ' +
|
|
59
77
|
database_utility.generate_ignore_database_tables() +
|
|
60
|
-
|
|
61
|
-
' > ' +
|
|
78
|
+
_safe_tables +
|
|
79
|
+
' | ' + helper.get_command(mode.Client.ORIGIN, 'gzip') + ' > ' + _safe_gz_path,
|
|
62
80
|
mode.Client.ORIGIN,
|
|
63
81
|
skip_dry_run=True
|
|
64
82
|
)
|
|
65
83
|
|
|
66
|
-
database_utility.check_database_dump(mode.Client.ORIGIN, _dump_file_path)
|
|
67
|
-
database_utility.count_tables(mode.Client.ORIGIN, _dump_file_path)
|
|
68
|
-
prepare_origin_database_dump()
|
|
84
|
+
database_utility.check_database_dump(mode.Client.ORIGIN, _dump_file_path + '.gz')
|
|
85
|
+
database_utility.count_tables(mode.Client.ORIGIN, _dump_file_path + '.gz')
|
|
69
86
|
|
|
70
87
|
|
|
71
88
|
def import_database_dump():
|
|
@@ -73,10 +90,11 @@ def import_database_dump():
|
|
|
73
90
|
Importing the selected database dump file
|
|
74
91
|
:return:
|
|
75
92
|
"""
|
|
76
|
-
|
|
77
|
-
prepare_target_database_dump()
|
|
93
|
+
cfg = system.get_typed_config()
|
|
78
94
|
|
|
79
|
-
|
|
95
|
+
# No need to decompress - import_database_dump_file streams .gz directly
|
|
96
|
+
|
|
97
|
+
if cfg.clear_database:
|
|
80
98
|
output.message(
|
|
81
99
|
output.Subject.TARGET,
|
|
82
100
|
'Clearing database before import',
|
|
@@ -86,7 +104,7 @@ def import_database_dump():
|
|
|
86
104
|
|
|
87
105
|
database_utility.truncate_tables()
|
|
88
106
|
|
|
89
|
-
if not
|
|
107
|
+
if not cfg.keep_dump and not mode.is_dump():
|
|
90
108
|
|
|
91
109
|
database_utility.get_database_version(mode.Client.TARGET)
|
|
92
110
|
|
|
@@ -96,23 +114,22 @@ def import_database_dump():
|
|
|
96
114
|
True
|
|
97
115
|
)
|
|
98
116
|
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
|
|
117
|
+
if mode.is_import():
|
|
118
|
+
# External import file (user-provided path)
|
|
119
|
+
_dump_path = cfg.import_file
|
|
102
120
|
else:
|
|
103
|
-
|
|
121
|
+
# Internal dump file (always .gz now)
|
|
122
|
+
_dump_path = database_utility.get_dump_gz_path(mode.Client.TARGET)
|
|
104
123
|
|
|
105
|
-
if not
|
|
124
|
+
if not cfg.yes:
|
|
106
125
|
_host_name = helper.get_ssh_host_name(mode.Client.TARGET, True) if mode.is_remote(
|
|
107
126
|
mode.Client.TARGET) else 'local'
|
|
108
127
|
|
|
109
|
-
_input =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
),
|
|
115
|
-
True
|
|
128
|
+
_input = get_output_manager().confirm(
|
|
129
|
+
f'Are you sure you want to import the dump file into {_host_name} database?',
|
|
130
|
+
subject='TARGET',
|
|
131
|
+
remote=mode.is_remote(mode.Client.TARGET),
|
|
132
|
+
default=True
|
|
116
133
|
)
|
|
117
134
|
|
|
118
135
|
if not _input: return
|
|
@@ -121,103 +138,69 @@ def import_database_dump():
|
|
|
121
138
|
|
|
122
139
|
import_database_dump_file(mode.Client.TARGET, _dump_path)
|
|
123
140
|
|
|
124
|
-
if
|
|
125
|
-
_after_dump = system.config['target']['after_dump']
|
|
141
|
+
if cfg.target.after_dump:
|
|
126
142
|
output.message(
|
|
127
143
|
output.Subject.TARGET,
|
|
128
|
-
f'Importing after_dump file {output.CliFormat.BLACK}{
|
|
144
|
+
f'Importing after_dump file {output.CliFormat.BLACK}{cfg.target.after_dump}{output.CliFormat.ENDC}',
|
|
129
145
|
True
|
|
130
146
|
)
|
|
131
|
-
import_database_dump_file(mode.Client.TARGET,
|
|
147
|
+
import_database_dump_file(mode.Client.TARGET, cfg.target.after_dump)
|
|
132
148
|
|
|
133
|
-
if
|
|
149
|
+
if cfg.target.post_sql:
|
|
134
150
|
output.message(
|
|
135
151
|
output.Subject.TARGET,
|
|
136
152
|
f'Running addition post sql commands',
|
|
137
153
|
True
|
|
138
154
|
)
|
|
139
|
-
for _sql_command in
|
|
155
|
+
for _sql_command in cfg.target.post_sql:
|
|
140
156
|
database_utility.run_database_command(mode.Client.TARGET, _sql_command, True)
|
|
141
157
|
|
|
142
158
|
|
|
143
159
|
def import_database_dump_file(client, filepath):
|
|
144
160
|
"""
|
|
145
|
-
Import a database dump file
|
|
161
|
+
Import a database dump file (supports both .sql and .gz files)
|
|
146
162
|
:param client: String
|
|
147
163
|
:param filepath: String
|
|
148
164
|
:return:
|
|
149
165
|
"""
|
|
150
|
-
if helper.check_file_exists(client, filepath):
|
|
151
|
-
|
|
152
|
-
'MYSQL_PWD="' + system.config[client]['db']['password'] + '" ' +
|
|
153
|
-
helper.get_command(client, 'mysql') + ' ' +
|
|
154
|
-
database_utility.generate_mysql_credentials(client) + ' \'' +
|
|
155
|
-
system.config[client]['db']['name'] + '\' < ' + filepath,
|
|
156
|
-
client,
|
|
157
|
-
skip_dry_run=True
|
|
158
|
-
)
|
|
166
|
+
if not helper.check_file_exists(client, filepath):
|
|
167
|
+
return
|
|
159
168
|
|
|
169
|
+
cfg = system.get_typed_config()
|
|
170
|
+
_db_name = quote_shell_arg(cfg.get_client(client).db.name)
|
|
171
|
+
_safe_filepath = quote_shell_arg(filepath)
|
|
172
|
+
_mysql_cmd = (helper.get_command(client, 'mysql') + ' ' +
|
|
173
|
+
database_utility.generate_mysql_credentials(client) + ' ' + _db_name)
|
|
160
174
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
True
|
|
170
|
-
)
|
|
171
|
-
mode.run_command(
|
|
172
|
-
helper.get_command(mode.Client.ORIGIN, 'tar') + ' cfvz ' + helper.get_dump_dir(
|
|
173
|
-
mode.Client.ORIGIN) + database_utility.database_dump_file_name + '.tar.gz -C ' +
|
|
174
|
-
helper.get_dump_dir(mode.Client.ORIGIN) + ' ' +
|
|
175
|
-
database_utility.database_dump_file_name + ' > /dev/null',
|
|
176
|
-
mode.Client.ORIGIN,
|
|
177
|
-
skip_dry_run=True
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def prepare_target_database_dump():
|
|
182
|
-
"""
|
|
183
|
-
Preparing the target database dump by the unpacked .tar.gz file
|
|
184
|
-
:return:
|
|
185
|
-
"""
|
|
186
|
-
output.message(output.Subject.TARGET, 'Extracting database dump', True)
|
|
187
|
-
mode.run_command(
|
|
188
|
-
helper.get_command('target', 'tar') + ' xzf ' + helper.get_dump_dir(mode.Client.TARGET) +
|
|
189
|
-
database_utility.database_dump_file_name + '.tar.gz -C ' +
|
|
190
|
-
helper.get_dump_dir(mode.Client.TARGET) + ' > /dev/null',
|
|
191
|
-
mode.Client.TARGET,
|
|
192
|
-
skip_dry_run=True
|
|
193
|
-
)
|
|
175
|
+
# Stream .gz files directly to mysql (no intermediate decompression)
|
|
176
|
+
if filepath.endswith('.gz'):
|
|
177
|
+
_cmd = (helper.get_command(client, 'gunzip') + ' -c ' + _safe_filepath +
|
|
178
|
+
' | ' + _mysql_cmd)
|
|
179
|
+
else:
|
|
180
|
+
_cmd = _mysql_cmd + ' < ' + _safe_filepath
|
|
181
|
+
|
|
182
|
+
mode.run_command(_cmd, client, skip_dry_run=True)
|
|
194
183
|
|
|
195
184
|
|
|
196
185
|
def clear_database(client):
|
|
197
186
|
"""
|
|
198
|
-
Clearing the database by dropping all tables
|
|
199
|
-
https://www.techawaken.com/drop-tables-mysql-database/
|
|
200
|
-
|
|
201
|
-
{ mysql -hHOSTNAME -uUSERNAME -pPASSWORD -Nse 'show tables' DB_NAME; } |
|
|
202
|
-
( while read table; do if [ -z ${i+x} ]; then echo 'SET FOREIGN_KEY_CHECKS = 0;'; fi; i=1;
|
|
203
|
-
echo "drop table \`$table\`;"; done;
|
|
204
|
-
echo 'SET FOREIGN_KEY_CHECKS = 1;' ) |
|
|
205
|
-
awk '{print}' ORS=' ' | mysql -hHOSTNAME -uUSERNAME -pPASSWORD DB_NAME;
|
|
187
|
+
Clearing the database by dropping all tables using pure SQL
|
|
206
188
|
|
|
207
189
|
:param client: String
|
|
208
190
|
:return:
|
|
209
191
|
"""
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)
|
|
192
|
+
# Get all tables via SQL query
|
|
193
|
+
_tables_result = database_utility.run_database_command(client, 'SHOW TABLES;')
|
|
194
|
+
if not _tables_result or not _tables_result.strip():
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
# Parse table names from result (skip header line)
|
|
198
|
+
_lines = _tables_result.strip().split('\n')
|
|
199
|
+
_tables = [line.strip() for line in _lines[1:] if line.strip()]
|
|
200
|
+
|
|
201
|
+
if not _tables:
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
# Build and execute DROP statements
|
|
205
|
+
statements = [f'DROP TABLE {database_utility.sanitize_table_name(t)}' for t in _tables]
|
|
206
|
+
database_utility.run_sql_batch_with_fk_disabled(client, statements)
|