Completed
Push — authenticator-refactor ( 5fce33...62753b )
by Damian
06:14
created

Database::concatOperator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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