Completed
Branch develop (e6f0e7)
by
unknown
24:49
created

DoliDBPgsql::error()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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 <http://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
	var $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
    var $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;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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
	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;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
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;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
97
		}
98
99
		// Essai connexion serveur
100
		//print "$host, $user, $pass, $name, $port";
101
		$this->db = $this->connect($host, $user, $pass, $name, $port);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->connect($host, $user, $pass, $name, $port) can also be of type false. However, the property $db is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
102
103
		if ($this->db)
104
		{
105
			$this->connected = true;
106
			$this->ok = true;
107
		}
108
		else
109
		{
110
			// host, login ou password incorrect
111
			$this->connected = false;
112
			$this->ok = false;
113
			$this->error='Host, login or password incorrect';
114
			dol_syslog(get_class($this)."::DoliDBPgsql : Erreur Connect ".$this->error,LOG_ERR);
115
		}
116
117
		// Si connexion serveur ok et si connexion base demandee, on essaie connexion base
118
		if ($this->connected && $name)
119
		{
120
			if ($this->select_db($name))
121
			{
122
				$this->database_selected = true;
123
				$this->database_name = $name;
124
				$this->ok = true;
125
			}
126
			else
127
			{
128
				$this->database_selected = false;
129
				$this->database_name = '';
130
				$this->ok = false;
131
				$this->error=$this->error();
132
				dol_syslog(get_class($this)."::DoliDBPgsql : Erreur Select_db ".$this->error,LOG_ERR);
133
			}
134
		}
135
		else
136
		{
137
			// Pas de selection de base demandee, ok ou ko
138
			$this->database_selected = false;
139
		}
140
141
		return $this->ok;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
142
	}
143
144
145
    /**
146
     *  Convert a SQL request in Mysql syntax to native syntax
147
     *
148
     *  @param  string	$line   			SQL request line to convert
149
     *  @param  string	$type				Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...)
150
     *  @param	bool	$unescapeslashquot	Unescape slash quote with quote quote
151
     *  @return string   					SQL request line converted
152
     */
153
	static function convertSQLFromMysql($line,$type='auto',$unescapeslashquot=false)
154
	{
155
		// Removed empty line if this is a comment line for SVN tagging
156
		if (preg_match('/^--\s\$Id/i',$line)) {
157
			return '';
158
		}
159
		// Return line if this is a comment
160
		if (preg_match('/^#/i',$line) || preg_match('/^$/i',$line) || preg_match('/^--/i',$line))
161
		{
162
			return $line;
163
		}
164
		if ($line != "")
165
		{
166
			// group_concat support (PgSQL >= 9.0)
167
			// Replace group_concat(x) or group_concat(x SEPARATOR ',') with string_agg(x, ',')
168
		    $line = preg_replace('/GROUP_CONCAT/i', 'STRING_AGG', $line);
169
			$line = preg_replace('/ SEPARATOR/i', ',', $line);
170
			$line = preg_replace('/STRING_AGG\(([^,\)]+)\)/i', 'STRING_AGG(\\1, \',\')', $line);
171
			//print $line."\n";
172
173
		    if ($type == 'auto')
174
		    {
175
              if (preg_match('/ALTER TABLE/i',$line)) $type='dml';
176
              else if (preg_match('/CREATE TABLE/i',$line)) $type='dml';
177
              else if (preg_match('/DROP TABLE/i',$line)) $type='dml';
178
		    }
179
180
    		$line=preg_replace('/ as signed\)/i',' as integer)',$line);
181
182
		    if ($type == 'dml')
183
		    {
184
                $line=preg_replace('/\s/',' ',$line);   // Replace tabulation with space
185
186
		        // we are inside create table statement so lets process datatypes
187
    			if (preg_match('/(ISAM|innodb)/i',$line)) { // end of create table sequence
188
    				$line=preg_replace('/\)[\s\t]*type[\s\t]*=[\s\t]*(MyISAM|innodb).*;/i',');',$line);
189
    				$line=preg_replace('/\)[\s\t]*engine[\s\t]*=[\s\t]*(MyISAM|innodb).*;/i',');',$line);
190
    				$line=preg_replace('/,$/','',$line);
191
    			}
192
193
    			// Process case: "CREATE TABLE llx_mytable(rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY,code..."
194
    			if (preg_match('/[\s\t\(]*(\w*)[\s\t]+int.*auto_increment/i',$line,$reg)) {
195
    				$newline=preg_replace('/([\s\t\(]*)([a-zA-Z_0-9]*)[\s\t]+int.*auto_increment[^,]*/i','\\1 \\2 SERIAL PRIMARY KEY',$line);
196
                    //$line = "-- ".$line." replaced by --\n".$newline;
197
                    $line=$newline;
198
    			}
199
200
    			// tinyint type conversion
201
    			$line=preg_replace('/tinyint\(?[0-9]*\)?/','smallint',$line);
202
    			$line=preg_replace('/tinyint/i','smallint',$line);
203
204
    			// nuke unsigned
205
    			$line=preg_replace('/(int\w+|smallint)\s+unsigned/i','\\1',$line);
206
207
    			// blob -> text
208
    			$line=preg_replace('/\w*blob/i','text',$line);
209
210
    			// tinytext/mediumtext -> text
211
    			$line=preg_replace('/tinytext/i','text',$line);
212
    			$line=preg_replace('/mediumtext/i','text',$line);
213
    			$line=preg_replace('/longtext/i','text',$line);
214
215
    			$line=preg_replace('/text\([0-9]+\)/i','text',$line);
216
217
    			// change not null datetime field to null valid ones
218
    			// (to support remapping of "zero time" to null
219
    			$line=preg_replace('/datetime not null/i','datetime',$line);
220
    			$line=preg_replace('/datetime/i','timestamp',$line);
221
222
    			// double -> numeric
223
    			$line=preg_replace('/^double/i','numeric',$line);
224
    			$line=preg_replace('/(\s*)double/i','\\1numeric',$line);
225
    			// float -> numeric
226
    			$line=preg_replace('/^float/i','numeric',$line);
227
    			$line=preg_replace('/(\s*)float/i','\\1numeric',$line);
228
229
    			//Check tms timestamp field case (in Mysql this field is defautled to now and
230
    			// on update defaulted by now
231
    			$line=preg_replace('/(\s*)tms(\s*)timestamp/i','\\1tms timestamp without time zone DEFAULT now() NOT NULL',$line);
232
233
    			// nuke ON UPDATE CURRENT_TIMESTAMP
234
    			$line=preg_replace('/(\s*)on(\s*)update(\s*)CURRENT_TIMESTAMP/i','\\1',$line);
235
236
    			// unique index(field1,field2)
237
    			if (preg_match('/unique index\s*\((\w+\s*,\s*\w+)\)/i',$line))
238
    			{
239
    				$line=preg_replace('/unique index\s*\((\w+\s*,\s*\w+)\)/i','UNIQUE\(\\1\)',$line);
240
    			}
241
242
    			// We remove end of requests "AFTER fieldxxx"
243
    			$line=preg_replace('/\sAFTER [a-z0-9_]+/i','',$line);
244
245
    			// We remove start of requests "ALTER TABLE tablexxx" if this is a DROP INDEX
246
    			$line=preg_replace('/ALTER TABLE [a-z0-9_]+\s+DROP INDEX/i','DROP INDEX',$line);
247
248
                // Translate order to rename fields
249
                if (preg_match('/ALTER TABLE ([a-z0-9_]+)\s+CHANGE(?: COLUMN)? ([a-z0-9_]+) ([a-z0-9_]+)(.*)$/i',$line,$reg))
250
                {
251
                	$line = "-- ".$line." replaced by --\n";
252
                    $line.= "ALTER TABLE ".$reg[1]." RENAME COLUMN ".$reg[2]." TO ".$reg[3];
253
                }
254
255
                // Translate order to modify field format
256
                if (preg_match('/ALTER TABLE ([a-z0-9_]+)\s+MODIFY(?: COLUMN)? ([a-z0-9_]+) (.*)$/i',$line,$reg))
257
                {
258
                    $line = "-- ".$line." replaced by --\n";
259
                    $newreg3=$reg[3];
260
                    $newreg3=preg_replace('/ DEFAULT NULL/i','',$newreg3);
261
                    $newreg3=preg_replace('/ NOT NULL/i','',$newreg3);
262
                    $newreg3=preg_replace('/ NULL/i','',$newreg3);
263
                    $newreg3=preg_replace('/ DEFAULT 0/i','',$newreg3);
264
                    $newreg3=preg_replace('/ DEFAULT \'?[0-9a-zA-Z_@]*\'?/i','',$newreg3);
265
                    $line.= "ALTER TABLE ".$reg[1]." ALTER COLUMN ".$reg[2]." TYPE ".$newreg3;
266
                    // TODO Add alter to set default value or null/not null if there is this in $reg[3]
267
                }
268
269
                // alter table add primary key (field1, field2 ...) -> We remove the primary key name not accepted by PostGreSQL
270
    			// ALTER TABLE llx_dolibarr_modules ADD PRIMARY KEY pk_dolibarr_modules (numero, entity)
271
    			if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+PRIMARY\s+KEY\s*(.*)\s*\((.*)$/i',$line,$reg))
272
    			{
273
    				$line = "-- ".$line." replaced by --\n";
274
    				$line.= "ALTER TABLE ".$reg[1]." ADD PRIMARY KEY (".$reg[3];
275
    			}
276
277
                // Translate order to drop foreign keys
278
                // ALTER TABLE llx_dolibarr_modules DROP FOREIGN KEY fk_xxx
279
                if (preg_match('/ALTER\s+TABLE\s*(.*)\s*DROP\s+FOREIGN\s+KEY\s*(.*)$/i',$line,$reg))
280
                {
281
                    $line = "-- ".$line." replaced by --\n";
282
                    $line.= "ALTER TABLE ".$reg[1]." DROP CONSTRAINT ".$reg[2];
283
                }
284
285
                // Translate order to add foreign keys
286
                // ALTER TABLE llx_tablechild ADD CONSTRAINT fk_tablechild_fk_fieldparent FOREIGN KEY (fk_fieldparent) REFERENCES llx_tableparent (rowid)
287
                if (preg_match('/ALTER\s+TABLE\s+(.*)\s*ADD CONSTRAINT\s+(.*)\s*FOREIGN\s+KEY\s*(.*)$/i',$line,$reg))
288
                {
289
                    $line=preg_replace('/;$/','',$line);
290
                    $line.=" DEFERRABLE INITIALLY IMMEDIATE;";
291
                }
292
293
                // alter table add [unique] [index] (field1, field2 ...)
294
    			// ALTER TABLE llx_accountingaccount ADD INDEX idx_accountingaccount_fk_pcg_version (fk_pcg_version)
295
    			if (preg_match('/ALTER\s+TABLE\s*(.*)\s*ADD\s+(UNIQUE INDEX|INDEX|UNIQUE)\s+(.*)\s*\(([\w,\s]+)\)/i',$line,$reg))
296
    			{
297
    				$fieldlist=$reg[4];
298
    				$idxname=$reg[3];
299
    				$tablename=$reg[1];
300
    				$line = "-- ".$line." replaced by --\n";
301
    				$line.= "CREATE ".(preg_match('/UNIQUE/',$reg[2])?'UNIQUE ':'')."INDEX ".$idxname." ON ".$tablename." (".$fieldlist.")";
302
    			}
303
            }
304
305
            // To have postgresql case sensitive
306
            $line=str_replace(' LIKE \'',' ILIKE \'',$line);
307
            $line=str_replace(' LIKE BINARY \'',' LIKE \'',$line);
308
309
            // Replace INSERT IGNORE into INSERT
310
            $line=preg_replace('/^INSERT IGNORE/','INSERT',$line);
311
312
			// Delete using criteria on other table must not declare twice the deleted table
313
			// DELETE FROM tabletodelete USING tabletodelete, othertable -> DELETE FROM tabletodelete USING othertable
314
			if (preg_match('/DELETE FROM ([a-z_]+) USING ([a-z_]+), ([a-z_]+)/i',$line,$reg))
315
			{
316
				if ($reg[1] == $reg[2])	// If same table, we remove second one
317
				{
318
					$line=preg_replace('/DELETE FROM ([a-z_]+) USING ([a-z_]+), ([a-z_]+)/i','DELETE FROM \\1 USING \\3', $line);
319
				}
320
			}
321
322
			// Remove () in the tables in FROM if 1 table
323
			$line=preg_replace('/FROM\s*\((([a-z_]+)\s+as\s+([a-z_]+)\s*)\)/i','FROM \\1',$line);
324
			//print $line."\n";
325
326
			// Remove () in the tables in FROM if 2 table
327
			$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);
328
			//print $line."\n";
329
330
			// Remove () in the tables in FROM if 3 table
331
			$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);
332
			//print $line."\n";
333
334
			// Remove () in the tables in FROM if 4 table
335
			$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);
336
			//print $line."\n";
337
338
			// Remove () in the tables in FROM if 5 table
339
			$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);
340
			//print $line."\n";
341
342
			// Replace espacing \' by ''.
343
			// By default we do not (should be already done by db->escape function if required
344
			// except for sql insert in data file that are mysql escaped so we removed them to
345
			// be compatible with standard_conforming_strings=on that considers \ as ordinary character).
346
			if ($unescapeslashquot) $line=preg_replace("/\\\'/","''",$line);
347
348
			//print "type=".$type." newline=".$line."<br>\n";
349
		}
350
351
		return $line;
352
	}
353
354
	/**
355
	 *	Select a database
356
     *  Ici postgresql n'a aucune fonction equivalente de mysql_select_db
357
     *  On compare juste manuellement si la database choisie est bien celle activee par la connexion
358
	 *
359
	 *	@param	    string	$database	Name of database
360
	 *	@return	    bool				true if OK, false if KO
361
	 */
362
	function select_db($database)
363
	{
364
		if ($database == $this->database_name) return true;
365
		else return false;
366
	}
367
368
	/**
369
	 *	Connexion to server
370
	 *
371
	 *	@param	    string		$host		Database server host
372
	 *	@param	    string		$login		Login
373
	 *	@param	    string		$passwd		Password
374
	 *	@param		string		$name		Name of database (not used for mysql, used for pgsql)
375
	 *	@param		integer		$port		Port of database server
376
	 *	@return		false|resource			Database access handler
377
	 *	@see		close
378
	 */
379
	function connect($host, $login, $passwd, $name, $port=0)
380
	{
381
		// use pg_pconnect() instead of pg_connect() if you want to use persistent connection costing 1ms, instead of 30ms for non persistent
382
383
		$this->db = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type resource of property $db.

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

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

Loading history...
384
385
		// connections parameters must be protected (only \ and ' according to pg_connect() manual)
386
		$host = str_replace(array("\\", "'"), array("\\\\", "\\'"), $host);
387
		$login = str_replace(array("\\", "'"), array("\\\\", "\\'"), $login);
388
		$passwd = str_replace(array("\\", "'"), array("\\\\", "\\'"), $passwd);
389
		$name = str_replace(array("\\", "'"), array("\\\\", "\\'"), $name);
390
		$port = str_replace(array("\\", "'"), array("\\\\", "\\'"), $port);
391
392
		if (! $name) $name="postgres";    // When try to connect using admin user
393
394
		// try first Unix domain socket (local)
395
		if ((! empty($host) && $host == "socket") && ! defined('NOLOCALSOCKETPGCONNECT'))
396
		{
397
			$con_string = "dbname='".$name."' user='".$login."' password='".$passwd."'";    // $name may be empty
398
			$this->db = @pg_connect($con_string);
399
		}
400
401
		// if local connection failed or not requested, use TCP/IP
402
		if (! $this->db)
403
		{
404
		    if (! $host) $host = "localhost";
405
			if (! $port) $port = 5432;
406
407
			$con_string = "host='".$host."' port='".$port."' dbname='".$name."' user='".$login."' password='".$passwd."'";
408
			$this->db = @pg_connect($con_string);
409
		}
410
411
		// now we test if at least one connect method was a success
412
		if ($this->db)
413
		{
414
			$this->database_name = $name;
415
			pg_set_error_verbosity($this->db, PGSQL_ERRORS_VERBOSE);	// Set verbosity to max
416
			pg_query($this->db, "set datestyle = 'ISO, YMD';");
417
		}
418
419
		return $this->db;
420
	}
421
422
	/**
423
	 *	Return version of database server
424
	 *
425
	 *	@return	        string      Version string
426
	 */
427
	function getVersion()
428
	{
429
		$resql=$this->query('SHOW server_version');
430
		if ($resql)
431
		{
432
		  $liste=$this->fetch_array($resql);
433
		  return $liste['server_version'];
434
		}
435
		return '';
436
	}
437
438
	/**
439
	 *	Return version of database client driver
440
	 *
441
	 *	@return	        string      Version string
442
	 */
443
	function getDriverInfo()
444
	{
445
		return 'pgsql php driver';
446
	}
447
448
    /**
449
     *  Close database connexion
450
     *
451
     *  @return     boolean     True if disconnect successfull, false otherwise
452
     *  @see        connect
453
     */
454
    function close()
455
    {
456
        if ($this->db)
457
        {
458
          if ($this->transaction_opened > 0) dol_syslog(get_class($this)."::close Closing a connection with an opened transaction depth=".$this->transaction_opened,LOG_ERR);
459
          $this->connected=false;
460
          return pg_close($this->db);
461
        }
462
        return false;
463
    }
464
465
	/**
466
	 * Convert request to PostgreSQL syntax, execute it and return the resultset
467
	 *
468
	 * @param	string	$query			SQL query string
469
	 * @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).
470
     * @param   string	$type           Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...)
471
	 * @return	false|resource			Resultset of answer
472
	 */
473
	function query($query,$usesavepoint=0,$type='auto')
474
	{
475
		global $conf;
476
477
		$query = trim($query);
478
479
		// Convert MySQL syntax to PostgresSQL syntax
480
		$query=$this->convertSQLFromMysql($query,$type,($this->unescapeslashquot && $this->standard_conforming_strings));
481
		//print "After convertSQLFromMysql:\n".$query."<br>\n";
482
483
		if (! empty($conf->global->MAIN_DB_AUTOFIX_BAD_SQL_REQUEST))
484
		{
485
			// Fix bad formed requests. If request contains a date without quotes, we fix this but this should not occurs.
486
			$loop=true;
487
			while ($loop)
488
			{
489
				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))
490
				{
491
					$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);
492
					dol_syslog("Warning: Bad formed request converted into ".$query,LOG_WARNING);
493
				}
494
				else $loop=false;
495
			}
496
		}
497
498
		if ($usesavepoint && $this->transaction_opened)
499
		{
500
			@pg_query($this->db, 'SAVEPOINT mysavepoint');
501
		}
502
503
		if (! in_array($query,array('BEGIN','COMMIT','ROLLBACK'))) dol_syslog('sql='.$query, LOG_DEBUG);
504
505
		$ret = @pg_query($this->db, $query);
506
507
		//print $query;
508
		if (! preg_match("/^COMMIT/i",$query) && ! preg_match("/^ROLLBACK/i",$query)) // Si requete utilisateur, on la sauvegarde ainsi que son resultset
509
		{
510
			if (! $ret)
511
			{
512
			    if ($this->errno() != 'DB_ERROR_25P02')	// Do not overwrite errors if this is a consecutive error
513
			    {
514
    				$this->lastqueryerror = $query;
515
    				$this->lasterror = $this->error();
516
    				$this->lasterrno = $this->errno();
0 ignored issues
show
Documentation Bug introduced by
The property $lasterrno was declared of type integer, but $this->errno() is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
517
518
    				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
519
					dol_syslog(get_class($this)."::query SQL Error message: ".$this->lasterror." (".$this->lasterrno.")", LOG_ERR);
520
					dol_syslog(get_class($this)."::query SQL Error usesavepoint = ".$usesavepoint, LOG_ERR);
521
			    }
522
523
				if ($usesavepoint && $this->transaction_opened)	// Warning, after that errno will be erased
524
				{
525
					@pg_query($this->db, 'ROLLBACK TO SAVEPOINT mysavepoint');
526
				}
527
			}
528
			$this->lastquery=$query;
529
			$this->_results = $ret;
530
		}
531
532
		return $ret;
533
	}
534
535
	/**
536
	 *	Renvoie la ligne courante (comme un objet) pour le curseur resultset
537
	 *
538
	 *	@param	resource	$resultset  Curseur de la requete voulue
539
	 *	@return	false|object			Object result line or false if KO or end of cursor
540
	 */
541
	function fetch_object($resultset)
542
	{
543
        // If resultset not provided, we take the last used by connexion
544
		if (! is_resource($resultset)) { $resultset=$this->_results; }
545
		return pg_fetch_object($resultset);
546
	}
547
548
	/**
549
     *	Return datas as an array
550
     *
551
     *	@param	resource	$resultset  Resultset of request
552
     *	@return	false|array				Array
553
	 */
554
	function fetch_array($resultset)
555
	{
556
        // If resultset not provided, we take the last used by connexion
557
		if (! is_resource($resultset)) { $resultset=$this->_results; }
558
		return pg_fetch_array($resultset);
559
	}
560
561
	/**
562
     *	Return datas as an array
563
     *
564
     *	@param	resource	$resultset  Resultset of request
565
     *	@return	false|array				Array
566
	 */
567
	function fetch_row($resultset)
568
	{
569
		// Si le resultset n'est pas fourni, on prend le dernier utilise sur cette connexion
570
		if (! is_resource($resultset)) { $resultset=$this->_results; }
571
		return pg_fetch_row($resultset);
572
	}
573
574
	/**
575
     *	Return number of lines for result of a SELECT
576
     *
577
     *	@param	resourse	$resultset  Resulset of requests
578
     *	@return int		    			Nb of lines, -1 on error
579
     *	@see    affected_rows
580
	 */
581
	function num_rows($resultset)
582
	{
583
        // If resultset not provided, we take the last used by connexion
584
		if (! is_resource($resultset)) { $resultset=$this->_results; }
585
		return pg_num_rows($resultset);
586
	}
587
588
	/**
589
	 * Renvoie le nombre de lignes dans le resultat d'une requete INSERT, DELETE ou UPDATE
590
	 *
591
	 * @param	resource	$resultset  Result set of request
592
	 * @return  int		    			Nb of lines
593
	 * @see 	num_rows
594
	 */
595
	function affected_rows($resultset)
596
	{
597
        // If resultset not provided, we take the last used by connexion
598
		if (! is_resource($resultset)) { $resultset=$this->_results; }
599
		// pgsql necessite un resultset pour cette fonction contrairement
600
		// a mysql qui prend un link de base
601
		return pg_affected_rows($resultset);
602
	}
603
604
605
	/**
606
	 * Libere le dernier resultset utilise sur cette connexion
607
	 *
608
	 * @param	resource	$resultset  Result set of request
609
	 * @return	void
610
	 */
611
	function free($resultset=null)
612
	{
613
        // If resultset not provided, we take the last used by connexion
614
		if (! is_resource($resultset)) { $resultset=$this->_results; }
615
		// Si resultset en est un, on libere la memoire
616
		if (is_resource($resultset)) pg_free_result($resultset);
617
	}
618
619
620
	/**
621
     *	Define limits and offset of request
622
     *
623
     *	@param	int		$limit      Maximum number of lines returned (-1=conf->liste_limit, 0=no limit)
624
     *	@param	int		$offset     Numero of line from where starting fetch
625
     *	@return	string      		String with SQL syntax to add a limit and offset
626
	 */
627
	function plimit($limit=0,$offset=0)
628
	{
629
		global $conf;
630
        if (empty($limit)) return "";
631
		if ($limit < 0) $limit=$conf->liste_limit;
632
		if ($offset > 0) return " LIMIT ".$limit." OFFSET ".$offset." ";
633
		else return " LIMIT $limit ";
634
	}
635
636
637
	/**
638
	 *   Escape a string to insert data
639
	 *
640
	 *   @param		string	$stringtoencode		String to escape
641
	 *   @return	string						String escaped
642
	 */
643
	function escape($stringtoencode)
644
	{
645
		return pg_escape_string($stringtoencode);
646
	}
647
648
    /**
649
	 *   Convert (by PHP) a GM Timestamp date into a GM string date to insert into a date field.
650
	 *   Function to use to build INSERT, UPDATE or WHERE predica
651
	 *
652
	 *   @param	    string	$param      Date TMS to convert
653
	 *   @return	string   			Date in a string YYYYMMDDHHMMSS
654
	 */
655
	function idate($param)
656
	{
657
		return dol_print_date($param,"%Y-%m-%d %H:%M:%S");
658
	}
659
660
	/**
661
     *  Format a SQL IF
662
     *
663
	 *  @param	string	$test           Test string (example: 'cd.statut=0', 'field IS NULL')
664
	 *  @param	string	$resok          resultat si test egal
665
	 *  @param	string	$resko          resultat si test non egal
666
	 *  @return	string          		chaine formate SQL
667
	 */
668
	function ifsql($test,$resok,$resko)
669
	{
670
		return '(CASE WHEN '.$test.' THEN '.$resok.' ELSE '.$resko.' END)';
671
	}
672
673
	/**
674
	 * Renvoie le code erreur generique de l'operation precedente.
675
	 *
676
	 * @return	string		Error code (Exemples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...)
677
	 */
678
	function errno()
679
	{
680
		if (! $this->connected) {
681
			// Si il y a eu echec de connexion, $this->db n'est pas valide.
682
			return 'DB_ERROR_FAILED_TO_CONNECT';
683
		}
684
		else {
685
			// Constants to convert error code to a generic Dolibarr error code
686
			$errorcode_map = array(
687
			1004 => 'DB_ERROR_CANNOT_CREATE',
688
			1005 => 'DB_ERROR_CANNOT_CREATE',
689
			1006 => 'DB_ERROR_CANNOT_CREATE',
690
			1007 => 'DB_ERROR_ALREADY_EXISTS',
691
			1008 => 'DB_ERROR_CANNOT_DROP',
692
			1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP',
693
			1044 => 'DB_ERROR_ACCESSDENIED',
694
			1046 => 'DB_ERROR_NODBSELECTED',
695
			1048 => 'DB_ERROR_CONSTRAINT',
696
			'42P07' => 'DB_ERROR_TABLE_OR_KEY_ALREADY_EXISTS',
697
			'42703' => 'DB_ERROR_NOSUCHFIELD',
698
			1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
699
			42701=> 'DB_ERROR_COLUMN_ALREADY_EXISTS',
700
			'42710' => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
701
			'23505' => 'DB_ERROR_RECORD_ALREADY_EXISTS',
702
			'42704' => 'DB_ERROR_NO_INDEX_TO_DROP',		// May also be Type xxx does not exists
703
			'42601' => 'DB_ERROR_SYNTAX',
704
			'42P16' => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS',
705
			1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY',
706
			1091 => 'DB_ERROR_NOSUCHFIELD',
707
			1100 => 'DB_ERROR_NOT_LOCKED',
708
			1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW',
709
			'42P01' => 'DB_ERROR_NOSUCHTABLE',
710
			'23503' => 'DB_ERROR_NO_PARENT',
711
			1217 => 'DB_ERROR_CHILD_EXISTS',
712
			1451 => 'DB_ERROR_CHILD_EXISTS',
713
			'42P04' => 'DB_DATABASE_ALREADY_EXISTS'
714
			);
715
716
			$errorlabel=pg_last_error($this->db);
717
			$errorcode='';
718
			if (preg_match('/: *([0-9P]+):/',$errorlabel,$reg))
719
			{
720
				$errorcode=$reg[1];
721
				if (isset($errorcode_map[$errorcode]))
722
				{
723
					return $errorcode_map[$errorcode];
724
				}
725
			}
726
			$errno=$errorcode?$errorcode:$errorlabel;
727
			return ($errno?'DB_ERROR_'.$errno:'0');
728
		}
729
		//                '/(Table does not exist\.|Relation [\"\'].*[\"\'] does not exist|sequence does not exist|class ".+" not found)$/' => 'DB_ERROR_NOSUCHTABLE',
730
		//                '/table [\"\'].*[\"\'] does not exist/' => 'DB_ERROR_NOSUCHTABLE',
731
		//                '/Relation [\"\'].*[\"\'] already exists|Cannot insert a duplicate key into (a )?unique index.*/'      => 'DB_ERROR_RECORD_ALREADY_EXISTS',
732
		//                '/divide by zero$/'                     => 'DB_ERROR_DIVZERO',
733
		//                '/pg_atoi: error in .*: can\'t parse /' => 'DB_ERROR_INVALID_NUMBER',
734
		//                '/ttribute [\"\'].*[\"\'] not found$|Relation [\"\'].*[\"\'] does not have attribute [\"\'].*[\"\']/' => 'DB_ERROR_NOSUCHFIELD',
735
		//                '/parser: parse error at or near \"/'   => 'DB_ERROR_SYNTAX',
736
		//                '/referential integrity violation/'     => 'DB_ERROR_CONSTRAINT'
737
	}
738
739
	/**
740
	 * Renvoie le texte de l'erreur pgsql de l'operation precedente
741
	 *
742
	 * @return	string		Error text
743
	 */
744
	function error()
745
	{
746
		return pg_last_error($this->db);
747
	}
748
749
	/**
750
	 * Get last ID after an insert INSERT
751
	 *
752
	 * @param   string	$tab    	Table name concerned by insert. Ne sert pas sous MySql mais requis pour compatibilite avec Postgresql
753
	 * @param	string	$fieldid	Field name
754
	 * @return  string     			Id of row
755
	 */
756
	function last_insert_id($tab,$fieldid='rowid')
757
	{
758
		//$result = pg_query($this->db,"SELECT MAX(".$fieldid.") FROM ".$tab);
759
		$result = pg_query($this->db,"SELECT currval('".$tab."_".$fieldid."_seq')");
760
		if (! $result)
761
		{
762
			print pg_last_error($this->db);
763
			exit;
764
		}
765
		//$nbre = pg_num_rows($result);
766
		$row = pg_fetch_result($result,0,0);
767
		return $row;
768
	}
769
770
	/**
771
     *  Encrypt sensitive data in database
772
     *  Warning: This function includes the escape, so it must use direct value
773
     *
774
     *  @param  string  $fieldorvalue   Field name or value to encrypt
775
     *  @param	int		$withQuotes     Return string with quotes
776
     *  @return string          		XXX(field) or XXX('value') or field or 'value'
777
	 */
778
	function encrypt($fieldorvalue, $withQuotes=0)
779
	{
780
		global $conf;
781
782
		// Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
783
		$cryptType = ($conf->db->dolibarr_main_db_encryption?$conf->db->dolibarr_main_db_encryption:0);
784
785
		//Encryption key
786
		$cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey)?$conf->db->dolibarr_main_db_cryptkey:'');
787
788
		$return = $fieldorvalue;
789
		return ($withQuotes?"'":"").$this->escape($return).($withQuotes?"'":"");
790
	}
791
792
793
	/**
794
	 *	Decrypt sensitive data in database
795
	 *
796
	 *	@param	int		$value			Value to decrypt
797
	 * 	@return	string					Decrypted value if used
798
	 */
799
	function decrypt($value)
800
	{
801
		global $conf;
802
803
		// Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
804
		$cryptType = ($conf->db->dolibarr_main_db_encryption?$conf->db->dolibarr_main_db_encryption:0);
805
806
		//Encryption key
807
		$cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey)?$conf->db->dolibarr_main_db_cryptkey:'');
808
809
		$return = $value;
810
		return $return;
811
	}
812
813
814
	/**
815
	 * Return connexion ID
816
	 *
817
	 * @return	        string      Id connexion
818
	 */
819
	function DDLGetConnectId()
820
	{
821
		return '?';
822
	}
823
824
825
826
	/**
827
	 *	Create a new database
828
	 *	Do not use function xxx_create_db (xxx=mysql, ...) as they are deprecated
829
	 *	We force to create database with charset this->forcecharset and collate this->forcecollate
830
	 *
831
	 *	@param	string	$database		Database name to create
832
	 * 	@param	string	$charset		Charset used to store data
833
	 * 	@param	string	$collation		Charset used to sort data
834
	 * 	@param	string	$owner			Username of database owner
835
	 * 	@return	false|resource				resource defined if OK, null if KO
836
	 */
837
	function DDLCreateDb($database,$charset='',$collation='',$owner='')
838
	{
839
	    if (empty($charset))   $charset=$this->forcecharset;
840
		if (empty($collation)) $collation=$this->forcecollate;
841
842
		// Test charset match LC_TYPE (pgsql error otherwise)
843
		//print $charset.' '.setlocale(LC_CTYPE,'0'); exit;
844
845
		$sql='CREATE DATABASE "'.$database.'" OWNER "'.$owner.'" ENCODING \''.$charset.'\'';
846
		dol_syslog($sql,LOG_DEBUG);
847
		$ret=$this->query($sql);
848
		return $ret;
849
	}
850
851
	/**
852
	 *  List tables into a database
853
	 *
854
	 *  @param	string		$database	Name of database
855
	 *  @param	string		$table		Name of table filter ('xxx%')
856
	 *  @return	array					List of tables in an array
857
	 */
858
	function DDLListTables($database, $table='')
859
	{
860
		$listtables=array();
861
862
		$like = '';
863
		if ($table) $like = " AND table_name LIKE '".$table."'";
864
		$result = pg_query($this->db, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'".$like." ORDER BY table_name");
865
        if ($result)
866
        {
867
    		while($row = $this->fetch_row($result))
868
    		{
869
    			$listtables[] = $row[0];
870
    		}
871
        }
872
		return $listtables;
873
	}
874
875
	/**
876
	 *	List information of columns into a table.
877
	 *
878
	 *	@param	string	$table		Name of table
879
	 *	@return	array				Tableau des informations des champs de la table
880
	 *
881
	 */
882
	function DDLInfoTable($table)
883
	{
884
		$infotables=array();
885
886
		$sql="SELECT ";
887
		$sql.="	infcol.column_name as \"Column\",";
888
		$sql.="	CASE WHEN infcol.character_maximum_length IS NOT NULL THEN infcol.udt_name || '('||infcol.character_maximum_length||')'";
889
		$sql.="		ELSE infcol.udt_name";
890
		$sql.="	END as \"Type\",";
891
		$sql.="	infcol.collation_name as \"Collation\",";
892
		$sql.="	infcol.is_nullable as \"Null\",";
893
		$sql.="	'' as \"Key\",";
894
		$sql.="	infcol.column_default as \"Default\",";
895
		$sql.="	'' as \"Extra\",";
896
		$sql.="	'' as \"Privileges\"";
897
		$sql.="	FROM information_schema.columns infcol";
898
		$sql.="	WHERE table_schema='public' ";
899
		$sql.="	AND table_name='".$table."'";
900
		$sql.="	ORDER BY ordinal_position;";
901
902
		dol_syslog($sql,LOG_DEBUG);
903
		$result = $this->query($sql);
904
		if ($result)
905
		{
906
    		 while($row = $this->fetch_row($result))
907
    		 {
908
    			$infotables[] = $row;
909
    		 }
910
		}
911
        return $infotables;
912
	}
913
914
915
	/**
916
	 *	Create a table into database
917
	 *
918
	 *	@param	    string	$table 			Nom de la table
919
	 *	@param	    array	$fields 		Tableau associatif [nom champ][tableau des descriptions]
920
	 *	@param	    string	$primary_key 	Nom du champ qui sera la clef primaire
921
	 *	@param	    string	$type 			Type de la table
922
	 *	@param	    array	$unique_keys 	Tableau associatifs Nom de champs qui seront clef unique => valeur
923
	 *	@param	    array	$fulltext_keys	Tableau des Nom de champs qui seront indexes en fulltext
924
	 *	@param	    array	$keys 			Tableau des champs cles noms => valeur
925
	 *	@return	    int						<0 if KO, >=0 if OK
926
	 */
927
	function DDLCreateTable($table,$fields,$primary_key,$type,$unique_keys=null,$fulltext_keys=null,$keys=null)
928
	{
929
		// FIXME: $fulltext_keys parameter is unused
930
931
		// cles recherchees dans le tableau des descriptions (fields) : type,value,attribute,null,default,extra
932
		// ex. : $fields['rowid'] = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
933
		$sql = "create table ".$table."(";
934
		$i=0;
935
		foreach($fields as $field_name => $field_desc)
936
		{
937
			$sqlfields[$i] = $field_name." ";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$sqlfields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sqlfields = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
938
			$sqlfields[$i]  .= $field_desc['type'];
0 ignored issues
show
Bug introduced by
The variable $sqlfields does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
939
			if( preg_match("/^[^\s]/i",$field_desc['value']))
940
			$sqlfields[$i]  .= "(".$field_desc['value'].")";
941
			else if( preg_match("/^[^\s]/i",$field_desc['attribute']))
942
			$sqlfields[$i]  .= " ".$field_desc['attribute'];
943
			else if( preg_match("/^[^\s]/i",$field_desc['default']))
944
			{
945
				if(preg_match("/null/i",$field_desc['default']))
946
				$sqlfields[$i]  .= " default ".$field_desc['default'];
947
				else
948
				$sqlfields[$i]  .= " default '".$field_desc['default']."'";
949
			}
950
			else if( preg_match("/^[^\s]/i",$field_desc['null']))
951
			$sqlfields[$i]  .= " ".$field_desc['null'];
952
953
			else if( preg_match("/^[^\s]/i",$field_desc['extra']))
954
			$sqlfields[$i]  .= " ".$field_desc['extra'];
955
			$i++;
956
		}
957
		if($primary_key != "")
958
		$pk = "primary key(".$primary_key.")";
959
960
		if(is_array($unique_keys))
961
		{
962
			$i = 0;
963
			foreach($unique_keys as $key => $value)
964
			{
965
				$sqluq[$i] = "UNIQUE KEY '".$key."' ('".$value."')";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$sqluq was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sqluq = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
966
				$i++;
967
			}
968
		}
969
		if(is_array($keys))
970
		{
971
			$i = 0;
972
			foreach($keys as $key => $value)
973
			{
974
				$sqlk[$i] = "KEY ".$key." (".$value.")";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$sqlk was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sqlk = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
975
				$i++;
976
			}
977
		}
978
		$sql .= implode(',',$sqlfields);
979
		if($primary_key != "")
980
		$sql .= ",".$pk;
0 ignored issues
show
Bug introduced by
The variable $pk does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
981
		if(is_array($unique_keys))
982
		$sql .= ",".implode(',',$sqluq);
0 ignored issues
show
Bug introduced by
The variable $sqluq does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
983
		if(is_array($keys))
984
		$sql .= ",".implode(',',$sqlk);
0 ignored issues
show
Bug introduced by
The variable $sqlk does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
985
		$sql .=") type=".$type;
986
987
		dol_syslog($sql,LOG_DEBUG);
988
		if(! $this->query($sql))
989
		return -1;
990
		else
991
		return 1;
992
	}
993
994
	/**
995
	 * 	Create a user to connect to database
996
	 *
997
	 *	@param	string	$dolibarr_main_db_host 		Ip server
998
	 *	@param	string	$dolibarr_main_db_user 		Name of user to create
999
	 *	@param	string	$dolibarr_main_db_pass 		Password of user to create
1000
	 *	@param	string	$dolibarr_main_db_name		Database name where user must be granted
1001
	 *	@return	int									<0 if KO, >=0 if OK
1002
	 */
1003
	function DDLCreateUser($dolibarr_main_db_host,$dolibarr_main_db_user,$dolibarr_main_db_pass,$dolibarr_main_db_name)
1004
	{
1005
		// Note: using ' on user does not works with pgsql
1006
		$sql = "CREATE USER ".$this->escape($dolibarr_main_db_user)." with password '".$this->escape($dolibarr_main_db_pass)."'";
1007
1008
		dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG);	// No sql to avoid password in log
1009
		$resql=$this->query($sql);
1010
		if (! $resql)
1011
		{
1012
			return -1;
1013
		}
1014
1015
		return 1;
1016
	}
1017
1018
	/**
1019
	 *	Return a pointer of line with description of a table or field
1020
	 *
1021
	 *	@param	string		$table	Name of table
1022
	 *	@param	string		$field	Optionnel : Name of field if we want description of field
1023
	 *	@return	false|resource		Resultset x (x->attname)
1024
	 */
1025
	function DDLDescTable($table,$field="")
1026
	{
1027
		$sql ="SELECT attname FROM pg_attribute, pg_type WHERE typname = '".$table."' AND attrelid = typrelid";
1028
		$sql.=" AND attname NOT IN ('cmin', 'cmax', 'ctid', 'oid', 'tableoid', 'xmin', 'xmax')";
1029
		if ($field) $sql.= " AND attname = '".$field."'";
1030
1031
		dol_syslog($sql,LOG_DEBUG);
1032
		$this->_results = $this->query($sql);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->query($sql) can also be of type false. However, the property $_results is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1033
		return $this->_results;
1034
	}
1035
1036
	/**
1037
	 *	Create a new field into table
1038
	 *
1039
	 *	@param	string	$table 				Name of table
1040
	 *	@param	string	$field_name 		Name of field to add
1041
	 *	@param	string	$field_desc 		Tableau associatif de description du champ a inserer[nom du parametre][valeur du parametre]
1042
	 *	@param	string	$field_position 	Optionnel ex.: "after champtruc"
1043
	 *	@return	int							<0 if KO, >0 if OK
1044
	 */
1045
	function DDLAddField($table,$field_name,$field_desc,$field_position="")
1046
	{
1047
		// cles recherchees dans le tableau des descriptions (field_desc) : type,value,attribute,null,default,extra
1048
		// ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
1049
		$sql= "ALTER TABLE ".$table." ADD ".$field_name." ";
1050
		$sql .= $field_desc['type'];
1051
		if(preg_match("/^[^\s]/i",$field_desc['value']))
1052
		    if (! in_array($field_desc['type'],array('int','date','datetime')))
1053
		    {
1054
		        $sql.= "(".$field_desc['value'].")";
1055
		    }
1056
		if (preg_match("/^[^\s]/i",$field_desc['attribute']))
1057
		$sql .= " ".$field_desc['attribute'];
1058
		if (preg_match("/^[^\s]/i",$field_desc['null']))
1059
		$sql .= " ".$field_desc['null'];
1060
		if (preg_match("/^[^\s]/i",$field_desc['default']))
1061
		if (preg_match("/null/i",$field_desc['default']))
1062
		$sql .= " default ".$field_desc['default'];
1063
		else
1064
		$sql .= " default '".$field_desc['default']."'";
1065
		if (preg_match("/^[^\s]/i",$field_desc['extra']))
1066
		$sql .= " ".$field_desc['extra'];
1067
		$sql .= " ".$field_position;
1068
1069
		dol_syslog($sql,LOG_DEBUG);
1070
		if(! $this -> query($sql))
1071
			return -1;
1072
		return 1;
1073
	}
1074
1075
	/**
1076
	 *	Update format of a field into a table
1077
	 *
1078
	 *	@param	string	$table 				Name of table
1079
	 *	@param	string	$field_name 		Name of field to modify
1080
	 *	@param	string	$field_desc 		Array with description of field format
1081
	 *	@return	int							<0 if KO, >0 if OK
1082
	 */
1083
	function DDLUpdateField($table,$field_name,$field_desc)
1084
	{
1085
		$sql = "ALTER TABLE ".$table;
1086
		$sql .= " MODIFY COLUMN ".$field_name." ".$field_desc['type'];
1087
		if ($field_desc['type'] == 'tinyint' || $field_desc['type'] == 'int' || $field_desc['type'] == 'varchar') {
1088
			$sql.="(".$field_desc['value'].")";
1089
		}
1090
1091
		if ($field_desc['null'] == 'not null' || $field_desc['null'] == 'NOT NULL')
1092
		{
1093
        	// 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
1094
        	if ($field_desc['type'] == 'varchar' || $field_desc['type'] == 'text')
1095
        	{
1096
        		$sqlbis="UPDATE ".$table." SET ".$field_name." = '".$this->escape($field_desc['default'] ? $field_desc['default'] : '')."' WHERE ".$field_name." IS NULL";
1097
        		$this->query($sqlbis);
1098
        	}
1099
        	elseif ($field_desc['type'] == 'tinyint' || $field_desc['type'] == 'int')
1100
        	{
1101
        		$sqlbis="UPDATE ".$table." SET ".$field_name." = ".((int) $this->escape($field_desc['default'] ? $field_desc['default'] : 0))." WHERE ".$field_name." IS NULL";
1102
        		$this->query($sqlbis);
1103
        	}
1104
		}
1105
1106
		if ($field_desc['default'])
1107
		{
1108
			if ($field_desc['type'] == 'tinyint' || $field_desc['type'] == 'int') $sql.=" DEFAULT ".$this->escape($field_desc['default']);
1109
        	elseif ($field_desc['type'] == 'text') $sql.=" DEFAULT '".$this->escape($field_desc['default'])."'";							// Default not supported on text fields
1110
		}
1111
1112
		dol_syslog($sql,LOG_DEBUG);
1113
		if (! $this->query($sql))
1114
			return -1;
1115
		return 1;
1116
	}
1117
1118
	/**
1119
	 *	Drop a field from table
1120
	 *
1121
	 *	@param	string	$table 			Name of table
1122
	 *	@param	string	$field_name 	Name of field to drop
1123
	 *	@return	int						<0 if KO, >0 if OK
1124
	 */
1125
	function DDLDropField($table,$field_name)
1126
	{
1127
		$sql= "ALTER TABLE ".$table." DROP COLUMN ".$field_name;
1128
		dol_syslog($sql,LOG_DEBUG);
1129
		if (! $this->query($sql))
1130
		{
1131
			$this->error=$this->lasterror();
1132
			return -1;
1133
		}
1134
		return 1;
1135
	}
1136
1137
	/**
1138
	 *	Return charset used to store data in database
1139
	 *
1140
	 *	@return		string		Charset
1141
	 */
1142
	function getDefaultCharacterSetDatabase()
1143
	{
1144
		$resql=$this->query('SHOW SERVER_ENCODING');
1145
		if ($resql)
1146
		{
1147
            $liste=$this->fetch_array($resql);
1148
		    return $liste['server_encoding'];
1149
		}
1150
		else return '';
1151
	}
1152
1153
	/**
1154
	 *	Return list of available charset that can be used to store data in database
1155
	 *
1156
	 *	@return		array		List of Charset
1157
	 */
1158
	function getListOfCharacterSet()
1159
	{
1160
		$resql=$this->query('SHOW SERVER_ENCODING');
1161
		$liste = array();
1162
		if ($resql)
1163
		{
1164
			$i = 0;
1165
			while ($obj = $this->fetch_object($resql))
1166
			{
1167
				$liste[$i]['charset'] = $obj->server_encoding;
1168
				$liste[$i]['description'] = 'Default database charset';
1169
				$i++;
1170
			}
1171
			$this->free($resql);
1172
		} else {
1173
			return null;
1174
		}
1175
		return $liste;
1176
	}
1177
1178
	/**
1179
	 *	Return collation used in database
1180
	 *
1181
	 *	@return		string		Collation value
1182
	 */
1183
	function getDefaultCollationDatabase()
1184
	{
1185
		$resql=$this->query('SHOW LC_COLLATE');
1186
		if ($resql)
1187
		{
1188
		    $liste=$this->fetch_array($resql);
1189
			return $liste['lc_collate'];
1190
		}
1191
		else return '';
1192
	}
1193
1194
	/**
1195
	 *	Return list of available collation that can be used for database
1196
	 *
1197
	 *	@return		array		Liste of Collation
1198
	 */
1199
	function getListOfCollation()
1200
	{
1201
		$resql=$this->query('SHOW LC_COLLATE');
1202
		$liste = array();
1203
		if ($resql)
1204
		{
1205
			$i = 0;
1206
			while ($obj = $this->fetch_object($resql) )
1207
			{
1208
				$liste[$i]['collation'] = $obj->lc_collate;
1209
				$i++;
1210
			}
1211
			$this->free($resql);
1212
		} else {
1213
			return null;
1214
		}
1215
		return $liste;
1216
	}
1217
1218
	/**
1219
	 *	Return full path of dump program
1220
	 *
1221
	 *	@return		string		Full path of dump program
1222
	 */
1223
	function getPathOfDump()
1224
	{
1225
		$fullpathofdump='/pathtopgdump/pg_dump';
1226
1227
		if (file_exists('/usr/bin/pg_dump'))
1228
		{
1229
		    $fullpathofdump='/usr/bin/pg_dump';
1230
		}
1231
		else
1232
		{
1233
            // TODO L'utilisateur de la base doit etre un superadmin pour lancer cette commande
1234
		    $resql=$this->query('SHOW data_directory');
1235
    		if ($resql)
1236
    		{
1237
    			$liste=$this->fetch_array($resql);
1238
    			$basedir=$liste['data_directory'];
1239
    			$fullpathofdump=preg_replace('/data$/','bin',$basedir).'/pg_dump';
1240
    		}
1241
		}
1242
1243
		return $fullpathofdump;
1244
	}
1245
1246
    /**
1247
     *	Return full path of restore program
1248
     *
1249
     *	@return		string		Full path of restore program
1250
     */
1251
	function getPathOfRestore()
1252
	{
1253
		//$tool='pg_restore';
1254
		$tool='psql';
1255
1256
		$fullpathofdump='/pathtopgrestore/'.$tool;
1257
1258
        if (file_exists('/usr/bin/'.$tool))
1259
        {
1260
            $fullpathofdump='/usr/bin/'.$tool;
1261
        }
1262
        else
1263
        {
1264
            // TODO L'utilisateur de la base doit etre un superadmin pour lancer cette commande
1265
            $resql=$this->query('SHOW data_directory');
1266
            if ($resql)
1267
            {
1268
                $liste=$this->fetch_array($resql);
1269
                $basedir=$liste['data_directory'];
1270
                $fullpathofdump=preg_replace('/data$/','bin',$basedir).'/'.$tool;
1271
            }
1272
        }
1273
1274
		return $fullpathofdump;
1275
	}
1276
1277
	/**
1278
	 * Return value of server parameters
1279
	 *
1280
	 * @param	string	$filter		Filter list on a particular value
1281
	 * @return	array				Array of key-values (key=>value)
1282
	 */
1283
	function getServerParametersValues($filter='')
1284
	{
1285
		$result=array();
1286
1287
		$resql='select name,setting from pg_settings';
1288
		if ($filter) $resql.=" WHERE name = '".$this->escape($filter)."'";
1289
		$resql=$this->query($resql);
1290
		if ($resql)
1291
		{
1292
			while ($obj=$this->fetch_object($resql)) $result[$obj->name]=$obj->setting;
1293
		}
1294
1295
		return $result;
1296
	}
1297
1298
	/**
1299
	 * Return value of server status
1300
	 *
1301
	 * @param	string	$filter		Filter list on a particular value
1302
	 * @return  array				Array of key-values (key=>value)
1303
	 */
1304
	function getServerStatusValues($filter='')
1305
	{
1306
		/* This is to return current running requests.
1307
		$sql='SELECT datname,procpid,current_query FROM pg_stat_activity ORDER BY procpid';
1308
        if ($filter) $sql.=" LIKE '".$this->escape($filter)."'";
1309
        $resql=$this->query($sql);
1310
        if ($resql)
1311
        {
1312
            $obj=$this->fetch_object($resql);
1313
            $result[$obj->Variable_name]=$obj->Value;
1314
        }
1315
		*/
1316
1317
		return array();
1318
	}
1319
}
1320