Completed
Push — master ( f5d71d...bfd9cb )
by Sam
12:52
created

Database::getDatabaseServer()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
nc 1
dl 0
loc 1
1
<?php
2
3
namespace SilverStripe\ORM\Connect;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\Dev\Debug;
8
use SilverStripe\ORM\PaginatedList;
9
use SilverStripe\ORM\Queries\SQLUpdate;
10
use SilverStripe\ORM\Queries\SQLInsert;
11
use BadMethodCallException;
12
use Exception;
13
14
/**
15
 * Abstract database connectivity class.
16
 * Sub-classes of this implement the actual database connection libraries
17
 */
18
abstract class Database {
19
20
	/**
21
	 * Database connector object
22
	 *
23
	 * @var DBConnector
24
	 */
25
	protected $connector = null;
26
27
	/**
28
	 * In cases where your environment does not have 'SHOW DATABASES' permission,
29
	 * you can set this to true. Then selectDatabase() will always connect without
30
	 * doing databaseExists() check.
31
	 *
32
	 * @var bool
33
	 */
34
	private static $optimistic_connect = false;
35
36
	/**
37
	 * Amount of queries executed, for debugging purposes.
38
	 *
39
	 * @var int
40
	 */
41
	protected $queryCount = 0;
42
43
	/**
44
	 * Get the current connector
45
	 *
46
	 * @return DBConnector
47
	 */
48
	public function getConnector() {
49
		return $this->connector;
50
	}
51
52
	/**
53
	 * Injector injection point for connector dependency
54
	 *
55
	 * @param DBConnector $connector
56
	 */
57
	public function setConnector(DBConnector $connector) {
58
		$this->connector = $connector;
59
	}
60
61
	/**
62
	 * Database schema manager object
63
	 *
64
	 * @var DBSchemaManager
65
	 */
66
	protected $schemaManager = null;
67
68
	/**
69
	 * Returns the current schema manager
70
	 *
71
	 * @return DBSchemaManager
72
	 */
73
	public function getSchemaManager() {
74
		return $this->schemaManager;
75
	}
76
77
	/**
78
	 * Injector injection point for schema manager
79
	 *
80
	 * @param DBSchemaManager $schemaManager
81
	 */
82
	public function setSchemaManager(DBSchemaManager $schemaManager) {
83
		$this->schemaManager = $schemaManager;
84
85
		if ($this->schemaManager) {
86
			$this->schemaManager->setDatabase($this);
87
		}
88
	}
89
90
	/**
91
	 * Query builder object
92
	 *
93
	 * @var DBQueryBuilder
94
	 */
95
	protected $queryBuilder = null;
96
97
	/**
98
	 * Returns the current query builder
99
	 *
100
	 * @return DBQueryBuilder
101
	 */
102
	public function getQueryBuilder() {
103
		return $this->queryBuilder;
104
	}
105
106
	/**
107
	 * Injector injection point for schema manager
108
	 *
109
	 * @param DBQueryBuilder $queryBuilder
110
	 */
111
	public function setQueryBuilder(DBQueryBuilder $queryBuilder) {
112
		$this->queryBuilder = $queryBuilder;
113
	}
114
115
	/**
116
	 * Execute the given SQL query.
117
	 *
118
	 * @param string $sql The SQL query to execute
119
	 * @param int $errorLevel The level of error reporting to enable for the query
120
	 * @return Query
121
	 */
122
	public function query($sql, $errorLevel = E_USER_ERROR) {
123
		// Check if we should only preview this query
124
		if ($this->previewWrite($sql)) {
125
			return null;
126
		}
127
128
		// Benchmark query
129
		$connector = $this->connector;
130
		return $this->benchmarkQuery(
131
			$sql,
132
			function($sql) use($connector, $errorLevel) {
133
				return $connector->query($sql, $errorLevel);
134
			}
135
		);
136
	}
137
138
139
	/**
140
	 * Execute the given SQL parameterised query with the specified arguments
141
	 *
142
	 * @param string $sql The SQL query to execute. The ? character will denote parameters.
143
	 * @param array $parameters An ordered list of arguments.
144
	 * @param int $errorLevel The level of error reporting to enable for the query
145
	 * @return Query
146
	 */
147
	public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
148
		// Check if we should only preview this query
149
		if ($this->previewWrite($sql)) {
150
			return null;
151
		}
152
153
		// Benchmark query
154
		$connector = $this->connector;
155
		return $this->benchmarkQuery(
156
			$sql,
157
			function($sql) use($connector, $parameters, $errorLevel) {
158
				return $connector->preparedQuery($sql, $parameters, $errorLevel);
159
			},
160
			$parameters
161
		);
162
	}
163
164
	/**
165
	 * Determines if the query should be previewed, and thus interrupted silently.
166
	 * If so, this function also displays the query via the debuging system.
167
	 * Subclasess should respect the results of this call for each query, and not
168
	 * execute any queries that generate a true response.
169
	 *
170
	 * @param string $sql The query to be executed
171
	 * @return boolean Flag indicating that the query was previewed
172
	 */
173
	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...
174
		// Only preview if previewWrite is set, we are in dev mode, and
175
		// the query is mutable
176
		if (isset($_REQUEST['previewwrite'])
177
			&& Director::isDev()
178
			&& $this->connector->isQueryMutable($sql)
179
		) {
180
			// output preview message
181
			Debug::message("Will execute: $sql");
182
			return true;
183
		} else {
184
			return false;
185
		}
186
	}
187
188
	/**
189
	 * Allows the display and benchmarking of queries as they are being run
190
	 *
191
	 * @param string $sql Query to run, and single parameter to callback
192
	 * @param callable $callback Callback to execute code
193
	 * @param array $parameters Parameters to display
194
	 * @return mixed Result of query
195
	 */
196
	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...
197
		if (isset($_REQUEST['showqueries']) && Director::isDev()) {
198
			$this->queryCount++;
199
			$starttime = microtime(true);
200
			$result = $callback($sql);
201
			$endtime = round(microtime(true) - $starttime, 4);
202
			$message = $sql;
203
			if($parameters) {
204
				$message .= "\nparams: \"" . implode('", "', $parameters) . '"';
205
			}
206
			Debug::message("\n$this->queryCount: {$message}\n{$endtime}s\n", false);
207
208
			return $result;
209
		} else {
210
			return $callback($sql);
211
		}
212
	}
213
214
	/**
215
	 * Get the autogenerated ID from the previous INSERT query.
216
	 *
217
	 * @param string $table The name of the table to get the generated ID for
218
	 * @return integer the most recently generated ID for the specified table
219
	 */
220
	public function getGeneratedID($table) {
221
		return $this->connector->getGeneratedID($table);
222
	}
223
224
	/**
225
	 * Determines if we are connected to a server AND have a valid database
226
	 * selected.
227
	 *
228
	 * @return boolean Flag indicating that a valid database is connected
229
	 */
230
	public function isActive() {
231
		return $this->connector->isActive();
232
	}
233
234
	/**
235
	 * Returns an escaped string. This string won't be quoted, so would be suitable
236
	 * for appending to other quoted strings.
237
	 *
238
	 * @param mixed $value Value to be prepared for database query
239
	 * @return string Prepared string
240
	 */
241
	public function escapeString($value) {
242
		return $this->connector->escapeString($value);
243
	}
244
245
	/**
246
	 * Wrap a string into DB-specific quotes.
247
	 *
248
	 * @param mixed $value Value to be prepared for database query
249
	 * @return string Prepared string
250
	 */
251
	public function quoteString($value) {
252
		return $this->connector->quoteString($value);
253
	}
254
255
	/**
256
	 * Escapes an identifier (table / database name). Typically the value
257
	 * is simply double quoted. Don't pass in already escaped identifiers in,
258
	 * as this will double escape the value!
259
	 *
260
	 * @param string|array $value The identifier to escape or list of split components
261
	 * @param string $separator Splitter for each component
262
	 * @return string
263
	 */
264
	public function escapeIdentifier($value, $separator = '.') {
265
		// Split string into components
266
		if(!is_array($value)) {
267
			$value = explode($separator, $value);
268
		}
269
270
		// Implode quoted column
271
		return '"' . implode('"'.$separator.'"', $value) . '"';
272
	}
273
274
	/**
275
	 * Escapes unquoted columns keys in an associative array
276
	 *
277
	 * @param array $fieldValues
278
	 * @return array List of field values with the keys as escaped column names
279
	 */
280
	protected function escapeColumnKeys($fieldValues) {
281
		$out = array();
282
		foreach($fieldValues as $field => $value) {
283
			$out[$this->escapeIdentifier($field)] = $value;
284
		}
285
		return $out;
286
	}
287
288
	/**
289
	 * Execute a complex manipulation on the database.
290
	 * A manipulation is an array of insert / or update sequences.  The keys of the array are table names,
291
	 * and the values are map containing 'command' and 'fields'.  Command should be 'insert' or 'update',
292
	 * and fields should be a map of field names to field values, NOT including quotes.
293
	 *
294
	 * The field values could also be in paramaterised format, such as
295
	 * array('MAX(?,?)' => array(42, 69)), allowing the use of raw SQL values such as
296
	 * array('NOW()' => array()).
297
	 *
298
	 * @see SQLWriteExpression::addAssignments for syntax examples
299
	 *
300
	 * @param array $manipulation
301
	 */
302
	public function manipulate($manipulation) {
303
		if (empty($manipulation)) return;
304
305
		foreach ($manipulation as $table => $writeInfo) {
306
			if(empty($writeInfo['fields'])) continue;
307
			// Note: keys of $fieldValues are not escaped
308
			$fieldValues = $writeInfo['fields'];
309
310
			// Switch command type
311
			switch ($writeInfo['command']) {
312
				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...
313
314
					// Build update
315
					$query = new SQLUpdate("\"$table\"", $this->escapeColumnKeys($fieldValues));
316
317
					// Set best condition to use
318
					if(!empty($writeInfo['where'])) {
319
						$query->addWhere($writeInfo['where']);
320
					} elseif(!empty($writeInfo['id'])) {
321
						$query->addWhere(array('"ID"' => $writeInfo['id']));
322
					}
323
324
					// Test to see if this update query shouldn't, in fact, be an insert
325
					if($query->toSelect()->count()) {
326
						$query->execute();
327
						break;
328
					}
329
					// ...if not, we'll skip on to the insert code
330
331
				case "insert":
332
					// Ensure that the ID clause is given if possible
333
					if (!isset($fieldValues['ID']) && isset($writeInfo['id'])) {
334
						$fieldValues['ID'] = $writeInfo['id'];
335
					}
336
337
					// Build insert
338
					$query = new SQLInsert("\"$table\"", $this->escapeColumnKeys($fieldValues));
339
340
					$query->execute();
341
					break;
342
343
				default:
344
					user_error("SS_Database::manipulate() Can't recognise command '{$writeInfo['command']}'",
345
								E_USER_ERROR);
346
			}
347
		}
348
	}
349
350
	/**
351
	 * Enable supression of database messages.
352
	 */
353
	public function quiet() {
354
		$this->schemaManager->quiet();
355
	}
356
357
	/**
358
	 * Clear all data out of the database
359
	 */
360
	public function clearAllData() {
361
		$tables = $this->getSchemaManager()->tableList();
362
		foreach ($tables as $table) {
363
			$this->clearTable($table);
364
		}
365
	}
366
367
	/**
368
	 * Clear all data in a given table
369
	 *
370
	 * @param string $table Name of table
371
	 */
372
	public function clearTable($table) {
373
		$this->query("TRUNCATE \"$table\"");
374
	}
375
376
	/**
377
	 * Generates a WHERE clause for null comparison check
378
	 *
379
	 * @param string $field Quoted field name
380
	 * @param bool $isNull Whether to check for NULL or NOT NULL
381
	 * @return string Non-parameterised null comparison clause
382
	 */
383
	public function nullCheckClause($field, $isNull) {
384
		$clause = $isNull
385
			? "%s IS NULL"
386
			: "%s IS NOT NULL";
387
		return sprintf($clause, $field);
388
	}
389
390
	/**
391
	 * Generate a WHERE clause for text matching.
392
	 *
393
	 * @param String $field Quoted field name
394
	 * @param String $value Escaped search. Can include percentage wildcards.
395
	 * Ignored if $parameterised is true.
396
	 * @param boolean $exact Exact matches or wildcard support.
397
	 * @param boolean $negate Negate the clause.
398
	 * @param boolean $caseSensitive Enforce case sensitivity if TRUE or FALSE.
399
	 * Fallback to default collation if set to NULL.
400
	 * @param boolean $parameterised Insert the ? placeholder rather than the
401
	 * given value. If this is true then $value is ignored.
402
	 * @return String SQL
403
	 */
404
	abstract public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null,
405
											$parameterised = false);
406
407
	/**
408
	 * function to return an SQL datetime expression that can be used with the adapter in use
409
	 * used for querying a datetime in a certain format
410
	 *
411
	 * @param string $date to be formated, can be either 'now', literal datetime like '1973-10-14 10:30:00' or
412
	 *                     field name, e.g. '"SiteTree"."Created"'
413
	 * @param string $format to be used, supported specifiers:
414
	 * %Y = Year (four digits)
415
	 * %m = Month (01..12)
416
	 * %d = Day (01..31)
417
	 * %H = Hour (00..23)
418
	 * %i = Minutes (00..59)
419
	 * %s = Seconds (00..59)
420
	 * %U = unix timestamp, can only be used on it's own
421
	 * @return string SQL datetime expression to query for a formatted datetime
422
	 */
423
	abstract public function formattedDatetimeClause($date, $format);
424
425
	/**
426
	 * function to return an SQL datetime expression that can be used with the adapter in use
427
	 * used for querying a datetime addition
428
	 *
429
	 * @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...
430
	 *                      e.g. '"SiteTree"."Created"'
431
	 * @param string $interval to be added, use the format [sign][integer] [qualifier], e.g. -1 Day, +15 minutes,
432
	 *                         +1 YEAR
433
	 * supported qualifiers:
434
	 * - years
435
	 * - months
436
	 * - days
437
	 * - hours
438
	 * - minutes
439
	 * - seconds
440
	 * This includes the singular forms as well
441
	 * @return string SQL datetime expression to query for a datetime (YYYY-MM-DD hh:mm:ss) which is the result of
442
	 *                the addition
443
	 */
444
	abstract public function datetimeIntervalClause($date, $interval);
445
446
	/**
447
	 * function to return an SQL datetime expression that can be used with the adapter in use
448
	 * used for querying a datetime substraction
449
	 *
450
	 * @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...
451
	 *                       e.g. '"SiteTree"."Created"'
452
	 * @param string $date2 to be substracted of $date1, can be either 'now', literal datetime
453
	 *                      like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
454
	 * @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which
455
	 *                is the result of the substraction
456
	 */
457
	abstract public function datetimeDifferenceClause($date1, $date2);
458
459
	/**
460
	 * Returns true if this database supports collations
461
	 *
462
	 * @return boolean
463
	 */
464
	abstract public function supportsCollations();
465
466
	/**
467
	 * Can the database override timezone as a connection setting,
468
	 * or does it use the system timezone exclusively?
469
	 *
470
	 * @return Boolean
471
	 */
472
	abstract public function supportsTimezoneOverride();
473
474
	/**
475
	 * Query for the version of the currently connected database
476
	 * @return string Version of this database
477
	 */
478
	public function getVersion() {
479
		return $this->connector->getVersion();
480
	}
481
482
	/**
483
	 * Get the database server type (e.g. mysql, postgresql).
484
	 * This value is passed to the connector as the 'driver' argument when
485
	 * initiating a database connection
486
	 *
487
	 * @return string
488
	 */
489
	abstract public function getDatabaseServer();
490
491
	/**
492
	 * Return the number of rows affected by the previous operation.
493
	 * @return int
494
	 */
495
	public function affectedRows() {
496
		return $this->connector->affectedRows();
497
	}
498
499
	/**
500
	 * The core search engine, used by this class and its subclasses to do fun stuff.
501
	 * Searches both SiteTree and File.
502
	 *
503
	 * @param array $classesToSearch List of classes to search
504
	 * @param string $keywords Keywords as a string.
505
	 * @param integer $start Item to start returning results from
506
	 * @param integer $pageLength Number of items per page
507
	 * @param string $sortBy Sort order expression
508
	 * @param string $extraFilter Additional filter
509
	 * @param boolean $booleanSearch Flag for boolean search mode
510
	 * @param string $alternativeFileFilter
511
	 * @param boolean $invertedMatch
512
	 * @return PaginatedList Search results
513
	 */
514
	abstract public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC",
515
		$extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false);
516
517
	/**
518
	 * Determines if this database supports transactions
519
	 *
520
	 * @return boolean Flag indicating support for transactions
521
	 */
522
	abstract public function supportsTransactions();
523
524
	/**
525
	 * Invoke $callback within a transaction
526
	 *
527
	 * @param callable $callback Callback to run
528
	 * @param callable $errorCallback Optional callback to run after rolling back transaction.
529
	 * @param bool|string $transactionMode Optional transaction mode to use
530
	 * @param bool $errorIfTransactionsUnsupported If true, this method will fail if transactions are unsupported.
531
	 * Otherwise, the $callback will potentially be invoked outside of a transaction.
532
	 * @throws Exception
533
	 */
534
	public function withTransaction(
535
		$callback, $errorCallback = null, $transactionMode = false, $errorIfTransactionsUnsupported = false
536
	) {
537
		$supported = $this->supportsTransactions();
538
		if(!$supported && $errorIfTransactionsUnsupported) {
539
			throw new BadMethodCallException("Transactions not supported by this database.");
540
		}
541
		if($supported) {
542
			$this->transactionStart($transactionMode);
543
		}
544
		try {
545
			call_user_func($callback);
546
		} catch (Exception $ex) {
547
			if($supported) {
548
				$this->transactionRollback();
549
			}
550
			if($errorCallback) {
551
				call_user_func($errorCallback);
552
			}
553
			throw $ex;
554
		}
555
		if($supported) {
556
			$this->transactionEnd();
557
		}
558
	}
559
560
	/*
561
	 * Determines if the current database connection supports a given list of extensions
562
	 *
563
	 * @param array $extensions List of extensions to check for support of. The key of this array
564
	 * will be an extension name, and the value the configuration for that extension. This
565
	 * could be one of partitions, tablespaces, or clustering
566
	 * @return boolean Flag indicating support for all of the above
567
	 * @todo Write test cases
568
	 */
569
	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...
570
		return false;
571
	}
572
573
	/**
574
	 * Start a prepared transaction
575
	 * See http://developer.postgresql.org/pgdocs/postgres/sql-set-transaction.html for details on
576
	 * transaction isolation options
577
	 *
578
	 * @param string|boolean $transactionMode Transaction mode, or false to ignore
579
	 * @param string|boolean $sessionCharacteristics Session characteristics, or false to ignore
580
	 */
581
	abstract public function transactionStart($transactionMode = false, $sessionCharacteristics = false);
582
583
	/**
584
	 * Create a savepoint that you can jump back to if you encounter problems
585
	 *
586
	 * @param string $savepoint Name of savepoint
587
	 */
588
	abstract public function transactionSavepoint($savepoint);
589
590
	/**
591
	 * Rollback or revert to a savepoint if your queries encounter problems
592
	 * If you encounter a problem at any point during a transaction, you may
593
	 * need to rollback that particular query, or return to a savepoint
594
	 *
595
	 * @param string|boolean $savepoint Name of savepoint, or leave empty to rollback
596
	 * to last savepoint
597
	 */
598
	abstract public function transactionRollback($savepoint = false);
599
600
	/**
601
	 * Commit everything inside this transaction so far
602
	 *
603
	 * @param boolean $chain
604
	 */
605
	abstract public function transactionEnd($chain = false);
606
607
	/**
608
	 * Determines if the used database supports application-level locks,
609
	 * which is different from table- or row-level locking.
610
	 * See {@link getLock()} for details.
611
	 *
612
	 * @return boolean Flag indicating that locking is available
613
	 */
614
	public function supportsLocks() {
615
		return false;
616
	}
617
618
	/**
619
	 * Returns if the lock is available.
620
	 * See {@link supportsLocks()} to check if locking is generally supported.
621
	 *
622
	 * @param string $name Name of the lock
623
	 * @return boolean
624
	 */
625
	public function canLock($name) {
626
		return false;
627
	}
628
629
	/**
630
	 * Sets an application-level lock so that no two processes can run at the same time,
631
	 * also called a "cooperative advisory lock".
632
	 *
633
	 * Return FALSE if acquiring the lock fails; otherwise return TRUE, if lock was acquired successfully.
634
	 * Lock is automatically released if connection to the database is broken (either normally or abnormally),
635
	 * making it less prone to deadlocks than session- or file-based locks.
636
	 * Should be accompanied by a {@link releaseLock()} call after the logic requiring the lock has completed.
637
	 * Can be called multiple times, in which case locks "stack" (PostgreSQL, SQL Server),
638
	 * or auto-releases the previous lock (MySQL).
639
	 *
640
	 * Note that this might trigger the database to wait for the lock to be released, delaying further execution.
641
	 *
642
	 * @param string $name Name of lock
643
	 * @param integer $timeout Timeout in seconds
644
	 * @return boolean
645
	 */
646
	public function getLock($name, $timeout = 5) {
647
		return false;
648
	}
649
650
	/**
651
	 * Remove an application-level lock file to allow another process to run
652
	 * (if the execution aborts (e.g. due to an error) all locks are automatically released).
653
	 *
654
	 * @param string $name Name of the lock
655
	 * @return boolean Flag indicating whether the lock was successfully released
656
	 */
657
	public function releaseLock($name) {
658
		return false;
659
	}
660
661
	/**
662
	 * Instruct the database to generate a live connection
663
	 *
664
	 * @param array $parameters An map of parameters, which should include:
665
	 *  - server: The server, eg, localhost
666
	 *  - username: The username to log on with
667
	 *  - password: The password to log on with
668
	 *  - database: The database to connect to
669
	 *  - charset: The character set to use. Defaults to utf8
670
	 *  - timezone: (optional) The timezone offset. For example: +12:00, "Pacific/Auckland", or "SYSTEM"
671
	 *  - driver: (optional) Driver name
672
	 */
673
	public function connect($parameters) {
674
		// Ensure that driver is available (required by PDO)
675
		if(empty($parameters['driver'])) {
676
			$parameters['driver'] = $this->getDatabaseServer();
677
		}
678
679
		// Notify connector of parameters
680
		$this->connector->connect($parameters);
681
682
		// SS_Database subclass maintains responsibility for selecting database
683
		// once connected in order to correctly handle schema queries about
684
		// existence of database, error handling at the correct level, etc
685
		if (!empty($parameters['database'])) {
686
			$this->selectDatabase($parameters['database'], false, false);
687
		}
688
	}
689
690
	/**
691
	 * Determine if the database with the specified name exists
692
	 *
693
	 * @param string $name Name of the database to check for
694
	 * @return boolean Flag indicating whether this database exists
695
	 */
696
	public function databaseExists($name) {
697
		return $this->schemaManager->databaseExists($name);
698
	}
699
700
	/**
701
	 * Retrieves the list of all databases the user has access to
702
	 *
703
	 * @return array List of database names
704
	 */
705
	public function databaseList() {
706
		return $this->schemaManager->databaseList();
707
	}
708
709
	/**
710
	 * Change the connection to the specified database, optionally creating the
711
	 * database if it doesn't exist in the current schema.
712
	 *
713
	 * @param string $name Name of the database
714
	 * @param boolean $create Flag indicating whether the database should be created
715
	 * if it doesn't exist. If $create is false and the database doesn't exist
716
	 * then an error will be raised
717
	 * @param int|boolean $errorLevel The level of error reporting to enable for the query, or false if no error
718
	 * should be raised
719
	 * @return boolean Flag indicating success
720
	 */
721
	public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR) {
722
		// In case our live environment is locked down, we can bypass a SHOW DATABASE check
723
		$canConnect = Config::inst()->get(get_class($this), 'optimistic_connect')
724
			|| $this->schemaManager->databaseExists($name);
725
		if($canConnect) {
726
			return $this->connector->selectDatabase($name);
727
		}
728
729
		// Check DB creation permisson
730
		if (!$create) {
731
			if ($errorLevel !== false) {
732
				user_error("Attempted to connect to non-existing database \"$name\"", $errorLevel);
733
			}
734
			// Unselect database
735
			$this->connector->unloadDatabase();
736
			return false;
737
		}
738
		$this->schemaManager->createDatabase($name);
739
		return $this->connector->selectDatabase($name);
740
	}
741
742
	/**
743
	 * Drop the database that this object is currently connected to.
744
	 * Use with caution.
745
	 */
746
	public function dropSelectedDatabase() {
747
		$databaseName = $this->connector->getSelectedDatabase();
748
		if ($databaseName) {
749
			$this->connector->unloadDatabase();
750
			$this->schemaManager->dropDatabase($databaseName);
751
		}
752
	}
753
754
	/**
755
	 * Returns the name of the currently selected database
756
	 *
757
	 * @return string|null Name of the selected database, or null if none selected
758
	 */
759
	public function getSelectedDatabase() {
760
		return $this->connector->getSelectedDatabase();
761
	}
762
763
	/**
764
	 * Return SQL expression used to represent the current date/time
765
	 *
766
	 * @return string Expression for the current date/time
767
	 */
768
	abstract public function now();
769
770
	/**
771
	 * Returns the database-specific version of the random() function
772
	 *
773
	 * @return string Expression for a random value
774
	 */
775
	abstract public function random();
776
}
777