Test Failed
Branch master (c7fd68)
by Gaetano
06:34
created

SqlExecute::execute()   D

Complexity

Conditions 18
Paths 63

Size

Total Lines 112
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 59
dl 0
loc 112
rs 4.8666
c 0
b 0
f 0
cc 18
nc 63
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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