Skip to content

Commit 2c5b1dc

Browse files
author
epriestley
committed
Provide "--output" flags for "bin/storage renamespace"
Summary: Ref T6996. Depends on D16407. This does the same stuff as D16407, but for `bin/storage renamespace`. In particular: - Support writing directly to a file (so we can get good errors on failure). - Support in-process compression. Also add support for reading out of a `storage dump` subprocess, so we don't have to do a dump-to-disk + renamespace + compress dance and can just stream out of MySQL directly to a compressed file on disk. This is used in the second stage of instance exports (see T7148). It would be nice to share more code with `bin/storage dump`, and possibly to just make this a flag for it, although we still do need to do the file-based version when importing (vs exporting). I figured that was better left for another time. Test Plan: Ran `bin/storage renamespace --live --output x --from A --to B --compress --overwrite` and similar commands. Verified that a compressed, renamespaced dump came out of the other end. Reviewers: chad Reviewed By: chad Maniphest Tasks: T6996 Differential Revision: https://secure.phabricator.com/D16410
1 parent 37b8ec5 commit 2c5b1dc

File tree

1 file changed

+138
-22
lines changed

1 file changed

+138
-22
lines changed

src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php

Lines changed: 138 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@ protected function didConstruct() {
88
->setName('renamespace')
99
->setExamples(
1010
'**renamespace** [__options__] '.
11-
'--in __dump.sql__ --from __old__ --to __new__ > __out.sql__')
11+
'--input __dump.sql__ --from __old__ --to __new__ > __out.sql__')
1212
->setSynopsis(pht('Change the database namespace of a .sql dump file.'))
1313
->setArguments(
1414
array(
1515
array(
16-
'name' => 'in',
16+
'name' => 'input',
1717
'param' => 'file',
1818
'help' => pht('SQL dumpfile to process.'),
1919
),
20+
array(
21+
'name' => 'live',
22+
'help' => pht(
23+
'Generate a live dump instead of processing a file on disk.'),
24+
),
2025
array(
2126
'name' => 'from',
2227
'param' => 'namespace',
@@ -27,6 +32,21 @@ protected function didConstruct() {
2732
'param' => 'namespace',
2833
'help' => pht('Desired database namespace for output.'),
2934
),
35+
array(
36+
'name' => 'output',
37+
'param' => 'file',
38+
'help' => pht('Write output directly to a file on disk.'),
39+
),
40+
array(
41+
'name' => 'compress',
42+
'help' => pht('Emit gzipped output instead of plain text.'),
43+
),
44+
array(
45+
'name' => 'overwrite',
46+
'help' => pht(
47+
'With __--output__, write to disk even if the file already '.
48+
'exists.'),
49+
),
3050
));
3151
}
3252

@@ -37,12 +57,13 @@ protected function isReadOnlyWorkflow() {
3757
public function didExecute(PhutilArgumentParser $args) {
3858
$console = PhutilConsole::getConsole();
3959

40-
$in = $args->getArg('in');
41-
if (!strlen($in)) {
60+
$input = $args->getArg('input');
61+
$is_live = $args->getArg('live');
62+
if (!strlen($input) && !$is_live) {
4263
throw new PhutilArgumentUsageException(
4364
pht(
44-
'Specify the dumpfile to read with %s.',
45-
'--in'));
65+
'Specify the dumpfile to read with "--in", or use "--live" to '.
66+
'generate one automatically.'));
4667
}
4768

4869
$from = $args->getArg('from');
@@ -61,34 +82,129 @@ public function didExecute(PhutilArgumentParser $args) {
6182
'--to'));
6283
}
6384

85+
86+
$output_file = $args->getArg('output');
87+
$is_overwrite = $args->getArg('overwrite');
88+
$is_compress = $args->getArg('compress');
89+
90+
if ($is_overwrite) {
91+
if ($output_file === null) {
92+
throw new PhutilArgumentUsageException(
93+
pht(
94+
'The "--overwrite" flag can only be used alongside "--output".'));
95+
}
96+
}
97+
98+
if ($output_file !== null) {
99+
if (Filesystem::pathExists($output_file)) {
100+
if (!$is_overwrite) {
101+
throw new PhutilArgumentUsageException(
102+
pht(
103+
'Output file "%s" already exists. Use "--overwrite" '.
104+
'to overwrite.',
105+
$output_file));
106+
}
107+
}
108+
}
109+
110+
if ($is_live) {
111+
$root = dirname(phutil_get_library_root('phabricator'));
112+
113+
$future = new ExecFuture(
114+
'%R dump',
115+
$root.'/bin/storage');
116+
117+
$lines = new LinesOfALargeExecFuture($future);
118+
} else {
119+
$lines = new LinesOfALargeFile($input);
120+
}
121+
122+
if ($output_file === null) {
123+
$file = fopen('php://stdout', 'wb');
124+
$output_name = pht('stdout');
125+
} else {
126+
if ($is_compress) {
127+
$file = gzopen($output_file, 'wb');
128+
} else {
129+
$file = fopen($output_file, 'wb');
130+
}
131+
$output_name = $output_file;
132+
}
133+
134+
if (!$file) {
135+
throw new Exception(
136+
pht(
137+
'Failed to open output file "%s" for writing.',
138+
$output_name));
139+
}
140+
64141
$patterns = array(
65142
'use' => '@^(USE `)([^_]+)(_.*)$@',
66143
'create' => '@^(CREATE DATABASE /\*.*?\*/ `)([^_]+)(_.*)$@',
67144
);
68145

69146
$found = array_fill_keys(array_keys($patterns), 0);
70147

71-
$matches = null;
72-
foreach (new LinesOfALargeFile($in) as $line) {
73-
74-
foreach ($patterns as $key => $pattern) {
75-
if (preg_match($pattern, $line, $matches)) {
76-
$namespace = $matches[2];
77-
if ($namespace != $from) {
78-
throw new Exception(
79-
pht(
80-
'Expected namespace "%s", found "%s": %s.',
81-
$from,
82-
$namespace,
83-
$line));
148+
try {
149+
$matches = null;
150+
foreach ($lines as $line) {
151+
152+
foreach ($patterns as $key => $pattern) {
153+
if (preg_match($pattern, $line, $matches)) {
154+
$namespace = $matches[2];
155+
if ($namespace != $from) {
156+
throw new Exception(
157+
pht(
158+
'Expected namespace "%s", found "%s": %s.',
159+
$from,
160+
$namespace,
161+
$line));
162+
}
163+
164+
$line = $matches[1].$to.$matches[3];
165+
$found[$key]++;
84166
}
167+
}
85168

86-
$line = $matches[1].$to.$matches[3];
87-
$found[$key]++;
169+
$data = $line."\n";
170+
171+
if ($is_compress) {
172+
$bytes = gzwrite($file, $data);
173+
} else {
174+
$bytes = fwrite($file, $data);
175+
}
176+
177+
if ($bytes !== strlen($data)) {
178+
throw new Exception(
179+
pht(
180+
'Failed to write %d byte(s) to "%s".',
181+
new PhutilNumber(strlen($data)),
182+
$output_name));
183+
}
184+
}
185+
186+
if ($is_compress) {
187+
$ok = gzclose($file);
188+
} else {
189+
$ok = fclose($file);
190+
}
191+
192+
if ($ok !== true) {
193+
throw new Exception(
194+
pht(
195+
'Failed to close file "%s".',
196+
$output_file));
197+
}
198+
} catch (Exception $ex) {
199+
try {
200+
if ($output_file !== null) {
201+
Filesystem::remove($output_file);
88202
}
203+
} catch (Exception $ex) {
204+
// Ignore any exception.
89205
}
90206

91-
echo $line."\n";
207+
throw $ex;
92208
}
93209

94210
// Give the user a chance to catch things if the results are crazy.

0 commit comments

Comments
 (0)