Passed
Push — master ( 1eafe3...862746 )
by Gaetano
07:42
created

SqlExecute::writeResultsToFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 11
rs 10
cc 3
nc 3
nop 1
1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
namespace Db3v4l\Command;
4
5
use Db3v4l\Core\DatabaseSchemaManager;
6
use Db3v4l\Core\SqlAction\Command;
7
use Db3v4l\Core\SqlAction\File;
8
use Db3v4l\Core\SqlExecutor\Forked\NativeClient;
9
use Db3v4l\Core\SqlExecutor\Forked\TimedExecutor;
10
use Symfony\Component\Console\Input\InputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Input\InputInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use Symfony\Component\Console\Output\OutputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Output\OutputInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use Symfony\Component\Console\Input\InputOption;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Input\InputOption was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use Db3v4l\Util\Process;
14
15
class SqlExecute extends DatabaseManagingCommand
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class SqlExecute
Loading history...
16
{
17
    protected static $defaultName = 'db3v4l:sql:execute';
18
19
    protected function configure()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function configure()
Loading history...
20
    {
21
        $this
22
            ->setDescription('Executes an SQL command in parallel on all configured database instances, by default in a dedicated temporary database/user')
23
            ->addOption('sql', 's', InputOption::VALUE_REQUIRED, 'The sql command(s) string to execute')
24
            ->addOption('file', null, InputOption::VALUE_REQUIRED, "A file with sql commands to execute. The tokens '{dbtype}' and '{instancename}' will be replaced with actual values")
25
26
            ->addOption('database', 'd', InputOption::VALUE_REQUIRED, 'The name of an existing database to use. If omitted, a dedicated temporary database will be created on the fly and disposed after use')
27
            ->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'The name of the user to use for connecting to the existing database. Temporary databases get created with a temp user and random password')
28
            ->addOption('password', 'p', InputOption::VALUE_REQUIRED, 'The user password')
29
            ->addOption('charset', 'c', InputOption::VALUE_REQUIRED, 'The collation/character-set to use, _only_ for the dedicated temporary database. If omitted, the default collation for the instance will be used')
30
31
            ->addCommonOptions()
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
32
        ;
33
    }
34
35
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
36
     * @param InputInterface $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
37
     * @param OutputInterface $output
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
38
     * @return int
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
39
     * @throws \Exception
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
40
     */
41
    protected function execute(InputInterface $input, OutputInterface $output)
42
    {
43
        $start = microtime(true);
44
45
        // as per https://www.php.net/manual/en/function.ignore-user-abort.php: for cli scripts, it is probably a good idea
46
        // to use ignore_user_abort
47
        ignore_user_abort(true);
48
49
        $this->setOutput($output);
50
        $this->setVerbosity($output->getVerbosity());
51
52
        $instanceList = $this->parseCommonOptions($input);
53
54
        $sql = $input->getOption('sql');
55
        $file = $input->getOption('file');
56
        $dbName = $input->getOption('database');
57
        $userName = $input->getOption('user');
58
        $password = $input->getOption('password');
59
60
        if ($sql == null && $file == null) {
61
            throw new \Exception("Please provide an sql command or file to be executed");
62
        }
63
        if ($sql != null && $file != null) {
64
            throw new \Exception("Please provide either an sql command or file to be executed, not both");
65
        }
66
67
        if (($dbName == null) xor ($userName == null)) {
68
69
            /// @todo is it possible to let the user log in to the 'root' db, regardless of db type ?
70
71
            throw new \Exception("Please provide both a custom database name and associated user account");
72
        }
73
74
        $createDB = ($dbName == null);
75
76
        if ($createDB) {
77
            // create temp databases
78
79
            if ($this->outputFormat === 'text') {
80
                $this->writeln('<info>Creating temporary databases...</info>', OutputInterface::VERBOSITY_VERBOSE);
81
            }
82
83
            /// @todo inject more randomness in the username, by allowing more chars than bin2hex produces
84
85
            $userName = 'db3v4l_' . substr(bin2hex(random_bytes(5)), 0, 9); // some mysql versions have a limitation of 16 chars for usernames
86
            $password = bin2hex(random_bytes(16));
87
            //$dbName = bin2hex(random_bytes(31));
88
            $dbName = $userName; // $userName will be used as db name
89
90
            $tempDbSpecs = [];
91
            foreach($instanceList as $instanceName => $instanceSpecs) {
0 ignored issues
show
Coding Style introduced by
Expected "foreach (...) {\n"; found "foreach(...) {\n"
Loading history...
92
                $tempDbSpecs[$instanceName] = [
93
                    'dbname' => $dbName,
94
                    'user' => $userName,
95
                    'password' => $password
96
                ];
97
            }
98
99
            if (($charset = $input->getOption('charset')) != '') {
100
                foreach($instanceList as $instanceName) {
0 ignored issues
show
Coding Style introduced by
Expected "foreach (...) {\n"; found "foreach(...) {\n"
Loading history...
101
                    $tempDbSpecs[$instanceName]['charset'] = $charset;
102
                }
103
            }
104
105
            // for now, we always use NativeClient for creating/dropping temp dbs
106
            $previousStrategy = $this->executionStrategy;
107
            $this->executionStrategy = NativeClient::EXECUTION_STRATEGY;
108
            $creationResults = $this->createDatabases($instanceList, $tempDbSpecs);
109
            $this->executionStrategy = $previousStrategy;
110
111
            $dbConnectionSpecs = $creationResults['data'];
112
113
        } else {
114
            // use existing databases
115
116
            if ($this->outputFormat === 'text') {
117
                $this->writeln('<info>Using existing databases...</info>', OutputInterface::VERBOSITY_VERBOSE);
118
            }
119
120
            $dbConfig = [
121
                'user' => $userName,
122
                'password' => $password,
123
                'dbname' => $dbName,
124
            ];
125
126
            $dbConnectionSpecs = [];
127
            foreach($instanceList as $instanceName => $instanceSpecs) {
0 ignored issues
show
Coding Style introduced by
Expected "foreach (...) {\n"; found "foreach(...) {\n"
Loading history...
128
                $dbConnectionSpecs[$instanceName] = array_merge($instanceSpecs, $dbConfig);
129
            }
130
        }
131
132
        if (count($dbConnectionSpecs)) {
133
            $results = $this->executeSQL($dbConnectionSpecs, $sql, $file);
134
135
            if ($this->outputFormat === 'text') {
136
                $this->writeln('<info>Dropping temporary databases...</info>', OutputInterface::VERBOSITY_VERBOSE);
137
            }
138
139
            if ($createDB) {
140
                $results['failed'] += $creationResults['failed'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $creationResults does not seem to be defined for all execution paths leading up to this point.
Loading history...
141
142
                foreach($instanceList as $instanceName  => $instanceSpecs) {
0 ignored issues
show
Coding Style introduced by
Expected "foreach (...) {\n"; found "foreach(...) {\n"
Loading history...
143
                    if (!isset($dbConnectionSpecs[$instanceName])) {
144
                        unset($instanceList[$instanceName]);
145
                    }
146
                }
147
                // for now, we always use NativeClient for creating/dropping temp dbs
148
                $previousStrategy = $this->executionStrategy;
149
                $this->executionStrategy = NativeClient::EXECUTION_STRATEGY;
150
                $this->dropDatabases($instanceList, $dbConnectionSpecs, true);
151
                $this->executionStrategy = $previousStrategy;
152
            }
153
        } else {
154
            $results = ['succeeded' => 0,  'failed' => 0, 'data' => null];
155
        }
156
157
        $time = microtime(true) - $start;
158
159
        $this->writeResults($results, $time);
160
161
        return (int)$results['failed'];
162
    }
163
164
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
165
     * @param array $dbConnectionSpecs
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
166
     * @param string $sql
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
167
     * @param string $filename
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
168
     * @param string $format
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Superfluous parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
169
     * @param int $maxParallel
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 4 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Superfluous parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
170
     * @param int $timeout
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 4 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Superfluous parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
171
     * @return array
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
172
     * @throws \Exception
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
173
     */
174
    protected function executeSQL(array $dbConnectionSpecs, $sql, $filename = null)
175
    {
176
        return $this->executeSqlAction(
177
            $dbConnectionSpecs,
178
            'Execution of SQL',
179
            function ($schemaManager, $instanceName) use ($sql, $filename) {
180
                if ($sql != null) {
181
                    return new Command(
182
                        $sql,
183
                        function ($output, $executor) {
184
                            /** @var TimedExecutor $executor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
185
                            return array_merge(['stdout' => $output], $executor->getTimingData());
186
                        }
187
                    );
188
                } else {
189
                    /** @var DatabaseSchemaManager $schemaManager */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
190
                    $realFileName = $this->replaceDBSpecTokens($filename, $instanceName, $schemaManager->getDatabaseConfiguration());
191
                    if (!is_file($realFileName)) {
192
                        throw new \RuntimeException("Can not find sql file for execution: '$realFileName'");
193
                    }
194
                    return new File(
195
                        $realFileName,
196
                        function ($output, $executor) {
197
                            /** @var TimedExecutor $executor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
198
                            return array_merge(['stdout' => $output], $executor->getTimingData());
199
                        }
200
                    );
201
                }
202
            },
203
            true,
204
            array($this, 'onSubProcessOutput')
205
        );
206
    }
207
208
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $processIndex should have a doc-comment as per coding-style.
Loading history...
209
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
210
     * @param string $buffer
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
211
     * @param string $processIndex;
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Doc comment for parameter $processIndex; does not match actual variable name $processIndex
Loading history...
212
     * @param Process $process
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
213
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
214
    public function onSubProcessOutput($type, $buffer, $processIndex, $process = null)
215
    {
216
        if (is_object($processIndex) && $process === null) {
217
            /// @todo php bug ? investigate deeper...
218
            $process = $processIndex;
219
            $processIndex = '?';
220
        }
221
222
        $pid = is_object($process) ? $process->getPid() : '';
223
224
        $lines = explode("\n", trim($buffer));
225
226
        foreach ($lines as $line) {
227
            // we tag the output from the different processes
228
            if (trim($line) !== '') {
229
                if ($type === 'err') {
230
                    $this->writeErrorln(
231
                        '[' . $processIndex . '][' . $pid . '] ' . trim($line),
232
                        OutputInterface::VERBOSITY_VERBOSE,
233
                        OutputInterface::OUTPUT_RAW
234
                    );
235
                } else {
236
                    $this->writeln(
237
                        '[' . $processIndex . '][' . $pid . '] ' . trim($line),
238
                        OutputInterface::VERBOSITY_VERBOSE,
239
                        OutputInterface::OUTPUT_RAW
240
                    );
241
                }
242
            }
243
        }
244
    }
245
246
    /**
247
     * Replaces tokens
248
     * @param string $string the original string
0 ignored issues
show
Coding Style introduced by
There must be exactly one blank line before the tags in a doc comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Expected 11 spaces after parameter name; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
249
     * @param string $instanceName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
250
     * @param string[] $dbConnectionSpec
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
251
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
252
     * @todo add support for more tokens, eg '{version}'
0 ignored issues
show
Coding Style introduced by
Tag @todo cannot be grouped with parameter tags in a doc comment
Loading history...
Coding Style introduced by
Tag value for @todo tag indented incorrectly; expected 3 spaces but found 1
Loading history...
253
     */
254
    protected function replaceDBSpecTokens($string, $instanceName, $dbConnectionSpec)
255
    {
256
        $dbType = $dbConnectionSpec['vendor'];
257
        return str_replace(
258
            array('{dbtype}', '{instancename}', '{vendor}'),
259
            array($dbType, $instanceName, $dbConnectionSpec['vendor']),
260
            $string
261
        );
262
    }
263
264
    protected function hasDBSpecTokens($string)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function hasDBSpecTokens()
Loading history...
265
    {
266
        return $string != str_replace(
267
            array('{dbtype}', '{instancename}', '{vendor}'),
268
            '',
269
            $string
270
        );
271
    }
272
273
    protected function writeResultsToFile(array $results)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function writeResultsToFile()
Loading history...
274
    {
275
        if (!$this->hasDBSpecTokens($this->outputFile)) {
276
            parent::writeResultsToFile($results);
277
            return;
278
        }
279
280
        foreach($results['data'] as $instanceName => $data) {
0 ignored issues
show
Coding Style introduced by
Expected "foreach (...) {\n"; found "foreach(...) {\n"
Loading history...
281
            $formattedData = $this->formatResults(array('data' => $data));
282
            $outputFile = $this->replaceDBSpecTokens($this->outputFile, $instanceName, $this->dbConfigurationManager->getInstanceConfiguration($instanceName));
283
            file_put_contents($outputFile, $formattedData);
284
        }
285
    }
286
}
287