ExportFacade::exportDatabases()   A
last analyzed

Complexity

Conditions 5
Paths 10

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
c 0
b 0
f 0
nc 10
nop 2
dl 0
loc 27
rs 9.5222
1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Facades;
4
5
use Lagdo\DbAdmin\Driver\Entity\FieldType;
6
use Lagdo\DbAdmin\Driver\Entity\RoutineEntity;
7
use Lagdo\DbAdmin\Driver\Entity\RoutineInfoEntity;
8
use Lagdo\Facades\Logger;
9
use Exception;
10
11
use function array_filter;
12
use function array_map;
13
use function count;
14
use function implode;
15
use function ksort;
16
use function preg_match;
17
use function rtrim;
18
use function str_replace;
19
use function trim;
20
21
/**
22
 * Facade to export functions
23
 */
24
class ExportFacade extends AbstractFacade
25
{
26
    use Traits\TableExportTrait;
0 ignored issues
show
Bug introduced by
The trait Lagdo\DbAdmin\Db\Facades\Traits\TableExportTrait requires the property $trans which is not provided by Lagdo\DbAdmin\Db\Facades\ExportFacade.
Loading history...
27
    use Traits\TableDumpTrait;
0 ignored issues
show
introduced by
The trait Lagdo\DbAdmin\Db\Facades\Traits\TableDumpTrait requires some properties which are not provided by Lagdo\DbAdmin\Db\Facades\ExportFacade: $fullType, $name, $dataRows, $type, $table
Loading history...
28
29
    /**
30
     * The databases to dump
31
     *
32
     * @var array
33
     */
34
    private $databases;
0 ignored issues
show
introduced by
The private property $databases is not used, and could be removed.
Loading history...
35
36
    /**
37
     * The tables to dump
38
     *
39
     * @var array
40
     */
41
    private $tables;
0 ignored issues
show
introduced by
The private property $tables is not used, and could be removed.
Loading history...
42
43
    /**
44
     * Get data for export
45
     *
46
     * @param string $database      The database name
47
     * @param string $table
48
     *
49
     * @return array
50
     */
51
    public function getExportOptions(string $database, string $table = ''): array
52
    {
53
        return $database === '' ? [
54
            'databases' => $this->getDatabases(),
55
            'options' => $this->getBaseOptions($database, $table),
56
            'prefixes' => [],
57
        ] : [
58
            'tables' => $this->getDbTables(),
59
            'options' => $this->getBaseOptions($database, $table),
60
            'prefixes' => [],
61
        ];
62
    }
63
64
    /**
65
     * @param array<FieldType> $params
66
     *
67
     * @return string
68
     */
69
    private function getRoutineParams(array $params): string
70
    {
71
        // From dump.inc.php create_routine()
72
        $params = array_filter($params, fn($param) => $param->name !== '');
73
        ksort($params); // enforce params order
74
        $regex = "~^(" . $this->driver->inout() . ")\$~";
0 ignored issues
show
Bug introduced by
The method inout() does not exist on null. ( Ignorable by Annotation )

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

74
        $regex = "~^(" . $this->driver->/** @scrutinizer ignore-call */ inout() . ")\$~";

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...
75
76
        $params = array_map(function($param) use($regex) {
77
            $inout = preg_match($regex, $param->inout) ? "{$param->inout} " : '';
78
            return $inout . $this->driver->escapeId($param->name) .
79
                $this->driver->processType($param, 'CHARACTER SET');
80
        },$params);
81
        return implode(', ', $params);
82
    }
83
84
    /**
85
     * Generate SQL query for creating routine
86
     *
87
     * @param RoutineEntity $routine
88
     * @param RoutineInfoEntity $routineInfo
89
     *
90
     * @return string
91
     */
92
    private function getRoutineQuery(RoutineEntity $routine, RoutineInfoEntity $routineInfo): string
93
    {
94
        // From dump.inc.php create_routine()
95
        $routineName = $this->driver->escapeId(trim($routine->name));
96
        $routineParams = $this->getRoutineParams($routineInfo->params);
97
        $routineReturns = $routine->type !== 'FUNCTION' ? '' :
98
            ' RETURNS' . $this->driver->processType($routineInfo->return, 'CHARACTER SET');
0 ignored issues
show
Bug introduced by
It seems like $routineInfo->return can also be of type null; however, parameter $field of Lagdo\DbAdmin\Driver\Driver::processType() does only seem to accept Lagdo\DbAdmin\Driver\Entity\FieldType, maybe add an additional type check? ( Ignorable by Annotation )

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

98
            ' RETURNS' . $this->driver->processType(/** @scrutinizer ignore-type */ $routineInfo->return, 'CHARACTER SET');
Loading history...
99
        $routineLanguage = $routineInfo->language ? " LANGUAGE {$routineInfo->language}" : '';
100
        $definition = rtrim($routineInfo->definition, ';');
101
        $routineDefinition = $this->driver->jush() !== 'pgsql' ? "\n$definition;" :
102
            ' AS ' . $this->driver->quote($definition);
103
104
        return "CREATE {$routine->type} $routineName ($routineParams)" .
105
            "{$routineReturns}{$routineLanguage}{$routineDefinition};";
106
    }
107
108
    /**
109
     * Dump types in the connected database
110
     *
111
     * @return void
112
     */
113
    private function dumpTypes()
114
    {
115
        if (!$this->options['types']) {
116
            return;
117
        }
118
119
        // From dump.inc.php
120
        $style = $this->options['db_style'];
121
        foreach ($this->driver->userTypes(true) as $type) {
122
            $this->queries[] = ''; // Empty line
123
            if (count($type->enums) === 0) {
124
                //! https://github.com/postgres/postgres/blob/REL_17_4/src/bin/pg_dump/pg_dump.c#L10846
125
                $this->queries[] = "-- Could not export type {$type->name}";
126
                continue;
127
            }
128
129
            $typeName = $this->driver->escapeId($type->name);
130
            if ($style !== 'DROP+CREATE') {
131
                $this->queries[] = "DROP TYPE IF EXISTS $typeName;;";
132
            }
133
            $enums = implode("', '", $type->enums);
134
            $this->queries[] = "CREATE TYPE $typeName AS ENUM ('$enums');";
135
        }
136
    }
137
138
    /**
139
     * Dump routines in the connected database
140
     *
141
     * @return void
142
     */
143
    private function dumpRoutines()
144
    {
145
        if (!$this->options['routines']) {
146
            return;
147
        }
148
149
        // From dump.inc.php
150
        $style = $this->options['db_style'];
151
        foreach ($this->driver->routines() as $routine) {
152
            $routineName = $this->driver->escapeId(trim($routine->name));
153
            $routineInfo = $this->driver->routine($routine->specificName, $routine->type);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $routineInfo is correct as $this->driver->routine($...icName, $routine->type) targeting Lagdo\DbAdmin\Driver\Driver::routine() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
154
            if ($routineInfo === null) {
155
                continue;
156
            }
157
158
            $create = $this->getRoutineQuery($routine, $routineInfo);
0 ignored issues
show
Bug introduced by
$routineInfo of type null is incompatible with the type Lagdo\DbAdmin\Driver\Entity\RoutineInfoEntity expected by parameter $routineInfo of Lagdo\DbAdmin\Db\Facades...cade::getRoutineQuery(). ( Ignorable by Annotation )

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

158
            $create = $this->getRoutineQuery($routine, /** @scrutinizer ignore-type */ $routineInfo);
Loading history...
159
            $this->driver->setUtf8mb4($create);
160
            $this->queries[] = ''; // Empty line
161
            if ($style !== 'DROP+CREATE') {
162
                $this->queries[] = "DROP {$routine->type} IF EXISTS $routineName;;";
163
            }
164
            $this->queries[] = $create;
165
        }
166
    }
167
168
    /**
169
     * Dump events in the connected database
170
     *
171
     * @return void
172
     */
173
    private function dumpEvents()
174
    {
175
        if (!$this->options['events']) {
176
            return;
177
        }
178
179
        // From dump.inc.php
180
        $style = $this->options['db_style'];
181
        foreach ($this->driver->rows('SHOW EVENTS') as $row) {
182
            $sql = 'SHOW CREATE EVENT ' . $this->driver->escapeId($row['Name']);
183
            $create = $this->driver->removeDefiner($this->driver->result($sql, 3));
184
            $this->driver->setUtf8mb4($create);
185
            $this->queries[] = ''; // Empty line
186
            if ($style !== 'DROP+CREATE') {
187
                $this->queries[] = 'DROP EVENT IF EXISTS ' . $this->driver->escapeId($row['Name']) . ';;';
188
            }
189
            $this->queries[] = "$create;;\n";
190
        }
191
    }
192
193
    /**
194
     * @param string $database
195
     *
196
     * @return void
197
     */
198
    private function dumpUseDatabaseQuery(string $database)
199
    {
200
        $style = $this->options['db_style'];
201
        if ($style === '' || !preg_match('~sql~', $this->options['format'])) {
202
            return;
203
        }
204
205
        $this->queries[] = $this->driver->getUseDatabaseQuery($database, $style);
206
    }
207
208
    /**
209
     * @param string $database
210
     * @param array $tableOptions
211
     *
212
     * @return void
213
     */
214
    private function dumpDatabase(string $database, array $tableOptions)
215
    {
216
        $this->driver->open($database); // New connection
217
        $this->dumpUseDatabaseQuery($database);
218
219
        if ($this->options['to_sql']) {
220
            $this->dumpTypes();
221
            $this->dumpRoutines();
222
            $this->dumpEvents();
223
        }
224
225
        if (!$this->options['table_style'] && !$this->options['data_style']) {
226
            return;
227
        }
228
229
        $statuses = array_filter($this->driver->tableStatuses(true), fn($status) =>
230
            isset($tableOptions['*']) || isset($tableOptions[$status->name]));
231
        $this->dumpTables($statuses, $tableOptions);
232
        // Dump the views after all the tables
233
        $this->dumpViews($statuses);
234
    }
235
236
    /**
237
     * @return array
238
     */
239
    private function getDatabaseExportHeaders(): array
240
    {
241
        $headers = [
242
            'version' => $this->driver->version(),
243
            'driver' => $this->driver->name(),
244
            'server' => str_replace("\n", ' ', $this->driver->serverInfo()),
245
            'sql' => false,
246
            'data_style' => false,
247
        ];
248
        if ($this->driver->jush() === 'sql') {
249
            $headers['sql'] = true;
250
            if (isset($this->options['data_style'])) {
251
                $headers['data_style'] = true;
252
            }
253
            // Set some options in database server
254
            $this->driver->execute("SET time_zone = '+00:00'");
255
            $this->driver->execute("SET sql_mode = ''");
256
        }
257
        return $headers;
258
    }
259
260
    /**
261
     * Export databases
262
     *
263
     * @param array  $databases     The databases to dump
264
     * @param array  $options       The export options
265
     *
266
     * @return array|string
267
     */
268
    public function exportDatabases(array $databases, array $options): array|string
269
    {
270
        // From dump.inc.php
271
        // $tables = array_flip($options['tables']) + array_flip($options['data']);
272
        // $ext = dump_headers((count($tables) == 1 ? key($tables) : DB), (DB == '' || count($tables) > 1));
273
        $this->options = $options;
274
        // Export to SQL format (renamed from is_sql to to_sql).
275
        $this->options['to_sql'] = preg_match('~sql~', $options['format']) === 1;
276
277
        $headers = !$this->options['to_sql'] ? null : $this->getDatabaseExportHeaders();
278
279
        foreach ($databases as $database => $tables) {
280
            try {
281
                $this->dumpDatabase($database, $tables);
282
            }
283
            catch (Exception $e) {
284
                return $e->getMessage();
285
            }
286
        }
287
288
        if ($this->options['to_sql']) {
289
            $this->queries[] = '-- ' . $this->driver->result('SELECT NOW()');
290
        }
291
292
        return [
293
            'headers' => $headers,
294
            'queries' => $this->queries,
295
        ];
296
    }
297
}
298