Completed
Push — master ( 1bcd75...700cf9 )
by Hamish
11:32
created

SS_Database::withTransaction()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 25
rs 5.3846
cc 8
eloc 17
nc 13
nop 4
1
<?php
2
3
/**
4
 * Abstract database connectivity class.
5
 * Sub-classes of this implement the actual database connection libraries
6
 *
7
 * @package framework
8
 * @subpackage model
9
 */
10
abstract class SS_Database {
11
12
	/**
13
	 * Database connector object
14
	 *
15
	 * @var DBConnector
16
	 */
17
	protected $connector = null;
18
19
	/**
20
	 * Get the current connector
21
	 *
22
	 * @return DBConnector
23
	 */
24
	public function getConnector() {
25
		return $this->connector;
26
	}
27
28
	/**
29
	 * Injector injection point for connector dependency
30
	 *
31
	 * @param DBConnector $connector
32
	 */
33
	public function setConnector(DBConnector $connector) {
34
		$this->connector = $connector;
35
	}
36
37
	/**
38
	 * Database schema manager object
39
	 *
40
	 * @var DBSchemaManager
41
	 */
42
	protected $schemaManager = null;
43
44
	/**
45
	 * Returns the current schema manager
46
	 *
47
	 * @return DBSchemaManager
48
	 */
49
	public function getSchemaManager() {
50
		return $this->schemaManager;
51
	}
52
53
	/**
54
	 * Injector injection point for schema manager
55
	 *
56
	 * @param DBSchemaManager $schemaManager
57
	 */
58
	public function setSchemaManager(DBSchemaManager $schemaManager) {
59
		$this->schemaManager = $schemaManager;
60
61
		if ($this->schemaManager) {
62
			$this->schemaManager->setDatabase($this);
63
		}
64
	}
65
66
	/**
67
	 * Query builder object
68
	 *
69
	 * @var DBQueryBuilder
70
	 */
71
	protected $queryBuilder = null;
72
73
	/**
74
	 * Returns the current query builder
75
	 *
76
	 * @return DBQueryBuilder
77
	 */
78
	public function getQueryBuilder() {
79
		return $this->queryBuilder;
80
	}
81
82
	/**
83
	 * Injector injection point for schema manager
84
	 *
85
	 * @param DBQueryBuilder $queryBuilder
86
	 */
87
	public function setQueryBuilder(DBQueryBuilder $queryBuilder) {
88
		$this->queryBuilder = $queryBuilder;
89
	}
90
91
	/**
92
	 * Execute the given SQL query.
93
	 *
94
	 * @param string $sql The SQL query to execute
95
	 * @param int $errorLevel The level of error reporting to enable for the query
96
	 * @return SS_Query
97
	 */
98
	public function query($sql, $errorLevel = E_USER_ERROR) {
99
		// Check if we should only preview this query
100
		if ($this->previewWrite($sql)) {
101
			return;
102
		}
103
104
		// Benchmark query
105
		$connector = $this->connector;
106
		return $this->benchmarkQuery(
107
			$sql,
108
			function($sql) use($connector, $errorLevel) {
109
				return $connector->query($sql, $errorLevel);
110
			}
111
		);
112
	}
113
114
115
	/**
116
	 * Execute the given SQL parameterised query with the specified arguments
117
	 *
118
	 * @param string $sql The SQL query to execute. The ? character will denote parameters.
119
	 * @param array $parameters An ordered list of arguments.
120
	 * @param int $errorLevel The level of error reporting to enable for the query
121
	 * @return SS_Query
122
	 */
123
	public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
124
		// Check if we should only preview this query
125
		if ($this->previewWrite($sql)) {
126
			return;
127
		}
128
129
		// Benchmark query
130
		$connector = $this->connector;
131
		return $this->benchmarkQuery(
132
			$sql,
133
			function($sql) use($connector, $parameters, $errorLevel) {
134
				return $connector->preparedQuery($sql, $parameters, $errorLevel);
135
			},
136
			$parameters
137
		);
138
	}
139
140
	/**
141
	 * Determines if the query should be previewed, and thus interrupted silently.
142
	 * If so, this function also displays the query via the debuging system.
143
	 * Subclasess should respect the results of this call for each query, and not
144
	 * execute any queries that generate a true response.
145
	 *
146
	 * @param string $sql The query to be executed
147
	 * @return boolean Flag indicating that the query was previewed
148
	 */
149
	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...
150
		// Only preview if previewWrite is set, we are in dev mode, and
151
		// the query is mutable
152
		if (isset($_REQUEST['previewwrite'])
153
			&& Director::isDev()
154
			&& $this->connector->isQueryMutable($sql)
155
		) {
156
			// output preview message
157
			Debug::message("Will execute: $sql");
158
			return true;
159
		} else {
160
			return false;
161
		}
162
	}
163
164
	/**
165
	 * Allows the display and benchmarking of queries as they are being run
166
	 *
167
	 * @param string $sql Query to run, and single parameter to callback
168
	 * @param callable $callback Callback to execute code
169
	 * @param array $parameters Parameters to display
170
	 * @return mixed Result of query
171
	 */
172
	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...
173
		if (isset($_REQUEST['showqueries']) && Director::isDev()) {
174
			$starttime = microtime(true);
175
			$result = $callback($sql);
176
			$endtime = round(microtime(true) - $starttime, 4);
177
			$message = $sql;
178
			if($parameters) {
179
				$message .= "\nparams: \"" . implode('", "', $parameters) . '"';
180
			}
181
			Debug::message("\n{$message}\n{$endtime}s\n", false);
182
183
			return $result;
184
		} else {
185
			return $callback($sql);
186
		}
187
	}
188
189
	/**
190
	 * Get the autogenerated ID from the previous INSERT query.
191
	 *
192
	 * @param string $table The name of the table to get the generated ID for
193
	 * @return integer the most recently generated ID for the specified table
194
	 */
195
	public function getGeneratedID($table) {
196
		return $this->connector->getGeneratedID($table);
197
	}
198
199
	/**
200
	 * Determines if we are connected to a server AND have a valid database
201
	 * selected.
202
	 *
203
	 * @return boolean Flag indicating that a valid database is connected
204
	 */
205
	public function isActive() {
206
		return $this->connector->isActive();
207
	}
208
209
	/**
210
	 * Returns an escaped string. This string won't be quoted, so would be suitable
211
	 * for appending to other quoted strings.
212
	 *
213
	 * @param mixed $value Value to be prepared for database query
214
	 * @return string Prepared string
215
	 */
216
	public function escapeString($value) {
217
		return $this->connector->escapeString($value);
218
	}
219
220
	/**
221
	 * Wrap a string into DB-specific quotes.
222
	 *
223
	 * @param mixed $value Value to be prepared for database query
224
	 * @return string Prepared string
225
	 */
226
	public function quoteString($value) {
227
		return $this->connector->quoteString($value);
228
	}
229
230
	/**
231
	 * Escapes an identifier (table / database name). Typically the value
232
	 * is simply double quoted. Don't pass in already escaped identifiers in,
233
	 * as this will double escape the value!
234
	 *
235
	 * @param string $value The identifier to escape
236
	 * @param string $separator optional identifier splitter
237
	 */
238
	public function escapeIdentifier($value, $separator = '.') {
239
		return $this->connector->escapeIdentifier($value, $separator);
240
	}
241
242
	/**
243
	 * Escapes unquoted columns keys in an associative array
244
	 *
245
	 * @param array $fieldValues
246
	 * @return array List of field values with the keys as escaped column names
247
	 */
248
	protected function escapeColumnKeys($fieldValues) {
249
		$out = array();
250
		foreach($fieldValues as $field => $value) {
251
			$out[$this->escapeIdentifier($field)] = $value;
252
		}
253
		return $out;
254
	}
255
256
	/**
257
	 * Execute a complex manipulation on the database.
258
	 * A manipulation is an array of insert / or update sequences.  The keys of the array are table names,
259
	 * and the values are map containing 'command' and 'fields'.  Command should be 'insert' or 'update',
260
	 * and fields should be a map of field names to field values, NOT including quotes.
261
	 *
262
	 * The field values could also be in paramaterised format, such as
263
	 * array('MAX(?,?)' => array(42, 69)), allowing the use of raw SQL values such as
264
	 * array('NOW()' => array()).
265
	 *
266
	 * @see SQLWriteExpression::addAssignments for syntax examples
267
	 *
268
	 * @param array $manipulation
269
	 */
270
	public function manipulate($manipulation) {
271
		if (empty($manipulation)) return;
272
273
		foreach ($manipulation as $table => $writeInfo) {
274
			if(empty($writeInfo['fields'])) continue;
275
			// Note: keys of $fieldValues are not escaped
276
			$fieldValues = $writeInfo['fields'];
277
278
			// Switch command type
279
			switch ($writeInfo['command']) {
280
				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...
281
282
					// Build update
283
					$query = new SQLUpdate("\"$table\"", $this->escapeColumnKeys($fieldValues));
284
285
					// Set best condition to use
286
					if(!empty($writeInfo['where'])) {
287
						$query->addWhere($writeInfo['where']);
288
					} elseif(!empty($writeInfo['id'])) {
289
						$query->addWhere(array('"ID"' => $writeInfo['id']));
290
					}
291
292
					// Test to see if this update query shouldn't, in fact, be an insert
293
					if($query->toSelect()->count()) {
294
						$query->execute();
295
						break;
296
					}
297
					// ...if not, we'll skip on to the insert code
298
299
				case "insert":
300
					// Ensure that the ID clause is given if possible
301
					if (!isset($fieldValues['ID']) && isset($writeInfo['id'])) {
302
						$fieldValues['ID'] = $writeInfo['id'];
303
					}
304
305
					// Build insert
306
					$query = new SQLInsert("\"$table\"", $this->escapeColumnKeys($fieldValues));
307
308
					$query->execute();
309
					break;
310
311
				default:
312
					user_error("SS_Database::manipulate() Can't recognise command '{$writeInfo['command']}'",
313
								E_USER_ERROR);
314
			}
315
		}
316
	}
317
318
	/**
319
	 * Enable supression of database messages.
320
	 */
321
	public function quiet() {
322
		$this->schemaManager->quiet();
323
	}
324
325
	/**
326
	 * Clear all data out of the database
327
	 */
328
	public function clearAllData() {
329
		$tables = $this->getSchemaManager()->tableList();
330
		foreach ($tables as $table) {
331
			$this->clearTable($table);
332
		}
333
	}
334
335
	/**
336
	 * Clear all data in a given table
337
	 *
338
	 * @param string $table Name of table
339
	 */
340
	public function clearTable($table) {
341
		$this->query("TRUNCATE \"$table\"");
342
	}
343
344
	/**
345
	 * Generates a WHERE clause for null comparison check
346
	 *
347
	 * @param string $field Quoted field name
348
	 * @param bool $isNull Whether to check for NULL or NOT NULL
349
	 * @return string Non-parameterised null comparison clause
350
	 */
351
	public function nullCheckClause($field, $isNull) {
352
		$clause = $isNull
353
			? "%s IS NULL"
354
			: "%s IS NOT NULL";
355
		return sprintf($clause, $field);
356
	}
357
358
	/**
359
	 * Generate a WHERE clause for text matching.
360
	 *
361
	 * @param String $field Quoted field name
362
	 * @param String $value Escaped search. Can include percentage wildcards.
363
	 * Ignored if $parameterised is true.
364
	 * @param boolean $exact Exact matches or wildcard support.
365
	 * @param boolean $negate Negate the clause.
366
	 * @param boolean $caseSensitive Enforce case sensitivity if TRUE or FALSE.
367
	 * Fallback to default collation if set to NULL.
368
	 * @param boolean $parameterised Insert the ? placeholder rather than the
369
	 * given value. If this is true then $value is ignored.
370
	 * @return String SQL
371
	 */
372
	abstract public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null,
373
											$parameterised = false);
374
375
	/**
376
	 * function to return an SQL datetime expression that can be used with the adapter in use
377
	 * used for querying a datetime in a certain format
378
	 *
379
	 * @param string $date to be formated, can be either 'now', literal datetime like '1973-10-14 10:30:00' or
380
	 *                     field name, e.g. '"SiteTree"."Created"'
381
	 * @param string $format to be used, supported specifiers:
382
	 * %Y = Year (four digits)
383
	 * %m = Month (01..12)
384
	 * %d = Day (01..31)
385
	 * %H = Hour (00..23)
386
	 * %i = Minutes (00..59)
387
	 * %s = Seconds (00..59)
388
	 * %U = unix timestamp, can only be used on it's own
389
	 * @return string SQL datetime expression to query for a formatted datetime
390
	 */
391
	abstract public function formattedDatetimeClause($date, $format);
392
393
	/**
394
	 * function to return an SQL datetime expression that can be used with the adapter in use
395
	 * used for querying a datetime addition
396
	 *
397
	 * @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...
398
	 *                      e.g. '"SiteTree"."Created"'
399
	 * @param string $interval to be added, use the format [sign][integer] [qualifier], e.g. -1 Day, +15 minutes,
400
	 *                         +1 YEAR
401
	 * supported qualifiers:
402
	 * - years
403
	 * - months
404
	 * - days
405
	 * - hours
406
	 * - minutes
407
	 * - seconds
408
	 * This includes the singular forms as well
409
	 * @return string SQL datetime expression to query for a datetime (YYYY-MM-DD hh:mm:ss) which is the result of
410
	 *                the addition
411
	 */
412
	abstract public function datetimeIntervalClause($date, $interval);
413
414
	/**
415
	 * function to return an SQL datetime expression that can be used with the adapter in use
416
	 * used for querying a datetime substraction
417
	 *
418
	 * @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...
419
	 *                       e.g. '"SiteTree"."Created"'
420
	 * @param string $date2 to be substracted of $date1, can be either 'now', literal datetime
421
	 *                      like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
422
	 * @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which
423
	 *                is the result of the substraction
424
	 */
425
	abstract public function datetimeDifferenceClause($date1, $date2);
426
427
	/**
428
	 * Returns true if this database supports collations
429
	 *
430
	 * @return boolean
431
	 */
432
	abstract public function supportsCollations();
433
434
	/**
435
	 * Can the database override timezone as a connection setting,
436
	 * or does it use the system timezone exclusively?
437
	 *
438
	 * @return Boolean
439
	 */
440
	abstract public function supportsTimezoneOverride();
441
442
	/**
443
	 * Query for the version of the currently connected database
444
	 * @return string Version of this database
445
	 */
446
	public function getVersion() {
447
		return $this->connector->getVersion();
448
	}
449
450
	/**
451
	 * Get the database server type (e.g. mysql, postgresql).
452
	 * This value is passed to the connector as the 'driver' argument when
453
	 * initiating a database connection
454
	 *
455
	 * @return string
456
	 */
457
	abstract public function getDatabaseServer();
458
459
	/**
460
	 * Return the number of rows affected by the previous operation.
461
	 * @return int
462
	 */
463
	public function affectedRows() {
464
		return $this->connector->affectedRows();
465
	}
466
467
	/**
468
	 * The core search engine, used by this class and its subclasses to do fun stuff.
469
	 * Searches both SiteTree and File.
470
	 *
471
	 * @param array $classesToSearch List of classes to search
472
	 * @param string $keywords Keywords as a string.
473
	 * @param integer $start Item to start returning results from
474
	 * @param integer $pageLength Number of items per page
475
	 * @param string $sortBy Sort order expression
476
	 * @param string $extraFilter Additional filter
477
	 * @param boolean $booleanSearch Flag for boolean search mode
478
	 * @param string $alternativeFileFilter
479
	 * @param boolean $invertedMatch
480
	 * @return PaginatedList Search results
481
	 */
482
	abstract public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC",
483
		$extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false);
484
485
	/**
486
	 * Determines if this database supports transactions
487
	 *
488
	 * @return boolean Flag indicating support for transactions
489
	 */
490
	abstract public function supportsTransactions();
491
492
	/**
493
	 * Invoke $callback within a transaction
494
	 *
495
	 * @param callable $callback Callback to run
496
	 * @param callable $errorCallback Optional callback to run after rolling back transaction.
497
	 * @param bool|string $transactionMode Optional transaction mode to use
498
	 * @param bool $errorIfTransactionsUnsupported If true, this method will fail if transactions are unsupported.
499
	 * Otherwise, the $callback will potentially be invoked outside of a transaction.
500
	 * @throws Exception
501
	 */
502
	public function withTransaction(
503
		$callback, $errorCallback = null, $transactionMode = false, $errorIfTransactionsUnsupported = false
504
	) {
505
		$supported = $this->supportsTransactions();
506
		if(!$supported && $errorIfTransactionsUnsupported) {
507
			throw new BadMethodCallException("Transactions not supported by this database.");
508
		}
509
		if($supported) {
510
			$this->transactionStart($transactionMode);
511
		}
512
		try {
513
			call_user_func($callback);
514
		} catch (Exception $ex) {
515
			if($supported) {
516
				$this->transactionRollback();
517
			}
518
			if($errorCallback) {
519
				call_user_func($errorCallback);
520
			}
521
			throw $ex;
522
		}
523
		if($supported) {
524
			$this->transactionEnd();
525
		}
526
	}
527
528
	/*
529
	 * Determines if the current database connection supports a given list of extensions
530
	 *
531
	 * @param array $extensions List of extensions to check for support of. The key of this array
532
	 * will be an extension name, and the value the configuration for that extension. This
533
	 * could be one of partitions, tablespaces, or clustering
534
	 * @return boolean Flag indicating support for all of the above
535
	 * @todo Write test cases
536
	 */
537
	protected 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...
538
		return false;
539
	}
540
541
	/**
542
	 * Start a prepared transaction
543
	 * See http://developer.postgresql.org/pgdocs/postgres/sql-set-transaction.html for details on
544
	 * transaction isolation options
545
	 *
546
	 * @param string|boolean $transactionMode Transaction mode, or false to ignore
547
	 * @param string|boolean $sessionCharacteristics Session characteristics, or false to ignore
548
	 */
549
	abstract public function transactionStart($transactionMode = false, $sessionCharacteristics = false);
550
551
	/**
552
	 * Create a savepoint that you can jump back to if you encounter problems
553
	 *
554
	 * @param string $savepoint Name of savepoint
555
	 */
556
	abstract public function transactionSavepoint($savepoint);
557
558
	/**
559
	 * Rollback or revert to a savepoint if your queries encounter problems
560
	 * If you encounter a problem at any point during a transaction, you may
561
	 * need to rollback that particular query, or return to a savepoint
562
	 *
563
	 * @param string|boolean $savepoint Name of savepoint, or leave empty to rollback
564
	 * to last savepoint
565
	 */
566
	abstract public function transactionRollback($savepoint = false);
567
568
	/**
569
	 * Commit everything inside this transaction so far
570
	 *
571
	 * @param boolean $chain
572
	 */
573
	abstract public function transactionEnd($chain = false);
574
575
	/**
576
	 * Determines if the used database supports application-level locks,
577
	 * which is different from table- or row-level locking.
578
	 * See {@link getLock()} for details.
579
	 *
580
	 * @return boolean Flag indicating that locking is available
581
	 */
582
	public function supportsLocks() {
583
		return false;
584
	}
585
586
	/**
587
	 * Returns if the lock is available.
588
	 * See {@link supportsLocks()} to check if locking is generally supported.
589
	 *
590
	 * @param string $name Name of the lock
591
	 * @return boolean
592
	 */
593
	public function canLock($name) {
594
		return false;
595
	}
596
597
	/**
598
	 * Sets an application-level lock so that no two processes can run at the same time,
599
	 * also called a "cooperative advisory lock".
600
	 *
601
	 * Return FALSE if acquiring the lock fails; otherwise return TRUE, if lock was acquired successfully.
602
	 * Lock is automatically released if connection to the database is broken (either normally or abnormally),
603
	 * making it less prone to deadlocks than session- or file-based locks.
604
	 * Should be accompanied by a {@link releaseLock()} call after the logic requiring the lock has completed.
605
	 * Can be called multiple times, in which case locks "stack" (PostgreSQL, SQL Server),
606
	 * or auto-releases the previous lock (MySQL).
607
	 *
608
	 * Note that this might trigger the database to wait for the lock to be released, delaying further execution.
609
	 *
610
	 * @param string $name Name of lock
611
	 * @param integer $timeout Timeout in seconds
612
	 * @return boolean
613
	 */
614
	public function getLock($name, $timeout = 5) {
615
		return false;
616
	}
617
618
	/**
619
	 * Remove an application-level lock file to allow another process to run
620
	 * (if the execution aborts (e.g. due to an error) all locks are automatically released).
621
	 *
622
	 * @param string $name Name of the lock
623
	 * @return boolean Flag indicating whether the lock was successfully released
624
	 */
625
	public function releaseLock($name) {
626
		return false;
627
	}
628
629
	/**
630
	 * Instruct the database to generate a live connection
631
	 *
632
	 * @param array $parameters An map of parameters, which should include:
633
	 *  - server: The server, eg, localhost
634
	 *  - username: The username to log on with
635
	 *  - password: The password to log on with
636
	 *  - database: The database to connect to
637
	 *  - charset: The character set to use. Defaults to utf8
638
	 *  - timezone: (optional) The timezone offset. For example: +12:00, "Pacific/Auckland", or "SYSTEM"
639
	 *  - driver: (optional) Driver name
640
	 */
641
	public function connect($parameters) {
642
		// Ensure that driver is available (required by PDO)
643
		if(empty($parameters['driver'])) {
644
			$parameters['driver'] = $this->getDatabaseServer();
645
		}
646
647
		// Notify connector of parameters
648
		$this->connector->connect($parameters);
649
650
		// SS_Database subclass maintains responsibility for selecting database
651
		// once connected in order to correctly handle schema queries about
652
		// existence of database, error handling at the correct level, etc
653
		if (!empty($parameters['database'])) {
654
			$this->selectDatabase($parameters['database'], false, false);
655
		}
656
	}
657
658
	/**
659
	 * Determine if the database with the specified name exists
660
	 *
661
	 * @param string $name Name of the database to check for
662
	 * @return boolean Flag indicating whether this database exists
663
	 */
664
	public function databaseExists($name) {
665
		return $this->schemaManager->databaseExists($name);
666
	}
667
668
	/**
669
	 * Retrieves the list of all databases the user has access to
670
	 *
671
	 * @return array List of database names
672
	 */
673
	public function databaseList() {
674
		return $this->schemaManager->databaseList();
675
	}
676
677
	/**
678
	 * Change the connection to the specified database, optionally creating the
679
	 * database if it doesn't exist in the current schema.
680
	 *
681
	 * @param string $name Name of the database
682
	 * @param boolean $create Flag indicating whether the database should be created
683
	 * if it doesn't exist. If $create is false and the database doesn't exist
684
	 * then an error will be raised
685
	 * @param int|boolean $errorLevel The level of error reporting to enable for the query, or false if no error
686
	 * should be raised
687
	 * @return boolean Flag indicating success
688
	 */
689
	public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR) {
690
		if (!$this->schemaManager->databaseExists($name)) {
691
			// Check DB creation permisson
692
			if (!$create) {
693
				if ($errorLevel !== false) {
694
					user_error("Attempted to connect to non-existing database \"$name\"", $errorLevel);
695
				}
696
				// Unselect database
697
				$this->connector->unloadDatabase();
698
				return false;
699
			}
700
			$this->schemaManager->createDatabase($name);
701
		}
702
		return $this->connector->selectDatabase($name);
703
	}
704
705
	/**
706
	 * Drop the database that this object is currently connected to.
707
	 * Use with caution.
708
	 */
709
	public function dropSelectedDatabase() {
710
		$databaseName = $this->connector->getSelectedDatabase();
711
		if ($databaseName) {
712
			$this->connector->unloadDatabase();
713
			$this->schemaManager->dropDatabase($databaseName);
714
		}
715
	}
716
717
	/**
718
	 * Returns the name of the currently selected database
719
	 *
720
	 * @return string|null Name of the selected database, or null if none selected
721
	 */
722
	public function getSelectedDatabase() {
723
		return $this->connector->getSelectedDatabase();
724
	}
725
726
	/**
727
	 * Return SQL expression used to represent the current date/time
728
	 *
729
	 * @return string Expression for the current date/time
730
	 */
731
	abstract public function now();
732
733
	/**
734
	 * Returns the database-specific version of the random() function
735
	 *
736
	 * @return string Expression for a random value
737
	 */
738
	abstract public function random();
739
740
	/**
741
	 * @deprecated since version 4.0 Use DB::get_schema()->dbDataType($type) instead
742
	 */
743
	public function dbDataType($type){
744
		Deprecation::notice('4.0', 'Use DB::get_schema()->dbDataType($type) instead');
745
		return $this->getSchemaManager()->dbDataType($type);
746
	}
747
748
	/**
749
	 * @deprecated since version 4.0 Use selectDatabase('dbname', true) instead
750
	 */
751
	public function createDatabase() {
752
		Deprecation::notice('4.0', 'Use selectDatabase(\'dbname\',true) instead');
753
		$database = $this->connector->getSelectedDatabase();
754
		$this->selectDatabase($database, true);
755
		return $this->isActive();
756
	}
757
758
	/**
759
	 * @deprecated since version 4.0 SS_Database::getConnect was never implemented and is obsolete
760
	 */
761
	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...
762
		Deprecation::notice('4.0', 'SS_Database::getConnect was never implemented and is obsolete');
763
	}
764
765
	/**
766
	 * @deprecated since version 4.0 Use Convert::raw2sql($string, true) instead
767
	 */
768
	public function prepStringForDB($string) {
769
		Deprecation::notice('4.0', 'Use Convert::raw2sql($string, true) instead');
770
		return $this->quoteString($string);
771
	}
772
773
	/**
774
	 * @deprecated since version 4.0 Use dropSelectedDatabase instead
775
	 */
776
	public function dropDatabase() {
777
		Deprecation::notice('4.0', 'Use dropSelectedDatabase instead');
778
		$this->dropSelectedDatabase();
779
	}
780
781
	/**
782
	 * @deprecated since version 4.0 Use databaseList instead
783
	 */
784
	public function allDatabaseNames() {
785
		Deprecation::notice('4.0', 'Use databaseList instead');
786
		return $this->databaseList();
787
	}
788
789
	/**
790
	 * @deprecated since version 4.0 Use DB::create_table instead
791
	 */
792
	public function createTable($table, $fields = null, $indexes = null, $options = null, $advancedOptions = null) {
793
		Deprecation::notice('4.0', 'Use DB::create_table instead');
794
		return $this->getSchemaManager()->createTable($table, $fields, $indexes, $options, $advancedOptions);
795
	}
796
797
	/**
798
	 * @deprecated since version 4.0 Use DB::get_schema()->alterTable() instead
799
	 */
800
	public function alterTable($table, $newFields = null, $newIndexes = null,
801
		$alteredFields = null, $alteredIndexes = null, $alteredOptions = null,
802
		$advancedOptions = null
803
	) {
804
		Deprecation::notice('4.0', 'Use DB::get_schema()->alterTable() instead');
805
		return $this->getSchemaManager()->alterTable(
806
			$table, $newFields, $newIndexes, $alteredFields,
807
			$alteredIndexes, $alteredOptions, $advancedOptions
808
		);
809
	}
810
811
	/**
812
	 * @deprecated since version 4.0 Use DB::get_schema()->renameTable() instead
813
	 */
814
	public function renameTable($oldTableName, $newTableName) {
815
		Deprecation::notice('4.0', 'Use DB::get_schema()->renameTable() instead');
816
		$this->getSchemaManager()->renameTable($oldTableName, $newTableName);
817
	}
818
819
	/**
820
	 * @deprecated since version 4.0 Use DB::create_field() instead
821
	 */
822
	public function createField($table, $field, $spec) {
823
		Deprecation::notice('4.0', 'Use DB::create_field() instead');
824
		$this->getSchemaManager()->createField($table, $field, $spec);
825
	}
826
827
	/**
828
	 * @deprecated since version 4.0 Use DB::get_schema()->renameField() instead
829
	 */
830
	public function renameField($tableName, $oldName, $newName) {
831
		Deprecation::notice('4.0', 'Use DB::get_schema()->renameField() instead');
832
		$this->getSchemaManager()->renameField($tableName, $oldName, $newName);
833
	}
834
835
	/**
836
	 * @deprecated since version 4.0 Use getSelectedDatabase instead
837
	 */
838
	public function currentDatabase() {
839
		Deprecation::notice('4.0', 'Use getSelectedDatabase instead');
840
		return $this->getSelectedDatabase();
841
	}
842
843
	/**
844
	 * @deprecated since version 4.0 Use DB::field_list instead
845
	 */
846
	public function fieldList($table) {
847
		Deprecation::notice('4.0', 'Use DB::field_list instead');
848
		return $this->getSchemaManager()->fieldList($table);
849
	}
850
851
	/**
852
	 * @deprecated since version 4.0 Use DB::table_list instead
853
	 */
854
	public function tableList() {
855
		Deprecation::notice('4.0', 'Use DB::table_list instead');
856
		return $this->getSchemaManager()->tableList();
857
	}
858
859
	/**
860
	 * @deprecated since version 4.0 Use DB::get_schema()->hasTable() instead
861
	 */
862
	public function hasTable($tableName) {
863
		Deprecation::notice('4.0', 'Use DB::get_schema()->hasTable() instead');
864
		return $this->getSchemaManager()->hasTable($tableName);
865
	}
866
867
	/**
868
	 * @deprecated since version 4.0 Use DB::get_schema()->enumValuesForField() instead
869
	 */
870
	public function enumValuesForField($tableName, $fieldName) {
871
		Deprecation::notice('4.0', 'Use DB::get_schema()->enumValuesForField() instead');
872
		return $this->getSchemaManager()->enumValuesForField($tableName, $fieldName);
873
	}
874
875
	/**
876
	 * @deprecated since version 4.0 Use Convert::raw2sql instead
877
	 */
878
	public function addslashes($value) {
879
		Deprecation::notice('4.0', 'Use Convert::raw2sql instead');
880
		return $this->escapeString($value);
881
	}
882
883
	/**
884
	 * @deprecated since version 3.2 Use DB::get_schema()->schemaUpdate with a callback instead
885
	 */
886
	public function beginSchemaUpdate() {
887
		Deprecation::notice('3.2', 'Use DB::get_schema()->schemaUpdate with a callback instead');
888
		// Unable to recover so throw appropriate exception
889
		throw new BadMethodCallException('Use DB::get_schema()->schemaUpdate with a callback instead');
890
	}
891
892
	/**
893
	 * @deprecated since version 3.2 Use DB::get_schema()->schemaUpdate with a callback instead
894
	 */
895
	public function endSchemaUpdate() {
896
		Deprecation::notice('3.2', 'Use DB::get_schema()->schemaUpdate with a callback instead');
897
		// Unable to recover so throw appropriate exception
898
		throw new BadMethodCallException('Use DB::get_schema()->schemaUpdate with a callback instead');
899
	}
900
901
	/**
902
	 * @deprecated since version 4.0 Use DB::get_schema()->cancelSchemaUpdate instead
903
	 */
904
	public function cancelSchemaUpdate() {
905
		Deprecation::notice('4.0', 'Use DB::get_schema()->cancelSchemaUpdate instead');
906
		$this->getSchemaManager()->cancelSchemaUpdate();
907
	}
908
909
	/**
910
	 * @deprecated since version 4.0 Use DB::get_schema()->isSchemaUpdating() instead
911
	 */
912
	public function isSchemaUpdating() {
913
		Deprecation::notice('4.0', 'Use DB::get_schema()->isSchemaUpdating() instead');
914
		return $this->getSchemaManager()->isSchemaUpdating();
915
	}
916
917
	/**
918
	 * @deprecated since version 4.0 Use DB::get_schema()->doesSchemaNeedUpdating() instead
919
	 */
920
	public function doesSchemaNeedUpdating() {
921
		Deprecation::notice('4.0', 'Use DB::get_schema()->doesSchemaNeedUpdating() instead');
922
		return $this->getSchemaManager()->doesSchemaNeedUpdating();
923
	}
924
925
	/**
926
	 * @deprecated since version 4.0 Use DB::get_schema()->transCreateTable() instead
927
	 */
928
	public function transCreateTable($table, $options = null, $advanced_options = null) {
929
		Deprecation::notice('4.0', 'Use DB::get_schema()->transCreateTable() instead');
930
		$this->getSchemaManager()->transCreateTable($table, $options, $advanced_options);
931
	}
932
933
	/**
934
	 * @deprecated since version 4.0 Use DB::get_schema()->transAlterTable() instead
935
	 */
936
	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...
937
		Deprecation::notice('4.0', 'Use DB::get_schema()->transAlterTable() instead');
938
		$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...
939
	}
940
941
	/**
942
	 * @deprecated since version 4.0 Use DB::get_schema()->transCreateField() instead
943
	 */
944
	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...
945
		Deprecation::notice('4.0', 'Use DB::get_schema()->transCreateField() instead');
946
		$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...
947
	}
948
949
	/**
950
	 * @deprecated since version 4.0 Use DB::get_schema()->transCreateIndex() instead
951
	 */
952
	public function transCreateIndex($table, $index, $schema) {
953
		Deprecation::notice('4.0', 'Use DB::get_schema()->transCreateIndex() instead');
954
		$this->getSchemaManager()->transCreateIndex($table, $index, $schema);
955
	}
956
957
	/**
958
	 * @deprecated since version 4.0 Use DB::get_schema()->transAlterField() instead
959
	 */
960
	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...
961
		Deprecation::notice('4.0', 'Use DB::get_schema()->transAlterField() instead');
962
		$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...
963
	}
964
965
	/**
966
	 * @deprecated since version 4.0 Use DB::get_schema()->transAlterIndex() instead
967
	 */
968
	public function transAlterIndex($table, $index, $schema) {
969
		Deprecation::notice('4.0', 'Use DB::get_schema()->transAlterIndex() instead');
970
		$this->getSchemaManager()->transAlterIndex($table, $index, $schema);
971
	}
972
973
	/**
974
	 * @deprecated since version 4.0 Use DB::require_table() instead
975
	 */
976
	public function requireTable($table, $fieldSchema = null, $indexSchema = null,
977
		$hasAutoIncPK = true, $options = array(), $extensions = false
978
	) {
979
		Deprecation::notice('4.0', 'Use DB::require_table() instead');
980
		return $this->getSchemaManager()->requireTable(
981
			$table, $fieldSchema, $indexSchema, $hasAutoIncPK, $options, $extensions
982
		);
983
	}
984
985
	/**
986
	 * @deprecated since version 4.0 Use DB::dont_require_table() instead
987
	 */
988
	public function dontRequireTable($table) {
989
		Deprecation::notice('4.0', 'Use DB::dont_require_table() instead');
990
		$this->getSchemaManager()->dontRequireTable($table);
991
	}
992
993
	/**
994
	 * @deprecated since version 4.0 Use DB::require_index() instead
995
	 */
996
	public function requireIndex($table, $index, $spec) {
997
		Deprecation::notice('4.0', 'Use DB::require_index() instead');
998
		$this->getSchemaManager()->requireIndex($table, $index, $spec);
999
	}
1000
1001
	/**
1002
	 * @deprecated since version 4.0 Use DB::get_schema()->hasField() instead
1003
	 */
1004
	public function hasField($tableName, $fieldName) {
1005
		Deprecation::notice('4.0', 'Use DB::get_schema()->hasField() instead');
1006
		return $this->getSchemaManager()->hasField($tableName, $fieldName);
1007
	}
1008
1009
	/**
1010
	 * @deprecated since version 4.0 Use DB::require_field() instead
1011
	 */
1012
	public function requireField($table, $field, $spec) {
1013
		Deprecation::notice('4.0', 'Use DB::require_field() instead');
1014
		$this->getSchemaManager()->requireField($table, $field, $spec);
1015
	}
1016
1017
	/**
1018
	 * @deprecated since version 4.0 Use DB::dont_require_field() instead
1019
	 */
1020
	public function dontRequireField($table, $fieldName) {
1021
		Deprecation::notice('4.0', 'Use DB::dont_require_field() instead');
1022
		$this->getSchemaManager()->dontRequireField($table, $fieldName);
1023
	}
1024
1025
	/**
1026
	 * @deprecated since version 4.0 Use DB::build_sql() instead
1027
	 */
1028
	public function sqlQueryToString(SQLExpression $query, &$parameters = array()) {
1029
		Deprecation::notice('4.0', 'Use DB::build_sql() instead');
1030
		return $this->getQueryBuilder()->buildSQL($query, $parameters);
1031
	}
1032
}
1033