Completed
Push — master ( 6e68f3...d19955 )
by Daniel
11:51
created

SS_Database::renameField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 4
rs 10
1
<?php
2
3
namespace SilverStripe\ORM\Connect;
4
5
use Director;
6
use Debug;
7
use BadMethodCallException;
8
use Exception;
9
use Deprecation;
10
use SilverStripe\ORM\Queries\SQLUpdate;
11
use SilverStripe\ORM\Queries\SQLInsert;
12
use SilverStripe\ORM\Queries\SQLExpression;
13
use Config;
14
15
/**
16
 * Abstract database connectivity class.
17
 * Sub-classes of this implement the actual database connection libraries
18
 *
19
 * @package framework
20
 * @subpackage orm
21
 */
22
abstract class SS_Database {
23
24
	/**
25
	 * Database connector object
26
	 *
27
	 * @var DBConnector
28
	 */
29
	protected $connector = null;
30
31
	/**
32
	 * In cases where your environment does not have 'SHOW DATABASES' permission,
33
	 * you can set this to true. Then selectDatabase() will always connect without
34
	 * doing databaseExists() check.
35
	 *
36
	 * @var bool
37
	 */
38
	private static $optimistic_connect = false;
39
40
	/**
41
	 * Amount of queries executed, for debugging purposes.
42
	 *
43
	 * @var int
44
	 */
45
	protected $queryCount = 0;
46
47
	/**
48
	 * Get the current connector
49
	 *
50
	 * @return DBConnector
51
	 */
52
	public function getConnector() {
53
		return $this->connector;
54
	}
55
56
	/**
57
	 * Injector injection point for connector dependency
58
	 *
59
	 * @param DBConnector $connector
60
	 */
61
	public function setConnector(DBConnector $connector) {
62
		$this->connector = $connector;
63
	}
64
65
	/**
66
	 * Database schema manager object
67
	 *
68
	 * @var DBSchemaManager
69
	 */
70
	protected $schemaManager = null;
71
72
	/**
73
	 * Returns the current schema manager
74
	 *
75
	 * @return DBSchemaManager
76
	 */
77
	public function getSchemaManager() {
78
		return $this->schemaManager;
79
	}
80
81
	/**
82
	 * Injector injection point for schema manager
83
	 *
84
	 * @param DBSchemaManager $schemaManager
85
	 */
86
	public function setSchemaManager(DBSchemaManager $schemaManager) {
87
		$this->schemaManager = $schemaManager;
88
89
		if ($this->schemaManager) {
90
			$this->schemaManager->setDatabase($this);
91
		}
92
	}
93
94
	/**
95
	 * Query builder object
96
	 *
97
	 * @var DBQueryBuilder
98
	 */
99
	protected $queryBuilder = null;
100
101
	/**
102
	 * Returns the current query builder
103
	 *
104
	 * @return DBQueryBuilder
105
	 */
106
	public function getQueryBuilder() {
107
		return $this->queryBuilder;
108
	}
109
110
	/**
111
	 * Injector injection point for schema manager
112
	 *
113
	 * @param DBQueryBuilder $queryBuilder
114
	 */
115
	public function setQueryBuilder(DBQueryBuilder $queryBuilder) {
116
		$this->queryBuilder = $queryBuilder;
117
	}
118
119
	/**
120
	 * Execute the given SQL query.
121
	 *
122
	 * @param string $sql The SQL query to execute
123
	 * @param int $errorLevel The level of error reporting to enable for the query
124
	 * @return SS_Query
125
	 */
126
	public function query($sql, $errorLevel = E_USER_ERROR) {
127
		// Check if we should only preview this query
128
		if ($this->previewWrite($sql)) {
129
			return;
130
		}
131
132
		// Benchmark query
133
		$connector = $this->connector;
134
		return $this->benchmarkQuery(
135
			$sql,
136
			function($sql) use($connector, $errorLevel) {
137
				return $connector->query($sql, $errorLevel);
138
			}
139
		);
140
	}
141
142
143
	/**
144
	 * Execute the given SQL parameterised query with the specified arguments
145
	 *
146
	 * @param string $sql The SQL query to execute. The ? character will denote parameters.
147
	 * @param array $parameters An ordered list of arguments.
148
	 * @param int $errorLevel The level of error reporting to enable for the query
149
	 * @return SS_Query
150
	 */
151
	public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
152
		// Check if we should only preview this query
153
		if ($this->previewWrite($sql)) {
154
			return;
155
		}
156
157
		// Benchmark query
158
		$connector = $this->connector;
159
		return $this->benchmarkQuery(
160
			$sql,
161
			function($sql) use($connector, $parameters, $errorLevel) {
162
				return $connector->preparedQuery($sql, $parameters, $errorLevel);
163
			},
164
			$parameters
165
		);
166
	}
167
168
	/**
169
	 * Determines if the query should be previewed, and thus interrupted silently.
170
	 * If so, this function also displays the query via the debuging system.
171
	 * Subclasess should respect the results of this call for each query, and not
172
	 * execute any queries that generate a true response.
173
	 *
174
	 * @param string $sql The query to be executed
175
	 * @return boolean Flag indicating that the query was previewed
176
	 */
177
	protected function previewWrite($sql) {
0 ignored issues
show
Coding Style introduced by
previewWrite uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
178
		// Only preview if previewWrite is set, we are in dev mode, and
179
		// the query is mutable
180
		if (isset($_REQUEST['previewwrite'])
181
			&& Director::isDev()
182
			&& $this->connector->isQueryMutable($sql)
183
		) {
184
			// output preview message
185
			Debug::message("Will execute: $sql");
186
			return true;
187
		} else {
188
			return false;
189
		}
190
	}
191
192
	/**
193
	 * Allows the display and benchmarking of queries as they are being run
194
	 *
195
	 * @param string $sql Query to run, and single parameter to callback
196
	 * @param callable $callback Callback to execute code
197
	 * @param array $parameters Parameters to display
198
	 * @return mixed Result of query
199
	 */
200
	protected function benchmarkQuery($sql, $callback, $parameters = null) {
0 ignored issues
show
Coding Style introduced by
benchmarkQuery uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
201
		if (isset($_REQUEST['showqueries']) && Director::isDev()) {
202
			$this->queryCount++;
203
			$starttime = microtime(true);
204
			$result = $callback($sql);
205
			$endtime = round(microtime(true) - $starttime, 4);
206
			$message = $sql;
207
			if($parameters) {
208
				$message .= "\nparams: \"" . implode('", "', $parameters) . '"';
209
			}
210
			Debug::message("\n$this->queryCount: {$message}\n{$endtime}s\n", false);
211
212
			return $result;
213
		} else {
214
			return $callback($sql);
215
		}
216
	}
217
218
	/**
219
	 * Get the autogenerated ID from the previous INSERT query.
220
	 *
221
	 * @param string $table The name of the table to get the generated ID for
222
	 * @return integer the most recently generated ID for the specified table
223
	 */
224
	public function getGeneratedID($table) {
225
		return $this->connector->getGeneratedID($table);
226
	}
227
228
	/**
229
	 * Determines if we are connected to a server AND have a valid database
230
	 * selected.
231
	 *
232
	 * @return boolean Flag indicating that a valid database is connected
233
	 */
234
	public function isActive() {
235
		return $this->connector->isActive();
236
	}
237
238
	/**
239
	 * Returns an escaped string. This string won't be quoted, so would be suitable
240
	 * for appending to other quoted strings.
241
	 *
242
	 * @param mixed $value Value to be prepared for database query
243
	 * @return string Prepared string
244
	 */
245
	public function escapeString($value) {
246
		return $this->connector->escapeString($value);
247
	}
248
249
	/**
250
	 * Wrap a string into DB-specific quotes.
251
	 *
252
	 * @param mixed $value Value to be prepared for database query
253
	 * @return string Prepared string
254
	 */
255
	public function quoteString($value) {
256
		return $this->connector->quoteString($value);
257
	}
258
259
	/**
260
	 * Escapes an identifier (table / database name). Typically the value
261
	 * is simply double quoted. Don't pass in already escaped identifiers in,
262
	 * as this will double escape the value!
263
	 *
264
	 * @param string|array $value The identifier to escape or list of split components
265
	 * @param string $separator Splitter for each component
266
	 * @return string
267
	 */
268
	public function escapeIdentifier($value, $separator = '.') {
269
		// Split string into components
270
		if(!is_array($value)) {
271
			$value = explode($separator, $value);
272
		}
273
274
		// Implode quoted column
275
		return '"' . implode('"'.$separator.'"', $value) . '"';
276
	}
277
278
	/**
279
	 * Escapes unquoted columns keys in an associative array
280
	 *
281
	 * @param array $fieldValues
282
	 * @return array List of field values with the keys as escaped column names
283
	 */
284
	protected function escapeColumnKeys($fieldValues) {
285
		$out = array();
286
		foreach($fieldValues as $field => $value) {
287
			$out[$this->escapeIdentifier($field)] = $value;
288
		}
289
		return $out;
290
	}
291
292
	/**
293
	 * Execute a complex manipulation on the database.
294
	 * A manipulation is an array of insert / or update sequences.  The keys of the array are table names,
295
	 * and the values are map containing 'command' and 'fields'.  Command should be 'insert' or 'update',
296
	 * and fields should be a map of field names to field values, NOT including quotes.
297
	 *
298
	 * The field values could also be in paramaterised format, such as
299
	 * array('MAX(?,?)' => array(42, 69)), allowing the use of raw SQL values such as
300
	 * array('NOW()' => array()).
301
	 *
302
	 * @see SQLWriteExpression::addAssignments for syntax examples
303
	 *
304
	 * @param array $manipulation
305
	 */
306
	public function manipulate($manipulation) {
307
		if (empty($manipulation)) return;
308
309
		foreach ($manipulation as $table => $writeInfo) {
310
			if(empty($writeInfo['fields'])) continue;
311
			// Note: keys of $fieldValues are not escaped
312
			$fieldValues = $writeInfo['fields'];
313
314
			// Switch command type
315
			switch ($writeInfo['command']) {
316
				case "update":
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

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

Loading history...
317
318
					// Build update
319
					$query = new SQLUpdate("\"$table\"", $this->escapeColumnKeys($fieldValues));
320
321
					// Set best condition to use
322
					if(!empty($writeInfo['where'])) {
323
						$query->addWhere($writeInfo['where']);
324
					} elseif(!empty($writeInfo['id'])) {
325
						$query->addWhere(array('"ID"' => $writeInfo['id']));
326
					}
327
328
					// Test to see if this update query shouldn't, in fact, be an insert
329
					if($query->toSelect()->count()) {
330
						$query->execute();
331
						break;
332
					}
333
					// ...if not, we'll skip on to the insert code
334
335
				case "insert":
336
					// Ensure that the ID clause is given if possible
337
					if (!isset($fieldValues['ID']) && isset($writeInfo['id'])) {
338
						$fieldValues['ID'] = $writeInfo['id'];
339
					}
340
341
					// Build insert
342
					$query = new SQLInsert("\"$table\"", $this->escapeColumnKeys($fieldValues));
343
344
					$query->execute();
345
					break;
346
347
				default:
348
					user_error("SS_Database::manipulate() Can't recognise command '{$writeInfo['command']}'",
349
								E_USER_ERROR);
350
			}
351
		}
352
	}
353
354
	/**
355
	 * Enable supression of database messages.
356
	 */
357
	public function quiet() {
358
		$this->schemaManager->quiet();
359
	}
360
361
	/**
362
	 * Clear all data out of the database
363
	 */
364
	public function clearAllData() {
365
		$tables = $this->getSchemaManager()->tableList();
366
		foreach ($tables as $table) {
367
			$this->clearTable($table);
368
		}
369
	}
370
371
	/**
372
	 * Clear all data in a given table
373
	 *
374
	 * @param string $table Name of table
375
	 */
376
	public function clearTable($table) {
377
		$this->query("TRUNCATE \"$table\"");
378
	}
379
380
	/**
381
	 * Generates a WHERE clause for null comparison check
382
	 *
383
	 * @param string $field Quoted field name
384
	 * @param bool $isNull Whether to check for NULL or NOT NULL
385
	 * @return string Non-parameterised null comparison clause
386
	 */
387
	public function nullCheckClause($field, $isNull) {
388
		$clause = $isNull
389
			? "%s IS NULL"
390
			: "%s IS NOT NULL";
391
		return sprintf($clause, $field);
392
	}
393
394
	/**
395
	 * Generate a WHERE clause for text matching.
396
	 *
397
	 * @param String $field Quoted field name
398
	 * @param String $value Escaped search. Can include percentage wildcards.
399
	 * Ignored if $parameterised is true.
400
	 * @param boolean $exact Exact matches or wildcard support.
401
	 * @param boolean $negate Negate the clause.
402
	 * @param boolean $caseSensitive Enforce case sensitivity if TRUE or FALSE.
403
	 * Fallback to default collation if set to NULL.
404
	 * @param boolean $parameterised Insert the ? placeholder rather than the
405
	 * given value. If this is true then $value is ignored.
406
	 * @return String SQL
407
	 */
408
	abstract public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null,
409
											$parameterised = false);
410
411
	/**
412
	 * function to return an SQL datetime expression that can be used with the adapter in use
413
	 * used for querying a datetime in a certain format
414
	 *
415
	 * @param string $date to be formated, can be either 'now', literal datetime like '1973-10-14 10:30:00' or
416
	 *                     field name, e.g. '"SiteTree"."Created"'
417
	 * @param string $format to be used, supported specifiers:
418
	 * %Y = Year (four digits)
419
	 * %m = Month (01..12)
420
	 * %d = Day (01..31)
421
	 * %H = Hour (00..23)
422
	 * %i = Minutes (00..59)
423
	 * %s = Seconds (00..59)
424
	 * %U = unix timestamp, can only be used on it's own
425
	 * @return string SQL datetime expression to query for a formatted datetime
426
	 */
427
	abstract public function formattedDatetimeClause($date, $format);
428
429
	/**
430
	 * function to return an SQL datetime expression that can be used with the adapter in use
431
	 * used for querying a datetime addition
432
	 *
433
	 * @param string $date, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name,
0 ignored issues
show
Bug introduced by
There is no parameter named $date,. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
434
	 *                      e.g. '"SiteTree"."Created"'
435
	 * @param string $interval to be added, use the format [sign][integer] [qualifier], e.g. -1 Day, +15 minutes,
436
	 *                         +1 YEAR
437
	 * supported qualifiers:
438
	 * - years
439
	 * - months
440
	 * - days
441
	 * - hours
442
	 * - minutes
443
	 * - seconds
444
	 * This includes the singular forms as well
445
	 * @return string SQL datetime expression to query for a datetime (YYYY-MM-DD hh:mm:ss) which is the result of
446
	 *                the addition
447
	 */
448
	abstract public function datetimeIntervalClause($date, $interval);
449
450
	/**
451
	 * function to return an SQL datetime expression that can be used with the adapter in use
452
	 * used for querying a datetime substraction
453
	 *
454
	 * @param string $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name
0 ignored issues
show
Documentation introduced by
There is no parameter named $date1,. Did you maybe mean $date1?

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

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

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

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

Loading history...
455
	 *                       e.g. '"SiteTree"."Created"'
456
	 * @param string $date2 to be substracted of $date1, can be either 'now', literal datetime
457
	 *                      like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
458
	 * @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which
459
	 *                is the result of the substraction
460
	 */
461
	abstract public function datetimeDifferenceClause($date1, $date2);
462
463
	/**
464
	 * Returns true if this database supports collations
465
	 *
466
	 * @return boolean
467
	 */
468
	abstract public function supportsCollations();
469
470
	/**
471
	 * Can the database override timezone as a connection setting,
472
	 * or does it use the system timezone exclusively?
473
	 *
474
	 * @return Boolean
475
	 */
476
	abstract public function supportsTimezoneOverride();
477
478
	/**
479
	 * Query for the version of the currently connected database
480
	 * @return string Version of this database
481
	 */
482
	public function getVersion() {
483
		return $this->connector->getVersion();
484
	}
485
486
	/**
487
	 * Get the database server type (e.g. mysql, postgresql).
488
	 * This value is passed to the connector as the 'driver' argument when
489
	 * initiating a database connection
490
	 *
491
	 * @return string
492
	 */
493
	abstract public function getDatabaseServer();
494
495
	/**
496
	 * Return the number of rows affected by the previous operation.
497
	 * @return int
498
	 */
499
	public function affectedRows() {
500
		return $this->connector->affectedRows();
501
	}
502
503
	/**
504
	 * The core search engine, used by this class and its subclasses to do fun stuff.
505
	 * Searches both SiteTree and File.
506
	 *
507
	 * @param array $classesToSearch List of classes to search
508
	 * @param string $keywords Keywords as a string.
509
	 * @param integer $start Item to start returning results from
510
	 * @param integer $pageLength Number of items per page
511
	 * @param string $sortBy Sort order expression
512
	 * @param string $extraFilter Additional filter
513
	 * @param boolean $booleanSearch Flag for boolean search mode
514
	 * @param string $alternativeFileFilter
515
	 * @param boolean $invertedMatch
516
	 * @return PaginatedList Search results
517
	 */
518
	abstract public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC",
519
		$extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false);
520
521
	/**
522
	 * Determines if this database supports transactions
523
	 *
524
	 * @return boolean Flag indicating support for transactions
525
	 */
526
	abstract public function supportsTransactions();
527
528
	/**
529
	 * Invoke $callback within a transaction
530
	 *
531
	 * @param callable $callback Callback to run
532
	 * @param callable $errorCallback Optional callback to run after rolling back transaction.
533
	 * @param bool|string $transactionMode Optional transaction mode to use
534
	 * @param bool $errorIfTransactionsUnsupported If true, this method will fail if transactions are unsupported.
535
	 * Otherwise, the $callback will potentially be invoked outside of a transaction.
536
	 * @throws Exception
537
	 */
538
	public function withTransaction(
539
		$callback, $errorCallback = null, $transactionMode = false, $errorIfTransactionsUnsupported = false
540
	) {
541
		$supported = $this->supportsTransactions();
542
		if(!$supported && $errorIfTransactionsUnsupported) {
543
			throw new BadMethodCallException("Transactions not supported by this database.");
544
		}
545
		if($supported) {
546
			$this->transactionStart($transactionMode);
547
		}
548
		try {
549
			call_user_func($callback);
550
		} catch (Exception $ex) {
551
			if($supported) {
552
				$this->transactionRollback();
553
			}
554
			if($errorCallback) {
555
				call_user_func($errorCallback);
556
			}
557
			throw $ex;
558
		}
559
		if($supported) {
560
			$this->transactionEnd();
561
		}
562
	}
563
564
	/*
565
	 * Determines if the current database connection supports a given list of extensions
566
	 *
567
	 * @param array $extensions List of extensions to check for support of. The key of this array
568
	 * will be an extension name, and the value the configuration for that extension. This
569
	 * could be one of partitions, tablespaces, or clustering
570
	 * @return boolean Flag indicating support for all of the above
571
	 * @todo Write test cases
572
	 */
573
	public function supportsExtensions($extensions) {
0 ignored issues
show
Unused Code introduced by
The parameter $extensions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
574
		return false;
575
	}
576
577
	/**
578
	 * Start a prepared transaction
579
	 * See http://developer.postgresql.org/pgdocs/postgres/sql-set-transaction.html for details on
580
	 * transaction isolation options
581
	 *
582
	 * @param string|boolean $transactionMode Transaction mode, or false to ignore
583
	 * @param string|boolean $sessionCharacteristics Session characteristics, or false to ignore
584
	 */
585
	abstract public function transactionStart($transactionMode = false, $sessionCharacteristics = false);
586
587
	/**
588
	 * Create a savepoint that you can jump back to if you encounter problems
589
	 *
590
	 * @param string $savepoint Name of savepoint
591
	 */
592
	abstract public function transactionSavepoint($savepoint);
593
594
	/**
595
	 * Rollback or revert to a savepoint if your queries encounter problems
596
	 * If you encounter a problem at any point during a transaction, you may
597
	 * need to rollback that particular query, or return to a savepoint
598
	 *
599
	 * @param string|boolean $savepoint Name of savepoint, or leave empty to rollback
600
	 * to last savepoint
601
	 */
602
	abstract public function transactionRollback($savepoint = false);
603
604
	/**
605
	 * Commit everything inside this transaction so far
606
	 *
607
	 * @param boolean $chain
608
	 */
609
	abstract public function transactionEnd($chain = false);
610
611
	/**
612
	 * Determines if the used database supports application-level locks,
613
	 * which is different from table- or row-level locking.
614
	 * See {@link getLock()} for details.
615
	 *
616
	 * @return boolean Flag indicating that locking is available
617
	 */
618
	public function supportsLocks() {
619
		return false;
620
	}
621
622
	/**
623
	 * Returns if the lock is available.
624
	 * See {@link supportsLocks()} to check if locking is generally supported.
625
	 *
626
	 * @param string $name Name of the lock
627
	 * @return boolean
628
	 */
629
	public function canLock($name) {
630
		return false;
631
	}
632
633
	/**
634
	 * Sets an application-level lock so that no two processes can run at the same time,
635
	 * also called a "cooperative advisory lock".
636
	 *
637
	 * Return FALSE if acquiring the lock fails; otherwise return TRUE, if lock was acquired successfully.
638
	 * Lock is automatically released if connection to the database is broken (either normally or abnormally),
639
	 * making it less prone to deadlocks than session- or file-based locks.
640
	 * Should be accompanied by a {@link releaseLock()} call after the logic requiring the lock has completed.
641
	 * Can be called multiple times, in which case locks "stack" (PostgreSQL, SQL Server),
642
	 * or auto-releases the previous lock (MySQL).
643
	 *
644
	 * Note that this might trigger the database to wait for the lock to be released, delaying further execution.
645
	 *
646
	 * @param string $name Name of lock
647
	 * @param integer $timeout Timeout in seconds
648
	 * @return boolean
649
	 */
650
	public function getLock($name, $timeout = 5) {
651
		return false;
652
	}
653
654
	/**
655
	 * Remove an application-level lock file to allow another process to run
656
	 * (if the execution aborts (e.g. due to an error) all locks are automatically released).
657
	 *
658
	 * @param string $name Name of the lock
659
	 * @return boolean Flag indicating whether the lock was successfully released
660
	 */
661
	public function releaseLock($name) {
662
		return false;
663
	}
664
665
	/**
666
	 * Instruct the database to generate a live connection
667
	 *
668
	 * @param array $parameters An map of parameters, which should include:
669
	 *  - server: The server, eg, localhost
670
	 *  - username: The username to log on with
671
	 *  - password: The password to log on with
672
	 *  - database: The database to connect to
673
	 *  - charset: The character set to use. Defaults to utf8
674
	 *  - timezone: (optional) The timezone offset. For example: +12:00, "Pacific/Auckland", or "SYSTEM"
675
	 *  - driver: (optional) Driver name
676
	 */
677
	public function connect($parameters) {
678
		// Ensure that driver is available (required by PDO)
679
		if(empty($parameters['driver'])) {
680
			$parameters['driver'] = $this->getDatabaseServer();
681
		}
682
683
		// Notify connector of parameters
684
		$this->connector->connect($parameters);
685
686
		// SS_Database subclass maintains responsibility for selecting database
687
		// once connected in order to correctly handle schema queries about
688
		// existence of database, error handling at the correct level, etc
689
		if (!empty($parameters['database'])) {
690
			$this->selectDatabase($parameters['database'], false, false);
691
		}
692
	}
693
694
	/**
695
	 * Determine if the database with the specified name exists
696
	 *
697
	 * @param string $name Name of the database to check for
698
	 * @return boolean Flag indicating whether this database exists
699
	 */
700
	public function databaseExists($name) {
701
		return $this->schemaManager->databaseExists($name);
702
	}
703
704
	/**
705
	 * Retrieves the list of all databases the user has access to
706
	 *
707
	 * @return array List of database names
708
	 */
709
	public function databaseList() {
710
		return $this->schemaManager->databaseList();
711
	}
712
713
	/**
714
	 * Change the connection to the specified database, optionally creating the
715
	 * database if it doesn't exist in the current schema.
716
	 *
717
	 * @param string $name Name of the database
718
	 * @param boolean $create Flag indicating whether the database should be created
719
	 * if it doesn't exist. If $create is false and the database doesn't exist
720
	 * then an error will be raised
721
	 * @param int|boolean $errorLevel The level of error reporting to enable for the query, or false if no error
722
	 * should be raised
723
	 * @return boolean Flag indicating success
724
	 */
725
	public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR) {
726
		// In case our live environment is locked down, we can bypass a SHOW DATABASE check
727
		$canConnect = Config::inst()->get(get_class($this), 'optimistic_connect')
728
			|| $this->schemaManager->databaseExists($name);
729
		if($canConnect) {
730
			return $this->connector->selectDatabase($name);
731
		}
732
733
		// Check DB creation permisson
734
		if (!$create) {
735
			if ($errorLevel !== false) {
736
				user_error("Attempted to connect to non-existing database \"$name\"", $errorLevel);
737
			}
738
			// Unselect database
739
			$this->connector->unloadDatabase();
740
			return false;
741
		}
742
		$this->schemaManager->createDatabase($name);
743
		return $this->connector->selectDatabase($name);
744
	}
745
746
	/**
747
	 * Drop the database that this object is currently connected to.
748
	 * Use with caution.
749
	 */
750
	public function dropSelectedDatabase() {
751
		$databaseName = $this->connector->getSelectedDatabase();
752
		if ($databaseName) {
753
			$this->connector->unloadDatabase();
754
			$this->schemaManager->dropDatabase($databaseName);
755
		}
756
	}
757
758
	/**
759
	 * Returns the name of the currently selected database
760
	 *
761
	 * @return string|null Name of the selected database, or null if none selected
762
	 */
763
	public function getSelectedDatabase() {
764
		return $this->connector->getSelectedDatabase();
765
	}
766
767
	/**
768
	 * Return SQL expression used to represent the current date/time
769
	 *
770
	 * @return string Expression for the current date/time
771
	 */
772
	abstract public function now();
773
774
	/**
775
	 * Returns the database-specific version of the random() function
776
	 *
777
	 * @return string Expression for a random value
778
	 */
779
	abstract public function random();
780
781
	/**
782
	 * @deprecated since version 4.0 Use DB::get_schema()->dbDataType($type) instead
783
	 */
784
	public function dbDataType($type){
785
		Deprecation::notice('4.0', 'Use DB::get_schema()->dbDataType($type) instead');
786
		return $this->getSchemaManager()->dbDataType($type);
787
	}
788
789
	/**
790
	 * @deprecated since version 4.0 Use selectDatabase('dbname', true) instead
791
	 */
792
	public function createDatabase() {
793
		Deprecation::notice('4.0', 'Use selectDatabase(\'dbname\',true) instead');
794
		$database = $this->connector->getSelectedDatabase();
795
		$this->selectDatabase($database, true);
796
		return $this->isActive();
797
	}
798
799
	/**
800
	 * @deprecated since version 4.0 SS_Database::getConnect was never implemented and is obsolete
801
	 */
802
	public function getConnect($parameters) {
0 ignored issues
show
Unused Code introduced by
The parameter $parameters is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
803
		Deprecation::notice('4.0', 'SS_Database::getConnect was never implemented and is obsolete');
804
	}
805
806
	/**
807
	 * @deprecated since version 4.0 Use Convert::raw2sql($string, true) instead
808
	 */
809
	public function prepStringForDB($string) {
810
		Deprecation::notice('4.0', 'Use Convert::raw2sql($string, true) instead');
811
		return $this->quoteString($string);
812
	}
813
814
	/**
815
	 * @deprecated since version 4.0 Use dropSelectedDatabase instead
816
	 */
817
	public function dropDatabase() {
818
		Deprecation::notice('4.0', 'Use dropSelectedDatabase instead');
819
		$this->dropSelectedDatabase();
820
	}
821
822
	/**
823
	 * @deprecated since version 4.0 Use databaseList instead
824
	 */
825
	public function allDatabaseNames() {
826
		Deprecation::notice('4.0', 'Use databaseList instead');
827
		return $this->databaseList();
828
	}
829
830
	/**
831
	 * @deprecated since version 4.0 Use DB::create_table instead
832
	 */
833
	public function createTable($table, $fields = null, $indexes = null, $options = null, $advancedOptions = null) {
834
		Deprecation::notice('4.0', 'Use DB::create_table instead');
835
		return $this->getSchemaManager()->createTable($table, $fields, $indexes, $options, $advancedOptions);
836
	}
837
838
	/**
839
	 * @deprecated since version 4.0 Use DB::get_schema()->alterTable() instead
840
	 */
841
	public function alterTable($table, $newFields = null, $newIndexes = null,
842
		$alteredFields = null, $alteredIndexes = null, $alteredOptions = null,
843
		$advancedOptions = null
844
	) {
845
		Deprecation::notice('4.0', 'Use DB::get_schema()->alterTable() instead');
846
		return $this->getSchemaManager()->alterTable(
847
			$table, $newFields, $newIndexes, $alteredFields,
848
			$alteredIndexes, $alteredOptions, $advancedOptions
849
		);
850
	}
851
852
	/**
853
	 * @deprecated since version 4.0 Use DB::get_schema()->renameTable() instead
854
	 */
855
	public function renameTable($oldTableName, $newTableName) {
856
		Deprecation::notice('4.0', 'Use DB::get_schema()->renameTable() instead');
857
		$this->getSchemaManager()->renameTable($oldTableName, $newTableName);
858
	}
859
860
	/**
861
	 * @deprecated since version 4.0 Use DB::create_field() instead
862
	 */
863
	public function createField($table, $field, $spec) {
864
		Deprecation::notice('4.0', 'Use DB::create_field() instead');
865
		$this->getSchemaManager()->createField($table, $field, $spec);
866
	}
867
868
	/**
869
	 * @deprecated since version 4.0 Use DB::get_schema()->renameField() instead
870
	 */
871
	public function renameField($tableName, $oldName, $newName) {
872
		Deprecation::notice('4.0', 'Use DB::get_schema()->renameField() instead');
873
		$this->getSchemaManager()->renameField($tableName, $oldName, $newName);
874
	}
875
876
	/**
877
	 * @deprecated since version 4.0 Use getSelectedDatabase instead
878
	 */
879
	public function currentDatabase() {
880
		Deprecation::notice('4.0', 'Use getSelectedDatabase instead');
881
		return $this->getSelectedDatabase();
882
	}
883
884
	/**
885
	 * @deprecated since version 4.0 Use DB::field_list instead
886
	 */
887
	public function fieldList($table) {
888
		Deprecation::notice('4.0', 'Use DB::field_list instead');
889
		return $this->getSchemaManager()->fieldList($table);
890
	}
891
892
	/**
893
	 * @deprecated since version 4.0 Use DB::table_list instead
894
	 */
895
	public function tableList() {
896
		Deprecation::notice('4.0', 'Use DB::table_list instead');
897
		return $this->getSchemaManager()->tableList();
898
	}
899
900
	/**
901
	 * @deprecated since version 4.0 Use DB::get_schema()->hasTable() instead
902
	 */
903
	public function hasTable($tableName) {
904
		Deprecation::notice('4.0', 'Use DB::get_schema()->hasTable() instead');
905
		return $this->getSchemaManager()->hasTable($tableName);
906
	}
907
908
	/**
909
	 * @deprecated since version 4.0 Use DB::get_schema()->enumValuesForField() instead
910
	 */
911
	public function enumValuesForField($tableName, $fieldName) {
912
		Deprecation::notice('4.0', 'Use DB::get_schema()->enumValuesForField() instead');
913
		return $this->getSchemaManager()->enumValuesForField($tableName, $fieldName);
914
	}
915
916
	/**
917
	 * @deprecated since version 4.0 Use Convert::raw2sql instead
918
	 */
919
	public function addslashes($value) {
920
		Deprecation::notice('4.0', 'Use Convert::raw2sql instead');
921
		return $this->escapeString($value);
922
	}
923
924
	/**
925
	 * @deprecated since version 3.2 Use DB::get_schema()->schemaUpdate with a callback instead
926
	 */
927
	public function beginSchemaUpdate() {
928
		Deprecation::notice('3.2', 'Use DB::get_schema()->schemaUpdate with a callback instead');
929
		// Unable to recover so throw appropriate exception
930
		throw new BadMethodCallException('Use DB::get_schema()->schemaUpdate with a callback instead');
931
	}
932
933
	/**
934
	 * @deprecated since version 3.2 Use DB::get_schema()->schemaUpdate with a callback instead
935
	 */
936
	public function endSchemaUpdate() {
937
		Deprecation::notice('3.2', 'Use DB::get_schema()->schemaUpdate with a callback instead');
938
		// Unable to recover so throw appropriate exception
939
		throw new BadMethodCallException('Use DB::get_schema()->schemaUpdate with a callback instead');
940
	}
941
942
	/**
943
	 * @deprecated since version 4.0 Use DB::get_schema()->cancelSchemaUpdate instead
944
	 */
945
	public function cancelSchemaUpdate() {
946
		Deprecation::notice('4.0', 'Use DB::get_schema()->cancelSchemaUpdate instead');
947
		$this->getSchemaManager()->cancelSchemaUpdate();
948
	}
949
950
	/**
951
	 * @deprecated since version 4.0 Use DB::get_schema()->isSchemaUpdating() instead
952
	 */
953
	public function isSchemaUpdating() {
954
		Deprecation::notice('4.0', 'Use DB::get_schema()->isSchemaUpdating() instead');
955
		return $this->getSchemaManager()->isSchemaUpdating();
956
	}
957
958
	/**
959
	 * @deprecated since version 4.0 Use DB::get_schema()->doesSchemaNeedUpdating() instead
960
	 */
961
	public function doesSchemaNeedUpdating() {
962
		Deprecation::notice('4.0', 'Use DB::get_schema()->doesSchemaNeedUpdating() instead');
963
		return $this->getSchemaManager()->doesSchemaNeedUpdating();
964
	}
965
966
	/**
967
	 * @deprecated since version 4.0 Use DB::get_schema()->transCreateTable() instead
968
	 */
969
	public function transCreateTable($table, $options = null, $advanced_options = null) {
970
		Deprecation::notice('4.0', 'Use DB::get_schema()->transCreateTable() instead');
971
		$this->getSchemaManager()->transCreateTable($table, $options, $advanced_options);
972
	}
973
974
	/**
975
	 * @deprecated since version 4.0 Use DB::get_schema()->transAlterTable() instead
976
	 */
977
	public function transAlterTable($table, $options, $advanced_options) {
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $advanced_options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
978
		Deprecation::notice('4.0', 'Use DB::get_schema()->transAlterTable() instead');
979
		$this->getSchemaManager()->transAlterTable($table, $index, $schema);
0 ignored issues
show
Bug introduced by
The variable $index does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $schema does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
980
	}
981
982
	/**
983
	 * @deprecated since version 4.0 Use DB::get_schema()->transCreateField() instead
984
	 */
985
	public function transCreateField($table, $field, $schema) {
0 ignored issues
show
Unused Code introduced by
The parameter $field is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
986
		Deprecation::notice('4.0', 'Use DB::get_schema()->transCreateField() instead');
987
		$this->getSchemaManager()->transCreateField($table, $index, $schema);
0 ignored issues
show
Bug introduced by
The variable $index does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
988
	}
989
990
	/**
991
	 * @deprecated since version 4.0 Use DB::get_schema()->transCreateIndex() instead
992
	 */
993
	public function transCreateIndex($table, $index, $schema) {
994
		Deprecation::notice('4.0', 'Use DB::get_schema()->transCreateIndex() instead');
995
		$this->getSchemaManager()->transCreateIndex($table, $index, $schema);
996
	}
997
998
	/**
999
	 * @deprecated since version 4.0 Use DB::get_schema()->transAlterField() instead
1000
	 */
1001
	public function transAlterField($table, $field, $schema) {
0 ignored issues
show
Unused Code introduced by
The parameter $field is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1002
		Deprecation::notice('4.0', 'Use DB::get_schema()->transAlterField() instead');
1003
		$this->getSchemaManager()->transAlterField($table, $index, $schema);
0 ignored issues
show
Bug introduced by
The variable $index does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1004
	}
1005
1006
	/**
1007
	 * @deprecated since version 4.0 Use DB::get_schema()->transAlterIndex() instead
1008
	 */
1009
	public function transAlterIndex($table, $index, $schema) {
1010
		Deprecation::notice('4.0', 'Use DB::get_schema()->transAlterIndex() instead');
1011
		$this->getSchemaManager()->transAlterIndex($table, $index, $schema);
1012
	}
1013
1014
	/**
1015
	 * @deprecated since version 4.0 Use DB::require_table() instead
1016
	 */
1017
	public function requireTable($table, $fieldSchema = null, $indexSchema = null,
1018
		$hasAutoIncPK = true, $options = array(), $extensions = false
1019
	) {
1020
		Deprecation::notice('4.0', 'Use DB::require_table() instead');
1021
		return $this->getSchemaManager()->requireTable(
1022
			$table, $fieldSchema, $indexSchema, $hasAutoIncPK, $options, $extensions
1023
		);
1024
	}
1025
1026
	/**
1027
	 * @deprecated since version 4.0 Use DB::dont_require_table() instead
1028
	 */
1029
	public function dontRequireTable($table) {
1030
		Deprecation::notice('4.0', 'Use DB::dont_require_table() instead');
1031
		$this->getSchemaManager()->dontRequireTable($table);
1032
	}
1033
1034
	/**
1035
	 * @deprecated since version 4.0 Use DB::require_index() instead
1036
	 */
1037
	public function requireIndex($table, $index, $spec) {
1038
		Deprecation::notice('4.0', 'Use DB::require_index() instead');
1039
		$this->getSchemaManager()->requireIndex($table, $index, $spec);
1040
	}
1041
1042
	/**
1043
	 * @deprecated since version 4.0 Use DB::get_schema()->hasField() instead
1044
	 */
1045
	public function hasField($tableName, $fieldName) {
1046
		Deprecation::notice('4.0', 'Use DB::get_schema()->hasField() instead');
1047
		return $this->getSchemaManager()->hasField($tableName, $fieldName);
1048
	}
1049
1050
	/**
1051
	 * @deprecated since version 4.0 Use DB::require_field() instead
1052
	 */
1053
	public function requireField($table, $field, $spec) {
1054
		Deprecation::notice('4.0', 'Use DB::require_field() instead');
1055
		$this->getSchemaManager()->requireField($table, $field, $spec);
1056
	}
1057
1058
	/**
1059
	 * @deprecated since version 4.0 Use DB::dont_require_field() instead
1060
	 */
1061
	public function dontRequireField($table, $fieldName) {
1062
		Deprecation::notice('4.0', 'Use DB::dont_require_field() instead');
1063
		$this->getSchemaManager()->dontRequireField($table, $fieldName);
1064
	}
1065
1066
	/**
1067
	 * @deprecated since version 4.0 Use DB::build_sql() instead
1068
	 */
1069
	public function sqlQueryToString(SQLExpression $query, &$parameters = array()) {
1070
		Deprecation::notice('4.0', 'Use DB::build_sql() instead');
1071
		return $this->getQueryBuilder()->buildSQL($query, $parameters);
1072
	}
1073
}
1074