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

DBManager   D

Complexity

Total Complexity 619

Size/Duplication

Total Lines 3797
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 3797
rs 4.4102
c 0
b 0
f 0
wmc 619
lcom 3
cbo 1

168 Methods

Rating   Name   Duplication   Size   Complexity  
A __get() 0 5 1
A getDatabase() 0 5 1
A getHelper() 0 4 1
A checkError() 0 15 3
B registerError() 0 21 7
A lastError() 0 4 1
B dump_slow_queries() 0 18 5
C checkQuery() 0 52 19
A getQueryTime() 0 4 1
A insert() 0 7 1
D insertParams() 0 44 17
A update() 0 7 1
A delete() 0 7 1
A retrieve() 0 7 1
A retrieveView() 0 6 1
A createTable() 0 14 4
A createConstraintSql() 0 4 1
B createTableParams() 0 20 8
B repairTable() 0 23 6
B isNullable() 0 16 13
F repairTableParams() 0 203 60
C compareVarDefs() 0 34 13
C compareFieldInTables() 0 37 8
A createIndex() 0 7 1
C getConstraintSql() 0 22 8
A addIndexes() 0 15 4
B dropIndexes() 0 19 6
A modifyIndexes() 0 5 1
A addColumn() 0 15 3
A alterColumn() 0 21 4
A dropTable() 0 4 1
A dropTableName() 0 5 1
A deleteColumn() 0 7 1
F generateInsertSQL() 0 145 42
A disconnectAll() 0 4 1
A setQueryLimit() 0 6 1
A getQueryCount() 0 4 1
A resetQueryCount() 0 4 1
B countQuery() 0 9 5
A quoteInternal() 0 4 1
A quoted() 0 4 1
B quoteType() 0 17 6
A arrayQuote() 0 7 2
A freeResult() 0 10 3
A hasLimit() 0 4 1
A getOne() 0 18 4
A fetchOne() 0 15 3
A getAffectedRowCount() 0 4 1
A getRowCount() 0 4 1
A getTableDescription() 0 7 3
A describeField() 0 13 4
B describeIndex() 0 14 5
A truncate() 0 8 3
A concat() 0 12 4
B prepareQuery() 0 30 5
C executePreparedQuery() 0 41 8
A pQuery() 0 5 1
A createTableSQL() 0 7 1
A insertSQL() 0 7 2
C updateSQL() 0 66 25
A updateWhereArray() 0 14 3
B getColumnWhereClause() 0 25 5
A getWhereClause() 0 4 1
C massageValue() 0 68 28
C massageFieldDef() 0 22 12
C getSelectFieldsFromQuery() 0 38 7
B getFieldNameFromSelect() 0 18 5
A deleteSQL() 0 5 1
A retrieveSQL() 0 5 1
C retrieveViewSQL() 0 70 13
A createIndexSQL() 0 16 4
B getFieldType() 0 20 6
B getTypeParts() 0 20 5
F oneColumnSQLRep() 0 81 30
B columnSQLRep() 0 18 5
A getAutoIncrement() 0 4 1
A getAutoIncrementSQL() 0 4 1
A setAutoIncrement() 0 5 1
A setAutoIncrementStart() 0 4 1
A deleteAutoIncrement() 0 4 1
A addColumnSQL() 0 4 1
A alterColumnSQL() 0 4 1
A dropTableSQL() 0 4 1
A dropTableNameSQL() 0 4 1
A truncateTableSQL() 0 4 1
A deleteColumnSQL() 0 4 1
A dropColumnSQL() 0 4 1
D getValidDBName() 0 38 9
A getColumnType() 0 4 2
A isFieldArray() 0 13 3
A validColumnType() 0 5 1
B auditSQL() 0 27 2
A save_audit_records() 0 4 1
D getDataChanges() 0 82 19
A getAuditDataChanges() 0 5 1
A full_text_indexing_setup() 0 4 1
A escape_quote() 0 4 1
A quoteFormEmail() 0 4 1
A renameIndexDefs() 0 5 1
A isBooleanType() 0 4 1
A _getBooleanValue() 0 8 3
A isNumericType() 0 7 4
C _emptyValue() 0 29 15
A isTextType() 0 4 1
A supports() 0 4 1
A orderByEnum() 0 14 3
B emptyValue() 0 8 5
A getDefaultCollation() 0 4 1
A getCollationList() 0 4 1
A number_of_columns() 0 5 1
A limitQuerySql() 0 4 1
A now() 0 4 1
C checkPrivilege() 0 44 11
A isSelect() 0 12 3
D parseFulltextQuery() 0 34 9
A extractTableName() 0 11 2
B verifySQLStatement() 0 20 5
B verifyCreateTable() 0 29 4
A queryArray() 0 10 3
B fetchByAssoc() 0 16 7
A getScriptName() 0 5 1
A setOptions() 0 5 1
A getOptions() 0 4 1
A getOption() 0 7 2
A commit() 0 5 1
A rollback() 0 5 1
A isDatabaseNameValid() 0 5 1
A canInstall() 0 4 1
A preInstall() 0 3 1
A postInstall() 0 3 1
A disableKeys() 0 3 1
A enableKeys() 0 3 1
quote() 0 1 ?
quoteIdentifier() 0 1 ?
convert() 0 1 ?
fromConvert() 0 1 ?
query() 0 1 ?
limitQuery() 0 1 ?
freeDbResult() 0 1 ?
renameColumnSQL() 0 1 ?
get_indices() 0 1 ?
get_columns() 0 1 ?
add_drop_constraint() 0 1 ?
getFieldsArray() 0 1 ?
getTablesArray() 0 1 ?
version() 0 1 ?
tableExists() 0 1 ?
fetchRow() 0 1 ?
connect() 0 1 ?
createTableSQLParams() 0 1 ?
changeColumnSQL() 0 1 ?
disconnect() 0 1 ?
lastDbError() 0 1 ?
validateQuery() 0 1 ?
valid() 0 1 ?
dbExists() 0 1 ?
tablesLike() 0 1 ?
createDatabase() 0 1 ?
dropDatabase() 0 1 ?
getDbInfo() 0 1 ?
userExists() 0 1 ?
createDbUser() 0 1 ?
full_text_indexing_installed() 0 1 ?
getFulltextQuery() 0 1 ?
installConfig() 0 1 ?
getFromDummyTable() 0 1 ?
getGuidSQL() 0 1 ?
A setDieOnError() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like DBManager 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 DBManager, 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 - 2016 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
 * Base database driver implementation
96
 * @api
97
 */
98
abstract class DBManager
99
{
100
	/**
101
	 * Name of database
102
	 * @var resource
103
	 */
104
	public $database = null;
105
106
	/**
107
	 * Indicates whether we should die when we get an error from the DB
108
	 */
109
	protected $dieOnError = false;
110
111
	/**
112
	 * Indicates whether we should html encode the results from a query by default
113
	 */
114
	protected $encode = true;
115
116
	/**
117
	 * Records the execution time of the last query
118
	 */
119
	protected $query_time = 0;
120
121
	/**
122
	 * Last error message from the DB backend
123
	 */
124
	protected $last_error = false;
125
126
	/**
127
	 * Registry of available result sets
128
	 */
129
	protected $lastResult;
130
131
	/**
132
	 * Current query count
133
	 */
134
	private static $queryCount = 0;
135
136
	/**
137
	 * Query threshold limit
138
	 */
139
	private static $queryLimit = 0;
140
141
	/**
142
	 * Array of prepared statements and their correspoding parsed tokens
143
	 */
144
	protected $preparedTokens = array();
145
146
	/**
147
	 * TimeDate instance
148
	 * @var TimeDate
149
	 */
150
	protected $timedate;
151
152
	/**
153
	 * PHP Logger
154
	 * @var Logger
155
	 */
156
	protected $log;
157
158
	/**
159
	 * Table descriptions
160
	 * @var array
161
	 */
162
	protected static $table_descriptions = array();
163
164
	/**
165
	 * Index descriptions
166
	 * @var array
167
	 */
168
	protected static $index_descriptions = array();
169
170
	/**
171
	 * Maximum length of identifiers
172
	 * @abstract
173
	 * @var array
174
	 */
175
	protected $maxNameLengths = array(
176
		'table' => 64,
177
		'column' => 64,
178
		'index' => 64,
179
		'alias' => 64
180
	);
181
182
	/**
183
	 * DB driver priority
184
	 * Higher priority drivers override lower priority ones
185
	 * @var int
186
	 */
187
	public $priority = 0;
188
189
	/**
190
	 * Driver name label, for install
191
	 * @absrtact
192
	 * @var string
193
	 */
194
	public $label = '';
195
196
	/**
197
	 * Type names map
198
	 * @abstract
199
	 * @var array
200
	 */
201
	protected $type_map = array();
202
203
	/**
204
	 * Type classification into:
205
	 * - int
206
	 * - bool
207
	 * - float
208
	 * - date
209
	 * @abstract
210
	 * @var array
211
	 */
212
	protected $type_class = array(
213
			'int'      => 'int',
214
			'double'   => 'float',
215
			'float'    => 'float',
216
			'uint'     => 'int',
217
			'ulong'    => 'bigint',
218
			'long'     => 'bigint',
219
			'short'    => 'int',
220
			'date'     => 'date',
221
			'datetime' => 'date',
222
			'datetimecombo' => 'date',
223
			'time'     => 'time',
224
			'bool'     => 'bool',
225
			'tinyint'  => 'int',
226
			'currency' => 'float',
227
			'decimal'  => 'float',
228
			'decimal2' => 'float',
229
	);
230
231
	/**
232
	 * Capabilities this DB supports. Supported list:
233
	 * affected_rows	Can report query affected rows for UPDATE/DELETE
234
	 * 					implement getAffectedRowCount()
235
	 * select_rows		Can report row count for SELECT
236
	 * 					implement getRowCount()
237
	 * case_sensitive	Supports case-sensitive text columns
238
	 * fulltext			Supports fulltext search indexes
239
	 * inline_keys		Supports defining keys together with the table
240
	 * auto_increment_sequence Autoincrement support implemented as sequence
241
	 * limit_subquery   Supports LIMIT clauses in subqueries
242
	 * create_user		Can create users for Sugar
243
	 * create_db		Can create databases
244
	 * collation		Supports setting collations
245
	 * disable_keys     Supports temporarily disabling keys (for upgrades, etc.)
246
	 *
247
	 * @abstract
248
	 * Special cases:
249
	 * fix:expandDatabase - needs expandDatabase fix, see expandDatabase.php
250
	 * TODO: verify if we need these cases
251
	 */
252
	protected $capabilities = array();
253
254
	/**
255
	 * Database options
256
	 * @var array
257
	 */
258
	protected $options = array();
259
260
    /**
261
     * Create DB Driver
262
     */
263
	public function __construct()
264
	{
265
		$this->timedate = TimeDate::getInstance();
266
		$this->log = isset($GLOBALS['log']) ? $GLOBALS['log'] : null;
267
		$this->helper = $this; // compatibility
268
	}
269
270
    /**
271
     * Wrapper for those trying to access the private and protected class members directly
272
     * @param string $p var name
273
     * @return mixed
274
     */
275
	public function __get($p)
276
	{
277
		$this->log->info('Call to DBManager::$'.$p.' is deprecated');
278
		return $this->$p;
279
	}
280
281
	/**
282
	 * Returns the current database handle
283
	 * @return resource
284
	 */
285
	public function getDatabase()
286
	{
287
		$this->checkConnection();
288
		return $this->database;
289
	}
290
291
	/**
292
	 * Returns this instance's DBHelper
293
	 * Actually now returns $this
294
	 * @deprecated
295
	 * @return DBManager
296
	 */
297
	public function getHelper()
298
	{
299
		return $this;
300
	}
301
302
	/**
303
	 * Checks for error happening in the database
304
	 *
305
	 * @param  string $msg        message to prepend to the error message
306
	 * @param  bool   $dieOnError true if we want to die immediately on error
307
	 * @return bool True if there was an error
308
	 */
309
	public function checkError($msg = '', $dieOnError = false)
310
	{
311
		if (empty($this->database)) {
312
			$this->registerError($msg, "Database Is Not Connected", $dieOnError);
313
			return true;
314
		}
315
316
		$dberror = $this->lastDbError();
317
		if($dberror === false) {
318
    		$this->last_error = false;
319
	    	return false;
320
		}
321
		$this->registerError($msg, $dberror, $dieOnError);
322
        return true;
323
	}
324
325
	/**
326
	 * Register database error
327
	 * If die-on-error flag is set, logs the message and dies,
328
	 * otherwise sets last_error to the message
329
	 * @param string $userMessage Message from function user
330
	 * @param string $message Message from SQL driver
331
	 * @param bool $dieOnError
332
	 */
333
	protected function registerError($userMessage, $message, $dieOnError = false)
334
	{
335
		if(!empty($message)) {
336
			if(!empty($userMessage)) {
337
				$message = "$userMessage: $message";
338
			}
339
			if(empty($message)) {
340
			    $message = "Database error";
341
			}
342
			$this->log->fatal($message);
343
			if ($dieOnError || $this->dieOnError) {
344
				if(isset($GLOBALS['app_strings']['ERR_DB_FAIL'])) {
345
					sugar_die($GLOBALS['app_strings']['ERR_DB_FAIL']);
346
				} else {
347
					sugar_die("Database error. Please check suitecrm.log for details.");
348
				}
349
			} else {
350
				$this->last_error = $message;
351
			}
352
		}
353
	}
354
355
	/**
356
	 * Return DB error message for the last query executed
357
	 * @return string Last error message
358
	 */
359
	public function lastError()
360
	{
361
		return $this->last_error;
362
	}
363
364
	/**
365
	 * This method is called by every method that runs a query.
366
	 * If slow query dumping is turned on and the query time is beyond
367
	 * the time limit, we will log the query. This function may do
368
	 * additional reporting or log in a different area in the future.
369
	 *
370
	 * @param  string  $query query to log
371
	 * @return boolean true if the query was logged, false otherwise
372
	 */
373
	protected function dump_slow_queries($query)
374
	{
375
		global $sugar_config;
376
377
		$do_the_dump = isset($sugar_config['dump_slow_queries'])
378
			? $sugar_config['dump_slow_queries'] : false;
379
		$slow_query_time_msec = isset($sugar_config['slow_query_time_msec'])
380
			? $sugar_config['slow_query_time_msec'] : 5000;
381
382
		if($do_the_dump) {
383
			if($slow_query_time_msec < ($this->query_time * 1000)) {
384
				// Then log both the query and the query time
385
				$this->log->fatal('Slow Query (time:'.$this->query_time."\n".$query);
386
				return true;
387
			}
388
		}
389
		return false;
390
	}
391
392
/**
393
	* Scans order by to ensure that any field being ordered by is.
394
	*
395
	* It will throw a warning error to the log file - fatal if slow query logging is enabled
396
	*
397
	* @param  string $sql         query to be run
398
	* @param  bool   $object_name optional, object to look up indices in
399
	* @return bool   true if an index is found false otherwise
400
	*/
401
protected function checkQuery($sql, $object_name = false)
402
{
403
	$match = array();
404
	preg_match_all("'.* FROM ([^ ]*).* ORDER BY (.*)'is", $sql, $match);
405
	$indices = false;
406
	if (!empty($match[1][0]))
407
		$table = $match[1][0];
408
	else
409
		return false;
410
411
	if (!empty($object_name) && !empty($GLOBALS['dictionary'][$object_name]))
412
		$indices = $GLOBALS['dictionary'][$object_name]['indices'];
413
414
	if (empty($indices)) {
415
		foreach ( $GLOBALS['dictionary'] as $current ) {
416
			if ($current['table'] == $table){
417
				$indices = $current['indices'];
418
				break;
419
			}
420
		}
421
	}
422
	if (empty($indices)) {
423
		$this->log->warn('CHECK QUERY: Could not find index definitions for table ' . $table);
424
		return false;
425
	}
426
	if (!empty($match[2][0])) {
427
		$orderBys = explode(' ', $match[2][0]);
428
		foreach ($orderBys as $orderBy){
429
			$orderBy = trim($orderBy);
430
			if (empty($orderBy))
431
				continue;
432
			$orderBy = strtolower($orderBy);
433
			if ($orderBy == 'asc' || $orderBy == 'desc')
434
				continue;
435
436
			$orderBy = str_replace(array($table . '.', ','), '', $orderBy);
437
438
			foreach ($indices as $index)
439
				if (empty($index['db']) || $index['db'] == $this->dbType)
440
					foreach ($index['fields'] as $field)
441
						if ($field == $orderBy)
442
							return true;
443
444
			$warning = 'Missing Index For Order By Table: ' . $table . ' Order By:' . $orderBy ;
445
			if (!empty($GLOBALS['sugar_config']['dump_slow_queries']))
446
				$this->log->fatal('CHECK QUERY:' .$warning);
447
			else
448
				$this->log->warn('CHECK QUERY:' .$warning);
449
		}
450
	}
451
	return false;
452
	}
453
454
	/**
455
	 * Returns the time the last query took to execute
456
	 *
457
	 * @return int
458
	 */
459
	public function getQueryTime()
460
	{
461
		return $this->query_time;
462
	}
463
464
	/**
465
	 * Checks the current connection; if it is not connected then reconnect
466
	 */
467
	public function checkConnection()
468
	{
469
		$this->last_error = '';
0 ignored issues
show
Documentation Bug introduced by
The property $last_error was declared of type boolean, but '' 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...
470
		if (!isset($this->database))
471
			$this->connect();
472
	}
473
474
	/**
475
	 * Sets the dieOnError value
476
	 *
477
	 * @param bool $value
478
	 */
479
	public function setDieOnError($value)
480
	{
481
		$this->dieOnError = $value;
482
	}
483
484
    /**
485
     * Implements a generic insert for any bean.
486
     *
487
     * @param SugarBean $bean SugarBean instance
488
     * @return bool query result
489
     *
490
     */
491
	public function insert(SugarBean $bean)
492
	{
493
		$sql = $this->insertSQL($bean);
494
		$tablename =  $bean->getTableName();
495
		$msg = "Error inserting into table: $tablename:";
496
		return $this->query($sql,true,$msg);
497
	}
498
499
	/**
500
	 * Insert data into table by parameter definition
501
	 * @param string $table Table name
502
	 * @param array $field_defs Definitions in vardef-like format
503
	 * @param array $data Key/value to insert
504
	 * @param array $field_map Fields map from SugarBean
505
	 * @param bool $execute Execute or return query?
506
     * @return bool query result
507
     */
508
	public function insertParams($table, $field_defs, $data, $field_map = null, $execute = true)
509
	{
510
		$values = array();
511
		foreach ($field_defs as $field => $fieldDef)
512
		{
513
			if (isset($fieldDef['source']) && $fieldDef['source'] != 'db')  continue;
514
			//custom fields handle there save seperatley
515
			if(!empty($field_map) && !empty($field_map[$field]['custom_type'])) continue;
516
517
			if(isset($data[$field])) {
518
				// clean the incoming value..
519
				$val = from_html($data[$field]);
520
			} else {
521
				if(isset($fieldDef['default']) && strlen($fieldDef['default']) > 0) {
522
					$val = $fieldDef['default'];
523
				} else {
524
					$val = null;
525
				}
526
			}
527
528
			//handle auto increment values here - we may have to do something like nextval for oracle
529
			if (!empty($fieldDef['auto_increment'])) {
530
				$auto = $this->getAutoIncrementSQL($table, $fieldDef['name']);
531
				if(!empty($auto)) {
532
					$values[$field] = $auto;
533
				}
534
			} elseif ($fieldDef['name'] == 'deleted') {
535
				$values['deleted'] = (int)$val;
536
			} else {
537
				// need to do some thing about types of values
538
				if(!is_null($val) || !empty($fieldDef['required'])) {
539
					$values[$field] = $this->massageValue($val, $fieldDef);
540
				}
541
			}
542
		}
543
544
		if (empty($values))
545
			return $execute?true:''; // no columns set
546
547
		// get the entire sql
548
		$query = "INSERT INTO $table (".implode(",", array_keys($values)).")
549
					VALUES (".implode(",", $values).")";
550
		return $execute?$this->query($query):$query;
551
	}
552
553
    /**
554
     * Implements a generic update for any bean
555
     *
556
     * @param SugarBean $bean Sugarbean instance
557
     * @param array $where values with the keys as names of fields.
558
     * If we want to pass multiple values for a name, pass it as an array
559
     * If where is not passed, it defaults to id of table
560
     * @return bool query result
561
     *
562
     */
563
	public function update(SugarBean $bean, array $where = array())
564
	{
565
		$sql = $this->updateSQL($bean, $where);
566
		$tablename = $bean->getTableName();
567
		$msg = "Error updating table: $tablename:";
568
		return $this->query($sql,true,$msg);
569
	}
570
571
    /**
572
     * Implements a generic delete for any bean identified by id
573
     *
574
     * @param SugarBean $bean Sugarbean instance
575
     * @param array  $where values with the keys as names of fields.
576
     * If we want to pass multiple values for a name, pass it as an array
577
     * If where is not passed, it defaults to id of table
578
     * @return bool query result
579
     */
580
	public function delete(SugarBean $bean, array $where = array())
581
	{
582
		$sql = $this->deleteSQL($bean, $where);
583
		$tableName = $bean->getTableName();
584
		$msg = "Error deleting from table: ".$tableName. ":";
585
		return $this->query($sql,true,$msg);
586
	}
587
588
	/**
589
	 * Implements a generic retrieve for any bean identified by id
590
	 *
591
	 * If we want to pass multiple values for a name, pass it as an array
592
	 * If where is not passed, it defaults to id of table
593
	 *
594
	 * @param  SugarBean   $bean  Sugarbean instance
595
	 * @param  array    $where values with the keys as names of fields.
596
	 * @return resource result from the query
597
	 */
598
	public function retrieve(SugarBean $bean, array $where = array())
599
	{
600
		$sql = $this->retrieveSQL($bean, $where);
601
		$tableName = $bean->getTableName();
602
		$msg = "Error retriving values from table:".$tableName. ":";
603
		return $this->query($sql,true,$msg);
604
	}
605
606
	/**
607
	 * Implements a generic retrieve for a collection of beans.
608
	 *
609
	 * These beans will be joined in the sql by the key attribute of field defs.
610
	 * Currently, this function does support outer joins.
611
	 *
612
	 * @param  array $beans Sugarbean instance(s)
613
	 * @param  array $cols  columns to be returned with the keys as names of bean as identified by
614
	 * get_class of bean. Values of this array is the array of fieldDefs to be returned for a bean.
615
	 * If an empty array is passed, all columns are selected.
616
	 * @param  array $where  values with the keys as names of bean as identified by get_class of bean
617
	 * Each value at the first level is an array of values for that bean identified by name of fields.
618
	 * If we want to pass multiple values for a name, pass it as an array
619
	 * If where is not passed, all the rows will be returned.
620
	 * @return resource
621
	 */
622
	public function retrieveView(array $beans, array $cols = array(), array $where = array())
623
	{
624
		$sql = $this->retrieveViewSQL($beans, $cols, $where);
625
		$msg = "Error retriving values from View Collection:";
626
		return $this->query($sql,true,$msg);
627
	}
628
629
630
	/**
631
	 * Implements creation of a db table for a bean.
632
	 *
633
	 * NOTE: does not handle out-of-table constraints, use createConstraintSQL for that
634
	 * @param SugarBean $bean  Sugarbean instance
635
	 */
636
	public function createTable(SugarBean $bean)
637
	{
638
		$sql = $this->createTableSQL($bean);
639
		$tablename = $bean->getTableName();
640
		$msg = "Error creating table: $tablename:";
641
		$this->query($sql,true,$msg);
642
		if(!$this->supports("inline_keys")) {
643
		// handle constraints and indices
644
			$indicesArr = $this->createConstraintSql($bean);
645
			if (count($indicesArr) > 0)
646
				foreach ($indicesArr as $indexSql)
647
					$this->query($indexSql, true, $msg);
648
		}
649
	}
650
651
	/**
652
	 * returns SQL to create constraints or indices
653
	 *
654
	 * @param  SugarBean $bean SugarBean instance
655
	 * @return array list of SQL statements
656
	 */
657
	protected function createConstraintSql(SugarBean $bean)
658
	{
659
		return $this->getConstraintSql($bean->getIndices(), $bean->getTableName());
660
	}
661
662
	/**
663
	 * Implements creation of a db table
664
	 *
665
	 * @param string $tablename
666
	 * @param array  $fieldDefs  Field definitions, in vardef format
667
	 * @param array  $indices    Index definitions, in vardef format
668
	 * @param string $engine    Engine parameter, used for MySQL engine so far
669
     * @todo: refactor engine param to be more generic
670
     * @return bool success value
671
     */
672
	public function createTableParams($tablename, $fieldDefs, $indices, $engine = null)
673
	{
674
		if (!empty($fieldDefs)) {
675
			$sql = $this->createTableSQLParams($tablename, $fieldDefs, $indices, $engine);
676
			$res = true;
677
			if ($sql) {
678
				$msg = "Error creating table: $tablename";
679
				$res = ($res and $this->query($sql,true,$msg));
680
			}
681
			if(!$this->supports("inline_keys")) {
682
				// handle constraints and indices
683
				$indicesArr = $this->getConstraintSql($indices, $tablename);
684
				if (count($indicesArr) > 0)
685
					foreach ($indicesArr as $indexSql)
686
						$res = ($res and $this->query($indexSql, true, "Error creating indexes"));
687
			}
688
			return $res;
689
		}
690
		return false;
691
	}
692
693
	/**
694
	 * Implements repair of a db table for a bean.
695
	 *
696
	 * @param  SugarBean $bean    SugarBean instance
697
	 * @param  bool   $execute true if we want the action to take place, false if we just want the sql returned
698
	 * @return string SQL statement or empty string, depending upon $execute
699
	 */
700
	public function repairTable(SugarBean $bean, $execute = true)
701
	{
702
		$indices   = $bean->getIndices();
703
		$fielddefs = $bean->getFieldDefinitions();
704
		$tablename = $bean->getTableName();
705
706
		//Clean the indexes to prevent duplicate definitions
707
		$new_index = array();
708
		foreach($indices as $ind_def){
709
			$new_index[$ind_def['name']] = $ind_def;
710
		}
711
		//jc: added this for beans that do not actually have a table, namely
712
		//ForecastOpportunities
713
		if($tablename == 'does_not_exist' || $tablename == '')
714
			return '';
715
716
		global $dictionary;
717
		$engine=null;
718
		if (isset($dictionary[$bean->getObjectName()]['engine']) && !empty($dictionary[$bean->getObjectName()]['engine']) )
719
			$engine = $dictionary[$bean->getObjectName()]['engine'];
720
721
		return $this->repairTableParams($tablename, $fielddefs,$new_index,$execute,$engine);
722
	}
723
724
	/**
725
	 * Can this field be null?
726
	 * Auto-increment and ID fields can not be null
727
	 * @param array $vardef
728
     * @return bool
729
     */
730
	protected function isNullable($vardef)
731
	{
732
733
		if(isset($vardef['isnull']) && (strtolower($vardef['isnull']) == 'false' || $vardef['isnull'] === false)
734
			&& !empty($vardef['required'])) {
735
				/* required + is_null=false => not null */
736
			return false;
737
		}
738
		if(empty($vardef['auto_increment']) && (empty($vardef['type']) || $vardef['type'] != 'id')
739
					&& (empty($vardef['dbType']) || $vardef['dbType'] != 'id')
740
					&& (empty($vardef['name']) || ($vardef['name'] != 'id' && $vardef['name'] != 'deleted'))
741
		) {
742
			return true;
743
		}
744
		return false;
745
	}
746
747
748
	/**
749
	 * Builds the SQL commands that repair a table structure
750
	 *
751
	 * @param  string $tablename
752
	 * @param  array  $fielddefs Field definitions, in vardef format
753
	 * @param  array  $indices   Index definitions, in vardef format
754
	 * @param  bool   $execute   optional, true if we want the queries executed instead of returned
755
	 * @param  string $engine    optional, MySQL engine
756
     * @todo: refactor engine param to be more generic
757
     * @return string
758
     */
759
	public function repairTableParams($tablename, $fielddefs,  $indices, $execute = true, $engine = null)
760
	{
761
		//jc: had a bug when running the repair if the tablename is blank the repair will
762
		//fail when it tries to create a repair table
763
		if ($tablename == '' || empty($fielddefs))
764
			return '';
765
766
		//if the table does not exist create it and we are done
767
		$sql = "/* Table : $tablename */\n";
768
		if (!$this->tableExists($tablename)) {
769
			$createtablesql = $this->createTableSQLParams($tablename,$fielddefs,$indices,$engine);
770
			if($execute && $createtablesql){
771
				$this->createTableParams($tablename,$fielddefs,$indices,$engine);
772
			}
773
774
			$sql .= "/* MISSING TABLE: {$tablename} */\n";
775
			$sql .= $createtablesql . "\n";
776
			return $sql;
777
		}
778
779
		$compareFieldDefs = $this->get_columns($tablename);
780
		$compareIndices = $this->get_indices($tablename);
781
782
		$take_action = false;
783
784
		// do column comparisons
785
		$sql .=	"/*COLUMNS*/\n";
786
		foreach ($fielddefs as $name => $value) {
787
			if (isset($value['source']) && $value['source'] != 'db')
788
				continue;
789
790
            // Bug #42406. Skipping breaked vardef without type or name
791
            if (isset($value['name']) == false || $value['name'] == false)
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...
792
            {
793
                $sql .= "/* NAME IS MISSING IN VARDEF $tablename::$name */\n";
794
                continue;
795
            }
796
            else if (isset($value['type']) == false || $value['type'] == false)
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...
797
            {
798
                $sql .= "/* TYPE IS MISSING IN VARDEF $tablename::$name */\n";
799
                continue;
800
            }
801
802
			$name = strtolower($value['name']);
803
			// add or fix the field defs per what the DB is expected to give us back
804
			$this->massageFieldDef($value,$tablename);
805
806
			$ignorerequired=false;
807
808
			//Do not track requiredness in the DB, auto_increment, ID,
809
			// and deleted fields are always required in the DB, so don't force those
810
			if ($this->isNullable($value)) {
811
				$value['required'] = false;
812
			}
813
			//Should match the conditions in DBManager::oneColumnSQLRep for DB required fields, type='id' fields will sometimes
814
815
			//come into this function as 'type' = 'char', 'dbType' = 'id' without required set in $value. Assume they are correct and leave them alone.
816
			else if (($name == 'id' || $value['type'] == 'id' || (isset($value['dbType']) && $value['dbType'] == 'id'))
817
				&& (!isset($value['required']) && isset($compareFieldDefs[$name]['required'])))
818
			{
819
				$value['required'] = $compareFieldDefs[$name]['required'];
820
			}
821
822
			if ( !isset($compareFieldDefs[$name]) ) {
823
				// ok we need this field lets create it
824
				$sql .=	"/*MISSING IN DATABASE - $name -  ROW*/\n";
825
				$sql .= $this->addColumnSQL($tablename, $value) .  "\n";
826
				if ($execute)
827
					$this->addColumn($tablename, $value);
828
				$take_action = true;
829
			} elseif ( !$this->compareVarDefs($compareFieldDefs[$name],$value)) {
830
				//fields are different lets alter it
831
				$sql .=	"/*MISMATCH WITH DATABASE - $name -  ROW ";
832
				foreach($compareFieldDefs[$name] as $rKey => $rValue) {
833
					$sql .=	"[$rKey] => '$rValue'  ";
834
				}
835
				$sql .=	"*/\n";
836
				$sql .=	"/* VARDEF - $name -  ROW";
837
				foreach($value as $rKey => $rValue) {
838
					$sql .=	"[$rKey] => '$rValue'  ";
839
				}
840
				$sql .=	"*/\n";
841
842
				//jc: oracle will complain if you try to execute a statement that sets a column to (not) null
843
				//when it is already (not) null
844
				if ( isset($value['isnull']) && isset($compareFieldDefs[$name]['isnull']) &&
845
					$value['isnull'] === $compareFieldDefs[$name]['isnull']) {
846
					unset($value['required']);
847
					$ignorerequired=true;
848
				}
849
850
				//dwheeler: Once a column has been defined as null, we cannot try to force it back to !null
851
				if ((isset($value['required']) && ($value['required'] === true || $value['required'] == 'true' || $value['required'] === 1))
852
					&& (empty($compareFieldDefs[$name]['required']) || $compareFieldDefs[$name]['required'] != 'true'))
853
				{
854
					$ignorerequired = true;
855
				}
856
				$altersql = $this->alterColumnSQL($tablename, $value,$ignorerequired);
857
				if(is_array($altersql)) {
858
					$altersql = join("\n", $altersql);
859
				}
860
				$sql .= $altersql .  "\n";
861
				if($execute){
862
					$this->alterColumn($tablename, $value, $ignorerequired);
863
				}
864
				$take_action = true;
865
			}
866
		}
867
868
		// do index comparisons
869
		$sql .=	"/* INDEXES */\n";
870
		$correctedIndexs = array();
871
872
        $compareIndices_case_insensitive = array();
873
874
		// do indices comparisons case-insensitive
875
		foreach($compareIndices as $k => $value){
876
			$value['name'] = strtolower($value['name']);
877
			$compareIndices_case_insensitive[strtolower($k)] = $value;
878
		}
879
		$compareIndices = $compareIndices_case_insensitive;
880
		unset($compareIndices_case_insensitive);
881
882
		foreach ($indices as $value) {
883
			if (isset($value['source']) && $value['source'] != 'db')
884
				continue;
885
886
887
			$validDBName = $this->getValidDBName($value['name'], true, 'index', true);
888
			if (isset($compareIndices[$validDBName])) {
889
				$value['name'] = $validDBName;
890
			}
891
		    $name = strtolower($value['name']);
892
893
			//Don't attempt to fix the same index twice in one pass;
894
			if (isset($correctedIndexs[$name]))
895
				continue;
896
897
			//don't bother checking primary nothing we can do about them
898
			if (isset($value['type']) && $value['type'] == 'primary')
899
				continue;
900
901
			//database helpers do not know how to handle full text indices
902
			if ($value['type']=='fulltext')
903
				continue;
904
905
			if ( in_array($value['type'],array('alternate_key','foreign')) )
906
				$value['type'] = 'index';
907
908
			if ( !isset($compareIndices[$name]) ) {
909
				//First check if an index exists that doesn't match our name, if so, try to rename it
910
				$found = false;
911
				foreach ($compareIndices as $ex_name => $ex_value) {
912
					if($this->compareVarDefs($ex_value, $value, true)) {
913
						$found = $ex_name;
914
						break;
915
					}
916
				}
917
				if ($found) {
918
					$sql .=	 "/*MISSNAMED INDEX IN DATABASE - $name - $ex_name */\n";
0 ignored issues
show
Bug introduced by
The variable $ex_name seems to be defined by a foreach iteration on line 911. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
919
					$rename = $this->renameIndexDefs($ex_value, $value, $tablename);
0 ignored issues
show
Bug introduced by
The variable $ex_value seems to be defined by a foreach iteration on line 911. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
920
					if($execute) {
921
						$this->query($rename, true, "Cannot rename index");
922
					}
923
					$sql .= is_array($rename)?join("\n", $rename). "\n":$rename."\n";
924
925
				} else {
926
					// ok we need this field lets create it
927
					$sql .=	 "/*MISSING INDEX IN DATABASE - $name -{$value['type']}  ROW */\n";
928
					$sql .= $this->addIndexes($tablename,array($value), $execute) .  "\n";
929
				}
930
				$take_action = true;
931
				$correctedIndexs[$name] = true;
932
			} elseif ( !$this->compareVarDefs($compareIndices[$name],$value) ) {
933
				// fields are different lets alter it
934
				$sql .=	"/*INDEX MISMATCH WITH DATABASE - $name -  ROW ";
935
				foreach ($compareIndices[$name] as $n1 => $t1) {
936
					$sql .=	 "<$n1>";
937
					if ( $n1 == 'fields' )
938
						foreach($t1 as $rKey => $rValue)
939
							$sql .= "[$rKey] => '$rValue'  ";
940
					else
941
						$sql .= " $t1 ";
942
				}
943
				$sql .=	"*/\n";
944
				$sql .=	"/* VARDEF - $name -  ROW";
945
				foreach ($value as $n1 => $t1) {
946
					$sql .=	"<$n1>";
947
					if ( $n1 == 'fields' )
948
						foreach ($t1 as $rKey => $rValue)
949
							$sql .=	"[$rKey] => '$rValue'  ";
950
					else
951
						$sql .= " $t1 ";
952
				}
953
				$sql .=	"*/\n";
954
				$sql .= $this->modifyIndexes($tablename,array($value), $execute) .  "\n";
955
				$take_action = true;
956
				$correctedIndexs[$name] = true;
957
			}
958
		}
959
960
		return ($take_action === true) ? $sql : '';
961
	}
962
963
    /**
964
     * Compares two vardefs
965
     *
966
     * @param  array  $fielddef1 This is from the database
967
     * @param  array  $fielddef2 This is from the vardef
968
     * @param bool $ignoreName Ignore name-only differences?
969
     * @return bool   true if they match, false if they don't
970
     */
971
	public function compareVarDefs($fielddef1, $fielddef2, $ignoreName = false)
972
	{
973
		foreach ( $fielddef1 as $key => $value ) {
974
			if ($key == 'name' && $ignoreName)
975
				continue;
976
            if (isset($fielddef2[$key]))
977
            {
978
                if (!is_array($fielddef1[$key]) && !is_array($fielddef2[$key]))
979
                {
980
                    if (strtolower($fielddef1[$key]) == strtolower($fielddef2[$key]))
981
                    {
982
                        continue;
983
                    }
984
                }
985
                else
986
                {
987
                    if (array_map('strtolower', $fielddef1[$key]) == array_map('strtolower',$fielddef2[$key]))
988
                    {
989
                        continue;
990
                    }
991
                }
992
            }
993
			//Ignore len if its not set in the vardef
994
			if ($key == 'len' && empty($fielddef2[$key]))
995
				continue;
996
            // if the length in db is greather than the vardef, ignore it
997
            if ($key == 'len' && ($fielddef1[$key] >= $fielddef2[$key])) {
998
                continue;
999
            }
1000
			return false;
1001
		}
1002
1003
		return true;
1004
	}
1005
1006
	/**
1007
	 * Compare a field in two tables
1008
	 * @deprecated
1009
	 * @param  string $name   field name
1010
	 * @param  string $table1
1011
	 * @param  string $table2
1012
	 * @return array  array with keys 'msg','table1','table2'
1013
	 */
1014
	public function compareFieldInTables($name, $table1, $table2)
1015
	{
1016
		$row1 = $this->describeField($name, $table1);
1017
		$row2 = $this->describeField($name, $table2);
1018
		$returnArray = array(
1019
			'table1' => $row1,
1020
			'table2' => $row2,
1021
			'msg'    => 'error',
1022
			);
1023
1024
		$ignore_filter = array('Key'=>1);
1025
		if ($row1) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row1 of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1026
			if (!$row2) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row2 of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1027
				// Exists on table1 but not table2
1028
				$returnArray['msg'] = 'not_exists_table2';
1029
			}
1030
			else {
1031
				if (sizeof($row1) != sizeof($row2)) {
1032
					$returnArray['msg'] = 'no_match';
1033
				}
1034
				else {
1035
					$returnArray['msg'] = 'match';
1036
					foreach($row1 as $key => $value){
1037
						//ignore keys when checking we will check them when we do the index check
1038
						if( !isset($ignore_filter[$key]) && (!isset($row2[$key]) || $row1[$key] !== $row2[$key])){
1039
							$returnArray['msg'] = 'no_match';
1040
						}
1041
					}
1042
				}
1043
			}
1044
		}
1045
		else {
1046
			$returnArray['msg'] = 'not_exists_table1';
1047
		}
1048
1049
		return $returnArray;
1050
	}
1051
//
1052
//    /**
1053
//     * Compare an index in two different tables
1054
//     * @deprecated
1055
//     * @param  string $name   index name
1056
//     * @param  string $table1
1057
//     * @param  string $table2
1058
//     * @return array  array with keys 'msg','table1','table2'
1059
//     */
1060
//    public function compareIndexInTables($name, $table1, $table2)
1061
//    {
1062
//        $row1 = $this->describeIndex($name, $table1);
1063
//        $row2 = $this->describeIndex($name, $table2);
1064
//        $returnArray = array(
1065
//            'table1' => $row1,
1066
//            'table2' => $row2,
1067
//            'msg'    => 'error',
1068
//            );
1069
//        $ignore_filter = array('Table'=>1, 'Seq_in_index'=>1,'Cardinality'=>1, 'Sub_part'=>1, 'Packed'=>1, 'Comment'=>1);
1070
//
1071
//        if ($row1) {
1072
//            if (!$row2) {
1073
//                //Exists on table1 but not table2
1074
//                $returnArray['msg'] = 'not_exists_table2';
1075
//            }
1076
//            else {
1077
//                if (sizeof($row1) != sizeof($row2)) {
1078
//                    $returnArray['msg'] = 'no_match';
1079
//                }
1080
//                else {
1081
//                    $returnArray['msg'] = 'match';
1082
//                    foreach ($row1 as $fname => $fvalue) {
1083
//                        if (!isset($row2[$fname])) {
1084
//                            $returnArray['msg'] = 'no_match';
1085
//                        }
1086
//                        if(!isset($ignore_filter[$fname]) && $row1[$fname] != $row2[$fname]){
1087
//                            $returnArray['msg'] = 'no_match';
1088
//                        }
1089
//                    }
1090
//                }
1091
//            }
1092
//        } else {
1093
//            $returnArray['msg'] = 'not_exists_table1';
1094
//        }
1095
//
1096
//        return $returnArray;
1097
//    }
1098
1099
1100
	/**
1101
	 * Creates an index identified by name on the given fields.
1102
	 *
1103
	 * @param SugarBean $bean      SugarBean instance
1104
	 * @param array  $fieldDefs Field definitions, in vardef format
1105
	 * @param string $name      index name
1106
	 * @param bool   $unique    optional, true if we want to create an unique index
1107
     * @return bool query result
1108
     */
1109
	public function createIndex(SugarBean $bean, $fieldDefs, $name, $unique = true)
1110
	{
1111
		$sql = $this->createIndexSQL($bean, $fieldDefs, $name, $unique);
1112
		$tablename = $bean->getTableName();
1113
		$msg = "Error creating index $name on table: $tablename:";
1114
		return $this->query($sql,true,$msg);
1115
	}
1116
1117
	/**
1118
	 * returns a SQL query that creates the indices as defined in metadata
1119
	 * @param  array  $indices Assoc array with index definitions from vardefs
1120
	 * @param  string $table Focus table
1121
	 * @return array  Array of SQL queries to generate indices
1122
	 */
1123
	public function getConstraintSql($indices, $table)
1124
	{
1125
		if (!$this->isFieldArray($indices))
1126
			$indices = array($indices);
1127
1128
		$columns = array();
1129
1130
		foreach ($indices as $index) {
1131
			if(!empty($index['db']) && $index['db'] != $this->dbType)
1132
				continue;
1133
			if (isset($index['source']) && $index['source'] != 'db')
1134
			continue;
1135
1136
			$sql = $this->add_drop_constraint($table, $index);
1137
1138
			if(!empty($sql)) {
1139
				$columns[] = $sql;
1140
			}
1141
		}
1142
1143
		return $columns;
1144
	}
1145
1146
	/**
1147
	 * Adds a new indexes
1148
	 *
1149
	 * @param  string $tablename
1150
	 * @param  array  $indexes   indexes to add
1151
	 * @param  bool   $execute   true if we want to execute the returned sql statement
1152
	 * @return string SQL statement
1153
	 */
1154
	public function addIndexes($tablename, $indexes, $execute = true)
1155
	{
1156
		$alters = $this->getConstraintSql($indexes, $tablename);
1157
		if ($execute) {
1158
			foreach($alters as $sql) {
1159
				$this->query($sql, true, "Error adding index: ");
1160
			}
1161
		}
1162
		if(!empty($alters)) {
1163
			$sql = join(";\n", $alters).";\n";
1164
		} else {
1165
			$sql = '';
1166
		}
1167
		return $sql;
1168
	}
1169
1170
	/**
1171
	 * Drops indexes
1172
	 *
1173
	 * @param  string $tablename
1174
	 * @param  array  $indexes   indexes to drop
1175
	 * @param  bool   $execute   true if we want to execute the returned sql statement
1176
	 * @return string SQL statement
1177
	 */
1178
	public function dropIndexes($tablename, $indexes, $execute = true)
1179
	{
1180
		$sqls = array();
1181
		foreach ($indexes as $index) {
1182
			$name =$index['name'];
1183
			$sqls[$name] = $this->add_drop_constraint($tablename,$index,true);
1184
		}
1185
		if (!empty($sqls) && $execute) {
1186
			foreach($sqls as $name => $sql) {
1187
				unset(self::$index_descriptions[$tablename][$name]);
1188
				$this->query($sql);
1189
			}
1190
		}
1191
		if(!empty($sqls)) {
1192
			return join(";\n",$sqls).";";
1193
		} else {
1194
			return '';
1195
		}
1196
	}
1197
1198
	/**
1199
	 * Modifies indexes
1200
	 *
1201
	 * @param  string $tablename
1202
	 * @param  array  $indexes   indexes to modify
1203
	 * @param  bool   $execute   true if we want to execute the returned sql statement
1204
	 * @return string SQL statement
1205
	 */
1206
	public function modifyIndexes($tablename, $indexes, $execute = true)
1207
	{
1208
		return $this->dropIndexes($tablename, $indexes, $execute)."\n".
1209
			$this->addIndexes($tablename, $indexes, $execute);
1210
	}
1211
1212
	/**
1213
	 * Adds a column to table identified by field def.
1214
	 *
1215
	 * @param string $tablename
1216
	 * @param array  $fieldDefs
1217
     * @return bool query result
1218
     */
1219
	public function addColumn($tablename, $fieldDefs)
1220
	{
1221
		$sql = $this->addColumnSQL($tablename, $fieldDefs);
1222
		if ($this->isFieldArray($fieldDefs)){
1223
			$columns = array();
1224
			foreach ($fieldDefs as $fieldDef)
1225
				$columns[] = $fieldDef['name'];
1226
			$columns = implode(",", $columns);
1227
		}
1228
		else {
1229
			$columns = $fieldDefs['name'];
1230
		}
1231
		$msg = "Error adding column(s) $columns on table: $tablename:";
1232
		return $this->query($sql,true,$msg);
1233
	}
1234
1235
	/**
1236
	 * Alters old column identified by oldFieldDef to new fieldDef.
1237
	 *
1238
	 * @param string $tablename
1239
	 * @param array  $newFieldDef
1240
	 * @param bool   $ignoreRequired optional, true if we are ignoring this being a required field
1241
     * @return bool query result
1242
     */
1243
	public function alterColumn($tablename, $newFieldDef, $ignoreRequired = false)
1244
	{
1245
		$sql = $this->alterColumnSQL($tablename, $newFieldDef,$ignoreRequired);
1246
		if ($this->isFieldArray($newFieldDef)){
1247
			$columns = array();
1248
			foreach ($newFieldDef as $fieldDef) {
1249
				$columns[] = $fieldDef['name'];
1250
			}
1251
			$columns = implode(",", $columns);
1252
		}
1253
		else {
1254
			$columns = $newFieldDef['name'];
1255
		}
1256
1257
		$msg = "Error altering column(s) $columns on table: $tablename:";
1258
		$res = $this->query($sql,true,$msg);
1259
		if($res) {
1260
			$this->getTableDescription($tablename, true); // reload table description after altering
1261
		}
1262
		return $res;
1263
	}
1264
1265
	/**
1266
	 * Drops the table associated with a bean
1267
	 *
1268
	 * @param SugarBean $bean SugarBean instance
1269
     * @return bool query result
1270
	 */
1271
	public function dropTable(SugarBean $bean)
1272
	{
1273
		return $this->dropTableName($bean->getTableName());
1274
	}
1275
1276
	/**
1277
	 * Drops the table by name
1278
	 *
1279
	 * @param string $name Table name
1280
     * @return bool query result
1281
	 */
1282
	public function dropTableName($name)
1283
	{
1284
		$sql = $this->dropTableNameSQL($name);
1285
		return $this->query($sql,true,"Error dropping table $name:");
1286
	}
1287
1288
    /**
1289
     * Deletes a column identified by fieldDef.
1290
     *
1291
     * @param SugarBean $bean   SugarBean containing the field
1292
     * @param array  $fieldDefs Vardef definition of the field
1293
     * @return bool query result
1294
     */
1295
	public function deleteColumn(SugarBean $bean, $fieldDefs)
1296
	{
1297
		$tablename = $bean->getTableName();
1298
		$sql = $this->dropColumnSQL($tablename, $fieldDefs);
1299
		$msg = "Error deleting column(s) on table: $tablename:";
1300
		return $this->query($sql,true,$msg);
1301
	}
1302
1303
    /**
1304
     * Generate a set of Insert statements based on the bean given
1305
     *
1306
     * @deprecated
1307
     *
1308
     * @param  SugarBean $bean         the bean from which table we will generate insert stmts
1309
     * @param  string $select_query the query which will give us the set of objects we want to place into our insert statement
1310
     * @param  int    $start        the first row to query
1311
     * @param  int    $count        the number of rows to query
1312
     * @param  string $table        the table to query from
1313
     * @param bool $is_related_query
1314
     * @return string SQL insert statement
1315
     */
1316
	public function generateInsertSQL(SugarBean $bean, $select_query, $start, $count = -1, $table, $is_related_query = false)
1317
	{
1318
		$this->log->info('call to DBManager::generateInsertSQL() is deprecated');
1319
		global $sugar_config;
1320
1321
		$rows_found = 0;
1322
		$count_query = $bean->create_list_count_query($select_query);
1323
		if(!empty($count_query))
1324
		{
1325
			// We have a count query.  Run it and get the results.
1326
			$result = $this->query($count_query, true, "Error running count query for $this->object_name List: ");
1327
			$assoc = $this->fetchByAssoc($result);
1328
			if(!empty($assoc['c']))
1329
			{
1330
				$rows_found = $assoc['c'];
1331
			}
1332
		}
1333
		if($count == -1){
1334
			$count 	= $sugar_config['list_max_entries_per_page'];
1335
		}
1336
		$next_offset = $start + $count;
1337
1338
		$result = $this->limitQuery($select_query, $start, $count);
1339
		// get basic insert
1340
		$sql = "INSERT INTO ".$table;
1341
		$custom_sql = "INSERT INTO ".$table."_cstm";
1342
1343
		// get field definitions
1344
		$fields = $bean->getFieldDefinitions();
1345
		$custom_fields = array();
1346
1347
		if($bean->hasCustomFields()){
1348
			foreach ($fields as $fieldDef){
1349
				if($fieldDef['source'] == 'custom_fields'){
1350
					$custom_fields[$fieldDef['name']] = $fieldDef['name'];
1351
				}
1352
			}
1353
			if(!empty($custom_fields)){
1354
				$custom_fields['id_c'] = 'id_c';
1355
				$id_field = array('name' => 'id_c', 'custom_type' => 'id',);
1356
				$fields[] = $id_field;
1357
			}
1358
		}
1359
1360
		// get column names and values
1361
		$row_array = array();
1362
		$columns = array();
1363
		$cstm_row_array = array();
1364
		$cstm_columns = array();
1365
		$built_columns = false;
1366
		while(($row = $this->fetchByAssoc($result)) != null)
1367
		{
1368
			$values = array();
1369
			$cstm_values = array();
1370
			if(!$is_related_query){
1371
				foreach ($fields as $fieldDef)
1372
				{
1373
					if(isset($fieldDef['source']) && $fieldDef['source'] != 'db' && $fieldDef['source'] != 'custom_fields') continue;
1374
					$val = $row[$fieldDef['name']];
1375
1376
					//handle auto increment values here only need to do this on insert not create
1377
					if ($fieldDef['name'] == 'deleted'){
1378
							$values['deleted'] = $val;
1379
							if(!$built_columns){
1380
							$columns[] = 'deleted';
1381
						}
1382
					}
1383
					else
1384
					{
1385
						$type = $fieldDef['type'];
1386
						if(!empty($fieldDef['custom_type'])){
1387
							$type = $fieldDef['custom_type'];
1388
						}
1389
						// need to do some thing about types of values
1390
						if($this->dbType == 'mysql' && $val == '' && ($type == 'datetime' ||  $type == 'date' || $type == 'int' || $type == 'currency' || $type == 'decimal')){
1391
							if(!empty($custom_fields[$fieldDef['name']]))
1392
								$cstm_values[$fieldDef['name']] = 'null';
1393
							else
1394
								$values[$fieldDef['name']] = 'null';
1395
						}else{
1396
							if(isset($type) && $type=='int') {
1397
								if(!empty($custom_fields[$fieldDef['name']]))
1398
									$cstm_values[$fieldDef['name']] = $GLOBALS['db']->quote(from_html($val));
1399
								else
1400
									$values[$fieldDef['name']] = $GLOBALS['db']->quote(from_html($val));
1401
							} else {
1402
								if(!empty($custom_fields[$fieldDef['name']]))
1403
									$cstm_values[$fieldDef['name']] = "'".$GLOBALS['db']->quote(from_html($val))."'";
1404
								else
1405
									$values[$fieldDef['name']] = "'".$GLOBALS['db']->quote(from_html($val))."'";
1406
							}
1407
						}
1408
						if(!$built_columns){
1409
							if(!empty($custom_fields[$fieldDef['name']]))
1410
								$cstm_columns[] = $fieldDef['name'];
1411
							else
1412
								$columns[] = $fieldDef['name'];
1413
						}
1414
					}
1415
1416
				}
1417
			} else {
1418
			foreach ($row as $key=>$val)
0 ignored issues
show
Bug introduced by
The expression $row of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1419
			{
1420
					if($key != 'orc_row'){
1421
						$values[$key] = "'$val'";
1422
						if(!$built_columns){
1423
							$columns[] = $key;
1424
						}
1425
					}
1426
			}
1427
			}
1428
			$built_columns = true;
1429
			if(!empty($values)){
1430
				$row_array[] = $values;
1431
			}
1432
			if(!empty($cstm_values) && !empty($cstm_values['id_c']) && (strlen($cstm_values['id_c']) > 7)){
1433
				$cstm_row_array[] = $cstm_values;
1434
			}
1435
		}
1436
1437
		//if (sizeof ($values) == 0) return ""; // no columns set
1438
1439
		// get the entire sql
1440
		$sql .= "(".implode(",", $columns).") ";
1441
		$sql .= "VALUES";
1442
		for($i = 0; $i < count($row_array); $i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1443
			$sql .= " (".implode(",", $row_array[$i]).")";
1444
			if($i < (count($row_array) - 1)){
1445
				$sql .= ", ";
1446
			}
1447
		}
1448
		//custom
1449
		// get the entire sql
1450
		$custom_sql .= "(".implode(",", $cstm_columns).") ";
1451
		$custom_sql .= "VALUES";
1452
1453
		for($i = 0; $i < count($cstm_row_array); $i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1454
			$custom_sql .= " (".implode(",", $cstm_row_array[$i]).")";
1455
			if($i < (count($cstm_row_array) - 1)){
1456
				$custom_sql .= ", ";
1457
			}
1458
		}
1459
		return array('data' => $sql, 'cstm_sql' => $custom_sql, /*'result_count' => $row_count, */ 'total_count' => $rows_found, 'next_offset' => $next_offset);
1460
	}
1461
1462
	/**
1463
	 * @deprecated
1464
	 * Disconnects all instances
1465
	 */
1466
	public function disconnectAll()
1467
	{
1468
		DBManagerFactory::disconnectAll();
1469
	}
1470
1471
	/**
1472
	 * This function sets the query threshold limit
1473
	 *
1474
	 * @param int $limit value of query threshold limit
1475
	 */
1476
	public static function setQueryLimit($limit)
1477
	{
1478
		//reset the queryCount
1479
		self::$queryCount = 0;
1480
		self::$queryLimit = $limit;
1481
	}
1482
1483
	/**
1484
	 * Returns the static queryCount value
1485
	 *
1486
	 * @return int value of the queryCount static variable
1487
	 */
1488
	public static function getQueryCount()
1489
	{
1490
		return self::$queryCount;
1491
	}
1492
1493
1494
	/**
1495
	 * Resets the queryCount value to 0
1496
	 *
1497
	 */
1498
	public static function resetQueryCount()
1499
	{
1500
		self::$queryCount = 0;
1501
	}
1502
1503
	/**
1504
	 * This function increments the global $sql_queries variable
1505
	 */
1506
	public function countQuery()
1507
	{
1508
		if (self::$queryLimit != 0 && ++self::$queryCount > self::$queryLimit
1509
			&&(empty($GLOBALS['current_user']) || !is_admin($GLOBALS['current_user']))) {
1510
            require_once('include/resource/ResourceManager.php');
1511
            $resourceManager = ResourceManager::getInstance();
1512
            $resourceManager->notifyObservers('ERR_QUERY_LIMIT');
1513
		}
1514
	}
1515
1516
	/**
1517
	 * Pre-process string for quoting
1518
	 * @internal
1519
	 * @param string $string
1520
     * @return string
1521
     */
1522
	protected function quoteInternal($string)
1523
	{
1524
		return from_html($string);
1525
	}
1526
1527
	/**
1528
	 * Return string properly quoted with ''
1529
	 * @param string $string
1530
	 * @return string
1531
	 */
1532
	public function quoted($string)
1533
	{
1534
		return "'".$this->quote($string)."'";
1535
	}
1536
1537
	/**
1538
     * Quote value according to type
1539
     * Numerics aren't quoted
1540
     * Dates are converted and quoted
1541
     * Rest is just quoted
1542
     * @param string $type
1543
     * @param string $value
1544
     * @return string Quoted value
1545
     */
1546
    public function quoteType($type, $value)
1547
	{
1548
	    if($type == 'date') {
1549
	        return $this->convert($this->quoted($value), "date");
1550
	    }
1551
	    if($type == 'time') {
1552
	        return $this->convert($this->quoted($value), "time");
1553
	    }
1554
        if(isset($this->type_class[$type]) &&  $this->type_class[$type] == "date") {
1555
            return $this->convert($this->quoted($value), "datetime");
1556
        }
1557
        if($this->isNumericType($type)) {
1558
            return 0+$value; // ensure it's numeric
1559
        }
1560
1561
        return $this->quoted($value);
1562
	}
1563
1564
    /**
1565
     * Quote the strings of the passed in array
1566
     *
1567
     * The array must only contain strings
1568
     *
1569
     * @param array $array
1570
     * @return array Quoted strings
1571
     */
1572
	public function arrayQuote(array &$array)
1573
	{
1574
		foreach($array as &$val) {
1575
			$val = $this->quote($val);
1576
		}
1577
		return $array;
1578
	}
1579
1580
    /**
1581
     * Frees out previous results
1582
     *
1583
     * @param resource|bool $result optional, pass if you want to free a single result instead of all results
1584
     */
1585
	protected function freeResult($result = false)
1586
	{
1587
		if($result) {
1588
			$this->freeDbResult($result);
1589
		}
1590
		if($this->lastResult) {
1591
			$this->freeDbResult($this->lastResult);
1592
			$this->lastResult = null;
1593
		}
1594
	}
1595
1596
	/**
1597
	 * @abstract
1598
	 * Check if query has LIMIT clause
1599
	 * Relevant for now only for Mysql
1600
	 * @param string $sql
1601
	 * @return bool
1602
	 */
1603
	protected function hasLimit($sql)
1604
	{
1605
	    return false;
1606
	}
1607
1608
	/**
1609
	 * Runs a query and returns a single row containing single value
1610
	 *
1611
	 * @param  string   $sql        SQL Statement to execute
1612
	 * @param  bool     $dieOnError True if we want to call die if the query returns errors
1613
	 * @param  string   $msg        Message to log if error occurs
1614
	 * @return array    single value from the query
1615
	 */
1616
	public function getOne($sql, $dieOnError = false, $msg = '')
1617
	{
1618
		$this->log->info("Get One: |$sql|");
1619
		if(!$this->hasLimit($sql)) {
1620
		    $queryresult = $this->limitQuery($sql, 0, 1, $dieOnError, $msg);
1621
		} else {
1622
		    // support old code that passes LIMIT to sql
1623
		    // works only for mysql, so do not rely on this
1624
		    $queryresult = $this->query($sql, $dieOnError, $msg);
1625
		}
1626
		$this->checkError($msg.' Get One Failed:' . $sql, $dieOnError);
1627
		if (!$queryresult) return false;
1628
		$row = $this->fetchByAssoc($queryresult);
1629
		if(!empty($row)) {
1630
			return array_shift($row);
1631
		}
1632
		return false;
1633
	}
1634
1635
	/**
1636
	 * Runs a query and returns a single row
1637
	 *
1638
	 * @param  string   $sql        SQL Statement to execute
1639
	 * @param  bool     $dieOnError True if we want to call die if the query returns errors
1640
	 * @param  string   $msg        Message to log if error occurs
1641
	 * @param  bool     $suppress   Message to log if error occurs
1642
	 * @return array    single row from the query
1643
	 */
1644
	public function fetchOne($sql, $dieOnError = false, $msg = '', $suppress = false)
1645
	{
1646
		$this->log->info("Fetch One: |$sql|");
1647
		$this->checkConnection();
1648
		$queryresult = $this->query($sql, $dieOnError, $msg);
1649
		$this->checkError($msg.' Fetch One Failed:' . $sql, $dieOnError);
1650
1651
		if (!$queryresult) return false;
1652
1653
		$row = $this->fetchByAssoc($queryresult);
1654
		if ( !$row ) return false;
1655
1656
		$this->freeResult($queryresult);
1657
		return $row;
1658
	}
1659
1660
    /**
1661
     * Returns the number of rows affected by the last query
1662
     * @abstract
1663
	 * See also affected_rows capability, will return 0 unless the DB supports it
1664
     * @param resource $result query result resource
1665
     * @return int
1666
     */
1667
	public function getAffectedRowCount($result)
1668
	{
1669
		return 0;
1670
	}
1671
1672
	/**
1673
	 * Returns the number of rows returned by the result
1674
	 *
1675
	 * This function can't be reliably implemented on most DB, do not use it.
1676
	 * @abstract
1677
	 * @deprecated
1678
	 * @param  resource $result
1679
	 * @return int
1680
	 */
1681
	public function getRowCount($result)
1682
	{
1683
	    return 0;
1684
	}
1685
1686
	/**
1687
     * Get table description
1688
     * @param string $tablename
1689
     * @param bool $reload true means load from DB, false allows using cache
1690
     * @return array Vardef-format table description
1691
     *
1692
     */
1693
	public function getTableDescription($tablename, $reload = false)
1694
	{
1695
		if($reload || empty(self::$table_descriptions[$tablename])) {
1696
			self::$table_descriptions[$tablename] = $this->get_columns($tablename);
1697
		}
1698
		return self::$table_descriptions[$tablename];
1699
	}
1700
1701
	/**
1702
	 * Returns the field description for a given field in table
1703
	 *
1704
	 * @param  string $name
1705
	 * @param  string $tablename
1706
	 * @return array
1707
	 */
1708
	protected function describeField($name, $tablename)
1709
	{
1710
		$table = $this->getTableDescription($tablename);
1711
		if(!empty($table) && isset($table[$name]))
1712
			return 	$table[$name];
1713
1714
		$table = $this->getTableDescription($tablename, true);
1715
1716
		if(isset($table[$name]))
1717
		return $table[$name];
1718
1719
		return array();
1720
	}
1721
1722
	/**
1723
	 * Returns the index description for a given index in table
1724
	 *
1725
	 * @param  string $name
1726
	 * @param  string $tablename
1727
	 * @return array
1728
	 */
1729
	protected function describeIndex($name, $tablename)
1730
	{
1731
		if(isset(self::$index_descriptions[$tablename]) && isset(self::$index_descriptions[$tablename]) && isset(self::$index_descriptions[$tablename][$name])){
1732
			return 	self::$index_descriptions[$tablename][$name];
1733
		}
1734
1735
		self::$index_descriptions[$tablename] = $this->get_indices($tablename);
1736
1737
		if(isset(self::$index_descriptions[$tablename][$name])){
1738
			return 	self::$index_descriptions[$tablename][$name];
1739
		}
1740
1741
		return array();
1742
	}
1743
1744
    /**
1745
     * Truncates a string to a given length
1746
     *
1747
     * @param string $string
1748
     * @param int    $len    length to trim to
1749
     * @return string
1750
     *
1751
     */
1752
	public function truncate($string, $len)
1753
	{
1754
		if ( is_numeric($len) && $len > 0)
1755
		{
1756
			$string = mb_substr($string,0,(int) $len, "UTF-8");
1757
		}
1758
		return $string;
1759
	}
1760
1761
    /**
1762
     * Returns the database string needed for concatinating multiple database strings together
1763
     *
1764
     * @param string $table table name of the database fields to concat
1765
     * @param array $fields fields in the table to concat together
1766
     * @param string $space Separator between strings, default is single space
1767
     * @return string
1768
     */
1769
	public function concat($table, array $fields, $space = ' ')
1770
	{
1771
		if(empty($fields)) return '';
1772
		$elems = array();
1773
		$space = $this->quoted($space);
1774
		foreach ( $fields as $field ) {
1775
			if(!empty($elems)) $elems[] = $space;
1776
			$elems[] = $this->convert("$table.$field", 'IFNULL', array("''"));
1777
		}
1778
		$first = array_shift($elems);
1779
		return "LTRIM(RTRIM(".$this->convert($first, 'CONCAT', $elems)."))";
1780
	}
1781
1782
	/**
1783
	 * Given a sql stmt attempt to parse it into the sql and the tokens. Then return the index of this prepared statement
1784
	 * Tokens can come in the following forms:
1785
	 * ? - a scalar which will be quoted
1786
	 * ! - a literal which will not be quoted
1787
	 * & - binary data to read from a file
1788
	 *
1789
	 * @param  string	$sql        The sql to parse
1790
	 * @return int index of the prepared statement to be used with execute
1791
	 */
1792
	public function prepareQuery($sql)
1793
	{
1794
		//parse out the tokens
1795
		$tokens = preg_split('/((?<!\\\)[&?!])/', $sql, -1, PREG_SPLIT_DELIM_CAPTURE);
1796
1797
		//maintain a count of the actual tokens for quick reference in execute
1798
		$count = 0;
1799
1800
		$sqlStr = '';
1801
		foreach ($tokens as $key => $val) {
1802
			switch ($val) {
1803
				case '?' :
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...
1804
				case '!' :
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...
1805
				case '&' :
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...
1806
					$count++;
1807
					$sqlStr .= '?';
1808
					break;
1809
1810
				default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

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

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

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

Loading history...
1811
					//escape any special characters
1812
					$tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
1813
					$sqlStr .= $tokens[$key];
1814
					break;
1815
			} // switch
1816
		} // foreach
1817
1818
		$this->preparedTokens[] = array('tokens' => $tokens, 'tokenCount' => $count, 'sqlString' => $sqlStr);
1819
		end($this->preparedTokens);
1820
		return key($this->preparedTokens);
1821
	}
1822
1823
	/**
1824
	 * Takes a prepared stmt index and the data to replace and creates the query and runs it.
1825
	 *
1826
	 * @param  int		$stmt       The index of the prepared statement from preparedTokens
1827
	 * @param  array    $data 		The array of data to replace the tokens with.
1828
	 * @return resource result set or false on error
1829
	 */
1830
	public function executePreparedQuery($stmt, $data = array())
1831
	{
1832
		if(!empty($this->preparedTokens[$stmt])){
1833
			if(!is_array($data)){
1834
				$data = array($data);
1835
			}
1836
1837
			$pTokens = $this->preparedTokens[$stmt];
1838
1839
			//ensure that the number of data elements matches the number of replacement tokens
1840
			//we found in prepare().
1841
			if(count($data) != $pTokens['tokenCount']){
1842
				//error the data count did not match the token count
1843
				return false;
1844
			}
1845
1846
			$query = '';
1847
			$dataIndex = 0;
1848
			$tokens = $pTokens['tokens'];
1849
			foreach ($tokens as $val) {
1850
				switch ($val) {
1851
					case '?':
1852
						$query .= $this->quote($data[$dataIndex++]);
1853
						break;
1854
					case '&':
1855
						$filename = $data[$dataIndex++];
1856
						$query .= file_get_contents($filename);
1857
						break;
1858
					case '!':
1859
						$query .= $data[$dataIndex++];
1860
						break;
1861
					default:
1862
						$query .= $val;
1863
						break;
1864
				}//switch
1865
			}//foreach
1866
			return $this->query($query);
1867
		}else{
1868
			return false;
1869
		}
1870
	}
1871
1872
	/**
1873
	 * Run both prepare and execute without the client having to run both individually.
1874
	 *
1875
	 * @param  string	$sql        The sql to parse
1876
	 * @param  array    $data 		The array of data to replace the tokens with.
1877
	 * @return resource result set or false on error
1878
	 */
1879
	public function pQuery($sql, $data = array())
1880
	{
1881
		$stmt = $this->prepareQuery($sql);
1882
		return $this->executePreparedQuery($stmt, $data);
1883
	}
1884
1885
/********************** SQL FUNCTIONS ****************************/
1886
    /**
1887
     * Generates sql for create table statement for a bean.
1888
     *
1889
     * NOTE: does not handle out-of-table constraints, use createConstraintSQL for that
1890
     * @param SugarBean $bean SugarBean instance
1891
     * @return string SQL Create Table statement
1892
     */
1893
	public function createTableSQL(SugarBean $bean)
1894
	{
1895
		$tablename = $bean->getTableName();
1896
		$fieldDefs = $bean->getFieldDefinitions();
1897
		$indices = $bean->getIndices();
1898
		return $this->createTableSQLParams($tablename, $fieldDefs, $indices);
1899
	}
1900
1901
	/**
1902
	 * Generates SQL for insert statement.
1903
	 *
1904
	 * @param  SugarBean $bean SugarBean instance
1905
	 * @return string SQL Create Table statement
1906
	 */
1907
	public function insertSQL(SugarBean $bean)
1908
	{
1909
		// get column names and values
1910
		$sql = $this->insertParams($bean->getTableName(), $bean->getFieldDefinitions(), get_object_vars($bean),
1911
		        isset($bean->field_name_map)?$bean->field_name_map:null, false);
1912
		return $sql;
1913
	}
1914
1915
	/**
1916
	 * Generates SQL for update statement.
1917
	 *
1918
	 * @param  SugarBean $bean SugarBean instance
1919
	 * @param  array  $where Optional, where conditions in an array
1920
	 * @return string SQL Create Table statement
1921
	 */
1922
	public function updateSQL(SugarBean $bean, array $where = array())
1923
	{
1924
		$primaryField = $bean->getPrimaryFieldDefinition();
1925
		$columns = array();
1926
        $fields = $bean->getFieldDefinitions();
1927
		// get column names and values
1928
		foreach ($fields as $field => $fieldDef) {
1929
			if (isset($fieldDef['source']) && $fieldDef['source'] != 'db')  continue;
1930
			// Do not write out the id field on the update statement.
1931
    		// We are not allowed to change ids.
1932
    		if (empty($fieldDef['name']) || $fieldDef['name'] == $primaryField['name']) continue;
1933
1934
    		// If the field is an auto_increment field, then we shouldn't be setting it.  This was added
1935
    		// specially for Bugs and Cases which have a number associated with them.
1936
    		if (!empty($bean->field_name_map[$field]['auto_increment'])) continue;
1937
1938
    		//custom fields handle their save separately
1939
    		if(isset($bean->field_name_map) && !empty($bean->field_name_map[$field]['custom_type']))  continue;
1940
1941
    		// no need to clear deleted since we only update not deleted records anyway
1942
    		if($fieldDef['name'] == 'deleted' && empty($bean->deleted)) continue;
1943
1944
    		if(isset($bean->$field)) {
1945
    			$val = from_html($bean->$field);
1946
    		} else {
1947
    			continue;
1948
    		}
1949
1950
    		if(!empty($fieldDef['type']) && $fieldDef['type'] == 'bool'){
1951
    			$val = $bean->getFieldValue($field);
1952
    		}
1953
1954
    		if(strlen($val) == 0) {
1955
    			if(isset($fieldDef['default']) && strlen($fieldDef['default']) > 0) {
1956
    				$val = $fieldDef['default'];
1957
    			} else {
1958
    				$val = null;
1959
    			}
1960
    		}
1961
1962
    		if(!empty($val) && !empty($fieldDef['len']) && strlen($val) > $fieldDef['len']) {
1963
			    $val = $this->truncate($val, $fieldDef['len']);
1964
			}
1965
		$columnName = $this->quoteIdentifier($fieldDef['name']);
1966
    		if(!is_null($val) || !empty($fieldDef['required'])) {
1967
    			$columns[] = "{$columnName}=".$this->massageValue($val, $fieldDef);
1968
    		} elseif($this->isNullable($fieldDef)) {
1969
    			$columns[] = "{$columnName}=NULL";
1970
    		} else {
1971
    		    $columns[] = "{$columnName}=".$this->emptyValue($fieldDef['type']);
1972
    		}
1973
		}
1974
1975
		if ( sizeof($columns) == 0 )
1976
			return ""; // no columns set
1977
1978
		// build where clause
1979
		$where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
1980
		if(isset($fields['deleted'])) {
1981
		    $where .= " AND deleted=0";
1982
		}
1983
1984
		return "UPDATE ".$bean->getTableName()."
1985
					SET ".implode(",", $columns)."
1986
					$where";
1987
	}
1988
1989
	/**
1990
	 * This method returns a where array so that it has id entry if
1991
	 * where is not an array or is empty
1992
	 *
1993
	 * @param  SugarBean $bean SugarBean instance
1994
	 * @param  array  $where Optional, where conditions in an array
1995
	 * @return array
1996
	 */
1997
	protected function updateWhereArray(SugarBean $bean, array $where = array())
1998
	{
1999
		if (count($where) == 0) {
2000
			$fieldDef = $bean->getPrimaryFieldDefinition();
2001
			$primaryColumn = $fieldDef['name'];
2002
2003
			$val = $bean->getFieldValue($fieldDef['name']);
2004
			if ($val != FALSE){
2005
				$where[$primaryColumn] = $val;
2006
			}
2007
		}
2008
2009
		return $where;
2010
	}
2011
2012
	/**
2013
	 * Returns a where clause without the 'where' key word
2014
	 *
2015
	 * The clause returned does not have an 'and' at the beginning and the columns
2016
	 * are joined by 'and'.
2017
	 *
2018
	 * @param  string $table table name
2019
	 * @param  array  $whereArray Optional, where conditions in an array
2020
	 * @return string
2021
	 */
2022
	protected function getColumnWhereClause($table, array $whereArray = array())
2023
	{
2024
		$where = array();
2025
		foreach ($whereArray as $name => $val) {
2026
			$op = "=";
2027
			if (is_array($val)) {
2028
				$op = "IN";
2029
				$temp = array();
2030
				foreach ($val as $tval){
2031
					$temp[] = $this->quoted($tval);
2032
				}
2033
				$val = implode(",", $temp);
2034
				$val = "($val)";
2035
			} else {
2036
				$val = $this->quoted($val);
2037
			}
2038
2039
			$where[] = " $table.$name $op $val";
2040
		}
2041
2042
		if (!empty($where))
2043
			return implode(" AND ", $where);
2044
2045
		return '';
2046
	}
2047
2048
	/**
2049
	 * This method returns a complete where clause built from the
2050
	 * where values specified.
2051
	 *
2052
	 * @param  SugarBean $bean SugarBean that describes the table
2053
	 * @param  array  $whereArray Optional, where conditions in an array
2054
	 * @return string
2055
	 */
2056
	protected function getWhereClause(SugarBean $bean, array $whereArray=array())
2057
	{
2058
	    return " WHERE " . $this->getColumnWhereClause($bean->getTableName(), $whereArray);
2059
	}
2060
2061
	/**
2062
	 * Outputs a correct string for the sql statement according to value
2063
	 *
2064
	 * @param  mixed $val
2065
	 * @param  array $fieldDef field definition
2066
	 * @return mixed
2067
	 */
2068
	public function massageValue($val, $fieldDef)
2069
	{
2070
		$type = $this->getFieldType($fieldDef);
2071
2072
		if(isset($this->type_class[$type])) {
2073
			// handle some known types
2074
			switch($this->type_class[$type]) {
2075
				case 'bool':
2076
				case 'int':
2077
					if (!empty($fieldDef['required']) && $val == ''){
2078
						if (isset($fieldDef['default'])){
2079
							return $fieldDef['default'];
2080
						}
2081
						return 0;
2082
					}
2083
					return intval($val);
2084
                case 'bigint' :
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...
2085
                    $val = (float)$val;
2086
					if (!empty($fieldDef['required']) && $val == false){
2087
						if (isset($fieldDef['default'])){
2088
							return $fieldDef['default'];
2089
						}
2090
						return 0;
2091
					}
2092
                    return $val;
2093
				case 'float':
2094
					if (!empty($fieldDef['required'])  && $val == ''){
2095
						if (isset($fieldDef['default'])){
2096
							return $fieldDef['default'];
2097
						}
2098
						return 0;
2099
					}
2100
					return floatval($val);
2101
				case 'time':
2102
				case 'date':
2103
					// empty date can't be '', so convert it to either NULL or empty date value
2104
					if($val == '') {
2105
						if (!empty($fieldDef['required'])) {
2106
							if (isset($fieldDef['default'])) {
2107
								return $fieldDef['default'];
2108
							}
2109
							return $this->emptyValue($type);
2110
						}
2111
						return "NULL";
2112
					}
2113
					break;
2114
			}
2115
		} else {
2116
		    if(!empty($val) && !empty($fieldDef['len']) && strlen($val) > $fieldDef['len']) {
2117
			    $val = $this->truncate($val, $fieldDef['len']);
2118
			}
2119
		}
2120
2121
		if ( is_null($val) ) {
2122
			if(!empty($fieldDef['required'])) {
2123
				if (isset($fieldDef['default'])  && $fieldDef['default'] != ''){
2124
					return $fieldDef['default'];
2125
				}
2126
				return $this->emptyValue($type);
2127
			} else {
2128
				return "NULL";
2129
			}
2130
		}
2131
        if($type == "datetimecombo") {
2132
            $type = "datetime";
2133
        }
2134
		return $this->convert($this->quoted($val), $type);
2135
	}
2136
2137
	/**
2138
	 * Massages the field defintions to fill in anything else the DB backend may add
2139
	 *
2140
	 * @param  array  $fieldDef
2141
	 * @param  string $tablename
2142
	 * @return array
2143
	 */
2144
	public function massageFieldDef(&$fieldDef, $tablename)
2145
	{
2146
		if ( !isset($fieldDef['dbType']) ) {
2147
			if ( isset($fieldDef['dbtype']) )
2148
				$fieldDef['dbType'] = $fieldDef['dbtype'];
2149
			else
2150
				$fieldDef['dbType'] = $fieldDef['type'];
2151
		}
2152
		$type = $this->getColumnType($fieldDef['dbType'],$fieldDef['name'],$tablename);
2153
		$matches = array();
2154
        // len can be a number or a string like 'max', for example, nvarchar(max)
2155
        preg_match_all('/(\w+)(?:\(([0-9]+,?[0-9]*|\w+)\)|)/i', $type, $matches);
2156
		if ( isset($matches[1][0]) )
2157
			$fieldDef['type'] = $matches[1][0];
2158
		if ( isset($matches[2][0]) && empty($fieldDef['len']) )
2159
			$fieldDef['len'] = $matches[2][0];
2160
		if ( !empty($fieldDef['precision']) && is_numeric($fieldDef['precision']) && !strstr($fieldDef['len'],',') )
2161
			$fieldDef['len'] .= ",{$fieldDef['precision']}";
2162
		if (!empty($fieldDef['required']) || ($fieldDef['name'] == 'id' && !isset($fieldDef['required'])) ) {
2163
			$fieldDef['required'] = 'true';
2164
		}
2165
	}
2166
2167
	/**
2168
	 * Take an SQL statement and produce a list of fields used in that select
2169
	 * @param string $selectStatement
2170
	 * @return array
2171
	 */
2172
	public function getSelectFieldsFromQuery($selectStatement)
2173
	{
2174
		$selectStatement = trim($selectStatement);
2175
		if (strtoupper(substr($selectStatement, 0, 6)) == "SELECT")
2176
			$selectStatement = trim(substr($selectStatement, 6));
2177
2178
		//Due to sql functions existing in many selects, we can't use php explode
2179
		$fields = array();
2180
		$level = 0;
2181
		$selectField = "";
2182
		$strLen = strlen($selectStatement);
2183
		for($i = 0; $i < $strLen; $i++)
2184
		{
2185
			$char = $selectStatement[$i];
2186
2187
			if ($char == "," && $level == 0)
2188
			{
2189
				$field = $this->getFieldNameFromSelect(trim($selectField));
2190
				$fields[$field] = $selectField;
2191
				$selectField = "";
2192
			}
2193
			else if ($char == "("){
2194
				$level++;
2195
				$selectField .= $char;
2196
			}
2197
			else if($char == ")"){
2198
				$level--;
2199
				$selectField .= $char;
2200
2201
2202
			}else{
2203
				$selectField .= $char;
2204
			}
2205
2206
		}
2207
		$fields[$this->getFieldNameFromSelect($selectField)] = $selectField;
2208
		return $fields;
2209
	}
2210
2211
	/**
2212
	 * returns the field name used in a select
2213
	 * @param string $string SELECT query
2214
     * @return string
2215
     */
2216
	protected function getFieldNameFromSelect($string)
2217
	{
2218
		if(strncasecmp($string, "DISTINCT ", 9) == 0) {
2219
			$string = substr($string, 9);
2220
		}
2221
		if (stripos($string, " as ") !== false)
2222
			//"as" used for an alias
2223
			return trim(substr($string, strripos($string, " as ") + 4));
2224
		else if (strrpos($string, " ") != 0)
2225
			//Space used as a delimiter for an alias
2226
			return trim(substr($string, strrpos($string, " ")));
2227
		else if (strpos($string, ".") !== false)
2228
			//No alias, but a table.field format was used
2229
			return substr($string, strpos($string, ".") + 1);
2230
		else
2231
			//Give up and assume the whole thing is the field name
2232
			return $string;
2233
	}
2234
2235
	/**
2236
	 * Generates SQL for delete statement identified by id.
2237
	 *
2238
	 * @param  SugarBean $bean SugarBean instance
2239
	 * @param  array  $where where conditions in an array
2240
	 * @return string SQL Update Statement
2241
	 */
2242
	public function deleteSQL(SugarBean $bean, array $where)
2243
	{
2244
		$where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
2245
		return "UPDATE ".$bean->getTableName()." SET deleted=1 $where";
2246
	}
2247
2248
    /**
2249
     * Generates SQL for select statement for any bean identified by id.
2250
     *
2251
     * @param  SugarBean $bean SugarBean instance
2252
     * @param  array  $where where conditions in an array
2253
     * @return string SQL Select Statement
2254
     */
2255
	public function retrieveSQL(SugarBean $bean, array $where)
2256
	{
2257
		$where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
2258
		return "SELECT * FROM ".$bean->getTableName()." $where AND deleted=0";
2259
	}
2260
2261
    /**
2262
     * This method implements a generic sql for a collection of beans.
2263
     *
2264
     * Currently, this function does not support outer joins.
2265
     *
2266
     * @param array $beans Array of values returned by get_class method as the keys and a bean as
2267
     *      the value for that key. These beans will be joined in the sql by the key
2268
     *      attribute of field defs.
2269
     * @param  array $cols Optional, columns to be returned with the keys as names of bean
2270
     *      as identified by get_class of bean. Values of this array is the array of fieldDefs
2271
     *      to be returned for a bean. If an empty array is passed, all columns are selected.
2272
     * @param  array $whereClause Optional, values with the keys as names of bean as identified
2273
     *      by get_class of bean. Each value at the first level is an array of values for that
2274
     *      bean identified by name of fields. If we want to pass multiple values for a name,
2275
     *      pass it as an array. If where is not passed, all the rows will be returned.
2276
     *
2277
     * @return string SQL Select Statement
2278
     */
2279
	public function retrieveViewSQL(array $beans, array $cols = array(), array $whereClause = array())
2280
	{
2281
		$relations = array(); // stores relations between tables as they are discovered
2282
		$where = $select = array();
2283
		foreach ($beans as $beanID => $bean) {
2284
			$tableName = $bean->getTableName();
2285
			$beanTables[$beanID] = $tableName;
2286
2287
			$table = $beanID;
2288
			$tables[$table] = $tableName;
2289
			$aliases[$tableName][] = $table;
2290
2291
			// build part of select for this table
2292
			if (is_array($cols[$beanID]))
2293
				foreach ($cols[$beanID] as $def) $select[] = $table.".".$def['name'];
2294
2295
			// build part of where clause
2296
			if (is_array($whereClause[$beanID])){
2297
				$where[] = $this->getColumnWhereClause($table, $whereClause[$beanID]);
2298
			}
2299
			// initialize so that it can be used properly in form clause generation
2300
			$table_used_in_from[$table] = false;
2301
2302
			$indices = $bean->getIndices();
2303
			foreach ($indices as $index){
2304
				if ($index['type'] == 'foreign') {
2305
					$relationship[$table][] = array('foreignTable'=> $index['foreignTable']
2306
												,'foreignColumn'=>$index['foreignField']
2307
												,'localColumn'=> $index['fields']
2308
												);
2309
				}
2310
			}
2311
			$where[] = " $table.deleted = 0";
2312
		}
2313
2314
		// join these clauses
2315
		$select = !empty($select) ? implode(",", $select) : "*";
2316
		$where = implode(" AND ", $where);
2317
2318
		// generate the from clause. Use relations array to generate outer joins
2319
		// all the rest of the tables will be used as a simple from
2320
		// relations table define relations between table1 and table2 through column on table 1
2321
		// table2 is assumed to joining through primary key called id
2322
		$separator = "";
2323
		$from = ''; $table_used_in_from = array();
2324
		foreach ($relations as $table1 => $rightsidearray){
2325
			if ($table_used_in_from[$table1]) continue; // table has been joined
2326
2327
			$from .= $separator." ".$table1;
2328
			$table_used_in_from[$table1] = true;
2329
			foreach ($rightsidearray as $tablearray){
2330
				$table2 = $tablearray['foreignTable']; // get foreign table
2331
				$tableAlias = $aliases[$table2]; // get a list of aliases for this table
2332
				foreach ($tableAlias as $table2) {
2333
					//choose first alias that does not match
2334
					// we are doing this because of self joins.
2335
					// in case of self joins, the same table will have many aliases.
2336
					if ($table2 != $table1) break;
2337
				}
2338
2339
				$col = $tablearray['foreingColumn'];
2340
				$name = $tablearray['localColumn'];
2341
				$from .= " LEFT JOIN $table on ($table1.$name = $table2.$col)";
2342
				$table_used_in_from[$table2] = true;
2343
			}
2344
			$separator = ",";
2345
		}
2346
2347
		return "SELECT $select FROM $from WHERE $where";
2348
	}
2349
2350
	/**
2351
	 * Generates SQL for create index statement for a bean.
2352
	 *
2353
	 * @param  SugarBean $bean SugarBean instance
2354
	 * @param  array  $fields fields used in the index
2355
	 * @param  string $name index name
2356
	 * @param  bool   $unique Optional, set to true if this is an unique index
2357
	 * @return string SQL Select Statement
2358
	 */
2359
	public function createIndexSQL(SugarBean $bean, array $fields, $name, $unique = true)
2360
	{
2361
		$unique = ($unique) ? "unique" : "";
2362
		$tablename = $bean->getTableName();
2363
		$columns = array();
2364
		// get column names
2365
		foreach ($fields as $fieldDef)
2366
			$columns[] = $fieldDef['name'];
2367
2368
		if (empty($columns))
2369
			return "";
2370
2371
		$columns = implode(",", $columns);
2372
2373
		return "CREATE $unique INDEX $name ON $tablename ($columns)";
2374
	}
2375
2376
	/**
2377
	 * Returns the type of the variable in the field
2378
	 *
2379
	 * @param  array $fieldDef Vardef-format field def
2380
	 * @return string
2381
	 */
2382
	public function getFieldType($fieldDef)
2383
	{
2384
		// get the type for db type. if that is not set,
2385
		// get it from type. This is done so that
2386
		// we do not have change a lot of existing code
2387
		// and add dbtype where type is being used for some special
2388
		// purposes like referring to foreign table etc.
2389
		if(!empty($fieldDef['dbType']))
2390
			return  $fieldDef['dbType'];
2391
		if(!empty($fieldDef['dbtype']))
2392
			return  $fieldDef['dbtype'];
2393
		if (!empty($fieldDef['type']))
2394
			return  $fieldDef['type'];
2395
		if (!empty($fieldDef['Type']))
2396
			return  $fieldDef['Type'];
2397
		if (!empty($fieldDef['data_type']))
2398
			return  $fieldDef['data_type'];
2399
2400
		return null;
2401
	}
2402
2403
    /**
2404
     * retrieves the different components from the passed column type as it is used in the type mapping and vardefs
2405
     * type format: <baseType>[(<len>[,<scale>])]
2406
     * @param string $type Column type
2407
     * @return array|bool array containing the different components of the passed in type or false in case the type contains illegal characters
2408
     */
2409
    public function getTypeParts($type)
2410
    {
2411
        if(preg_match("#(?P<type>\w+)\s*(?P<arg>\((?P<len>\w+)\s*(,\s*(?P<scale>\d+))*\))*#", $type, $matches))
2412
        {
2413
            $return = array();  // Not returning matches array as such as we don't want to expose the regex make up on the interface
2414
            $return['baseType'] = $matches['type'];
2415
            if( isset($matches['arg'])) {
2416
                $return['arg'] = $matches['arg'];
2417
            }
2418
            if( isset($matches['len'])) {
2419
                $return['len'] = $matches['len'];
2420
            }
2421
            if( isset($matches['scale'])) {
2422
                $return['scale'] = $matches['scale'];
2423
            }
2424
            return $return;
2425
        } else {
2426
            return false;
2427
        }
2428
    }
2429
2430
	/**
2431
	 * Returns the defintion for a single column
2432
	 *
2433
	 * @param  array  $fieldDef Vardef-format field def
2434
	 * @param  bool   $ignoreRequired  Optional, true if we should ignore this being a required field
2435
	 * @param  string $table           Optional, table name
2436
	 * @param  bool   $return_as_array Optional, true if we should return the result as an array instead of sql
2437
	 * @return string or array if $return_as_array is true
2438
	 */
2439
	protected function oneColumnSQLRep($fieldDef, $ignoreRequired = false, $table = '', $return_as_array = false)
2440
	{
2441
		$name = $fieldDef['name'];
2442
		$type = $this->getFieldType($fieldDef);
2443
        $colType = $this->getColumnType($type);
2444
2445
        if($parts = $this->getTypeParts($colType))
2446
        {
2447
            $colBaseType = $parts['baseType'];
2448
            $defLen =  isset($parts['len']) ? $parts['len'] : '255'; // Use the mappings length (precision) as default if it exists
2449
        }
2450
2451
        if(!empty($fieldDef['len'])) {
2452
            if (in_array($colBaseType, array( 'nvarchar', 'nchar', 'varchar', 'varchar2', 'char',
2453
                                          'clob', 'blob', 'text'))) {
2454
          	    $colType = "$colBaseType(${fieldDef['len']})";
2455
            } elseif(($colBaseType == 'decimal' || $colBaseType == 'float')){
2456
                  if(!empty($fieldDef['precision']) && is_numeric($fieldDef['precision']))
2457
                      if(strpos($fieldDef['len'],',') === false){
2458
                          $colType = $colBaseType . "(".$fieldDef['len'].",".$fieldDef['precision'].")";
2459
                      }else{
2460
                          $colType = $colBaseType . "(".$fieldDef['len'].")";
2461
                      }
2462
                  else
2463
                          $colType = $colBaseType . "(".$fieldDef['len'].")";
2464
              }
2465
        } else {
2466
            if (in_array($colBaseType, array( 'nvarchar', 'nchar', 'varchar', 'varchar2', 'char'))) {
2467
                $colType = "$colBaseType($defLen)";
2468
            }
2469
        }
2470
2471
        $default = '';
2472
2473
        // Bug #52610 We should have ability don't add DEFAULT part to query for boolean fields
2474
        if (!empty($fieldDef['no_default']))
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2475
        {
2476
            // nothing to do
2477
        }
2478
        elseif (isset($fieldDef['default']) && strlen($fieldDef['default']) > 0)
2479
        {
2480
            $default = " DEFAULT ".$this->quoted($fieldDef['default']);
2481
        }
2482
        elseif (!isset($default) && $type == 'bool')
2483
        {
2484
            $default = " DEFAULT 0 ";
2485
        }
2486
2487
		$auto_increment = '';
2488
		if(!empty($fieldDef['auto_increment']) && $fieldDef['auto_increment'])
2489
			$auto_increment = $this->setAutoIncrement($table , $fieldDef['name']);
2490
2491
		$required = 'NULL';  // MySQL defaults to NULL, SQL Server defaults to NOT NULL -- must specify
2492
		//Starting in 6.0, only ID and auto_increment fields will be NOT NULL in the DB.
2493
		if ((empty($fieldDef['isnull']) || strtolower($fieldDef['isnull']) == 'false') &&
2494
			(!empty($auto_increment) || $name == 'id' || ($fieldDef['type'] == 'id' && !empty($fieldDef['required'])))) {
2495
			$required =  "NOT NULL";
2496
		}
2497
		// If the field is marked both required & isnull=>false - alwqys make it not null
2498
		// Use this to ensure primary key fields never defined as null
2499
		if(isset($fieldDef['isnull']) && (strtolower($fieldDef['isnull']) == 'false' || $fieldDef['isnull'] === false)
2500
			&& !empty($fieldDef['required'])) {
2501
			$required =  "NOT NULL";
2502
		}
2503
		if ($ignoreRequired)
2504
			$required = "";
2505
2506
		if ( $return_as_array ) {
2507
			return array(
2508
				'name' => $name,
2509
				'colType' => $colType,
2510
                'colBaseType' => $colBaseType,  // Adding base type for easier processing in derived classes
2511
				'default' => $default,
2512
				'required' => $required,
2513
				'auto_increment' => $auto_increment,
2514
				'full' => "$name $colType $default $required $auto_increment",
2515
				);
2516
		} else {
2517
			return "$name $colType $default $required $auto_increment";
2518
		}
2519
	}
2520
2521
	/**
2522
	 * Returns SQL defintions for all columns in a table
2523
	 *
2524
	 * @param  array  $fieldDefs  Vardef-format field def
2525
	 * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
2526
	 * @param  string $tablename      Optional, table name
2527
	 * @return string SQL column definitions
2528
	 */
2529
	protected function columnSQLRep($fieldDefs, $ignoreRequired = false, $tablename)
2530
	{
2531
		$columns = array();
2532
2533
		if ($this->isFieldArray($fieldDefs)) {
2534
			foreach ($fieldDefs as $fieldDef) {
2535
				if(!isset($fieldDef['source']) || $fieldDef['source'] == 'db') {
2536
					$columns[] = $this->oneColumnSQLRep($fieldDef,false, $tablename);
2537
				}
2538
			}
2539
			$columns = implode(",", $columns);
2540
		}
2541
		else {
2542
			$columns = $this->oneColumnSQLRep($fieldDefs,$ignoreRequired, $tablename);
2543
		}
2544
2545
		return $columns;
2546
	}
2547
2548
	/**
2549
	 * Returns the next value for an auto increment
2550
	 * @abstract
2551
	 * @param  string $table Table name
2552
	 * @param  string $field_name Field name
2553
	 * @return string
2554
	 */
2555
	public function getAutoIncrement($table, $field_name)
2556
	{
2557
		return "";
2558
	}
2559
2560
	/**
2561
	 * Returns the sql for the next value in a sequence
2562
	 * @abstract
2563
	 * @param  string $table  Table name
2564
	 * @param  string $field_name  Field name
2565
	 * @return string
2566
	 */
2567
	public function getAutoIncrementSQL($table, $field_name)
2568
	{
2569
		return "";
2570
	}
2571
2572
	/**
2573
	 * Either creates an auto increment through queries or returns sql for auto increment
2574
	 * that can be appended to the end of column defination (mysql)
2575
	 * @abstract
2576
	 * @param  string $table Table name
2577
	 * @param  string $field_name Field name
2578
	 * @return string
2579
	 */
2580
	protected function setAutoIncrement($table, $field_name)
2581
	{
2582
		$this->deleteAutoIncrement($table, $field_name);
0 ignored issues
show
Unused Code introduced by
The call to the method DBManager::deleteAutoIncrement() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
2583
		return "";
2584
	}
2585
2586
    /**
2587
     * Sets the next auto-increment value of a column to a specific value.
2588
     * @abstract
2589
     * @param  string $table Table name
2590
     * @param  string $field_name Field name
2591
     * @param  int $start_value  Starting autoincrement value
2592
     * @return string
2593
     *
2594
     */
2595
	public function setAutoIncrementStart($table, $field_name, $start_value)
2596
	{
2597
		return "";
2598
	}
2599
2600
	/**
2601
	 * Deletes an auto increment
2602
	 * @abstract
2603
	 * @param string $table tablename
2604
	 * @param string $field_name
2605
	 */
2606
	public function deleteAutoIncrement($table, $field_name)
2607
	{
2608
		return;
2609
	}
2610
2611
	/**
2612
	 * This method generates sql for adding a column to table identified by field def.
2613
	 *
2614
	 * @param  string $tablename
2615
	 * @param  array  $fieldDefs
2616
	 * @return string SQL statement
2617
	 */
2618
	public function addColumnSQL($tablename, $fieldDefs)
2619
	{
2620
	    return $this->changeColumnSQL($tablename, $fieldDefs, 'add');
2621
	}
2622
2623
	/**
2624
	 * This method genrates sql for altering old column identified by oldFieldDef to new fieldDef.
2625
	 *
2626
	 * @param  string $tablename
2627
	 * @param  array  $newFieldDefs
2628
	 * @param  bool  $ignorerequired Optional, true if we should ignor this being a required field
2629
	 * @return string|array SQL statement(s)
2630
	 */
2631
	public function alterColumnSQL($tablename, $newFieldDefs, $ignorerequired = false)
2632
	{
2633
		return $this->changeColumnSQL($tablename, $newFieldDefs, 'modify', $ignorerequired);
2634
	}
2635
2636
	/**
2637
	 * Generates SQL for dropping a table.
2638
	 *
2639
	 * @param  SugarBean $bean Sugarbean instance
2640
	 * @return string SQL statement
2641
	 */
2642
	public function dropTableSQL(SugarBean $bean)
2643
	{
2644
		return $this->dropTableNameSQL($bean->getTableName());
2645
	}
2646
2647
	/**
2648
	 * Generates SQL for dropping a table.
2649
	 *
2650
	 * @param  string $name table name
2651
	 * @return string SQL statement
2652
	 */
2653
	public function dropTableNameSQL($name)
2654
	{
2655
		return "DROP TABLE ".$name;
2656
	}
2657
2658
	/**
2659
	 * Generates SQL for truncating a table.
2660
	 * @param  string $name  table name
2661
	 * @return string
2662
	 */
2663
	public function truncateTableSQL($name)
2664
	{
2665
		return "TRUNCATE $name";
2666
	}
2667
2668
	/**
2669
	 * This method generates sql that deletes a column identified by fieldDef.
2670
	 *
2671
	 * @param  SugarBean $bean      Sugarbean instance
2672
	 * @param  array  $fieldDefs
2673
	 * @return string SQL statement
2674
	 */
2675
	public function deleteColumnSQL(SugarBean $bean, $fieldDefs)
2676
	{
2677
		return $this->dropColumnSQL($bean->getTableName(), $fieldDefs);
2678
	}
2679
2680
	/**
2681
	 * This method generates sql that drops a column identified by fieldDef.
2682
	 * Designed to work like the other addColumnSQL() and alterColumnSQL() functions
2683
	 *
2684
	 * @param  string $tablename
2685
	 * @param  array  $fieldDefs
2686
	 * @return string SQL statement
2687
	 */
2688
	public function dropColumnSQL($tablename, $fieldDefs)
2689
	{
2690
		return $this->changeColumnSQL($tablename, $fieldDefs, 'drop');
2691
	}
2692
2693
    /**
2694
     * Return a version of $proposed that can be used as a column name in any of our supported databases
2695
     * Practically this means no longer than 25 characters as the smallest identifier length for our supported DBs is 30 chars for Oracle plus we add on at least four characters in some places (for indicies for example)
2696
     * @param string|array $name Proposed name for the column
2697
     * @param bool|string $ensureUnique Ensure the name is unique
2698
     * @param string $type Name type (table, column)
2699
     * @param bool $force Force new name
2700
     * @return string|array Valid column name trimmed to right length and with invalid characters removed
2701
     */
2702
	public function getValidDBName($name, $ensureUnique = false, $type = 'column', $force = false)
2703
	{
2704
		if(is_array($name)) {
2705
			$result = array();
2706
			foreach($name as $field) {
2707
				$result[] = $this->getValidDBName($field, $ensureUnique, $type);
2708
			}
2709
			return $result;
2710
		} else {
2711
		    if(strchr($name, ".")) {
2712
		        // this is a compound name with dots, handle separately
2713
		        $parts = explode(".", $name);
2714
		        if(count($parts) > 2) {
2715
		            // some weird name, cut to table.name
2716
		            array_splice($parts, 0, count($parts)-2);
2717
		        }
2718
		        $parts = $this->getValidDBName($parts, $ensureUnique, $type, $force);
2719
                return join(".", $parts);
2720
		    }
2721
			// first strip any invalid characters - all but word chars (which is alphanumeric and _)
2722
			$name = preg_replace( '/[^\w]+/i', '', $name ) ;
2723
			$len = strlen( $name ) ;
2724
			$maxLen = empty($this->maxNameLengths[$type]) ? $this->maxNameLengths[$type]['column'] : $this->maxNameLengths[$type];
2725
			if ($len <= $maxLen && !$force) {
2726
				return strtolower($name);
2727
			}
2728
			if ($ensureUnique) {
2729
				$md5str = md5($name);
2730
				$tail = substr ( $name, -11) ;
2731
				$temp = substr($md5str , strlen($md5str)-4 );
2732
				$result = substr( $name, 0, 10) . $temp . $tail ;
2733
			} else {
2734
				$result = substr( $name, 0, 11) . substr( $name, 11 - $maxLen);
2735
			}
2736
2737
			return strtolower( $result ) ;
2738
		}
2739
	}
2740
2741
	/**
2742
	 * Returns the valid type for a column given the type in fieldDef
2743
	 *
2744
	 * @param  string $type field type
2745
	 * @return string valid type for the given field
2746
	 */
2747
	public function getColumnType($type)
2748
	{
2749
		return isset($this->type_map[$type])?$this->type_map[$type]:$type;
2750
	}
2751
2752
	/**
2753
	 * Checks to see if passed array is truely an array of defitions
2754
	 *
2755
	 * Such an array may have type as a key but it will point to an array
2756
	 * for a true array of definitions an to a col type for a definition only
2757
	 *
2758
	 * @param  mixed $defArray
2759
	 * @return bool
2760
	 */
2761
	public function isFieldArray($defArray)
2762
	{
2763
		if ( !is_array($defArray) )
2764
			return false;
2765
2766
		if ( isset($defArray['type']) ){
2767
			// type key exists. May be an array of defs or a simple definition
2768
			return is_array($defArray['type']); // type is not an array => definition else array
2769
		}
2770
2771
		// type does not exist. Must be array of definitions
2772
		return true;
2773
	}
2774
2775
	/**
2776
	 * returns true if the type can be mapped to a valid column type
2777
	 *
2778
	 * @param  string $type
2779
	 * @return bool
2780
	 */
2781
	protected function validColumnType($type)
2782
	{
2783
		$type = $this->getColumnType($type);
2784
		return !empty($type);
2785
	}
2786
2787
	/**
2788
	 * Generate query for audit table
2789
	 * @param SugarBean $bean SugarBean that was changed
2790
	 * @param array $changes List of changes, contains 'before' and 'after'
2791
     * @return string  Audit table INSERT query
2792
     */
2793
	protected function auditSQL(SugarBean $bean, $changes)
2794
	{
2795
		global $current_user;
2796
		$sql = "INSERT INTO ".$bean->get_audit_table_name();
2797
		//get field defs for the audit table.
2798
		require('metadata/audit_templateMetaData.php');
2799
		$fieldDefs = $dictionary['audit']['fields'];
2800
2801
		$values=array();
2802
		$values['id'] = $this->massageValue(create_guid(), $fieldDefs['id']);
2803
		$values['parent_id']= $this->massageValue($bean->id, $fieldDefs['parent_id']);
2804
		$values['field_name']= $this->massageValue($changes['field_name'], $fieldDefs['field_name']);
2805
		$values['data_type'] = $this->massageValue($changes['data_type'], $fieldDefs['data_type']);
2806
		if ($changes['data_type']=='text') {
2807
			$values['before_value_text'] = $this->massageValue($changes['before'], $fieldDefs['before_value_text']);
2808
			$values['after_value_text'] = $this->massageValue($changes['after'], $fieldDefs['after_value_text']);
2809
		} else {
2810
			$values['before_value_string'] = $this->massageValue($changes['before'], $fieldDefs['before_value_string']);
2811
			$values['after_value_string'] = $this->massageValue($changes['after'], $fieldDefs['after_value_string']);
2812
		}
2813
		$values['date_created'] = $this->massageValue(TimeDate::getInstance()->nowDb(), $fieldDefs['date_created'] );
2814
		$values['created_by'] = $this->massageValue($current_user->id, $fieldDefs['created_by']);
2815
2816
		$sql .= "(".implode(",", array_keys($values)).") ";
2817
		$sql .= "VALUES(".implode(",", $values).")";
2818
		return $sql;
2819
	}
2820
2821
    /**
2822
     * Saves changes to module's audit table
2823
     *
2824
     * @param SugarBean $bean Sugarbean instance that was changed
2825
     * @param array $changes List of changes, contains 'before' and 'after'
2826
     * @return bool query result
2827
     *
2828
     */
2829
	public function save_audit_records(SugarBean $bean, $changes)
2830
	{
2831
		return $this->query($this->auditSQL($bean, $changes));
2832
	}
2833
2834
    /**
2835
     * Finds fields whose value has changed.
2836
     * The before and after values are stored in the bean.
2837
     * Uses $bean->fetched_row && $bean->fetched_rel_row to compare
2838
     *
2839
     * @param SugarBean $bean Sugarbean instance that was changed
2840
     * @param array|null $field_filter Array of filter names to be inspected (NULL means all fields)
2841
     * @return array
2842
     */
2843
    public function getDataChanges(SugarBean &$bean, array $field_filter = null)
2844
	{
2845
        $changed_values=array();
2846
2847
        $fetched_row = array();
2848
        if (is_array($bean->fetched_row))
2849
        {
2850
            $fetched_row = array_merge($bean->fetched_row, $bean->fetched_rel_row);
2851
        }
2852
2853
        if ($fetched_row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fetched_row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2854
2855
            $field_defs = $bean->field_defs;
2856
2857
            if (is_array($field_filter)) {
2858
                $field_defs = array_intersect_key($field_defs, array_flip($field_filter));
2859
            }
2860
2861
            // remove fields which do not present in fetched row
2862
            $field_defs = array_intersect_key($field_defs, $fetched_row);
2863
2864
            // remove fields which do not exist as bean property
2865
            $field_defs = array_intersect_key($field_defs, (array) $bean);
2866
2867
            foreach ($field_defs as $field => $properties) {
2868
                $before_value = $fetched_row[$field];
2869
                $after_value=$bean->$field;
2870
                if (isset($properties['type'])) {
2871
                    $field_type=$properties['type'];
2872
                } else {
2873
                    if (isset($properties['dbType'])) {
2874
                        $field_type=$properties['dbType'];
2875
                    }
2876
                    else if(isset($properties['data_type'])) {
2877
                        $field_type=$properties['data_type'];
2878
                    }
2879
                    else {
2880
                        $field_type=$properties['dbtype'];
2881
                    }
2882
                }
2883
2884
                //Because of bug #25078(sqlserver haven't 'date' type, trim extra "00:00:00" when insert into *_cstm table).
2885
                // so when we read the audit datetime field from sqlserver, we have to replace the extra "00:00:00" again.
2886
                if(!empty($field_type) && $field_type == 'date'){
2887
                    $before_value = $this->fromConvert($before_value , $field_type);
2888
                }
2889
                //if the type and values match, do nothing.
2890
                if (!($this->_emptyValue($before_value,$field_type) && $this->_emptyValue($after_value,$field_type))) {
2891
                    $change = false;
2892
                    if (trim($before_value) !== trim($after_value)) {
2893
                        // Bug #42475: Don't directly compare numeric values, instead do the subtract and see if the comparison comes out to be "close enough", it is necessary for floating point numbers.
2894
                        // Manual merge of fix 95727f2eed44852f1b6bce9a9eccbe065fe6249f from DBHelper
2895
                        // This fix also fixes Bug #44624 in a more generic way and therefore eliminates the need for fix 0a55125b281c4bee87eb347709af462715f33d2d in DBHelper
2896
                        if ($this->isNumericType($field_type)) {
2897
                            $numerator = abs(2*((trim($before_value)+0)-(trim($after_value)+0)));
2898
                            $denominator = abs(((trim($before_value)+0)+(trim($after_value)+0)));
2899
                            // detect whether to use absolute or relative error. use absolute if denominator is zero to avoid division by zero
2900
                            $error = ($denominator == 0) ? $numerator : $numerator / $denominator;
2901
                            if ($error >= 0.0000000001) {    // Smaller than 10E-10
2902
                                $change = true;
2903
                            }
2904
                        }
2905
                        else if ($this->isBooleanType($field_type)) {
2906
                            if ($this->_getBooleanValue($before_value) != $this->_getBooleanValue($after_value)) {
2907
                                $change = true;
2908
                            }
2909
                        }
2910
                        else {
2911
                            $change = true;
2912
                        }
2913
                        if ($change) {
2914
                            $changed_values[$field]=array('field_name'=>$field,
2915
                                'data_type'=>$field_type,
2916
                                'before'=>$before_value,
2917
                                'after'=>$after_value);
2918
                        }
2919
                    }
2920
                }
2921
			}
2922
		}
2923
		return $changed_values;
2924
	}
2925
2926
    /**
2927
     * Uses the audit enabled fields array to find fields whose value has changed.
2928
     * The before and after values are stored in the bean.
2929
     * Uses $bean->fetched_row && $bean->fetched_rel_row to compare
2930
     *
2931
     * @param SugarBean $bean Sugarbean instance that was changed
2932
     * @return array
2933
     */
2934
    public function getAuditDataChanges(SugarBean $bean)
2935
    {
2936
        $audit_fields = $bean->getAuditEnabledFieldDefinitions();
2937
        return $this->getDataChanges($bean, array_keys($audit_fields));
2938
    }
2939
2940
	/**
2941
	 * Setup FT indexing
2942
	 * @abstract
2943
	 */
2944
	public function full_text_indexing_setup()
2945
	{
2946
		// Most DBs have nothing to setup, so provide default empty function
2947
	}
2948
2949
	/**
2950
	 * Quotes a string for storing in the database
2951
	 * @deprecated
2952
	 * Return value will be not surrounded by quotes
2953
	 *
2954
	 * @param  string $string
2955
	 * @return string
2956
	 */
2957
	public function escape_quote($string)
2958
	{
2959
		return $this->quote($string);
2960
	}
2961
2962
	/**
2963
	 * Quotes a string for storing in the database
2964
	 * @deprecated
2965
	 * Return value will be not surrounded by quotes
2966
	 *
2967
	 * @param  string $string
2968
	 * @return string
2969
	 */
2970
	public function quoteFormEmail($string)
2971
	{
2972
		return $this->quote($string);
2973
	}
2974
2975
    /**
2976
     * Renames an index using fields definition
2977
     *
2978
     * @param  array  $old_definition
2979
     * @param  array  $new_definition
2980
     * @param  string $table_name
2981
     * @return string SQL statement
2982
     */
2983
	public function renameIndexDefs($old_definition, $new_definition, $table_name)
2984
	{
2985
		return array($this->add_drop_constraint($table_name,$old_definition,true),
2986
				$this->add_drop_constraint($table_name,$new_definition), false);
2987
	}
2988
2989
	/**
2990
	 * Check if type is boolean
2991
	 * @param string $type
2992
     * @return bool
2993
     */
2994
	public function isBooleanType($type)
2995
	{
2996
		return 'bool' == $type;
2997
	}
2998
2999
	/**
3000
	 * Get truth value for boolean type
3001
	 * Allows 'off' to mean false, along with all 'empty' values
3002
	 * @param mixed $val
3003
     * @return bool
3004
	 */
3005
	protected function _getBooleanValue($val)
3006
	{
3007
		//need to put the === sign here otherwise true == 'non empty string'
3008
		if (empty($val) or $val==='off')
3009
			return false;
3010
3011
		return true;
3012
	}
3013
3014
	/**
3015
	 * Check if type is a number
3016
	 * @param string $type
3017
     * @return bool
3018
	 */
3019
	public function isNumericType($type)
3020
	{
3021
	    if(isset($this->type_class[$type]) && ($this->type_class[$type] == 'int' || $this->type_class[$type] == 'float')) {
3022
	        return true;
3023
	    }
3024
		return false;
3025
	}
3026
3027
    /**
3028
     * Check if the value is empty value for this type
3029
     * @param mixed $val Value
3030
     * @param string $type Type (one of vardef types)
3031
     * @return bool true if the value if empty
3032
     */
3033
	protected function _emptyValue($val, $type)
3034
	{
3035
		if (empty($val))
3036
			return true;
3037
3038
		if($this->emptyValue($type) == $val) {
3039
			return true;
3040
		}
3041
		switch ($type) {
3042
			case 'decimal':
3043
			case 'decimal2':
3044
			case 'int':
3045
			case 'double':
3046
			case 'float':
3047
			case 'uint':
3048
			case 'ulong':
3049
			case 'long':
3050
			case 'short':
3051
				return ($val == 0);
3052
			case 'date':
3053
				if ($val == '0000-00-00')
3054
					return true;
3055
				if ($val == 'NULL')
3056
					return true;
3057
				return false;
3058
		}
3059
3060
		return false;
3061
	}
3062
3063
	/**
3064
     * @abstract
3065
	 * Does this type represent text (i.e., non-varchar) value?
3066
	 * @param string $type
3067
     * @return bool
3068
	 */
3069
	public function isTextType($type)
3070
	{
3071
		return false;
3072
	}
3073
3074
	/**
3075
	 * Check if this DB supports certain capability
3076
	 * See $this->capabilities for the list
3077
	 * @param string $cap
3078
     * @return bool
3079
	 */
3080
	public function supports($cap)
3081
	{
3082
		return !empty($this->capabilities[$cap]);
3083
	}
3084
3085
	/**
3086
	 * Create ORDER BY clause for ENUM type field
3087
	 * @param string $order_by Field name
3088
	 * @param array $values Possible enum value
3089
	 * @param string $order_dir Order direction, ASC or DESC
3090
     * @return string
3091
     */
3092
	public function orderByEnum($order_by, $values, $order_dir)
3093
	{
3094
		$i = 0;
3095
		$order_by_arr = array();
3096
		foreach ($values as $key => $value) {
3097
			if($key == '') {
3098
				$order_by_arr[] = "WHEN ($order_by='' OR $order_by IS NULL) THEN $i";
3099
			} else {
3100
				$order_by_arr[] = "WHEN $order_by=".$this->quoted($key)." THEN $i";
3101
			}
3102
			$i++;
3103
		}
3104
		return "CASE ".implode("\n", $order_by_arr)." ELSE $i END $order_dir\n";
3105
	}
3106
3107
	/**
3108
	 * Return representation of an empty value depending on type
3109
	 * The value is fully quoted, converted, etc.
3110
	 * @param string $type
3111
     * @return mixed Empty value
3112
     */
3113
	public function emptyValue($type)
3114
	{
3115
		if(isset($this->type_class[$type]) && ($this->type_class[$type] == 'bool' || $this->type_class[$type] == 'int' || $this->type_class[$type] == 'float')) {
3116
			return 0;
3117
		}
3118
3119
		return "''";
3120
	}
3121
3122
	/**
3123
	 * List of available collation settings
3124
     * @abstract
3125
	 * @return string
3126
	 */
3127
	public function getDefaultCollation()
3128
	{
3129
		return null;
3130
	}
3131
3132
	/**
3133
	 * List of available collation settings
3134
     * @abstract
3135
	 * @return array
3136
	 */
3137
	public function getCollationList()
3138
	{
3139
		return null;
3140
	}
3141
3142
	/**
3143
	 * Returns the number of columns in a table
3144
	 *
3145
	 * @param  string $table_name
3146
	 * @return int
3147
	 */
3148
	public function number_of_columns($table_name)
3149
	{
3150
		$table = $this->getTableDescription($table_name);
3151
		return count($table);
3152
	}
3153
3154
	/**
3155
	 * Return limit query based on given query
3156
	 * @param string $sql
3157
	 * @param int $start
3158
	 * @param int $count
3159
	 * @param bool $dieOnError
3160
	 * @param string $msg
3161
     * @return resource|bool query result
3162
     * @see DBManager::limitQuery()
3163
	 */
3164
	public function limitQuerySql($sql, $start, $count, $dieOnError=false, $msg='')
3165
	{
3166
		return $this->limitQuery($sql,$start,$count,$dieOnError,$msg,false);
3167
	}
3168
3169
	/**
3170
	 * Return current time in format fit for insertion into DB (with quotes)
3171
	 * @return string
3172
	 */
3173
	public function now()
3174
	{
3175
		return $this->convert($this->quoted(TimeDate::getInstance()->nowDb()), "datetime");
3176
	}
3177
3178
	/**
3179
	 * Check if connecting user has certain privilege
3180
	 * @param string $privilege
3181
     * @return bool Privilege allowed?
3182
     */
3183
	public function checkPrivilege($privilege)
3184
	{
3185
		switch($privilege) {
3186
			case "CREATE TABLE":
3187
				$this->query("CREATE TABLE temp (id varchar(36))");
3188
				break;
3189
			case "DROP TABLE":
3190
				$sql = $this->dropTableNameSQL("temp");
3191
				$this->query($sql);
3192
				break;
3193
			case "INSERT":
3194
				$this->query("INSERT INTO temp (id) VALUES ('abcdef0123456789abcdef0123456789abcd')");
3195
				break;
3196
			case "UPDATE":
3197
				$this->query("UPDATE temp SET id = '100000000000000000000000000000000000' WHERE id = 'abcdef0123456789abcdef0123456789abcd'");
3198
				break;
3199
			case 'SELECT':
3200
				return $this->getOne('SELECT id FROM temp WHERE id=\'100000000000000000000000000000000000\'', false);
3201
			case 'DELETE':
3202
				$this->query("DELETE FROM temp WHERE id = '100000000000000000000000000000000000'");
3203
				break;
3204
			case "ADD COLUMN":
3205
				$test = array("test" => array("name" => "test", "type" => "varchar", "len" => 50));
3206
				$sql = 	$this->changeColumnSQL("temp", $test, "add");
3207
				$this->query($sql);
3208
				break;
3209
			case "CHANGE COLUMN":
3210
				$test = array("test" => array("name" => "test", "type" => "varchar", "len" => 100));
3211
				$sql = 	$this->changeColumnSQL("temp", $test, "modify");
3212
				$this->query($sql);
3213
				break;
3214
			case "DROP COLUMN":
3215
				$test = array("test" => array("name" => "test", "type" => "varchar", "len" => 100));
3216
				$sql = 	$this->changeColumnSQL("temp", $test, "drop");
3217
				$this->query($sql);
3218
				break;
3219
			default:
3220
				return false;
3221
		}
3222
		if($this->checkError("Checking privileges")) {
3223
			return false;
3224
		}
3225
		return true;
3226
	}
3227
3228
	/**
3229
	 * Check if the query is a select query
3230
	 * @param string $query
3231
     * @return bool  Is query SELECT?
3232
     */
3233
	protected function isSelect($query)
3234
	{
3235
		$query = trim($query);
3236
		$select_check = strpos(strtolower($query), strtolower("SELECT"));
3237
		//Checks to see if there is union select which is valid
3238
		$select_check2 = strpos(strtolower($query), strtolower("(SELECT"));
3239
		if($select_check==0 || $select_check2==0){
3240
			//Returning false means query is ok!
3241
			return true;
3242
		}
3243
		return false;
3244
	}
3245
3246
	/**
3247
	 * Parse fulltext search query with mysql syntax:
3248
	 *  terms quoted by ""
3249
	 *  + means the term must be included
3250
	 *  - means the term must be excluded
3251
	 *  * or % at the end means wildcard
3252
	 * @param string $query
3253
	 * @return array of 3 elements - query terms, mandatory terms and excluded terms
3254
	 */
3255
	public function parseFulltextQuery($query)
3256
	{
3257
		/* split on space or comma, double quotes with \ for escape */
3258
		if(strpbrk($query, " ,")) {
3259
			// ("([^"]*?)"|[^" ,]+)((, )+)?
3260
			// '/([^" ,]+|".*?[^\\\\]")(,|\s)\s*/'
3261
			if(!preg_match_all('/("([^"]*?)"|[^"\s,]+)((,\s)+)?/', $query, $m)) {
3262
				return false;
3263
			}
3264
			$qterms = $m[1];
3265
		} else {
3266
			$qterms = array($query);
3267
		}
3268
		$terms = $must_terms = $not_terms = array();
3269
		foreach($qterms as $item) {
3270
			if($item[0] == '"') {
3271
				$item = trim($item, '"');
3272
			}
3273
			if($item[0] == '+') {
3274
                if (strlen($item) > 1) {
3275
                    $must_terms[] = substr($item, 1);
3276
                }
3277
                continue;
3278
			}
3279
			if($item[0] == '-') {
3280
                if (strlen($item) > 1) {
3281
				    $not_terms[] = substr($item, 1);
3282
                }
3283
                continue;
3284
			}
3285
			$terms[] = $item;
3286
		}
3287
		return array($terms, $must_terms, $not_terms);
3288
	}
3289
3290
    // Methods to check respective queries
3291
	protected $standardQueries = array(
3292
		'ALTER TABLE' => 'verifyAlterTable',
3293
		'DROP TABLE' => 'verifyDropTable',
3294
		'CREATE TABLE' => 'verifyCreateTable',
3295
		'INSERT INTO' => 'verifyInsertInto',
3296
		'UPDATE' => 'verifyUpdate',
3297
		'DELETE FROM' => 'verifyDeleteFrom',
3298
	);
3299
3300
3301
    /**
3302
     * Extract table name from a query
3303
     * @param string $query SQL query
3304
     * @return string
3305
     */
3306
	protected function extractTableName($query)
3307
	{
3308
        $query = preg_replace('/[^A-Za-z0-9_\s]/', "", $query);
3309
        $query = trim(str_replace(array_keys($this->standardQueries), '', $query));
3310
3311
        $firstSpc = strpos($query, " ");
3312
        $end = ($firstSpc > 0) ? $firstSpc : strlen($query);
3313
        $table = substr($query, 0, $end);
3314
3315
        return $table;
3316
	}
3317
3318
    /**
3319
     * Verify SQl statement using per-DB verification function
3320
     * provided the function exists
3321
     * @param string $query Query to verify
3322
     * @param array $skipTables List of blacklisted tables that aren't checked
3323
     * @return string
3324
     */
3325
	public function verifySQLStatement($query, $skipTables)
3326
	{
3327
		$query = trim($query);
3328
		foreach($this->standardQueries as $qstart => $check) {
3329
			if(strncasecmp($qstart, $query, strlen($qstart)) == 0) {
3330
				if(is_callable(array($this, $check))) {
3331
					$table = $this->extractTableName($query);
3332
					if(!in_array($table, $skipTables)) {
3333
						return call_user_func(array($this, $check), $table, $query);
3334
					} else {
3335
						$this->log->debug("Skipping table $table as blacklisted");
3336
					}
3337
				} else {
3338
					$this->log->debug("No verification for $qstart on {$this->dbType}");
3339
				}
3340
				break;
3341
			}
3342
		}
3343
		return "";
3344
	}
3345
3346
	/**
3347
	 * Tests an CREATE TABLE query
3348
	 * @param string $table The table name to get DDL
3349
	 * @param string $query The query to test.
3350
	 * @return string Non-empty if error found
3351
	 */
3352
	protected function verifyCreateTable($table, $query)
3353
	{
3354
		$this->log->debug('verifying CREATE statement...');
3355
3356
		// rewrite DDL with _temp name
3357
		$this->log->debug('testing query: ['.$query.']');
3358
		$tempname = $table."__uw_temp";
3359
		$tempTableQuery = str_replace("CREATE TABLE {$table}", "CREATE TABLE $tempname", $query);
3360
3361
		if(strpos($tempTableQuery, '__uw_temp') === false) {
3362
			return 'Could not use a temp table to test query!';
3363
		}
3364
3365
		$this->query($tempTableQuery, false, "Preflight Failed for: {$query}");
3366
3367
		$error = $this->lastError(); // empty on no-errors
3368
		if(!empty($error)) {
3369
			return $error;
3370
		}
3371
3372
		// check if table exists
3373
		$this->log->debug('testing for table: '.$table);
3374
		if(!$this->tableExists($tempname)) {
3375
			return "Failed to create temp table!";
3376
		}
3377
3378
		$this->dropTableName($tempname);
3379
		return '';
3380
	}
3381
3382
	/**
3383
	 * Execute multiple queries one after another
3384
	 * @param array $sqls Queries
3385
	 * @param bool $dieOnError Die on error, passed to query()
3386
	 * @param string $msg Error message, passed to query()
3387
	 * @param bool $suppress Supress errors, passed to query()
3388
	 * @return resource|bool result set or success/failure bool
3389
	 */
3390
	public function queryArray(array $sqls, $dieOnError = false, $msg = '', $suppress = false)
3391
	{
3392
		$last = true;
3393
		foreach($sqls as $sql) {
3394
			if(!($last = $this->query($sql, $dieOnError, $msg, $suppress))) {
3395
				break;
3396
			}
3397
		}
3398
		return $last;
3399
	}
3400
3401
	/**
3402
	 * Fetches the next row in the query result into an associative array
3403
	 *
3404
	 * @param  resource $result
3405
	 * @param  bool $encode Need to HTML-encode the result?
3406
	 * @return array    returns false if there are no more rows available to fetch
3407
	 */
3408
	public function fetchByAssoc($result, $encode = true)
3409
	{
3410
	    if (empty($result))	return false;
3411
3412
	    if(is_int($encode) && func_num_args() == 3) {
3413
	        // old API: $result, $rowNum, $encode
3414
	        $GLOBALS['log']->deprecated("Using row number in fetchByAssoc is not portable and no longer supported. Please fix your code.");
3415
	        $encode = func_get_arg(2);
3416
	    }
3417
	    $row = $this->fetchRow($result);
3418
	    if (!empty($row) && $encode && $this->encode) {
3419
	    	return array_map('to_html', $row);
3420
	    } else {
3421
	       return $row;
3422
	    }
3423
	}
3424
3425
	/**
3426
	 * Get DB driver name used for install/upgrade scripts
3427
	 * @return string
3428
	 */
3429
	public function getScriptName()
3430
	{
3431
		// Usually the same name as dbType
3432
		return $this->dbType;
3433
	}
3434
3435
	/**
3436
	 * Set database options
3437
	 * Options are usually db-dependant and derive from $config['dbconfigoption']
3438
	 * @param array $options
3439
	 * @return DBManager
3440
	 */
3441
	public function setOptions($options)
3442
	{
3443
	    $this->options = $options;
3444
	    return $this;
3445
	}
3446
3447
	/**
3448
	 * Get DB options
3449
	 * @return array
3450
	 */
3451
	public function getOptions()
3452
	{
3453
	    return $this->options;
3454
	}
3455
3456
	/**
3457
	 * Get DB option by name
3458
	 * @param string $option Option name
3459
	 * @return mixed Option value or null if doesn't exist
3460
	 */
3461
	public function getOption($option)
3462
	{
3463
	    if(isset($this->options[$option])) {
3464
	        return $this->options[$option];
3465
	    }
3466
	    return null;
3467
	}
3468
3469
	/**
3470
	 * Commits pending changes to the database when the driver is setup to support transactions.
3471
	 * Note that the default implementation is applicable for transaction-less or auto commit scenarios.
3472
	 * @abstract
3473
	 * @return bool true if commit succeeded, false if it failed
3474
	 */
3475
	public function commit()
3476
	{
3477
		$this->log->info("DBManager.commit() stub");
3478
		return true;
3479
	}
3480
3481
	/**
3482
	 * Rollsback pending changes to the database when the driver is setup to support transactions.
3483
	 * Note that the default implementation is applicable for transaction-less or auto commit scenarios.
3484
	 * Since rollbacks cannot be done, this implementation always returns false.
3485
	 * @abstract
3486
	 * @return bool true if rollback succeeded, false if it failed
3487
	 */
3488
	public function rollback()
3489
	{
3490
		$this->log->info("DBManager.rollback() stub");
3491
		return false;
3492
	}
3493
3494
	/**
3495
	 * Check if this DB name is valid
3496
	 *
3497
	 * @param string $name
3498
	 * @return bool
3499
	 */
3500
	public function isDatabaseNameValid($name)
3501
	{
3502
		// Generic case - no slashes, no dots
3503
		return preg_match('#[/.\\\\]#', $name)==0;
3504
	}
3505
3506
	/**
3507
	 * Check special requirements for DB installation.
3508
	 * @abstract
3509
	 * If everything is OK, return true.
3510
	 * If something's wrong, return array of error code and parameters
3511
	 * @return mixed
3512
	 */
3513
	public function canInstall()
3514
	{
3515
		return true;
3516
	}
3517
3518
	/**
3519
	 * @abstract
3520
     * Code run on new database before installing
3521
	 */
3522
	public function preInstall()
3523
	{
3524
	}
3525
3526
	/**
3527
     * @abstract
3528
	 * Code run on new database after installing
3529
	 */
3530
	public function postInstall()
3531
	{
3532
	}
3533
3534
	/**
3535
	 * Disable keys on the table
3536
	 * @abstract
3537
	 * @param string $tableName
3538
	 */
3539
	public function disableKeys($tableName)
3540
	{
3541
	}
3542
3543
	/**
3544
	 * Re-enable keys on the table
3545
	 * @abstract
3546
	 * @param string $tableName
3547
	 */
3548
	public function enableKeys($tableName)
3549
	{
3550
	}
3551
3552
	/**
3553
	 * Quote string in DB-specific manner
3554
	 * @param string $string
3555
	 * @return string
3556
	 */
3557
	abstract public function quote($string);
3558
3559
    abstract public function quoteIdentifier($string);
3560
3561
	/**
3562
	 * Use when you need to convert a database string to a different value; this function does it in a
3563
	 * database-backend aware way
3564
	 * Supported conversions:
3565
	 *      today		return current date
3566
	 *      left		Take substring from the left
3567
	 *      date_format	Format date as string, supports %Y-%m-%d, %Y-%m, %Y
3568
     *      time_format Format time as string
3569
     *      date        Convert date string to datetime value
3570
     *      time        Convert time string to datetime value
3571
	 *      datetime	Convert datetime string to datetime value
3572
	 *      ifnull		If var is null, use default value
3573
	 *      concat		Concatenate strings
3574
	 *      quarter		Quarter number of the date
3575
	 *      length		Length of string
3576
	 *      month		Month number of the date
3577
	 *      add_date	Add specified interval to a date
3578
     *      add_time    Add time interval to a date
3579
     *      text2char   Convert text field to varchar
3580
	 *
3581
	 * @param string $string database string to convert
3582
	 * @param string $type type of conversion to do
3583
	 * @param array  $additional_parameters optional, additional parameters to pass to the db function
3584
	 * @return string
3585
	 */
3586
	abstract public function convert($string, $type, array $additional_parameters = array());
3587
3588
	/**
3589
	 * Converts from Database data to app data
3590
	 *
3591
	 * Supported types
3592
	 * - date
3593
	 * - time
3594
	 * - datetime
3595
     * - datetimecombo
3596
     * - timestamp
3597
	 *
3598
	 * @param string $string database string to convert
3599
	 * @param string $type type of conversion to do
3600
	 * @return string
3601
	 */
3602
	abstract public function fromConvert($string, $type);
3603
3604
    /**
3605
     * Parses and runs queries
3606
     *
3607
     * @param  string   $sql        SQL Statement to execute
3608
     * @param  bool     $dieOnError True if we want to call die if the query returns errors
3609
     * @param  string   $msg        Message to log if error occurs
3610
     * @param  bool     $suppress   Flag to suppress all error output unless in debug logging mode.
3611
     * @param  bool     $keepResult Keep query result in the object?
3612
     * @return resource|bool result set or success/failure bool
3613
     */
3614
	abstract public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false);
3615
3616
    /**
3617
     * Runs a limit query: one where we specify where to start getting records and how many to get
3618
     *
3619
     * @param  string   $sql     SELECT query
3620
     * @param  int      $start   Starting row
3621
     * @param  int      $count   How many rows
3622
     * @param  boolean  $dieOnError  True if we want to call die if the query returns errors
3623
     * @param  string   $msg     Message to log if error occurs
3624
     * @param  bool     $execute Execute or return SQL?
3625
     * @return resource query result
3626
     */
3627
	abstract function limitQuery($sql, $start, $count, $dieOnError = false, $msg = '', $execute = true);
3628
3629
3630
	/**
3631
	 * Free Database result
3632
	 * @param resource $dbResult
3633
	 */
3634
	abstract protected function freeDbResult($dbResult);
3635
3636
	/**
3637
	 * Rename column in the DB
3638
	 * @param string $tablename
3639
	 * @param string $column
3640
	 * @param string $newname
3641
	 */
3642
	abstract function renameColumnSQL($tablename, $column, $newname);
3643
3644
	/**
3645
	 * Returns definitions of all indies for passed table.
3646
	 *
3647
	 * return will is a multi-dimensional array that
3648
	 * categorizes the index definition by types, unique, primary and index.
3649
	 * <code>
3650
	 * <?php
3651
	 * array(                                                              O
3652
	 *       'index1'=> array (
3653
	 *           'name'   => 'index1',
3654
	 *           'type'   => 'primary',
3655
	 *           'fields' => array('field1','field2')
3656
	 *           )
3657
	 *       )
3658
	 * ?>
3659
	 * </code>
3660
	 * This format is similar to how indicies are defined in vardef file.
3661
	 *
3662
	 * @param  string $tablename
3663
	 * @return array
3664
	 */
3665
	abstract public function get_indices($tablename);
3666
3667
	/**
3668
	 * Returns definitions of all indies for passed table.
3669
	 *
3670
	 * return will is a multi-dimensional array that
3671
	 * categorizes the index definition by types, unique, primary and index.
3672
	 * <code>
3673
	 * <?php
3674
	 * array(
3675
	 *       'field1'=> array (
3676
	 *           'name'   => 'field1',
3677
	 *           'type'   => 'varchar',
3678
	 *           'len' => '200'
3679
	 *           )
3680
	 *       )
3681
	 * ?>
3682
	 * </code>
3683
	 * This format is similar to how indicies are defined in vardef file.
3684
	 *
3685
	 * @param  string $tablename
3686
	 * @return array
3687
	 */
3688
	abstract public function get_columns($tablename);
3689
3690
	/**
3691
	 * Generates alter constraint statement given a table name and vardef definition.
3692
	 *
3693
	 * Supports both adding and droping a constraint.
3694
	 *
3695
	 * @param  string $table      tablename
3696
	 * @param  array  $definition field definition
3697
	 * @param  bool   $drop       true if we are dropping the constraint, false if we are adding it
3698
	 * @return string SQL statement
3699
	 */
3700
	abstract public function add_drop_constraint($table, $definition, $drop = false);
3701
3702
	/**
3703
	 * Returns the description of fields based on the result
3704
	 *
3705
	 * @param  resource $result
3706
	 * @param  boolean  $make_lower_case
3707
	 * @return array field array
3708
	 */
3709
	abstract public function getFieldsArray($result, $make_lower_case = false);
3710
3711
	/**
3712
	 * Returns an array of tables for this database
3713
	 *
3714
	 * @return	array|false 	an array of with table names, false if no tables found
3715
	 */
3716
	abstract public function getTablesArray();
3717
3718
	/**
3719
	 * Return's the version of the database
3720
	 *
3721
	 * @return string
3722
	 */
3723
	abstract public function version();
3724
3725
	/**
3726
	 * Checks if a table with the name $tableName exists
3727
	 * and returns true if it does or false otherwise
3728
	 *
3729
	 * @param  string $tableName
3730
	 * @return bool
3731
	 */
3732
	abstract public function tableExists($tableName);
3733
3734
	/**
3735
	 * Fetches the next row in the query result into an associative array
3736
	 *
3737
	 * @param  resource $result
3738
	 * @return array    returns false if there are no more rows available to fetch
3739
	 */
3740
	abstract public function fetchRow($result);
3741
3742
	/**
3743
	 * Connects to the database backend
3744
	 *
3745
	 * Takes in the database settings and opens a database connection based on those
3746
	 * will open either a persistent or non-persistent connection.
3747
	 * If a persistent connection is desired but not available it will defualt to non-persistent
3748
	 *
3749
	 * configOptions must include
3750
	 * db_host_name - server ip
3751
	 * db_user_name - database user name
3752
	 * db_password - database password
3753
	 *
3754
	 * @param array   $configOptions
3755
	 * @param boolean $dieOnError
3756
	 */
3757
	abstract public function connect(array $configOptions = null, $dieOnError = false);
3758
3759
	/**
3760
	 * Generates sql for create table statement for a bean.
3761
	 *
3762
	 * @param  string $tablename
3763
	 * @param  array  $fieldDefs
3764
	 * @param  array  $indices
3765
	 * @return string SQL Create Table statement
3766
	 */
3767
	abstract public function createTableSQLParams($tablename, $fieldDefs, $indices);
3768
3769
	/**
3770
	 * Generates the SQL for changing columns
3771
	 *
3772
	 * @param string $tablename
3773
	 * @param array  $fieldDefs
3774
	 * @param string $action
3775
	 * @param bool   $ignoreRequired Optional, true if we should ignor this being a required field
3776
	 * @return string|array
3777
	 */
3778
	abstract protected function changeColumnSQL($tablename, $fieldDefs, $action, $ignoreRequired = false);
3779
3780
	/**
3781
	 * Disconnects from the database
3782
	 *
3783
	 * Also handles any cleanup needed
3784
	 */
3785
	abstract public function disconnect();
3786
3787
	/**
3788
	 * Get last database error
3789
	 * This function should return last error as reported by DB driver
3790
	 * and should return false if no error condition happened
3791
	 * @return string|false Error message or false if no error happened
3792
	 */
3793
	abstract public function lastDbError();
3794
3795
    /**
3796
     * Check if this query is valid
3797
     * Validates only SELECT queries
3798
     * @param string $query
3799
     * @return bool
3800
     */
3801
	abstract public function validateQuery($query);
3802
3803
	/**
3804
	 * Check if this driver can be used
3805
	 * @return bool
3806
	 */
3807
	abstract public function valid();
3808
3809
	/**
3810
	 * Check if certain database exists
3811
	 * @param string $dbname
3812
	 */
3813
	abstract public function dbExists($dbname);
3814
3815
	/**
3816
	 * Get tables like expression
3817
	 * @param string $like Expression describing tables
3818
	 * @return array
3819
	 */
3820
	abstract public function tablesLike($like);
3821
3822
	/**
3823
	 * Create a database
3824
	 * @param string $dbname
3825
	 */
3826
	abstract public function createDatabase($dbname);
3827
3828
	/**
3829
	 * Drop a database
3830
	 * @param string $dbname
3831
	 */
3832
	abstract public function dropDatabase($dbname);
3833
3834
	/**
3835
	 * Get database configuration information (DB-dependent)
3836
	 * @return array|null
3837
	 */
3838
	abstract public function getDbInfo();
3839
3840
	/**
3841
	 * Check if certain DB user exists
3842
	 * @param string $username
3843
	 */
3844
	abstract public function userExists($username);
3845
3846
	/**
3847
	 * Create DB user
3848
	 * @param string $database_name
3849
	 * @param string $host_name
3850
	 * @param string $user
3851
	 * @param string $password
3852
	 */
3853
	abstract public function createDbUser($database_name, $host_name, $user, $password);
3854
3855
	/**
3856
	 * Check if the database supports fulltext indexing
3857
	 * Note that database driver can be capable of supporting FT (see supports('fulltext))
3858
	 * but particular instance can still have it disabled
3859
	 * @return bool
3860
	 */
3861
	abstract public function full_text_indexing_installed();
3862
3863
	/**
3864
	 * Generate fulltext query from set of terms
3865
	 * @param string $field Field to search against
3866
	 * @param array $terms Search terms that may be or not be in the result
3867
	 * @param array $must_terms Search terms that have to be in the result
3868
	 * @param array $exclude_terms Search terms that have to be not in the result
3869
	 */
3870
	abstract public function getFulltextQuery($field, $terms, $must_terms = array(), $exclude_terms = array());
3871
3872
	/**
3873
	 * Get install configuration for this DB
3874
	 * @return array
3875
	 */
3876
	abstract public function installConfig();
3877
3878
    /**
3879
     * Returns a DB specific FROM clause which can be used to select against functions.
3880
     * Note that depending on the database that this may also be an empty string.
3881
     * @abstract
3882
     * @return string
3883
     */
3884
    abstract public function getFromDummyTable();
3885
3886
    /**
3887
     * Returns a DB specific piece of SQL which will generate GUID (UUID)
3888
     * This string can be used in dynamic SQL to do multiple inserts with a single query.
3889
     * I.e. generate a unique Sugar id in a sub select of an insert statement.
3890
     * @abstract
3891
     * @return string
3892
     */
3893
	abstract public function getGuidSQL();
3894
}
3895