ADOdbBase::getLastExecutedSQL()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 10
1
<?php
2
3
/**
4
 * PHPPgAdmin 6.1.3
5
 */
6
7
namespace PHPPgAdmin\Database;
8
9
/**
10
 * @file
11
 * Parent class of all ADODB objects.
12
 *
13
 * Id: ADOdbBase.php,v 1.24 2008/02/20 20:43:10 ioguix Exp $
14
 */
15
class ADOdbBase
16
{
17
    use \PHPPgAdmin\Traits\HelperTrait;
18
    use \PHPPgAdmin\Database\Traits\HasTrait;
19
    use \PHPPgAdmin\Database\Traits\DatabaseTrait;
20
21
    /**
22
     * @var array
23
     */
24
    public $lang;
25
26
    /**
27
     * @var array
28
     */
29
    public $conf;
30
31
    /**
32
     * @var \ADODB_postgres9
33
     */
34
    public $conn;
35
36
    /**
37
     * @var \PHPPgAdmin\ContainerUtils
38
     */
39
    protected $container;
40
41
    /**
42
     * @var array
43
     */
44
    protected $server_info;
45
46
    /**
47
     * @var string
48
     */
49
    protected $lastExecutedSql;
50
51
    /**
52
     * Base constructor.
53
     *
54
     * @param \ADODB_postgres9 $conn        The connection object
55
     * @param mixed            $container
56
     * @param mixed            $server_info
57
     */
58
    public function __construct(&$conn, $container, $server_info)
59
    {
60
        $this->container = $container;
61
        $this->server_info = $server_info;
62
63
        $this->lang = $container->get('lang');
64
        $this->conf = $container->get('conf');
65
66
        $this->prtrace('instanced connection class');
67
        $this->lastExecutedSql = '';
68
        $this->conn = $conn;
69
    }
70
71
    /**
72
     * Given an array of attnums and a relation, returns an array mapping
73
     * attribute number to attribute name.
74
     *
75
     * @param string $table The table to get attributes for
76
     * @param array  $atts  An array of attribute numbers
77
     *
78
     * @return array|int An array mapping attnum to attname or error code
79
     *                   - -1 $atts must be an array
80
     *                   - -2 wrong number of attributes found
81
     */
82
    public function getAttributeNames($table, $atts)
83
    {
84
        $c_schema = $this->_schema;
85
        $this->clean($c_schema);
86
        $this->clean($table);
87
        $this->arrayClean($atts);
88
89
        if (!\is_array($atts)) {
90
            return -1;
91
        }
92
93
        if (0 === \count($atts)) {
94
            return [];
95
        }
96
97
        $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
98
			attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
99
			relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
100
			AND attnum IN ('" . \implode("','", $atts) . "')";
101
102
        $rs = $this->selectSet($sql);
103
104
        if ($rs->recordCount() !== \count($atts)) {
105
            return -2;
106
        }
107
108
        $temp = [];
109
110
        while (!$rs->EOF) {
111
            $temp[$rs->fields['attnum']] = $rs->fields['attname'];
112
            $rs->moveNext();
113
        }
114
115
        return $temp;
116
    }
117
118
    /**
119
     * Sets the comment for an object in the database.
120
     *
121
     * @pre All parameters must already be cleaned
122
     *
123
     * @param string      $obj_type One of 'TABLE' | 'COLUMN' | 'VIEW' | 'SCHEMA' | 'SEQUENCE' | 'TYPE' | 'FUNCTION' | 'AGGREGATE'
124
     * @param string      $obj_name the name of the object for which to attach a comment
125
     * @param string      $table    Name of table that $obj_name belongs to.  Ignored unless $obj_type is 'TABLE' or 'COLUMN'.
126
     * @param string      $comment  the comment to add
127
     * @param null|string $basetype
128
     *
129
     * @return int|\PHPPgAdmin\ADORecordSet recordset of results or error code
130
     */
131
    public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null)
132
    {
133
        $sql = "COMMENT ON {$obj_type} ";
134
135
        $f_schema = $this->_schema;
136
        $this->fieldClean($f_schema);
137
        $this->clean($comment); // Passing in an already cleaned comment will lead to double escaped data
138
        // So, while counter-intuitive, it is important to not clean comments before
139
        // calling setComment. We will clean it here instead.
140
        /*
141
         * $this->fieldClean($table);
142
         * $this->fieldClean($obj_name);
143
         */
144
145
        switch ($obj_type) {
146
            case 'TABLE':
147
                $sql .= "\"{$f_schema}\".\"{$table}\" IS ";
148
149
                break;
150
            case 'COLUMN':
151
                $sql .= "\"{$f_schema}\".\"{$table}\".\"{$obj_name}\" IS ";
152
153
                break;
154
            case 'SEQUENCE':
155
            case 'VIEW':
156
            case 'MATERIALIZED VIEW':
157
            case 'TEXT SEARCH CONFIGURATION':
158
            case 'TEXT SEARCH DICTIONARY':
159
            case 'TEXT SEARCH TEMPLATE':
160
            case 'TEXT SEARCH PARSER':
161
            case 'TYPE':
162
                $sql .= "\"{$f_schema}\".";
163
            // no break
164
            case 'DATABASE':
165
            case 'ROLE':
166
            case 'SCHEMA':
167
            case 'TABLESPACE':
168
                $sql .= "\"{$obj_name}\" IS ";
169
170
                break;
171
            case 'FUNCTION':
172
                $sql .= "\"{$f_schema}\".{$obj_name} IS ";
173
174
                break;
175
            case 'AGGREGATE':
176
                $sql .= "\"{$f_schema}\".\"{$obj_name}\" (\"{$basetype}\") IS ";
177
178
                break;
179
180
            default:
181
                // Unknown object type
182
                return -1;
183
        }
184
185
        if ('' !== $comment) {
186
            $sql .= "'{$comment}';";
187
        } else {
188
            $sql .= 'NULL;';
189
        }
190
        $this->lastExecutedSql = $sql;
191
192
        return $this->execute($sql);
193
    }
194
195
    public function getLastExecutedSQL(): string
196
    {
197
        $lastExecutedSql = $this->lastExecutedSql;
198
        $this->lastExecutedSql = '';
199
200
        return $lastExecutedSql;
201
    }
202
203
    /**
204
     * Turns on or off query debugging.
205
     *
206
     * @param bool $debug True to turn on debugging, false otherwise
207
     */
208
    public function setDebug($debug): void
209
    {
210
        $this->conn->debug = $debug;
211
    }
212
213
    /**
214
     * Cleans (escapes) an array of field names.
215
     *
216
     * @param array $arr The array to clean, by reference
217
     *
218
     * @return array The cleaned array
219
     */
220
    public function fieldArrayClean(&$arr)
221
    {
222
        foreach ($arr as $k => $v) {
223
            if (null === $v) {
224
                continue;
225
            }
226
227
            $arr[$k] = \str_replace('"', '""', $v);
228
        }
229
230
        return $arr;
231
    }
232
233
    /**
234
     * Cleans (escapes) an array.
235
     *
236
     * @param array $arr The array to clean, by reference
237
     *
238
     * @return array The cleaned array
239
     */
240
    public function arrayClean(&$arr)
241
    {
242
        foreach ($arr as $k => $v) {
243
            if (null === $v) {
244
                continue;
245
            }
246
            $arr[$k] = \pg_escape_string($v);
247
        }
248
249
        return $arr;
250
    }
251
252
    /**
253
     * Executes a query on the underlying connection.
254
     *
255
     * @param string $sql The SQL query to execute
256
     *
257
     * @return int|\PHPPgAdmin\ADORecordSet A recordset or an error code
258
     */
259
    public function execute($sql)
260
    {
261
        // Execute the statement
262
        try {
263
            $rs = $this->conn->Execute($sql);
0 ignored issues
show
Bug introduced by
$sql of type string is incompatible with the type SQL expected by parameter $sql of ADOConnection::Execute(). ( Ignorable by Annotation )

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

263
            $rs = $this->conn->Execute(/** @scrutinizer ignore-type */ $sql);
Loading history...
264
265
            return $this->ErrorNo();
266
        } catch (\Exception $e) {
267
            return $e->getCode();
268
        }
269
    }
270
271
    /**
272
     * Closes the connection the database class
273
     * relies on.
274
     */
275
    public function close(): void
276
    {
277
        $this->conn->close();
278
    }
279
280
    /**
281
     * Retrieves a ResultSet from a query.
282
     *
283
     * @param string $sql The SQL statement to be executed
284
     *
285
     * @return int|\PHPPgAdmin\ADORecordSet A recordset or an error number
286
     */
287
    public function selectSet($sql)
288
    {
289
        // Execute the statement
290
        try {
291
            return $this->conn->Execute($sql);
0 ignored issues
show
Bug introduced by
$sql of type string is incompatible with the type SQL expected by parameter $sql of ADOConnection::Execute(). ( Ignorable by Annotation )

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

291
            return $this->conn->Execute(/** @scrutinizer ignore-type */ $sql);
Loading history...
292
        } catch (\Exception $e) {
293
            return $e->getCode();
294
        }
295
    }
296
297
    public function ErrorNo(): int
298
    {
299
        return $this->conn->ErrorNo();
300
    }
301
302
    public function ErrorMsg(): string
303
    {
304
        return $this->conn->ErrorMsg();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->ErrorMsg() could return the type true which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
305
    }
306
307
    /**
308
     * Retrieves a single value from a query.
309
     *
310
     * @@ assumes that the query will return only one row - returns field value in the first row
311
     *
312
     * @param string $sql   The SQL statement to be executed
313
     * @param string $field The field name to be returned
314
     *
315
     * @return int|string single field value, error number on error or -1 if no rows where found
316
     */
317
    public function selectField($sql, $field)
318
    {
319
        // Execute the statement
320
        $rs = $this->conn->Execute($sql);
0 ignored issues
show
Bug introduced by
$sql of type string is incompatible with the type SQL expected by parameter $sql of ADOConnection::Execute(). ( Ignorable by Annotation )

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

320
        $rs = $this->conn->Execute(/** @scrutinizer ignore-type */ $sql);
Loading history...
321
322
        // If failure, or no rows returned, return error value
323
        if (!$rs) {
0 ignored issues
show
introduced by
$rs is of type RecordSet, thus it always evaluated to true.
Loading history...
324
            return $this->ErrorNo();
325
        }
326
327
        if (0 === $rs->recordCount()) {
328
            return -1;
329
        }
330
331
        return $rs->fields[$field];
332
    }
333
334
    /**
335
     * Delete from the database.
336
     *
337
     * @param string $table      The name of the table
338
     * @param array  $conditions (array) A map of field names to conditions
339
     * @param string $schema     (optional) The table's schema
340
     *
341
     * @return int 0 success
342
     */
343
    public function delete($table, $conditions, $schema = '')
344
    {
345
        $this->fieldClean($table);
346
347
        \reset($conditions);
348
349
        if (!empty($schema)) {
350
            $this->fieldClean($schema);
351
            $schema = "\"{$schema}\".";
352
        }
353
354
        // Build clause
355
        $sql = '';
356
        //while (list($key, $value) = each($conditions)) {
357
        foreach ($conditions as $key => $value) {
358
            $this->clean($key);
359
            $this->clean($value);
360
361
            if ($sql) {
362
                $sql .= " AND \"{$key}\"='{$value}'";
363
            } else {
364
                $sql = "DELETE FROM {$schema}\"{$table}\" WHERE \"{$key}\"='{$value}'";
365
            }
366
        }
367
368
        // Check for failures
369
        if (!$this->conn->Execute($sql)) {
0 ignored issues
show
Bug introduced by
$sql of type string is incompatible with the type SQL expected by parameter $sql of ADOConnection::Execute(). ( Ignorable by Annotation )

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

369
        if (!$this->conn->Execute(/** @scrutinizer ignore-type */ $sql)) {
Loading history...
370
            // Check for referential integrity failure
371
            if (\mb_stristr($this->ErrorMsg(), 'referential')) {
372
                return -1;
373
            }
374
        }
375
376
        // Check for no rows modified
377
        if (0 === $this->conn->Affected_Rows()) {
0 ignored issues
show
introduced by
The condition 0 === $this->conn->Affected_Rows() is always false.
Loading history...
378
            return -2;
379
        }
380
381
        return $this->ErrorNo();
382
    }
383
384
    /**
385
     * Cleans (escapes) an object name (eg. table, field).
386
     *
387
     * @param null|string $str The string to clean, by reference
388
     *
389
     * @return null|string The cleaned string
390
     */
391
    public function fieldClean(&$str)
392
    {
393
        if (null === $str) {
394
            return null;
395
        }
396
        $str = \str_replace('"', '""', $str);
397
398
        return $str;
399
    }
400
401
    /**
402
     * Cleans (escapes) a string.
403
     *
404
     * @param null|string $str The string to clean, by reference
405
     *
406
     * @return null|string The cleaned string
407
     */
408
    public function clean(&$str)
409
    {
410
        if (null === $str) {
411
            return null;
412
        }
413
        $str = \str_replace("\r\n", "\n", $str);
414
        $str = \pg_escape_string($str);
415
416
        return $str;
417
    }
418
419
    /**
420
     * Escapes bytea data for display on the screen.
421
     *
422
     * @param string $data The bytea data
423
     *
424
     * @return string Data formatted for on-screen display
425
     */
426
    public function escapeBytea($data)
427
    {
428
        return \htmlentities($data, \ENT_QUOTES, 'UTF-8');
429
    }
430
431
    /**
432
     * Insert a set of values into the database.
433
     *
434
     * @param string $table The table to insert into
435
     * @param array  $vars  (array) A mapping of the field names to the values to be inserted
436
     *
437
     * @return int 0 success
438
     */
439
    public function insert($table, $vars)
440
    {
441
        $this->fieldClean($table);
442
        $sql = '';
443
        // Build clause
444
        if (0 < \count($vars)) {
445
            $fields = '';
446
            $values = '';
447
448
            foreach ($vars as $key => $value) {
449
                $this->clean($key);
450
                $this->clean($value);
451
452
                if ($fields) {
453
                    $fields .= ", \"{$key}\"";
454
                } else {
455
                    $fields = "INSERT INTO \"{$table}\" (\"{$key}\"";
456
                }
457
458
                if ($values) {
459
                    $values .= ", '{$value}'";
460
                } else {
461
                    $values = ") VALUES ('{$value}'";
462
                }
463
            }
464
            $sql .= $fields . $values . ')';
465
        }
466
467
        // Check for failures
468
        if (!$this->conn->Execute($sql)) {
0 ignored issues
show
Bug introduced by
$sql of type string is incompatible with the type SQL expected by parameter $sql of ADOConnection::Execute(). ( Ignorable by Annotation )

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

468
        if (!$this->conn->Execute(/** @scrutinizer ignore-type */ $sql)) {
Loading history...
469
            // Check for unique constraint failure
470
            if (\mb_stristr($this->ErrorMsg(), 'unique')) {
471
                return -1;
472
            }
473
474
            if (\mb_stristr($this->ErrorMsg(), 'referential')) {
475
                return -2;
476
            } // Check for referential integrity failure
477
        }
478
479
        return $this->ErrorNo();
480
    }
481
482
    /**
483
     * Update a row in the database.
484
     *
485
     * @param string $table The table that is to be updated
486
     * @param array  $vars  (array) A mapping of the field names to the values to be updated
487
     * @param array  $where (array) A mapping of field names to values for the where clause
488
     * @param array  $nulls (array, optional) An array of fields to be set null
489
     *
490
     * @return int 0 success
491
     */
492
    public function update($table, $vars, $where, $nulls = [])
493
    {
494
        $this->fieldClean($table);
495
496
        $setClause = '';
497
        $whereClause = '';
498
499
        // Populate the syntax arrays
500
        \reset($vars);
501
        //while (list($key, $value) = each($vars)) {
502
        foreach ($vars as $key => $value) {
503
            $this->fieldClean($key);
504
            $this->clean($value);
505
506
            if ($setClause) {
507
                $setClause .= ", \"{$key}\"='{$value}'";
508
            } else {
509
                $setClause = "UPDATE \"{$table}\" SET \"{$key}\"='{$value}'";
510
            }
511
        }
512
513
        \reset($nulls);
514
        //while (list(, $value) = each($nulls)) {
515
        foreach ($nulls as $key => $value) {
516
            $this->fieldClean($value);
517
518
            if ($setClause) {
519
                $setClause .= ", \"{$value}\"=NULL";
520
            } else {
521
                $setClause = "UPDATE \"{$table}\" SET \"{$value}\"=NULL";
522
            }
523
        }
524
525
        \reset($where);
526
        //while (list($key, $value) = each($where)) {
527
        foreach ($where as $key => $value) {
528
            $this->fieldClean($key);
529
            $this->clean($value);
530
531
            if ($whereClause) {
532
                $whereClause .= " AND \"{$key}\"='{$value}'";
533
            } else {
534
                $whereClause = " WHERE \"{$key}\"='{$value}'";
535
            }
536
        }
537
538
        // Check for failures
539
        if (!$this->conn->Execute($setClause . $whereClause)) {
0 ignored issues
show
Bug introduced by
$setClause . $whereClause of type string is incompatible with the type SQL expected by parameter $sql of ADOConnection::Execute(). ( Ignorable by Annotation )

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

539
        if (!$this->conn->Execute(/** @scrutinizer ignore-type */ $setClause . $whereClause)) {
Loading history...
540
            // Check for unique constraint failure
541
            if (\mb_stristr($this->ErrorMsg(), 'unique')) {
542
                return -1;
543
            }
544
545
            if (\mb_stristr($this->ErrorMsg(), 'referential')) {
546
                return -2;
547
            } // Check for referential integrity failure
548
        }
549
550
        // Check for no rows modified
551
        if (0 === $this->conn->Affected_Rows()) {
0 ignored issues
show
introduced by
The condition 0 === $this->conn->Affected_Rows() is always false.
Loading history...
552
            return -3;
553
        }
554
555
        return $this->ErrorNo();
556
    }
557
558
    /**
559
     * Begin a transaction.
560
     *
561
     * @return int 0 success
562
     */
563
    public function beginTransaction()
564
    {
565
        return (int) (!$this->conn->BeginTrans());
566
    }
567
568
    /**
569
     * End a transaction.
570
     *
571
     * @return int 0 success
572
     */
573
    public function endTransaction()
574
    {
575
        return (int) (!$this->conn->CommitTrans());
576
    }
577
578
    /**
579
     * Roll back a transaction.
580
     *
581
     * @return int 0 success
582
     */
583
    public function rollbackTransaction()
584
    {
585
        return (int) (!$this->conn->RollbackTrans());
586
    }
587
588
    /**
589
     * Get the backend platform.
590
     *
591
     * @return string The backend platform
592
     */
593
    public function getPlatform()
594
    {
595
        try {
596
            return $this->conn->platform;
0 ignored issues
show
Bug introduced by
The property platform does not seem to exist on ADODB_postgres9.
Loading history...
597
        } catch (\Exception $e) {
598
            $this->prtrace($e->getMessage());
599
600
            return 'UNKNOWN';
601
        }
602
    }
603
604
    // Type conversion routines
605
606
    /**
607
     * Change the value of a parameter to database representation depending on whether it evaluates to true or false.
608
     *
609
     * @param mixed $parameter the parameter
610
     *
611
     * @return string boolean  database representation
612
     */
613
    public function dbBool(&$parameter)
614
    {
615
        if ($parameter) {
616
            $parameter = 't';
617
        } else {
618
            $parameter = 'f';
619
        }
620
621
        return $parameter;
622
    }
623
624
    /**
625
     * Change a parameter from database representation to a boolean, (others evaluate to false).
626
     *
627
     * @param string $parameter the parameter
628
     *
629
     * @return bool
630
     */
631
    public function phpBool($parameter)
632
    {
633
        return 't' === $parameter;
634
    }
635
636
    /**
637
     * Change a db array into a PHP array.
638
     *
639
     * @param string $dbarr
640
     *
641
     * @return array A PHP array
642
     *
643
     * @internal param String $arr representing the DB array
644
     */
645
    public function phpArray($dbarr)
646
    {
647
        // Take off the first and last characters (the braces)
648
        $arr = \mb_substr($dbarr, 1, \mb_strlen($dbarr) - 2);
649
650
        // Pick out array entries by carefully parsing.  This is necessary in order
651
        // to cope with double quotes and commas, etc.
652
        $elements = [];
653
        $i = $j = 0;
654
        $in_quotes = false;
655
656
        while (\mb_strlen($arr) > $i) {
657
            // If current char is a double quote and it's not escaped, then
658
            // enter quoted bit
659
            $char = \mb_substr($arr, $i, 1);
660
661
            if ('"' === $char && (0 === $i || '\\' !== \mb_substr($arr, $i - 1, 1))) {
662
                $in_quotes = !$in_quotes;
663
            } elseif (',' === $char && !$in_quotes) {
664
                // Add text so far to the array
665
                $elements[] = \mb_substr($arr, $j, $i - $j);
666
                $j = $i + 1;
667
            }
668
            ++$i;
669
        }
670
        // Add final text to the array
671
        $elements[] = \mb_substr($arr, $j);
672
673
        $elementcount = \count($elements);
674
        // Do one further loop over the elements array to remote double quoting
675
        // and escaping of double quotes and backslashes
676
        for ($i = 0; $i < $elementcount; ++$i) {
677
            $v = $elements[$i];
678
679
            if (0 === \mb_strpos($v, '"')) {
680
                $v = \mb_substr($v, 1, \mb_strlen($v) - 2);
681
                $v = \str_replace('\\"', '"', $v);
682
                $v = \str_replace('\\\\', '\\', $v);
683
                $elements[$i] = $v;
684
            }
685
        }
686
687
        return $elements;
688
    }
689
}
690