dolibarr 25.0.0-alpha
pgsql.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2001 Fabien Seisen <seisen@linuxfr.org>
3 * Copyright (C) 2002-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
4 * Copyright (C) 2004-2014 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
6 * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
7 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
8 * Copyright (C) 2012 Yann Droneaud <yann@droneaud.fr>
9 * Copyright (C) 2012 Florian Henry <florian.henry@open-concept.pro>
10 * Copyright (C) 2015 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
12 * Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program. If not, see <https://www.gnu.org/licenses/>.
26 */
27
33require_once DOL_DOCUMENT_ROOT.'/core/db/DoliDB.class.php';
34
38class DoliDBPgsql extends DoliDB
39{
41 public $type = 'pgsql'; // Name of manager
42
44 const LABEL = 'PostgreSQL'; // Label of manager
45
47 public $forcecharset = 'UTF8'; // Can't be static as it may be forced with a dynamic value
48
50 public $forcecollate = ''; // Can't be static as it may be forced with a dynamic value
51
53 const VERSIONMIN = '9.0.0'; // Version min database
54
58 public $unescapeslashquot = false;
62 public $standard_conforming_strings = false;
63
64
68 private $_results;
69
70
71
84 public function __construct($type, $host, $user, $pass, $name = '', $port = 0, $forcenew = false) // @phpstan-ignore constructor.unusedParameter
85 {
86 global $conf, $langs;
87
88 // Note that having "static" property for "$forcecharset" and "$forcecollate" will make error here in strict mode, so they are not static
89 if (!empty($conf->db->character_set)) {
90 $this->forcecharset = $conf->db->character_set;
91 }
92 if (!empty($conf->db->dolibarr_main_db_collation)) {
93 $this->forcecollate = $conf->db->dolibarr_main_db_collation;
94 }
95
96 $this->database_user = $user;
97 $this->database_host = $host;
98 $this->database_port = $port;
99
100 $this->transaction_opened = 0;
101
102 //print "Name DB: $host,$user,$pass,$name<br>";
103
104 if (!function_exists("pg_connect")) {
105 $this->connected = false;
106 $this->ok = false;
107 $this->error = "Pgsql PHP functions are not available in this version of PHP";
108 dol_syslog(get_class($this)."::DoliDBPgsql : Pgsql PHP functions are not available in this version of PHP", LOG_ERR);
109 return;
110 }
111
112 if (!$host) {
113 $this->connected = false;
114 $this->ok = false;
115 $this->error = $langs->trans("ErrorWrongHostParameter");
116 dol_syslog(get_class($this)."::DoliDBPgsql : Connection Error, wrong host parameters", LOG_ERR);
117 return;
118 }
119
120 // Try server connection
121 //print "$host, $user, $pass, $name, $port";
122 $this->db = $this->connect($host, $user, $pass, $name, $port, $forcenew);
123
124 if ($this->db) {
125 $this->connected = true;
126 $this->ok = true;
127 } else {
128 // host, login ou password incorrect
129 $this->connected = false;
130 $this->ok = false;
131 $this->error = 'Host, login or password incorrect';
132 dol_syslog(get_class($this)."::DoliDBPgsql : Connection Error ".$this->error.'. Failed to connect to host='.$host.' port='.$port.' user='.$user, LOG_ERR);
133 }
134
135 // If server connection ok and DB connection is requested, try to connect to DB
136 if ($this->connected && $name) {
137 if ($this->select_db($name)) {
138 $this->database_selected = true;
139 $this->database_name = $name;
140 $this->ok = true;
141 } else {
142 $this->database_selected = false;
143 $this->database_name = '';
144 $this->ok = false;
145 $this->error = $this->error();
146 dol_syslog(get_class($this)."::DoliDBPgsql : Select_db Error ".$this->error, LOG_ERR);
147 }
148 } else {
149 // No database selection requested, ok or ko
150 $this->database_selected = false;
151 }
152 }
153
154
163 public function convertSQLFromMysql($line, $type = 'auto', $unescapeslashquot = false)
164 {
165 global $conf;
166
167 // Removed empty line if this is a comment line for SVN tagging
168 if (preg_match('/^--\s\$Id/i', $line)) {
169 return '';
170 }
171 // Return line if this is a comment
172 if (preg_match('/^#/i', $line) || preg_match('/^$/i', $line) || preg_match('/^--/i', $line)) {
173 return $line;
174 }
175 if ($line != "") {
176 // group_concat support (PgSQL >= 9.0)
177 // Replace group_concat(x) or group_concat(x SEPARATOR ',') with string_agg(x, ',')
178 $line = preg_replace('/GROUP_CONCAT/i', 'STRING_AGG', $line);
179 $line = preg_replace('/ SEPARATOR/i', ',', $line);
180 $line = preg_replace('/STRING_AGG\‍(([^,\‍)]+)\‍)/i', 'STRING_AGG(\\1, \',\')', $line);
181 $line = preg_replace('/STRING_AGG\‍(([^,]+),([^\‍)]+)\‍)/i', 'STRING_AGG(\\1::TEXT,\\2::TEXT)', $line);
182 //print $line."\n";
183
184 if ($type == 'auto') {
185 if (preg_match('/ALTER TABLE/i', $line)) {
186 $type = 'dml';
187 } elseif (preg_match('/CREATE TABLE/i', $line)) {
188 $type = 'dml';
189 } elseif (preg_match('/DROP TABLE/i', $line)) {
190 $type = 'dml';
191 }
192 }
193
194 $line = preg_replace('/ as signed\‍)/i', ' as integer)', $line);
195
196 if ($type == 'dml') {
197 $reg = array();
198
199 $line = preg_replace('/\s/', ' ', $line); // Replace tabulation with space
200
201 // we are inside create table statement so let's process datatypes
202 if (preg_match('/(ISAM|innodb)/i', $line)) { // end of create table sequence
203 $line = preg_replace('/\‍)[\s\t]*type[\s\t]*=[\s\t]*(MyISAM|innodb).*;/i', ');', $line);
204 $line = preg_replace('/\‍)[\s\t]*engine[\s\t]*=[\s\t]*(MyISAM|innodb).*;/i', ');', $line);
205 $line = preg_replace('/,$/', '', $line);
206 }
207
208 // Process case: "CREATE TABLE llx_mytable(rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY,code..."
209 if (preg_match('/[\s\t\‍(]*(\w*)[\s\t]+int.*auto_increment/i', $line, $reg)) {
210 $newline = preg_replace('/([\s\t\‍(]*)([a-zA-Z_0-9]*)[\s\t]+int.*auto_increment[^,]*/i', '\\1 \\2 SERIAL PRIMARY KEY', $line);
211 //$line = "-- ".$line." replaced by --\n".$newline;
212 $line = $newline;
213 }
214
215 if (preg_match('/[\s\t\‍(]*(\w*)[\s\t]+bigint.*auto_increment/i', $line, $reg)) {
216 $newline = preg_replace('/([\s\t\‍(]*)([a-zA-Z_0-9]*)[\s\t]+bigint.*auto_increment[^,]*/i', '\\1 \\2 BIGSERIAL PRIMARY KEY', $line);
217 //$line = "-- ".$line." replaced by --\n".$newline;
218 $line = $newline;
219 }
220
221 // tinyint type conversion
222 $line = preg_replace('/tinyint\‍(?[0-9]*\‍)?/', 'smallint', $line);
223 $line = preg_replace('/tinyint/i', 'smallint', $line);
224
225 // nuke unsigned
226 $line = preg_replace('/(int\w+|smallint|bigint)\s+unsigned/i', '\\1', $line);
227
228 // blob -> text
229 $line = preg_replace('/\w*blob/i', 'text', $line);
230
231 // tinytext/mediumtext -> text
232 $line = preg_replace('/tinytext/i', 'text', $line);
233 $line = preg_replace('/mediumtext/i', 'text', $line);
234 $line = preg_replace('/longtext/i', 'text', $line);
235
236 $line = preg_replace('/text\‍([0-9]+\‍)/i', 'text', $line);
237
238 // change not null datetime field to null valid ones
239 // (to support remapping of "zero time" to null
240 $line = preg_replace('/datetime not null/i', 'datetime', $line);
241 $line = preg_replace('/datetime/i', 'timestamp', $line);
242
243 // double -> numeric
244 $line = preg_replace('/^double/i', 'numeric', $line);
245 $line = preg_replace('/(\s*)double/i', '\\1numeric', $line);
246 // float -> numeric
247 $line = preg_replace('/^float/i', 'numeric', $line);
248 $line = preg_replace('/(\s*)float/i', '\\1numeric', $line);
249
250 //Check tms timestamp field case (in Mysql this field is defaulted to now and
251 // on update defaulted by now
252 $line = preg_replace('/(\s*)tms(\s*)timestamp/i', '\\1tms timestamp without time zone DEFAULT now() NOT NULL', $line);
253
254 // nuke DEFAULT CURRENT_TIMESTAMP
255 $line = preg_replace('/(\s*)DEFAULT(\s*)CURRENT_TIMESTAMP/i', '\\1', $line);
256
257 // nuke ON UPDATE CURRENT_TIMESTAMP
258 $line = preg_replace('/(\s*)ON(\s*)UPDATE(\s*)CURRENT_TIMESTAMP/i', '\\1', $line);
259
260 // unique index(field1,field2)
261 if (preg_match('/unique index\s*\‍((\w+\s*,\s*\w+)\‍)/i', $line)) {
262 $line = preg_replace('/unique index\s*\‍((\w+\s*,\s*\w+)\‍)/i', 'UNIQUE\‍(\\1\‍)', $line);
263 }
264
265 // We remove end of requests "AFTER fieldxxx"
266 $line = preg_replace('/\sAFTER [a-z0-9_]+/i', '', $line);
267
268 // We remove start of requests "ALTER TABLE tablexxx" if this is a DROP INDEX
269 $line = preg_replace('/ALTER TABLE [a-z0-9_]+\s+DROP INDEX/i', 'DROP INDEX', $line);
270
271 // Translate order to rename fields
272 if (preg_match('/ALTER TABLE ([a-z0-9_]+)\s+CHANGE(?: COLUMN)? ([a-z0-9_]+) ([a-z0-9_]+)(.*)$/i', $line, $reg)) {
273 $line = "-- ".$line." replaced by --\n";
274 $line .= "ALTER TABLE ".$reg[1]." RENAME COLUMN ".$reg[2]." TO ".$reg[3];
275 }
276
277 // Translate order to modify field format
278 if (preg_match('/ALTER TABLE ([a-z0-9_]+)\s+MODIFY(?: COLUMN)? ([a-z0-9_]+) (.*)$/i', $line, $reg)) {
279 $line = "-- ".$line." replaced by --\n";
280 $newreg3 = $reg[3];
281 $newreg3 = preg_replace('/ DEFAULT NULL/i', '', $newreg3);
282 $newreg3 = preg_replace('/ NOT NULL/i', '', $newreg3);
283 $newreg3 = preg_replace('/ NULL/i', '', $newreg3);
284 $newreg3 = preg_replace('/ DEFAULT 0/i', '', $newreg3);
285 $newreg3 = preg_replace('/ DEFAULT \'?[0-9a-zA-Z_@]*\'?/i', '', $newreg3);
286 $line .= "ALTER TABLE ".$reg[1]." ALTER COLUMN ".$reg[2]." TYPE ".$newreg3;
287 // TODO Add alter to set default value or null/not null if there is this in $reg[3]
288 }
289
290 // alter table add primary key (field1, field2 ...) -> We remove the primary key name not accepted by PostGreSQL
291 // ALTER TABLE llx_dolibarr_modules ADD PRIMARY KEY pk_dolibarr_modules (numero, entity)
292 if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+PRIMARY\s+KEY\s*(.*)\s*\‍((.*)$/i', $line, $reg)) {
293 $line = "-- ".$line." replaced by --\n";
294 $line .= "ALTER TABLE ".$reg[1]." ADD PRIMARY KEY (".$reg[3];
295 }
296
297 // Translate order to drop primary keys
298 // ALTER TABLE llx_dolibarr_modules DROP PRIMARY KEY pk_xxx
299 if (preg_match('/ALTER\s+TABLE\s*(.*)\s*DROP\s+PRIMARY\s+KEY\s*([^;]+)$/i', $line, $reg)) {
300 $line = "-- ".$line." replaced by --\n";
301 $line .= "ALTER TABLE ".$reg[1]." DROP CONSTRAINT ".$reg[2];
302 }
303
304 // Translate order to drop foreign keys
305 // ALTER TABLE llx_dolibarr_modules DROP FOREIGN KEY fk_xxx
306 if (preg_match('/ALTER\s+TABLE\s*(.*)\s*DROP\s+FOREIGN\s+KEY\s*(.*)$/i', $line, $reg)) {
307 $line = "-- ".$line." replaced by --\n";
308 $line .= "ALTER TABLE ".$reg[1]." DROP CONSTRAINT ".$reg[2];
309 }
310
311 // Translate order to add foreign keys
312 // ALTER TABLE llx_tablechild ADD CONSTRAINT fk_tablechild_fk_fieldparent FOREIGN KEY (fk_fieldparent) REFERENCES llx_tableparent (rowid)
313 if (preg_match('/ALTER\s+TABLE\s+(.*)\s*ADD CONSTRAINT\s+(.*)\s*FOREIGN\s+KEY\s*(.*)$/i', $line, $reg)) {
314 $line = preg_replace('/;$/', '', $line);
315 $line .= " DEFERRABLE INITIALLY IMMEDIATE;";
316 }
317
318 // alter table add [unique] [index] (field1, field2 ...)
319 // ALTER TABLE llx_accountingaccount ADD INDEX idx_accountingaccount_fk_pcg_version (fk_pcg_version)
320 if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+(UNIQUE INDEX|INDEX|UNIQUE)\s+(.*)\s*\‍(([\w,\s]+)\‍)/i', $line, $reg)) {
321 $fieldlist = $reg[4];
322 $idxname = $reg[3];
323 $tablename = $reg[1];
324 $line = "-- ".$line." replaced by --\n";
325 $line .= "CREATE ".(preg_match('/UNIQUE/', $reg[2]) ? 'UNIQUE ' : '')."INDEX ".$idxname." ON ".$tablename." (".$fieldlist.")";
326 }
327 }
328
329 // To have PostgreSQL case sensitive
330 $count_like = 0;
331 $line = str_replace(" LIKE '", " ILIKE '", $line, $count_like);
332 if (getDolGlobalString('PSQL_USE_UNACCENT') && $count_like > 0) {
333 // @see https://docs.PostgreSQL.fr/11/unaccent.html : 'unaccent()' function must be installed before
334 $line = preg_replace('/\s+(\‍(+\s*)([a-zA-Z0-9\-\_\.]+) ILIKE /', ' \1unaccent(\2) ILIKE ', $line);
335 }
336
337 $line = str_replace(" LIKE BINARY '", " LIKE '", $line);
338
339 // Replace INSERT IGNORE into INSERT
340 $line = preg_replace('/^INSERT IGNORE/', 'INSERT', $line);
341
342 // Delete using criteria on other table must not declare twice the deleted table
343 // DELETE FROM tabletodelete USING tabletodelete, othertable -> DELETE FROM tabletodelete USING othertable
344 if (preg_match('/DELETE FROM ([a-z_]+) USING ([a-z_]+), ([a-z_]+)/i', $line, $reg)) {
345 if ($reg[1] == $reg[2]) { // If same table, we remove second one
346 $line = preg_replace('/DELETE FROM ([a-z_]+) USING ([a-z_]+), ([a-z_]+)/i', 'DELETE FROM \\1 USING \\3', $line);
347 }
348 }
349
350 // Remove () in the tables in FROM if 1 table
351 $line = preg_replace('/FROM\s*\‍((([a-z_]+)\s+as\s+([a-z_]+)\s*)\‍)/i', 'FROM \\1', $line);
352 //print $line."\n";
353
354 // Remove () in the tables in FROM if 2 table
355 $line = preg_replace('/FROM\s*\‍(([a-z_]+\s+as\s+[a-z_]+)\s*,\s*([a-z_]+\s+as\s+[a-z_]+\s*)\‍)/i', 'FROM \\1, \\2', $line);
356 //print $line."\n";
357
358 // Remove () in the tables in FROM if 3 table
359 $line = preg_replace('/FROM\s*\‍(([a-z_]+\s+as\s+[a-z_]+)\s*,\s*([a-z_]+\s+as\s+[a-z_]+\s*),\s*([a-z_]+\s+as\s+[a-z_]+\s*)\‍)/i', 'FROM \\1, \\2, \\3', $line);
360 //print $line."\n";
361
362 // Remove () in the tables in FROM if 4 table
363 $line = preg_replace('/FROM\s*\‍(([a-z_]+\s+as\s+[a-z_]+)\s*,\s*([a-z_]+\s+as\s+[a-z_]+\s*),\s*([a-z_]+\s+as\s+[a-z_]+\s*),\s*([a-z_]+\s+as\s+[a-z_]+\s*)\‍)/i', 'FROM \\1, \\2, \\3, \\4', $line);
364 //print $line."\n";
365
366 // Remove () in the tables in FROM if 5 table
367 $line = preg_replace('/FROM\s*\‍(([a-z_]+\s+as\s+[a-z_]+)\s*,\s*([a-z_]+\s+as\s+[a-z_]+\s*),\s*([a-z_]+\s+as\s+[a-z_]+\s*),\s*([a-z_]+\s+as\s+[a-z_]+\s*),\s*([a-z_]+\s+as\s+[a-z_]+\s*)\‍)/i', 'FROM \\1, \\2, \\3, \\4, \\5', $line);
368 //print $line."\n";
369
370 // Replace spacing ' with ''.
371 // By default we do not (should be already done by db->escape function if required
372 // except for sql insert in data file that are mysql escaped so we removed them to
373 // be compatible with standard_conforming_strings=on that considers \ as ordinary character).
374 if ($unescapeslashquot) {
375 $line = preg_replace("/\\\'/", "''", $line);
376 }
377
378 //print "type=".$type." newline=".$line."<br>\n";
379 }
380
381 return $line;
382 }
383
384 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
393 public function select_db($database)
394 {
395 // phpcs:enable
396 if ($database == $this->database_name) {
397 return true;
398 } else {
399 return false;
400 }
401 }
402
420 public function connect($host, $login, $passwd, $name, $port = 0, $forcenew = false)
421 {
422 // use pg_pconnect() instead of pg_connect() if you want to use persistent connection costing 1ms, instead of 30ms for non persistent
423
424 $this->db = false;
425
426 // connections parameters must be protected (only \ and ' according to pg_connect() manual)
427 $host = str_replace(array("\\", "'"), array("\\\\", "\\'"), $host);
428 $login = str_replace(array("\\", "'"), array("\\\\", "\\'"), $login);
429 $passwd = str_replace(array("\\", "'"), array("\\\\", "\\'"), $passwd);
430 $name = str_replace(array("\\", "'"), array("\\\\", "\\'"), $name);
431 $port = str_replace(array("\\", "'"), array("\\\\", "\\'"), (string) $port);
432
433 if (!$name) {
434 $name = "postgres"; // When try to connect using admin user
435 }
436
437 $connectflags = $forcenew ? PGSQL_CONNECT_FORCE_NEW : 0;
438
439 // try first Unix domain socket (local)
440 if ((!empty($host) && $host == "socket") && !defined('NOLOCALSOCKETPGCONNECT')) {
441 $con_string = "dbname='".$name."' user='".$login."' password='".$passwd."'"; // $name may be empty
442 try {
443 // PGSQL_CONNECT_FORCE_NEW is required: pg_connect() otherwise returns the connection already
444 // opened for the same connection string, so a second handle would share the main one and
445 // closing it would close the connection still in use by the caller.
446 $this->db = @pg_connect($con_string, $connectflags);
447 } catch (Throwable $e) {
448 // No message
449 }
450 }
451
452 // if local connection failed or not requested, use TCP/IP
453 if (empty($this->db)) {
454 if (!$host) {
455 $host = "localhost";
456 }
457 if (!$port) {
458 $port = 5432;
459 }
460
461 $con_string = "host='".$host."' port='".$port."' dbname='".$name."' user='".$login."' password='".$passwd."'";
462 try {
463 $this->db = @pg_connect($con_string, $connectflags);
464 } catch (Throwable $e) {
465 print $e->getMessage();
466 }
467 }
468
469 // now we test if at least one connect method was a success
470 if ($this->db) {
471 $this->database_name = $name;
472 pg_set_error_verbosity($this->db, PGSQL_ERRORS_VERBOSE); // Set verbosity to max
473 pg_query($this->db, "set datestyle = 'ISO, YMD';");
474 }
475
476 return $this->db;
477 }
478
484 public function getVersion()
485 {
486 $resql = $this->query('SHOW server_version');
487 if ($resql) {
488 $liste = $this->fetch_array($resql);
489 return $liste['server_version'];
490 }
491 return '';
492 }
493
499 public function getDriverInfo()
500 {
501 return 'pgsql php driver';
502 }
503
510 public function close()
511 {
512 if ($this->db) {
513 if ($this->transaction_opened > 0) {
514 dol_syslog(get_class($this)."::close Closing a connection with an opened transaction depth=".$this->transaction_opened, LOG_ERR);
515 }
516 $this->connected = false;
517 return pg_close($this->db);
518 }
519 return false;
520 }
521
531 public function query($query, $usesavepoint = 0, $type = 'auto', $result_mode = 0)
532 {
533 global $dolibarr_main_db_readonly;
534
535 $query = trim($query);
536
537 // Convert MySQL syntax to PostgreSQL syntax
538 $query = $this->convertSQLFromMysql($query, $type, ($this->unescapeslashquot && $this->standard_conforming_strings));
539 //print "After convertSQLFromMysql:\n".$query."<br>\n";
540
541 if (getDolGlobalString('MAIN_DB_AUTOFIX_BAD_SQL_REQUEST')) {
542 // Fix bad formed requests. If request contains a date without quotes, we fix this but this should not occurs.
543 $loop = true;
544 while ($loop) {
545 if (preg_match('/([^\'])([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9])/', $query)) {
546 $query = preg_replace('/([^\'])([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9])/', '\\1\'\\2\'', $query);
547 dol_syslog("Warning: Bad formed request converted into ".$query, LOG_WARNING);
548 } else {
549 $loop = false;
550 }
551 }
552 }
553
554 if ($usesavepoint && $this->transaction_opened) {
555 @pg_query($this->db, 'SAVEPOINT mysavepoint');
556 }
557
558 if (!in_array($query, array('BEGIN', 'COMMIT', 'ROLLBACK'))) {
559 $SYSLOG_SQL_LIMIT = 10000; // limit log to 10kb per line to limit DOS attacks
560 dol_syslog('sql='.substr($query, 0, $SYSLOG_SQL_LIMIT), LOG_DEBUG);
561 }
562 if (empty($query)) {
563 return false; // Return false = error if empty request
564 }
565
566 if (!empty($dolibarr_main_db_readonly)) {
567 if (preg_match('/^(INSERT|UPDATE|REPLACE|DELETE|CREATE|ALTER|TRUNCATE|DROP)/i', $query)) {
568 $this->lasterror = 'Application in read-only mode';
569 $this->lasterrno = 'APPREADONLY';
570 $this->lastquery = $query;
571 return false;
572 }
573 }
574
575 $ret = @pg_query($this->db, $query);
576
577 //print $query;
578 if (!preg_match("/^COMMIT/i", $query) && !preg_match("/^ROLLBACK/i", $query)) { // If it is a user query, save it along with its resultset
579 if (!$ret) {
580 if ($this->errno() != 'DB_ERROR_25P02') { // Do not overwrite errors if this is a consecutive error
581 $this->lastqueryerror = $query;
582 $this->lasterror = $this->error();
583 $this->lasterrno = $this->errno();
584
585 if (getDolGlobalInt('SYSLOG_LEVEL') < LOG_DEBUG) {
586 dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); // Log of request was not yet done previously
587 }
588 dol_syslog(get_class($this)."::query SQL Error message: ".$this->lasterror." (".$this->lasterrno.")", LOG_ERR);
589 dol_syslog(get_class($this)."::query SQL Error usesavepoint = ".$usesavepoint, LOG_ERR);
590 }
591
592 if ($usesavepoint && $this->transaction_opened) { // Warning, after that errno will be erased
593 @pg_query($this->db, 'ROLLBACK TO SAVEPOINT mysavepoint');
594 }
595 }
596 $this->lastquery = $query;
597 $this->_results = $ret;
598 }
599
600 return $ret;
601 }
602
603 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
610 public function fetch_object($resultset)
611 {
612 // phpcs:enable
613 // If resultset not provided, we take the last used by connection
614 if (!is_resource($resultset) && !is_object($resultset)) {
615 $resultset = $this->_results;
616 }
617 return pg_fetch_object($resultset);
618 }
619
620 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
627 public function fetch_array($resultset)
628 {
629 // phpcs:enable
630 // If resultset not provided, we take the last used by connection
631 if (!is_resource($resultset) && !is_object($resultset)) {
632 $resultset = $this->_results;
633 }
634 return pg_fetch_array($resultset);
635 }
636
637 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
645 public function fetch_row($resultset)
646 {
647 // phpcs:enable
648 // If resultset not provided, we take the last used by connection
649 if (!is_resource($resultset) && !is_object($resultset)) {
650 $resultset = $this->_results;
651 }
652 if (is_bool($resultset)) {
653 return 0;
654 }
655 return pg_fetch_row($resultset); // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal
656 }
657
658 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
666 public function num_rows($resultset)
667 {
668 // phpcs:enable
669 // If resultset not provided, we take the last used by connection
670 if (!is_resource($resultset) && !is_object($resultset)) {
671 $resultset = $this->_results;
672 }
673 // avoid error if $resultset = null or false
674 if ($resultset) {
675 return pg_num_rows($resultset);
676 } else {
677 return 0;
678 } // end of avoid error
679 }
680
681 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
689 public function affected_rows($resultset)
690 {
691 // phpcs:enable
692 // If resultset not provided, we take the last used by connection
693 if (!is_resource($resultset) && !is_object($resultset)) {
694 $resultset = $this->_results;
695 }
696 // pgsql requires a resultset for this function contrary to
697 // mysql that requires a database link
698 return pg_affected_rows($resultset);
699 }
700
701
708 public function free($resultset = null)
709 {
710 // If resultset not provided, we take the last used by connection
711 if (!is_resource($resultset) && !is_object($resultset)) {
712 $resultset = $this->_results;
713 }
714 // If it is a resource, we free the memory
715 if (is_resource($resultset) || is_object($resultset)) {
716 pg_free_result($resultset);
717 }
718 }
719
720
728 public function plimit($limit = 0, $offset = 0)
729 {
730 global $conf;
731 if (empty($limit)) {
732 return "";
733 }
734 if ($limit < 0) {
735 $limit = $conf->liste_limit;
736 }
737 if ($offset > 0) {
738 return " LIMIT ".$limit." OFFSET ".$offset." ";
739 } else {
740 return " LIMIT $limit ";
741 }
742 }
743
744
751 public function escape($stringtoencode)
752 {
753 return pg_escape_string($this->db, (string) $stringtoencode);
754 }
755
762 public function escapeforlike($stringtoencode)
763 {
764 return str_replace(array('\\', '_', '%'), array('\\\\', '\_', '\%'), (string) $stringtoencode);
765 }
766
775 public function ifsql($test, $resok, $resko)
776 {
777 return '(CASE WHEN '.$test.' THEN '.$resok.' ELSE '.$resko.' END)';
778 }
779
788 public function regexpsql($subject, $pattern, $sqlstring = 0)
789 {
790 if ($sqlstring) {
791 return "(". $subject ." ~ '" . $this->escape($pattern) . "')";
792 }
793
794 return "('". $this->escape($subject) ."' ~ '" . $this->escape($pattern) . "')";
795 }
796
797
803 public function errno()
804 {
805 if (!$this->connected) {
806 // If the connection failed, $this->db is not valid.
807 return 'DB_ERROR_FAILED_TO_CONNECT';
808 } else {
809 // Constants to convert error code to a generic Dolibarr error code
810 $errorcode_map = array(
811 1004 => 'DB_ERROR_CANNOT_CREATE',
812 1005 => 'DB_ERROR_CANNOT_CREATE',
813 1006 => 'DB_ERROR_CANNOT_CREATE',
814 1007 => 'DB_ERROR_ALREADY_EXISTS',
815 1008 => 'DB_ERROR_CANNOT_DROP',
816 1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP',
817 1044 => 'DB_ERROR_ACCESSDENIED',
818 1046 => 'DB_ERROR_NODBSELECTED',
819 1048 => 'DB_ERROR_CONSTRAINT',
820 '42P07' => 'DB_ERROR_TABLE_OR_KEY_ALREADY_EXISTS',
821 '42703' => 'DB_ERROR_NOSUCHFIELD',
822 1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
823 42701 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
824 '42710' => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
825 '23505' => 'DB_ERROR_RECORD_ALREADY_EXISTS',
826 '42704' => 'DB_ERROR_NO_INDEX_TO_DROP', // May also be Type xxx does not exists
827 '42601' => 'DB_ERROR_SYNTAX',
828 '42P16' => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS',
829 1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY',
830 1091 => 'DB_ERROR_NOSUCHFIELD',
831 1100 => 'DB_ERROR_NOT_LOCKED',
832 1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW',
833 '42P01' => 'DB_ERROR_NOSUCHTABLE',
834 '23503' => 'DB_ERROR_NO_PARENT',
835 1217 => 'DB_ERROR_CHILD_EXISTS',
836 1451 => 'DB_ERROR_CHILD_EXISTS',
837 '42P04' => 'DB_DATABASE_ALREADY_EXISTS'
838 );
839
840 $errorlabel = pg_last_error($this->db);
841 $errorcode = '';
842 $reg = array();
843 if (preg_match('/: *([0-9P]+):/', $errorlabel, $reg)) {
844 $errorcode = $reg[1];
845 if (isset($errorcode_map[$errorcode])) {
846 return $errorcode_map[$errorcode];
847 }
848 }
849 $errno = $errorcode ? $errorcode : $errorlabel;
850 return ($errno ? 'DB_ERROR_'.$errno : '0');
851 }
852 // '/(Table does not exist\.|Relation [\"\'].*[\"\'] does not exist|sequence does not exist|class ".+" not found)$/' => 'DB_ERROR_NOSUCHTABLE',
853 // '/table [\"\'].*[\"\'] does not exist/' => 'DB_ERROR_NOSUCHTABLE',
854 // '/Relation [\"\'].*[\"\'] already exists|Cannot insert a duplicate key into (a )?unique index.*/' => 'DB_ERROR_RECORD_ALREADY_EXISTS',
855 // '/divide by zero$/' => 'DB_ERROR_DIVZERO',
856 // '/pg_atoi: error in .*: can\'t parse /' => 'DB_ERROR_INVALID_NUMBER',
857 // '/ttribute [\"\'].*[\"\'] not found$|Relation [\"\'].*[\"\'] does not have attribute [\"\'].*[\"\']/' => 'DB_ERROR_NOSUCHFIELD',
858 // '/parser: parse error at or near \"/' => 'DB_ERROR_SYNTAX',
859 // '/referential integrity violation/' => 'DB_ERROR_CONSTRAINT'
860 }
861
867 public function error()
868 {
869 return pg_last_error($this->db);
870 }
871
872 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
880 public function last_insert_id($table, $fieldid = 'rowid')
881 {
882 // phpcs:enable
883 $sequencename = $table."_".$fieldid."_seq";
884
885 //$result = pg_query($this->db,"SELECT MAX(".$fieldid.") FROM ".$table);
886 $result = pg_query($this->db, "SELECT currval('".$sequencename."')");
887 if (!$result) {
888 print pg_last_error($this->db);
889 return -1;
890 }
891 //$nbre = pg_num_rows($result);
892 $row = pg_fetch_result($result, 0, 0);
893 return (int) $row;
894 }
895
904 public function encrypt($fieldorvalue, $withQuotes = 1)
905 {
906 //global $conf;
907
908 // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
909 //$cryptType = ($conf->db->dolibarr_main_db_encryption ? $conf->db->dolibarr_main_db_encryption : 0);
910
911 //Encryption key
912 //$cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
913
914 $return = $fieldorvalue;
915 return ($withQuotes ? "'" : "").$this->escape($return).($withQuotes ? "'" : "");
916 }
917
918
925 public function decrypt($value)
926 {
927 //global $conf;
928
929 // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
930 //$cryptType = ($conf->db->dolibarr_main_db_encryption ? $conf->db->dolibarr_main_db_encryption : 0);
931
932 //Encryption key
933 //$cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
934
935 $return = $value;
936 return $return;
937 }
938
939
940 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
946 public function DDLGetConnectId()
947 {
948 // phpcs:enable
949 return '?';
950 }
951
952
953
954 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
966 public function DDLCreateDb($database, $charset = '', $collation = '', $owner = '')
967 {
968 // phpcs:enable
969 if (empty($charset)) {
970 $charset = $this->forcecharset;
971 }
972 if (empty($collation)) {
973 $collation = $this->forcecollate;
974 }
975
976 // Test charset match LC_TYPE (pgsql error otherwise)
977 //print $charset.' '.setlocale(LC_CTYPE,'0'); exit;
978
979 // NOTE: Do not use ' around the database name
980 $sql = "CREATE DATABASE ".$this->sanitize($database)." OWNER '".$this->escape($owner)."' ENCODING '".$this->escape((string) $charset)."'";
981
982 dol_syslog($sql, LOG_DEBUG);
983 $ret = $this->query($sql);
984
985 return $ret;
986 }
987
988 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
996 public function DDLListTables($database, $table = '')
997 {
998 // phpcs:enable
999 $listtables = array();
1000
1001 $escapedlike = '';
1002 if ($table) {
1003 $tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);
1004
1005 $escapedlike = " AND table_name LIKE '".$this->escape($tmptable)."'";
1006 }
1007 $result = pg_query($this->db, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'".$escapedlike." ORDER BY table_name");
1008 if ($result) {
1009 while ($row = $this->fetch_row($result)) { // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal
1010 $listtables[] = $row[0];
1011 }
1012 }
1013 return $listtables;
1014 }
1015
1016 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1024 public function DDLListTablesFull($database, $table = '')
1025 {
1026 // phpcs:enable
1027 $listtables = array();
1028
1029 $escapedlike = '';
1030 if ($table) {
1031 $tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);
1032
1033 $escapedlike = " AND table_name LIKE '".$this->escape($tmptable)."'";
1034 }
1035 $result = pg_query($this->db, "SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'public'".$escapedlike." ORDER BY table_name");
1036 if ($result) {
1037 while ($row = $this->fetch_row($result)) { // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal
1038 $listtables[] = $row;
1039 }
1040 }
1041 return $listtables;
1042 }
1043
1044 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1051 public function DDLInfoTable($table)
1052 {
1053 // phpcs:enable
1054 $infotables = array();
1055
1056 $sql = "SELECT ";
1057 $sql .= " infcol.column_name as \"Column\","; // pgsql need " for alias names !
1058 $sql .= " CASE WHEN infcol.character_maximum_length IS NOT NULL THEN infcol.udt_name || '('||infcol.character_maximum_length||')'";
1059 $sql .= " ELSE infcol.udt_name";
1060 $sql .= " END as \"Type\","; // pgsql need " for alias names !
1061 $sql .= " infcol.collation_name as \"Collation\","; // pgsql need " for alias names !
1062 $sql .= " infcol.is_nullable as \"Null\","; // pgsql need " for alias names !
1063 $sql .= " '' as \"Key\","; // pgsql need " for alias names !
1064 $sql .= " infcol.column_default as \"Default\","; // pgsql need " for alias names !
1065 $sql .= " '' as \"Extra\","; // pgsql need " for alias names !
1066 $sql .= " '' as \"Privileges\""; // pgsql need " for alias names !
1067 $sql .= " FROM information_schema.columns infcol";
1068 $sql .= " WHERE table_schema = 'public' ";
1069 $sql .= " AND table_name = '".$this->escape($table)."'";
1070 $sql .= " ORDER BY ordinal_position;";
1071
1072 $result = $this->query($sql);
1073 if ($result) {
1074 while ($row = $this->fetch_row($result)) {
1075 $infotables[] = $row;
1076 }
1077 }
1078 return $infotables;
1079 }
1080
1081
1082 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1095 public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null)
1096 {
1097 // phpcs:enable
1098 // @TODO: $fulltext_keys parameter is unused
1099
1100 $sqlk = array();
1101 $sqluq = array();
1102
1103 // Keys found into the array $fields: type,value,attribute,null,default,extra
1104 // ex. : $fields['rowid'] = array(
1105 // 'type'=>'int' or 'integer',
1106 // 'value'=>'11',
1107 // 'null'=>'not null',
1108 // 'extra'=> 'auto_increment'
1109 // );
1110 $sql = "CREATE TABLE ".$this->sanitize($table)."(";
1111 $i = 0;
1112 $sqlfields = array();
1113 foreach ($fields as $field_name => $field_desc) {
1114 $sqlfields[$i] = $this->sanitize($field_name)." ";
1115 $sqlfields[$i] .= $this->sanitize($field_desc['type']);
1116 if (isset($field_desc['value']) && $field_desc['value'] !== '') {
1117 $sqlfields[$i] .= "(".$this->sanitize($field_desc['value']).")";
1118 }
1119 if (isset($field_desc['attribute']) && $field_desc['attribute'] !== '') {
1120 $sqlfields[$i] .= " ".$this->sanitize($field_desc['attribute'], 0, 0, 1); // Allow space to accept attributes like "ON UPDATE CURRENT_TIMESTAMP"
1121 }
1122 if (isset($field_desc['default']) && $field_desc['default'] !== '') {
1123 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1124 $sqlfields[$i] .= " DEFAULT ".((float) $field_desc['default']);
1125 } elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
1126 $sqlfields[$i] .= " DEFAULT ".$this->sanitize($field_desc['default']);
1127 } else {
1128 $sqlfields[$i] .= " DEFAULT '".$this->escape($field_desc['default'])."'";
1129 }
1130 }
1131 if (isset($field_desc['null']) && $field_desc['null'] !== '') {
1132 $sqlfields[$i] .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
1133 }
1134 if (isset($field_desc['extra']) && $field_desc['extra'] !== '') {
1135 $sqlfields[$i] .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
1136 }
1137 if (!empty($primary_key) && $primary_key == $field_name) {
1138 $sqlfields[$i] .= " AUTO_INCREMENT PRIMARY KEY"; // mysql instruction that will be converted by driver late
1139 }
1140 $i++;
1141 }
1142
1143 if (is_array($unique_keys)) {
1144 $i = 0;
1145 foreach ($unique_keys as $key => $value) {
1146 $sqluq[$i] = "UNIQUE KEY '".$this->sanitize($key)."' ('".$this->escape($value)."')";
1147 $i++;
1148 }
1149 }
1150 if (is_array($keys)) {
1151 $i = 0;
1152 foreach ($keys as $key => $value) {
1153 $sqlk[$i] = "KEY ".$this->sanitize($key)." (".$value.")";
1154 $i++;
1155 }
1156 }
1157 $sql .= implode(', ', $sqlfields);
1158 if (!is_array($unique_keys) && $unique_keys != "") {
1159 $sql .= ",".implode(',', $sqluq);
1160 }
1161 if (is_array($keys)) {
1162 $sql .= ",".implode(',', $sqlk);
1163 }
1164 $sql .= ")";
1165 //$sql .= " engine=".$this->sanitize($type);
1166
1167 if (!$this->query($sql, 1)) {
1168 return -1;
1169 } else {
1170 return 1;
1171 }
1172 }
1173
1174 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1181 public function DDLDropTable($table)
1182 {
1183 // phpcs:enable
1184 $tmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);
1185
1186 $sql = "DROP TABLE ".$this->sanitize($tmptable);
1187
1188 if (!$this->query($sql, 1)) {
1189 return -1;
1190 } else {
1191 return 1;
1192 }
1193 }
1194
1195 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1203 public function DDLDescTable($table, $field = "")
1204 {
1205 // phpcs:enable
1206 $sql = "SELECT attname FROM pg_attribute, pg_type WHERE typname = '".$this->escape($table)."' AND attrelid = typrelid";
1207 $sql .= " AND attname NOT IN ('cmin', 'cmax', 'ctid', 'oid', 'tableoid', 'xmin', 'xmax')";
1208 if ($field) {
1209 $sql .= " AND attname = '".$this->escape($field)."'";
1210 }
1211
1212 dol_syslog($sql, LOG_DEBUG);
1213 $this->_results = $this->query($sql);
1214 return $this->_results;
1215 }
1216
1217 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1227 public function DDLAddField($table, $field_name, $field_desc, $field_position = "")
1228 {
1229 // phpcs:enable
1230 // keys looked up in the descriptions array (field_desc): type,value,attribute,null,default,extra
1231 // ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
1232 $sql = "ALTER TABLE ".$this->sanitize($table)." ADD ".$this->sanitize($field_name)." ";
1233
1234 if ($field_desc['type'] !== 'datetimegmt') {
1235 $sql .= $this->sanitize($field_desc['type']);
1236 } else {
1237 $sql .= 'datetime';
1238 }
1239
1240 if (in_array($field_desc['type'], array('varchar')) && array_key_exists('value', $field_desc) && !empty($field_desc['value'])) {
1241 $sql .= "(".$this->sanitize($field_desc['value']).")";
1242 }
1243 if (isset($field_desc['attribute']) && preg_match("/^[^\s]/i", $field_desc['attribute'])) {
1244 $sql .= " ".$this->sanitize($field_desc['attribute']);
1245 }
1246 if (isset($field_desc['null']) && preg_match("/^[^\s]/i", $field_desc['null'])) {
1247 if ($field_desc['null'] == 'NOT NULL') {
1248 $sql .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
1249 } else {
1250 $sql .= " ".$this->sanitize($field_desc['null']);
1251 }
1252 }
1253 if (isset($field_desc['default']) && preg_match("/^[^\s]/i", $field_desc['default'])) {
1254 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1255 $sql .= " DEFAULT ".((float) $field_desc['default']);
1256 } elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
1257 $sql .= " DEFAULT ".$this->sanitize($field_desc['default']);
1258 } else {
1259 $sql .= " DEFAULT '".$this->escape($field_desc['default'])."'";
1260 }
1261 }
1262 if (isset($field_desc['extra']) && preg_match("/^[^\s]/i", $field_desc['extra'])) {
1263 $sql .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
1264 }
1265 $sql .= " ".$this->sanitize($field_position, 0, 0, 1);
1266
1267 dol_syslog(get_class($this)."::DDLAddField ".$sql, LOG_DEBUG);
1268 if ($this->query($sql)) {
1269 return 1;
1270 }
1271 return -1;
1272 }
1273
1274 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1283 public function DDLUpdateField($table, $field_name, $field_desc)
1284 {
1285 // phpcs:enable
1286 $sql = "ALTER TABLE ".$this->sanitize($table);
1287 $sql .= " ALTER COLUMN ".$this->sanitize($field_name)." TYPE ";
1288
1289 if ($field_desc['type'] !== 'datetimegmt') {
1290 $sql .= $this->sanitize($field_desc['type']);
1291 } else {
1292 $sql .= 'datetime';
1293 }
1294
1295 if (in_array($field_desc['type'], array('varchar')) && array_key_exists('value', $field_desc) && !empty($field_desc['value'])) {
1296 $sql .= "(".$this->sanitize($field_desc['value']).")";
1297 }
1298
1299 if (isset($field_desc['null']) && ($field_desc['null'] == 'not null' || $field_desc['null'] == 'NOT NULL')) {
1300 // We will try to change format of column to NOT NULL. To be sure the ALTER works, we try to update fields that are NULL
1301 if ($field_desc['type'] == 'varchar' || $field_desc['type'] == 'text') {
1302 $sqlbis = "UPDATE ".$this->sanitize($table)." SET ".$this->sanitize($field_name)." = '".$this->escape(isset($field_desc['default']) ? $field_desc['default'] : '')."' WHERE ".$this->sanitize($field_name)." IS NULL";
1303 $this->query($sqlbis);
1304 } elseif (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1305 $sqlbis = "UPDATE ".$this->sanitize($table)." SET ".$this->sanitize($field_name)." = ".((float) $this->escape(isset($field_desc['default']) ? $field_desc['default'] : 0))." WHERE ".$this->sanitize($field_name)." IS NULL";
1306 $this->query($sqlbis);
1307 }
1308 }
1309
1310 if (isset($field_desc['default']) && $field_desc['default'] != '') {
1311 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1312 $sql .= ", ALTER COLUMN ".$this->sanitize($field_name)." SET DEFAULT ".((float) $field_desc['default']);
1313 } elseif ($field_desc['type'] != 'text') { // Default not supported on text fields ?
1314 $sql .= ", ALTER COLUMN ".$this->sanitize($field_name)." SET DEFAULT '".$this->escape($field_desc['default'])."'";
1315 }
1316 }
1317
1318 dol_syslog($sql, LOG_DEBUG);
1319 if (!$this->query($sql)) {
1320 return -1;
1321 }
1322 return 1;
1323 }
1324
1325 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1333 public function DDLDropField($table, $field_name)
1334 {
1335 // phpcs:enable
1336 $tmp_field_name = preg_replace('/[^a-z0-9\.\-\_]/i', '', $field_name);
1337
1338 $sql = "ALTER TABLE ".$this->sanitize($table)." DROP COLUMN ".$this->sanitize($tmp_field_name);
1339 if (!$this->query($sql)) {
1340 $this->error = $this->lasterror();
1341 return -1;
1342 }
1343 return 1;
1344 }
1345
1346 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1356 public function DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name)
1357 {
1358 // phpcs:enable
1359 // Note: using ' on user does not works with pgsql
1360 $sql = "CREATE USER ".$this->sanitize($dolibarr_main_db_user)." with password '".$this->escape($dolibarr_main_db_pass)."'";
1361
1362 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
1363 $resql = $this->query($sql);
1364 if (!$resql) {
1365 return -1;
1366 }
1367
1368 return 1;
1369 }
1370
1377 {
1378 $resql = $this->query('SHOW SERVER_ENCODING');
1379 if ($resql) {
1380 $liste = $this->fetch_array($resql);
1381 return $liste['server_encoding'];
1382 } else {
1383 return '';
1384 }
1385 }
1386
1392 public function getListOfCharacterSet()
1393 {
1394 $resql = $this->query('SHOW SERVER_ENCODING');
1395 $liste = array();
1396 if ($resql) {
1397 $i = 0;
1398 while ($obj = $this->fetch_object($resql)) {
1399 $liste[$i]['charset'] = $obj->server_encoding;
1400 $liste[$i]['description'] = 'Default database charset';
1401 $i++;
1402 }
1403 $this->free($resql);
1404 } else {
1405 return null;
1406 }
1407 return $liste;
1408 }
1409
1416 {
1417 $resql = $this->query('SHOW LC_COLLATE');
1418 if ($resql) {
1419 $liste = $this->fetch_array($resql);
1420 return $liste['lc_collate'];
1421 } else {
1422 return '';
1423 }
1424 }
1425
1431 public function getListOfCollation()
1432 {
1433 $resql = $this->query('SHOW LC_COLLATE');
1434 $liste = array();
1435 if ($resql) {
1436 $i = 0;
1437 while ($obj = $this->fetch_object($resql)) {
1438 $liste[$i]['collation'] = $obj->lc_collate;
1439 $i++;
1440 }
1441 $this->free($resql);
1442 } else {
1443 return null;
1444 }
1445 return $liste;
1446 }
1447
1453 public function getPathOfDump()
1454 {
1455 $fullpathofdump = '/pathtopgdump/pg_dump';
1456
1457 if (file_exists('/usr/bin/pg_dump')) {
1458 $fullpathofdump = '/usr/bin/pg_dump';
1459 } else {
1460 // TODO The database user must be a superadmin to run this command
1461 $resql = $this->query('SHOW data_directory');
1462 if ($resql) {
1463 $liste = $this->fetch_array($resql);
1464 $basedir = $liste['data_directory'];
1465 $fullpathofdump = preg_replace('/data$/', 'bin', $basedir).'/pg_dump';
1466 }
1467 }
1468
1469 return $fullpathofdump;
1470 }
1471
1477 public function getPathOfRestore()
1478 {
1479 //$tool='pg_restore';
1480 $tool = 'psql';
1481
1482 $fullpathofdump = '/pathtopgrestore/'.$tool;
1483
1484 if (file_exists('/usr/bin/'.$tool)) {
1485 $fullpathofdump = '/usr/bin/'.$tool;
1486 } else {
1487 // TODO The database user must be a superadmin to run this command
1488 $resql = $this->query('SHOW data_directory');
1489 if ($resql) {
1490 $liste = $this->fetch_array($resql);
1491 $basedir = $liste['data_directory'];
1492 $fullpathofdump = preg_replace('/data$/', 'bin', $basedir).'/'.$tool;
1493 }
1494 }
1495
1496 return $fullpathofdump;
1497 }
1498
1505 public function getServerParametersValues($filter = '')
1506 {
1507 $result = array();
1508
1509 $resql = 'select name,setting from pg_settings';
1510 if ($filter) {
1511 $resql .= " WHERE name = '".$this->escape($filter)."'";
1512 }
1513 $resql = $this->query($resql);
1514 if ($resql) {
1515 while ($obj = $this->fetch_object($resql)) {
1516 $result[$obj->name] = $obj->setting;
1517 }
1518 }
1519
1520 return $result;
1521 }
1522
1529 public function getServerStatusValues($filter = '')
1530 {
1531 /* This is to return current running requests.
1532 $sql='SELECT datname,procpid,current_query FROM pg_stat_activity ORDER BY procpid';
1533 if ($filter) $sql.=" LIKE '".$this->escape($filter)."'";
1534 $resql=$this->query($sql);
1535 if ($resql)
1536 {
1537 $obj=$this->fetch_object($resql);
1538 $result[$obj->Variable_name]=$obj->Value;
1539 }
1540 */
1541
1542 return array();
1543 }
1544
1551 public function getNextAutoIncrementId($table)
1552 {
1553 return $this->last_insert_id($table, 'rowid') + 1;
1554 }
1555
1556
1567 public function prepare($sql)
1568 {
1569 dol_syslog(get_class($this)."::prepare sql=".$sql, LOG_DEBUG);
1570
1571 // Translate '?' -> '$1', '$2', ... while skipping single-quoted string literals
1572 $translated = '';
1573 $num = 0;
1574 $len = strlen($sql);
1575 $inquote = false;
1576 for ($i = 0; $i < $len; $i++) {
1577 $c = $sql[$i];
1578 if ($c === "'") {
1579 if ($inquote && $i + 1 < $len && $sql[$i + 1] === "'") {
1580 // '' is an escaped quote inside a literal
1581 $translated .= "''";
1582 $i++;
1583 continue;
1584 }
1585 $inquote = !$inquote;
1586 $translated .= $c;
1587 continue;
1588 }
1589 if ($c === '?' && !$inquote) {
1590 $num++;
1591 $translated .= '$'.$num;
1592 continue;
1593 }
1594 $translated .= $c;
1595 }
1596
1597 $stmtname = 'dolipgstmt_' . bin2hex(random_bytes(8)); // Generate a unique identifier for the statement
1598
1599 $result = @pg_prepare($this->db, $stmtname, $translated);
1600 if (!$result) {
1601 $this->lasterror = pg_last_error($this->db);
1602 $this->lastqueryerror = $sql;
1603 return false;
1604 }
1605
1606 return $stmtname; // We just return the name of the prepared statement
1607 }
1608
1619 public function execute($stmt, $params = array())
1620 {
1621 if (!is_string($stmt) || $stmt === '') {
1622 $this->lasterror = 'execute() called with an invalid statement';
1623 return false;
1624 }
1625
1626 $this->lasterror = '';
1627 $this->lastqueryerror = '';
1628
1629 // pg_execute() wants an ordered array of scalars; map bool -> 't'/'f', keep null as null
1630 $values = array();
1631 foreach (array_values($params) as $v) {
1632 $values[] = is_bool($v) ? ($v ? 't' : 'f') : $v;
1633 }
1634
1635 dol_syslog(get_class($this)."::execute ".$stmt." (".count($values)." bound param(s))", LOG_DEBUG);
1636
1637 $res = @pg_execute($this->db, $stmt, $values);
1638 if ($res === false) {
1639 $this->lasterror = pg_last_error($this->db);
1640 $this->lastqueryerror = $stmt;
1641 return false;
1642 }
1643
1644 $this->_results = $res;
1645
1646 // A SELECT (or INSERT ... RETURNING) has fields to fetch; a plain DML statement does not.
1647 return (pg_num_fields($res) > 0) ? $res : true;
1648 }
1649}
$c
Definition line.php:346
Class to manage Dolibarr database access.
lastqueryerror()
Return last query in error.
lasterror()
Return last error label.
lasterrno()
Return last error code.
lastquery()
Return last request executed with query()
sanitize($stringtosanitize, $allowsimplequote=0, $allowsequals=0, $allowsspace=0, $allowschars=1)
Sanitize a string for SQL forging.
Class to drive a PostgreSQL database for Dolibarr.
errno()
Renvoie le code erreur generique de l'operation precedente.
DDLListTablesFull($database, $table='')
List tables into a database.
DDLGetConnectId()
Return connection ID.
num_rows($resultset)
Return number of lines for result of a SELECT.
const VERSIONMIN
Version min database.
DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name)
Create a user to connect to database.
DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys=null, $fulltext_keys=null, $keys=null)
Create a table into database.
getNextAutoIncrementId($table)
Get the last ID of an auto-increment field of a table.
DDLDropTable($table)
Drop a table into database.
DDLUpdateField($table, $field_name, $field_desc)
Update format of a field into a table.
getPathOfDump()
Return full path of dump program.
select_db($database)
Select a database PostgreSQL does not have an equivalent for mysql_select_db Only compare if the chos...
getServerStatusValues($filter='')
Return value of server status.
plimit($limit=0, $offset=0)
Define limits and offset of request.
decrypt($value)
Decrypt sensitive data in database.
error()
Renvoie le texte de l'erreur pgsql de l'operation precedente.
escape($stringtoencode)
Escape a string to insert data.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Convert request to PostgreSQL syntax, execute it and return the resultset.
fetch_object($resultset)
Returns the current line (as an object) for the resultset cursor.
close()
Close database connection.
encrypt($fieldorvalue, $withQuotes=1)
Encrypt sensitive data in database Warning: This function includes the escape and add the SQL simple ...
getListOfCharacterSet()
Return list of available charset that can be used to store data in database.
fetch_array($resultset)
Return data as an array.
getPathOfRestore()
Return full path of restore program.
DDLAddField($table, $field_name, $field_desc, $field_position="")
Create a new field into table.
connect($host, $login, $passwd, $name, $port=0, $forcenew=false)
Connection to server.
$type
Database type.
DDLInfoTable($table)
List information of columns in a table.
__construct($type, $host, $user, $pass, $name='', $port=0, $forcenew=false)
Constructor.
getVersion()
Return version of database server.
$forcecharset
Charset.
escapeforlike($stringtoencode)
Escape a string to insert data into a like.
last_insert_id($table, $fieldid='rowid')
Get last ID after an insert INSERT.
affected_rows($resultset)
Return the number of rows in the result of a request INSERT, DELETE or UPDATE.
DDLDropField($table, $field_name)
Drop a field from table.
regexpsql($subject, $pattern, $sqlstring=0)
Format a SQL REGEXP.
DDLCreateDb($database, $charset='', $collation='', $owner='')
Create a new database Do not use function xxx_create_db (xxx=mysql, ...) as they are deprecated We fo...
const LABEL
Database label.
getListOfCollation()
Return list of available collation that can be used for database.
getDriverInfo()
Return version of database client driver.
free($resultset=null)
Free the last pointer resultset used by this connection.
DDLDescTable($table, $field="")
Return a pointer of line with description of a table or field.
ifsql($test, $resok, $resko)
Format a SQL IF.
getDefaultCollationDatabase()
Return collation used in database.
execute($stmt, $params=array())
Execute a statement previously created with prepare().
convertSQLFromMysql($line, $type='auto', $unescapeslashquot=false)
Convert a SQL request in Mysql syntax to native syntax.
getDefaultCharacterSetDatabase()
Return charset used to store data in database.
$forcecollate
Collation used to force collate when creating database.
fetch_row($resultset)
Return datas as an array.
getServerParametersValues($filter='')
Return value of server parameters.
prepare($sql)
Prepare a SQL statement for execution (PostgreSQL prepared statement).
DDLListTables($database, $table='')
List tables into a database.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.