Passed
Push — 4.2 ( 7d90a1...df2576 )
by Maxime
06:52
created

Database::transactionDepth()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
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;
0 ignored issues
show
introduced by
The private property $optimistic_connect is not used, and could be removed.
Loading history...
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,
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
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. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

628
    public function supportsExtensions(/** @scrutinizer ignore-unused */ $extensions)

This check looks for 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
     * @return bool|null Boolean is returned if success state is known, or null if
658
     * unknown. Note: For error checking purposes null should not be treated as error.
659
     */
660
    abstract public function transactionRollback($savepoint = false);
661
662
    /**
663
     * Commit everything inside this transaction so far
664
     *
665
     * @param bool $chain
666
     * @return bool|null Boolean is returned if success state is known, or null if
667
     * unknown. Note: For error checking purposes null should not be treated as error.
668
     */
669
    abstract public function transactionEnd($chain = false);
670
671
    /**
672
     * Return depth of current transaction
673
     *
674
     * @return int Nesting level, or 0 if not in a transaction
675
     */
676
    public function transactionDepth()
677
    {
678
        // Placeholder error for transactional DBs that don't expose depth
679
        if (!$this->supportsTransactions()) {
680
            user_error(get_class($this) . " does not support transactionDepth", E_USER_WARNING);
681
        }
682
        return 0;
683
    }
684
685
    /**
686
     * Determines if the used database supports application-level locks,
687
     * which is different from table- or row-level locking.
688
     * See {@link getLock()} for details.
689
     *
690
     * @return bool Flag indicating that locking is available
691
     */
692
    public function supportsLocks()
693
    {
694
        return false;
695
    }
696
697
    /**
698
     * Returns if the lock is available.
699
     * See {@link supportsLocks()} to check if locking is generally supported.
700
     *
701
     * @param string $name Name of the lock
702
     * @return bool
703
     */
704
    public function canLock($name)
705
    {
706
        return false;
707
    }
708
709
    /**
710
     * Sets an application-level lock so that no two processes can run at the same time,
711
     * also called a "cooperative advisory lock".
712
     *
713
     * Return FALSE if acquiring the lock fails; otherwise return TRUE, if lock was acquired successfully.
714
     * Lock is automatically released if connection to the database is broken (either normally or abnormally),
715
     * making it less prone to deadlocks than session- or file-based locks.
716
     * Should be accompanied by a {@link releaseLock()} call after the logic requiring the lock has completed.
717
     * Can be called multiple times, in which case locks "stack" (PostgreSQL, SQL Server),
718
     * or auto-releases the previous lock (MySQL).
719
     *
720
     * Note that this might trigger the database to wait for the lock to be released, delaying further execution.
721
     *
722
     * @param string $name Name of lock
723
     * @param integer $timeout Timeout in seconds
724
     * @return bool
725
     */
726
    public function getLock($name, $timeout = 5)
0 ignored issues
show
Unused Code introduced by
The parameter $timeout is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

726
    public function getLock($name, /** @scrutinizer ignore-unused */ $timeout = 5)

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

Loading history...
727
    {
728
        return false;
729
    }
730
731
    /**
732
     * Remove an application-level lock file to allow another process to run
733
     * (if the execution aborts (e.g. due to an error) all locks are automatically released).
734
     *
735
     * @param string $name Name of the lock
736
     * @return bool Flag indicating whether the lock was successfully released
737
     */
738
    public function releaseLock($name)
739
    {
740
        return false;
741
    }
742
743
    /**
744
     * Instruct the database to generate a live connection
745
     *
746
     * @param array $parameters An map of parameters, which should include:
747
     *  - server: The server, eg, localhost
748
     *  - username: The username to log on with
749
     *  - password: The password to log on with
750
     *  - database: The database to connect to
751
     *  - charset: The character set to use. Defaults to utf8
752
     *  - timezone: (optional) The timezone offset. For example: +12:00, "Pacific/Auckland", or "SYSTEM"
753
     *  - driver: (optional) Driver name
754
     */
755
    public function connect($parameters)
756
    {
757
        // Ensure that driver is available (required by PDO)
758
        if (empty($parameters['driver'])) {
759
            $parameters['driver'] = $this->getDatabaseServer();
760
        }
761
762
        // Notify connector of parameters
763
        $this->connector->connect($parameters);
764
765
        // SS_Database subclass maintains responsibility for selecting database
766
        // once connected in order to correctly handle schema queries about
767
        // existence of database, error handling at the correct level, etc
768
        if (!empty($parameters['database'])) {
769
            $this->selectDatabase($parameters['database'], false, false);
770
        }
771
    }
772
773
    /**
774
     * Determine if the database with the specified name exists
775
     *
776
     * @param string $name Name of the database to check for
777
     * @return bool Flag indicating whether this database exists
778
     */
779
    public function databaseExists($name)
780
    {
781
        return $this->schemaManager->databaseExists($name);
782
    }
783
784
    /**
785
     * Retrieves the list of all databases the user has access to
786
     *
787
     * @return array List of database names
788
     */
789
    public function databaseList()
790
    {
791
        return $this->schemaManager->databaseList();
792
    }
793
794
    /**
795
     * Change the connection to the specified database, optionally creating the
796
     * database if it doesn't exist in the current schema.
797
     *
798
     * @param string $name Name of the database
799
     * @param bool $create Flag indicating whether the database should be created
800
     * if it doesn't exist. If $create is false and the database doesn't exist
801
     * then an error will be raised
802
     * @param int|bool $errorLevel The level of error reporting to enable for the query, or false if no error
803
     * should be raised
804
     * @return bool Flag indicating success
805
     */
806
    public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR)
807
    {
808
        // In case our live environment is locked down, we can bypass a SHOW DATABASE check
809
        $canConnect = Config::inst()->get(static::class, 'optimistic_connect')
810
            || $this->schemaManager->databaseExists($name);
811
        if ($canConnect) {
812
            return $this->connector->selectDatabase($name);
813
        }
814
815
        // Check DB creation permisson
816
        if (!$create) {
817
            if ($errorLevel !== false) {
818
                user_error("Attempted to connect to non-existing database \"$name\"", $errorLevel);
0 ignored issues
show
Bug introduced by
It seems like $errorLevel can also be of type true; however, parameter $error_type of user_error() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

818
                user_error("Attempted to connect to non-existing database \"$name\"", /** @scrutinizer ignore-type */ $errorLevel);
Loading history...
819
            }
820
            // Unselect database
821
            $this->connector->unloadDatabase();
822
            return false;
823
        }
824
        $this->schemaManager->createDatabase($name);
825
        return $this->connector->selectDatabase($name);
826
    }
827
828
    /**
829
     * Drop the database that this object is currently connected to.
830
     * Use with caution.
831
     */
832
    public function dropSelectedDatabase()
833
    {
834
        $databaseName = $this->connector->getSelectedDatabase();
835
        if ($databaseName) {
836
            $this->connector->unloadDatabase();
837
            $this->schemaManager->dropDatabase($databaseName);
838
        }
839
    }
840
841
    /**
842
     * Returns the name of the currently selected database
843
     *
844
     * @return string|null Name of the selected database, or null if none selected
845
     */
846
    public function getSelectedDatabase()
847
    {
848
        return $this->connector->getSelectedDatabase();
849
    }
850
851
    /**
852
     * Return SQL expression used to represent the current date/time
853
     *
854
     * @return string Expression for the current date/time
855
     */
856
    abstract public function now();
857
858
    /**
859
     * Returns the database-specific version of the random() function
860
     *
861
     * @return string Expression for a random value
862
     */
863
    abstract public function random();
864
}
865