Passed
Branch main (9c812e)
by Thierry
03:26
created

CommandAdmin   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 91
c 5
b 0
f 0
dl 0
loc 231
rs 9.28
wmc 39

6 Methods

Rating   Name   Duplication   Size   Complexity  
A connection() 0 10 3
B values() 0 19 8
A message() 0 11 4
B executeCommands() 0 51 8
B select() 0 43 8
B executeQuery() 0 27 8
1
<?php
2
3
namespace Lagdo\DbAdmin\DbAdmin;
4
5
use Lagdo\DbAdmin\Driver\Db\ConnectionInterface;
6
use Lagdo\DbAdmin\Driver\Entity\QueryEntity;
7
8
/**
9
 * Admin command functions
10
 */
11
class CommandAdmin extends AbstractAdmin
12
{
13
    /**
14
     * Connection for exploring indexes and EXPLAIN (to not replace FOUND_ROWS())
15
     * //! PDO - silent error
16
     *
17
     * @var ConnectionInterface
18
     */
19
    protected $connection = null;
20
21
    /**
22
     * @var array
23
     */
24
    protected $results;
25
26
    /**
27
     * Open a second connection to the server
28
     *
29
     * @return ConnectionInterface|null
30
     */
31
    private function connection()
32
    {
33
        if ($this->connection === null && $this->driver->database() !== '') {
34
            // Connection for exploring indexes and EXPLAIN (to not replace FOUND_ROWS())
35
            //! PDO - silent error
36
            $connection = $this->driver->createConnection();
37
            $connection->open($this->driver->database(), $this->driver->schema());
38
            $this->connection = $connection;
39
        }
40
        return $this->connection;
41
    }
42
43
    /**
44
     * @param array $row
45
     * @param array $blobs
46
     * @param array $types
47
     *
48
     * @return string
49
    */
50
    protected function values(array $row, array $blobs, array $types)
51
    {
52
        $values = [];
53
        foreach ($row as $key => $value) {
54
            // $link = $this->editLink($val);
55
            if ($value === null) {
56
                $value = '<i>NULL</i>';
57
            } elseif (isset($blobs[$key]) && $blobs[$key] && !$this->util->isUtf8($value)) {
58
                //! link to download
59
                $value = '<i>' . $this->trans->lang('%d byte(s)', \strlen($value)) . '</i>';
60
            } else {
61
                $value = $this->util->html($value);
62
                if (isset($types[$key]) && $types[$key] == 254) { // 254 - char
63
                    $value = "<code>$value</code>";
64
                }
65
            }
66
            $values[$key] = $value;
67
        }
68
        return $values;
69
    }
70
71
    /**
72
     * @param mixed $statement
73
     * @param int $limit
74
     *
75
     * @return string
76
    */
77
    private function message($statement, int $limit)
78
    {
79
        $numRows = $statement->rowCount();
80
        $message = '';
81
        if ($numRows > 0) {
82
            if ($limit > 0 && $numRows > $limit) {
83
                $message = $this->trans->lang('%d / ', $limit);
84
            }
85
            $message .= $this->trans->lang('%d row(s)', $numRows);
86
        }
87
        return $message;
88
    }
89
90
    /**
91
     * Print select result
92
     * From editing.inc.php
93
     *
94
     * @param mixed $statement
95
     * @param int $limit
96
     *
97
     * @return array
98
    */
99
    protected function select($statement, $limit = 0)
100
    {
101
        // No resultset
102
        if ($statement === true) {
103
            $affected = $this->driver->affectedRows();
104
            $message = $this->trans->lang('Query executed OK, %d row(s) affected.', $affected); //  . "$time";
105
            return [null, [$message]];
106
        }
107
        // Fetch the first row.
108
        if (!($row = $statement->fetchRow())) {
109
            // Empty resultset.
110
            $message = $this->trans->lang('No rows.');
111
            return [null, [$message]];
112
        }
113
114
        $blobs = []; // colno => bool - display bytes for blobs
115
        $types = []; // colno => type - display char in <code>
116
        $tables = []; // table => orgtable - mapping to use in EXPLAIN
117
        $headers = [];
118
        $details = [];
119
        // Table headers.
120
        $colCount = \count($row);
121
        for ($j = 0; $j < $colCount; $j++) {
122
            $field = $statement->fetchField();
123
            // PostgreSQL fix: the table field can be missing.
124
            $tables[$field->tableName()] = $field->orgTable();
125
            // $this->indexes($field);
126
            if ($field->isBinary()) {
127
                $blobs[$j] = true;
128
            }
129
            $types[$j] = $field->type(); // Some drivers don't set the type field.
130
            $headers[] = $this->util->html($field->name());
131
        }
132
133
        // Table rows (the first was already fetched).
134
        $rowCount = 0;
135
        do {
136
            $rowCount++;
137
            $details[] = $this->values($row, $blobs, $types);
138
        } while (($limit === 0 || $rowCount < $limit) && ($row = $statement->fetchRow()));
139
140
        $message = $this->message($statement, $limit);
141
        return [\compact('tables', 'headers', 'details'), [$message]];
142
    }
143
144
    /**
145
     * @param string $query       The query to execute
146
     * @param int    $limit         The max number of rows to return
147
     * @param bool   $errorStops    Stop executing the requests in case of error
148
     * @param bool   $onlyErrors    Return only errors
149
     *
150
     * @return bool
151
     */
152
    private function executeQuery(string $query, int $limit, bool $errorStops, bool $onlyErrors)
153
    {
154
        //! Don't allow changing of character_set_results, convert encoding of displayed query
155
        if ($this->driver->multiQuery($query) && ($connection = $this->connection()) !== null) {
156
            $connection->execUseQuery($query);
157
        }
158
159
        do {
160
            $select = null;
161
            $errors = [];
162
            $messages = [];
163
            $statement = $this->driver->storedResult();
164
165
            if ($this->driver->hasError()) {
166
                $errors[] = $this->driver->errorMessage();
167
            } elseif (!$onlyErrors) {
168
                [$select, $messages] = $this->select($statement, $limit);
169
            }
170
171
            $this->results[] = \compact('query', 'errors', 'messages', 'select');
172
            if ($this->driver->hasError() && $errorStops) {
173
                return false;
174
            }
175
            // $start = \microtime(true);
176
        } while ($this->driver->nextResult());
177
178
        return true;
179
    }
180
181
    /**
182
     * Execute a set of queries
183
     *
184
     * @param string $queries       The queries to execute
185
     * @param int    $limit         The max number of rows to return
186
     * @param bool   $errorStops    Stop executing the requests in case of error
187
     * @param bool   $onlyErrors    Return only errors
188
     *
189
     * @return array
190
     */
191
    public function executeCommands(string $queries, int $limit, bool $errorStops, bool $onlyErrors)
192
    {
193
        if (\function_exists('memory_get_usage')) {
194
            // @ - may be disabled, 2 - substr and trim, 8e6 - other variables
195
            try {
196
                \ini_set('memory_limit', \max($this->util->iniBytes('memory_limit'),
197
                    2 * \strlen($queries) + \memory_get_usage() + 8e6));
198
            }
199
            catch(\Exception $e) {
200
                // Do nothing if the option is not modified.
201
            }
202
        }
203
204
        // if($queries != '' && \strlen($queries) < 1e6) { // don't add big queries
205
        // 	$q = $queries . (\preg_match("~;[ \t\r\n]*\$~", $queries) ? '' : ';'); //! doesn't work with DELIMITER |
206
        // 	if(!$history || \reset(\end($history)) != $q) { // no repeated queries
207
        // 		\restart_session();
208
        // 		$history[] = [$q, \time()]; //! add elapsed time
209
        // 		\set_session('queries', $history_all); // required because reference is unlinked by stop_session()
210
        // 		\stop_session();
211
        // 	}
212
        // }
213
214
        // $timestamps = [];
215
        // $total_start = \microtime(true);
216
        // \parse_str($_COOKIE['adminer_export'], $adminer_export);
217
        // $dump_format = $this->util->dumpFormat();
218
        // unset($dump_format['sql']);
219
220
        $this->results = [];
221
        $commands = 0;
222
        $errors = 0;
223
        $queryEntity = new QueryEntity($queries);
224
        while ($this->driver->parseQueries($queryEntity)) {
225
            $commands++;
226
            if (!$this->executeQuery($queryEntity->query, $limit, $errorStops, $onlyErrors)) {
227
                $errors++;
228
                if ($errorStops) {
229
                    break;
230
                }
231
            }
232
        }
233
234
        $messages = [];
235
        if ($commands === 0) {
236
            $messages[] = $this->trans->lang('No commands to execute.');
237
        } elseif ($onlyErrors) {
238
            $messages[] =  $this->trans->lang('%d query(s) executed OK.', $commands - $errors);
239
        }
240
241
        return ['results' => $this->results, 'messages' => $messages];
242
    }
243
}
244