Passed
Branch develop (07a336)
by
unknown
39:19
created

DoliDBPgsql::DDLInfoTable()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 22
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 31
rs 9.568
1
<?php
2
/* Copyright (C) 2001		Fabien Seisen			<[email protected]>
3
 * Copyright (C) 2002-2005	Rodolphe Quiedeville	<[email protected]>
4
 * Copyright (C) 2004-2014	Laurent Destailleur		<[email protected]>
5
 * Copyright (C) 2004		Sebastien Di Cintio		<[email protected]>
6
 * Copyright (C) 2004		Benoit Mortier			<[email protected]>
7
 * Copyright (C) 2005-2012	Regis Houssin			<[email protected]>
8
 * Copyright (C) 2012		Yann Droneaud			<[email protected]>
9
 * Copyright (C) 2012		Florian Henry			<[email protected]>
10
 * Copyright (C) 2015       Marcos García           <[email protected]>
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 3 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24
 */
25
26
/**
27
 *	\file       htdocs/core/db/pgsql.class.php
28
 *	\brief      Fichier de la classe permettant de gerer une base pgsql
29
 */
30
31
require_once DOL_DOCUMENT_ROOT.'/core/db/DoliDB.class.php';
32
33
/**
34
 *	Class to drive a Postgresql database for Dolibarr
35
 */
36
class DoliDBPgsql extends DoliDB
37
{
38
	//! Database type
39
	public $type = 'pgsql'; // Name of manager
40
	//! Database label
41
	const LABEL = 'PostgreSQL'; // Label of manager
42
	//! Charset
43
	public $forcecharset = 'UTF8'; // Can't be static as it may be forced with a dynamic value
44
	//! Collate used to force collate when creating database
45
	public $forcecollate = ''; // Can't be static as it may be forced with a dynamic value
46
	//! Version min database
47
	const VERSIONMIN = '9.0.0'; // Version min database
48
	/** @var resource Resultset of last query */
49
	private $_results;
50
51
	public $unescapeslashquot;
52
	public $standard_conforming_strings;
53
54
	/**
55
	 *	Constructor.
56
	 *	This create an opened connexion to a database server and eventually to a database
57
	 *
58
	 *	@param      string	$type		Type of database (mysql, pgsql...)
59
	 *	@param	    string	$host		Address of database server
60
	 *	@param	    string	$user		Nom de l'utilisateur autorise
61
	 *	@param	    string	$pass		Mot de passe
62
	 *	@param	    string	$name		Nom de la database
63
	 *	@param	    int		$port		Port of database server
64
	 */
65
	public function __construct($type, $host, $user, $pass, $name = '', $port = 0)
66
	{
67
		global $conf, $langs;
68
69
		// Note that having "static" property for "$forcecharset" and "$forcecollate" will make error here in strict mode, so they are not static
70
		if (!empty($conf->db->character_set)) $this->forcecharset = $conf->db->character_set;
71
		if (!empty($conf->db->dolibarr_main_db_collation))	$this->forcecollate = $conf->db->dolibarr_main_db_collation;
72
73
		$this->database_user = $user;
74
		$this->database_host = $host;
75
		$this->database_port = $port;
76
77
		$this->transaction_opened = 0;
78
79
		//print "Name DB: $host,$user,$pass,$name<br>";
80
81
		if (!function_exists("pg_connect"))
82
		{
83
			$this->connected = false;
84
			$this->ok = false;
85
			$this->error = "Pgsql PHP functions are not available in this version of PHP";
86
			dol_syslog(get_class($this)."::DoliDBPgsql : Pgsql PHP functions are not available in this version of PHP", LOG_ERR);
87
			return $this->ok;
88
		}
89
90
		if (!$host)
91
		{
92
			$this->connected = false;
93
			$this->ok = false;
94
			$this->error = $langs->trans("ErrorWrongHostParameter");
95
			dol_syslog(get_class($this)."::DoliDBPgsql : Erreur Connect, wrong host parameters", LOG_ERR);
96
			return $this->ok;
97
		}
98
99
		// Essai connexion serveur
100
		//print "$host, $user, $pass, $name, $port";
101
		$this->db = $this->connect($host, $user, $pass, $name, $port);
102
103
		if ($this->db)
104
		{
105
			$this->connected = true;
106
			$this->ok = true;
107
		} else {
108
			// host, login ou password incorrect
109
			$this->connected = false;
110
			$this->ok = false;
111
			$this->error = 'Host, login or password incorrect';
112
			dol_syslog(get_class($this)."::DoliDBPgsql : Erreur Connect ".$this->error, LOG_ERR);
113
		}
114
115
		// Si connexion serveur ok et si connexion base demandee, on essaie connexion base
116
		if ($this->connected && $name)
117
		{
118
			if ($this->select_db($name))
119
			{
120
				$this->database_selected = true;
121
				$this->database_name = $name;
122
				$this->ok = true;
123
			} else {
124
				$this->database_selected = false;
125
				$this->database_name = '';
126
				$this->ok = false;
127
				$this->error = $this->error();
128
				dol_syslog(get_class($this)."::DoliDBPgsql : Erreur Select_db ".$this->error, LOG_ERR);
129
			}
130
		} else {
131
			// Pas de selection de base demandee, ok ou ko
132
			$this->database_selected = false;
133
		}
134
135
		return $this->ok;
136
	}
137
138
139
	/**
140
	 *  Convert a SQL request in Mysql syntax to native syntax
141
	 *
142
	 *  @param  string	$line   			SQL request line to convert
143
	 *  @param  string	$type				Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...)
144
	 *  @param	bool	$unescapeslashquot	Unescape slash quote with quote quote
145
	 *  @return string   					SQL request line converted
146
	 */
147
	public static function convertSQLFromMysql($line, $type = 'auto', $unescapeslashquot = false)
148
	{
149
		global $conf;
150
151
		// Removed empty line if this is a comment line for SVN tagging
152
		if (preg_match('/^--\s\$Id/i', $line)) {
153
			return '';
154
		}
155
		// Return line if this is a comment
156
		if (preg_match('/^#/i', $line) || preg_match('/^$/i', $line) || preg_match('/^--/i', $line))
157
		{
158
			return $line;
159
		}
160
		if ($line != "")
161
		{
162
			// group_concat support (PgSQL >= 9.0)
163
			// Replace group_concat(x) or group_concat(x SEPARATOR ',') with string_agg(x, ',')
164
			$line = preg_replace('/GROUP_CONCAT/i', 'STRING_AGG', $line);
165
			$line = preg_replace('/ SEPARATOR/i', ',', $line);
166
			$line = preg_replace('/STRING_AGG\(([^,\)]+)\)/i', 'STRING_AGG(\\1, \',\')', $line);
167
			//print $line."\n";
168
169
			if ($type == 'auto')
170
			{
171
				if (preg_match('/ALTER TABLE/i', $line)) $type = 'dml';
172
				elseif (preg_match('/CREATE TABLE/i', $line)) $type = 'dml';
173
				elseif (preg_match('/DROP TABLE/i', $line)) $type = 'dml';
174
			}
175
176
			$line = preg_replace('/ as signed\)/i', ' as integer)', $line);
177
178
			if ($type == 'dml')
179
			{
180
				$reg = array();
181
182
				$line = preg_replace('/\s/', ' ', $line); // Replace tabulation with space
183
184
				// we are inside create table statement so lets process datatypes
185
				if (preg_match('/(ISAM|innodb)/i', $line)) { // end of create table sequence
186
					$line = preg_replace('/\)[\s\t]*type[\s\t]*=[\s\t]*(MyISAM|innodb).*;/i', ');', $line);
187
					$line = preg_replace('/\)[\s\t]*engine[\s\t]*=[\s\t]*(MyISAM|innodb).*;/i', ');', $line);
188
					$line = preg_replace('/,$/', '', $line);
189
				}
190
191
				// Process case: "CREATE TABLE llx_mytable(rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY,code..."
192
				if (preg_match('/[\s\t\(]*(\w*)[\s\t]+int.*auto_increment/i', $line, $reg)) {
193
					$newline = preg_replace('/([\s\t\(]*)([a-zA-Z_0-9]*)[\s\t]+int.*auto_increment[^,]*/i', '\\1 \\2 SERIAL PRIMARY KEY', $line);
194
					//$line = "-- ".$line." replaced by --\n".$newline;
195
					$line = $newline;
196
				}
197
198
				if (preg_match('/[\s\t\(]*(\w*)[\s\t]+bigint.*auto_increment/i', $line, $reg)) {
199
					$newline = preg_replace('/([\s\t\(]*)([a-zA-Z_0-9]*)[\s\t]+bigint.*auto_increment[^,]*/i', '\\1 \\2 BIGSERIAL PRIMARY KEY', $line);
200
					//$line = "-- ".$line." replaced by --\n".$newline;
201
					$line = $newline;
202
				}
203
204
				// tinyint type conversion
205
				$line = preg_replace('/tinyint\(?[0-9]*\)?/', 'smallint', $line);
206
				$line = preg_replace('/tinyint/i', 'smallint', $line);
207
208
				// nuke unsigned
209
				$line = preg_replace('/(int\w+|smallint)\s+unsigned/i', '\\1', $line);
210
211
				// blob -> text
212
				$line = preg_replace('/\w*blob/i', 'text', $line);
213
214
				// tinytext/mediumtext -> text
215
				$line = preg_replace('/tinytext/i', 'text', $line);
216
				$line = preg_replace('/mediumtext/i', 'text', $line);
217
				$line = preg_replace('/longtext/i', 'text', $line);
218
219
				$line = preg_replace('/text\([0-9]+\)/i', 'text', $line);
220
221
				// change not null datetime field to null valid ones
222
				// (to support remapping of "zero time" to null
223
				$line = preg_replace('/datetime not null/i', 'datetime', $line);
224
				$line = preg_replace('/datetime/i', 'timestamp', $line);
225
226
				// double -> numeric
227
				$line = preg_replace('/^double/i', 'numeric', $line);
228
				$line = preg_replace('/(\s*)double/i', '\\1numeric', $line);
229
				// float -> numeric
230
				$line = preg_replace('/^float/i', 'numeric', $line);
231
				$line = preg_replace('/(\s*)float/i', '\\1numeric', $line);
232
233
				//Check tms timestamp field case (in Mysql this field is defautled to now and
234
				// on update defaulted by now
235
				$line = preg_replace('/(\s*)tms(\s*)timestamp/i', '\\1tms timestamp without time zone DEFAULT now() NOT NULL', $line);
236
237
				// nuke DEFAULT CURRENT_TIMESTAMP
238
				$line = preg_replace('/(\s*)DEFAULT(\s*)CURRENT_TIMESTAMP/i', '\\1', $line);
239
240
				// nuke ON UPDATE CURRENT_TIMESTAMP
241
				$line = preg_replace('/(\s*)ON(\s*)UPDATE(\s*)CURRENT_TIMESTAMP/i', '\\1', $line);
242
243
				// unique index(field1,field2)
244
				if (preg_match('/unique index\s*\((\w+\s*,\s*\w+)\)/i', $line))
245
				{
246
					$line = preg_replace('/unique index\s*\((\w+\s*,\s*\w+)\)/i', 'UNIQUE\(\\1\)', $line);
247
				}
248
249
				// We remove end of requests "AFTER fieldxxx"
250
				$line = preg_replace('/\sAFTER [a-z0-9_]+/i', '', $line);
251
252
				// We remove start of requests "ALTER TABLE tablexxx" if this is a DROP INDEX
253
				$line = preg_replace('/ALTER TABLE [a-z0-9_]+\s+DROP INDEX/i', 'DROP INDEX', $line);
254
255
				// Translate order to rename fields
256
				if (preg_match('/ALTER TABLE ([a-z0-9_]+)\s+CHANGE(?: COLUMN)? ([a-z0-9_]+) ([a-z0-9_]+)(.*)$/i', $line, $reg))
257
				{
258
					$line = "-- ".$line." replaced by --\n";
259
					$line .= "ALTER TABLE ".$reg[1]." RENAME COLUMN ".$reg[2]." TO ".$reg[3];
260
				}
261
262
				// Translate order to modify field format
263
				if (preg_match('/ALTER TABLE ([a-z0-9_]+)\s+MODIFY(?: COLUMN)? ([a-z0-9_]+) (.*)$/i', $line, $reg))
264
				{
265
					$line = "-- ".$line." replaced by --\n";
266
					$newreg3 = $reg[3];
267
					$newreg3 = preg_replace('/ DEFAULT NULL/i', '', $newreg3);
268
					$newreg3 = preg_replace('/ NOT NULL/i', '', $newreg3);
269
					$newreg3 = preg_replace('/ NULL/i', '', $newreg3);
270
					$newreg3 = preg_replace('/ DEFAULT 0/i', '', $newreg3);
271
					$newreg3 = preg_replace('/ DEFAULT \'?[0-9a-zA-Z_@]*\'?/i', '', $newreg3);
272
					$line .= "ALTER TABLE ".$reg[1]." ALTER COLUMN ".$reg[2]." TYPE ".$newreg3;
273
					// TODO Add alter to set default value or null/not null if there is this in $reg[3]
274
				}
275
276
				// alter table add primary key (field1, field2 ...) -> We remove the primary key name not accepted by PostGreSQL
277
				// ALTER TABLE llx_dolibarr_modules ADD PRIMARY KEY pk_dolibarr_modules (numero, entity)
278
				if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+PRIMARY\s+KEY\s*(.*)\s*\((.*)$/i', $line, $reg))
279
				{
280
					$line = "-- ".$line." replaced by --\n";
281
					$line .= "ALTER TABLE ".$reg[1]." ADD PRIMARY KEY (".$reg[3];
282
				}
283
284
				// Translate order to drop primary keys
285
				// ALTER TABLE llx_dolibarr_modules DROP PRIMARY KEY pk_xxx
286
				if (preg_match('/ALTER\s+TABLE\s*(.*)\s*DROP\s+PRIMARY\s+KEY\s*([^;]+)$/i', $line, $reg))
287
				{
288
					$line = "-- ".$line." replaced by --\n";
289
					$line .= "ALTER TABLE ".$reg[1]." DROP CONSTRAINT ".$reg[2];
290
				}
291
292
				// Translate order to drop foreign keys
293
				// ALTER TABLE llx_dolibarr_modules DROP FOREIGN KEY fk_xxx
294
				if (preg_match('/ALTER\s+TABLE\s*(.*)\s*DROP\s+FOREIGN\s+KEY\s*(.*)$/i', $line, $reg))
295
				{
296
					$line = "-- ".$line." replaced by --\n";
297
					$line .= "ALTER TABLE ".$reg[1]." DROP CONSTRAINT ".$reg[2];
298
				}
299
300
				// Translate order to add foreign keys
301
				// ALTER TABLE llx_tablechild ADD CONSTRAINT fk_tablechild_fk_fieldparent FOREIGN KEY (fk_fieldparent) REFERENCES llx_tableparent (rowid)
302
				if (preg_match('/ALTER\s+TABLE\s+(.*)\s*ADD CONSTRAINT\s+(.*)\s*FOREIGN\s+KEY\s*(.*)$/i', $line, $reg))
303
				{
304
					$line = preg_replace('/;$/', '', $line);
305
					$line .= " DEFERRABLE INITIALLY IMMEDIATE;";
306
				}
307
308
				// alter table add [unique] [index] (field1, field2 ...)
309
				// ALTER TABLE llx_accountingaccount ADD INDEX idx_accountingaccount_fk_pcg_version (fk_pcg_version)
310
				if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+(UNIQUE INDEX|INDEX|UNIQUE)\s+(.*)\s*\(([\w,\s]+)\)/i', $line, $reg))
311
				{
312
					$fieldlist = $reg[4];
313
					$idxname = $reg[3];
314
					$tablename = $reg[1];
315
					$line = "-- ".$line." replaced by --\n";
316
					$line .= "CREATE ".(preg_match('/UNIQUE/', $reg[2]) ? 'UNIQUE ' : '')."INDEX ".$idxname." ON ".$tablename." (".$fieldlist.")";
317
				}
318
			}
319
320
			// To have postgresql case sensitive
321
			$count_like = 0;
322
			$line = str_replace(' LIKE \'', ' ILIKE \'', $line, $count_like);
323
			if (!empty($conf->global->PSQL_USE_UNACCENT) && $count_like > 0)
324
			{
325
				// @see https://docs.postgresql.fr/11/unaccent.html : 'unaccent()' function must be installed before
326
				$line = preg_replace('/\s+(\(+\s*)([a-zA-Z0-9\-\_\.]+) ILIKE /', ' \1unaccent(\2) ILIKE ', $line);
327
			}
328
329
			$line = str_replace(' LIKE BINARY \'', ' LIKE \'', $line);
330
331
			// Replace INSERT IGNORE into INSERT
332
			$line = preg_replace('/^INSERT IGNORE/', 'INSERT', $line);
333
334
			// Delete using criteria on other table must not declare twice the deleted table
335
			// DELETE FROM tabletodelete USING tabletodelete, othertable -> DELETE FROM tabletodelete USING othertable
336
			if (preg_match('/DELETE FROM ([a-z_]+) USING ([a-z_]+), ([a-z_]+)/i', $line, $reg))
337
			{
338
				if ($reg[1] == $reg[2])	// If same table, we remove second one
339
				{
340
					$line = preg_replace('/DELETE FROM ([a-z_]+) USING ([a-z_]+), ([a-z_]+)/i', 'DELETE FROM \\1 USING \\3', $line);
341
				}
342
			}
343
344
			// Remove () in the tables in FROM if 1 table
345
			$line = preg_replace('/FROM\s*\((([a-z_]+)\s+as\s+([a-z_]+)\s*)\)/i', 'FROM \\1', $line);
346
			//print $line."\n";
347
348
			// Remove () in the tables in FROM if 2 table
349
			$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);
350
			//print $line."\n";
351
352
			// Remove () in the tables in FROM if 3 table
353
			$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);
354
			//print $line."\n";
355
356
			// Remove () in the tables in FROM if 4 table
357
			$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);
358
			//print $line."\n";
359
360
			// Remove () in the tables in FROM if 5 table
361
			$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);
362
			//print $line."\n";
363
364
			// Replace espacing \' by ''.
365
			// By default we do not (should be already done by db->escape function if required
366
			// except for sql insert in data file that are mysql escaped so we removed them to
367
			// be compatible with standard_conforming_strings=on that considers \ as ordinary character).
368
			if ($unescapeslashquot) $line = preg_replace("/\\\'/", "''", $line);
369
370
			//print "type=".$type." newline=".$line."<br>\n";
371
		}
372
373
		return $line;
374
	}
375
376
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
377
	/**
378
	 *  Select a database
379
	 *  Ici postgresql n'a aucune fonction equivalente de mysql_select_db
380
	 *  On compare juste manuellement si la database choisie est bien celle activee par la connexion
381
	 *
382
	 *	@param	    string	$database	Name of database
383
	 *	@return	    bool				true if OK, false if KO
384
	 */
385
	public function select_db($database)
386
	{
387
		// phpcs:enable
388
		if ($database == $this->database_name) {
389
			return true;
390
		} else {
391
			return false;
392
		}
393
	}
394
395
	/**
396
	 *	Connexion to server
397
	 *
398
	 *	@param	    string		$host		Database server host
399
	 *	@param	    string		$login		Login
400
	 *	@param	    string		$passwd		Password
401
	 *	@param		string		$name		Name of database (not used for mysql, used for pgsql)
402
	 *	@param		integer		$port		Port of database server
403
	 *	@return		bool|resource			Database access handler
404
	 *	@see		close()
405
	 */
406
	public function connect($host, $login, $passwd, $name, $port = 0)
407
	{
408
		// use pg_pconnect() instead of pg_connect() if you want to use persistent connection costing 1ms, instead of 30ms for non persistent
409
410
		$this->db = false;
411
412
		// connections parameters must be protected (only \ and ' according to pg_connect() manual)
413
		$host = str_replace(array("\\", "'"), array("\\\\", "\\'"), $host);
414
		$login = str_replace(array("\\", "'"), array("\\\\", "\\'"), $login);
415
		$passwd = str_replace(array("\\", "'"), array("\\\\", "\\'"), $passwd);
416
		$name = str_replace(array("\\", "'"), array("\\\\", "\\'"), $name);
417
		$port = str_replace(array("\\", "'"), array("\\\\", "\\'"), $port);
418
419
		if (!$name) $name = "postgres"; // When try to connect using admin user
420
421
		// try first Unix domain socket (local)
422
		if ((!empty($host) && $host == "socket") && !defined('NOLOCALSOCKETPGCONNECT'))
423
		{
424
			$con_string = "dbname='".$name."' user='".$login."' password='".$passwd."'"; // $name may be empty
425
			$this->db = @pg_connect($con_string);
426
		}
427
428
		// if local connection failed or not requested, use TCP/IP
429
		if (!$this->db)
430
		{
431
			if (!$host) $host = "localhost";
432
			if (!$port) $port = 5432;
433
434
			$con_string = "host='".$host."' port='".$port."' dbname='".$name."' user='".$login."' password='".$passwd."'";
435
			$this->db = @pg_connect($con_string);
436
		}
437
438
		// now we test if at least one connect method was a success
439
		if ($this->db)
440
		{
441
			$this->database_name = $name;
442
			pg_set_error_verbosity($this->db, PGSQL_ERRORS_VERBOSE); // Set verbosity to max
443
			pg_query($this->db, "set datestyle = 'ISO, YMD';");
444
		}
445
446
		return $this->db;
447
	}
448
449
	/**
450
	 *	Return version of database server
451
	 *
452
	 *	@return	        string      Version string
453
	 */
454
	public function getVersion()
455
	{
456
		$resql = $this->query('SHOW server_version');
457
		if ($resql)
458
		{
459
			$liste = $this->fetch_array($resql);
460
			return $liste['server_version'];
461
		}
462
		return '';
463
	}
464
465
	/**
466
	 *	Return version of database client driver
467
	 *
468
	 *	@return	        string      Version string
469
	 */
470
	public function getDriverInfo()
471
	{
472
		return 'pgsql php driver';
473
	}
474
475
	/**
476
	 *  Close database connexion
477
	 *
478
	 *  @return     boolean     True if disconnect successfull, false otherwise
479
	 *  @see        connect()
480
	 */
481
	public function close()
482
	{
483
		if ($this->db)
484
		{
485
			if ($this->transaction_opened > 0) dol_syslog(get_class($this)."::close Closing a connection with an opened transaction depth=".$this->transaction_opened, LOG_ERR);
486
			$this->connected = false;
487
			return pg_close($this->db);
488
		}
489
		return false;
490
	}
491
492
	/**
493
	 * Convert request to PostgreSQL syntax, execute it and return the resultset
494
	 *
495
	 * @param	string	$query			SQL query string
496
	 * @param	int		$usesavepoint	0=Default mode, 1=Run a savepoint before and a rollback to savepoint if error (this allow to have some request with errors inside global transactions).
497
	 * @param   string	$type           Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...)
498
	 * @return	false|resource			Resultset of answer
499
	 */
500
	public function query($query, $usesavepoint = 0, $type = 'auto')
501
	{
502
		global $conf;
503
504
		$query = trim($query);
505
506
		// Convert MySQL syntax to PostgresSQL syntax
507
		$query = $this->convertSQLFromMysql($query, $type, ($this->unescapeslashquot && $this->standard_conforming_strings));
508
		//print "After convertSQLFromMysql:\n".$query."<br>\n";
509
510
		if (!empty($conf->global->MAIN_DB_AUTOFIX_BAD_SQL_REQUEST))
511
		{
512
			// Fix bad formed requests. If request contains a date without quotes, we fix this but this should not occurs.
513
			$loop = true;
514
			while ($loop)
515
			{
516
				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))
517
				{
518
					$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);
519
					dol_syslog("Warning: Bad formed request converted into ".$query, LOG_WARNING);
520
				} else $loop = false;
521
			}
522
		}
523
524
		if ($usesavepoint && $this->transaction_opened)
525
		{
526
			@pg_query($this->db, 'SAVEPOINT mysavepoint');
527
		}
528
529
		if (!in_array($query, array('BEGIN', 'COMMIT', 'ROLLBACK')))
530
		{
531
			$SYSLOG_SQL_LIMIT = 10000; // limit log to 10kb per line to limit DOS attacks
532
			dol_syslog('sql='.substr($query, 0, $SYSLOG_SQL_LIMIT), LOG_DEBUG);
533
		}
534
535
		$ret = @pg_query($this->db, $query);
536
537
		//print $query;
538
		if (!preg_match("/^COMMIT/i", $query) && !preg_match("/^ROLLBACK/i", $query)) // Si requete utilisateur, on la sauvegarde ainsi que son resultset
539
		{
540
			if (!$ret)
541
			{
542
				if ($this->errno() != 'DB_ERROR_25P02')	// Do not overwrite errors if this is a consecutive error
543
				{
544
					$this->lastqueryerror = $query;
545
					$this->lasterror = $this->error();
546
					$this->lasterrno = $this->errno();
547
548
					if ($conf->global->SYSLOG_LEVEL < LOG_DEBUG) dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); // Log of request was not yet done previously
549
					dol_syslog(get_class($this)."::query SQL Error message: ".$this->lasterror." (".$this->lasterrno.")", LOG_ERR);
550
					dol_syslog(get_class($this)."::query SQL Error usesavepoint = ".$usesavepoint, LOG_ERR);
551
				}
552
553
				if ($usesavepoint && $this->transaction_opened)	// Warning, after that errno will be erased
554
				{
555
					@pg_query($this->db, 'ROLLBACK TO SAVEPOINT mysavepoint');
556
				}
557
			}
558
			$this->lastquery = $query;
559
			$this->_results = $ret;
560
		}
561
562
		return $ret;
563
	}
564
565
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
566
	/**
567
	 *	Renvoie la ligne courante (comme un objet) pour le curseur resultset
568
	 *
569
	 *	@param	resource	$resultset  Curseur de la requete voulue
570
	 *	@return	false|object			Object result line or false if KO or end of cursor
571
	 */
572
	public function fetch_object($resultset)
573
	{
574
		// phpcs:enable
575
		// If resultset not provided, we take the last used by connexion
576
		if (!is_resource($resultset)) { $resultset = $this->_results; }
577
		return pg_fetch_object($resultset);
578
	}
579
580
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
581
	/**
582
	 *	Return datas as an array
583
	 *
584
	 *	@param	resource	$resultset  Resultset of request
585
	 *	@return	false|array				Array
586
	 */
587
	public function fetch_array($resultset)
588
	{
589
		// phpcs:enable
590
		// If resultset not provided, we take the last used by connexion
591
		if (!is_resource($resultset)) { $resultset = $this->_results; }
592
		return pg_fetch_array($resultset);
593
	}
594
595
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
596
	/**
597
	 *	Return datas as an array
598
	 *
599
	 *	@param	resource	$resultset  Resultset of request
600
	 *	@return	false|array				Array
601
	 */
602
	public function fetch_row($resultset)
603
	{
604
		// phpcs:enable
605
		// Si le resultset n'est pas fourni, on prend le dernier utilise sur cette connexion
606
		if (!is_resource($resultset)) { $resultset = $this->_results; }
607
		return pg_fetch_row($resultset);
608
	}
609
610
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
611
	/**
612
	 *	Return number of lines for result of a SELECT
613
	 *
614
	 *	@param	resource	$resultset  Resulset of requests
615
	 *	@return int		    			Nb of lines, -1 on error
616
	 *	@see    affected_rows()
617
	 */
618
	public function num_rows($resultset)
619
	{
620
		// phpcs:enable
621
		// If resultset not provided, we take the last used by connexion
622
		if (!is_resource($resultset)) { $resultset = $this->_results; }
623
		return pg_num_rows($resultset);
624
	}
625
626
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
627
	/**
628
	 * Return the number of lines in the result of a request INSERT, DELETE or UPDATE
629
	 *
630
	 * @param	resource	$resultset  Result set of request
631
	 * @return  int		    			Nb of lines
632
	 * @see 	num_rows()
633
	 */
634
	public function affected_rows($resultset)
635
	{
636
		// phpcs:enable
637
		// If resultset not provided, we take the last used by connexion
638
		if (!is_resource($resultset)) { $resultset = $this->_results; }
639
		// pgsql necessite un resultset pour cette fonction contrairement
640
		// a mysql qui prend un link de base
641
		return pg_affected_rows($resultset);
642
	}
643
644
645
	/**
646
	 * Libere le dernier resultset utilise sur cette connexion
647
	 *
648
	 * @param	resource	$resultset  Result set of request
649
	 * @return	void
650
	 */
651
	public function free($resultset = null)
652
	{
653
		// If resultset not provided, we take the last used by connexion
654
		if (!is_resource($resultset)) { $resultset = $this->_results; }
655
		// Si resultset en est un, on libere la memoire
656
		if (is_resource($resultset)) pg_free_result($resultset);
657
	}
658
659
660
	/**
661
	 *	Define limits and offset of request
662
	 *
663
	 *	@param	int		$limit      Maximum number of lines returned (-1=conf->liste_limit, 0=no limit)
664
	 *	@param	int		$offset     Numero of line from where starting fetch
665
	 *	@return	string      		String with SQL syntax to add a limit and offset
666
	 */
667
	public function plimit($limit = 0, $offset = 0)
668
	{
669
		global $conf;
670
		if (empty($limit)) return "";
671
		if ($limit < 0) $limit = $conf->liste_limit;
672
		if ($offset > 0) return " LIMIT ".$limit." OFFSET ".$offset." ";
673
		else return " LIMIT $limit ";
674
	}
675
676
677
	/**
678
	 *   Escape a string to insert data
679
	 *
680
	 *   @param		string	$stringtoencode		String to escape
681
	 *   @return	string						String escaped
682
	 */
683
	public function escape($stringtoencode)
684
	{
685
		return pg_escape_string($stringtoencode);
0 ignored issues
show
Bug introduced by
The call to pg_escape_string() has too few arguments starting with data. ( Ignorable by Annotation )

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

685
		return /** @scrutinizer ignore-call */ pg_escape_string($stringtoencode);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
686
	}
687
688
	/**
689
	 *  Format a SQL IF
690
	 *
691
	 *  @param	string	$test           Test string (example: 'cd.statut=0', 'field IS NULL')
692
	 *  @param	string	$resok          resultat si test egal
693
	 *  @param	string	$resko          resultat si test non egal
694
	 *  @return	string          		chaine formate SQL
695
	 */
696
	public function ifsql($test, $resok, $resko)
697
	{
698
		return '(CASE WHEN '.$test.' THEN '.$resok.' ELSE '.$resko.' END)';
699
	}
700
701
	/**
702
	 * Renvoie le code erreur generique de l'operation precedente.
703
	 *
704
	 * @return	string		Error code (Exemples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...)
705
	 */
706
	public function errno()
707
	{
708
		if (!$this->connected) {
709
			// Si il y a eu echec de connexion, $this->db n'est pas valide.
710
			return 'DB_ERROR_FAILED_TO_CONNECT';
711
		} else {
712
			// Constants to convert error code to a generic Dolibarr error code
713
			$errorcode_map = array(
714
			1004 => 'DB_ERROR_CANNOT_CREATE',
715
			1005 => 'DB_ERROR_CANNOT_CREATE',
716
			1006 => 'DB_ERROR_CANNOT_CREATE',
717
			1007 => 'DB_ERROR_ALREADY_EXISTS',
718
			1008 => 'DB_ERROR_CANNOT_DROP',
719
			1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP',
720
			1044 => 'DB_ERROR_ACCESSDENIED',
721
			1046 => 'DB_ERROR_NODBSELECTED',
722
			1048 => 'DB_ERROR_CONSTRAINT',
723
			'42P07' => 'DB_ERROR_TABLE_OR_KEY_ALREADY_EXISTS',
724
			'42703' => 'DB_ERROR_NOSUCHFIELD',
725
			1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
726
			42701=> 'DB_ERROR_COLUMN_ALREADY_EXISTS',
727
			'42710' => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
728
			'23505' => 'DB_ERROR_RECORD_ALREADY_EXISTS',
729
			'42704' => 'DB_ERROR_NO_INDEX_TO_DROP', // May also be Type xxx does not exists
730
			'42601' => 'DB_ERROR_SYNTAX',
731
			'42P16' => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS',
732
			1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY',
733
			1091 => 'DB_ERROR_NOSUCHFIELD',
734
			1100 => 'DB_ERROR_NOT_LOCKED',
735
			1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW',
736
			'42P01' => 'DB_ERROR_NOSUCHTABLE',
737
			'23503' => 'DB_ERROR_NO_PARENT',
738
			1217 => 'DB_ERROR_CHILD_EXISTS',
739
			1451 => 'DB_ERROR_CHILD_EXISTS',
740
			'42P04' => 'DB_DATABASE_ALREADY_EXISTS'
741
			);
742
743
			$errorlabel = pg_last_error($this->db);
744
			$errorcode = '';
745
			if (preg_match('/: *([0-9P]+):/', $errorlabel, $reg))
746
			{
747
				$errorcode = $reg[1];
748
				if (isset($errorcode_map[$errorcode]))
749
				{
750
					return $errorcode_map[$errorcode];
751
				}
752
			}
753
			$errno = $errorcode ? $errorcode : $errorlabel;
754
			return ($errno ? 'DB_ERROR_'.$errno : '0');
755
		}
756
		//                '/(Table does not exist\.|Relation [\"\'].*[\"\'] does not exist|sequence does not exist|class ".+" not found)$/' => 'DB_ERROR_NOSUCHTABLE',
757
		//                '/table [\"\'].*[\"\'] does not exist/' => 'DB_ERROR_NOSUCHTABLE',
758
		//                '/Relation [\"\'].*[\"\'] already exists|Cannot insert a duplicate key into (a )?unique index.*/'      => 'DB_ERROR_RECORD_ALREADY_EXISTS',
759
		//                '/divide by zero$/'                     => 'DB_ERROR_DIVZERO',
760
		//                '/pg_atoi: error in .*: can\'t parse /' => 'DB_ERROR_INVALID_NUMBER',
761
		//                '/ttribute [\"\'].*[\"\'] not found$|Relation [\"\'].*[\"\'] does not have attribute [\"\'].*[\"\']/' => 'DB_ERROR_NOSUCHFIELD',
762
		//                '/parser: parse error at or near \"/'   => 'DB_ERROR_SYNTAX',
763
		//                '/referential integrity violation/'     => 'DB_ERROR_CONSTRAINT'
764
	}
765
766
	/**
767
	 * Renvoie le texte de l'erreur pgsql de l'operation precedente
768
	 *
769
	 * @return	string		Error text
770
	 */
771
	public function error()
772
	{
773
		return pg_last_error($this->db);
774
	}
775
776
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
777
	/**
778
	 * Get last ID after an insert INSERT
779
	 *
780
	 * @param   string	$tab    	Table name concerned by insert. Ne sert pas sous MySql mais requis pour compatibilite avec Postgresql
781
	 * @param	string	$fieldid	Field name
782
	 * @return  string     			Id of row
783
	 */
784
	public function last_insert_id($tab, $fieldid = 'rowid')
785
	{
786
		// phpcs:enable
787
		//$result = pg_query($this->db,"SELECT MAX(".$fieldid.") FROM ".$tab);
788
		$result = pg_query($this->db, "SELECT currval('".$tab."_".$fieldid."_seq')");
789
		if (!$result)
790
		{
791
			print pg_last_error($this->db);
792
			exit;
793
		}
794
		//$nbre = pg_num_rows($result);
795
		$row = pg_fetch_result($result, 0, 0);
796
		return $row;
797
	}
798
799
	/**
800
	 *  Encrypt sensitive data in database
801
	 *  Warning: This function includes the escape, so it must use direct value
802
	 *
803
	 *  @param  string  $fieldorvalue   Field name or value to encrypt
804
	 *  @param	int		$withQuotes     Return string with quotes
805
	 *  @return string          		XXX(field) or XXX('value') or field or 'value'
806
	 */
807
	public function encrypt($fieldorvalue, $withQuotes = 0)
808
	{
809
		global $conf;
810
811
		// Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
812
		$cryptType = ($conf->db->dolibarr_main_db_encryption ? $conf->db->dolibarr_main_db_encryption : 0);
813
814
		//Encryption key
815
		$cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
816
817
		$return = $fieldorvalue;
818
		return ($withQuotes ? "'" : "").$this->escape($return).($withQuotes ? "'" : "");
819
	}
820
821
822
	/**
823
	 *	Decrypt sensitive data in database
824
	 *
825
	 *	@param	int		$value			Value to decrypt
826
	 * 	@return	string					Decrypted value if used
827
	 */
828
	public function decrypt($value)
829
	{
830
		global $conf;
831
832
		// Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
833
		$cryptType = ($conf->db->dolibarr_main_db_encryption ? $conf->db->dolibarr_main_db_encryption : 0);
834
835
		//Encryption key
836
		$cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
837
838
		$return = $value;
839
		return $return;
840
	}
841
842
843
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
844
	/**
845
	 * Return connexion ID
846
	 *
847
	 * @return	        string      Id connexion
848
	 */
849
	public function DDLGetConnectId()
850
	{
851
		// phpcs:enable
852
		return '?';
853
	}
854
855
856
857
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
858
	/**
859
	 *	Create a new database
860
	 *	Do not use function xxx_create_db (xxx=mysql, ...) as they are deprecated
861
	 *	We force to create database with charset this->forcecharset and collate this->forcecollate
862
	 *
863
	 *	@param	string	$database		Database name to create
864
	 * 	@param	string	$charset		Charset used to store data
865
	 * 	@param	string	$collation		Charset used to sort data
866
	 * 	@param	string	$owner			Username of database owner
867
	 * 	@return	false|resource				resource defined if OK, null if KO
868
	 */
869
	public function DDLCreateDb($database, $charset = '', $collation = '', $owner = '')
870
	{
871
		// phpcs:enable
872
		if (empty($charset))   $charset = $this->forcecharset;
873
		if (empty($collation)) $collation = $this->forcecollate;
874
875
		// Test charset match LC_TYPE (pgsql error otherwise)
876
		//print $charset.' '.setlocale(LC_CTYPE,'0'); exit;
877
878
		$sql = 'CREATE DATABASE "'.$database.'" OWNER "'.$owner.'" ENCODING \''.$charset.'\'';
879
		dol_syslog($sql, LOG_DEBUG);
880
		$ret = $this->query($sql);
881
		return $ret;
882
	}
883
884
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
885
	/**
886
	 *  List tables into a database
887
	 *
888
	 *  @param	string		$database	Name of database
889
	 *  @param	string		$table		Name of table filter ('xxx%')
890
	 *  @return	array					List of tables in an array
891
	 */
892
	public function DDLListTables($database, $table = '')
893
	{
894
		// phpcs:enable
895
		$listtables = array();
896
897
		$like = '';
898
		if ($table) $like = " AND table_name LIKE '".$this->escape($table)."'";
899
		$result = pg_query($this->db, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'".$like." ORDER BY table_name");
900
		if ($result)
901
		{
902
			while ($row = $this->fetch_row($result))
903
			{
904
				$listtables[] = $row[0];
905
			}
906
		}
907
		return $listtables;
908
	}
909
910
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
911
	/**
912
	 *	List information of columns into a table.
913
	 *
914
	 *	@param	string	$table		Name of table
915
	 *	@return	array				Tableau des informations des champs de la table
916
	 *
917
	 */
918
	public function DDLInfoTable($table)
919
	{
920
		// phpcs:enable
921
		$infotables = array();
922
923
		$sql = "SELECT ";
924
		$sql .= "	infcol.column_name as \"Column\",";
925
		$sql .= "	CASE WHEN infcol.character_maximum_length IS NOT NULL THEN infcol.udt_name || '('||infcol.character_maximum_length||')'";
926
		$sql .= "		ELSE infcol.udt_name";
927
		$sql .= "	END as \"Type\",";
928
		$sql .= "	infcol.collation_name as \"Collation\",";
929
		$sql .= "	infcol.is_nullable as \"Null\",";
930
		$sql .= "	'' as \"Key\",";
931
		$sql .= "	infcol.column_default as \"Default\",";
932
		$sql .= "	'' as \"Extra\",";
933
		$sql .= "	'' as \"Privileges\"";
934
		$sql .= "	FROM information_schema.columns infcol";
935
		$sql .= "	WHERE table_schema='public' ";
936
		$sql .= "	AND table_name='".$this->escape($table)."'";
937
		$sql .= "	ORDER BY ordinal_position;";
938
939
		dol_syslog($sql, LOG_DEBUG);
940
		$result = $this->query($sql);
941
		if ($result)
942
		{
943
			while ($row = $this->fetch_row($result))
944
			{
945
				$infotables[] = $row;
946
			}
947
		}
948
		return $infotables;
949
	}
950
951
952
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
953
	/**
954
	 *	Create a table into database
955
	 *
956
	 *	@param	    string	$table 			Nom de la table
957
	 *	@param	    array	$fields 		Tableau associatif [nom champ][tableau des descriptions]
958
	 *	@param	    string	$primary_key 	Nom du champ qui sera la clef primaire
959
	 *	@param	    string	$type 			Type de la table
960
	 *	@param	    array	$unique_keys 	Tableau associatifs Nom de champs qui seront clef unique => valeur
961
	 *	@param	    array	$fulltext_keys	Tableau des Nom de champs qui seront indexes en fulltext
962
	 *	@param	    array	$keys 			Tableau des champs cles noms => valeur
963
	 *	@return	    int						<0 if KO, >=0 if OK
964
	 */
965
	public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null)
966
	{
967
		// phpcs:enable
968
		// FIXME: $fulltext_keys parameter is unused
969
970
		// cles recherchees dans le tableau des descriptions (fields) : type,value,attribute,null,default,extra
971
		// ex. : $fields['rowid'] = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
972
		$sql = "create table ".$table."(";
973
		$i = 0;
974
		foreach ($fields as $field_name => $field_desc)
975
		{
976
			$sqlfields[$i] = $field_name." ";
977
			$sqlfields[$i] .= $field_desc['type'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sqlfields seems to be defined later in this foreach loop on line 976. Are you sure it is defined here?
Loading history...
978
			if (preg_match("/^[^\s]/i", $field_desc['value']))
979
				$sqlfields[$i] .= "(".$field_desc['value'].")";
980
			elseif (preg_match("/^[^\s]/i", $field_desc['attribute']))
981
				$sqlfields[$i] .= " ".$field_desc['attribute'];
982
			elseif (preg_match("/^[^\s]/i", $field_desc['default']))
983
			{
984
				if (preg_match("/null/i", $field_desc['default']))
985
					$sqlfields[$i] .= " default ".$field_desc['default'];
986
				else $sqlfields[$i] .= " default '".$this->escape($field_desc['default'])."'";
987
			} elseif (preg_match("/^[^\s]/i", $field_desc['null']))
988
				$sqlfields[$i]  .= " ".$field_desc['null'];
989
990
			elseif (preg_match("/^[^\s]/i", $field_desc['extra']))
991
				$sqlfields[$i]  .= " ".$field_desc['extra'];
992
			$i++;
993
		}
994
		if ($primary_key != "")
995
		$pk = "primary key(".$primary_key.")";
996
997
		if (is_array($unique_keys))
998
		{
999
			$i = 0;
1000
			foreach ($unique_keys as $key => $value)
1001
			{
1002
				$sqluq[$i] = "UNIQUE KEY '".$key."' ('".$this->escape($value)."')";
1003
				$i++;
1004
			}
1005
		}
1006
		if (is_array($keys))
1007
		{
1008
			$i = 0;
1009
			foreach ($keys as $key => $value)
1010
			{
1011
				$sqlk[$i] = "KEY ".$key." (".$value.")";
1012
				$i++;
1013
			}
1014
		}
1015
		$sql .= implode(',', $sqlfields);
1016
		if ($primary_key != "")
1017
		$sql .= ",".$pk;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pk does not seem to be defined for all execution paths leading up to this point.
Loading history...
1018
		if (is_array($unique_keys))
1019
		$sql .= ",".implode(',', $sqluq);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sqluq does not seem to be defined for all execution paths leading up to this point.
Loading history...
1020
		if (is_array($keys))
1021
		$sql .= ",".implode(',', $sqlk);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sqlk does not seem to be defined for all execution paths leading up to this point.
Loading history...
1022
		$sql .= ") type=".$type;
1023
1024
		dol_syslog($sql, LOG_DEBUG);
1025
		if (!$this->query($sql))
1026
		return -1;
1027
		else return 1;
1028
	}
1029
1030
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1031
	/**
1032
	 *	Drop a table into database
1033
	 *
1034
	 *	@param	    string	$table 			Name of table
1035
	 *	@return	    int						<0 if KO, >=0 if OK
1036
	 */
1037
	public function DDLDropTable($table)
1038
	{
1039
		// phpcs:enable
1040
		$sql = "DROP TABLE ".$table;
1041
1042
		if (!$this->query($sql))
1043
			return -1;
1044
		else return 1;
1045
	}
1046
1047
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1048
	/**
1049
	 * 	Create a user to connect to database
1050
	 *
1051
	 *	@param	string	$dolibarr_main_db_host 		Ip server
1052
	 *	@param	string	$dolibarr_main_db_user 		Name of user to create
1053
	 *	@param	string	$dolibarr_main_db_pass 		Password of user to create
1054
	 *	@param	string	$dolibarr_main_db_name		Database name where user must be granted
1055
	 *	@return	int									<0 if KO, >=0 if OK
1056
	 */
1057
	public function DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name)
1058
	{
1059
		// phpcs:enable
1060
		// Note: using ' on user does not works with pgsql
1061
		$sql = "CREATE USER ".$this->escape($dolibarr_main_db_user)." with password '".$this->escape($dolibarr_main_db_pass)."'";
1062
1063
		dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
1064
		$resql = $this->query($sql);
1065
		if (!$resql)
1066
		{
1067
			return -1;
1068
		}
1069
1070
		return 1;
1071
	}
1072
1073
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1074
	/**
1075
	 *	Return a pointer of line with description of a table or field
1076
	 *
1077
	 *	@param	string		$table	Name of table
1078
	 *	@param	string		$field	Optionnel : Name of field if we want description of field
1079
	 *	@return	false|resource		Resultset x (x->attname)
1080
	 */
1081
	public function DDLDescTable($table, $field = "")
1082
	{
1083
		// phpcs:enable
1084
		$sql = "SELECT attname FROM pg_attribute, pg_type WHERE typname = '".$this->escape($table)."' AND attrelid = typrelid";
1085
		$sql .= " AND attname NOT IN ('cmin', 'cmax', 'ctid', 'oid', 'tableoid', 'xmin', 'xmax')";
1086
		if ($field) $sql .= " AND attname = '".$this->escape($field)."'";
1087
1088
		dol_syslog($sql, LOG_DEBUG);
1089
		$this->_results = $this->query($sql);
1090
		return $this->_results;
1091
	}
1092
1093
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1094
	/**
1095
	 *	Create a new field into table
1096
	 *
1097
	 *	@param	string	$table 				Name of table
1098
	 *	@param	string	$field_name 		Name of field to add
1099
	 *	@param	string	$field_desc 		Tableau associatif de description du champ a inserer[nom du parametre][valeur du parametre]
1100
	 *	@param	string	$field_position 	Optionnel ex.: "after champtruc"
1101
	 *	@return	int							<0 if KO, >0 if OK
1102
	 */
1103
	public function DDLAddField($table, $field_name, $field_desc, $field_position = "")
1104
	{
1105
		// phpcs:enable
1106
		// cles recherchees dans le tableau des descriptions (field_desc) : type,value,attribute,null,default,extra
1107
		// ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
1108
		$sql = "ALTER TABLE ".$table." ADD ".$field_name." ";
1109
		$sql .= $field_desc['type'];
1110
		if (preg_match("/^[^\s]/i", $field_desc['value'])) {
1111
			if (!in_array($field_desc['type'], array('int', 'date', 'datetime')) && $field_desc['value'])
1112
			{
1113
				$sql .= "(".$field_desc['value'].")";
1114
			}
1115
		}
1116
		if (preg_match("/^[^\s]/i", $field_desc['attribute']))
1117
			$sql .= " ".$field_desc['attribute'];
1118
		if (preg_match("/^[^\s]/i", $field_desc['null']))
1119
			$sql .= " ".$field_desc['null'];
1120
		if (preg_match("/^[^\s]/i", $field_desc['default'])) {
1121
			if (preg_match("/null/i", $field_desc['default'])) {
1122
				$sql .= " default ".$field_desc['default'];
1123
			} else {
1124
				$sql .= " default '".$this->escape($field_desc['default'])."'";
1125
			}
1126
		}
1127
		if (preg_match("/^[^\s]/i", $field_desc['extra'])) {
1128
			$sql .= " ".$field_desc['extra'];
1129
		}
1130
		$sql .= " ".$field_position;
1131
1132
		dol_syslog($sql, LOG_DEBUG);
1133
		if (!$this -> query($sql))
1134
			return -1;
1135
		return 1;
1136
	}
1137
1138
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1139
	/**
1140
	 *	Update format of a field into a table
1141
	 *
1142
	 *	@param	string	$table 				Name of table
1143
	 *	@param	string	$field_name 		Name of field to modify
1144
	 *	@param	string	$field_desc 		Array with description of field format
1145
	 *	@return	int							<0 if KO, >0 if OK
1146
	 */
1147
	public function DDLUpdateField($table, $field_name, $field_desc)
1148
	{
1149
		// phpcs:enable
1150
		$sql = "ALTER TABLE ".$table;
1151
		$sql .= " MODIFY COLUMN ".$field_name." ".$field_desc['type'];
1152
		if (in_array($field_desc['type'], array('double', 'tinyint', 'int', 'varchar')) && $field_desc['value']) {
1153
			$sql .= "(".$field_desc['value'].")";
1154
		}
1155
1156
		if ($field_desc['null'] == 'not null' || $field_desc['null'] == 'NOT NULL')
1157
		{
1158
			// 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
1159
			if ($field_desc['type'] == 'varchar' || $field_desc['type'] == 'text')
1160
			{
1161
				$sqlbis = "UPDATE ".$table." SET ".$field_name." = '".$this->escape($field_desc['default'] ? $field_desc['default'] : '')."' WHERE ".$field_name." IS NULL";
1162
				$this->query($sqlbis);
1163
			} elseif ($field_desc['type'] == 'tinyint' || $field_desc['type'] == 'int')
1164
			{
1165
				$sqlbis = "UPDATE ".$table." SET ".$field_name." = ".((int) $this->escape($field_desc['default'] ? $field_desc['default'] : 0))." WHERE ".$field_name." IS NULL";
1166
				$this->query($sqlbis);
1167
			}
1168
		}
1169
1170
		if ($field_desc['default'] != '')
1171
		{
1172
			if ($field_desc['type'] == 'double' || $field_desc['type'] == 'tinyint' || $field_desc['type'] == 'int') $sql .= " DEFAULT ".$this->escape($field_desc['default']);
1173
			elseif ($field_desc['type'] != 'text') $sql .= " DEFAULT '".$this->escape($field_desc['default'])."'"; // Default not supported on text fields
1174
		}
1175
1176
		dol_syslog($sql, LOG_DEBUG);
1177
		if (!$this->query($sql))
1178
			return -1;
1179
		return 1;
1180
	}
1181
1182
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1183
	/**
1184
	 *	Drop a field from table
1185
	 *
1186
	 *	@param	string	$table 			Name of table
1187
	 *	@param	string	$field_name 	Name of field to drop
1188
	 *	@return	int						<0 if KO, >0 if OK
1189
	 */
1190
	public function DDLDropField($table, $field_name)
1191
	{
1192
		// phpcs:enable
1193
		$sql = "ALTER TABLE ".$table." DROP COLUMN ".$field_name;
1194
		dol_syslog($sql, LOG_DEBUG);
1195
		if (!$this->query($sql))
1196
		{
1197
			$this->error = $this->lasterror();
1198
			return -1;
1199
		}
1200
		return 1;
1201
	}
1202
1203
	/**
1204
	 *	Return charset used to store data in database
1205
	 *
1206
	 *	@return		string		Charset
1207
	 */
1208
	public function getDefaultCharacterSetDatabase()
1209
	{
1210
		$resql = $this->query('SHOW SERVER_ENCODING');
1211
		if ($resql)
1212
		{
1213
			$liste = $this->fetch_array($resql);
1214
			return $liste['server_encoding'];
1215
		} else return '';
1216
	}
1217
1218
	/**
1219
	 *	Return list of available charset that can be used to store data in database
1220
	 *
1221
	 *	@return		array		List of Charset
1222
	 */
1223
	public function getListOfCharacterSet()
1224
	{
1225
		$resql = $this->query('SHOW SERVER_ENCODING');
1226
		$liste = array();
1227
		if ($resql)
1228
		{
1229
			$i = 0;
1230
			while ($obj = $this->fetch_object($resql))
1231
			{
1232
				$liste[$i]['charset'] = $obj->server_encoding;
1233
				$liste[$i]['description'] = 'Default database charset';
1234
				$i++;
1235
			}
1236
			$this->free($resql);
1237
		} else {
1238
			return null;
1239
		}
1240
		return $liste;
1241
	}
1242
1243
	/**
1244
	 *	Return collation used in database
1245
	 *
1246
	 *	@return		string		Collation value
1247
	 */
1248
	public function getDefaultCollationDatabase()
1249
	{
1250
		$resql = $this->query('SHOW LC_COLLATE');
1251
		if ($resql)
1252
		{
1253
			$liste = $this->fetch_array($resql);
1254
			return $liste['lc_collate'];
1255
		} else return '';
1256
	}
1257
1258
	/**
1259
	 *	Return list of available collation that can be used for database
1260
	 *
1261
	 *	@return		array		Liste of Collation
1262
	 */
1263
	public function getListOfCollation()
1264
	{
1265
		$resql = $this->query('SHOW LC_COLLATE');
1266
		$liste = array();
1267
		if ($resql)
1268
		{
1269
			$i = 0;
1270
			while ($obj = $this->fetch_object($resql))
1271
			{
1272
				$liste[$i]['collation'] = $obj->lc_collate;
1273
				$i++;
1274
			}
1275
			$this->free($resql);
1276
		} else {
1277
			return null;
1278
		}
1279
		return $liste;
1280
	}
1281
1282
	/**
1283
	 *	Return full path of dump program
1284
	 *
1285
	 *	@return		string		Full path of dump program
1286
	 */
1287
	public function getPathOfDump()
1288
	{
1289
		$fullpathofdump = '/pathtopgdump/pg_dump';
1290
1291
		if (file_exists('/usr/bin/pg_dump'))
1292
		{
1293
			$fullpathofdump = '/usr/bin/pg_dump';
1294
		} else {
1295
			// TODO L'utilisateur de la base doit etre un superadmin pour lancer cette commande
1296
			$resql = $this->query('SHOW data_directory');
1297
			if ($resql)
1298
			{
1299
				$liste = $this->fetch_array($resql);
1300
				$basedir = $liste['data_directory'];
1301
				$fullpathofdump = preg_replace('/data$/', 'bin', $basedir).'/pg_dump';
1302
			}
1303
		}
1304
1305
		return $fullpathofdump;
1306
	}
1307
1308
	/**
1309
	 *	Return full path of restore program
1310
	 *
1311
	 *	@return		string		Full path of restore program
1312
	 */
1313
	public function getPathOfRestore()
1314
	{
1315
		//$tool='pg_restore';
1316
		$tool = 'psql';
1317
1318
		$fullpathofdump = '/pathtopgrestore/'.$tool;
1319
1320
		if (file_exists('/usr/bin/'.$tool))
1321
		{
1322
			$fullpathofdump = '/usr/bin/'.$tool;
1323
		} else {
1324
			// TODO L'utilisateur de la base doit etre un superadmin pour lancer cette commande
1325
			$resql = $this->query('SHOW data_directory');
1326
			if ($resql)
1327
			{
1328
				$liste = $this->fetch_array($resql);
1329
				$basedir = $liste['data_directory'];
1330
				$fullpathofdump = preg_replace('/data$/', 'bin', $basedir).'/'.$tool;
1331
			}
1332
		}
1333
1334
		return $fullpathofdump;
1335
	}
1336
1337
	/**
1338
	 * Return value of server parameters
1339
	 *
1340
	 * @param	string	$filter		Filter list on a particular value
1341
	 * @return	array				Array of key-values (key=>value)
1342
	 */
1343
	public function getServerParametersValues($filter = '')
1344
	{
1345
		$result = array();
1346
1347
		$resql = 'select name,setting from pg_settings';
1348
		if ($filter) $resql .= " WHERE name = '".$this->escape($filter)."'";
1349
		$resql = $this->query($resql);
1350
		if ($resql)
1351
		{
1352
			while ($obj = $this->fetch_object($resql)) $result[$obj->name] = $obj->setting;
1353
		}
1354
1355
		return $result;
1356
	}
1357
1358
	/**
1359
	 * Return value of server status
1360
	 *
1361
	 * @param	string	$filter		Filter list on a particular value
1362
	 * @return  array				Array of key-values (key=>value)
1363
	 */
1364
	public function getServerStatusValues($filter = '')
1365
	{
1366
		/* This is to return current running requests.
1367
		$sql='SELECT datname,procpid,current_query FROM pg_stat_activity ORDER BY procpid';
1368
        if ($filter) $sql.=" LIKE '".$this->escape($filter)."'";
1369
        $resql=$this->query($sql);
1370
        if ($resql)
1371
        {
1372
            $obj=$this->fetch_object($resql);
1373
            $result[$obj->Variable_name]=$obj->Value;
1374
        }
1375
		*/
1376
1377
		return array();
1378
	}
1379
}
1380