Issues (100)

src/Pgsql/Db.php (13 issues)

1
<?php
2
/**
3
 * PostgreSQL Related Functionality
4
 * @author Joe Huss <[email protected]>
5
 * @copyright 2025
6
 * @package MyAdmin
7
 * @category SQL
8
 */
9
10
namespace MyDb\Pgsql;
11
12
use MyDb\Generic;
13
use MyDb\Db_Interface;
14
15
/**
16
 * Db
17
 *
18
 * @access public
19
 */
20
class Db extends Generic implements Db_Interface
21
{
22
    /* public: this is an api revision, not a CVS revision. */
23
    public $type = 'pgsql';
24
    public $port = '';
25
    public $defaultPort = '5432';
26
27
28
    /**
29
     * adds if not blank
30
     *
31
     * @param string $add the value to set
32
     * @param string $me the key/field to set the value for
33
     * @param false|string $quote optional indicate the value needs quoted
34
     * @return string
35
     */
36
    public function ifadd($add, $me, $quote = false)
37
    {
38
        if ('' != $add) {
39
            return ' '.$me.($quote === false ? '' : $quote).$add.($quote === false ? '' : $quote);
40
        }
41
        return '';
42
    }
43
44
    /**
45
     * @param $string
46
     * @return string
47
     */
48
    public function real_escape($string = '')
49
    {
50
        return $this->escape($string);
51
    }
52
53
    /**
54
     * alias function of select_db, changes the database we are working with.
55
     *
56
     * @param string $database the name of the database to use
57
     * @return void
58
     */
59
    public function useDb($database)
60
    {
61
        $this->selectDb($database);
62
    }
63
64
    /**
65
     * changes the database we are working with.
66
     *
67
     * @param string $database the name of the database to use
68
     * @return void
69
     */
70
    public function selectDb($database)
0 ignored issues
show
The parameter $database is not used and could be removed. ( Ignorable by Annotation )

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

70
    public function selectDb(/** @scrutinizer ignore-unused */ $database)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
71
    {
72
        /*if ($database != $this->database) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
73
            $this->database = $database;
74
            $this->linkId = null;
75
            $this->connect();
76
        }*/
77
    }
78
79
    /**
80
     * Db::connect()
81
     * @return void
82
     */
83
    public function connect()
84
    {
85
        if (0 == $this->linkId) {
86
            $connectString = trim($this->ifadd($this->host, 'host=').
87
                             $this->ifadd($this->port, 'port=').
88
                             $this->ifadd($this->database, 'dbname=').
89
                             $this->ifadd($this->user, 'user=').
90
                             $this->ifadd($this->password, 'password=', "'"));
91
            $this->linkId = pg_connect($connectString);
0 ignored issues
show
Documentation Bug introduced by
It seems like pg_connect($connectString) of type resource is incompatible with the declared type integer|object of property $linkId.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
92
            if (!$this->linkId) {
93
                $this->halt('Link-ID == FALSE, connect failed');
94
            }
95
        }
96
    }
97
98
    /* This only affects systems not using persistent connections */
99
100
    /**
101
     * Db::disconnect()
102
     * @return bool
103
     */
104
    public function disconnect()
105
    {
106
        return @pg_close($this->linkId);
0 ignored issues
show
$this->linkId of type integer|object is incompatible with the type resource expected by parameter $connection of pg_close(). ( Ignorable by Annotation )

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

106
        return @pg_close(/** @scrutinizer ignore-type */ $this->linkId);
Loading history...
107
    }
108
109
    /**
110
     * Db::queryReturn()
111
     *
112
     * Sends an SQL query to the server like the normal query() command but iterates through
113
     * any rows and returns the row or rows immediately or FALSE on error
114
     *
115
     * @param mixed $query SQL Query to be used
116
     * @param string $line optionally pass __LINE__ calling the query for logging
117
     * @param string $file optionally pass __FILE__ calling the query for logging
118
     * @return mixed FALSE if no rows, if a single row it returns that, if multiple it returns an array of rows, associative responses only
119
     */
120
    public function queryReturn($query, $line = '', $file = '')
121
    {
122
        $this->query($query, $line, $file);
123
        if ($this->num_rows() == 0) {
124
            return false;
125
        } elseif ($this->num_rows() == 1) {
126
            $this->next_record(MYSQL_ASSOC);
0 ignored issues
show
The constant MYSQL_ASSOC has been deprecated: 5.5 ( Ignorable by Annotation )

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

126
            $this->next_record(/** @scrutinizer ignore-deprecated */ MYSQL_ASSOC);
Loading history...
127
            return $this->Record;
128
        } else {
129
            $out = [];
130
            while ($this->next_record(MYSQL_ASSOC)) {
0 ignored issues
show
The constant MYSQL_ASSOC has been deprecated: 5.5 ( Ignorable by Annotation )

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

130
            while ($this->next_record(/** @scrutinizer ignore-deprecated */ MYSQL_ASSOC)) {
Loading history...
131
                $out[] = $this->Record;
132
            }
133
            return $out;
134
        }
135
    }
136
137
    /**
138
     * db:qr()
139
     *
140
     *  alias of queryReturn()
141
     *
142
     * @param mixed $query SQL Query to be used
143
     * @param string $line optionally pass __LINE__ calling the query for logging
144
     * @param string $file optionally pass __FILE__ calling the query for logging
145
     * @return mixed FALSE if no rows, if a single row it returns that, if multiple it returns an array of rows, associative responses only
146
     */
147
    public function qr($query, $line = '', $file = '')
148
    {
149
        return $this->queryReturn($query, $line, $file);
150
    }
151
152
    /**
153
     * Db::query()
154
     *
155
     *  Sends an SQL query to the database
156
     *
157
     * @param mixed $queryString
158
     * @param string $line
159
     * @param string $file
160
     * @return mixed 0 if no query or query id handler, safe to ignore this return
161
     */
162
    public function query($queryString, $line = '', $file = '')
163
    {
164
        /* No empty queries, please, since PHP4 chokes on them. */
165
        /* The empty query string is passed on from the constructor,
166
        * when calling the class without a query, e.g. in situations
167
        * like these: '$db = new db_Subclass;'
168
        */
169
        if ($queryString == '') {
170
            return 0;
171
        }
172
173
        $this->connect();
174
175
        /* printf("<br>Debug: query = %s<br>\n", $queryString); */
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
176
177
        $this->queryId = @pg_exec($this->linkId, $queryString);
178
        $this->Row = 0;
179
180
        $this->Error = pg_errormessage($this->linkId);
181
        $this->Errno = ($this->Error == '') ? 0 : 1;
182
        if (!$this->queryId) {
183
            $this->halt('Invalid SQL: '.$queryString, $line, $file);
184
        }
185
186
        return $this->queryId;
187
    }
188
189
    /**
190
     * Db::free()
191
     *
192
     * @return void
193
     */
194
    public function free()
195
    {
196
        @pg_freeresult($this->queryId);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for pg_freeresult(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

196
        /** @scrutinizer ignore-unhandled */ @pg_freeresult($this->queryId);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
197
        $this->queryId = 0;
198
    }
199
200
    /**
201
     * Db::next_record()
202
     * @param mixed $resultType
203
     * @return bool
204
     */
205
    public function next_record($resultType = PGSQL_BOTH)
206
    {
207
        $this->Record = @pg_fetch_array($this->queryId, $this->Row++, $resultType);
0 ignored issues
show
$this->queryId of type integer is incompatible with the type resource expected by parameter $result of pg_fetch_array(). ( Ignorable by Annotation )

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

207
        $this->Record = @pg_fetch_array(/** @scrutinizer ignore-type */ $this->queryId, $this->Row++, $resultType);
Loading history...
208
209
        $this->Error = pg_errormessage($this->linkId);
210
        $this->Errno = ($this->Error == '') ? 0 : 1;
211
212
        $stat = is_array($this->Record);
213
        if (!$stat && $this->autoFree) {
0 ignored issues
show
The condition $stat is always true.
Loading history...
214
            pg_freeresult($this->queryId);
215
            $this->queryId = 0;
216
        }
217
        return $stat;
218
    }
219
220
    /**
221
     * Db::seek()
222
     *
223
     * @param mixed $pos
224
     * @return void
225
     */
226
    public function seek($pos)
227
    {
228
        $this->Row = $pos;
229
    }
230
231
    /**
232
     * Db::transactionBegin()
233
     *
234
     * @return mixed
235
     */
236
    public function transactionBegin()
237
    {
238
        return $this->query('begin');
239
    }
240
241
    /**
242
     * Db::transactionCommit()
243
     * @return bool|mixed
244
     */
245
    public function transactionCommit()
246
    {
247
        if (!$this->Errno) {
248
            return pg_exec($this->linkId, 'commit');
249
        } else {
250
            return false;
251
        }
252
    }
253
254
    /**
255
     * Db::transactionAbort()
256
     * @return mixed
257
     */
258
    public function transactionAbort()
259
    {
260
        return pg_exec($this->linkId, 'rollback');
261
    }
262
263
    /**
264
     * Db::getLastInsertId()
265
     * @param mixed $table
266
     * @param mixed $field
267
     * @return int
268
     */
269
    public function getLastInsertId($table, $field)
270
    {
271
        /* This will get the last insert ID created on the current connection.  Should only be called
272
        * after an insert query is run on a table that has an auto incrementing field.  Of note, table
273
        * and field are required because pgsql returns the last inserted OID, which is unique across
274
        * an entire installation.  These params allow us to retrieve the sequenced field without adding
275
        * conditional code to the apps.
276
        */
277
        if (!isset($table) || $table == '' || !isset($field) || $field == '') {
278
            return -1;
279
        }
280
281
        $oid = pg_getlastoid($this->queryId);
282
        if ($oid == -1) {
283
            return -1;
284
        }
285
286
        $result = @pg_exec($this->linkId, "select $field from $table where oid=$oid");
287
        if (!$result) {
288
            return -1;
289
        }
290
291
        $Record = @pg_fetch_array($result, 0);
292
        @pg_freeresult($result);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for pg_freeresult(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

292
        /** @scrutinizer ignore-unhandled */ @pg_freeresult($result);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
293
        if (!is_array($Record)) /* OID not found? */
0 ignored issues
show
The condition is_array($Record) is always true.
Loading history...
294
        {
295
            return -1;
296
        }
297
298
        return $Record[0];
299
    }
300
301
    /**
302
     * Db::lock()
303
     * @param mixed  $table
304
     * @param string $mode
305
     * @return int|mixed
306
     */
307
    public function lock($table, $mode = 'write')
308
    {
309
        $result = $this->transactionBegin();
310
311
        if ($mode == 'write') {
312
            if (is_array($table)) {
313
                foreach ($table as $t) {
314
                    $result = pg_exec($this->linkId, 'lock table '.$t[1].' in share mode');
315
                }
316
            } else {
317
                $result = pg_exec($this->linkId, 'lock table '.$table.' in share mode');
318
            }
319
        } else {
320
            $result = 1;
321
        }
322
323
        return $result;
324
    }
325
326
    /**
327
     * Db::unlock()
328
     * @return bool|mixed
329
     */
330
    public function unlock()
331
    {
332
        return $this->transactionCommit();
333
    }
334
335
    /**
336
     * Db::affectedRows()
337
     * @return void
338
     */
339
    public function affectedRows()
340
    {
341
        return pg_cmdtuples($this->queryId);
342
    }
343
344
    /**
345
     * Db::num_rows()
346
     * @return int
347
     */
348
    public function num_rows()
349
    {
350
        return pg_numrows($this->queryId);
351
    }
352
353
    /**
354
     * Db::num_fields()
355
     * @return int
356
     */
357
    public function num_fields()
358
    {
359
        return pg_numfields($this->queryId);
360
    }
361
362
    /**
363
     * @param mixed $msg
364
     * @param string $line
365
     * @param string $file
366
     * @return mixed|void
367
     */
368
    public function haltmsg($msg, $line = '', $file = '')
369
    {
370
        $this->log("Database error: $msg", $line, $file, 'error');
371
        if ($this->Errno != '0' || $this->Error != '()') {
372
            $this->log('PostgreSQL Error: '.pg_last_error($this->linkId), $line, $file, 'error');
0 ignored issues
show
$this->linkId of type integer|object is incompatible with the type resource expected by parameter $connection of pg_last_error(). ( Ignorable by Annotation )

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

372
            $this->log('PostgreSQL Error: '.pg_last_error(/** @scrutinizer ignore-type */ $this->linkId), $line, $file, 'error');
Loading history...
373
        }
374
        $this->logBackTrace($msg, $line, $file);
375
    }
376
377
    /**
378
     * Db::tableNames()
379
     *
380
     * @return array
381
     */
382
    public function tableNames()
383
    {
384
        $return = [];
385
        $this->query("select relname from pg_class where relkind = 'r' and not relname like 'pg_%'");
386
        $i = 0;
387
        while ($this->next_record()) {
388
            $return[$i]['table_name'] = $this->f(0);
389
            $return[$i]['tablespace_name'] = $this->database;
390
            $return[$i]['database'] = $this->database;
391
            ++$i;
392
        }
393
        return $return;
394
    }
395
396
    /**
397
     * Db::indexNames()
398
     *
399
     * @return array
400
     */
401
    public function indexNames()
402
    {
403
        $return = [];
404
        $this->query("SELECT relname FROM pg_class WHERE NOT relname ~ 'pg_.*' AND relkind ='i' ORDER BY relname");
405
        $i = 0;
406
        while ($this->next_record()) {
407
            $return[$i]['index_name'] = $this->f(0);
408
            $return[$i]['tablespace_name'] = $this->database;
409
            $return[$i]['database'] = $this->database;
410
            ++$i;
411
        }
412
        return $return;
413
    }
414
}
415