-
Notifications
You must be signed in to change notification settings - Fork 19
/
fbclone.dpr
335 lines (297 loc) · 11.5 KB
/
fbclone.dpr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
(*
* The contents of this file are subject to the
* Initial Developer's Public License Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License from the Firebird Project website,
* at http://www.firebirdsql.org/index.php?op=doc&id=idpl.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code was created by Pierre Yager and Henri Gourvest.
*)
program fbclone;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
SysUtils,
Classes,
Generics.Collections,
uib,
uiblib,
uibase,
uibmetadata,
uibconst,
console.getopts in 'console.getopts.pas',
fbclone.cloner in 'fbclone.cloner.pas',
{$IFDEF ENABLE_BENCHMARK}
fbclone.benchmark in 'fbclone.benchmark.pas',
{$ENDIF}
fbclone.database in 'fbclone.database.pas',
fbclone.logger in 'fbclone.logger.pas';
procedure BackupRepairFile(const repair_file_name: string);
var
repair_file_ext: string;
backup_file_name: string;
begin
if FileExists(repair_file_name) then
begin
repair_file_ext := ExtractFileExt(repair_file_name);
if repair_file_ext <> '' then
Insert('~', repair_file_ext, 2);
backup_file_name := ChangeFileExt(repair_file_name, repair_file_ext);
if FileExists(backup_file_name) then
DeleteFile(backup_file_name);
RenameFile(repair_file_name, backup_file_name);
end;
end;
procedure RegisterExcludedTables(Cloner: TCloner; const TablesList: string);
var
L: TStringList;
S: String;
begin
L := TStringList.Create;
try
L.Delimiter := ',';
L.DelimitedText := TablesList;
for S in L do
Cloner.AddExcludedTable(S);
finally
L.Free;
end;
end;
var
GO: TGetOpt;
O: POption;
P: TPair<POption, string>;
src, tgt: TDatabase;
target_charset, read_charset, write_charset: string;
opts: TClonerOptions;
dump_file, repair_file: string;
commit_interval: integer;
page_size: Integer;
excluded_tables: string;
c: TCloner;
l: ILogger;
begin
opts := [];
commit_interval := 10000;
target_charset := '';
read_charset := '';
write_charset := '';
dump_file := '';
page_size := 0;
l := TConsoleLogger.Create;
GO := TGetOpt.Create;
try
try
GO.RegisterFlag('h', 'help', '', 'Show this help message', false);
GO.RegisterFlag('v', 'verbose', '', 'Show details', false);
GO.RegisterFlag('po', 'pump-only', '', 'Only pump data from source database into target database (source database and target database must share the same metadata structure)', false);
GO.RegisterFlag('e', 'empty-tables', '', 'Empty tables before data pump', false);
GO.RegisterSwitch('d', 'dump', 'file', 'Dump SQL script into file', false);
GO.RegisterSwitch('rd', 'repair-dump', 'file', 'Trace errors and SQL into a repair.sql file', false);
GO.RegisterSwitch('ps', 'page-size', 'page size', 'Overrides target database page size', false);
GO.RegisterSwitch('s', 'source', 'database', 'Source database connection string', true);
GO.RegisterSwitch('su', 'source-user', 'user', 'User name used to connect source database', false);
GO.RegisterSwitch('sp', 'source-password', 'password', 'Password used to connect source database', false);
GO.RegisterSwitch('sl', 'source-library', 'library', 'Client Library used to connect source database', false);
GO.RegisterSwitch('t', 'target', 'database', 'Target database connection string', true);
GO.RegisterSwitch('tu', 'target-user', 'user', 'User name used to connect target database', false);
GO.RegisterSwitch('tp', 'target-password', 'password', 'Password used to connect target dat base', false);
GO.RegisterSwitch('tl', 'target-library', 'library', 'Client Library used to connect target database', false);
GO.RegisterSwitch('tc', 'target-charset', 'charset', 'Target database default character set (default: source charset)', false);
GO.RegisterSwitch('rc', 'read-charset', 'charset', 'Character set to read from source database (default: source charset)', false);
GO.RegisterSwitch('wc', 'write-charset', 'charset', 'Character set to write into target database (default: source charset)', false);
GO.RegisterFlag('ics', 'ignore-charset', '', 'Ignore Source database Character set (force using default)', false);
GO.RegisterFlag('ko', 'keep-octets', '', 'Keep OCTETS Charset (use with -ics)', false);
GO.RegisterFlag('ic', 'ignore-collation', '', 'Ignore Source database Collation (force using default)', false);
GO.RegisterSwitch('xt', 'exclude-table', 'list', 'Comma separated list of tables to exclude from data pump', false);
GO.RegisterSwitch('u', 'user', 'user', 'User name used to connect both source and target databases', false);
GO.RegisterSwitch('p', 'password', 'password', 'Password used to connect both source and target databases', false);
GO.RegisterSwitch('l', 'library', 'library', 'Client Library used to connect both source and target databases', false);
GO.RegisterFlag('f', 'failsafe', '', 'Commit transaction every record (same as using -ci 1)', false);
GO.RegisterSwitch('ci', 'commit-interval', 'number', 'Commit transaction every <number> record', false);
GO.Parse;
if GO.Flag['h'] then
begin
l.Info(GO.PrintSyntax);
l.Info;
l.Info(GO.PrintHelp);
Exit;
end;
if not GO.Validate then
begin
l.Error('Missing parameters on command line:');
for O in GO.Missing do
l.Error(' ' + O.ToShortSyntax);
l.Error;
l.Error(GO.PrintSyntax);
Halt(1);
end;
if GO.Flag['u'] and (GO.Flag['su'] or GO.Flag['tu']) then
begin
l.Error('Conflict between parameters on command line:');
l.Error(' Flags -tu and -su cannot be used with -u');
l.Error;
l.Error(GO.PrintSyntax);
Halt(1);
end;
if GO.Flag['p'] and (GO.Flag['sp'] or GO.Flag['tp']) then
begin
l.Error('Conflict between parameters on command line:');
l.Error(' Flags -tp and -sp cannot be used with -p');
l.Error;
l.Error(GO.PrintSyntax);
Halt(1);
end;
if GO.Flag['l'] and (GO.Flag['sl'] or GO.Flag['tl']) then
begin
l.Error('Conflict between parameters on command line:');
l.Error(' Flags -tl and -sl cannot be used with -l');
l.Error;
l.Error(GO.PrintSyntax);
Halt(1);
end;
if GO.Flag['po'] and GO.Flag['ps'] then
begin
l.Error('Useless flag on command line:');
l.Error(' The flag -ps (Page Size) will be ignored if -po (Pump Only Mode) is specified');
l.Error;
end;
if GO.Flag['po'] and GO.Flag['tc'] then
begin
l.Error('Useless flag on command line:');
l.Error(' The flag -tc (Target Character Set) will be ignored if -po (Pump Only Mode) is specified');
l.Error;
end;
if GO.Flag['e'] and not GO.Flag['po'] then
begin
l.Error('Useless flag on command line:');
l.Error(' The flag -e (Empty Tables) will be ignored if -po (Pump Only Mode) is not specified');
l.Error;
end;
if GO.Flag['f'] and GO.Flag['ci'] then
begin
l.Error('Useless flag on command line:');
l.Error(' The flag -ci (Commit Interval) will be ignored if -f (Failsafe Mode) is specified');
l.Error;
end;
if GO.Flag['ko'] and not GO.Flag['ics'] then
begin
l.Error('Useless flag on command line:');
l.Error(' The flag -ko (Keep OCTETS Character set) will be ignored if -ics (Ignore Character set) is not specified');
l.Error;
end;
for P in GO.Parameters do
begin
if P.Key^.Short = 's' then
src.ConnectionString := P.Value
else if P.Key^.Short = 'u' then
begin
src.Username := P.Value;
tgt.Username := P.Value;
end
else if P.Key^.Short = 'p' then
begin
src.Password := P.Value;
tgt.Password := P.Value;
end
else if P.Key^.Short = 'l' then
begin
src.LibraryFileName := P.Value;
tgt.LibraryFileName := P.Value;
end
else if P.Key^.Short = 'su' then
src.Username := P.Value
else if P.Key^.Short = 'sp' then
src.Password := P.Value
else if P.Key^.Short = 'sl' then
src.LibraryFileName := P.Value
else if P.Key^.Short = 't' then
tgt.ConnectionString := P.Value
else if P.Key^.Short = 'tu' then
tgt.Username := P.Value
else if P.Key^.Short = 'tp' then
tgt.Password := P.Value
else if P.Key^.Short = 'tl' then
tgt.LibraryFileName := P.Value
else if P.Key^.Short = 'tc' then
target_charset := P.Value
else if P.Key^.Short = 'rc' then
read_charset := P.Value
else if P.Key^.Short = 'wc' then
write_charset := P.Value
else if P.Key^.Short = 'v' then
Include(opts, coVerbose)
else if P.Key^.Short = 'd' then
dump_file := P.Value
else if P.Key^.Short = 'rd' then
repair_file := P.Value
else if P.Key^.Short = 'po' then
Include(opts, coPumpOnly)
else if P.Key^.Short = 'e' then
Include(opts, coEmptyTables)
else if P.Key^.Short = 'f' then
Include(opts, coFailSafe)
else if P.Key^.Short = 'ci' then
commit_interval := StrToInt(P.Value)
else if P.Key^.Short = 'ps' then
page_size := StrToInt(P.Value)
else if P.Key^.Short = 'xt' then
excluded_tables := P.Value
else if P.Key^.Short = 'ics' then
Include(opts, coIgnoreCharset)
else if P.Key^.Short = 'ic' then
Include(opts, coIgnoreCollation)
else if P.Key^.Short = 'ko' then
Include(opts, coKeepOctets)
end;
MapEnvironment(src);
MapEnvironment(tgt);
if repair_file <> '' then
BackupRepairFile(repair_file);
except
on E: Exception do
begin
l.Error('Error on command line ' + E.Message);
l.Error(GO.PrintSyntax);
Halt(1);
end;
end;
finally
GO.Free;
end;
try
c := TCloner.Create;
try
c.Logger := l;
c.PageSize := page_size;
c.TargetCharset := target_charset;
c.ReadCharset := read_charset;
c.WriteCharset := write_charset;
c.Options := opts;
c.CommitInterval := commit_interval;
c.DumpFile := dump_file;
c.RepairFile := repair_file;
RegisterExcludedTables(c, excluded_tables);
if not c.Clone(src, tgt) then
Halt(1);
finally
c.Free;
end;
{$IFDEF DEBUG}
WriteLn;
WriteLn('[DEBUG] Press a key to terminate');
ReadLn;
{$ENDIF}
except
on E: Exception do
begin
l.Error('General exception ' + E.Message);
Halt(1);
end;
end;
end.