Completed
Push — console-installer ( e2b50d...6ce748 )
by Adam
22:30
created

MysqlManager   D

Complexity

Total Complexity 253

Size/Duplication

Total Lines 1412
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 1412
rs 4.4102
wmc 253
lcom 1
cbo 0

70 Methods

Rating   Name   Duplication   Size   Complexity  
B query() 0 23 4
A getAffectedRowCount() 0 4 1
A getRowCount() 0 4 1
A disconnect() 0 9 2
A freeDbResult() 0 5 2
A hasLimit() 0 4 1
A limitQuery() 0 20 4
C checkQuery() 0 36 13
C get_columns() 0 23 8
B getFieldsArray() 0 21 5
A fetchRow() 0 6 2
A getTablesArray() 0 18 4
A version() 0 4 1
A tableExists() 0 13 3
A tablesLike() 0 15 4
A quote() 0 7 2
A quoteIdentifier() 0 4 1
D connect() 0 65 14
B repairTableParams() 0 40 5
C convert() 0 52 20
A fromConvert() 0 4 1
A getEngine() 0 9 2
B isEngineEnabled() 0 14 5
A createTableSQL() 0 8 1
C createTableSQLParams() 0 27 8
A isTextType() 0 5 1
B oneColumnSQLRep() 0 19 6
B changeColumnSQL() 0 19 5
C keysSQL() 0 58 18
A setAutoIncrement() 0 4 1
A setAutoIncrementStart() 0 5 1
A getAutoIncrement() 0 9 2
A get_indices() 0 21 4
C add_drop_constraint() 0 39 11
A fetchOne() 0 8 2
A full_text_indexing_installed() 0 4 1
F massageFieldDef() 0 35 20
A dropTableNameSQL() 0 4 1
B dropIndexes() 0 23 6
A getDefaultCollation() 0 4 1
A getCollationList() 0 10 2
A renameColumnSQL() 0 6 1
A emptyValue() 0 14 4
A lastDbError() 0 14 4
A quoteTerm() 0 7 2
A getFulltextQuery() 0 15 4
A getCharsetInfo() 0 9 2
A getDbInfo() 0 15 2
A validateQuery() 0 5 1
B makeTempTableCopy() 0 25 5
B verifyAlterTable() 0 37 6
A verifyGenericReplaceQuery() 0 19 3
A verifyDropTable() 0 4 1
A verifyInsertInto() 0 4 1
A verifyUpdate() 0 4 1
A verifyDeleteFrom() 0 4 1
A dbExists() 0 5 1
A selectDb() 0 4 1
A userExists() 0 12 3
A createDbUser() 0 13 2
A createDatabase() 0 4 1
A preInstall() 0 6 1
A dropDatabase() 0 4 1
A valid() 0 4 1
A canInstall() 0 11 3
A installConfig() 0 16 1
A disableKeys() 0 4 1
A enableKeys() 0 4 1
A getFromDummyTable() 0 4 1
A getGuidSQL() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like MysqlManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MysqlManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3
/*********************************************************************************
4
 * SugarCRM Community Edition is a customer relationship management program developed by
5
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6
7
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
8
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU Affero General Public License version 3 as published by the
12
 * Free Software Foundation with the addition of the following permission added
13
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
14
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
15
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License along with
23
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
24
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
 * 02110-1301 USA.
26
 *
27
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
28
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
29
 *
30
 * The interactive user interfaces in modified source and object code versions
31
 * of this program must display Appropriate Legal Notices, as required under
32
 * Section 5 of the GNU Affero General Public License version 3.
33
 *
34
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
35
 * these Appropriate Legal Notices must retain the display of the "Powered by
36
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
37
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
38
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
39
 ********************************************************************************/
40
41
/*********************************************************************************
42
43
* Description: This file handles the Data base functionality for the application.
44
* It acts as the DB abstraction layer for the application. It depends on helper classes
45
* which generate the necessary SQL. This sql is then passed to PEAR DB classes.
46
* The helper class is chosen in DBManagerFactory, which is driven by 'db_type' in 'dbconfig' under config.php.
47
*
48
* All the functions in this class will work with any bean which implements the meta interface.
49
* The passed bean is passed to helper class which uses these functions to generate correct sql.
50
*
51
* The meta interface has the following functions:
52
* getTableName()	        	Returns table name of the object.
53
* getFieldDefinitions()	    	Returns a collection of field definitions in order.
54
* getFieldDefintion(name)		Return field definition for the field.
55
* getFieldValue(name)	    	Returns the value of the field identified by name.
56
*                           	If the field is not set, the function will return boolean FALSE.
57
* getPrimaryFieldDefinition()	Returns the field definition for primary key
58
*
59
* The field definition is an array with the following keys:
60
*
61
* name 		This represents name of the field. This is a required field.
62
* type 		This represents type of the field. This is a required field and valid values are:
63
*           �   int
64
*           �   long
65
*           �   varchar
66
*           �   text
67
*           �   date
68
*           �   datetime
69
*           �   double
70
*           �   float
71
*           �   uint
72
*           �   ulong
73
*           �   time
74
*           �   short
75
*           �   enum
76
* length    This is used only when the type is varchar and denotes the length of the string.
77
*           The max value is 255.
78
* enumvals  This is a list of valid values for an enum separated by "|".
79
*           It is used only if the type is �enum�;
80
* required  This field dictates whether it is a required value.
81
*           The default value is �FALSE�.
82
* isPrimary This field identifies the primary key of the table.
83
*           If none of the fields have this flag set to �TRUE�,
84
*           the first field definition is assume to be the primary key.
85
*           Default value for this field is �FALSE�.
86
* default   This field sets the default value for the field definition.
87
*
88
*
89
* Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
90
* All Rights Reserved.
91
* Contributor(s): ______________________________________..
92
********************************************************************************/
93
94
/**
95
 * MySQL manager implementation for mysql extension
96
 */
97
class MysqlManager extends DBManager
98
{
99
	/**
100
	 * @see DBManager::$dbType
101
	 */
102
	public $dbType = 'mysql';
103
	public $variant = 'mysql';
104
	public $dbName = 'MySQL';
105
	public $label = 'LBL_MYSQL';
106
107
	protected $maxNameLengths = array(
108
		'table' => 64,
109
		'column' => 64,
110
		'index' => 64,
111
		'alias' => 256
112
	);
113
114
	protected $type_map = array(
115
			'int'      => 'int',
116
			'double'   => 'double',
117
			'float'    => 'float',
118
			'uint'     => 'int unsigned',
119
			'ulong'    => 'bigint unsigned',
120
			'long'     => 'bigint',
121
			'short'    => 'smallint',
122
			'varchar'  => 'varchar',
123
			'text'     => 'text',
124
			'longtext' => 'longtext',
125
			'date'     => 'date',
126
			'enum'     => 'varchar',
127
			'relate'   => 'varchar',
128
			'multienum'=> 'text',
129
			'html'     => 'text',
130
			'longhtml' => 'longtext',
131
			'datetime' => 'datetime',
132
			'datetimecombo' => 'datetime',
133
			'time'     => 'time',
134
			'bool'     => 'bool',
135
			'tinyint'  => 'tinyint',
136
			'char'     => 'char',
137
			'blob'     => 'blob',
138
			'longblob' => 'longblob',
139
			'currency' => 'decimal(26,6)',
140
			'decimal'  => 'decimal',
141
			'decimal2' => 'decimal',
142
			'id'       => 'char(36)',
143
			'url'      => 'varchar',
144
			'encrypt'  => 'varchar',
145
			'file'     => 'varchar',
146
			'decimal_tpl' => 'decimal(%d, %d)',
147
148
	);
149
150
	protected $capabilities = array(
151
		"affected_rows" => true,
152
		"select_rows" => true,
153
		"inline_keys" => true,
154
		"create_user" => true,
155
		"fulltext" => true,
156
	    "collation" => true,
157
	    "create_db" => true,
158
	    "disable_keys" => true,
159
	);
160
161
	/**
162
	 * Parses and runs queries
163
	 *
164
	 * @param  string   $sql        SQL Statement to execute
165
	 * @param  bool     $dieOnError True if we want to call die if the query returns errors
166
	 * @param  string   $msg        Message to log if error occurs
167
	 * @param  bool     $suppress   Flag to suppress all error output unless in debug logging mode.
168
	 * @param  bool     $keepResult True if we want to push this result into the $lastResult array.
169
	 * @return resource result set
170
	 */
171
	public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false)
172
	{
173
		if(is_array($sql)) {
174
			return $this->queryArray($sql, $dieOnError, $msg, $suppress);
175
		}
176
177
		parent::countQuery($sql);
178
		$GLOBALS['log']->debug('Query:' . $sql);
179
		$this->checkConnection();
180
		$this->query_time = microtime(true);
0 ignored issues
show
Documentation Bug introduced by
The property $query_time was declared of type integer, but microtime(true) is of type double. 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...
181
		$this->lastsql = $sql;
182
		$result = $suppress?@mysql_query($sql, $this->database):mysql_query($sql, $this->database);
183
184
		$this->query_time = microtime(true) - $this->query_time;
0 ignored issues
show
Documentation Bug introduced by
The property $query_time was declared of type integer, but microtime(true) - $this->query_time is of type double. 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...
185
		$GLOBALS['log']->debug('Query Execution Time:'.$this->query_time);
186
187
188
		if($keepResult)
189
			$this->lastResult = $result;
190
191
		$this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
192
		return $result;
193
	}
194
195
    /**
196
     * Returns the number of rows affected by the last query
197
     * @param $result
198
     * @return int
199
     */
200
	public function getAffectedRowCount($result)
201
	{
202
		return mysql_affected_rows($this->getDatabase());
203
	}
204
205
	/**
206
	 * Returns the number of rows returned by the result
207
	 *
208
	 * This function can't be reliably implemented on most DB, do not use it.
209
	 * @abstract
210
	 * @deprecated
211
	 * @param  resource $result
212
	 * @return int
213
	 */
214
	public function getRowCount($result)
215
	{
216
	    return mysql_num_rows($result);
217
	}
218
219
	/**
220
	 * Disconnects from the database
221
	 *
222
	 * Also handles any cleanup needed
223
	 */
224
	public function disconnect()
225
	{
226
		$GLOBALS['log']->debug('Calling MySQL::disconnect()');
227
		if(!empty($this->database)){
228
			$this->freeResult();
229
			mysql_close($this->database);
230
			$this->database = null;
231
		}
232
	}
233
234
	/**
235
	 * @see DBManager::freeDbResult()
236
	 */
237
	protected function freeDbResult($dbResult)
238
	{
239
		if(!empty($dbResult))
240
			mysql_free_result($dbResult);
241
	}
242
243
244
	/**
245
	 * @abstract
246
	 * Check if query has LIMIT clause
247
	 * Relevant for now only for Mysql
248
	 * @param string $sql
249
	 * @return bool
250
	 */
251
	protected function hasLimit($sql)
252
	{
253
	    return stripos($sql, " limit ") !== false;
254
	}
255
256
	/**
257
	 * @see DBManager::limitQuery()
258
	 */
259
	public function limitQuery($sql, $start, $count, $dieOnError = false, $msg = '', $execute = true)
260
	{
261
        $start = (int)$start;
262
        $count = (int)$count;
263
	    if ($start < 0)
264
			$start = 0;
265
		$GLOBALS['log']->debug('Limit Query:' . $sql. ' Start: ' .$start . ' count: ' . $count);
266
267
	    $sql = "$sql LIMIT $start,$count";
268
		$this->lastsql = $sql;
269
270
		if(!empty($GLOBALS['sugar_config']['check_query'])){
271
			$this->checkQuery($sql);
272
		}
273
		if(!$execute) {
274
			return $sql;
275
		}
276
277
		return $this->query($sql, $dieOnError, $msg);
278
	}
279
280
281
	/**
282
	 * @see DBManager::checkQuery()
283
	 */
284
	protected function checkQuery($sql, $object_name = false)
285
	{
286
		$result   = $this->query('EXPLAIN ' . $sql);
287
		$badQuery = array();
288
		while ($row = $this->fetchByAssoc($result)) {
289
			if (empty($row['table']))
290
				continue;
291
			$badQuery[$row['table']] = '';
292
			if (strtoupper($row['type']) == 'ALL')
293
				$badQuery[$row['table']]  .=  ' Full Table Scan;';
294
			if (empty($row['key']))
295
				$badQuery[$row['table']] .= ' No Index Key Used;';
296
			if (!empty($row['Extra']) && substr_count($row['Extra'], 'Using filesort') > 0)
297
				$badQuery[$row['table']] .= ' Using FileSort;';
298
			if (!empty($row['Extra']) && substr_count($row['Extra'], 'Using temporary') > 0)
299
				$badQuery[$row['table']] .= ' Using Temporary Table;';
300
		}
301
302
		if ( empty($badQuery) )
303
			return true;
304
305
		foreach($badQuery as $table=>$data ){
306
			if(!empty($data)){
307
				$warning = ' Table:' . $table . ' Data:' . $data;
308
				if(!empty($GLOBALS['sugar_config']['check_query_log'])){
309
					$GLOBALS['log']->fatal($sql);
310
					$GLOBALS['log']->fatal('CHECK QUERY:' .$warning);
311
				}
312
				else{
313
					$GLOBALS['log']->warn('CHECK QUERY:' .$warning);
314
				}
315
			}
316
		}
317
318
		return false;
319
	}
320
321
	/**
322
	 * @see DBManager::get_columns()
323
	 */
324
	public function get_columns($tablename)
325
	{
326
		//find all unique indexes and primary keys.
327
		$result = $this->query("DESCRIBE $tablename");
328
329
		$columns = array();
330
		while (($row=$this->fetchByAssoc($result)) !=null) {
331
			$name = strtolower($row['Field']);
332
			$columns[$name]['name']=$name;
333
			$matches = array();
334
			preg_match_all('/(\w+)(?:\(([0-9]+,?[0-9]*)\)|)( unsigned)?/i', $row['Type'], $matches);
335
			$columns[$name]['type']=strtolower($matches[1][0]);
336
			if ( isset($matches[2][0]) && in_array(strtolower($matches[1][0]),array('varchar','char','varchar2','int','decimal','float')) )
337
				$columns[$name]['len']=strtolower($matches[2][0]);
338
			if ( stristr($row['Extra'],'auto_increment') )
339
				$columns[$name]['auto_increment'] = '1';
340
			if ($row['Null'] == 'NO' && !stristr($row['Key'],'PRI'))
341
				$columns[$name]['required'] = 'true';
342
			if (!empty($row['Default']) )
343
				$columns[$name]['default'] = $row['Default'];
344
		}
345
		return $columns;
346
	}
347
348
	/**
349
	 * @see DBManager::getFieldsArray()
350
	 */
351
	public function getFieldsArray($result, $make_lower_case=false)
352
	{
353
		$field_array = array();
354
355
		if(empty($result))
356
			return 0;
357
358
		$fields = mysql_num_fields($result);
359
		for ($i=0; $i < $fields; $i++) {
360
			$meta = mysql_fetch_field($result, $i);
361
			if (!$meta)
362
				return array();
363
364
			if($make_lower_case == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
365
				$meta->name = strtolower($meta->name);
366
367
			$field_array[] = $meta->name;
368
		}
369
370
		return $field_array;
371
	}
372
373
	/**
374
	 * @see DBManager::fetchRow()
375
	 */
376
	public function fetchRow($result)
377
	{
378
		if (empty($result))	return false;
379
380
		return mysql_fetch_assoc($result);
381
	}
382
383
	/**
384
	 * @see DBManager::getTablesArray()
385
	 */
386
	public function getTablesArray()
387
	{
388
		$this->log->debug('Fetching table list');
389
390
		if ($this->getDatabase()) {
391
			$tables = array();
392
			$r = $this->query('SHOW TABLES');
393
			if (!empty($r)) {
394
				while ($a = $this->fetchByAssoc($r)) {
395
					$row = array_values($a);
396
					$tables[]=$row[0];
397
				}
398
				return $tables;
399
			}
400
		}
401
402
		return false; // no database available
403
	}
404
405
	/**
406
	 * @see DBManager::version()
407
	 */
408
	public function version()
409
	{
410
		return $this->getOne("SELECT version() version");
411
	}
412
413
	/**
414
	 * @see DBManager::tableExists()
415
	 */
416
	public function tableExists($tableName)
417
	{
418
		$this->log->debug("tableExists: $tableName");
419
420
		if ($this->getDatabase()) {
421
			$result = $this->query("SHOW TABLES LIKE ".$this->quoted($tableName));
422
			if(empty($result)) return false;
423
			$row = $this->fetchByAssoc($result);
424
			return !empty($row);
425
		}
426
427
		return false;
428
	}
429
430
	/**
431
	 * Get tables like expression
432
	 * @param $like string
433
	 * @return array
434
	 */
435
	public function tablesLike($like)
436
	{
437
		if ($this->getDatabase()) {
438
			$tables = array();
439
			$r = $this->query('SHOW TABLES LIKE '.$this->quoted($like));
440
			if (!empty($r)) {
441
				while ($a = $this->fetchByAssoc($r)) {
442
					$row = array_values($a);
443
					$tables[]=$row[0];
444
				}
445
				return $tables;
446
			}
447
		}
448
		return false;
449
	}
450
451
	/**
452
	 * @see DBManager::quote()
453
	 */
454
	public function quote($string)
455
	{
456
		if(is_array($string)) {
457
			return $this->arrayQuote($string);
458
		}
459
		return mysql_real_escape_string($this->quoteInternal($string), $this->getDatabase());
460
	}
461
462
    /**
463
     * @see DBManager::quoteIdentifier()
464
     */
465
    public function quoteIdentifier($string)
466
    {
467
        return '`'.$string.'`';
468
    }
469
470
	/**
471
	 * @see DBManager::connect()
472
	 */
473
	public function connect(array $configOptions = null, $dieOnError = false)
474
	{
475
		global $sugar_config;
476
477
		if(is_null($configOptions))
478
			$configOptions = $sugar_config['dbconfig'];
479
480
		if ($this->getOption('persistent')) {
481
			$this->database = @mysql_pconnect(
482
				$configOptions['db_host_name'],
483
				$configOptions['db_user_name'],
484
				$configOptions['db_password']
485
				);
486
		}
487
488
		if (!$this->database) {
489
			$this->database = mysql_connect(
490
					$configOptions['db_host_name'],
491
					$configOptions['db_user_name'],
492
					$configOptions['db_password']
493
					);
494
			if(empty($this->database)) {
495
				$GLOBALS['log']->fatal("Could not connect to server ".$configOptions['db_host_name']." as ".$configOptions['db_user_name'].":".mysql_error());
496
				if($dieOnError) {
497
					if(isset($GLOBALS['app_strings']['ERR_NO_DB'])) {
498
						sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
499
					} else {
500
						sugar_die("Could not connect to the database. Please refer to suitecrm.log for details.");
501
					}
502
				} else {
503
					return false;
504
				}
505
			}
506
			// Do not pass connection information because we have not connected yet
507
			if($this->database  && $this->getOption('persistent')){
508
				$_SESSION['administrator_error'] = "<b>Severe Performance Degradation: Persistent Database Connections "
509
					. "not working.  Please set \$sugar_config['dbconfigoption']['persistent'] to false "
510
					. "in your config.php file</b>";
511
			}
512
		}
513
		if(!empty($configOptions['db_name']) && !@mysql_select_db($configOptions['db_name'])) {
514
			$GLOBALS['log']->fatal( "Unable to select database {$configOptions['db_name']}: " . mysql_error($this->database));
515
			if($dieOnError) {
516
				sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
517
			} else {
518
				return false;
519
			}
520
		}
521
522
		// cn: using direct calls to prevent this from spamming the Logs
523
	    mysql_query("SET CHARACTER SET utf8", $this->database);
524
	    $names = "SET NAMES 'utf8'";
525
	    $collation = $this->getOption('collation');
526
	    if(!empty($collation)) {
527
	        $names .= " COLLATE '$collation'";
528
		}
529
	    mysql_query($names, $this->database);
530
531
		if(!$this->checkError('Could Not Connect:', $dieOnError))
532
			$GLOBALS['log']->info("connected to db");
533
		$this->connectOptions = $configOptions;
534
535
		$GLOBALS['log']->info("Connect:".$this->database);
536
		return true;
537
	}
538
539
	/**
540
	 * @see DBManager::repairTableParams()
541
	 *
542
	 * For MySQL, we can write the ALTER TABLE statement all in one line, which speeds things
543
	 * up quite a bit. So here, we'll parse the returned SQL into a single ALTER TABLE command.
544
	 */
545
	public function repairTableParams($tablename, $fielddefs, $indices, $execute = true, $engine = null)
546
	{
547
		$sql = parent::repairTableParams($tablename,$fielddefs,$indices,false,$engine);
548
549
		if ( $sql == '' )
550
			return '';
551
552
		if ( stristr($sql,'create table') )
553
		{
554
			if ($execute) {
555
				$msg = "Error creating table: ".$tablename. ":";
556
				$this->query($sql,true,$msg);
557
			}
558
			return $sql;
559
		}
560
561
		// first, parse out all the comments
562
		$match = array();
563
		preg_match_all('!/\*.*?\*/!is', $sql, $match);
564
		$commentBlocks = $match[0];
565
		$sql = preg_replace('!/\*.*?\*/!is','', $sql);
566
567
		// now, we should only have alter table statements
568
		// let's replace the 'alter table name' part with a comma
569
		$sql = preg_replace("!alter table $tablename!is",', ', $sql);
570
571
		// re-add it at the beginning
572
		$sql = substr_replace($sql,'',strpos($sql,','),1);
573
		$sql = str_replace(";","",$sql);
574
		$sql = str_replace("\n","",$sql);
575
		$sql = "ALTER TABLE $tablename $sql";
576
577
		if ( $execute )
578
			$this->query($sql,'Error with MySQL repair table');
579
580
		// and re-add the comments at the beginning
581
		$sql = implode("\n",$commentBlocks) . "\n". $sql . "\n";
582
583
		return $sql;
584
	}
585
586
	/**
587
	 * @see DBManager::convert()
588
	 */
589
	public function convert($string, $type, array $additional_parameters = array())
590
	{
591
		$all_parameters = $additional_parameters;
592
		if(is_array($string)) {
593
			$all_parameters = array_merge($string, $all_parameters);
594
		} elseif (!is_null($string)) {
595
			array_unshift($all_parameters, $string);
596
		}
597
		$all_strings = implode(',', $all_parameters);
598
599
		switch (strtolower($type)) {
600
			case 'today':
601
				return "CURDATE()";
602
			case 'left':
603
				return "LEFT($all_strings)";
604
			case 'date_format':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
605
				if(empty($additional_parameters)) {
606
					return "DATE_FORMAT($string,'%Y-%m-%d')";
607
				} else {
608
					$format = $additional_parameters[0];
609
					if($format[0] != "'") {
610
						$format = $this->quoted($format);
611
					}
612
					return "DATE_FORMAT($string,$format)";
613
				}
614
			case 'ifnull':
615
				if(empty($additional_parameters) && !strstr($all_strings, ",")) {
616
					$all_strings .= ",''";
617
				}
618
				return "IFNULL($all_strings)";
619
			case 'concat':
620
				return "CONCAT($all_strings)";
621
			case 'quarter':
622
					return "QUARTER($string)";
623
			case "length":
624
					return "LENGTH($string)";
625
			case 'month':
626
					return "MONTH($string)";
627
			case 'add_date':
628
					return "DATE_ADD($string, INTERVAL {$additional_parameters[0]} {$additional_parameters[1]})";
629
			case 'add_time':
630
					return "DATE_ADD($string, INTERVAL + CONCAT({$additional_parameters[0]}, ':', {$additional_parameters[1]}) HOUR_MINUTE)";
631
            case 'add_tz_offset' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
632
                $getUserUTCOffset = $GLOBALS['timedate']->getUserUTCOffset();
633
                $operation = $getUserUTCOffset < 0 ? '-' : '+';
634
                return $string . ' ' . $operation . ' INTERVAL ' . abs($getUserUTCOffset) . ' MINUTE';
635
            case 'avg':
636
                return "avg($string)";
637
		}
638
639
		return $string;
640
	}
641
642
	/**
643
	 * (non-PHPdoc)
644
	 * @see DBManager::fromConvert()
645
	 */
646
	public function fromConvert($string, $type)
647
	{
648
		return $string;
649
	}
650
651
	/**
652
	 * Returns the name of the engine to use or null if we are to use the default
653
	 *
654
	 * @param  object $bean SugarBean instance
655
	 * @return string
656
	 */
657
	protected function getEngine($bean)
658
	{
659
		global $dictionary;
660
		$engine = null;
661
		if (isset($dictionary[$bean->getObjectName()]['engine'])) {
662
			$engine = $dictionary[$bean->getObjectName()]['engine'];
663
		}
664
		return $engine;
665
	}
666
667
	/**
668
	 * Returns true if the engine given is enabled in the backend
669
	 *
670
	 * @param  string $engine
671
	 * @return bool
672
	 */
673
	protected function isEngineEnabled($engine)
674
	{
675
		if(!is_string($engine)) return false;
676
677
		$engine = strtoupper($engine);
678
679
		$r = $this->query("SHOW ENGINES");
680
681
		while ( $row = $this->fetchByAssoc($r) )
682
			if ( strtoupper($row['Engine']) == $engine )
683
				return ($row['Support']=='YES' || $row['Support']=='DEFAULT');
684
685
		return false;
686
	}
687
688
	/**
689
	 * @see DBManager::createTableSQL()
690
	 */
691
	public function createTableSQL(SugarBean $bean)
692
	{
693
		$tablename = $bean->getTableName();
694
		$fieldDefs = $bean->getFieldDefinitions();
695
		$indices   = $bean->getIndices();
696
		$engine    = $this->getEngine($bean);
697
		return $this->createTableSQLParams($tablename, $fieldDefs, $indices, $engine);
698
	}
699
700
	/**
701
	 * Generates sql for create table statement for a bean.
702
	 *
703
	 * @param  string $tablename
704
	 * @param  array  $fieldDefs
705
	 * @param  array  $indices
706
	 * @param  string $engine optional, MySQL engine to use
707
	 * @return string SQL Create Table statement
708
	*/
709
	public function createTableSQLParams($tablename, $fieldDefs, $indices, $engine = null)
710
	{
711
		if ( empty($engine) && isset($fieldDefs['engine']))
712
			$engine = $fieldDefs['engine'];
713
		if ( !$this->isEngineEnabled($engine) )
714
			$engine = '';
715
716
		$columns = $this->columnSQLRep($fieldDefs, false, $tablename);
717
		if (empty($columns))
718
			return false;
719
720
		$keys = $this->keysSQL($indices);
721
		if (!empty($keys))
722
			$keys = ",$keys";
723
724
		// cn: bug 9873 - module tables do not get created in utf8 with assoc collation
725
		$collation = $this->getOption('collation');
726
		if(empty($collation)) {
727
		    $collation = 'utf8_general_ci';
728
		}
729
		$sql = "CREATE TABLE $tablename ($columns $keys) CHARACTER SET utf8 COLLATE $collation";
730
731
		if (!empty($engine))
732
			$sql.= " ENGINE=$engine";
733
734
		return $sql;
735
	}
736
737
    /**
738
     * Does this type represent text (i.e., non-varchar) value?
739
     * @param string $type
740
     */
741
    public function isTextType($type)
742
    {
743
        $type = $this->getColumnType(strtolower($type));
744
        return in_array($type, array('blob','text','longblob', 'longtext'));
745
    }
746
747
	/**
748
	 * @see DBManager::oneColumnSQLRep()
749
	 */
750
	protected function oneColumnSQLRep($fieldDef, $ignoreRequired = false, $table = '', $return_as_array = false)
751
	{
752
		// always return as array for post-processing
753
		$ref = parent::oneColumnSQLRep($fieldDef, $ignoreRequired, $table, true);
754
755
		if ( $ref['colType'] == 'int' && !empty($fieldDef['len']) ) {
756
			$ref['colType'] .= "(".$fieldDef['len'].")";
757
		}
758
759
		// bug 22338 - don't set a default value on text or blob fields
760
		if ( isset($ref['default']) &&
761
            in_array($ref['colBaseType'], array('text', 'blob', 'longtext', 'longblob')))
762
			    $ref['default'] = '';
763
764
		if ( $return_as_array )
765
			return $ref;
766
		else
767
			return "{$ref['name']} {$ref['colType']} {$ref['default']} {$ref['required']} {$ref['auto_increment']}";
768
	}
769
770
	/**
771
	 * @see DBManager::changeColumnSQL()
772
	 */
773
	protected function changeColumnSQL($tablename, $fieldDefs, $action, $ignoreRequired = false)
774
	{
775
		$columns = array();
776
		if ($this->isFieldArray($fieldDefs)){
777
			foreach ($fieldDefs as $def){
778
				if ($action == 'drop')
779
					$columns[] = $def['name'];
780
				else
781
					$columns[] = $this->oneColumnSQLRep($def, $ignoreRequired);
782
			}
783
		} else {
784
			if ($action == 'drop')
785
				$columns[] = $fieldDefs['name'];
786
		else
787
			$columns[] = $this->oneColumnSQLRep($fieldDefs);
788
		}
789
790
		return "ALTER TABLE $tablename $action COLUMN ".implode(",$action column ", $columns);
791
	}
792
793
	/**
794
	 * Generates SQL for key specification inside CREATE TABLE statement
795
	 *
796
	 * The passes array is an array of field definitions or a field definition
797
	 * itself. The keys generated will be either primary, foreign, unique, index
798
	 * or none at all depending on the setting of the "key" parameter of a field definition
799
	 *
800
	 * @param  array  $indices
801
	 * @param  bool   $alter_table
802
	 * @param  string $alter_action
803
	 * @return string SQL Statement
804
	 */
805
	protected function keysSQL($indices, $alter_table = false, $alter_action = '')
806
	{
807
	// check if the passed value is an array of fields.
808
	// if not, convert it into an array
809
	if (!$this->isFieldArray($indices))
810
		$indices[] = $indices;
811
812
	$columns = array();
813
	foreach ($indices as $index) {
814
		if(!empty($index['db']) && $index['db'] != $this->dbType)
815
			continue;
816
		if (isset($index['source']) && $index['source'] != 'db')
817
			continue;
818
819
		$type = $index['type'];
820
		$name = $index['name'];
821
822
		if (is_array($index['fields']))
823
			$fields = implode(", ", $index['fields']);
824
		else
825
			$fields = $index['fields'];
826
827
		switch ($type) {
828
		case 'unique':
829
			$columns[] = " UNIQUE $name ($fields)";
830
			break;
831
		case 'primary':
832
			$columns[] = " PRIMARY KEY ($fields)";
833
			break;
834
		case 'index':
835
		case 'foreign':
836
		case 'clustered':
837
		case 'alternate_key':
838
			/**
839
				* @todo here it is assumed that the primary key of the foreign
840
				* table will always be named 'id'. It must be noted though
841
				* that this can easily be fixed by referring to db dictionary
842
				* to find the correct primary field name
843
				*/
844
			if ( $alter_table )
845
				$columns[] = " INDEX $name ($fields)";
846
			else
847
				$columns[] = " KEY $name ($fields)";
848
			break;
849
		case 'fulltext':
850
			if ($this->full_text_indexing_installed())
851
				$columns[] = " FULLTEXT ($fields)";
852
			else
853
				$GLOBALS['log']->debug('MYISAM engine is not available/enabled, full-text indexes will be skipped. Skipping:',$name);
854
			break;
855
		}
856
	}
857
	$columns = implode(", $alter_action ", $columns);
858
	if(!empty($alter_action)){
859
		$columns = $alter_action . ' '. $columns;
860
	}
861
	return $columns;
862
	}
863
864
	/**
865
	 * @see DBManager::setAutoIncrement()
866
	 */
867
	protected function setAutoIncrement($table, $field_name)
868
	{
869
		return "auto_increment";
870
	}
871
872
	/**
873
	 * Sets the next auto-increment value of a column to a specific value.
874
	 *
875
	 * @param  string $table tablename
876
	 * @param  string $field_name
877
	 */
878
	public function setAutoIncrementStart($table, $field_name, $start_value)
879
	{
880
		$start_value = (int)$start_value;
881
		return $this->query( "ALTER TABLE $table AUTO_INCREMENT = $start_value;");
882
	}
883
884
	/**
885
	 * Returns the next value for an auto increment
886
	 *
887
	 * @param  string $table tablename
888
	 * @param  string $field_name
889
	 * @return string
890
	 */
891
	public function getAutoIncrement($table, $field_name)
892
	{
893
		$result = $this->query("SHOW TABLE STATUS LIKE '$table'");
894
		$row = $this->fetchByAssoc($result);
895
		if (!empty($row['Auto_increment']))
896
			return $row['Auto_increment'];
897
898
		return "";
899
	}
900
901
	/**
902
	 * @see DBManager::get_indices()
903
	 */
904
	public function get_indices($tablename)
905
	{
906
		//find all unique indexes and primary keys.
907
		$result = $this->query("SHOW INDEX FROM $tablename");
908
909
		$indices = array();
910
		while (($row=$this->fetchByAssoc($result)) !=null) {
911
			$index_type='index';
912
			if ($row['Key_name'] =='PRIMARY') {
913
				$index_type='primary';
914
			}
915
			elseif ( $row['Non_unique'] == '0' ) {
916
				$index_type='unique';
917
			}
918
			$name = strtolower($row['Key_name']);
919
			$indices[$name]['name']=$name;
920
			$indices[$name]['type']=$index_type;
921
			$indices[$name]['fields'][]=strtolower($row['Column_name']);
922
		}
923
		return $indices;
924
	}
925
926
	/**
927
	 * @see DBManager::add_drop_constraint()
928
	 */
929
	public function add_drop_constraint($table, $definition, $drop = false)
930
	{
931
		$type         = $definition['type'];
932
		$fields       = implode(',',$definition['fields']);
933
		$name         = $definition['name'];
934
		$sql          = '';
935
936
		switch ($type){
937
		// generic indices
938
		case 'index':
939
		case 'alternate_key':
940
		case 'clustered':
941
			if ($drop)
942
				$sql = "ALTER TABLE {$table} DROP INDEX {$name} ";
943
			else
944
				$sql = "ALTER TABLE {$table} ADD INDEX {$name} ({$fields})";
945
			break;
946
		// constraints as indices
947
		case 'unique':
948
			if ($drop)
949
				$sql = "ALTER TABLE {$table} DROP INDEX $name";
950
			else
951
				$sql = "ALTER TABLE {$table} ADD CONSTRAINT UNIQUE {$name} ({$fields})";
952
			break;
953
		case 'primary':
954
			if ($drop)
955
				$sql = "ALTER TABLE {$table} DROP PRIMARY KEY";
956
			else
957
				$sql = "ALTER TABLE {$table} ADD CONSTRAINT PRIMARY KEY ({$fields})";
958
			break;
959
		case 'foreign':
960
			if ($drop)
961
				$sql = "ALTER TABLE {$table} DROP FOREIGN KEY ({$fields})";
962
			else
963
				$sql = "ALTER TABLE {$table} ADD CONSTRAINT FOREIGN KEY {$name} ({$fields}) REFERENCES {$definition['foreignTable']}({$definition['foreignField']})";
964
			break;
965
		}
966
		return $sql;
967
	}
968
969
	/**
970
	 * Runs a query and returns a single row
971
	 *
972
	 * @param  string   $sql        SQL Statement to execute
973
	 * @param  bool     $dieOnError True if we want to call die if the query returns errors
974
	 * @param  string   $msg        Message to log if error occurs
975
	 * @param  bool     $suppress   Message to log if error occurs
976
	 * @return array    single row from the query
977
	 */
978
	public function fetchOne($sql, $dieOnError = false, $msg = '', $suppress = false)
979
	{
980
		if(stripos($sql, ' LIMIT ') === false) {
981
			// little optimization to just fetch one row
982
			$sql .= " LIMIT 0,1";
983
		}
984
		return parent::fetchOne($sql, $dieOnError, $msg, $suppress);
985
	}
986
987
	/**
988
	 * @see DBManager::full_text_indexing_installed()
989
	 */
990
	public function full_text_indexing_installed($dbname = null)
991
	{
992
		return $this->isEngineEnabled('MyISAM');
993
	}
994
995
	/**
996
	 * @see DBManager::massageFieldDef()
997
	 */
998
	public function massageFieldDef(&$fieldDef, $tablename)
999
	{
1000
		parent::massageFieldDef($fieldDef,$tablename);
1001
1002
		if ( isset($fieldDef['default']) &&
1003
			($fieldDef['dbType'] == 'text'
1004
				|| $fieldDef['dbType'] == 'blob'
1005
				|| $fieldDef['dbType'] == 'longtext'
1006
				|| $fieldDef['dbType'] == 'longblob' ))
1007
			unset($fieldDef['default']);
1008
		if ($fieldDef['dbType'] == 'uint')
1009
			$fieldDef['len'] = '10';
1010
		if ($fieldDef['dbType'] == 'ulong')
1011
			$fieldDef['len'] = '20';
1012
		if ($fieldDef['dbType'] == 'bool')
1013
			$fieldDef['type'] = 'tinyint';
1014
		if ($fieldDef['dbType'] == 'bool' && empty($fieldDef['default']) )
1015
			$fieldDef['default'] = '0';
1016
		if (($fieldDef['dbType'] == 'varchar' || $fieldDef['dbType'] == 'enum') && empty($fieldDef['len']) )
1017
			$fieldDef['len'] = '255';
1018
		if ($fieldDef['dbType'] == 'uint')
1019
			$fieldDef['len'] = '10';
1020
		if ($fieldDef['dbType'] == 'int' && empty($fieldDef['len']) )
1021
			$fieldDef['len'] = '11';
1022
1023
		if($fieldDef['dbType'] == 'decimal') {
1024
			if(isset($fieldDef['len'])) {
1025
				if(strstr($fieldDef['len'], ",") === false) {
1026
					$fieldDef['len'] .= ",0";
1027
				}
1028
			} else {
1029
				$fieldDef['len']  = '10,0';
1030
			}
1031
		}
1032
	}
1033
1034
	/**
1035
	 * Generates SQL for dropping a table.
1036
	 *
1037
	 * @param  string $name table name
1038
	 * @return string SQL statement
1039
	 */
1040
	public function dropTableNameSQL($name)
1041
	{
1042
		return "DROP TABLE IF EXISTS ".$name;
1043
	}
1044
1045
	public function dropIndexes($tablename, $indexes, $execute = true)
1046
	{
1047
		$sql = array();
1048
		foreach ($indexes as $index) {
1049
			$name =$index['name'];
1050
			if($execute) {
1051
			unset(self::$index_descriptions[$tablename][$name]);
1052
			}
1053
			if ($index['type'] == 'primary') {
1054
				$sql[] = 'DROP PRIMARY KEY';
1055
			} else {
1056
				$sql[] = "DROP INDEX $name";
1057
			}
1058
		}
1059
		if (!empty($sql)) {
1060
            $sql = "ALTER TABLE $tablename " . join(",", $sql) . ";";
1061
			if($execute)
1062
				$this->query($sql);
1063
		} else {
1064
			$sql = '';
1065
		}
1066
		return $sql;
1067
	}
1068
1069
	/**
1070
	 * List of available collation settings
1071
	 * @return string
1072
	 */
1073
	public function getDefaultCollation()
1074
	{
1075
		return "utf8_general_ci";
1076
	}
1077
1078
	/**
1079
	 * List of available collation settings
1080
	 * @return array
1081
	 */
1082
	public function getCollationList()
1083
	{
1084
		$q = "SHOW COLLATION LIKE 'utf8%'";
1085
		$r = $this->query($q);
1086
		$res = array();
1087
		while($a = $this->fetchByAssoc($r)) {
1088
			$res[] = $a['Collation'];
1089
		}
1090
		return $res;
1091
	}
1092
1093
	/**
1094
	 * (non-PHPdoc)
1095
	 * @see DBManager::renameColumnSQL()
1096
	 */
1097
	public function renameColumnSQL($tablename, $column, $newname)
1098
	{
1099
		$field = $this->describeField($column, $tablename);
1100
		$field['name'] = $newname;
1101
		return "ALTER TABLE $tablename CHANGE COLUMN $column ".$this->oneColumnSQLRep($field);
1102
	}
1103
1104
	public function emptyValue($type)
1105
	{
1106
		$ctype = $this->getColumnType($type);
1107
		if($ctype == "datetime") {
1108
			return $this->convert($this->quoted("0000-00-00 00:00:00"), "datetime");
1109
		}
1110
		if($ctype == "date") {
1111
			return $this->convert($this->quoted("0000-00-00"), "date");
1112
		}
1113
		if($ctype == "time") {
1114
			return $this->convert($this->quoted("00:00:00"), "time");
1115
		}
1116
		return parent::emptyValue($type);
1117
	}
1118
1119
	/**
1120
	 * (non-PHPdoc)
1121
	 * @see DBManager::lastDbError()
1122
	 */
1123
	public function lastDbError()
1124
	{
1125
		if($this->database) {
1126
		    if(mysql_errno($this->database)) {
1127
			    return "MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database);
1128
		    }
1129
		} else {
1130
			$err =  mysql_error();
1131
			if($err) {
1132
			    return $err;
1133
			}
1134
		}
1135
        return false;
1136
    }
1137
1138
	/**
1139
	 * Quote MySQL search term
1140
	 * @param unknown_type $term
1141
	 */
1142
	protected function quoteTerm($term)
1143
	{
1144
		if(strpos($term, ' ') !== false) {
1145
			return '"'.$term.'"';
1146
		}
1147
		return $term;
1148
	}
1149
1150
	/**
1151
	 * Generate fulltext query from set of terms
1152
	 * @param string $fields Field to search against
0 ignored issues
show
Documentation introduced by
There is no parameter named $fields. Did you maybe mean $field?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1153
	 * @param array $terms Search terms that may be or not be in the result
1154
	 * @param array $must_terms Search terms that have to be in the result
1155
	 * @param array $exclude_terms Search terms that have to be not in the result
1156
	 */
1157
	public function getFulltextQuery($field, $terms, $must_terms = array(), $exclude_terms = array())
1158
	{
1159
		$condition = array();
1160
		foreach($terms as $term) {
1161
			$condition[] = $this->quoteTerm($term);
1162
		}
1163
		foreach($must_terms as $term) {
1164
			$condition[] = "+".$this->quoteTerm($term);
1165
		}
1166
		foreach($exclude_terms as $term) {
1167
			$condition[] = "-".$this->quoteTerm($term);
1168
		}
1169
		$condition = $this->quoted(join(" ",$condition));
1170
		return "MATCH($field) AGAINST($condition IN BOOLEAN MODE)";
1171
	}
1172
1173
	/**
1174
	 * Get list of all defined charsets
1175
	 * @return array
1176
	 */
1177
	protected function getCharsetInfo()
1178
	{
1179
		$charsets = array();
1180
		$res = $this->query("show variables like 'character\\_set\\_%'");
1181
		while($row = $this->fetchByAssoc($res)) {
1182
			$charsets[$row['Variable_name']] = $row['Value'];
1183
		}
1184
		return $charsets;
1185
	}
1186
1187
	public function getDbInfo()
1188
	{
1189
		$charsets = $this->getCharsetInfo();
1190
		$charset_str = array();
1191
		foreach($charsets as $name => $value) {
1192
			$charset_str[] = "$name = $value";
1193
		}
1194
		return array(
1195
			"MySQL Version" => @mysql_get_client_info(),
1196
			"MySQL Host Info" => @mysql_get_host_info($this->database),
1197
			"MySQL Server Info" => @mysql_get_server_info($this->database),
1198
			"MySQL Client Encoding" =>  @mysql_client_encoding($this->database),
1199
			"MySQL Character Set Settings" => join(", ", $charset_str),
1200
		);
1201
	}
1202
1203
	public function validateQuery($query)
1204
	{
1205
		$res = $this->query("EXPLAIN $query");
1206
		return !empty($res);
1207
	}
1208
1209
	protected function makeTempTableCopy($table)
1210
	{
1211
		$this->log->debug("creating temp table for [$table]...");
1212
		$result = $this->query("SHOW CREATE TABLE {$table}");
1213
		if(empty($result)) {
1214
			return false;
1215
		}
1216
		$row = $this->fetchByAssoc($result);
1217
		if(empty($row) || empty($row['Create Table'])) {
1218
		    return false;
1219
		}
1220
		$create = $row['Create Table'];
1221
		// rewrite DDL with _temp name
1222
		$tempTableQuery = str_replace("CREATE TABLE `{$table}`", "CREATE TABLE `{$table}__uw_temp`", $create);
1223
		$r2 = $this->query($tempTableQuery);
1224
		if(empty($r2)) {
1225
			return false;
1226
		}
1227
1228
		// get sample data into the temp table to test for data/constraint conflicts
1229
		$this->log->debug('inserting temp dataset...');
1230
		$q3 = "INSERT INTO `{$table}__uw_temp` SELECT * FROM `{$table}` LIMIT 10";
1231
		$this->query($q3, false, "Preflight Failed for: {$q3}");
1232
		return true;
1233
	}
1234
1235
	/**
1236
	 * Tests an ALTER TABLE query
1237
	 * @param string table The table name to get DDL
1238
	 * @param string query The query to test.
1239
	 * @return string Non-empty if error found
1240
	 */
1241
	protected function verifyAlterTable($table, $query)
1242
	{
1243
		$this->log->debug("verifying ALTER TABLE");
1244
		// Skipping ALTER TABLE [table] DROP PRIMARY KEY because primary keys are not being copied
1245
		// over to the temp tables
1246
		if(strpos(strtoupper($query), 'DROP PRIMARY KEY') !== false) {
1247
			$this->log->debug("Skipping DROP PRIMARY KEY");
1248
			return '';
1249
		}
1250
		if(!$this->makeTempTableCopy($table)) {
1251
			return 'Could not create temp table copy';
1252
		}
1253
1254
		// test the query on the test table
1255
		$this->log->debug('testing query: ['.$query.']');
1256
		$tempTableTestQuery = str_replace("ALTER TABLE `{$table}`", "ALTER TABLE `{$table}__uw_temp`", $query);
1257
		if (strpos($tempTableTestQuery, 'idx') === false) {
1258
			if(strpos($tempTableTestQuery, '__uw_temp') === false) {
1259
				return 'Could not use a temp table to test query!';
1260
			}
1261
1262
			$this->log->debug('testing query on temp table: ['.$tempTableTestQuery.']');
1263
			$this->query($tempTableTestQuery, false, "Preflight Failed for: {$query}");
1264
		} else {
1265
			// test insertion of an index on a table
1266
			$tempTableTestQuery_idx = str_replace("ADD INDEX `idx_", "ADD INDEX `temp_idx_", $tempTableTestQuery);
1267
			$this->log->debug('testing query on temp table: ['.$tempTableTestQuery_idx.']');
1268
			$this->query($tempTableTestQuery_idx, false, "Preflight Failed for: {$query}");
1269
		}
1270
		$mysqlError = $this->getL();
1271
		if(!empty($mysqlError)) {
1272
			return $mysqlError;
1273
		}
1274
		$this->dropTableName("{$table}__uw_temp");
1275
1276
		return '';
1277
	}
1278
1279
	protected function verifyGenericReplaceQuery($querytype, $table, $query)
1280
	{
1281
		$this->log->debug("verifying $querytype statement");
1282
1283
		if(!$this->makeTempTableCopy($table)) {
1284
			return 'Could not create temp table copy';
1285
		}
1286
		// test the query on the test table
1287
		$this->log->debug('testing query: ['.$query.']');
1288
		$tempTableTestQuery = str_replace("$querytype `{$table}`", "$querytype `{$table}__uw_temp`", $query);
1289
		if(strpos($tempTableTestQuery, '__uw_temp') === false) {
1290
			return 'Could not use a temp table to test query!';
1291
		}
1292
1293
		$this->query($tempTableTestQuery, false, "Preflight Failed for: {$query}");
1294
		$error = $this->lastError(); // empty on no-errors
1295
		$this->dropTableName("{$table}__uw_temp"); // just in case
1296
		return $error;
1297
	}
1298
1299
	/**
1300
	 * Tests a DROP TABLE query
1301
	 * @param string table The table name to get DDL
1302
	 * @param string query The query to test.
1303
	 * @return string Non-empty if error found
1304
	 */
1305
	public function verifyDropTable($table, $query)
1306
	{
1307
		return $this->verifyGenericReplaceQuery("DROP TABLE", $table, $query);
1308
	}
1309
1310
	/**
1311
	 * Tests an INSERT INTO query
1312
	 * @param string table The table name to get DDL
1313
	 * @param string query The query to test.
1314
	 * @return string Non-empty if error found
1315
	 */
1316
	public function verifyInsertInto($table, $query)
1317
	{
1318
		return $this->verifyGenericReplaceQuery("INSERT INTO", $table, $query);
1319
	}
1320
1321
	/**
1322
	 * Tests an UPDATE query
1323
	 * @param string table The table name to get DDL
1324
	 * @param string query The query to test.
1325
	 * @return string Non-empty if error found
1326
	 */
1327
	public function verifyUpdate($table, $query)
1328
	{
1329
		return $this->verifyGenericReplaceQuery("UPDATE", $table, $query);
1330
	}
1331
1332
	/**
1333
	 * Tests an DELETE FROM query
1334
	 * @param string table The table name to get DDL
1335
	 * @param string query The query to test.
1336
	 * @return string Non-empty if error found
1337
	 */
1338
	public function verifyDeleteFrom($table, $query)
1339
	{
1340
		return $this->verifyGenericReplaceQuery("DELETE FROM", $table, $query);
1341
	}
1342
1343
	/**
1344
	 * Check if certain database exists
1345
	 * @param string $dbname
1346
	 */
1347
	public function dbExists($dbname)
1348
	{
1349
		$db = $this->getOne("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ".$this->quoted($dbname));
1350
		return !empty($db);
1351
	}
1352
1353
	/**
1354
	 * Select database
1355
	 * @param string $dbname
1356
	 */
1357
	protected function selectDb($dbname)
1358
	{
1359
		return mysql_select_db($dbname);
1360
	}
1361
1362
	/**
1363
	 * Check if certain DB user exists
1364
	 * @param string $username
1365
	 */
1366
	public function userExists($username)
1367
	{
1368
		$db = $this->getOne("SELECT DATABASE()");
1369
		if(!$this->selectDb("mysql")) {
1370
			return false;
1371
		}
1372
		$user = $this->getOne("select count(*) from user where user = ".$this->quoted($username));
1373
		if(!$this->selectDb($db)) {
1374
			$this->checkError("Cannot select database $db", true);
1375
		}
1376
		return !empty($user);
1377
	}
1378
1379
	/**
1380
	 * Create DB user
1381
	 * @param string $database_name
1382
	 * @param string $host_name
1383
	 * @param string $user
1384
	 * @param string $password
1385
	 */
1386
	public function createDbUser($database_name, $host_name, $user, $password)
1387
	{
1388
		$qpassword = $this->quote($password);
1389
		$this->query("GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX
1390
							ON `$database_name`.*
1391
							TO \"$user\"@\"$host_name\"
1392
							IDENTIFIED BY '{$qpassword}';", true);
1393
1394
		$this->query("SET PASSWORD FOR \"{$user}\"@\"{$host_name}\" = password('{$qpassword}');", true);
1395
		if($host_name != 'localhost') {
1396
			$this->createDbUser($database_name, "localhost", $user, $password);
1397
		}
1398
	}
1399
1400
	/**
1401
	 * Create a database
1402
	 * @param string $dbname
1403
	 */
1404
	public function createDatabase($dbname)
1405
	{
1406
		$this->query("CREATE DATABASE `$dbname` CHARACTER SET utf8 COLLATE utf8_general_ci", true);
1407
	}
1408
1409
	public function preInstall()
1410
	{
1411
		$db->query("ALTER DATABASE `{$setup_db_database_name}` DEFAULT CHARACTER SET utf8", true);
1412
		$db->query("ALTER DATABASE `{$setup_db_database_name}` DEFAULT COLLATE utf8_general_ci", true);
1413
1414
	}
1415
1416
	/**
1417
	 * Drop a database
1418
	 * @param string $dbname
1419
	 */
1420
	public function dropDatabase($dbname)
1421
	{
1422
		return $this->query("DROP DATABASE IF EXISTS `$dbname`", true);
1423
	}
1424
1425
	/**
1426
	 * Check if this driver can be used
1427
	 * @return bool
1428
	 */
1429
	public function valid()
1430
	{
1431
		return function_exists("mysql_connect");
1432
	}
1433
1434
	/**
1435
	 * Check DB version
1436
	 * @see DBManager::canInstall()
1437
	 */
1438
	public function canInstall()
1439
	{
1440
		$db_version = $this->version();
1441
		if(empty($db_version)) {
1442
			return array('ERR_DB_VERSION_FAILURE');
1443
		}
1444
		if(version_compare($db_version, '4.1.2') < 0) {
1445
			return array('ERR_DB_MYSQL_VERSION', $db_version);
1446
		}
1447
		return true;
1448
	}
1449
1450
	public function installConfig()
1451
	{
1452
		return array(
1453
			'LBL_DBCONFIG_MSG3' =>  array(
1454
				"setup_db_database_name" => array("label" => 'LBL_DBCONF_DB_NAME', "required" => true),
1455
			),
1456
			'LBL_DBCONFIG_MSG2' =>  array(
1457
				"setup_db_host_name" => array("label" => 'LBL_DBCONF_HOST_NAME', "required" => true),
1458
			),
1459
			'LBL_DBCONF_TITLE_USER_INFO' => array(),
1460
			'LBL_DBCONFIG_B_MSG1' => array(
1461
				"setup_db_admin_user_name" => array("label" => 'LBL_DBCONF_DB_ADMIN_USER', "required" => true),
1462
				"setup_db_admin_password" => array("label" => 'LBL_DBCONF_DB_ADMIN_PASSWORD', "type" => "password"),
1463
			)
1464
		);
1465
	}
1466
1467
	/**
1468
	 * Disable keys on the table
1469
	 * @abstract
1470
	 * @param string $tableName
1471
	 */
1472
	public function disableKeys($tableName)
1473
	{
1474
	    return $this->query('ALTER TABLE '.$tableName.' DISABLE KEYS');
1475
	}
1476
1477
	/**
1478
	 * Re-enable keys on the table
1479
	 * @abstract
1480
	 * @param string $tableName
1481
	 */
1482
	public function enableKeys($tableName)
1483
	{
1484
	    return $this->query('ALTER TABLE '.$tableName.' ENABLE KEYS');
1485
	}
1486
1487
    /**
1488
     * Returns a DB specific FROM clause which can be used to select against functions.
1489
     * Note that depending on the database that this may also be an empty string.
1490
     * @return string
1491
     */
1492
    public function getFromDummyTable()
1493
    {
1494
        return '';
1495
    }
1496
1497
    /**
1498
     * Returns a DB specific piece of SQL which will generate GUID (UUID)
1499
     * This string can be used in dynamic SQL to do multiple inserts with a single query.
1500
     * I.e. generate a unique Sugar id in a sub select of an insert statement.
1501
     * @return string
1502
     */
1503
1504
	public function getGuidSQL()
1505
    {
1506
      	return 'UUID()';
1507
    }
1508
}
1509