Issues (2354)

src/Statement.php (50 issues)

1
<?php
2
3
namespace ClickHouseDB;
4
5
use ClickHouseDB\Exception\ClickHouseUnavailableException;
6
use ClickHouseDB\Exception\DatabaseException;
7
use ClickHouseDB\Exception\QueryException;
8
use ClickHouseDB\Query\Query;
9
use ClickHouseDB\Transport\CurlerRequest;
10
use ClickHouseDB\Transport\CurlerResponse;
11
12
class Statement implements \Iterator
13
{
14
    private const CLICKHOUSE_ERROR_REGEX = "%Code:\s(\d+)\.\s*DB::Exception\s*:\s*(.*)(?:,\s*e\.what|\(version).*%ius";
15
16
    /**
0 ignored issues
show
Found multi-line comment for property \ClickHouseDB\Statement::$_rawData with single line content, use one-line comment instead.
Loading history...
17
     * @var string|mixed
18
     */
19
    private $_rawData;
20
21
    /**
0 ignored issues
show
Found multi-line comment for property \ClickHouseDB\Statement::$_http_code with single line content, use one-line comment instead.
Loading history...
22
     * @var int
23
     */
24
    private $_http_code = -1;
0 ignored issues
show
Member variable "_http_code" is not in valid camel caps format
Loading history...
25
26
    /**
0 ignored issues
show
Found multi-line comment for property \ClickHouseDB\Statement::$_request with single line content, use one-line comment instead.
Loading history...
27
     * @var CurlerRequest
28
     */
29
    private $_request = null;
30
31
    /**
0 ignored issues
show
Found multi-line comment for property \ClickHouseDB\Statement::$_init with single line content, use one-line comment instead.
Loading history...
32
     * @var bool
33
     */
34
    private $_init = false;
35
36
    /**
37
     * @var Query
38
     */
39
    private $query;
40
41
    /**
42
     * @var mixed
43
     */
44
    private $format;
45
46
    /**
47
     * @var string
48
     */
49
    private $sql = '';
50
51
    /**
52
     * @var array
53
     */
54
    private $meta;
55
56
    /**
57
     * @var array
58
     */
59
    private $totals;
60
61
    /**
62
     * @var array
63
     */
64
    private $extremes;
65
66
    /**
67
     * @var int
68
     */
69
    private $rows;
70
71
    /**
72
     * @var bool|integer
73
     */
74
    private $rows_before_limit_at_least = false;
0 ignored issues
show
Member variable "rows_before_limit_at_least" is not in valid camel caps format
Loading history...
75
76
    /**
77
     * @var array
78
     */
79
    private $array_data = [];
0 ignored issues
show
Member variable "array_data" is not in valid camel caps format
Loading history...
80
81
    /**
82
     * @var array|null
83
     */
84
    private $statistics = null;
85
86
    /**
87
     * @var int
88
     */
89
    public $iterator = 0;
90 45
91
92 45
    public function __construct(CurlerRequest $request)
93 45
    {
94 45
        $this->_request = $request;
95 45
        $this->format = $this->_request->getRequestExtendedInfo('format');
96 45
        $this->query = $this->_request->getRequestExtendedInfo('query');
0 ignored issues
show
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
97
        $this->sql = $this->_request->getRequestExtendedInfo('sql');
0 ignored issues
show
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
98
    }
99
100
    /**
101
     * @return CurlerRequest
102
     */
103
    public function getRequest()
104
    {
105
        return $this->_request;
106
    }
107
108
    /**
109
     * @return CurlerResponse
110 39
     * @throws Exception\TransportException
111
     */
112 39
    private function response()
113
    {
114
        return $this->_request->response();
115
    }
116
117
    /**
118
     * @return mixed
119 1
     * @throws Exception\TransportException
120
     */
121 1
    public function responseInfo()
122
    {
123
        return $this->response()->info();
124
    }
125
126
    /**
127 10
     * @return mixed|string
128
     */
129 10
    public function sql()
130
    {
131
        return $this->sql;
132
    }
133
134
    /**
135
     * @param string $body
136 5
     * @return array|bool
0 ignored issues
show
@return annotation of method \ClickHouseDB\Statement::parseErrorClickHouse() does not specify type hint for items of its traversable return value.
Loading history...
137
     */
138 5
    private function parseErrorClickHouse(string $body)
139 5
    {
140
        $body = trim($body);
0 ignored issues
show
Function trim() should not be referenced via a fallback global name, but via a use statement.
Loading history...
141
        $matches = [];
142
143
        // Code: 115. DB::Exception: Unknown setting readonly[0], e.what() = DB::Exception
144
        // Code: 192. DB::Exception: Unknown user x, e.what() = DB::Exception
145 5
        // Code: 60. DB::Exception: Table default.ZZZZZ doesn't exist., e.what() = DB::Exception
146 4
        // Code: 516. DB::Exception: test_username: Authentication failed: password is incorrect or there is no user with such name. (AUTHENTICATION_FAILED) (version 22.8.3.13 (official build))
147
148
        if (preg_match(self::CLICKHOUSE_ERROR_REGEX, $body, $matches)) {
0 ignored issues
show
Function preg_match() should not be referenced via a fallback global name, but via a use statement.
Loading history...
149 1
            return ['code' => $matches[1], 'message' => $matches[2]];
150
        }
151
152
        return false;
153
    }
154
155
    private function hasErrorClickhouse(string $body): bool {
156 8
157
        return preg_match(self::CLICKHOUSE_ERROR_REGEX, $body) === 1;
158 8
    }
159 2
160
    /**
161
     * @return bool
0 ignored issues
show
Incorrect annotations group.
Loading history...
162 6
     * @throws Exception\TransportException
163 6
     */
164 6
    public function error()
165
    {
166 6
        if (!$this->isError()) {
0 ignored issues
show
Expected 1 space after NOT operator; 0 found
Loading history...
167 6
            return false;
168 5
        }
169
170 5
        $body = $this->response()->body();
0 ignored issues
show
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
171 4
        $error_no = $this->response()->error_no();
0 ignored issues
show
The variable $error_no should be in camel caps format.
Loading history...
172
        $error = $this->response()->error();
0 ignored issues
show
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
173 1
174 1
        $dumpStatement = false;
175 1
        if (!$error_no && !$error) {
176
            $parse = $this->parseErrorClickHouse($body);
177
178 1
            if ($parse) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parse of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
$parse is a non-empty array, thus is always true.
Loading history...
179 1
                throw new DatabaseException($parse['message'] . "\nIN:" . $this->sql(), $parse['code']);
180
            } else {
181
                $code = $this->response()->http_code();
0 ignored issues
show
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
182 2
                $message = "HttpCode:" . $this->response()->http_code() . " ; " . $this->response()->error() . " ;" . $body;
183 2
                $dumpStatement = true;
184
            }
185
        } else {
186
            $code = $error_no;
0 ignored issues
show
The variable $error_no should be in camel caps format.
Loading history...
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
187 2
            $message = $this->response()->error();
188 1
        }
189 1
190
        $exception = new QueryException($message, $code);
191
        if ($code === CURLE_COULDNT_CONNECT) {
192 2
            $exception = new ClickHouseUnavailableException($message, $code);
193
        }
194
195
        if ($dumpStatement) {
196
            $exception->setRequestDetails($this->_request->getDetails());
197
            $exception->setResponseDetails($this->response()->getDetails());
198
        }
199 39
200
        throw $exception;
201 39
    }
202
203
    /**
204 32
     * @return bool
0 ignored issues
show
Method \ClickHouseDB\Statement::isError() has useless @return annotation.
Loading history...
205
     * @throws Exception\TransportException
206 32
     */
207
    public function isError(): bool
208
    {
209
        if ($this->response()->http_code() !== 200) {
210 32
            return true;
211 1
        }
212
213
        if ($this->response()->error_no()) {
214 31
            return true;
215
        }
216
217
        if ($this->hasErrorClickhouse($this->response()->body())) {
218
            return true;
219
        }
220
221 30
        return false;
222
    }
223 30
224
    private function check(): bool
225
    {
226
        if (!$this->_request->isResponseExists()) {
0 ignored issues
show
Expected 1 space after NOT operator; 0 found
Loading history...
227
            throw QueryException::noResponse();
228 30
        }
229
230
        if ($this->isError()) {
231 29
            $this->error();
232
        }
233 29
234
        return true;
235
    }
236
237 29
    /**
238 29
     * @return bool
239
     */
240 29
    public function isInited()
241 29
    {
242
        return $this->_init;
243 29
    }
244
245
    /**
246 29
     * @return bool
0 ignored issues
show
Incorrect annotations group.
Loading history...
247
     * @throws Exception\TransportException
248
     */
249
    private function init()
250
    {
251
        if ($this->_init) {
252 29
            return false;
253
        }
254
255
        $this->check();
256 29
257 29
        $this->_rawData = $this->response()->rawDataOrJson($this->format);
258 29
259 29
        if (!$this->_rawData) {
0 ignored issues
show
Expected 1 space after NOT operator; 0 found
Loading history...
260
            $this->_init = true;
261
            return false;
262 29
        }
263
264 1
        $data = [];
265
        foreach (['meta', 'data', 'totals', 'extremes', 'rows', 'rows_before_limit_at_least', 'statistics'] as $key) {
266
267 28
            if (isset($this->_rawData[$key])) {
0 ignored issues
show
Use early exit to reduce code nesting.
Loading history...
268 28
                if ($key=='data') {
269
                    $data=$this->_rawData[$key];
270
                } else {
271
                    $this->{$key} = $this->_rawData[$key];
272 29
                }
273
            }
274
        }
275
276 29
        if (empty($this->meta)) {
277
            throw new QueryException('Can`t find meta');
278
        }
279
280
        $isJSONCompact=(stripos($this->format,'JSONCompact')!==false?true:false);
281
        $this->array_data = [];
0 ignored issues
show
Member variable "array_data" is not in valid camel caps format
Loading history...
282
        foreach ($data as $rows) {
283
            $r = [];
284
285
            if ($isJSONCompact) {
286
                $r[] = $rows;
287
            } else {
288
                foreach ($this->meta as $meta) {
289
                    $r[$meta['name']] = $rows[$meta['name']];
290
                }
291
            }
292
293 1
            $this->array_data[] = $r;
0 ignored issues
show
Member variable "array_data" is not in valid camel caps format
Loading history...
294
        }
295 1
296 1
        $this->_init = true;
297
298
        return true;
299
    }
300
301
    /**
302
     * @return array
303
     * @throws \Exception
304 1
     */
305
    public function extremes()
306 1
    {
307
        $this->init();
308 1
        return $this->extremes;
309
    }
310
311
    /**
312 1
     * @return mixed
313
     * @throws Exception\TransportException
314
     */
315
    public function totalTimeRequest()
316
    {
317
        $this->check();
318
        return $this->response()->total_time();
319
320
    }
321
322
    /**
323
     * @return array
324
     * @throws \Exception
325
     */
326
    public function extremesMin()
327
    {
328
        $this->init();
329
330
        if (empty($this->extremes['min'])) {
331
            return [];
332
        }
333
334 1
        return $this->extremes['min'];
335
    }
336 1
337 1
    /**
338
     * @return array
339
     * @throws \Exception
340
     */
341
    public function extremesMax()
342
    {
343
        $this->init();
344
345
        if (empty($this->extremes['max'])) {
346
            return [];
347
        }
348
349
        return $this->extremes['max'];
350
    }
351
352
    /**
353
     * @return array
354
     * @throws Exception\TransportException
355
     */
356
    public function totals()
357
    {
358
        $this->init();
359
        return $this->totals;
360
    }
361 2
362
    /**
0 ignored issues
show
Empty comment
Loading history...
363 2
     *
364 2
     */
365
    public function dump()
366
    {
367
        $this->_request->dump();
368
        $this->response()->dump();
369
    }
370
371
    /**
372
     * @return bool|int
373
     * @throws Exception\TransportException
374
     */
375
    public function countAll()
376
    {
377
        $this->init();
378
        return $this->rows_before_limit_at_least;
379
    }
380
381
    /**
382
     * @param bool $key
383
     * @return array|mixed|null
384
     * @throws Exception\TransportException
385
     */
386
    public function statistics($key = false)
387
    {
388
        $this->init();
389
390
        if (!is_array($this->statistics)) {
391
            return null;
392
        }
393
394 8
        if (!$key) return $this->statistics;
395
396 8
        if (!isset($this->statistics[$key])) {
397 8
            return null;
398
        }
399
400
        return $this->statistics[$key];
401
402
    }
403
404 3
    /**
405
     * @return int
0 ignored issues
show
Incorrect annotations group.
Loading history...
406 3
     * @throws Exception\TransportException
407
     */
408
    public function count()
409
    {
410 3
        $this->init();
411
        return $this->rows;
412 3
    }
413
414
    /**
415
     * @return mixed|string
416
     * @throws Exception\TransportException
417
     */
418 2
    public function rawData()
419
    {
420 2
        if ($this->_init) {
421 2
            return $this->_rawData;
422
        }
423 2
424
        $this->check();
425 2
426
        return $this->response()->rawDataOrJson($this->format);
427 2
    }
428
429 2
    /**
0 ignored issues
show
Empty comment
Loading history...
430
     *
431
     */
432
    public function resetIterator()
433 2
    {
434
        $this->iterator=0;
435 2
    }
436
437
    public function fetchRow($key = null)
438 2
    {
439 1
        $this->init();
440
441
        $position=$this->iterator;
442 2
443
        if (!isset($this->array_data[$position])) {
444
            return null;
445
        }
446
447
        $this->iterator++;
448
449
        if (!$key) {
0 ignored issues
show
Expected 1 lines after "if", found 0.
Loading history...
450 18
            return $this->array_data[$position];
451
        }
452 18
        if (!isset($this->array_data[$position][$key])) {
453 18
            return null;
454
        }
455
456
        return $this->array_data[$position][$key];
457 18
458 3
    }
459
    /**
460
     * @param string $key
461 18
     * @return mixed|null
462 2
     * @throws Exception\TransportException
463
     */
464
    public function fetchOne($key = null)
465 17
    {
466
        $this->init();
467
        if (!isset($this->array_data[0])) {
468
            return null;
469
        }
470
471
        if (!$key) {
472
            return $this->array_data[0];
473 4
        }
474
475 4
        if (!isset($this->array_data[0][$key])) {
476
            return null;
477 4
        }
478 4
479 4
        return $this->array_data[0][$key];
480 4
    }
481
482
    /**
483 4
     * @param string|null $path
484
     * @return array
0 ignored issues
show
@return annotation of method \ClickHouseDB\Statement::rowsAsTree() does not specify type hint for items of its traversable return value.
Loading history...
485
     * @throws Exception\TransportException
486
     */
487
    public function rowsAsTree($path)
488
    {
489
        $this->init();
490
491
        $out = [];
492
        foreach ($this->array_data as $row) {
0 ignored issues
show
Member variable "array_data" is not in valid camel caps format
Loading history...
493
            $d = $this->array_to_tree($row, $path);
0 ignored issues
show
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
494
            $out = array_replace_recursive($d, $out);
0 ignored issues
show
Function array_replace_recursive() should not be referenced via a fallback global name, but via a use statement.
Loading history...
495
        }
496
497
        return $out;
498
    }
499
500
    /**
501
     * Return size_upload,upload_content,speed_upload,time_request
502
     *
503
     * @return array
0 ignored issues
show
Incorrect annotations group.
Loading history...
@return annotation of method \ClickHouseDB\Statement::info_upload() does not specify type hint for items of its traversable return value.
Loading history...
504
     * @throws Exception\TransportException
505
     */
506
    public function info_upload()
507
    {
508
        $this->check();
509 1
        return [
510
            'size_upload'    => $this->response()->size_upload(),
511 1
            'upload_content' => $this->response()->upload_content_length(),
512
            'speed_upload'   => $this->response()->speed_upload(),
513 1
            'time_request'   => $this->response()->total_time()
514 1
        ];
515 1
    }
516 1
517 1
    /**
518 1
     * Return size_upload,upload_content,speed_upload,time_request,starttransfer_time,size_download,speed_download
519 1
     *
520
     * @return array
521
     * @throws Exception\TransportException
522
     */
523
    public function info()
524
    {
525
        $this->check();
526
        return [
527 1
            'starttransfer_time'    => $this->response()->starttransfer_time(),
528
            'size_download'    => $this->response()->size_download(),
529 1
            'speed_download'    => $this->response()->speed_download(),
530
            'size_upload'    => $this->response()->size_upload(),
531
            'upload_content' => $this->response()->upload_content_length(),
532
            'speed_upload'   => $this->response()->speed_upload(),
533
            'time_request'   => $this->response()->total_time()
534
        ];
535
    }
536 9
537
    /**
538 9
     * get format in sql
539 8
     * @return mixed
540
     */
541
    public function getFormat()
542
    {
543
        return $this->format;
544
    }
545
546
    /**
547 4
     * @return array
0 ignored issues
show
Incorrect annotations group.
Loading history...
@return annotation of method \ClickHouseDB\Statement::rows() does not specify type hint for items of its traversable return value.
Loading history...
548
     * @throws Exception\TransportException
549 4
     */
550
    public function rows()
551
    {
552 4
        $this->init();
553 4
        return $this->array_data;
554
    }
555 4
556 4
    /**
557 4
     * @return false|string
558
    */
559
    public function jsonRows()
560
    {
561
        return json_encode($this->rows(), JSON_PRETTY_PRINT);
562
    }
563
564 4
    /**
565 4
     * @param array|string $arr
566 4
     * @param null|string|array $path
567
     * @return array
0 ignored issues
show
@return annotation of method \ClickHouseDB\Statement::array_to_tree() does not specify type hint for items of its traversable return value.
Loading history...
568 4
     */
569 4
    private function array_to_tree($arr, $path = null)
0 ignored issues
show
Method name "Statement::array_to_tree" is not in camel caps format
Loading history...
Method \ClickHouseDB\Statement::array_to_tree() does not have native return type hint for its return value but it should be possible to add it based on @return annotation "array".
Loading history...
570
    {
571
        if (is_array($path)) {
0 ignored issues
show
Function is_array() should not be referenced via a fallback global name, but via a use statement.
Loading history...
572
            $keys = $path;
573
        } else {
574 4
            $args = func_get_args();
0 ignored issues
show
Function func_get_args() should not be referenced via a fallback global name, but via a use statement.
Loading history...
575
            array_shift($args);
576 4
577
            if (sizeof($args) < 2) {
0 ignored issues
show
Function sizeof() should not be referenced via a fallback global name, but via a use statement.
Loading history...
The use of function sizeof() is forbidden; use count() instead
Loading history...
578
                $separator = '.';
579 4
                $keys = explode($separator, $path);
580
            } else {
581
                $keys = $args;
582
            }
583
        }
584
585
        //
586
        $tree = $arr;
587
        while (count($keys)) {
588
            $key = array_pop($keys);
0 ignored issues
show
Function array_pop() should not be referenced via a fallback global name, but via a use statement.
Loading history...
589
590
            if (isset($arr[$key])) {
591
                $val = $arr[$key];
592
            } else {
593
                $val = $key;
594
            }
595
596
            $tree = array($val => $tree);
0 ignored issues
show
Short array syntax must be used to define arrays
Loading history...
597
        }
598
        if (!is_array($tree)) {
599
            return [];
600
        }
601
        return $tree;
602
    }
603
604
605
    public function rewind(): void {
606
        $this->iterator = 0;
607
    }
608
609
    /**
610
     * @return mixed
611
     */
612
    #[\ReturnTypeWillChange]
613
    public function current() {
614
        if (!isset($this->array_data[$this->iterator])) {
615
            return null;
616
        }
617
        return $this->array_data[$this->iterator];
618
    }
619
620
    public function key(): int {
621
        return $this->iterator;
622
    }
623
624
    public function next(): void {
625
        ++$this->iterator;
626
    }
627
628
    public function valid(): bool {
629
        $this->init();
630
        return isset($this->array_data[$this->iterator]);
631
    }
632
}
633