Issues (1426)

app/src/Command/SQLExecutingCommand.php (69 issues)

1
<?php
2
0 ignored issues
show
Missing file doc comment
Loading history...
3
namespace Db3v4l\Command;
4
5
use Db3v4l\API\Interfaces\SqlAction\CommandAction;
6
use Db3v4l\API\Interfaces\SqlAction\FileAction;
7
use Db3v4l\Core\DatabaseSchemaManager;
0 ignored issues
show
The type Db3v4l\Core\DatabaseSchemaManager 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...
8
use Db3v4l\Service\DatabaseConfigurationManager;
9
use Db3v4l\Service\DatabaseManagerFactory;
10
use Db3v4l\Service\ProcessManager;
11
use Db3v4l\Service\SqlExecutorFactory;
12
use Db3v4l\Util\Process;
13
use Symfony\Component\Console\Input\InputInterface;
0 ignored issues
show
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...
14
use Symfony\Component\Console\Input\InputOption;
0 ignored issues
show
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...
15
use Symfony\Component\Console\Output\OutputInterface;
0 ignored issues
show
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...
16
use Symfony\Component\Yaml\Yaml;
0 ignored issues
show
The type Symfony\Component\Yaml\Yaml 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...
17
18
abstract class SQLExecutingCommand extends BaseCommand
0 ignored issues
show
Missing doc comment for class SQLExecutingCommand
Loading history...
19
{
20
    /** @var DatabaseConfigurationManager $dbConfigurationManager */
0 ignored issues
show
The open comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
The close comment tag must be the only content on the line
Loading history...
21
    protected $dbConfigurationManager;
22
    /** @var SqlExecutorFactory $executorFactory */
0 ignored issues
show
The open comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
The close comment tag must be the only content on the line
Loading history...
23
    protected $executorFactory;
24
    protected $processManager;
25
    /** @var DatabaseManagerFactory $databaseManagerFactory */
0 ignored issues
show
The open comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
The close comment tag must be the only content on the line
Loading history...
26
    protected $databaseManagerFactory;
27
28
    const DEFAULT_OUTPUT_FORMAT = 'text';
29
    const DEFAULT_PARALLEL_PROCESSES = 16;
30
    const DEFAULT_PROCESS_TIMEOUT = 600;
31
    const DEFAULT_EXECUTOR_TYPE = 'NativeClient';
32
33
    protected $outputFormat;
34
    protected $outputFile;
35
    protected $maxParallelProcesses;
36
    protected $processTimeout;
37
    protected $executionStrategy;
38
    protected $executeInProcess;
39
40
    public function __construct(
0 ignored issues
show
Missing doc comment for function __construct()
Loading history...
41
        DatabaseConfigurationManager $dbConfigurationManager,
42
        SqlExecutorFactory $executorFactory,
43
        ProcessManager $processManager,
44
        DatabaseManagerFactory $databaseManagerFactory
45
    )
46
    {
0 ignored issues
show
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
47
        $this->dbConfigurationManager = $dbConfigurationManager;
48
        $this->executorFactory = $executorFactory;
49
        $this->processManager = $processManager;
50
        $this->databaseManagerFactory = $databaseManagerFactory;
51
52
        parent::__construct();
53
    }
54
55
    protected function addCommonOptions()
0 ignored issues
show
Missing doc comment for function addCommonOptions()
Loading history...
56
    {
57
        $this
58
            ->addOption('only-instances', 'o', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'Filter the database servers to run this command against. Usage of * and ? wildcards is allowed. To see all instances available, use `instance:list`', null)
59
            ->addOption('except-instances', 'x', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'Filter the database servers to run this command against', null)
60
            ->addOption('output-type', null, InputOption::VALUE_REQUIRED, 'The format for the output: json, php, text or yml', self::DEFAULT_OUTPUT_FORMAT)
61
            ->addOption('output-file', null, InputOption::VALUE_REQUIRED, 'Save output to a file instead of writing it to stdout. NB: take care that dbconsole runs in a container, which has a different view of the filesystem. A good dir for output is ./shared')
62
            ->addOption('timeout', null, InputOption::VALUE_REQUIRED, 'The maximum time to wait for subprocess execution (secs)', self::DEFAULT_PROCESS_TIMEOUT)
63
            ->addOption('max-parallel', null, InputOption::VALUE_REQUIRED, 'The maximum number of subprocesses to run in parallel', self::DEFAULT_PARALLEL_PROCESSES)
64
            ->addOption('dont-force-enabled-sigchild', null, InputOption::VALUE_NONE, "When using a separate process to run each sql command, do not force Symfony to believe that php was compiled with --enable-sigchild option")
65
            ->addOption('execution-strategy', null, InputOption::VALUE_REQUIRED, "EXPERIMENTAL. Internal usage", self::DEFAULT_EXECUTOR_TYPE)
66
            ->addOption('execute-in-process', null, InputOption::VALUE_NONE, "EXPERIMENTAL. Internal usage")
0 ignored issues
show
Space after closing parenthesis of function call prohibited
Loading history...
67
        ;
68
    }
69
70
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
71
     * @param InputInterface $input
0 ignored issues
show
Missing parameter comment
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
72
     * @return string[][] the list of instances to use. key: name, value: connection spec
0 ignored issues
show
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
73
     */
74
    protected function parseCommonOptions(InputInterface $input)
75
    {
76
        $this->outputFormat = $input->getOption('output-type');
77
        $this->outputFile = $input->getOption('output-file');
78
        $this->processTimeout = $input->getOption('timeout');
79
        $this->maxParallelProcesses = $input->getOption('max-parallel');
80
        $this->executionStrategy = $input->getOption('execution-strategy');
81
        $this->executeInProcess = $input->getOption('execute-in-process');
82
83
        // On Debian, which we use by default, SF has troubles understanding that php was compiled with --enable-sigchild
84
        // We thus force it, but give end users an option to disable this
85
        // For more details, see comment 12 at https://bugs.launchpad.net/ubuntu/+source/php5/+bug/516061
86
        if (! $input->getOption('dont-force-enabled-sigchild')) {
87
            Process::forceSigchildEnabled(true);
88
        }
89
90
        return $this->dbConfigurationManager->getInstancesConfiguration(
91
            $this->dbConfigurationManager->listInstances($input->getOption('only-instances'), $input->getOption('except-instances'))
92
        );
93
    }
94
95
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
96
     * @param string[][] $instanceList list of instances to execute the action on. Key: instance name, value: connection spec
0 ignored issues
show
Expected 10 spaces after parameter name; 1 found
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
97
     * @param string $actionName used to build error messages
0 ignored issues
show
Expected 5 spaces after parameter type; 1 found
Loading history...
Expected 12 spaces after parameter name; 1 found
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
98
     * @param callable $getSqlActionCallable the method used to retrieve the desired SqlAction.
0 ignored issues
show
Expected 3 spaces after parameter type; 1 found
Loading history...
Expected 2 spaces after parameter name; 1 found
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
99
     *                                       It will be passed as arguments the SchemaManager and instance name, and should return a CommandAction or FileAction
100
     * @param bool $timed whether to use a timed executor
0 ignored issues
show
Expected 7 spaces after parameter type; 1 found
Loading history...
Expected 17 spaces after parameter name; 1 found
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
101
     * @param callable $onForkedProcessOutput a callback invoked when forked processes produce output
0 ignored issues
show
Expected 3 spaces after parameter type; 1 found
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
102
     * @return array 'succeeded': int, 'failed': int, 'data': mixed[]
0 ignored issues
show
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
103
     * @throws \Exception
0 ignored issues
show
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
104
     */
105
    protected function executeSqlAction($instanceList, $actionName, $getSqlActionCallable, $timed = false, $onForkedProcessOutput = null)
106
    {
107
        $processes = [];
108
        $callables = [];
109
        $outputFilters = [];
110
        $tempSQLFileNames = [];
111
        $executors = [];
112
113
        try {
114
115
            foreach ($instanceList as $instanceName => $dbConnectionSpec) {
116
117
                $schemaManager = $this->databaseManagerFactory->getDatabaseManager($dbConnectionSpec);
118
119
                /** @var CommandAction|FileAction $sqlAction */
0 ignored issues
show
The open comment tag must be the only content on the line
Loading history...
Missing short description in doc comment
Loading history...
The close comment tag must be the only content on the line
Loading history...
120
                $sqlAction = call_user_func_array($getSqlActionCallable, [$schemaManager, $instanceName]);
121
122
                if ($sqlAction instanceof CommandAction) {
123
                    $filename = null;
124
                    $sql = $sqlAction->getCommand();
125
                } else if ($sqlAction instanceof FileAction) {
126
                    $filename = $sqlAction->getFilename();
127
                    $sql = null;
128
                } else {
129
                    // this is a coding error, not a sql execution error
130
                    throw new \Exception("Unsupported action type: " . get_class($sqlAction));
131
                }
132
                $filterCallable = $sqlAction->getResultsFilterCallable();
133
134
                if ($filename === null && $sql === null) {
135
                    // no sql to execute as forked process - we run the 'filter' functions in a separate loop
136
                    $callables[$instanceName] = $filterCallable;
137
                } else {
138
                    $outputFilters[$instanceName] = $filterCallable;
139
140
                    if ($filename === null && !$sqlAction->isSingleStatement()) {
0 ignored issues
show
The method isSingleStatement() does not exist on Db3v4l\API\Interfaces\SqlAction\FileAction. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

140
                    if ($filename === null && !$sqlAction->/** @scrutinizer ignore-call */ isSingleStatement()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
141
                        $filename = tempnam(sys_get_temp_dir(), 'db3v4l_') . '.sql';
142
                        file_put_contents($filename, $sql);
143
                        $tempSQLFileNames[] = $filename;
144
                    }
145
146
                    if ($this->executeInProcess) {
147
                        $executor = $this->executorFactory->createInProcessExecutor($instanceName, $dbConnectionSpec, $this->executionStrategy, $timed);
148
                        if ($filename === null) {
149
                            $callables[$instanceName] = $executor->getExecuteCommandCallable($sql);
150
                        } else {
151
                            $callables[$instanceName] = $executor->getExecuteFileCallable($filename);
0 ignored issues
show
The method getExecuteFileCallable() does not exist on Db3v4l\Core\SqlExecutor\InProcess\PDO. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

151
                            /** @scrutinizer ignore-call */ 
152
                            $callables[$instanceName] = $executor->getExecuteFileCallable($filename);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
The method getExecuteFileCallable() does not exist on Db3v4l\Core\SqlExecutor\InProcess\Doctrine. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

151
                            /** @scrutinizer ignore-call */ 
152
                            $callables[$instanceName] = $executor->getExecuteFileCallable($filename);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
152
                        }
153
                    } else {
154
                        $executor = $this->executorFactory->createForkedExecutor($instanceName, $dbConnectionSpec, $this->executionStrategy, $timed);
155
                        $executors[$instanceName] = $executor;
156
157
                        if ($filename === null) {
158
                            $process = $executor->getExecuteStatementProcess($sql);
159
                        } else {
160
                            $process = $executor->getExecuteFileProcess($filename);
161
                        }
162
163
                        if ($this->outputFormat === 'text') {
164
                            $this->writeln('Command line: ' . $process->getCommandLine(), OutputInterface::VERBOSITY_VERY_VERBOSE);
165
                        }
166
167
                        $process->setTimeout($this->processTimeout);
168
169
                        $processes[$instanceName] = $process;
170
                    }
171
                }
172
            }
173
174
            /// @todo refactor the filtering loop so that filters can be applied as well to inProcess executors
175
176
            $succeeded = 0;
177
            $failed = 0;
178
            $results = [];
179
180
            foreach ($callables as $instanceName => $callable) {
181
                try {
182
                    $results[$instanceName] = call_user_func($callable);
183
                    $succeeded++;
184
                } catch (\Throwable $t) {
185
                    $results[$instanceName] = [
186
                        'exitcode' => $t->getCode(),
187
                        'stderr' => $t->getMessage(),
188
                    ];
189
                    $failed++;
190
                    $this->writeErrorln("\n<error>$actionName in instance '$instanceName' failed! Reason: " . $t->getMessage() . "</error>\n", OutputInterface::VERBOSITY_NORMAL);
191
                }
192
            }
193
194
            if (count($processes)) {
195
                if ($this->outputFormat === 'text') {
196
                    $this->writeln('<info>Starting parallel execution...</info>', OutputInterface::VERBOSITY_VERY_VERBOSE);
197
                }
198
                $this->processManager->runParallel($processes, $this->maxParallelProcesses, 100, $onForkedProcessOutput);
199
200
                foreach ($processes as $instanceName => $process) {
201
                    if ($process->isSuccessful()) {
202
                        /// @todo is it necessary to have rtrim here ? shall we maybe move it to the executor ?
203
                        $output = rtrim($process->getOutput());
204
                        if (isset($outputFilters[$instanceName])) {
205
                            try {
206
                                $output = call_user_func_array($outputFilters[$instanceName], [$output, $executors[$instanceName]]);
207
                            } catch (\Throwable $t) {
208
                                $output = [
209
                                    // q: shall we add to the results the sql output ?
210
                                    'exitcode' => $t->getCode(),
211
                                    'stderr' => $t->getMessage(),
212
                                ];
213
                                $failed++;
214
                                $succeeded--;
215
                                $this->writeErrorln("\n<error>$actionName in instance '$instanceName' failed! Reason: " . $t->getMessage() . "</error>\n", OutputInterface::VERBOSITY_NORMAL);
216
                            }
217
                        }
218
                        $results[$instanceName] = $output;
219
                        $succeeded++;
220
                    } else {
221
                        $err = trim($process->getErrorOutput());
222
                        // some command-line database tools mix up stdout and stderr - we go out of our way to accommodate them...
223
                        if ($err === '') {
224
                            $err = trim($process->getOutput());
225
                        }
226
                        $results[$instanceName] = [
227
                            'exitcode' => $process->getExitCode(),
228
                            'stderr' => $err,
229
                        ];
230
231
                        $failed++;
232
                        $this->writeErrorln("\n<error>$actionName in instance '$instanceName' failed! Reason: " . $err . "</error>\n", OutputInterface::VERBOSITY_NORMAL);
233
                    }
234
                }
235
            }
236
237
        } finally {
238
            // make sure that we clean up temp files, as they might contain sensitive data
239
            foreach($tempSQLFileNames as $tempSQLFileName) {
0 ignored issues
show
Expected "foreach (...) {\n"; found "foreach(...) {\n"
Loading history...
240
                unlink($tempSQLFileName);
241
            }
242
        }
243
244
        uksort($results, function ($a, $b) {
0 ignored issues
show
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
245
            $aParts = explode('_', $a, 2);
246
            $bParts = explode('_', $b, 2);
247
            $cmp = strcasecmp($aParts[0], $bParts[0]);
248
            if ($cmp !== 0) {
249
                return $cmp;
250
            }
251
            if (count($aParts) == 1) {
252
                return -1;
253
            }
254
            if (count($bParts) == 1) {
255
                return 1;
256
            }
257
            $aVersion = str_replace('_', '.', $aParts[1]);
258
            $bVersion = str_replace('_', '.', $bParts[1]);
259
            return version_compare($aVersion, $bVersion);
260
        });
0 ignored issues
show
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
261
262
        return [
263
            'succeeded' => $succeeded,
264
            'failed' => $failed,
265
            'data' => $results
266
        ];
267
    }
268
269
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
270
     * @param array $results
0 ignored issues
show
Missing parameter comment
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
271
     * @return string
0 ignored issues
show
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
272
     * @throws \OutOfBoundsException for unsupported output formats
0 ignored issues
show
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
273
     */
274
    protected function formatResults(array $results)
275
    {
276
        switch ($this->outputFormat) {
277
            case 'json':
0 ignored issues
show
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
278
                return json_encode($results['data'], JSON_PRETTY_PRINT);
279
            case 'php':
0 ignored issues
show
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
280
                return var_export($results['data'], true);
281
            case 'text':
0 ignored issues
show
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
282
            case 'yml':
0 ignored issues
show
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
283
            case 'yaml':
0 ignored issues
show
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
284
                return Yaml::dump($results['data'], 2, 4, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
285
            default:
0 ignored issues
show
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
286
                throw new \OutOfBoundsException("Unsupported output format: '{$this->outputFormat}'");
287
                break;
288
        }
289
    }
290
291
    /**
0 ignored issues
show
Missing short description in doc comment
Loading history...
292
     * @param array $results should contain elements: succeeded(int) failed(int), data(mixed)
0 ignored issues
show
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
293
     * @param float $time execution time in seconds
0 ignored issues
show
Expected 4 spaces after parameter name; 1 found
Loading history...
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
294
     * @throws \Exception for unsupported formats
0 ignored issues
show
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
295
     * @todo since we use could be using forked processes, we can not measure total memory used... is it worth measuring just ours?
0 ignored issues
show
Tag @todo cannot be grouped with parameter tags in a doc comment
Loading history...
Tag value for @todo tag indented incorrectly; expected 3 spaces but found 1
Loading history...
296
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
297
    protected function writeResults(array $results, $time = null)
298
    {
299
        if ($this->outputFile != null) {
300
            $this->writeResultsToFile($results);
301
        } else {
302
            $formattedResults = $this->formatResults($results);
303
            $this->writeln($formattedResults, OutputInterface::VERBOSITY_QUIET,  OutputInterface::OUTPUT_RAW);
304
        }
305
306
        if ($this->outputFormat === 'text' || $this->outputFile != null) {
307
            $this->writeln($results['succeeded'] . ' succeeded, ' . $results['failed'] . ' failed');
308
            if ($this->outputFile != null) {
309
                $this->writeln("Results saved to file {$this->outputFile}");
310
            }
311
            if ($time !== null) {
312
                $this->writeln("<info>Time taken: ".sprintf('%.2f', $time)." secs</info>");
313
            }
314
        }
315
    }
316
317
    protected function writeResultsToFile(array $results)
0 ignored issues
show
Missing doc comment for function writeResultsToFile()
Loading history...
318
    {
319
        $formattedResults = $this->formatResults($results);
320
        file_put_contents($this->outputFile, $formattedResults);
321
    }
322
}
323