Passed
Push — master ( 7b4f81...84e0ca )
by Igor
03:22
created

Statement::array_to_tree()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.3357

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 15
cts 19
cp 0.7895
rs 8.7697
c 0
b 0
f 0
cc 6
nc 18
nop 2
crap 6.3357
1
<?php
2
3
namespace ClickHouseDB;
4
5
use ClickHouseDB\Exception\DatabaseException;
6
use ClickHouseDB\Exception\QueryException;
7
use ClickHouseDB\Query\Query;
8
use ClickHouseDB\Transport\CurlerRequest;
9
use ClickHouseDB\Transport\CurlerResponse;
10
11
class Statement
12
{
13
    /**
14
     * @var string|mixed
15
     */
16
    private $_rawData;
17
18
    /**
19
     * @var int
20
     */
21
    private $_http_code = -1;
22
23
    /**
24
     * @var CurlerRequest
25
     */
26
    private $_request = null;
27
28
    /**
29
     * @var bool
30
     */
31
    private $_init = false;
32
33
    /**
34
     * @var Query
35
     */
36
    private $query;
37
38
    /**
39
     * @var mixed
40
     */
41
    private $format;
42
43
    /**
44
     * @var string
45
     */
46
    private $sql = '';
47
48
    /**
49
     * @var array
50
     */
51
    private $meta;
52
53
    /**
54
     * @var array
55
     */
56
    private $totals;
57
58
    /**
59
     * @var array
60
     */
61
    private $extremes;
62
63
    /**
64
     * @var int
65
     */
66
    private $rows;
67
68
    /**
69
     * @var bool|integer
70
     */
71
    private $rows_before_limit_at_least = false;
72
73
    /**
74
     * @var array
75
     */
76
    private $array_data = [];
77
78
    /**
79
     * @var array|null
80
     */
81
    private $statistics = null;
82
83
84 41
    public function __construct(CurlerRequest $request)
85
    {
86 41
        $this->_request = $request;
87 41
        $this->format = $this->_request->getRequestExtendedInfo('format');
88 41
        $this->query = $this->_request->getRequestExtendedInfo('query');
89 41
        $this->sql = $this->_request->getRequestExtendedInfo('sql');
90 41
    }
91
92
    /**
93
     * @return CurlerRequest
94
     */
95
    public function getRequest()
96
    {
97
        return $this->_request;
98
    }
99
100
    /**
101
     * @return CurlerResponse
102
     * @throws Exception\TransportException
103
     */
104 40
    private function response()
105
    {
106 40
        return $this->_request->response();
107
    }
108
109
    /**
110
     * @return mixed
111
     * @throws Exception\TransportException
112
     */
113 1
    public function responseInfo()
114
    {
115 1
        return $this->response()->info();
116
    }
117
118
    /**
119
     * @return mixed|string
120
     */
121 9
    public function sql()
122
    {
123 9
        return $this->sql;
124
    }
125
126
    /**
127
     * @param string $body
128
     * @return array|bool
129
     */
130 5
    private function parseErrorClickHouse($body)
131
    {
132 5
        $body = trim($body);
133 5
        $mathes = [];
134
135
        // Code: 115, e.displayText() = DB::Exception: Unknown setting readonly[0], e.what() = DB::Exception
136
        // Code: 192, e.displayText() = DB::Exception: Unknown user x, e.what() = DB::Exception
137
        // Code: 60, e.displayText() = DB::Exception: Table default.ZZZZZ doesn't exist., e.what() = DB::Exception
138
139 5
        if (preg_match("%Code: (\d+),\se\.displayText\(\) \=\s*DB\:\:Exception\s*:\s*(.*)\,\s*e\.what.*%ius", $body, $mathes)) {
140 5
            return ['code' => $mathes[1], 'message' => $mathes[2]];
141
        }
142
143
        return false;
144
    }
145
146
    /**
147
     * @return bool
148
     * @throws Exception\TransportException
149
     */
150 10
    public function error()
151
    {
152 10
        if (!$this->isError()) {
153 2
            return false;
154
        }
155
156 8
        $body = $this->response()->body();
157 8
        $error_no = $this->response()->error_no();
158 8
        $error = $this->response()->error();
159
160 8
        if (!$error_no && !$error) {
161 5
            $parse = $this->parseErrorClickHouse($body);
162
163 5
            if ($parse) {
164 5
                throw new DatabaseException($parse['message'] . "\nIN:" . $this->sql(), $parse['code']);
165
            } else {
166
                $code = $this->response()->http_code();
167
                $message = "HttpCode:" . $this->response()->http_code() . " ; " . $this->response()->error() . " ;" . $body;
168
            }
169
        } else {
170 3
            $code = $error_no;
171 3
            $message = $this->response()->error();
172
        }
173
174 3
        throw new QueryException($message, $code);
175
    }
176
177
    /**
178
     * @return bool
179
     * @throws Exception\TransportException
180
     */
181 40
    public function isError()
182
    {
183 40
        return ($this->response()->http_code() !== 200 || $this->response()->error_no());
184
    }
185
186
    /**
187
     * @return bool
188
     * @throws Exception\TransportException
189
     */
190 39
    private function check()
191
    {
192 39
        if (!$this->_request->isResponseExists()) {
193
            throw new QueryException('Not have response');
194
        }
195
196 39
        if ($this->isError()) {
197 3
            $this->error();
198
        }
199
200 39
        return true;
201
    }
202
203
    /**
204
     * @return bool
205
     * @throws Exception\TransportException
206
     */
207 39
    private function init()
208
    {
209 39
        if ($this->_init) {
210
            return false;
211
        }
212
213 39
        $this->check();
214
215
216 39
        $this->_rawData = $this->response()->rawDataOrJson($this->format);
217
218 39
        if (!$this->_rawData) {
219
            $this->_init = true;
220
            return false;
221
        }
222 39
        $data=[];
223 39
        foreach (['meta', 'data', 'totals', 'extremes', 'rows', 'rows_before_limit_at_least', 'statistics'] as $key) {
224
225 39
            if (isset($this->_rawData[$key])) {
226 39
                if ($key=='data')
227
                {
228 39
                    $data=$this->_rawData[$key];
229
                }
230
                else{
231 39
                    $this->{$key} = $this->_rawData[$key];
232
                }
233
234
            }
235
        }
236
237 39
        if (empty($this->meta)) {
238
            throw  new QueryException('Can`t find meta');
239
        }
240
241 39
        $isJSONCompact=(stripos($this->format,'JSONCompact')!==false?true:false);
242 39
        $this->array_data = [];
243 39
        foreach ($data as $rows) {
244 39
            $r = [];
245
246
247 39
            if ($isJSONCompact)
248
            {
249 1
                $r[]=$rows;
250
            }
251
            else {
252 38
                foreach ($this->meta as $meta) {
253 38
                    $r[$meta['name']] = $rows[$meta['name']];
254
                }
255
            }
256
257 39
            $this->array_data[] = $r;
258
        }
259
260 39
        return true;
261
    }
262
263
    /**
264
     * @return array
265
     * @throws \Exception
266
     */
267
    public function extremes()
268
    {
269
        $this->init();
270
        return $this->extremes;
271
    }
272
273
    /**
274
     * @return mixed
275
     * @throws Exception\TransportException
276
     */
277 1
    public function totalTimeRequest()
278
    {
279 1
        $this->check();
280 1
        return $this->response()->total_time();
281
282
    }
283
284
    /**
285
     * @return array
286
     * @throws \Exception
287
     */
288 1
    public function extremesMin()
289
    {
290 1
        $this->init();
291
292 1
        if (empty($this->extremes['min'])) {
293
            return [];
294
        }
295
296 1
        return $this->extremes['min'];
297
    }
298
299
    /**
300
     * @return array
301
     * @throws \Exception
302
     */
303
    public function extremesMax()
304
    {
305
        $this->init();
306
307
        if (empty($this->extremes['max'])) {
308
            return [];
309
        }
310
311
        return $this->extremes['max'];
312
    }
313
314
    /**
315
     * @return array
316
     * @throws Exception\TransportException
317
     */
318 1
    public function totals()
319
    {
320 1
        $this->init();
321 1
        return $this->totals;
322
    }
323
324
    /**
325
     *
326
     */
327
    public function dumpRaw()
328
    {
329
        print_r($this->_rawData);
330
    }
331
332
    /**
333
     *
334
     */
335
    public function dump()
336
    {
337
        $this->_request->dump();
338
        $this->response()->dump();
339
    }
340
341
    /**
342
     * @return bool|int
343
     * @throws Exception\TransportException
344
     */
345 2
    public function countAll()
346
    {
347 2
        $this->init();
348 2
        return $this->rows_before_limit_at_least;
349
    }
350
351
    /**
352
     * @param bool $key
353
     * @return array|mixed|null
354
     * @throws Exception\TransportException
355
     */
356
    public function statistics($key = false)
357
    {
358
        $this->init();
359
        if ($key)
360
        {
361
            if (!is_array($this->statistics)) {
362
                return null;
363
            }
364
            if (!isset($this->statistics[$key])) {
365
                return null;
366
            }
367
            return $this->statistics[$key];
368
        }
369
        return $this->statistics;
370
    }
371
372
    /**
373
     * @return int
374
     * @throws Exception\TransportException
375
     */
376 6
    public function count()
377
    {
378 6
        $this->init();
379 6
        return $this->rows;
380
    }
381
382
    /**
383
     * @return mixed|string
384
     * @throws Exception\TransportException
385
     */
386 3
    public function rawData()
387
    {
388 3
        if ($this->_init) {
389
            return $this->_rawData;
390
        }
391
392 3
        $this->check();
393
394 3
        return $this->response()->rawDataOrJson($this->format);
395
    }
396
397
    /**
398
     * @param string $key
399
     * @return mixed|null
400
     * @throws Exception\TransportException
401
     */
402 36
    public function fetchOne($key = '')
403
    {
404 36
        $this->init();
405
406 36
        if (isset($this->array_data[0])) {
407 36
            if ($key) {
408 36
                if (isset($this->array_data[0][$key])) {
409 36
                    return $this->array_data[0][$key];
410
                } else {
411
                    return null;
412
                }
413
            }
414
415 2
            return $this->array_data[0];
416
        }
417
418
        return null;
419
    }
420
421
    /**
422
     * @param string|null $path
423
     * @return array
424
     * @throws Exception\TransportException
425
     */
426 4
    public function rowsAsTree($path)
427
    {
428 4
        $this->init();
429
430 4
        $out = [];
431 4
        foreach ($this->array_data as $row) {
432 4
            $d = $this->array_to_tree($row, $path);
433 4
            $out = array_replace_recursive($d, $out);
434
        }
435
436 4
        return $out;
437
    }
438
439
    /**
440
     * Return size_upload,upload_content,speed_upload,time_request
441
     *
442
     * @return array
443
     * @throws Exception\TransportException
444
     */
445
    public function info_upload()
446
    {
447
        $this->check();
448
        return [
449
            'size_upload'    => $this->response()->size_upload(),
450
            'upload_content' => $this->response()->upload_content_length(),
451
            'speed_upload'   => $this->response()->speed_upload(),
452
            'time_request'   => $this->response()->total_time()
453
        ];
454
    }
455
456
    /**
457
     * Return size_upload,upload_content,speed_upload,time_request,starttransfer_time,size_download,speed_download
458
     *
459
     * @return array
460
     * @throws Exception\TransportException
461
     */
462 1
    public function info()
463
    {
464 1
        $this->check();
465
        return [
466 1
            'starttransfer_time'    => $this->response()->starttransfer_time(),
467 1
            'size_download'    => $this->response()->size_download(),
468 1
            'speed_download'    => $this->response()->speed_download(),
469 1
            'size_upload'    => $this->response()->size_upload(),
470 1
            'upload_content' => $this->response()->upload_content_length(),
471 1
            'speed_upload'   => $this->response()->speed_upload(),
472 1
            'time_request'   => $this->response()->total_time()
473
        ];
474
    }
475
476
    /**
477
     * get format in sql
478
     * @return mixed
479
     */
480 1
    public function getFormat()
481
    {
482 1
        return $this->format;
483
    }
484
485
    /**
486
     * @return array
487
     * @throws Exception\TransportException
488
     */
489 5
    public function rows()
490
    {
491 5
        $this->init();
492 4
        return $this->array_data;
493
    }
494
495
    /**
496
     * @param array|string $arr
497
     * @param null|string|array $path
498
     * @return array
499
     */
500 4
    private function array_to_tree($arr, $path = null)
501
    {
502 4
        if (is_array($path)) {
503
            $keys = $path;
504
        } else {
505 4
            $args = func_get_args();
506 4
            array_shift($args);
507
508 4
            if (sizeof($args) < 2) {
509 4
                $separator = '.';
510 4
                $keys = explode($separator, $path);
511
            } else {
512
                $keys = $args;
513
            }
514
        }
515
516
        //
517 4
        $tree = $arr;
518 4
        while (count($keys)) {
519 4
            $key = array_pop($keys);
520
521 4
            if (isset($arr[$key])) {
522 4
                $val = $arr[$key];
523
            } else {
524
                $val = $key;
525
            }
526
527 4
            $tree = array($val => $tree);
528
        }
529 4
        if (!is_array($tree)) {
530
            return [];
531
        }
532 4
        return $tree;
533
    }
534
}
535